Monday, August 26, 2013

Using Expect in RH Linux - example

Sometimes you need to deliver a file to a set of remote hosts. After the file is delivered you might need to execute a script on the remote host.
Below is a simple example of how to use expect for that.
First create some directories under the directory where you will be testing the script
you will need to create
./config - to hold a file containing the list of remote hosts
./data - the file that you are going to upload goes there
./logs - access logs will be written there
Now create a list of hosts in ./config directory (I called mine target_list) Put actual remote hostnames here
echo host1.mydomain.net:/tmp > ./config/target_list
echo host2.mydomain.net:/var/tmp >> ./config/target_list

The script will also parse target directory for each host - directory comes after the host name delimited with :. (Note, that I am not using these directories in this example)

Create your package file in ./data
I populated my package with the commands below
echo hello > test.log
cat test.log

And now the script itself (run it as ./test package_file target_list )

You will be prompted to enter your username and password for your remote hosts access. You need to enter this information only once. Ignore all other prompts.
Your package file will be delivered to /tmp directory on your remote hosts
#!/usr/bin/expect -f


set packagename       [lrange $argv 0 0]
set targets_list_file [lrange $argv 1 1]

set dir_config ./config
set dir_data   ./data
set dir_log    ./logs
set dir_target /tmp

set myname $argv0
set timeout 20
log_file -noappend -a ${dir_log}/${packagename}_access_log


set prompt "(#|%|\\\]|\\\$|>).*"

##
## If script started without parameters, prompt for correct usage
##
proc usage {myname} {
   send_error "\nIncorrect usage !\n"
   send_error "\nShould run as following :\n"
   send_error "\n  $myname \[Package\] \[hosts\]\n"
   send_error "\n Exiting ...\n"
   send_error "\n"
}

##
## Get username
##
proc getuser {} {
   set timeout -1
   send_user "Login : "
   expect_user -re "(.*)\n"
   send_user "\n"
   set timteout 10
   return $expect_out(1,string)
}

##
## Get password
##
proc getpassword {} {
   set timeout -1
   stty -echo
   send_user "Password : "
   expect_user -re "(.*)\n"
   send_user "\n"
   stty echo
   set timeout 20
   return $expect_out(1,string)
}


##
## If started with correct parameters, run script
##
if { $argc == 2 } {
   puts "Connecting to target hosts and running scripts ..."
   set targets_list [open ${dir_config}/${targets_list_file}] 
   
} else {
   usage $myname ; exit 1
}

##
## Get user and password
##
set user [ getuser ]      
expect *
set password [ getpassword ]
expect *

##
## Loop through the target hosts
##
while { [gets ${targets_list} input] != -1 } {

    set host [lrange [split $input ":"] 0 0]
    set targetdir [lrange [split $input ":"] 1 1]


###
### copy package using scp
###
spawn scp ${dir_data}/${packagename} $user@$host:${dir_target}
match_max 100000

expect {
       -re ".*Are.*.*yes.*no.*" {
       send "yes\r"
       exp_continue
       }

       #
       #look for the password prompt
       #
       "?assword:" {
       send -- "$password\r"
       exec sleep 2
       }
       default {
       continue
       }
}

expect {
         -re   "$prompt"  { 
             send "hostname\r" 
          }
         "?assword:" { 
             send_user "\n\nIncorrect login or password !\n\n\r" ; exit 1 
          }
         default { 
             send_user "\n\nLogin time out !\n\n\r" ; continue 
         }
}
###
### End copy package
###


###
### ssh into remote host 
###

spawn ssh $user@$host
match_max 100000

expect {
       -re ".*Are.*.*yes.*no.*" {
       send "yes\r"
       exp_continue
       #look for the password prompt
       }

       "?assword:" {
           send -- "$password\r"
           exec sleep 2
       }
       default {
           continue
       }
}
expect { 
       -re "$prompt"  { 
           send "hostname\r" 
       }
       "?assword:" { 
           send_user "\n\nIncorrect login or password !\n\n\r" ; exit 1 
       }
       default { 
           send_user "\n\nLogin time out !\n\n\r" ; continue 
       }
}


##
## Give full permissions to the copied file 
##  (\r needed in the end of each command)
##
expect -re "$prompt"
send "chmod 777 ${dir_target}/${packagename}\r"

##
## cd into target directory - ${dir_target} - set as /tmp in this script
##
expect -re "$prompt"
send "cd ${dir_target}\r"

##
## check if the package was delivered to the target directory
##
expect -re "$prompt"
send "ls -lrt ${dir_target}/${packagename}\r"

##
## run the package script
##
expect -re "$prompt"
send "${dir_target}/${packagename} \r"

}
##
## End of loop through hosts
##

###
### exit, close target list file
###
expect -re "$prompt"
send "exit\r"

expect eof

close $targets_list

puts "\n"