mssh 0.0.11 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
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: