mssh 0.0.11 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mcmd +11 -83
  3. data/bin/mssh +16 -113
  4. data/lib/cli.rb +182 -0
  5. data/lib/mcmd.rb +30 -27
  6. metadata +18 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8b1dc5731928a0e6c9bbefb194d034f4156c2ec7
4
- data.tar.gz: 008cbed76335b038760840f85167bcb9ef2c2829
3
+ metadata.gz: ff5ab53b19325189d26958ba44305d99d38a6a58
4
+ data.tar.gz: 957c71e1f478c67882899721be1b210ade946c87
5
5
  SHA512:
6
- metadata.gz: df5592d1342941ff5988bd047a96d82679c49b53a51340ccbf042f0e13c53a3a3dfd254105ca2f54f37407107cfdaca57c7b483c5d61d0055663c64d14c519ba
7
- data.tar.gz: b4174d36c61d8ff535525d98a7df0defdffc32214681a19d6680d8299afc36c2d2f16940043c82da082655a6664576225b7c15f26692d255930368a819818a8c
6
+ metadata.gz: f7d6388c50fd34c95d9e4258907265384c9ea8961d94e13e15ce89da5feefa29644dc35273a92335c4e62c980fbb047a0d00bf4e30839e90568bcb9e22ce46cb
7
+ data.tar.gz: ebfeb295fadbf60a0ebfd4b8fcf102371efdbf69046d1c4e03ebc1bf227d9f76eab47fb6abfe1fc983bc7f4a9779f8d6c083f6af7858894834e5ada84123fd1f
data/bin/mcmd CHANGED
@@ -1,97 +1,25 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'pp'
3
+ require_relative '../lib/cli'
4
4
 
5
- $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
6
- require 'mcmd'
7
-
8
- require 'optparse'
9
- options = {
5
+ cli = CommonCli.new({
6
+ # defaults
10
7
  :maxflight => 200,
11
8
  :timeout => 60,
12
9
  :global_timeout => 0,
13
- }
10
+ })
14
11
 
15
- optparse = OptionParser.new do |opts|
16
- opts.on('-r', '--range RANGE', 'currently takes a CSV list') do |arg|
17
- options[:range] = arg
18
- end
19
- opts.on('-m', '--maxflight 50', 'How many subprocesses? 50 by default') do |arg|
20
- options[:maxflight] = arg
21
- end
22
- opts.on('-t', '--timeout 60', 'How many seconds may each individual process take? 0 for no timeout') do |arg|
23
- options[:timeout] = arg
24
- end
25
- opts.on('-g', '--global_timeout 600', 'How many seconds for the whole shebang 0 for no timeout') do |arg|
26
- options[:global_timeout] = arg
27
- end
28
- opts.on('--noshell', "Don't invoke a shell. Args will be passed to exec verbatim ") do |arg|
29
- options[:noshell] = arg
30
- end
31
- opts.on('-c', '--collapse', "Collapse similar output ") do |arg|
32
- options[:collapse] = arg
33
- end
34
- opts.on('-v', '--verbose', "Verbose output") do |arg|
35
- options[:verbose] = arg
36
- end
37
- opts.on('-d', '--debug', "Debug output") do |arg|
38
- options[:debug] = arg
12
+ cli.extra_options = lambda do |parser, options|
13
+ parser.on('--noshell', "Don't invoke a shell. Args will be passed to exec verbatim ") do |arg|
14
+ options[:noshell] = arg
39
15
  end
40
- # option to merge stdin/stdout into one buf? how should this work?
41
- # option to ignore as-we-go yield output - this is off by default now except for success/fail
42
16
  end
43
- optparse.parse!
44
17
 
45
- raise "Error, need -r argument" if options[:range].nil? or options[:range].empty?
46
- raise "Error, need command to run" if ARGV.size.zero?
18
+ cli.parse ARGV
47
19
 
48
- m = MultipleCmd.new
49
-
50
- targets = options[:range].split ","
51
-
52
- m.commands = targets.map { |t| ["/bin/sh", "-c"].push ARGV.map { |arg| arg.gsub('HOSTNAME', t)}.join " " }
53
- command_to_target = Hash.new
54
- targets.size.times do |i|
55
- command_to_target[m.commands[i].object_id] = targets[i]
20
+ result = cli.runcmd do |cmd, t|
21
+ if cli.options[:noshell] then [cmd] else ["/bin/sh", "-c"].push cmd end
56
22
  end
