pdo 0.0.3 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/pdo +4 -7
- data/lib/pdo.rb +66 -64
- data/lib/pdo/host.rb +156 -154
- data/lib/pdo/logging.rb +17 -15
- data/lib/pdo/opts.rb +95 -0
- data/lib/pdo/task.rb +79 -77
- data/lib/pdo/version.rb +2 -2
- data/pdo.gemspec +1 -1
- metadata +3 -3
- data/lib/pdo/pdoopts.rb +0 -94
data/bin/pdo
CHANGED
@@ -3,17 +3,15 @@
|
|
3
3
|
lib = File.expand_path("#{File.dirname(__FILE__)}/../lib")
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include? lib
|
5
5
|
|
6
|
-
#require 'pdo/version'
|
7
|
-
|
8
6
|
require 'ostruct'
|
9
7
|
require 'pdo'
|
10
8
|
require 'pdo/logging'
|
11
9
|
require 'pdo/host'
|
12
|
-
require 'pdo/
|
10
|
+
require 'pdo/opts'
|
13
11
|
require 'pdo/task'
|
14
12
|
require 'pp'
|
15
13
|
|
16
|
-
include
|
14
|
+
include PDO
|
17
15
|
|
18
16
|
opts = OpenStruct.new
|
19
17
|
# setting default values
|
@@ -26,7 +24,7 @@ opts.nohush = false
|
|
26
24
|
|
27
25
|
begin
|
28
26
|
# parse ARGV; defaults in opts is overwritten if needed
|
29
|
-
opts =
|
27
|
+
opts = Opts.parse(ARGV, opts)
|
30
28
|
logger.debug { opts }
|
31
29
|
rescue => err
|
32
30
|
logger.error { "#{err.class}: #{err.message}" }
|
@@ -34,7 +32,6 @@ rescue => err
|
|
34
32
|
exit 1
|
35
33
|
end
|
36
34
|
|
37
|
-
include Host
|
38
35
|
Host.load_file opts.host_definition_file
|
39
36
|
|
40
37
|
excludes = []
|
@@ -82,7 +79,7 @@ if opts.groups then
|
|
82
79
|
end
|
83
80
|
end
|
84
81
|
end
|
85
|
-
pdo =
|
82
|
+
pdo = Worker.new(tasks, opts.thread_num)
|
86
83
|
pdo.run
|
87
84
|
else
|
88
85
|
opts.groups.each do |key|
|
data/lib/pdo.rb
CHANGED
@@ -2,88 +2,90 @@
|
|
2
2
|
|
3
3
|
require 'pdo/logging'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
module PDO
|
6
|
+
class Worker
|
7
|
+
include Process
|
8
|
+
include Logging
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def initialize(tasks, thread_num)
|
11
|
+
@tasks = tasks
|
12
|
+
@thread_num = thread_num
|
13
|
+
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
15
|
+
def run
|
16
|
+
# run first spawns a set of threads, tells them to execute the task
|
17
|
+
# queue. it then loop through the current Thread list (note
|
18
|
+
# Thread.list returns all running and sleeping threads), and outputs
|
19
|
+
# those new data.
|
20
|
+
# for the spawned threads, they execute one task, then wait for their
|
21
|
+
# data to be picked up by the main thread, then do the next task.
|
21
22
|
|
22
|
-
|
23
|
+
return 1 if @tasks.size == 0
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
25
|
+
n = @thread_num < @tasks.size ? @thread_num : @tasks.size
|
26
|
+
1.upto(n) do
|
27
|
+
Thread.new do
|
28
|
+
while true do
|
29
|
+
logger.info "#{Thread.current.object_id} started."
|
30
|
+
begin
|
31
|
+
execute(@tasks.next)
|
32
|
+
# presumably new data is ready, thus i stop.
|
33
|
+
# main thread will read :output and then wakes me up.
|
34
|
+
Thread.stop
|
35
|
+
rescue
|
36
|
+
logger.info "No more task for #{Thread.current.object_id}."
|
37
|
+
break
|
38
|
+
end
|
37
39
|
end
|
38
40
|
end
|
39
41
|
end
|
40
|
-
end
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
loop do
|
44
|
+
# break because the only left thread must be the main thread.
|
45
|
+
break if Thread.list.size == 1
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
47
|
+
Thread.list.each do |t|
|
48
|
+
next if t == Thread.main
|
49
|
+
if t.key? :output and t.key? :new_data and t[:new_data] then
|
50
|
+
logger.info "#{t.object_id} finished."
|
51
|
+
puts "=== #{t[:target]} ==="
|
52
|
+
t[:output].each {|x| puts x}
|
53
|
+
# puts t[:output].join('').gsub("\n", ' | ').chomp(' | ')
|
54
|
+
t[:new_data] = false
|
55
|
+
# wakes up the thread
|
56
|
+
t.run
|
57
|
+
end
|
56
58
|
end
|
57
59
|
end
|
58
60
|
end
|
59
|
-
end
|
60
61
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
62
|
+
def execute(task)
|
63
|
+
# execute forks a child process to run the task, then saves the
|
64
|
+
# output from the child process into a thread local variable, marks
|
65
|
+
# the :new_data tag.
|
65
66
|
|
66
|
-
|
67
|
+
raise "no task" if task.nil?
|
67
68
|
|
68
|
-
|
69
|
+
target, cmd = task
|
69
70
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
71
|
+
open("|-") do |child_io|
|
72
|
+
if child_io
|
73
|
+
# The waitpid statement below causes the program to hang when
|
74
|
+
# running some commands, such as wget.
|
75
|
+
# waitpid child_io.pid
|
76
|
+
Thread.current[:output] = child_io.readlines
|
77
|
+
Thread.current[:new_data] = true
|
78
|
+
Thread.current[:target] = target
|
79
|
+
else
|
80
|
+
STDIN.close
|
81
|
+
STDERR.reopen(STDOUT)
|
82
|
+
exec(*cmd)
|
83
|
+
end
|
82
84
|
end
|
83
|
-
end
|
84
85
|
|
85
|
-
|
86
|
+
end
|
86
87
|
|
87
|
-
end
|
88
|
+
end # class Worker
|
89
|
+
end # module PDO
|
88
90
|
|
89
91
|
# vim: set et ts=2 sts=2 sw=2 si sta :
|
data/lib/pdo/host.rb
CHANGED
@@ -4,186 +4,188 @@ require 'pp'
|
|
4
4
|
require 'yaml'
|
5
5
|
require_relative 'logging'
|
6
6
|
|
7
|
-
module
|
8
|
-
|
9
|
-
include Logging
|
10
|
-
|
11
|
-
@@recursive_level = 9
|
12
|
-
def self.recursive_level=(level)
|
13
|
-
@@recursive_level = level
|
14
|
-
end
|
15
|
-
def self.recursive_level
|
16
|
-
@@recursive_level
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.load_file(def_file=nil)
|
20
|
-
if def_file then
|
21
|
-
files = [ def_file ]
|
22
|
-
else
|
23
|
-
files = [ "/etc/pdo/pdo.yaml", "#{ENV['HOME']}/.pdo/pdo.yaml", ]
|
24
|
-
end
|
25
|
-
|
26
|
-
hosts = {}
|
27
|
-
files.each do |f|
|
28
|
-
begin
|
29
|
-
hosts.update(YAML::load_file(f))
|
30
|
-
rescue => ex
|
31
|
-
logger.warn { "#{ex.class}: #{ex.message}" }
|
32
|
-
logger.debug { ex.backtrace.join "\n" }
|
33
|
-
end
|
34
|
-
end
|
35
|
-
return @@host_hash = hosts
|
7
|
+
module PDO
|
8
|
+
module Host
|
36
9
|
|
37
|
-
|
10
|
+
include Logging
|
38
11
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
level += 1
|
47
|
-
if level > @@recursive_level then
|
48
|
-
logger.warn "circle detected in the host definition file."
|
49
|
-
return []
|
12
|
+
@@recursive_level = 9
|
13
|
+
def self.recursive_level=(level)
|
14
|
+
@@recursive_level = level
|
15
|
+
end
|
16
|
+
def self.recursive_level
|
17
|
+
@@recursive_level
|
50
18
|
end
|
51
19
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
20
|
+
def self.load_file(def_file=nil)
|
21
|
+
if def_file then
|
22
|
+
files = [ def_file ]
|
23
|
+
else
|
24
|
+
files = [ "/etc/pdo/pdo.yaml", "#{ENV['HOME']}/.pdo/pdo.yaml", ]
|
25
|
+
end
|
26
|
+
|
27
|
+
hosts = {}
|
28
|
+
files.each do |f|
|
29
|
+
begin
|
30
|
+
hosts.update(YAML::load_file(f))
|
31
|
+
rescue => ex
|
32
|
+
logger.warn { "#{ex.class}: #{ex.message}" }
|
33
|
+
logger.debug { ex.backtrace.join "\n" }
|
63
34
|
end
|
64
|
-
rescue => ex
|
65
|
-
logger.warn {
|
66
|
-
"failed to get hash value for #{group.inspect}. "\
|
67
|
-
'possibly an error in the yaml file.'
|
68
|
-
}
|
69
|
-
logger.debug { ex.backtrace.join "\n" }
|
70
|
-
return []
|
71
35
|
end
|
36
|
+
return @@host_hash = hosts
|
37
|
+
|
72
38
|
end
|
39
|
+
|
40
|
+
def expand_group(group, level)
|
41
|
+
# given a group, recursively expand it into a list of hosts.
|
42
|
+
# if the group can't expand to a list of hosts, return that group.
|
43
|
+
# if there are exception expanding the group, return an empty list.
|
73
44
|
|
74
|
-
|
75
|
-
return [] if list_of_hosts.nil?
|
45
|
+
hosts = []
|
76
46
|
|
77
|
-
|
78
|
-
|
79
|
-
|
47
|
+
level += 1
|
48
|
+
if level > @@recursive_level then
|
49
|
+
logger.warn "circle detected in the host definition file."
|
50
|
+
return []
|
80
51
|
end
|
81
|
-
|
82
|
-
|
83
|
-
|
52
|
+
|
53
|
+
# get the hash value
|
54
|
+
keys = group.gsub(/^\//,'').split('/')
|
55
|
+
list_of_hosts = @@host_hash
|
56
|
+
keys.each do |k|
|
57
|
+
begin
|
58
|
+
if list_of_hosts.key? k then
|
59
|
+
list_of_hosts = list_of_hosts[k]
|
60
|
+
else
|
61
|
+
# if any part of the group name is not a key in the
|
62
|
+
# host hash, then return the group.
|
63
|
+
return [ group ]
|
64
|
+
end
|
65
|
+
rescue => ex
|
84
66
|
logger.warn {
|
85
|
-
"
|
67
|
+
"failed to get hash value for #{group.inspect}. "\
|
86
68
|
'possibly an error in the yaml file.'
|
87
69
|
}
|
88
|
-
|
70
|
+
logger.debug { ex.backtrace.join "\n" }
|
71
|
+
return []
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# return empty array if the group is empty.
|
76
|
+
return [] if list_of_hosts.nil?
|
77
|
+
|
78
|
+
if list_of_hosts.is_a? Hash then
|
79
|
+
list_of_hosts.keys.each do |key|
|
80
|
+
hosts += expand_group("#{group}/#{key}".squeeze('/'), level)
|
89
81
|
end
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
82
|
+
elsif list_of_hosts.is_a? Array then
|
83
|
+
list_of_hosts.each do |h|
|
84
|
+
if not h.is_a? String then
|
85
|
+
logger.warn {
|
86
|
+
"invalid host or group format: #{h.inspect}\n"\
|
87
|
+
'possibly an error in the yaml file.'
|
88
|
+
}
|
89
|
+
next
|
90
|
+
end
|
91
|
+
if h.include? "/" then
|
92
|
+
hosts += expand_group(h, level)
|
93
|
+
else
|
94
|
+
hosts << h
|
95
|
+
end
|
94
96
|
end
|
97
|
+
else
|
98
|
+
logger.fatal "I don't know how to handle this. "\
|
99
|
+
'possibly an error in the yaml file.'
|
100
|
+
exit 1
|
95
101
|
end
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
102
|
+
return hosts
|
103
|
+
|
104
|
+
end # expand_group
|
105
|
+
private :expand_group
|
106
|
+
|
107
|
+
def get_hosts(group, step)
|
108
|
+
hosts = []
|
109
|
+
hosts += expand_group(group, 0)
|
110
|
+
hosts.uniq!
|
111
|
+
hosts = stepping hosts, step
|
112
|
+
return hosts
|
113
|
+
|
100
114
|
end
|
101
|
-
return hosts
|
102
|
-
|
103
|
-
end # expand_group
|
104
|
-
private :expand_group
|
105
115
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
116
|
+
def stepping(hosts, step)
|
117
|
+
unless hosts.is_a? Array or step.is_a? Array then
|
118
|
+
logger.warn "both hosts or step should be array."
|
119
|
+
return nil
|
120
|
+
end
|
121
|
+
|
122
|
+
start, stride = step[0].to_i, step[1].to_i
|
123
|
+
# if stride > 0, stepping forward
|
124
|
+
# if stride < 0, stepping backward
|
125
|
+
# if stride == 0, no stepping
|
126
|
+
# if start or stride makes no sense, no stepping either
|
127
|
+
index = case
|
128
|
+
when stride > 0 then
|
129
|
+
(start-1...hosts.size).step(stride).to_a
|
130
|
+
when stride < 0 then
|
131
|
+
(-start+1..0).step(-stride).to_a.map {|x| -x}
|
132
|
+
else []
|
133
|
+
end
|
134
|
+
|
135
|
+
unless index.empty? then
|
136
|
+
alist = []
|
137
|
+
index.each do |i|
|
138
|
+
alist << hosts[i]
|
139
|
+
end
|
140
|
+
hosts = alist
|
141
|
+
end
|
114
142
|
|
115
|
-
|
116
|
-
unless hosts.is_a? Array or step.is_a? Array then
|
117
|
-
logger.warn "both hosts or step should be array."
|
118
|
-
return nil
|
143
|
+
return hosts
|
119
144
|
end
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
# if stride < 0, stepping backward
|
124
|
-
# if stride == 0, no stepping
|
125
|
-
# if start or stride makes no sense, no stepping either
|
126
|
-
index = case
|
127
|
-
when stride > 0 then
|
128
|
-
(start-1...hosts.size).step(stride).to_a
|
129
|
-
when stride < 0 then
|
130
|
-
(-start+1..0).step(-stride).to_a.map {|x| -x}
|
131
|
-
else []
|
132
|
-
end
|
133
|
-
|
134
|
-
unless index.empty? then
|
135
|
-
alist = []
|
136
|
-
index.each do |i|
|
137
|
-
alist << hosts[i]
|
138
|
-
end
|
139
|
-
hosts = alist
|
145
|
+
|
146
|
+
def show_hosts
|
147
|
+
pp @@host_hash
|
140
148
|
end
|
141
149
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
150
|
+
def get_sub_groups(group)
|
151
|
+
|
152
|
+
keys = group.gsub(/^\//, '').split('/')
|
153
|
+
pointer = @@host_hash
|
154
|
+
keys.each do |k|
|
155
|
+
begin
|
156
|
+
if pointer.key? k then
|
157
|
+
pointer = pointer[k]
|
158
|
+
else
|
159
|
+
# if any part of the given group name is not a key in the
|
160
|
+
# host hash, then return an empty array.
|
161
|
+
return [ ]
|
162
|
+
end
|
163
|
+
rescue => ex
|
164
|
+
logger.warn {
|
165
|
+
"failed to get hash value for #{group.inspect}. "\
|
166
|
+
'maybe a wrong key is specifid?'
|
167
|
+
}
|
168
|
+
logger.debug { ex.backtrace.join "\n" }
|
169
|
+
return []
|
161
170
|
end
|
162
|
-
rescue => ex
|
163
|
-
logger.warn {
|
164
|
-
"failed to get hash value for #{group.inspect}. "\
|
165
|
-
'maybe a wrong key is specifid?'
|
166
|
-
}
|
167
|
-
logger.debug { ex.backtrace.join "\n" }
|
168
|
-
return []
|
169
171
|
end
|
170
|
-
end
|
171
172
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
173
|
+
# return empty array if the group is empty.
|
174
|
+
return [] if pointer.nil?
|
175
|
+
|
176
|
+
if pointer.is_a? Hash then
|
177
|
+
return pointer.keys
|
178
|
+
elsif pointer.is_a? Array then
|
179
|
+
return pointer
|
180
|
+
else
|
181
|
+
logger.fatal "I don't know how to handle this. "\
|
182
|
+
'possibly an error in the yaml file.'
|
183
|
+
exit 1
|
184
|
+
end
|
185
|
+
|
183
186
|
end
|
184
|
-
|
185
|
-
end
|
186
187
|
|
187
|
-
end # module Host
|
188
|
+
end # module Host
|
189
|
+
end # module PDO
|
188
190
|
|
189
191
|
# vim: set et ts=2 sts=2 sw=2 si sta :
|
data/lib/pdo/logging.rb
CHANGED
@@ -4,28 +4,30 @@ require 'log4r'
|
|
4
4
|
require 'log4r/yamlconfigurator'
|
5
5
|
require 'log4r/outputter/datefileoutputter'
|
6
6
|
|
7
|
-
module
|
7
|
+
module PDO
|
8
|
+
module Logging
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
include Log4r
|
11
|
+
# set the default log directory
|
12
|
+
Log4r::YamlConfigurator['LOGDIR'] =
|
13
|
+
File.expand_path("#{File.dirname(__FILE__)}/../../log")
|
13
14
|
|
14
|
-
|
15
|
+
def logger
|
15
16
|
|
16
|
-
|
17
|
+
return @logger if @logger
|
17
18
|
|
18
|
-
|
19
|
+
conf_dir = File.expand_path("#{File.dirname(__FILE__)}/../../conf")
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
[conf_dir, '/etc/pdo', "#{ENV['HOME']}/.pdo"].each { |dir|
|
22
|
+
conf = "#{dir}/log4r.yaml"
|
23
|
+
Log4r::YamlConfigurator.load_yaml_file conf if File.exists? conf
|
24
|
+
}
|
24
25
|
|
25
|
-
|
26
|
+
@logger = Logger[self.class.to_s] || Logger['pdo']
|
26
27
|
|
27
|
-
|
28
|
+
end
|
28
29
|
|
29
|
-
end # module Logging
|
30
|
+
end # module Logging
|
31
|
+
end # module PDO
|
30
32
|
|
31
33
|
# vim: set et ts=2 sts=2 sw=2 si sta :
|
data/lib/pdo/opts.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
module PDO
|
6
|
+
class Opts
|
7
|
+
def self.parse(args, opts)
|
8
|
+
op = OptionParser.new
|
9
|
+
op.set_summary_width 15
|
10
|
+
|
11
|
+
op.banner = "pdo [opts]"
|
12
|
+
op.separator ""
|
13
|
+
op.separator "Specific options:"
|
14
|
+
|
15
|
+
op.on('-c CMD', 'the command to be executed',
|
16
|
+
"if CMD equals to '-', then read command from stdin") do |cmd|
|
17
|
+
if cmd == '-' then
|
18
|
+
opts.cmd = $stdin.read
|
19
|
+
else
|
20
|
+
opts.cmd = cmd
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
op.on('--count', 'count the number of hosts') do |count|
|
25
|
+
opts.count = true
|
26
|
+
end
|
27
|
+
|
28
|
+
op.on('--enum', 'enumerate the hosts') do |enum|
|
29
|
+
opts.enum = true
|
30
|
+
end
|
31
|
+
|
32
|
+
op.on('-f <name>',
|
33
|
+
'name of the alternative host definition file') do |fn|
|
34
|
+
opts.host_definition_file = fn
|
35
|
+
end
|
36
|
+
|
37
|
+
op.on('-g <name1,name2,...>', Array,
|
38
|
+
'comma separated group or host names') do |groups|
|
39
|
+
opts.groups = groups
|
40
|
+
end
|
41
|
+
|
42
|
+
op.on('-l', 'run the command CMD locally') do
|
43
|
+
opts.local = true
|
44
|
+
end
|
45
|
+
|
46
|
+
op.on('--step <m,n>', Array,
|
47
|
+
'stepping the hosts, m and n must be integers;',
|
48
|
+
'if n > 0, stepping forward; if n < 0 stepping',
|
49
|
+
'backward') do |step|
|
50
|
+
opts.step = [step[0].to_i, step[1].to_i]
|
51
|
+
end
|
52
|
+
|
53
|
+
op.on('--sshopts <key1=val1,key2=val2,...>', Array,
|
54
|
+
'ssh options as described in "man ssh_config"') do |sshopts|
|
55
|
+
opts.sshopts = {} unless opts.sshopts
|
56
|
+
sshopts.each do |opt|
|
57
|
+
key, val = opt.split '='
|
58
|
+
opts.sshopts[:"#{key}"] = val if key and val
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
op.on('-t INTEGER', Integer, 'number of threads') do |thread_num|
|
63
|
+
if thread_num < 0 then
|
64
|
+
raise ArgumentError,
|
65
|
+
"thread number must be greater or equal to zero"
|
66
|
+
end
|
67
|
+
opts.thread_num = thread_num
|
68
|
+
end
|
69
|
+
|
70
|
+
op.on('-x <name1,name2,...>', Array,
|
71
|
+
'comma separated group or host names, which should be',
|
72
|
+
'excluded from the final list') do |e_groups|
|
73
|
+
opts.e_groups = e_groups
|
74
|
+
end
|
75
|
+
|
76
|
+
op.on('-y', 'do not confirm, execute immediately') do
|
77
|
+
opts.confirm_before_execute = false
|
78
|
+
end
|
79
|
+
|
80
|
+
op.separator ""
|
81
|
+
op.separator "Common options:"
|
82
|
+
|
83
|
+
op.on_tail('-h', '--help', 'this help') do
|
84
|
+
puts op.help
|
85
|
+
exit 0
|
86
|
+
end
|
87
|
+
|
88
|
+
op.parse!(args)
|
89
|
+
return opts
|
90
|
+
|
91
|
+
end # self.parse()
|
92
|
+
end # class Opts
|
93
|
+
end # module PDO
|
94
|
+
|
95
|
+
# vim: set et ts=2 sts=2 sw=2 si sta :
|
data/lib/pdo/task.rb
CHANGED
@@ -2,102 +2,104 @@
|
|
2
2
|
|
3
3
|
require_relative 'logging'
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
module PDO
|
6
|
+
class SSHCmd
|
7
|
+
include Logging
|
7
8
|
|
8
|
-
|
9
|
+
def initialize(local, sshopts)
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
def form(host, cmd)
|
26
|
-
if host.match(/^(?:(\w+)@)?(\w+(?:\.\w+)*)(?::(\d+))?$/) then
|
27
|
-
user, host, port = $1, $2, $3
|
11
|
+
@defaults = {
|
12
|
+
:user => 'root',
|
13
|
+
:port => '22',
|
14
|
+
:ssh => '/usr/bin/ssh',
|
15
|
+
:sshopts => {
|
16
|
+
:ConnectTimeout => '60',
|
17
|
+
:StrictHostKeyChecking => 'no',
|
18
|
+
},
|
19
|
+
}
|
20
|
+
@local = local
|
21
|
+
@sshopts = @defaults[:sshopts]
|
22
|
+
@sshopts = @defaults[:sshopts].update sshopts if sshopts
|
23
|
+
logger.debug { @sshopts.inspect }
|
28
24
|
end
|
25
|
+
|
26
|
+
def form(host, cmd)
|
27
|
+
if host.match(/^(?:(\w+)@)?(\w+(?:\.\w+)*)(?::(\d+))?$/) then
|
28
|
+
user, host, port = $1, $2, $3
|
29
|
+
end
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
31
|
+
cmd = cmd.dup # without dup, all tasks refer to the same
|
32
|
+
cmd.strip!
|
33
|
+
cmd.gsub! '_USER_', user ? user : @defaults[:user]
|
34
|
+
cmd.gsub! '_HOST_', host
|
35
|
+
cmd.gsub! '_PORT_', port ? port : @defaults[:port]
|
36
|
+
# when opts.cmd == '-', the command is read from stdin.
|
37
|
+
# in this case multiple lines can be entered. here I'm doing some
|
38
|
+
# simple substitution for "\n".
|
39
|
+
cmd.gsub! "\n", '; '
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
41
|
+
# escape "`$ characters in cmd, unless they're already escaped.
|
42
|
+
if RUBY_VERSION.to_f < 1.9 then
|
43
|
+
# v1.8 dose not support negative look-behind assertion, thus
|
44
|
+
# doing it in 2 steps.
|
45
|
+
if cmd.match /["`$]/ then
|
46
|
+
if not cmd.match /['\\]["`$]/ then
|
47
|
+
cmd.gsub! /(["`$])/, '\\\\\1'
|
48
|
+
end
|
47
49
|
end
|
50
|
+
else
|
51
|
+
cmd.gsub! /(?<!['\\])(["`$])/, '\\\\\1'
|
48
52
|
end
|
49
|
-
else
|
50
|
-
cmd.gsub! /(?<!['\\])(["`$])/, '\\\\\1'
|
51
|
-
end
|
52
53
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
54
|
+
if @local then
|
55
|
+
return cmd
|
56
|
+
else
|
57
|
+
ssh = @defaults[:ssh]
|
58
|
+
ssh = [ ssh, '-l', "#{user}"].join ' ' if user
|
59
|
+
ssh = [ ssh, '-p', "#{port}"].join ' ' if port
|
60
|
+
@sshopts.each do |k, v|
|
61
|
+
ssh = [ssh, '-o', "#{k}=#{v}"].join ' '
|
62
|
+
end
|
63
|
+
ssh = [ ssh, "#{host}", "\"#{cmd}\""].join ' '
|
64
|
+
return ssh
|
61
65
|
end
|
62
|
-
ssh = [ ssh, "#{host}", "\"#{cmd}\""].join ' '
|
63
|
-
return ssh
|
64
66
|
end
|
65
67
|
end
|
66
|
-
end
|
67
68
|
|
68
|
-
class Task
|
69
|
-
|
69
|
+
class Task
|
70
|
+
include Logging
|
70
71
|
|
71
|
-
|
72
|
-
|
73
|
-
|
72
|
+
def initialize
|
73
|
+
@task_q = Queue.new
|
74
|
+
end
|
74
75
|
|
75
|
-
|
76
|
-
|
77
|
-
|
76
|
+
def add(task)
|
77
|
+
@task_q << task
|
78
|
+
end
|
78
79
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
80
|
+
def next
|
81
|
+
begin
|
82
|
+
@task_q.deq(non_block=true)
|
83
|
+
rescue ThreadError
|
84
|
+
nil
|
85
|
+
end
|
84
86
|
end
|
85
|
-
end
|
86
87
|
|
87
|
-
|
88
|
-
|
89
|
-
|
88
|
+
def size
|
89
|
+
@task_q.length
|
90
|
+
end
|
90
91
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
92
|
+
def print_all
|
93
|
+
tmp_q = Queue.new
|
94
|
+
while not @task_q.empty? do
|
95
|
+
task = @task_q.deq
|
96
|
+
printf "%s\n", task
|
97
|
+
tmp_q << task
|
98
|
+
end
|
99
|
+
@task_q = tmp_q
|
97
100
|
end
|
98
|
-
@task_q = tmp_q
|
99
|
-
end
|
100
101
|
|
101
|
-
end
|
102
|
+
end # class SSHCmd
|
103
|
+
end # module PDO
|
102
104
|
|
103
105
|
# vim: set et ts=2 sts=2 sw=2 si sta :
|
data/lib/pdo/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module
|
2
|
-
VERSION = "0.0.
|
1
|
+
module PDO
|
2
|
+
VERSION = "0.0.5"
|
3
3
|
end
|
data/pdo.gemspec
CHANGED
@@ -6,7 +6,7 @@ require 'pdo/version'
|
|
6
6
|
Gem::Specification.new do |gem|
|
7
7
|
gem.name = 'pdo'
|
8
8
|
gem.license = 'MIT'
|
9
|
-
gem.version =
|
9
|
+
gem.version = PDO::VERSION
|
10
10
|
gem.authors = ['bqbn']
|
11
11
|
gem.email = ['bqbn@openken.com']
|
12
12
|
gem.description = 'pdo is a wrapper for running commands on or '\
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pdo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-10-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: log4r
|
@@ -47,7 +47,7 @@ files:
|
|
47
47
|
- lib/pdo.rb
|
48
48
|
- lib/pdo/host.rb
|
49
49
|
- lib/pdo/logging.rb
|
50
|
-
- lib/pdo/
|
50
|
+
- lib/pdo/opts.rb
|
51
51
|
- lib/pdo/task.rb
|
52
52
|
- lib/pdo/version.rb
|
53
53
|
- log/.gitignore
|
data/lib/pdo/pdoopts.rb
DELETED
@@ -1,94 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'optparse'
|
4
|
-
|
5
|
-
class PdoOpts
|
6
|
-
def self.parse(args, opts)
|
7
|
-
op = OptionParser.new
|
8
|
-
op.set_summary_width 15
|
9
|
-
|
10
|
-
op.banner = "pdo [opts]"
|
11
|
-
op.separator ""
|
12
|
-
op.separator "Specific options:"
|
13
|
-
|
14
|
-
op.on('-c CMD', 'the command to be executed',
|
15
|
-
"if CMD equals to '-', then read command from stdin") do |cmd|
|
16
|
-
if cmd == '-' then
|
17
|
-
opts.cmd = $stdin.read
|
18
|
-
else
|
19
|
-
opts.cmd = cmd
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
op.on('--count', 'count the number of hosts') do |count|
|
24
|
-
opts.count = true
|
25
|
-
end
|
26
|
-
|
27
|
-
op.on('--enum', 'enumerate the hosts') do |enum|
|
28
|
-
opts.enum = true
|
29
|
-
end
|
30
|
-
|
31
|
-
op.on('-f <name>',
|
32
|
-
'name of the alternative host definition file') do |fn|
|
33
|
-
opts.host_definition_file = fn
|
34
|
-
end
|
35
|
-
|
36
|
-
op.on('-g <name1,name2,...>', Array,
|
37
|
-
'comma separated group or host names') do |groups|
|
38
|
-
opts.groups = groups
|
39
|
-
end
|
40
|
-
|
41
|
-
op.on('-l', 'run the command CMD locally') do
|
42
|
-
opts.local = true
|
43
|
-
end
|
44
|
-
|
45
|
-
op.on('--step <m,n>', Array,
|
46
|
-
'stepping the hosts, m and n must be integers;',
|
47
|
-
'if n > 0, stepping forward; if n < 0 stepping',
|
48
|
-
'backward') do |step|
|
49
|
-
opts.step = [step[0].to_i, step[1].to_i]
|
50
|
-
end
|
51
|
-
|
52
|
-
op.on('--sshopts <key1=val1,key2=val2,...>', Array,
|
53
|
-
'ssh options as described in "man ssh_config"') do |sshopts|
|
54
|
-
opts.sshopts = {} unless opts.sshopts
|
55
|
-
sshopts.each do |opt|
|
56
|
-
key, val = opt.split '='
|
57
|
-
opts.sshopts[:"#{key}"] = val if key and val
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
op.on('-t INTEGER', Integer, 'number of threads') do |thread_num|
|
62
|
-
if thread_num < 0 then
|
63
|
-
raise ArgumentError,
|
64
|
-
"thread number must be greater or equal to zero"
|
65
|
-
end
|
66
|
-
opts.thread_num = thread_num
|
67
|
-
end
|
68
|
-
|
69
|
-
op.on('-x <name1,name2,...>', Array,
|
70
|
-
'comma separated group or host names, which should be',
|
71
|
-
'excluded from the final list') do |e_groups|
|
72
|
-
opts.e_groups = e_groups
|
73
|
-
end
|
74
|
-
|
75
|
-
op.on('-y', 'do not confirm, execute immediately') do
|
76
|
-
opts.confirm_before_execute = false
|
77
|
-
end
|
78
|
-
|
79
|
-
op.separator ""
|
80
|
-
op.separator "Common options:"
|
81
|
-
|
82
|
-
op.on_tail('-h', '--help', 'this help') do
|
83
|
-
puts op.help
|
84
|
-
exit 0
|
85
|
-
end
|
86
|
-
|
87
|
-
op.parse!(args)
|
88
|
-
return opts
|
89
|
-
|
90
|
-
end # self.parse()
|
91
|
-
|
92
|
-
end
|
93
|
-
|
94
|
-
# vim: set et ts=2 sts=2 sw=2 si sta :
|