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