57
- m.yield_startcmd = lambda { |p| puts "#{command_to_target[p.command.object_id]}: starting" } if options[:verbose]
58
- m.yield_wait = lambda { |p| puts "#{p.success? ? 'SUCCESS' : 'FAILURE'} #{command_to_target[p.command.object_id]}: '#{p.stdout_buf}'" }
59
- # m.yield_proc_timeout = lambda { |p| puts "am killing #{p.inspect}"}
60
-
61
- m.perchild_timeout = options[:timeout].to_i
62
- m.global_timeout = options[:global_timeout].to_i
63
- m.maxflight = options[:maxflight].to_i
64
- m.verbose = options[:verbose]
65
- m.debug = options[:debug]
66
-
67
- result = m.run
68
23
 
69
- if options[:collapse]
70
- # print a collapsed summary
71
- stdout_matches_success = Hash.new
72
- stdout_matches_failure = Hash.new
73
- result.each do |r|
74
- if r[:retval].success?
75
- stdout_matches_success[r[:stdout_buf]] = [] if stdout_matches_success[r[:stdout_buf]].nil?
76
- stdout_matches_success[r[:stdout_buf]] << command_to_target[r[:command].object_id]
77
- else
78
- stdout_matches_failure[r[:stdout_buf]] = [] if stdout_matches_failure[r[:stdout_buf]].nil?
79
- stdout_matches_failure[r[:stdout_buf]] << command_to_target[r[:command].object_id]
80
- end
81
- end
82
- # output => [targets ...]
83
- stdout_matches_success.each_pair do |k,v|
84
- puts "SUCCESS: #{v.join ','}: #{k}"
85
- end
86
- stdout_matches_failure.each_pair do |k,v|
87
- puts "FAILURE: #{v.join ','}: #{k}"
88
- end
89
- else
90
- # we already printed while in-flight; do nothing
91
- # not collapse, print one per host
92
- # result.each do |r|
93
- # target = command_to_target[r[:command].object_id]
94
- # puts "#{target}: '#{r[:stdout_buf].chomp}'\n"
95
- # end
96
- end
24
+ cli.output result
97
25
 
data/bin/mssh CHANGED
@@ -1,128 +1,31 @@
1
- #!/usr/bin/ruby
1
+ #!/usr/bin/env ruby
2
2
 
3
- require 'rubygems'
4
- require 'pp'
5
- require 'mcmd'
3
+ require_relative '../lib/cli'
6
4
 
7
- require 'optparse'
8
- require 'ostruct'
9
- options = {
5
+ cli = CommonCli.new({
6
+ # defaults
10
7
  :maxflight => 50,
11
8
  :timeout => 60,
12
9
  :global_timeout => 600,
13
- }
14
- optparse = OptionParser.new do |opts|
15
- opts.on('-r', '--range RANGE', 'Requires a configured Range::Client. Use --hostlist if you do not use range') do |arg|
16
- options[:range] = arg
17
- end
18
- opts.on('--hostlist x,y,z', Array, 'List of hostnames to execute on') do |arg|
19
- options[:hostlist] = arg
20
- end
21
- opts.on('-f', '--file FILE', 'List of hostnames in a FILE use (/dev/stdin) for reading from stdin') do |arg|
22
- options[:file] = arg
23
- end
24
- opts.on('-m', '--maxflight 50', 'How many subprocesses? 50 by default') do |arg|
25
- options[:maxflight] = arg
26
- end
27
- opts.on('-t', '--timeout 60', 'How many seconds may each individual process take? 0 for no timeout') do |arg|
28
- options[:timeout] = arg
29
- end
30
- opts.on('-g', '--global_timeout 600', 'How many seconds for the whole shebang 0 for no timeout') do |arg|
31
- options[:global_timeout] = arg
32
- end
33
- opts.on('-c', '--collapse', "Collapse similar output ") do |arg|
34
- options[:collapse] = arg
35
- end
36
- opts.on('-v', '--verbose', "verbose ") do |arg|
37
- options[:verbose] = arg
38
- end
39
- opts.on('-d', '--debug', "Debug output") do |arg|
40
- options[:debug] = arg
41
- end
42
- opts.on('--sshopt OPT', "ssh options, default [-2, -oBatchMode=yes, -A]. Can be used multiple times.") do |arg|
10
+ :sshcmd => "/usr/bin/ssh",
11
+ :sshopt => ["-2", "-oBatchMode=yes", "-A"]
12
+ })
13
+
14
+ ## Extra options for the SSH version
15
+ cli.extra_options = lambda do |parser, options|
16
+ parser.on('--sshopt OPT', "ssh options, default [-2, -oBatchMode=yes, -A]. Can be used multiple times.") do |arg|
43
17
  options[:sshopt] ||= []
