I am currently testing a bash script to perform database migration.
The scripts basically accepts some parameters such as:
- the name of the database to migrate
- the server from which migrate it
- the server to which migrate it
In the script there is a function which "builds" the mysql and mysqldump commands to be executed, depending on whether the server from/to is local/remote.
the function build_mysql_command
is then used like this:
_query="$(build_mysql_command mysql from)"
_dump="$(build_mysql_command mysqldump from)"
_restore="$(build_mysql_command mysql to)"
However, when the function build_mysql_command
has to call open_ssh_tunnel
, it hangs on the last instruction, as I have tested by using the script with the -x
switch.
If instead I put the SSH tunnel opening outside build_mysql_command
, and remove the call from there, it works.
However, I do not think I made any mistake in the above functions, so I do not understand why the script would hang.
Here is a very stripped down example which shows the problem, where I replaced the actual IP address of the remote server with 1.2.3.4
:
#!/bin/bash
set -x
set -o pipefail
# $1 = 'from' or 'to'
get_local_port() {
case "$1" in
from)
echo 30303
;;
to)
echo 31313
;;
*)
echo 0
;;
esac
}
# $1 = 'from' or 'to'
build_ssh_command() {
local _ssh="ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no"
if [ ! -z "${params[password-$1]}" ] ; then
_ssh="sshpass -f ${params[password-$1]} $_ssh"
fi
echo "$_ssh"
}
# $1 = 'from' or 'to'
open_ssh_tunnel() {
# se non già aperto
if [ -z "${cleanup[ssh-tunnel-$1]}" ] ; then
local _port="$(get_local_port "$1")"
local _ssh="$(build_ssh_command "$1")"
local _pregp="fnNTL $_port:localhost:3306 ${params[migrate-$1]}"
local _command="$_ssh -$_pregp"
# tento apertura tunnel SSH
if ! $_command ; then
return 1
else
# salvo PID del tunnel così aperto
local _pid="$(pgrep -f "$_pregp" 2> /dev/null)"
if [ -z "$_pid" ] ; then
return 1
fi
cleanup["ssh-tunnel-$1"]="$_pid"
fi
fi
return 0
}
# verifica se un indirizzo fa riferimento alla macchina locale
# $1 = indirizzo da verificare
is_local_address() {
local _host="$(hostname)"
case "$1" in
localhost|"127.0.0.1"|"$_host")
return 0
;;
*)
return 1
;;
esac
}
# costruisce un comando di dump o restore MySQL
# $1 = comando di base
# $2 = tipo server ('from' o 'to')
build_mysql_command() {
local _command="$1 --user=root --password=xxx"
if is_local_address "${params[migrate-$2]}" ; then
# connessione tramite socket
_command="$_command --protocol=socket --socket=/opt/agews64/data/mysql/mysql.sock"
elif open_ssh_tunnel "$2" ; then
# altrimenti uso connessione tramite tunnel SSH
_command="$_command --protocol=tcp --host=localhost --port=$(get_local_port "$2")"
else
_command=""
fi
echo "$_command"
}
# parametri di esecuzione dello script
declare -A params=(
["migrate-from"]="localhost"
["migrate-to"]="1.2.3.4"
)
_query="$(build_mysql_command "mysql" "from")"
echo "_query = $_query"
_dump="$(build_mysql_command "mysqldump" "to")"
echo "_dump = $_dump"
# fine test
and here is the output when run:
+ set -o pipefail
+ params=(["migrate-from"]="localhost" ["migrate-to"]="1.2.3.4")
+ declare -A params
++ build_mysql_command mysql from
++ local '_command=mysql --user=root --password=xxx'
++ is_local_address localhost
+++ hostname
++ local _host=my.host.name
++ case "$1" in
++ return 0
++ _command='mysql --user=root --password=xxx --protocol=socket --socket=/opt/agews64/data/mysql/mysql.sock'
++ echo 'mysql --user=root --password=xxx --protocol=socket --socket=/opt/agews64/data/mysql/mysql.sock'
+ _query='mysql --user=root --password=xxx --protocol=socket --socket=/opt/agews64/data/mysql/mysql.sock'
+ echo '_query = mysql --user=root --password=xxx --protocol=socket --socket=/opt/agews64/data/mysql/mysql.sock'
_query = mysql --user=root --password=xxx --protocol=socket --socket=/opt/agews64/data/mysql/mysql.sock
++ build_mysql_command mysqldump to
++ local '_command=mysqldump --user=root --password=xxx'
++ is_local_address 1.2.3.4
+++ hostname
++ local _host=asp10.626suite-online.it
++ case "$1" in
++ return 1
++ open_ssh_tunnel to
++ '[' -z '' ']'
+++ get_local_port to
+++ case "$1" in
+++ echo 31313
++ local _port=31313
+++ build_ssh_command to
+++ local '_ssh=ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no'
+++ '[' '!' -z '' ']'
+++ echo 'ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no'
++ local '_ssh=ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no'
++ local '_pregp=fnNTL 31313:localhost:3306 1.2.3.4'
++ local '_command=ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no -fnNTL 31313:localhost:3306 1.2.3.4'
++ ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no -fnNTL 31313:localhost:3306 1.2.3.4
Warning: Permanently added '1.2.3.4' (ECDSA) to the list of known hosts.
+++ pgrep -f 'fnNTL 31313:localhost:3306 1.2.3.4'
++ local _pid=8919
++ '[' -z 8919 ']'
++ cleanup["ssh-tunnel-$1"]=8919
++ return 0
+++ get_local_port to
+++ case "$1" in
+++ echo 31313
++ _command='mysqldump --user=root --password=xxx --protocol=tcp --host=localhost --port=31313'
++ echo 'mysqldump --user=root --password=xxx --protocol=tcp --host=localhost --port=31313'
As you can see, the script hangs at the very last line of build_mysql_command
when it has opened the SSH tunnel to the remote server, but shows no problem when it builds the local command.