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.
- checksums.yaml +4 -4
- data/bin/mcmd +11 -83
- data/bin/mssh +16 -113
- data/lib/cli.rb +182 -0
- data/lib/mcmd.rb +30 -27
- metadata +18 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ff5ab53b19325189d26958ba44305d99d38a6a58
|
4
|
+
data.tar.gz: 957c71e1f478c67882899721be1b210ade946c87
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
3
|
+
require_relative '../lib/cli'
|
4
4
|
|
5
|
-
|
6
|
-
|
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
|
-
|
16
|
-
|
17
|
-
options[:
|
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
|
-
|
46
|
-
raise "Error, need command to run" if ARGV.size.zero?
|
18
|
+
cli.parse ARGV
|
47
19
|
|
48
|
-
|
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
|
-
|
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
|
-
|
4
|
-
require 'pp'
|
5
|
-
require 'mcmd'
|
3
|
+
require_relative '../lib/cli'
|
6
4
|
|
7
|
-
|
8
|
-
|
9
|
-
options = {
|
5
|
+
cli = CommonCli.new({
|
6
|
+
# defaults
|
10
7
|
:maxflight => 50,
|
11
8
|
:timeout => 60,
|
12
9
|
:global_timeout => 600,
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
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
|
-
|
55
|
-
options[:sshopt] ||= ["-2", "-oBatchMode=yes", "-A"]
|
56
|
-
options[:sshcmd] = "/usr/bin/ssh"
|
25
|
+
cli.parse ARGV
|
57
26
|
|
58
|
-
|
59
|
-
|
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
|
-
|
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
|
-
|
34
|
+
exec([cmd[0], cmd[0]])
|
32
35
|
else
|
33
|
-
|
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
|
54
|
+
if pid
|
52
55
|
# parent
|
53
56
|
# for mapping to subproc by pid
|
54
57
|
subproc.pid = pid
|
55
|
-
|
58
|
+
subproc_by_pid[pid] = subproc
|
56
59
|
# for mapping to subproc by i/o handle (returned from select)
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
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 =
|
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
|
123
|
-
subproc =
|
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 =
|
134
|
-
read_fds =
|
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
|
-
|
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
|
-
|
159
|
-
|
160
|
-
|
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
|
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 #{
|
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
|
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
|
-
|
194
|
-
|
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 =
|
232
|
+
p = subproc_by_pid[pid]
|
230
233
|
p.time_end = Time.now.to_i
|
231
234
|
p.retval = $?
|
232
|
-
|
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.
|
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:
|
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.
|
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:
|