44
18
  options[:sshopt] << arg
45
19
  end
46
- opts.on('--sshcmd BINARY', "Path to the ssh binary") do |arg|
20
+ parser.on('--sshcmd BINARY', "Path to the ssh binary (default /usr/bin/ssh)") do |arg|
47
21
  options[:sshcmd] = arg
48
22
  end
49
- # option to merge stdin/stdout into one buf?
50
- # option to ignore as-we-go yield output
51
23
  end
52
- optparse.parse!
53
24
 
54
- # default options
55
- options[:sshopt] ||= ["-2", "-oBatchMode=yes", "-A"]
56
- options[:sshcmd] = "/usr/bin/ssh"
25
+ cli.parse ARGV
57
26
 
58
- targets = []
59
- if (options[:range].nil? and options[:file].nil? and options[:hostlist].nil?)
60
- raise "Error, need -r or -f or --hostlist option"
27
+ result = cli.runcmd do |command, target|
28
+ [cli.options[:sshcmd], *cli.options[:sshopt]].push target, command
61
29
  end
62
30
 
63
- if (!options[:range].nil?)
64
- require 'rangeclient'
65
- range = Range::Client.new
66
- targets.push *range.expand(options[:range])
67
- end
68
- if (!options[:file].nil?)
69
- targets_fd = File.open(options[:file])
70
- targets_fd.read.each_line { |x| targets << x.chomp }
71
- end
72
- if (!options[:hostlist].nil?)
73
- targets.push *options[:hostlist]
74
- end
75
-
76
- raise "no targets specified. Check your -r, -f or --hostlist inputs" if targets.size.zero?
77
- raise "need command to run" if ARGV.size.zero?
78
- raise "too many arguments" if ARGV.size != 1
79
-
80
-
81
-
82
- m = MultipleCmd.new
83
- m.commands = targets.map { |t| [options[:sshcmd], *options[:sshopt] ].push t, ARGV.first }
84
- command_to_target = Hash.new
85
- targets.size.times do |i|
86
- command_to_target[m.commands[i].object_id] = targets[i]
87
- end
88
- m.yield_startcmd = lambda { |p| puts "#{command_to_target[p.command.object_id]}: starting" } if options[:verbose]
89
- m.yield_wait = lambda { |p| puts "#{command_to_target[p.command.object_id]}: finished" } if options[:verbose]
90
-
91
- m.perchild_timeout = options[:timeout].to_i
92
- m.global_timeout = options[:global_timeout].to_i
93
- m.maxflight = options[:maxflight].to_i
94
- m.verbose = options[:verbose]
95
- m.debug = options[:debug]
96
-
97
- result = m.run
98
-
99
- if (options[:collapse] and options[:range])
100
- # print a collapsed summary
101
- out_matches = Hash.new
102
- result.each do |r|
103
- out = ""
104
- out += r[:stdout_buf].chomp if(!r[:stdout_buf].nil?)
105
- out += r[:stderr_buf].chomp if(!r[:stderr_buf].nil?)
106
- out_matches[out] = [] if out_matches[out].nil?
107
- out_matches[out] << command_to_target[r[:command].object_id]
108
- end
109
- # output => [targets ...]
110
- out_matches.each_pair do |k,v|
111
- hosts = range.compress v
112
- puts "#{hosts}: '#{k.chomp}'"
113
- end
114
- else
115
- # not collapse, print one per host
116
- result.each do |r|
117
- target = command_to_target[r[:command].object_id]
118
- out = ""
119
- out += r[:stdout_buf].chomp if(!r[:stdout_buf].nil?)
120
- out += r[:stderr_buf].chomp if(!r[:stderr_buf].nil?)
121
- if (r[:retval] == 0 )
122
- puts "#{target}:SUCCESS: #{out}\n"
123
- else
124
- exit_code = r[:retval].exitstatus.to_s if(!r[:retval].nil?)
125
- puts "#{target}:FAILURE[#{exit_code}]: #{out}\n"
126
- end
127
- end
128
- end
31
+ cli.output result
data/lib/cli.rb ADDED
@@ -0,0 +1,182 @@
1
+ require 'optparse'
2
+ require_relative './mcmd'
3
+
4
+ ##
5
+ # Implements what is shared
6
+ # between mssh and mcmd.
7
+ class CommonCli
8
+ attr_accessor :options, :result, :command_to_target, :targets
9
+ attr_writer :extra_options
10
+
11
+ def initialize(defaults)
12
+ @options = defaults
13
+ @options[:hostname_token] = 'HOSTNAME'
14
+ end
15
+
16
+ ##
17
+ # Parses a list of arguments, and yells
18
+ # at us if any are wrong.
19
+ def parse(argv)
20
+
21
+ @defs = Hash[@options.map{|k,v| [k, " (default: #{v})"] } ]
22
+
23
+ optparse = OptionParser.new do |opts|
24
+ opts.on('-r', '--range RANGE', 'currently takes a CSV list' + @defs[:range].to_s ) do |arg|
25
+ @options[:range] = arg
26
+ end
27
+ opts.on('--hostlist x,y,z', Array, 'List of hostnames to execute on' + @defs[:hostlist].to_s) do |arg|
28
+ @options[:hostlist] = arg
29
+ end
30
+ opts.on('-f', '--file FILE', 'List of hostnames in a FILE use (/dev/stdin) for reading from stdin' + @defs[:file].to_s) do |arg|
31
+ @options[:file] = arg
32
+ end
33
+ opts.on('-m', '--maxflight 200', 'How many subprocesses? 50 by default' + @defs[:maxflight].to_s) do |arg|
34
+ @options[:maxflight] = arg
35
+ end
36
+ opts.on('-t', '--timeout 60', 'How many seconds may each individual process take? 0 for no timeout' + @defs[:timeout].to_s) do |arg|
37
+ @options[:timeout] = arg
38
+ end
39
+ opts.on('-g', '--global_timeout 0', 'How many seconds for the whole shebang 0 for no timeout' + @defs[:global_timeout].to_s) do |arg|
40
+ @options[:global_timeout] = arg
41
+ end
42
+ opts.on('-c', '--collapse', "Collapse similar output " + @defs[:collapse].to_s) do |arg|
43
+ @options[:collapse] = arg
44
+ end
45
+ opts.on('-v', '--verbose', "Verbose output" + @defs[:verbose].to_s) do |arg|
46
+ @options[:verbose] = arg
47
+ end
48
+ opts.on('-d', '--debug', "Debug output" + @defs[:debug].to_s) do |arg|
49
+ @options[:debug] = arg
50
+ end
51
+ opts.on('--json', "Output results as JSON" + @defs[:json_out].to_s) do |arg|
52
+ @options[:json_out] = arg
53
+ end
54
+ opts.on('--hntoken HOSTNAME', "Token used for HOSTNAME substitution" + @defs[:hostname_token].to_s) do |arg|
55
+ @options[:hostname_token] = arg
56
+ end
57
+
58
+ @extra_options.call(opts, @options) if !@extra_options.nil?
59
+
60
+ # option to merge stdin/stdout into one buf? how should this work?
61
+ # option to ignore as-we-go yield output - this is off by default now except for success/fail
62
+ end
63
+
64
+ optparse.parse! argv
65
+
66
+ if options[:range] || options[:collapse]
67
+ require 'rangeclient'
68
+ @rangeclient = Range::Client.new
69
+ end
70
+
71
+ ## Get targets from -r or -f or --hostlist
72
+ @targets = []
73
+ if (options[:range].nil? and options[:file].nil? and options[:hostlist].nil?)
74
+ raise "Error, need -r or -f or --hostlist option"
75
+ end
76
+
77
+ if (!options[:range].nil?)
78
+ @targets.push *@rangeclient.expand(options[:range])
79
+ end
80
+
81
+ if (!options[:file].nil?)
82
+ targets_fd = File.open(options[:file])
83
+ targets_fd.read.each_line { |x| @targets << x.chomp }
84
+ end
85
+
86
+ if (!options[:hostlist].nil?)
87
+ @targets.push *options[:hostlist]
88
+ end
89
+
90
+ raise "no targets specified. Check your -r, -f or --hostlist inputs" if @targets.size.zero?
91
+ raise "need command to run" if argv.size.zero?
92
+ raise "too many arguments" if argv.size != 1
93
+
94
+ @command = argv.first
95
+
96
+ end
97
+
98
+ def runcmd
99
+
100
+ m = MultipleCmd.new
101
+
102
+ # We let the caller build the command
103
+ m.commands = @targets.map do |t|
104
+ yield(@command.gsub(@options[:hostname_token], t), t)
105
+ end
106
+
107
+ @command_to_target = Hash.new
108
+ @targets.size.times do |i|
109
+ @command_to_target[m.commands[i].object_id] = @targets[i]
110
+ end
111
+
112
+ m.yield_startcmd = lambda { |p| puts "#{@command_to_target[p.command.object_id]}: starting" } if @options[:verbose]
113
+ m.yield_wait = lambda { |p| puts "#{p.success? ? 'SUCCESS' : 'FAILURE'} #{@command_to_target[p.command.object_id]}: '#{p.stdout_buf}'" } if @options[:verbose]
114
+ # todo, from mssh m.yield_wait = lambda { |p| puts "#{@command_to_target[p.command.object_id]}: finished" } if @options[:verbose]
115
+
116
+ # was commented out in mcmd already: m.yield_proc_timeout = lambda { |p| puts "am killing #{p.inspect}"}
117
+
118
+ m.perchild_timeout = @options[:timeout].to_i
119
+ m.global_timeout = @options[:global_timeout].to_i
120
+ m.maxflight = @options[:maxflight].to_i
121
+ m.verbose = @options[:verbose]
122
+ m.debug = @options[:debug]
123
+
124
+ return m.run
125
+ end
126
+
127
+ def output result
128
+
129
+ ## Print as JSON array
130
+ if (@options[:json_out])
131
+ require 'json'
132
+ puts JSON.generate(result)
133
+ return
134
+ end
135
+
136
+ # Concat stdout / stderr -> :all_buf
137
+ result.each do |r|
138
+ r[:all_buf] = ""
139
+ r[:all_buf] += r[:stdout_buf].chomp if(!r[:stdout_buf].nil?)
140
+ r[:all_buf] += r[:stderr_buf].chomp if(!r[:stderr_buf].nil?)
141
+ end
142
+
143
+ ## Collapse similar results
144
+ if @options[:collapse]
145
+ stdout_matches_success = Hash.new
146
+ stdout_matches_failure = Hash.new
147
+ result.each do |r|
148
+
149
+ if r[:retval].success?
150
+ stdout_matches_success[r[:all_buf]] = [] if stdout_matches_success[r[:all_buf]].nil?
151
+ stdout_matches_success[r[:all_buf]] << @command_to_target[r[:command].object_id]
152
+ else
153
+ stdout_matches_failure[r[:all_buf]] = [] if stdout_matches_failure[r[:all_buf]].nil?
154
+ stdout_matches_failure[r[:all_buf]] << @command_to_target[r[:command].object_id]
155
+ end
156
+ end
157
+ # output => [targets ...]
158
+ stdout_matches_success.each_pair do |k,v|
159
+ hosts = @rangeclient.compress v
160
+ # puts "#{hosts}: '#{k.chomp}'"
161
+ puts "SUCCESS: #{hosts}: #{k}"
162
+ end
163
+ stdout_matches_failure.each_pair do |k,v|
164
+ hosts = @rangeclient.compress v
165
+ puts "FAILURE: #{hosts}: #{k}"
166
+ end
167
+
168
+ ## Dont collapse similar resutls
169
+ else
170
+ result.each do |r|
171
+ target = @command_to_target[r[:command].object_id]
172
+ if (r[:retval].success?)
173
+ puts "#{target}:SUCCESS: #{r[:all_buf]}\n"
174
+ else
175
+ exit_code = r[:retval].exitstatus.to_s if(!r[:retval].nil?)
176
+ puts "#{target}:FAILURE[#{exit_code}]: #{r[:all_buf]}\n"
177
+ end
178
+ end
179
+ end
180
+
181
+ end
182
+ end
data/lib/mcmd.rb CHANGED
@@ -10,6 +10,9 @@ class MultipleCmd
10
10
  attr_accessor :yield_wait, :yield_startcmd, :debug, :yield_proc_timeout
