[MJ] - When monitoring customer systems or safeguarding system configurations before doing an upgrade, it is very useful to get the current config files and outputs of certain commands (for instance ''show running-config'' on routers). I previously used a script written in [Ruby] for this, but that isn't easily distributed. Hence, I have ported the automated config file download to Tcl now. The result of local commands will be ported using [Expect]. The script is driven by an XML config file that defines a number of hosts and their types. And the actions that have to be performed for each type. For example: /etc ~ /etc '''The script''' package require Tcl 8.5 package require tdom package require vfs::ftp variable xmlfile variable hostpattern variable errors if {$argc < 1 || $argc > 2} { puts stderr "usage: freezeconfig xml-file ?host-pattern?" exit 1 } proc set_params {file {pattern .*}} { variable xmlfile variable hostpattern set xmlfile $file set hostpattern $pattern } proc parse_file {xmlfile} { set f [open $xmlfile] set dom [dom parse -channel $f] set doc [$dom documentElement] close $f return $doc } proc do_type {hostnode hostname address type} { # get the type info puts "Executing type $type" set typeInfo [$hostnode selectNodes {../type[@name=$type]}] if {$typeInfo eq {}} { error "type not defined" } set sessionnodes [$typeInfo selectNodes session] foreach sessionnode $sessionnodes { set user [$sessionnode getAttribute user {}] set currentUserNode [$hostnode selectNodes {user[@name=$user]}] if {$currentUserNode eq {}} { error "user $user not defined for $hostname" } puts "Starting session for user: $user..." set pass [$currentUserNode getAttribute pass {}] foreach protocolnode [$sessionnode selectNodes protocol] { do_protocol $protocolnode $hostname $user $pass } } } proc do_protocol {node host user pass} { set protocol [$node getAttribute name] variable errors puts "Protocol $protocol" protocol::${protocol}::do $node $host $user $pass } namespace eval protocol { namespace eval ftp { } } # different protocol handlers proc ::protocol::ftp::do {node host user pass} { file mkdir $host foreach dirNode [$node selectNodes dir] { set dir [$dirNode asText] file mkdir ./$host/$dir set filter [$dirNode getAttribute filter {^.*$}] set fd [vfs::ftp::Mount $user:$pass@$host/$dir _ftp] puts "copying files from '$dir' matching '$filter'" foreach file [glob -tails -directory ./_ftp *] { if {[regexp -- $filter $file]} { puts "...$file" file copy ./_ftp/$file ./$host/$dir } } vfs::ftp::Unmount $fd _ftp } } # main program set_params {*}$argv if {[catch {parse_file $xmlfile} doc]} { puts stderr "parsing failed: $doc" exit 1 } set name [$doc getAttribute name unknown] set timestamp [clock format [clock seconds] -timezone UTC -format %Y%m%d-%H%M%S] set line [string repeat - 80] puts $line puts $line puts "Freezing $name ($timestamp)" puts $line file mkdir $name cd $name file mkdir $timestamp cd $timestamp set errors {} foreach hostnode [$doc selectNodes host] { set hostname [$hostnode getAttribute name] if {[regexp -- $hostpattern $hostname]} { set address [$hostnode getAttribute address $hostname] puts "freezing $hostname ($address)..." foreach typenode [$hostnode selectNodes type] { set type [$typenode getAttribute name] if {[catch {do_type $hostnode $hostname $address $type} res]} { lappend errors [list $hostname $type $res] } } } else { puts "skipping $host..." } } puts "Freezing failed for:" puts $line puts [format "| %-15s| %-10s| %s" Host Type Error] puts $line foreach error $errors { puts [format "| %-15s| %-10s| %s" {*}$error] } set timestamp [clock format [clock seconds] -timezone UTC -format %Y%m%d-%H%M%S] puts "Freezing $name finished ($timestamp)" '''Design remarks''' * The timestamp for the stored data uses UTC: When doing forensics on when a change occured, you want to be able to compare different freezes from different people (who are geographically separated) in time. One way to do this is to use ''[[clock seconds]]'' but this is not easily parsed by humans. Instead a date format that sorts chronologically is used at the UTC timezone. * Using ''variable'' instead of ''global'': this is done to make a possible conversion to a package in a seperate namespace as painless as possible. * Using Tcl: the reason for porting from [Ruby] to Tcl is twofold. I am really out of practice with Ruby and Tcl makes it much easier to distribute the script without requiring a local programming language installation thanks to [starpack]s. ---- !!!!!! %| [Category Design] |% !!!!!!