aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2019-11-28 20:41:14 +0200
committerSergey Poznyakoff <gray@gnu.org>2019-11-28 21:19:40 +0200
commit2ec31a163a6e6dc690aa7117a70374059451db76 (patch)
treeac21dbe4f9abfe9578d0b2ba133e4ea6dbc1b29f
parent1fdeaf5c01c4f2be12bc65d240baa2245ab03208 (diff)
downloadrex-2ec31a163a6e6dc690aa7117a70374059451db76.tar.gz
rex-2ec31a163a6e6dc690aa7117a70374059451db76.tar.bz2
rex group: initial implementation
Selected hosts and host groups are kept on a stack. The host group on top of the stack is the current one. The 'rex group' command is provided to maintain the host group stack. It takes four subcommands: git push NAME push current group selection on stack and select NAME as a new host group git pop pop current hostgroup selection off stack git show display the currently selected host group (and, optionally, hosts in it) git select COMMAND create a temporary unnamed hostgroup from hosts on which COMMAND exits with 0 status and push it on stack
-rw-r--r--NEWS9
-rwxr-xr-xrex335
2 files changed, 290 insertions, 54 deletions
diff --git a/NEWS b/NEWS
index 694c4a2..63f9e6f 100644
--- a/NEWS
+++ b/NEWS
@@ -1,10 +1,9 @@
-Rex NEWS -- history of user-visible changes. 2016-10-01
-Copyright (C) 2012-2016 Sergey Poznyakoff
+Rex NEWS -- history of user-visible changes. 2019-11-28
See the end of file for copying conditions.
Please send bug reports and suggestions to <gray+rex@gnu.org.ua>
-Version 4.0 (Git)
+Version 4.0, 2016-10-01
This release provide a set of commands for use in rc files. The use
of set statements and direct modification of the config array are
@@ -99,7 +98,7 @@ new rc:
option no-host-header on
-Version 3.0 (Git)
+Version 3.0, 2016-08-29
Major rewrite.
@@ -204,7 +203,7 @@ Initial version.
=========================================================================
Copyright information:
-Copyright (C) 2012-2016 Sergey Poznyakoff
+Copyright (C) 2012-2019 Sergey Poznyakoff
Permission is granted to anyone to make or distribute verbatim copies
of this document as received, in any medium, provided that the
diff --git a/rex b/rex
index 6b82905..6ff3f19 100755
--- a/rex
+++ b/rex
@@ -20,6 +20,7 @@ exec expect "$0" -- "$@"
set version "4.0"
set sysconfdir "/etc/rex"
set usrconfdir "$env(HOME)/.rex"
+set defaultfile "$usrconfdir/default"
set confpath [list $usrconfdir $sysconfdir]
set libpath [list $usrconfdir/script $sysconfdir/script]
@@ -157,7 +158,6 @@ proc catch_config_error {prog code} {
}
}
default {
- puts "B"
return -code [dict get $options -code] -options $options
}
}
@@ -167,7 +167,6 @@ proc option_set {args} {
global optiondef
global config
-# puts "option_set [llength $args] $args"
if {[llength $args] < 2 || [llength $args] > 3} {
return -code error "bad # args"
}
@@ -699,7 +698,7 @@ namespace eval ::getopt {
append line " $word"
}
}
- if {[string length $line] > 0} {
+ if {[info exists line] && [string length $line] > 0} {
puts $line
}
}
@@ -2197,7 +2196,7 @@ proc runcmd {hosts command} {
{set +e}
}
}
-
+
while {[job count $batch] > 0} {
expect {
-i [job sidlist $batch]
@@ -2289,6 +2288,11 @@ proc runcmd {hosts command} {
::hostproc::getline $host $text
}
+ {STATUS} {
+ if {[regexp -- {([0-9]+)\r} $expect_out(0,string) x status]} {
+ job set batch $sid status $status
+ }
+ }
{STTY} {
if {[regexp -- {REXDIR=(.+)\r} $expect_out(0,string) x dir]} {
job set batch $sid wd $dir
@@ -2328,6 +2332,10 @@ proc runcmd {hosts command} {
}
}
{COMMAND} {
+ send -i $sid "echo $?\r"
+ job transition batch $sid STATUS
+ }
+ {STATUS} {
if {[job exists $batch $sid wd]} {
set host [job get $batch $sid host]
debug 3 "[job id $batch $sid]: removing $host:[job get $batch $sid wd]"
@@ -2378,11 +2386,13 @@ proc runcmd {hosts command} {
set sid $expect_out(spawn_id)
switch -- [job get $batch $sid state] {
{LOGOUT} {
- debug 2 "EOF from [job get $batch $sid hostname], waiting for $sid"
+ set hostname [job get $batch $sid hostname]
+ debug 2 "EOF from $hostname, waiting for $sid"
+ set res [job get $batch $sid status]
job close batch $sid
wait -i $sid
- ::hostproc::finish $host output($host)
+ ::hostproc::finish $hostname output($host) $res
}
{COPY} {
debug 3 "[job get $batch $sid cmd] finished"
@@ -2448,18 +2458,22 @@ namespace eval ::hostproc {
puts -nonewline $line
}
+ proc logout {host ref status} {}
+
# The finish function is called when EOF has been received from a host.
# The REF argument is the name of the variable which, if exists, contains
- # full text received from the host.
- proc finish {host ref} {
+ # full text received from the host.
+ # STATUS is the return status as received from wait.
+ proc finish {host ref status} {
upvar $ref text
+ logout $host $ref $status
if {[info exists text]} {
if {![config_option no-host-header]} {
- puts "[hostname $host]:"
+ puts "$host:"
}
foreach line $text {
if {[config_option prefix]} {
- puts -nonewline "[hostname $host]> "
+ puts -nonewline "$host> "
}
puts -nonewline $line
}
@@ -2474,6 +2488,7 @@ namespace eval ::config {
variable initialized 0
variable cfgvars {sudo hosts user password command}
variable update_hint
+ variable hostgroup_stack
array set update_hint {
hosts {var { return "host $var" }}
sudo {var { if {$var} {
@@ -2687,7 +2702,7 @@ namespace eval ::config {
variable cfgvars
variable initialized
variable update_hint
-
+
foreach var $cfgvars {
if {[exists $var]} {
terror -nosave "warning: your rc file sets obsolete variable \"$var\""
@@ -2717,6 +2732,29 @@ namespace eval ::config {
debug 3 "config(hosts) = $config(hosts)"
}
}
+
+ proc push {} {
+ global config
+ array set cv {}
+ foreach key { option,hostgroup default,selected } {
+ if [info exists config($key)] {
+ set cv($key) $config($key)
+ }
+ }
+
+ lappend config(default,hostgroup_stack) [array get cv]
+ }
+
+ proc pop {} {
+ global config
+ unset -nocomplain config(option,hostgroup)
+ unset -nocomplain config(default,selected)
+ if [info exists config(default,hostgroup_stack)] {
+ array set config [lindex $config(default,hostgroup_stack) end]
+ set config(default,hostgroup_stack) \
+ [lreplace $config(default,hostgroup_stack) end end]
+ }
+ }
}
proc scanlibpath {file} {
@@ -2781,7 +2819,7 @@ proc hostlist_setup {} {
}
}
}
-
+
if {![info exists config(hosts)] || [llength $config(hosts)] == 0} {
if {![info exists config(option,hostgroup)]} {
terror "no host list"
@@ -2884,10 +2922,48 @@ proc ispipeline {cmd} {
regexp -- {(^(if|case|for|while|time|function|select)[[:space:]])|[|&><;]} $cmd
}
+proc read_hostgroup {groupname} {
+ global confpath
+
+ unset -nocomplain cfg
+ foreach dir $confpath {
+ set groupdir "$dir/hostgroup"
+ if {![file exists $groupdir]} {
+ lappend missing $groupdir
+ continue
+ }
+ set cfg "$groupdir/$groupname/rc"
+ if {[file exists $cfg]} {
+ debug 2 "reading configuration file $cfg"
+ ::config::read $cfg
+ break
+ }
+ unset -nocomplain cfg
+ }
+ if {![info exists cfg]} {
+ terror "no such hostgroup: $groupname"
+ if {[info exists missing]} {
+ if {[llength $missing] == [llength $confpath]} {
+ terror "at least one of the following directories must exist:"
+ foreach dir $missing {
+ terror " $dir"
+ }
+ } elseif {[config_option verbose]} {
+ warning "the following directories don't exist:"
+ foreach dir $missing {
+ terror " $dir"
+ }
+ }
+ }
+ exit 1
+ }
+}
+
proc rex_parse_cmdline {args} {
global argc argv argv0
set arglist {}
+ set nodefault false
while {[llength $args] > 0} {
switch -- [lindex $args 0] {
-usage -
@@ -2898,6 +2974,10 @@ proc rex_parse_cmdline {args} {
set opt [lshift args]
lappend arglist $opt [lshift args]
}
+ -nodefault {
+ lshift args
+ set nodefault true
+ }
default { break }
}
}
@@ -2910,6 +2990,7 @@ proc rex_parse_cmdline {args} {
global rexdb
global confpath
global config
+ global defaultfile
foreach dir $confpath {
set db "$dir/db"
@@ -2933,45 +3014,27 @@ proc rex_parse_cmdline {args} {
}
}
}
+ if !$nodefault {
+ readdefault
+ }
}
catch_config_error {::getopt::run $parser global } 1
# Read hostgroup config
- if {[info exists config(option,hostgroup)]} {
- unset -nocomplain cfg
- foreach dir $confpath {
- set groupdir "$dir/hostgroup"
- if {![file exists $groupdir]} {
- lappend missing $groupdir
- continue
- }
- set cfg "$groupdir/[config_option hostgroup]/rc"
- if {[file exists $cfg]} {
- debug 2 "reading configuration file $cfg"
- ::config::read $cfg
- break
- }
- unset -nocomplain cfg
- }
- if {![info exists cfg]} {
- terror "no such hostgroup: $config(option,hostgroup)"
- if {[info exists missing]} {
- if {[llength $missing] == [llength $confpath]} {
- terror "at least one of the following directories must exist:"
- foreach dir $missing {
- terror " $dir"
- }
- } elseif {[config_option verbose]} {
- warning "the following directories don't exist:"
- foreach dir $missing {
- terror " $dir"
- }
- }
- }
- exit 1
+ if {[info exists config(default,hostgroup_stack)] && \
+ [llength $config(default,hostgroup_stack)] > 0} {
+ unset -nocomplain config(option,hostgroup)
+ unset -nocomplain config(hosts)
+ array set config [lindex $config(default,hostgroup_stack) end]
+ if [info exists config(default,selected)] {
+ set config(hosts) $config(default,selected)
}
}
+
+ if {[info exists config(option,hostgroup)]} {
+ read_hostgroup [config_option hostgroup]
+ }
# Read in additional source file
if {[info exists config(option,source)]} {
@@ -2996,23 +3059,28 @@ proc rex_parse_cmdline {args} {
}
}
-proc rex_command args {
+proc rex_command_common args {
global argv0
global argc
global argv
global config
global env
+ set group {}
+
while {[llength $args] > 0} {
switch -- [lshift args] {
-copy { set config(option,copy) 1 }
+ -usage { set usage [lshift args] }
+ -docstring { set docstring [lshift args] }
+ -group { lappend group {-group} [lshift args] [lshift args] }
default { error "bad arguments to rex_command: $args" }
}
}
rex_parse_cmdline \
- -usage {rex run [OPTIONS] PROGRAM [ARGS...]} \
- -docstring {Runs PROGRAM on the given hosts.} \
+ -usage $usage \
+ -docstring $docstring \
-group global {
group,g=NAME
{select host group}
@@ -3070,7 +3138,8 @@ proc rex_command args {
zsh-quirk,Z
{try to cope with hosts running zsh}
{ option_set zsh-quirk true }
- }
+ } \
+ {*}$group
debug 2 "running prologue script"
::hostproc::prologue
@@ -3177,6 +3246,170 @@ proc rex_command args {
::hostproc::epilogue
}
+proc rex_command args {
+ rex_command_common \
+ -usage {rex run [OPTIONS] PROGRAM [ARGS...]} \
+ -docstring {Runs PROGRAM on the given hosts.}
+}
+
+proc readdefault {} {
+ global defaultfile
+ if [file exists $defaultfile] {
+ debug 2 "reading default file $defaultfile"
+ ::config::read $defaultfile
+ }
+}
+
+proc savedefault {} {
+ global config
+ global defaultfile
+
+ if {[catch {open "$defaultfile" "w"} fd] == 0} {
+ set key {default,hostgroup_stack}
+ if [info exists config($key)] {
+ puts $fd [list set ::config($key) $config($key)]
+ }
+ close $fd
+ } else {
+ terror "can't open $defaultfile: $fd"
+ exit 2
+ }
+}
+
+proc rex_group_select {} {
+ global config
+
+ proc ::hostproc::logout {host ref status} {
+ if {$status == 0} {
+ lappend ::config(default,selected) $host
+ }
+ }
+
+ unset -nocomplain config(default,selected)
+ rex_command_common \
+ -usage {rex group select [OPTIONS] COMMAND [ARGS...]} \
+ -docstring {Selects hosts on which COMMAND succeeds}
+
+ if {![info exists config(default,selected)]} {
+ terror "no hosts match the predicate"
+ exit 1
+ }
+ unset -nocomplain config(option,hostgroup)
+ ::config::push
+
+ savedefault
+}
+
+proc rex_group_drop {} {
+ global config
+
+ rex_parse_cmdline \
+ -usage {rex group drop} \
+ -docstring {Deselect the hostgroup}
+
+ ::config::pop
+ savedefault
+}
+
+proc rex_group_push {} {
+ global argc
+ global argv
+ global config
+
+ rex_parse_cmdline \
+ -usage {rex group push GROUP} \
+ -docstring {Select the hostgroup}
+
+ if {$argc != 1} {
+ terror "bad arguments"
+ exit 1
+ }
+
+ unset -nocomplain config(default,selected)
+ option_set group [lindex $argv 0]
+ read_hostgroup [config_option hostgroup]
+ ::config::push
+# ::config::export
+# hostlist_setup
+
+ savedefault
+}
+
+proc rex_group_show {} {
+ global argc
+ global argv
+ global config
+
+ set hosts 0
+
+ rex_parse_cmdline \
+ -usage {rex group show [hosts]} \
+ -docstring {Show selected hostgroup}
+
+ if {$argc == 1} {
+ if {[lindex $argv 0] == "hosts"} {
+ set hosts 1
+ } else {
+ terror "unrecognized argument: [lindex $argv 0]"
+ exit 2
+ }
+ } elseif {$argc != 0} {
+ terror "too many arguments"
+ exit 2
+ }
+
+ if [info exists config(option,hostgroup)] {
+ puts $config(option,hostgroup)
+ } elseif [info exists config(default,selected)] {
+ puts "selected hosts"
+ } else {
+ exit 2
+ }
+ if {$hosts && [info exists config(hosts)]} {
+ foreach h $config(hosts) {
+ puts $h
+ }
+ }
+}
+
+proc rex_group {} {
+ global argc
+ global argv
+ global argv0
+ global usrconfdir
+
+ ::getopt::parse \
+ -usage {rex group COMMAND [OPTIONS] [ARGS]} \
+ -docstring {Manipulate hostgroups} \
+ -footer {COMMANDs are:
+ select select hosts matching predicate command
+ show show selected hostgroup (and hosts)
+ push push new hostgroup
+ pop pop currently selected hostgroup off stack
+} \
+ -commit \
+ argc argv
+
+ if {[llength $argv] == 0} {
+ terror "missing subcommand; try 'rex group --help' for details"
+ exit 1
+ }
+ set subcom [argcvshift]
+
+ switch -- $subcom {
+ show rex_group_show
+ drop rex_group_drop
+ pop rex_group_drop
+ select rex_group_select
+ push rex_group_push
+
+ default {
+ terror "unknown subcommand $subcom; try 'rex group --usage' for details"
+ exit 1
+ }
+ }
+}
+
proc rex_list {} {
global argv0
global argc
@@ -3534,7 +3767,7 @@ proc rex_login {} {
denied {
set sid $expect_out(spawn_id)
- terror "bad password for [hostuser $host] on [job get $batch $sid hostname]"
+ terror "bad password for [hostuser [job get $batch $sid host]] on [job get $batch $sid hostname]"
job close batch $sid
}
@@ -3669,6 +3902,10 @@ trap {
switch -- $config(mode) {
run rex_command
+
+ group rex_group
+ select rex_select
+ deselect rex_deselect
copy-to rex_copy_to

Return to:

Send suggestions and report system problems to the System administrator.