11
11
  attr_accessor :verbose, :poll_period, :max_read_size
12
12
 
13
+ # TODO: move these to cattrs on MultipleCmd::SubProc
14
+ attr_accessor :subproc_by_pid, :subproc_by_fd
15
+
13
16
  def initialize
14
17
  # these are re-initialized after every run
15
18
  @subproc_by_pid = Hash.new
@@ -28,9 +31,9 @@ class MultipleCmd
28
31
 
29
32
  def noshell_exec(cmd)
30
33
  if cmd.length == 1
31
- Kernel.exec([cmd[0], cmd[0]])
34
+ exec([cmd[0], cmd[0]])
32
35
  else
33
- Kernel.exec([cmd[0], cmd[0]], *cmd[1..-1])
36
+ exec([cmd[0], cmd[0]], *cmd[1..-1])
34
37
  end
35
38
  end
36
39
 
@@ -48,18 +51,18 @@ class MultipleCmd
48
51
  subproc.command = cmd
49
52
 
50
53
  pid = fork
51
- if not pid.nil?
54
+ if pid
52
55
  # parent
53
56
  # for mapping to subproc by pid
54
57
  subproc.pid = pid
55
- @subproc_by_pid[pid] = subproc
58
+ subproc_by_pid[pid] = subproc
56
59
  # for mapping to subproc by i/o handle (returned from select)
