pdo 0.0.3 → 0.0.5
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/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 :
|