rutty 1.1.3 → 2.0.0
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.
- data/.gitignore +2 -0
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/bin/rutty +16 -244
- data/lib/rutty.rb +27 -0
- data/lib/rutty/actions.rb +176 -0
- data/lib/rutty/config.rb +48 -0
- data/lib/rutty/consts.rb +7 -0
- data/lib/rutty/errors.rb +5 -0
- data/lib/rutty/helpers.rb +18 -0
- data/lib/rutty/node.rb +19 -0
- data/lib/rutty/nodes.rb +32 -0
- data/lib/rutty/version.rb +10 -0
- data/rutty.gemspec +15 -3
- metadata +38 -13
data/.gitignore
CHANGED
data/Rakefile
CHANGED
@@ -14,7 +14,8 @@ begin
|
|
14
14
|
gem.email = "josh@cloudspace.com"
|
15
15
|
gem.homepage = "http://github.com/jlindsey/rutty"
|
16
16
|
gem.authors = ["Josh Lindsey"]
|
17
|
-
gem.
|
17
|
+
gem.add_development_dependency "bundler", ">= 1.0.0"
|
18
|
+
gem.add_development_dependency "jeweler", ">= 1.4.0"
|
18
19
|
gem.add_dependency "commander", ">= 4.0.3"
|
19
20
|
gem.add_dependency "net-ssh", ">= 2.0.23"
|
20
21
|
gem.add_dependency "net-scp", ">= 1.0.4"
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.0.0
|
data/bin/rutty
CHANGED
@@ -1,15 +1,9 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
# Constants
|
4
|
-
CONF_DIR = File.join(ENV['HOME'], '.rutty')
|
5
|
-
GENERAL_CONF = File.join(CONF_DIR, 'defaults.yaml')
|
6
|
-
NODES_CONF = File.join(CONF_DIR, 'nodes.yaml')
|
7
|
-
|
8
|
-
# Gems
|
9
2
|
require 'rubygems'
|
10
|
-
require 'bundler/setup'
|
11
3
|
require 'commander/import'
|
12
|
-
require '
|
4
|
+
require 'rutty'
|
5
|
+
|
6
|
+
$r = Rutty::Runner.new
|
13
7
|
|
14
8
|
# Helpers
|
15
9
|
add_filter_options = lambda { |c|
|
@@ -19,67 +13,10 @@ add_filter_options = lambda { |c|
|
|
19
13
|
c.option('--tags TAG1[,TAG2,...]', Array, 'Comma-separated list of tags')
|
20
14
|
}
|
21
15
|
|
22
|
-
def update_node_list &block
|
23
|
-
ary = YAML.load(File.open(NODES_CONF).read)
|
24
|
-
|
25
|
-
yield ary
|
26
|
-
|
27
|
-
File.open(NODES_CONF, 'w') do |f|
|
28
|
-
YAML.dump(ary, f)
|
29
|
-
end
|
30
|
-
|
31
|
-
@node_list = false # Force the next call to get_node_list to read from file again
|
32
|
-
end
|
33
|
-
|
34
|
-
def get_node_list
|
35
|
-
@node_list ||= YAML.load(File.open(NODES_CONF).read)
|
36
|
-
end
|
37
|
-
|
38
|
-
def get_defaults_config
|
39
|
-
@config ||= YAML.load(File.open(GENERAL_CONF).read)
|
40
|
-
end
|
41
|
-
|
42
|
-
def check_installed!
|
43
|
-
unless File.exists? CONF_DIR
|
44
|
-
raise "Can't find conf directory at #{CONF_DIR}. Run `rutty init' first. (Or rutty --help for usage)"
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def filter_nodes nodes, options
|
49
|
-
return nodes if options.a === true
|
50
|
-
|
51
|
-
nodes.delete_if { |node| node[:keypath].nil? or node[:keypath] != options.keypath } unless options.keypath.nil?
|
52
|
-
nodes.delete_if { |node| node[:user].nil? or node[:user] != options.user } unless options.user.nil?
|
53
|
-
nodes.delete_if { |node| node[:port].nil? or node[:port] != options.port } unless options.port.nil?
|
54
|
-
nodes.delete_if { |node| node[:tags].nil? or (node[:tags] & options.tags).empty? } unless options.tags.nil?
|
55
|
-
end
|
56
|
-
|
57
|
-
def options_for_node node
|
58
|
-
hash = node
|
59
|
-
|
60
|
-
hash[:user] ||= get_defaults_config[:user]
|
61
|
-
hash[:port] ||= get_defaults_config[:port]
|
62
|
-
hash[:keypath] ||= get_defaults_config[:keypath]
|
63
|
-
# :host will always be set on node
|
64
|
-
|
65
|
-
hash
|
66
|
-
end
|
67
|
-
|
68
|
-
def get_version
|
69
|
-
Gem.path.each do |path|
|
70
|
-
file = Dir.glob(File.join(path, 'gems', 'rutty-*', 'VERSION')).sort
|
71
|
-
next if file.empty?
|
72
|
-
|
73
|
-
return File.open(file.pop, 'r').read.chomp
|
74
|
-
end
|
75
|
-
|
76
|
-
return false
|
77
|
-
end
|
78
|
-
|
79
16
|
# Commander config
|
80
17
|
program :name, 'rutty'
|
81
18
|
program :description, 'A DSH implementation in Ruby'
|
82
|
-
program :version, get_version
|
19
|
+
program :version, $r.get_version
|
83
20
|
program :help_formatter, Commander::HelpFormatter::TerminalCompact
|
84
21
|
|
85
22
|
default_command :dsh
|
@@ -89,48 +26,8 @@ command :init do |c|
|
|
89
26
|
c.syntax = "rutty init"
|
90
27
|
c.summary = "Creates the default file structure for rutty."
|
91
28
|
c.when_called do
|
92
|
-
|
93
|
-
|
94
|
-
else
|
95
|
-
log "create", CONF_DIR
|
96
|
-
Dir.mkdir CONF_DIR
|
97
|
-
end
|
98
|
-
|
99
|
-
if File.exists? GENERAL_CONF
|
100
|
-
log "exists", GENERAL_CONF
|
101
|
-
else
|
102
|
-
log "create", GENERAL_CONF
|
103
|
-
|
104
|
-
defaults_hash = {
|
105
|
-
:user => 'root',
|
106
|
-
:keypath => File.join(ENV['HOME'], '.ssh', 'id_rsa'),
|
107
|
-
:port => 22
|
108
|
-
}
|
109
|
-
|
110
|
-
File.open(GENERAL_CONF, 'w') do |f|
|
111
|
-
YAML.dump(defaults_hash, f)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
if File.exists? NODES_CONF
|
116
|
-
log "exists", NODES_CONF
|
117
|
-
else
|
118
|
-
log "create", NODES_CONF
|
119
|
-
|
120
|
-
default_node = [
|
121
|
-
{
|
122
|
-
:host => 'localhost',
|
123
|
-
:port => 2222,
|
124
|
-
:keypath => File.join(ENV['HOME'], '.ssh', 'my_key.pem'),
|
125
|
-
:user => 'nobody',
|
126
|
-
:tags => %w(delete_me example localhost)
|
127
|
-
}
|
128
|
-
]
|
129
|
-
|
130
|
-
File.open(NODES_CONF, 'w') do |f|
|
131
|
-
YAML.dump(default_node, f)
|
132
|
-
end
|
133
|
-
end
|
29
|
+
r = Rutty::Runner.new
|
30
|
+
r.init
|
134
31
|
end
|
135
32
|
end
|
136
33
|
|
@@ -146,16 +43,8 @@ command :add_node do |c|
|
|
146
43
|
add_filter_options.call(c)
|
147
44
|
|
148
45
|
c.when_called do |args, options|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
hash = { :host => args.first }
|
153
|
-
hash[:keypath] = options.keypath unless options.keypath.nil?
|
154
|
-
hash[:user] = options.user unless options.user.nil?
|
155
|
-
hash[:port] = options.port unless options.port.nil?
|
156
|
-
hash[:tags] = options.tags unless options.tags.nil?
|
157
|
-
|
158
|
-
update_node_list { |nodes| nodes << hash }
|
46
|
+
r = Rutty::Runner.new
|
47
|
+
r.add_node args, options
|
159
48
|
end
|
160
49
|
end
|
161
50
|
|
@@ -172,13 +61,8 @@ command :list_nodes do |c|
|
|
172
61
|
add_filter_options.call(c)
|
173
62
|
|
174
63
|
c.when_called do |args, options|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
nodes = get_node_list
|
179
|
-
filter_nodes nodes, options
|
180
|
-
|
181
|
-
pp nodes
|
64
|
+
r = Rutty::Runner.new
|
65
|
+
r.list_nodes args, options
|
182
66
|
end
|
183
67
|
end
|
184
68
|
|
@@ -194,86 +78,8 @@ command :dsh do |c|
|
|
194
78
|
c.option('-d', '--debug', 'Enable debug output')
|
195
79
|
|
196
80
|
c.when_called do |args, options|
|
197
|
-
|
198
|
-
|
199
|
-
check_installed!
|
200
|
-
raise "Must supply a command to run. See `rutty help dsh' for usage" if args.empty?
|
201
|
-
raise "One of -a or --tags must be passed" if options.a.nil? and options.tags.nil?
|
202
|
-
raise "Use either -a or --tags, not both" if !options.a.nil? and !options.tags.nil?
|
203
|
-
raise "Multi-word commands must be enclosed in quotes (ex. rutty -a \"ps -ef | grep httpd\")" if args.length > 1
|
204
|
-
|
205
|
-
com_str = args.pop
|
206
|
-
|
207
|
-
nodes = get_node_list
|
208
|
-
filter_nodes nodes, options
|
209
|
-
|
210
|
-
require 'logger'
|
211
|
-
require 'net/ssh'
|
212
|
-
require 'pp'
|
213
|
-
|
214
|
-
@returns = {}
|
215
|
-
connections = []
|
216
|
-
|
217
|
-
# This is necessary in order to capture exit codes and/or signals,
|
218
|
-
# which are't passed through when using just the ssh.exec!() semantics.
|
219
|
-
exec_command = lambda { |ssh|
|
220
|
-
ssh.open_channel do |channel|
|
221
|
-
channel.exec(com_str) do |ch, success|
|
222
|
-
unless success
|
223
|
-
abort "FAILED: couldn't execute command (ssh.channel.exec
|
224
|
-
failure)"
|
225
|
-
end
|
226
|
-
|
227
|
-
channel.on_data do |ch, data| # stdout
|
228
|
-
@returns[ssh.host][:out] << data
|
229
|
-
end
|
230
|
-
|
231
|
-
channel.on_extended_data do |ch, type, data|
|
232
|
-
next unless type == 1 # only handle stderr
|
233
|
-
@returns[ssh.host][:out] << data
|
234
|
-
end
|
235
|
-
|
236
|
-
channel.on_request("exit-status") do |ch, data|
|
237
|
-
exit_code = data.read_long
|
238
|
-
@returns[ssh.host][:exit] = exit_code
|
239
|
-
end
|
240
|
-
|
241
|
-
channel.on_request("exit-signal") do |ch, data|
|
242
|
-
@returns[ssh.host][:sig] = data.read_long
|
243
|
-
end
|
244
|
-
end
|
245
|
-
end
|
246
|
-
ssh.loop
|
247
|
-
}
|
248
|
-
|
249
|
-
nodes.each do |node|
|
250
|
-
params = options_for_node(node)
|
251
|
-
@returns[node[:host]] = { :out => '' }
|
252
|
-
begin
|
253
|
-
connections << Net::SSH.start(params[:host], params[:user], :port => params[:port], :paranoid => false,
|
254
|
-
:user_known_hosts_file => '/dev/null', :keys => [params[:keypath]],
|
255
|
-
:logger => Logger.new(options.debug.nil? ? $stderr : $stdout),
|
256
|
-
:verbose => (options.debug.nil? ? Logger::FATAL : Logger::DEBUG))
|
257
|
-
rescue Errno::ECONNREFUSED
|
258
|
-
$stderr.puts "ERROR: Connection refused on #{node[:host]}"
|
259
|
-
@returns.delete node[:host]
|
260
|
-
rescue SocketError
|
261
|
-
$stderr.puts "ERROR: nodename nor servname provided, or not known for #{node[:host]}"
|
262
|
-
@returns.delete node[:host]
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
connections.each { |ssh| exec_command.call(ssh) }
|
267
|
-
|
268
|
-
loop do
|
269
|
-
connections.delete_if { |ssh| !ssh.process(0.1) { |s| s.busy? } }
|
270
|
-
break if connections.empty?
|
271
|
-
end
|
272
|
-
|
273
|
-
# TODO: Print this out in a better way
|
274
|
-
# TODO: Print a special alert for exit codes > 0
|
275
|
-
|
276
|
-
pp @returns
|
81
|
+
r = Rutty::Runner.new
|
82
|
+
r.dsh args, options
|
277
83
|
end
|
278
84
|
end
|
279
85
|
|
@@ -287,42 +93,8 @@ command :scp do |c|
|
|
287
93
|
c.option('-d', '--debug', 'Enable debug output')
|
288
94
|
|
289
95
|
c.when_called do |args, options|
|
290
|
-
|
291
|
-
|
292
|
-
raise "One of -a or --tags must be passed" if options.a.nil? and options.tags.nil?
|
293
|
-
raise "Use either -a or --tags, not both" if !options.a.nil? and !options.tags.nil?
|
294
|
-
|
295
|
-
require 'logger'
|
296
|
-
require 'net/ssh'
|
297
|
-
require 'net/scp'
|
298
|
-
require 'pp'
|
299
|
-
|
300
|
-
connections = []
|
301
|
-
|
302
|
-
remote_path = args.pop
|
303
|
-
local_path = args.pop
|
304
|
-
|
305
|
-
nodes = filter_nodes get_node_list, options
|
306
|
-
|
307
|
-
nodes.each do |node|
|
308
|
-
params = options_for_node(node)
|
309
|
-
begin
|
310
|
-
connections << Net::SSH.start(params[:host], params[:user], :port => params[:port], :paranoid => false,
|
311
|
-
:user_known_hosts_file => '/dev/null', :keys => [params[:keypath]],
|
312
|
-
:logger => Logger.new(options.debug.nil? ? $stderr : $stdout),
|
313
|
-
:verbose => (options.debug.nil? ? Logger::FATAL : Logger::DEBUG))
|
314
|
-
rescue Errno::ECONNREFUSED
|
315
|
-
$stderr.puts "ERROR: Connection refused on #{node[:host]}"
|
316
|
-
rescue SocketError
|
317
|
-
$stderr.puts "ERROR: nodename nor servname provided, or not known for #{node[:host]}"
|
318
|
-
end
|
319
|
-
end
|
320
|
-
|
321
|
-
connections.each { |ssh| ssh.scp.upload! local_path, remote_path }
|
322
|
-
|
323
|
-
loop do
|
324
|
-
connections.delete_if { |ssh| !ssh.process(0.1) { |s| s.busy? } }
|
325
|
-
break if connections.empty?
|
326
|
-
end
|
96
|
+
r = Rutty::Runner.new
|
97
|
+
r.dsh args, options
|
327
98
|
end
|
328
|
-
end
|
99
|
+
end
|
100
|
+
|
data/lib/rutty.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rutty/actions'
|
3
|
+
require 'rutty/config'
|
4
|
+
require 'rutty/consts'
|
5
|
+
require 'rutty/errors'
|
6
|
+
require 'rutty/helpers'
|
7
|
+
require 'rutty/node'
|
8
|
+
require 'rutty/nodes'
|
9
|
+
|
10
|
+
module Rutty
|
11
|
+
class Runner
|
12
|
+
attr_accessor :config
|
13
|
+
attr_accessor :nodes
|
14
|
+
|
15
|
+
include Rutty::Consts
|
16
|
+
include Rutty::Helpers
|
17
|
+
include Rutty::Actions
|
18
|
+
|
19
|
+
def config
|
20
|
+
@config ||= Rutty::Config.load_config
|
21
|
+
end
|
22
|
+
|
23
|
+
def nodes
|
24
|
+
@nodes ||= Rutty::Nodes.load_config
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'rutty/consts'
|
2
|
+
|
3
|
+
module Rutty
|
4
|
+
module Actions
|
5
|
+
def init
|
6
|
+
if File.exists? Rutty::Consts::CONF_DIR
|
7
|
+
log "exists", Rutty::Consts::CONF_DIR
|
8
|
+
else
|
9
|
+
log "create", Rutty::Consts::CONF_DIR
|
10
|
+
Dir.mkdir Rutty::Consts::CONF_DIR
|
11
|
+
end
|
12
|
+
|
13
|
+
if File.exists? Rutty::Consts::GENERAL_CONF
|
14
|
+
log "exists", Rutty::Consts::GENERAL_CONF
|
15
|
+
else
|
16
|
+
log "create", Rutty::Consts::GENERAL_CONF
|
17
|
+
|
18
|
+
defaults_hash = {
|
19
|
+
:user => 'root',
|
20
|
+
:keypath => File.join(ENV['HOME'], '.ssh', 'id_rsa'),
|
21
|
+
:port => 22
|
22
|
+
}
|
23
|
+
|
24
|
+
File.open(Rutty::Consts::GENERAL_CONF, 'w') do |f|
|
25
|
+
YAML.dump(defaults_hash, f)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
if File.exists? Rutty::Consts::NODES_CONF
|
30
|
+
log "exists", Rutty::Consts::NODES_CONF
|
31
|
+
else
|
32
|
+
log "create", Rutty::Consts::NODES_CONF
|
33
|
+
|
34
|
+
File.open(Rutty::Consts::NODES_CONF, 'w') do |f|
|
35
|
+
YAML.dump([], f)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_node args, options
|
41
|
+
raise Rutty::BadUsage.new "Must supply a hostname or IP address.
|
42
|
+
See `rutty help add_node' for usage" if args.empty?
|
43
|
+
|
44
|
+
hash = { :host => args.first }
|
45
|
+
hash[:keypath] = options.keypath unless options.keypath.nil?
|
46
|
+
hash[:user] = options.user unless options.user.nil?
|
47
|
+
hash[:port] = options.port unless options.port.nil?
|
48
|
+
hash[:tags] = options.tags unless options.tags.nil?
|
49
|
+
|
50
|
+
self.nodes << Rutty::Node.new(hash, self.config.to_hash)
|
51
|
+
self.nodes.write_config
|
52
|
+
end
|
53
|
+
|
54
|
+
def list_nodes args, options
|
55
|
+
require 'pp'
|
56
|
+
|
57
|
+
pp nodes.filter(options)
|
58
|
+
end
|
59
|
+
|
60
|
+
def dsh args, options
|
61
|
+
# TODO: Clean this up, it's pretty hard to read and follow
|
62
|
+
|
63
|
+
check_installed!
|
64
|
+
raise Rutty::BadUsage.new "Must supply a command to run. See `rutty help dsh' for usage" if args.empty?
|
65
|
+
raise Rutty::BadUsage.new "One of -a or --tags must be passed" if options.a.nil? and options.tags.nil?
|
66
|
+
raise Rutty::BadUsage.new "Use either -a or --tags, not both" if !options.a.nil? and !options.tags.nil?
|
67
|
+
raise Rutty::BadUsage.new "Multi-word commands must be enclosed in quotes (ex. rutty -a \"ps -ef | grep httpd\")" if args.length > 1
|
68
|
+
|
69
|
+
com_str = args.pop
|
70
|
+
|
71
|
+
require 'logger'
|
72
|
+
require 'net/ssh'
|
73
|
+
require 'pp'
|
74
|
+
|
75
|
+
@returns = {}
|
76
|
+
connections = []
|
77
|
+
|
78
|
+
# This is necessary in order to capture exit codes and/or signals,
|
79
|
+
# which are't passed through when using just the ssh.exec!() semantics.
|
80
|
+
exec_command = lambda { |ssh|
|
81
|
+
ssh.open_channel do |channel|
|
82
|
+
channel.exec(com_str) do |ch, success|
|
83
|
+
unless success
|
84
|
+
abort "FAILED: couldn't execute command (ssh.channel.exec
|
85
|
+
failure)"
|
86
|
+
end
|
87
|
+
|
88
|
+
channel.on_data do |ch, data| # stdout
|
89
|
+
@returns[ssh.host][:out] << data
|
90
|
+
end
|
91
|
+
|
92
|
+
channel.on_extended_data do |ch, type, data|
|
93
|
+
next unless type == 1 # only handle stderr
|
94
|
+
@returns[ssh.host][:out] << data
|
95
|
+
end
|
96
|
+
|
97
|
+
channel.on_request("exit-status") do |ch, data|
|
98
|
+
exit_code = data.read_long
|
99
|
+
@returns[ssh.host][:exit] = exit_code
|
100
|
+
end
|
101
|
+
|
102
|
+
channel.on_request("exit-signal") do |ch, data|
|
103
|
+
@returns[ssh.host][:sig] = data.read_long
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
ssh.loop
|
108
|
+
}
|
109
|
+
|
110
|
+
nodes.filter(options).each do |node|
|
111
|
+
@returns[node.host] = { :out => '' }
|
112
|
+
begin
|
113
|
+
connections << Net::SSH.start(node.host, node.user, :port => node.port, :paranoid => false,
|
114
|
+
:user_known_hosts_file => '/dev/null', :keys => [node.keypath],
|
115
|
+
:logger => Logger.new(options.debug.nil? ? $stderr : $stdout),
|
116
|
+
:verbose => (options.debug.nil? ? Logger::FATAL : Logger::DEBUG))
|
117
|
+
rescue Errno::ECONNREFUSED
|
118
|
+
$stderr.puts "ERROR: Connection refused on #{node.host}"
|
119
|
+
@returns.delete node.host
|
120
|
+
rescue SocketError
|
121
|
+
$stderr.puts "ERROR: nodename nor servname provided, or not known for #{node[:host]}"
|
122
|
+
@returns.delete node.host
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
connections.each { |ssh| exec_command.call(ssh) }
|
127
|
+
|
128
|
+
loop do
|
129
|
+
connections.delete_if { |ssh| !ssh.process(0.1) { |s| s.busy? } }
|
130
|
+
break if connections.empty?
|
131
|
+
end
|
132
|
+
|
133
|
+
# TODO: Print this out in a better way
|
134
|
+
# TODO: Print a special alert for exit codes > 0
|
135
|
+
|
136
|
+
pp @returns
|
137
|
+
end
|
138
|
+
|
139
|
+
def scp args, options
|
140
|
+
check_installed!
|
141
|
+
raise Rutty::BadUsage.new "Must supply a local path and a remote path" unless args.length == 2
|
142
|
+
raise Rutty::BadUsage.new "One of -a or --tags must be passed" if options.a.nil? and options.tags.nil?
|
143
|
+
raise Rutty::BadUsage.new "Use either -a or --tags, not both" if !options.a.nil? and !options.tags.nil?
|
144
|
+
|
145
|
+
require 'logger'
|
146
|
+
require 'net/ssh'
|
147
|
+
require 'net/scp'
|
148
|
+
require 'pp'
|
149
|
+
|
150
|
+
connections = []
|
151
|
+
|
152
|
+
remote_path = args.pop
|
153
|
+
local_path = args.pop
|
154
|
+
|
155
|
+
nodes.filter(options).each do |node|
|
156
|
+
begin
|
157
|
+
connections << Net::SSH.start(node.host, node.user, :port => node.port, :paranoid => false,
|
158
|
+
:user_known_hosts_file => '/dev/null', :keys => [node.keypath],
|
159
|
+
:logger => Logger.new(options.debug.nil? ? $stderr : $stdout),
|
160
|
+
:verbose => (options.debug.nil? ? Logger::FATAL : Logger::DEBUG))
|
161
|
+
rescue Errno::ECONNREFUSED
|
162
|
+
$stderr.puts "ERROR: Connection refused on #{node.host}"
|
163
|
+
rescue SocketError
|
164
|
+
$stderr.puts "ERROR: nodename nor servname provided, or not known for #{node.host}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
connections.each { |ssh| ssh.scp.upload! local_path, remote_path }
|
169
|
+
|
170
|
+
loop do
|
171
|
+
connections.delete_if { |ssh| !ssh.process(0.1) { |s| s.busy? } }
|
172
|
+
break if connections.empty?
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
data/lib/rutty/config.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# http://mjijackson.com/2010/02/flexible-ruby-config-objects
|
2
|
+
module Rutty
|
3
|
+
class Config
|
4
|
+
class << self
|
5
|
+
def load_config
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
data = YAML.load(File.open(Rutty::Consts::GENERAL_CONF).read)
|
9
|
+
Rutty::Config.new data
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(data={})
|
14
|
+
@data = {}
|
15
|
+
update!(data)
|
16
|
+
end
|
17
|
+
|
18
|
+
def update!(data)
|
19
|
+
data.each do |key, value|
|
20
|
+
self[key] = value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def [](key)
|
25
|
+
@data[key.to_sym]
|
26
|
+
end
|
27
|
+
|
28
|
+
def []=(key, value)
|
29
|
+
if value.class == Hash
|
30
|
+
@data[key.to_sym] = Config.new(value)
|
31
|
+
else
|
32
|
+
@data[key.to_sym] = value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_hash
|
37
|
+
@data
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_missing(sym, *args)
|
41
|
+
if sym.to_s =~ /(.+)=$/
|
42
|
+
self[$1] = args.first
|
43
|
+
else
|
44
|
+
self[sym]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/rutty/consts.rb
ADDED
data/lib/rutty/errors.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rutty/errors'
|
2
|
+
require 'rutty/consts'
|
3
|
+
require 'rutty/version'
|
4
|
+
|
5
|
+
module Rutty
|
6
|
+
module Helpers
|
7
|
+
def check_installed!
|
8
|
+
unless File.exists? Rutty::Consts::CONF_DIR
|
9
|
+
raise Rutty::NotInstalledError.new %Q(Can't find conf directory at #{Rutty::Consts::CONF_DIR}.
|
10
|
+
Run `rutty init' first. (Or rutty --help for usage))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_version
|
15
|
+
Rutty::Version::STRING
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/rutty/node.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rutty/config'
|
2
|
+
require 'rutty/consts'
|
3
|
+
|
4
|
+
module Rutty
|
5
|
+
class Node < Config
|
6
|
+
def initialize data, defaults = {}
|
7
|
+
merged_data = defaults.merge data
|
8
|
+
super merged_data
|
9
|
+
end
|
10
|
+
|
11
|
+
def has_tag? tag
|
12
|
+
self.tags.include? tag
|
13
|
+
end
|
14
|
+
|
15
|
+
def <=> other
|
16
|
+
self.host <=> other.host
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/rutty/nodes.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rutty/node'
|
2
|
+
require 'rutty/consts'
|
3
|
+
|
4
|
+
module Rutty
|
5
|
+
class Nodes < Array
|
6
|
+
class << self
|
7
|
+
def load_config
|
8
|
+
require 'yaml'
|
9
|
+
Rutty::Nodes.new YAML.load(File.open(Rutty::Consts::NODES_CONF).read)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def filter opts = {}
|
14
|
+
return self if opts[:all]
|
15
|
+
|
16
|
+
ary = Array.new self
|
17
|
+
|
18
|
+
ary.reject! { |n| n.keypath == opts[:keypath] } unless opts[:keypath].nil?
|
19
|
+
ary.reject! { |n| n.user == opts[:user] } unless opts[:user].nil?
|
20
|
+
ary.reject! { |n| n.port == opts[:port] } unless opts[:port].nil?
|
21
|
+
ary.reject! { |n| !(n.tags & opts[:tags]).empty? } unless opts[:tags].nil?
|
22
|
+
|
23
|
+
ary
|
24
|
+
end
|
25
|
+
|
26
|
+
def write_config
|
27
|
+
File.open(Rutty::Consts::NODES_CONF, 'w') do |f|
|
28
|
+
YAML.dump(self, f)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/rutty.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{rutty}
|
8
|
-
s.version = "
|
8
|
+
s.version = "2.0.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Josh Lindsey"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-11-01}
|
13
13
|
s.default_executable = %q{rutty}
|
14
14
|
s.description = %q{
|
15
15
|
RuTTY is a DSH (distributed / dancer's shell) written in Ruby. It's used to run commands
|
@@ -31,6 +31,15 @@ Gem::Specification.new do |s|
|
|
31
31
|
"Rakefile",
|
32
32
|
"VERSION",
|
33
33
|
"bin/rutty",
|
34
|
+
"lib/rutty.rb",
|
35
|
+
"lib/rutty/actions.rb",
|
36
|
+
"lib/rutty/config.rb",
|
37
|
+
"lib/rutty/consts.rb",
|
38
|
+
"lib/rutty/errors.rb",
|
39
|
+
"lib/rutty/helpers.rb",
|
40
|
+
"lib/rutty/node.rb",
|
41
|
+
"lib/rutty/nodes.rb",
|
42
|
+
"lib/rutty/version.rb",
|
34
43
|
"rutty.gemspec"
|
35
44
|
]
|
36
45
|
s.homepage = %q{http://github.com/jlindsey/rutty}
|
@@ -44,18 +53,21 @@ Gem::Specification.new do |s|
|
|
44
53
|
s.specification_version = 3
|
45
54
|
|
46
55
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
47
|
-
s.
|
56
|
+
s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
|
57
|
+
s.add_development_dependency(%q<jeweler>, [">= 1.4.0"])
|
48
58
|
s.add_runtime_dependency(%q<commander>, [">= 4.0.3"])
|
49
59
|
s.add_runtime_dependency(%q<net-ssh>, [">= 2.0.23"])
|
50
60
|
s.add_runtime_dependency(%q<net-scp>, [">= 1.0.4"])
|
51
61
|
else
|
52
62
|
s.add_dependency(%q<bundler>, [">= 1.0.0"])
|
63
|
+
s.add_dependency(%q<jeweler>, [">= 1.4.0"])
|
53
64
|
s.add_dependency(%q<commander>, [">= 4.0.3"])
|
54
65
|
s.add_dependency(%q<net-ssh>, [">= 2.0.23"])
|
55
66
|
s.add_dependency(%q<net-scp>, [">= 1.0.4"])
|
56
67
|
end
|
57
68
|
else
|
58
69
|
s.add_dependency(%q<bundler>, [">= 1.0.0"])
|
70
|
+
s.add_dependency(%q<jeweler>, [">= 1.4.0"])
|
59
71
|
s.add_dependency(%q<commander>, [">= 4.0.3"])
|
60
72
|
s.add_dependency(%q<net-ssh>, [">= 2.0.23"])
|
61
73
|
s.add_dependency(%q<net-scp>, [">= 1.0.4"])
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rutty
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
|
-
-
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version:
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 2.0.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Josh Lindsey
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-11-01 00:00:00 -04:00
|
19
19
|
default_executable: rutty
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -32,12 +32,28 @@ dependencies:
|
|
32
32
|
- 0
|
33
33
|
- 0
|
34
34
|
version: 1.0.0
|
35
|
-
type: :
|
35
|
+
type: :development
|
36
36
|
version_requirements: *id001
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
|
-
name:
|
38
|
+
name: jeweler
|
39
39
|
prerelease: false
|
40
40
|
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 7
|
46
|
+
segments:
|
47
|
+
- 1
|
48
|
+
- 4
|
49
|
+
- 0
|
50
|
+
version: 1.4.0
|
51
|
+
type: :development
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: commander
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
57
|
none: false
|
42
58
|
requirements:
|
43
59
|
- - ">="
|
@@ -49,11 +65,11 @@ dependencies:
|
|
49
65
|
- 3
|
50
66
|
version: 4.0.3
|
51
67
|
type: :runtime
|
52
|
-
version_requirements: *
|
68
|
+
version_requirements: *id003
|
53
69
|
- !ruby/object:Gem::Dependency
|
54
70
|
name: net-ssh
|
55
71
|
prerelease: false
|
56
|
-
requirement: &
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
57
73
|
none: false
|
58
74
|
requirements:
|
59
75
|
- - ">="
|
@@ -65,11 +81,11 @@ dependencies:
|
|
65
81
|
- 23
|
66
82
|
version: 2.0.23
|
67
83
|
type: :runtime
|
68
|
-
version_requirements: *
|
84
|
+
version_requirements: *id004
|
69
85
|
- !ruby/object:Gem::Dependency
|
70
86
|
name: net-scp
|
71
87
|
prerelease: false
|
72
|
-
requirement: &
|
88
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
73
89
|
none: false
|
74
90
|
requirements:
|
75
91
|
- - ">="
|
@@ -81,7 +97,7 @@ dependencies:
|
|
81
97
|
- 4
|
82
98
|
version: 1.0.4
|
83
99
|
type: :runtime
|
84
|
-
version_requirements: *
|
100
|
+
version_requirements: *id005
|
85
101
|
description: "\n RuTTY is a DSH (distributed / dancer's shell) written in Ruby. It's used to run commands \n on multiple remote servers at once, based on a tagging system. Also allows for multiple\n SCP-style uploads.\n "
|
86
102
|
email: josh@cloudspace.com
|
87
103
|
executables:
|
@@ -100,6 +116,15 @@ files:
|
|
100
116
|
- Rakefile
|
101
117
|
- VERSION
|
102
118
|
- bin/rutty
|
119
|
+
- lib/rutty.rb
|
120
|
+
- lib/rutty/actions.rb
|
121
|
+
- lib/rutty/config.rb
|
122
|
+
- lib/rutty/consts.rb
|
123
|
+
- lib/rutty/errors.rb
|
124
|
+
- lib/rutty/helpers.rb
|
125
|
+
- lib/rutty/node.rb
|
126
|
+
- lib/rutty/nodes.rb
|
127
|
+
- lib/rutty/version.rb
|
103
128
|
- rutty.gemspec
|
104
129
|
has_rdoc: true
|
105
130
|
homepage: http://github.com/jlindsey/rutty
|