57
- @subproc_by_fd[stdin_rd] = subproc
58
- @subproc_by_fd[stdin_wr] = subproc
59
- @subproc_by_fd[stdout_rd] = subproc
60
- @subproc_by_fd[stdout_wr] = subproc
61
- @subproc_by_fd[stderr_rd] = subproc
62
- @subproc_by_fd[stderr_wr] = subproc
60
+ subproc_by_fd[stdin_rd] = subproc
61
+ subproc_by_fd[stdin_wr] = subproc
62
+ subproc_by_fd[stdout_rd] = subproc
63
+ subproc_by_fd[stdout_wr] = subproc
64
+ subproc_by_fd[stderr_rd] = subproc
65
+ subproc_by_fd[stderr_wr] = subproc
63
66
 
64
67
  self.yield_startcmd.call(subproc) unless self.yield_startcmd.nil?
65
68
  else
@@ -76,10 +79,10 @@ class MultipleCmd
76
79
  def process_read_fds(read_fds)
77
80
  read_fds.each do |fd|
78
81
  # read available bytes, add to the subproc's read buf
79
- if not @subproc_by_fd.has_key?(fd)
82
+ if not subproc_by_fd.has_key?(fd)
80
83
  raise "Select returned a fd which I have not seen! fd: #{fd.inspect}"
