Hey there,
It's been a while since we took a look at Expect on this blog, so I'm throwing a monster out there today ;)
Today's script is a bit long-in-the-tooth, and may be complicated looking, but it's basically a simple script to enable you to do updates across your network all at once; only having to login once (or twice, but all at the same time ;)
I put a small section in the beginning that checks whether or not a user invoking the script is allowed to use it (although this script doesn't allow anyone to do anything that they don't have the privilege or access and knowledge to do already). It's a simple check against an exec of the logname command, to make sure that the user's logname is among the list of lognames allowed to use the script. This whole section can be removed without affecting the script's functionality. Simply delete the section with the header: "Weed out the undesirables pronto" and it'll no longer be an issue.
I also added separate login functions for Solaris, Linux, HP-UX and SCO Unix for variety (Note that you may need to tweak these Expect routines, in case the regular expression matching doesn't suit your flavor). You may not need all of these functions, and you may only need one. In any case, you can either call the script specifically to use it the way in which you desire, or edit it so that it will only run the way you desire. If you call it with the "-h" flag it should give you more help than you could ever possibly want ;)
Ex:
host # ./update.exp -h
Hope you enjoy this and find it useful. Good Times! :)
This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License#!/usr/local/bin/expect
#########################################################################
# update.exp - do mass updates across network
# without lifing a finger
#
# 2008 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#
# Usage: update.exp <-h> <-d>
# [ +host1 +hostn | (+sco &| +sun &| +hp &| +linux) | +all ]
# [ login ] [ passwd | <command> <args> ]
#########################################################################
#########################################################################
# Weed out the undesirables pronto
#########################################################################
set gauntlet [exec logname]
if { [string compare $gauntlet "user1"] != 0 && [string compare $gauntlet "user2"] != 0 && [string compare $gauntlet "user3"] != 0 } {
puts "Sorry, $gauntlet. You may not use update.exp."
exit
}
#########################################################################
# Go for the help short-circuit
#########################################################################
foreach member $argv {
if { $member == "-h" } {
puts "#############################################################################"
puts "USAGE:"
puts " update.exp <-h> <-d>"
puts " \[ +host1 +hostn | (+sco &| +sun &| +hp &| +linux) | +all \]"
puts " \[ login \] \[ passwd | <command> <args> \]"
puts "#############################################################################"
puts "SCO Hosts:"
puts " sco1 sco2 sco3"
puts "SUN Hosts:"
puts " sun1 sun2 sun3"
puts "HP Hosts:"
puts " hp1 hp2 hp3"
puts "LINUX Hosts:"
puts " linux1 linux2 linux3"
puts "#############################################################################"
puts "FLAGS:"
puts " -h: Show this message."
puts " -d: Force extremely verbose output during operation."
puts " +host\[1-n\]: The plus symbol followed by any vaild hostname."
puts " +sco: Process all sco hosts."
puts " +sun: Process all sun hosts."
puts " +hp: Process all hp hosts."
puts " +linux: Process all linux hosts."
puts " +all: Process all hosts."
puts " login: Your own."
puts " passwd: Change password on specified machines."
puts " command: Any architecture-supported command, with optional arguments."
puts "#############################################################################"
puts "A FEW VALID COMMAND LINE EXPRESSIONS:"
puts " update.exp -h"
puts " \(The -h command invalidates all others\)"
puts " update.exp -d *****"
puts " \(The -d command may be used in conjunction with any other\)"
puts " update.exp +host1 +host2 login command args"
puts " \(Multiple hosts may be named on command line\)"
puts " update.exp +host1 +sun login command args"
puts " \(Specific host may be coupled with groups\)"
puts " update.exp +sun +hp login command"
puts " \(Groups may be combined. Args to command are trivial\)"
puts " update.exp +all login passwd"
puts " \(+all obviates any other hosts named, but may be combined for"
puts " a specific purpose\)"
puts "#############################################################################"
exit
}
}
#########################################################################
# Hide the gory details unless they want blood
#########################################################################
if { [llength $argv] == 0 } {
set debug 0
} else {
foreach entrail $argv {
if { $entrail == "-d" } {
set debug 1
break
} else {
set debug 0
}
}
}
if { $debug } {
log_user 1
} else {
log_user 0
}
#########################################################################
# Generic login procs divided by architecture
#########################################################################
proc sun_login {name tprompt login lpass} {
send_user "$name\n"
set timeout 3
set hostname $name
set prompt $tprompt
set qlogin $login
set qpass $lpass
expect "ogin: " {send "$qlogin\r"}
expect "word: " {send "$qpass\r"}
}
proc linux_login {name tprompt login lpass} {
send_user "$name\n"
set timeout 3
set hostname $name
set prompt $tprompt
set qlogin $login
set qpass $lpass
expect "ogin: " {send "$qlogin\r"}
expect "word: " {send "$qpass\r"}
}
proc hp_login {name tprompt login lpass} {
send_user "$name\n"
set timeout 12
set hostname $name
set prompt $tprompt
set qlogin $login
set qpass $lpass
expect "ogin: " {send "$qlogin\r"}
expect "word:" {send "$qpass\r"}
}
proc sco_login {name tprompt login lpass} {
send_user "$name\n"
set timeout 6
set hostname $name
set prompt $tprompt
set qlogin $login
set qpass $lpass
expect "ogin: " {send "$qlogin\r"}
expect "word:" {send "$qpass\r"}
expect -re $tprompt {send "\r"}
}
#########################################################################
# Command and user passwd procs by architecture
#########################################################################
proc sunhp_q {command arglist tprompt debug login lpass} {
set timeout 5
set log $login
set opass $lpass
set argvec $arglist
set commvec $command
set prompt $tprompt
set db $debug
if { $command == "passwd" } {
set username $log
set oldpass $opass
set newpass $argvec
expect -re $prompt {send "$commvec $username\r"}
expect -re "word: ?$" {send "$oldpass\r"}
expect -re "New password: ?$" {send "$newpass\r"}
expect {
-re "New password: ?$" {
send "\r"
send_user "Password not accepted!\n"
expect -re "New password: ?$" {send "\r"}
expect -re "New password: ?$" {send "\r"}
}
-re "word: ?$" {send "$newpass\r"}
}
expect -re $prompt {send "exit\r"}
} else {
if { $argvec != 0 } {
expect -re $tprompt {send "$commvec $argvec\r"}
} else {
expect -re $tprompt {send "$commvec\r"}
}
log_user 1
expect -re $tprompt {
if { $db } {
log_user 1
} else {
log_user 0
}
send "exit\r"
}
}
}
proc linux_q {command arglist tprompt debug login lpass} {
set timeout 5
set log $login
set opass $lpass
set argvec $arglist
set commvec $command
set prompt $tprompt
set db $debug
if { $command == "passwd" } {
set username $log
set oldpass $opass
set newpass $argvec
expect -re $prompt {send "$commvec\r"}
expect -re "word: ?$" {send "$oldpass\r"}
expect -re "assword: ?$" {send "$newpass\r"}
expect {
-re "BAD.*$" {
send "\r"
send_user "Password not accepted!\n"
expect -re "BAD.*$" {send "\r"}
expect -re "BAD.*$" {send "\r"}
}
-re "word: ?$" {send "$newpass\r"}
}
expect -re $prompt {send "exit\r"}
} else {
if { $argvec != 0 } {
expect -re $tprompt {send "$commvec $argvec\r"}
} else {
expect -re $tprompt {send "$commvec\r"}
}
log_user 1
expect -re $tprompt {
if { $db } {
log_user 1
} else {
log_user 0
}
send "exit\r"
}
}
}
proc sco_q {command arglist tprompt debug login lpass} {
set timeout 5
set log $login
set opass $lpass
set argvec $arglist
set commvec $command
set prompt $tprompt
set db $debug
if { $db } {
log_user 1
} else {
log_user 0
}
if { $command == "passwd" } {
set username $log
set oldpass $opass
set newpass $argvec
expect -re $prompt {send "$commvec $username\r"}
expect -re "word: ?$" {send "$oldpass\r"}
expect -re "1\\\): ?$" {send "\r"}
expect -re "New password: ?$" {send "$newpass\r"}
expect {
-re "New password: ?$" {
send "$newpass\r"
expect -re "New password: ?$" {send "$newpass\r"}
send_user "Password not accepted!\n"
}
-re "word: ?$" {send "$newpass\r"}
}
expect -re $prompt {send "exit\r"}
} else {
expect -re $tprompt {send "\r"}
expect -re $tprompt {
if { $argvec != 0 } {
send "$commvec $argvec\r"
} else {
send "$commvec\r"
}
log_user 1
}
expect -re $tprompt {
if { $db } {
log_user 1
} else {
log_user 0
}
send "exit\r"
}
}
}
#########################################################################
# The main process loop
#########################################################################
#########################################################################
# Check command line args
#########################################################################
if { [llength $argv] < 3 } {
puts "Usage: $argv0 <-h> <-d> \\ "
puts "\t\[+host1 +hostn | \(+sun &| +sco &| +hp &| +linux\) | +all\] \\ "
puts "\t\[login\] \[rpasswd | passwd | <command> <args>\]"
exit
}
set gotserv 0
set gotlog 0
foreach name $argv {
if { [string match "+*" $name ] } {
set gotserv 1
} elseif { [string match "+*" $name ] && $gotserv } {
continue
} elseif { $gotserv } {
set gotlog 1
}
}
if { [expr $gotlog+$gotserv] != 2 } {
puts "Usage: $argv0 <-h> <-d> \\ "
puts "\t\[+host1 +hostn | \(+sun &| +sco &| +hp &| +linux\) | +all\] \\ "
puts "\t\[login\] \[rpasswd | passwd| <command> <args>\]"
exit
}
#########################################################################
# Set list of servers, passwords and other globals
#########################################################################
set sun [list sun1 sun2 sun3]
set sco [list sco1 sco2 sco3]
set hp [list hp1 hp2 hp3]
set linux [list linux1 linux2 linux3]
list servers
list arglist
set allservers 0
set sunservers 0
set linuxservers 0
set scoservers 0
set hpservers 0
set needargs 0
set gotargs 0
set verify 0
set tprompt "(%|#|\\\/|\\\$|\\\/|\\\/\\\/#|user1|user2|user3|\\\)|\\\>|\\\}) ?$"
#########################################################################
# Parse arg vector and delegate procs
#########################################################################
foreach name $argv {
if { [string match "+*" $name] == 1 } {
if { [string match "+all" $name] == 1 } {
set allservers 1
} elseif { [string match "+sun" $name] == 1 } {
set sunservers 1
} elseif { [string match "+linux" $name] == 1 } {
set linuxservers 1
} elseif { [string match "+sco" $name] == 1 } {
set scoservers 1
} elseif { [string match "+hp" $name] == 1 } {
set hpservers 1
} else {
lappend servers [string range $name 1 [expr [string length $name]-1]]
}
} else {
if { $needargs == 1 } {
lappend arglist $name
set gotargs 1
} elseif { $verify == 1 } {
set command $name
set needargs 1
} elseif { $name == "-d" } {
continue
} else {
set login $name
incr verify
}
}
}
set ssshservers [expr $sunservers + $linuxservers + $scoservers + $hpservers]
if { $gotargs == 0 } {
set arglist 0
}
#########################################################################
# Grab user password and new password if necessary.
#########################################################################
send_user "Enter your login password : "
stty -echo
expect -re "(.*)\n"
stty echo
set lpass $expect_out(1,string)
send_user "\n"
if { $command == "passwd" } {
send_user "Enter your new password : "
stty -echo
expect -re "(.*)\n"
stty echo
set arglist $expect_out(1,string)
}
send_user "\n"
#########################################################################
# Loop until all conditions are satisfied
#########################################################################
for {} {1} {} {
if {$allservers} {
foreach host $sun {
spawn telnet $host
sun_login $host $tprompt $login $lpass
sunhp_q $command $arglist $tprompt $debug $login $lpass
}
foreach host $linux {
spawn telnet $host
linux_login $host $tprompt $login $lpass
linux_q $command $arglist $tprompt $debug $login $lpass
}
foreach host $hp {
spawn telnet $host
hp_login $host $tprompt $login $lpass
sunhp_q $command $arglist $tprompt $debug $login $lpass
}
foreach host $sco {
spawn telnet $host
sco_login $host $tprompt $login $lpass
sco_q $command $arglist $tprompt $debug $login $lpass
}
break
} elseif {$sunservers} {
foreach host $sun {
spawn telnet $host
sun_login $host $tprompt $login $lpass
sunhp_q $command $arglist $tprompt $debug $login $lpass
}
if { $ssshservers > 1 } {
set sunservers 0
incr ssshservers -1
continue
}
break
} elseif {$linuxservers} {
foreach host $linux {
spawn telnet $host
linux_login $host $tprompt $login $lpass
linux_q $command $arglist $tprompt $debug $login $lpass
}
if { $ssshservers > 1 } {
set linuxservers 0
incr ssshservers -1
continue
}
break
} elseif {$scoservers} {
foreach host $sco {
spawn telnet $host
sco_login $host $tprompt $login $lpass
sco_q $command $arglist $tprompt $debug $login $lpass
}
if { $ssshservers > 1 } {
set scoservers 0
incr ssshservers -1
continue
}
break
} elseif {$hpservers} {
foreach host $hp {
spawn telnet $host
hp_login $host $tprompt $login $lpass
sunhp_q $command $arglist $tprompt $debug $login $lpass
}
if { $ssshservers > 1 } {
set hpservers 0
incr ssshservers -1
continue
}
break
} else {
foreach host $servers {
if { [lsearch $sun $host] >= 0 } {
spawn telnet $host
sun_login $host $tprompt $login $lpass
sunhp_q $command $arglist $tprompt $debug $login $lpass
} elseif { [lsearch $hp $host] >= 0 } {
spawn telnet $host
hp_login $host $tprompt $login $lpass
sunhp_q $command $arglist $tprompt $debug $login $lpass
} elseif { [lsearch $linux $host] >= 0 } {
spawn telnet $host
linux_login $host $tprompt $login $lpass
linux_q $command $arglist $tprompt $debug $login $lpass
} elseif { [lsearch $sco $host] >= 0 } {
spawn telnet $host
sco_login $host $tprompt $login $lpass
sco_q $command $arglist $tprompt $debug $login $lpass
}
}
if { $ssshservers > 1 } {
incr ssshservers -1
continue
}
break
}
}
exit
, Mike
linux unix internet technology
Monday, March 24, 2008
Doing Network Wide Updates Using Expect
Wednesday, December 12, 2007
Creating a Generic Init Script Template
I should begin today's post by apologizing, both for my recent fascination with the Creative Commons licensing system (I can't explain why I like the logo so much ;) and for the fact that what we're going to create here today isn't a template in the conventional sense. It's actually more like a script to create init scripts. All depends in how you look at it, I guess.
I have to write enough of these that I don't generally bother with making my life easier. I'll just take the last script I wrote and use a few vi editor tricks to substitute this and that and be done with it. However, if this was something I had to do on a massive scale on an initial build, I think I might like to have a little bit of help. And help, as they say, is always available for those who help themselves.
The following script is written to create straight sh init scripts. I wrote it this ways since Solaris' sh, Linux sh (really bash for the most part) and the posix shell all port almost exactly and I wanted to be able to do this on any platform. With that in mind, I made the init script location and link locations variables as well, since Solaris keeps things in /etc/init.d, HP-UX keeps them in /sbin/init.d and various other flavors of *nix keep them in various other places. My hope is that this will be usable by anyone on any remotely-posix-compliant system; and then maybe even for users of systems that aren't!
If you're interested in reading more about the init script, I'll be doing future posts in greater detail, almost definitely. You can also check out these previous postings (parts 1, 2 and 3) regarding init scripts if you'd like to learn a little bit about some simple ways to manage them without "losing track" (something Solaris can do to you with its rc hard-links).
Hope this helps you out. When I find the time, I'll post an addendum to this (referring back to it) using getopts for the command line processing. For now, this "template" assumes a strict command line parlance and won't swagger unless you rewrite some parts yourself. There's no error checking either because this has become another monster post (perhaps I'll do a completely separate post on error checking so I don't have to keep explaining that). I tried to keep this as generic as possible :)
This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License#!/bin/sh
#
# Simple Init Script Creator
# 2007 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#
# Usage: ./programName init_dir rc3_dir
# rc0_dir script_name program_name
# fully_qualified_program_name
# common_bin_directory start_options
# stop_options
#
# This script assumes that all listed
# variables have values, except for the last
# 2 (start options and stop_options - both
# should be surround by double quotes if
# spaces exist - If either of these do not
# exist, use the text string " " (double
# quoted space) to fill that option's
# place on the command line.
#
###########################################
trap 'rm -f ${init_dir}/${script_name} ${rc3_dir}/${script_name} ${rc0_dir}/${script_name} ${script_name}' 1 2 3 9 15
# Examples follow for all command line arguments
init_dir=$1 # /etc/init.d
rc3_dir=$2 # /etc/rc3.d
rc0_dir=$3 # /etc/rc0.d
script_name=$4 # WhateverYouWannaCallIt
program_name=$5 # apachectl
fully_qualified_program_name=$6 # /usr/local/sbin/apachectl
common_bin_directory=$7 # /usr/bin
start_options=$8 # "-f /usr/local/etc/httpd.conf" or " "
stop_options=$9 # "-k stop" or " "
cat <<- EOM >>$script_name
#!/bin/sh
#
# ${program_name} init script
# Usage: $script_name [start|stop]
#
case \$1 in
'start')
echo
echo Starting ${program_name}....
echo
${fully_qualified_program_name} ${start_options} >/dev/null 2>&1 &
${program_name}_running=\`${common_bin_directory}/ps -ef|${common_bin_directory}/grep "${fully_qualified_program_name}"|${common_bin_directory}/grep -v grep|${common_bin_directory}/awk '{print \$2}'\`
if [ "\$${program_name}_running" == "" ]
then
echo "${program_name} failed to start. Please check"
echo "application logs for further detail"
else
echo "${program_name} started successfully"
fi
;;
'stop')
echo
echo Stopping ${program_name}....
echo
if [ "$stop_options" != " " ]
then
${fully_qualified_program_name} ${stop_options} >/dev/null 2>&1 &
fi
${program_name}_walking=\`${common_bin_directory}/ps -ef|${common_bin_directory}/grep "${fully_qualified_program_name}"|${common_bin_directory}/grep -v grep|${common_bin_directory}/awk '{print \$2}'\`
if [ "\$${program_name}_walking" == "" ]
then
echo "${program_name} does not appear to be running."
echo "Process not found. Not shut down."
else
counter=5
dead="alive"
echo "Shutting ${program_name} down."
echo "${fully_qualified_program_name} - pid ${program_name}_walking - stopping."
while [ \$counter -gt 0 ]
do
${program_name}_still_walking=\`${common_bin_directory}/ps -ef|${common_bin_directory}/grep "${fully_qualified_program_name}"|${common_bin_directory}/grep -v grep|${common_bin_directory}/awk '{print \$2}'\`
if [ "\$${program_name}_still_walking" != "" ]
then
echo "killing pid \$${program_name}_still_walking "
${common_bin_directory}/kill \$${program_name}_still_walking >/dev/null 2>&1
counter=\`expr \$counter - 1\`
sleep 1
else
dead="dead"
echo "dead"
counter=0
fi
done
if [ \$dead = "alive" ]
then
echo "Could not kill process after 5 attempts."
echo "Process ${program_name}_walking still active."
${common_bin_directory}/ps -ef|${common_bin_directory}/grep "${fully_qualified_program_name}"|${common_bin_directory}/grep -v grep
fi
fi
;;
*)
echo "Usage: \$0 [start|stop]"
;;
esac
EOM
${common_bin_directory}/chmod 750 ${script_name};
${common_bin_directory}/cp ${script_name} ${init_dir}/${script_name}
${common_bin_directory}/ln -s ${init_dir}/${script_name} ${rc3_dir}/${script_name}
${common_bin_directory}/ln -s ${init_dir}/${script_name} ${rc0_dir}/${script_name}
, Mike
linux unix internet technology