81
84
  end
82
- subproc = @subproc_by_fd[fd]
85
+ subproc = subproc_by_fd[fd]
83
86
  buf = ""
84
87
  begin
85
88
  buf = fd.sysread(4096)
@@ -119,8 +122,8 @@ class MultipleCmd
119
122
  end # process_read_fds()
120
123
  def process_write_fds(write_fds)
121
124
  write_fds.each do |fd|
122
- raise "working on an unknown fd #{fd}" unless @subproc_by_fd.has_key?(fd)
123
- subproc = @subproc_by_fd[fd]
125
+ raise "working on an unknown fd #{fd}" unless subproc_by_fd.has_key?(fd)
126
+ subproc = subproc_by_fd[fd]
124
127
  buf = ""
125
128
  # add writing here, todo. not core feature
126
129
  end
@@ -130,8 +133,8 @@ class MultipleCmd
130
133
 
131
134
  # iterate and service fds in child procs, collect data and status
132
135
  def service_subprocess_io
133
- write_fds = @subproc_by_pid.values.select {|x| not x.stdin_fd.nil? and not x.terminated}.map {|x| x.stdin_fd}
134
- read_fds = @subproc_by_pid.values.select {|x| not x.terminated}.map {|x| [x.stdout_fd, x.stderr_fd].select {|x| not x.nil? } }.flatten
136
+ write_fds = subproc_by_pid.values.select {|x| not x.stdin_fd.nil? and not x.terminated}.map {|x| x.stdin_fd}
137
+ read_fds = subproc_by_pid.values.select {|x| not x.terminated}.map {|x| [x.stdout_fd, x.stderr_fd].select {|x| not x.nil? } }.flatten
135
138
 
136
139
  read_fds, write_fds, err_fds = IO.select_using_poll(read_fds, write_fds, nil, self.poll_period)
137
140
 
@@ -143,7 +146,7 @@ class MultipleCmd
143
146
 
144
147
  def process_timeouts
145
148
  now = Time.now.to_i
146
- @subproc_by_pid.values.each do |p|
149
+ subproc_by_pid.values.each do |p|
147
150
  if ((now - p.time_start) > self.perchild_timeout) and self.perchild_timeout > 0
148
151
  # expire this child process
149
152
 
@@ -155,9 +158,9 @@ class MultipleCmd
155
158
 
156
159
  def kill_process(p)
157
160
  # do not remove from pid list until waited on
158
- @subproc_by_fd.delete(p.stdin_fd)
159
- @subproc_by_fd.delete(p.stdout_fd)
160
- @subproc_by_fd.delete(p.stderr_fd)
161
+ subproc_by_fd.delete(p.stdin_fd)
162
+ subproc_by_fd.delete(p.stdout_fd)
163
+ subproc_by_fd.delete(p.stderr_fd)
161
164
  # must kill after deleting from maps
162
165
  # kill closes fds
163
166
  p.kill
@@ -168,7 +171,7 @@ class MultipleCmd
168
171
  done = false
169
172
  while not done
170
173
  # start up as many as maxflight processes
171
- while @subproc_by_pid.length < self.maxflight and not @commands.empty?
174
+ while subproc_by_pid.length < self.maxflight and not @commands.empty?
172
175
  # take one from @commands and start it
173
176
  commands = @commands.shift
174
177
  self.add_subprocess(commands)
@@ -179,19 +182,19 @@ class MultipleCmd
179
182
  self.process_timeouts
180
183
  # service process cleanup
181
184
  self.wait
182
- puts "have #{@subproc_by_pid.length} left to go" if self.debug
185
+ puts "have #{subproc_by_pid.length} left to go" if self.debug
183
186
  # if we have nothing in flight (active pid)
184
187
  # and nothing pending on the input list
185
188
  # then we're done
186
- if @subproc_by_pid.length.zero? and @commands.empty?
189
+ if subproc_by_pid.length.zero? and @commands.empty?
187
190
  done = true
188
191
  end
189
192
  end
190
193
 
191
194
  data = self.return_rundata
192
195
  # these are re-initialized after every run
193
- @subproc_by_pid = Hash.new
194
- @subproc_by_fd = Hash.new
196
+ subproc_by_pid = Hash.new
197
+ subproc_by_fd = Hash.new
195
198
  @processed_commands = []
196
199
  # end items which are re-initialized
197
200
  return data
@@ -226,10 +229,10 @@ class MultipleCmd
226
229
  else
227
230
  # pid is now gone. remove from subproc_by_pid and
228
231
  # add to the processed commands list
229
- p = @subproc_by_pid[pid]
232
+ p = subproc_by_pid[pid]
230
233
  p.time_end = Time.now.to_i
231
234
  p.retval = $?
232
- @subproc_by_pid.delete(pid)
235
+ subproc_by_pid.delete(pid)
233
236
  @processed_commands << p
234
237
  just_reaped << p
235
238
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mssh
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.11
4
+ version: 0.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Miller
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-29 00:00:00.000000000 Z
11
+ date: 2017-03-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  description: Simple library for running jobs and sshing to many hosts at once.
42
56
  email:
43
57
  - github@squareup.com
@@ -52,6 +66,7 @@ files:
52
66
  - README.md
53
67
  - bin/mcmd
54
68
  - bin/mssh
69
+ - lib/cli.rb
55
70
  - lib/mcmd.rb
56
71
  homepage: http://github.com/square/mssh
57
72
  licenses: []
@@ -73,9 +88,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
88
  version: 1.3.6
74
89
  requirements: []
75
90
  rubyforge_project:
76
- rubygems_version: 2.4.4
91
+ rubygems_version: 2.5.1
77
92
  signing_key:
78
93
  specification_version: 4
79
94
  summary: Parallel ssh and command execution.
80
95
  test_files: []
81
- has_rdoc: