pdo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZjJkNzY1MWMyNjZiODFiMjY2ZDliZDIyZGMyNTkxNjM1MmJjNTQwYg==
5
+ data.tar.gz: !binary |-
6
+ NzVmYWRhOTQwMzdmNGYwOGY5YWVlN2ZjMzIzM2Y3ZWQ5MThjMjRmMQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ YjNlM2QyMGEyY2M0Nzg3YzFlODhlNWQzMGNlYWE4NGRhZmI0NWQ1NmYyYWZi
10
+ NTk3MDRiNWZjOWY2ZDFkYjQ5ZGI5ZDExZDU2ZGQwNmUwYWM0NjU1NzBjMWJi
11
+ OWQzMzZjYmRjZTI2YzE3MDNkMGFmMjdkMjc0MDU5Mzk4YzYyMmM=
12
+ data.tar.gz: !binary |-
13
+ OGJjMmU2MTk1YTJjMzAzNzIwY2E3NDBjNjc0MjAxYWMwZDVlMTExYWNjZTA3
14
+ NTFkMjk4Zjc2ZTg3YWUzMTVkMDE5ODQ3ZDY3YzQ3ZjcyNzY4MWNkMDFiYmE2
15
+ ZDk0ZWM3YzMyOTJkYWIzYTViZjVmNWI5MzJkYzE4ZWExMGY0YzE=
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.log
3
+ *.rbc
4
+ .*.swp
5
+ .bundle
6
+ .config
7
+ .rvmrc
8
+ .yardoc
9
+ Gemfile.lock
10
+ InstalledFiles
11
+ _yardoc
12
+ coverage
13
+ doc/
14
+ lib/bundler/man
15
+ pdo.yaml
16
+ pkg
17
+ rdoc
18
+ spec/reports
19
+ test/tmp
20
+ test/version_tmp
21
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pdo.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 bqbn
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # Pdo
2
+
3
+ pdo is a wrapper for running commands on or against multiple hosts at
4
+ the same time.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'pdo'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install pdo
19
+
20
+ ## Usage
21
+
22
+ The following usage examples assume the host definition file in the
23
+ example/ directory is used.
24
+
25
+ * to list the hosts or groups
26
+ $ pdo -g /
27
+ $ pdo -g dc0/perf
28
+
29
+ * to enumerate hosts in groups
30
+ $ pdo -g lab0 --enum
31
+ $ pdo -g dc0/perf --enum
32
+
33
+ * to count # of hosts in groups
34
+ $ pdo -g dc0/func,dc0/perf --count
35
+ $ pdo -g lab0/empty --count
36
+
37
+ * to run 'ls -l' on some hosts
38
+ $ pdo -g env0 -c 'ls -l' -t 3 -y
39
+
40
+ * to rsync some file to some hosts
41
+ $ pdo -g lab0/dc1/util -c "rsync -avc example.txt _HOST_:/var/tmp/" -l -t 5
42
+
43
+
44
+ ## Contributing
45
+
46
+ 1. Fork it
47
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
48
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
49
+ 4. Push to the branch (`git push origin my-new-feature`)
50
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/pdo ADDED
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path("#{File.dirname(__FILE__)}/../lib")
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include? lib
5
+
6
+ #require 'pdo/version'
7
+
8
+ require 'ostruct'
9
+ require 'pdo'
10
+ require 'pdo/logging'
11
+ require 'pdo/host'
12
+ require 'pdo/pdoopts'
13
+ require 'pdo/task'
14
+ require 'pp'
15
+
16
+ logger = Logger['main'] || Logger['pdo']
17
+
18
+ opts = OpenStruct.new
19
+ # setting default values
20
+ opts.confirm_before_execute = true
21
+ opts.hosts = []
22
+ opts.local = false
23
+ opts.step = [0, 0]
24
+ opts.thread_num = 1
25
+ opts.nohush = false
26
+
27
+ begin
28
+ # parse ARGV; defaults in opts is overwritten if needed
29
+ opts = PdoOpts.parse(ARGV, opts)
30
+ logger.debug { opts }
31
+ rescue => err
32
+ logger.error { "#{err.class}: #{err.message}" }
33
+ logger.debug { err.backtrace.join "\n" }
34
+ exit 1
35
+ end
36
+
37
+ include Host
38
+ Host.load_file opts.host_definition_file
39
+
40
+ excludes = []
41
+ opts.e_groups and opts.e_groups.each do |group|
42
+ excludes += get_hosts(group, opts.step)
43
+ end
44
+
45
+ if opts.groups then
46
+ # the following all depend on existing of opts.groups option
47
+ if opts.count then
48
+ opts.groups.each do |group|
49
+ hosts = get_hosts(group, opts.step) - excludes
50
+ printf "%s: %d\n", group, hosts.size
51
+ end
52
+ exit 0
53
+ elsif opts.enum then
54
+ hosts = []
55
+ opts.groups.each do |group|
56
+ hosts += get_hosts(group, opts.step)
57
+ end
58
+ puts (hosts - excludes).join(' ')
59
+ exit 0
60
+ elsif opts.cmd then
61
+ hosts = []
62
+ opts.groups.each do |group|
63
+ hosts += get_hosts(group, opts.step)
64
+ end
65
+ hosts -= excludes
66
+
67
+ tasks = Task.new
68
+ hosts.each do |host|
69
+ ssh = SSHCmd.new(opts.local, opts.sshopts)
70
+ tasks.add [host, ssh.form(host, opts.cmd)]
71
+ end
72
+
73
+ if opts.confirm_before_execute
74
+ tasks.print_all
75
+ loop do
76
+ printf "proceed? (y/N) : "
77
+ response = gets.rstrip
78
+ if response == 'y'
79
+ break
80
+ elsif response == 'N' or response == ''
81
+ exit 2 # user given up
82
+ end
83
+ end
84
+ end
85
+ pdo = PDO.new(tasks, opts.thread_num)
86
+ pdo.run
87
+ else
88
+ opts.groups.each do |key|
89
+ printf "%s:\n", key
90
+ get_sub_groups(key).each do |sub_group|
91
+ printf "#{' ' * key.length}%s\n", sub_group
92
+ end
93
+ end
94
+ exit
95
+ end
96
+ end # end of "if opts.groups then"
97
+
98
+ # vim: set et ts=2 sts=2 sw=2 si sta :
data/conf/log4r.yaml ADDED
@@ -0,0 +1,18 @@
1
+ ---
2
+ log4r_config:
3
+ loggers:
4
+ - name : pdo
5
+ level : INFO
6
+ outputters:
7
+ - stderr
8
+
9
+ # define all outputters (incl. formatters)
10
+ outputters:
11
+ - type : StderrOutputter
12
+ name : stderr
13
+ level : INFO
14
+ formatter:
15
+ type : PatternFormatter
16
+ pattern: "%d, %C: %l, %m"
17
+
18
+ # vim: set et ts=2 sts=2 sw=2 si sta:
@@ -0,0 +1,83 @@
1
+ #
2
+ # This is an example host definition file.
3
+ #
4
+ # The host definition file must be a YAML (http://www.yaml.org/) file.
5
+ #
6
+ # The below rules should be followed when defining your environments
7
+ # and hosts.
8
+ #
9
+ # * A host name should be in the format of [user@]<host>[:port].
10
+ # * A group name should be in the format of
11
+ # <group1/>[group2[/group3[...]]]
12
+ # Note the trailing slash ("/") for "group1". Without it, "group1"
13
+ # will be deemed as a host name.
14
+ # * A group names must be an absolute path. The leading slash ("/")
15
+ # can be omitted.
16
+ # * The leaf node must be either a group or host name.
17
+
18
+
19
+ # A simple environment with 3 hosts.
20
+ env0:
21
+ - h1
22
+ - h2
23
+ - h3
24
+
25
+ # A data center that contains 2 environments
26
+ dc0:
27
+ func: # functional test environment
28
+ - h1.func.dc0
29
+ - h2.func.dc0
30
+
31
+ perf: # performance test environment
32
+ - dc0/env2/type1
33
+ - dc0/env2/type2
34
+ - h3.perf.dc0
35
+ - h4.perf.dc0
36
+
37
+ env2: # dc0/env2
38
+ type1:
39
+ - h1.type1.env2.dc0
40
+ - h2.type2.env2.dc0
41
+ type2:
42
+ - h1.type2.dc0
43
+ - h2.type2.dc0
44
+
45
+ # A lab that contains 2 data centers and more
46
+ lab0:
47
+ empty: # empty group is OK.
48
+
49
+ # a functional grouping containing two data centers and an
50
+ # extra host
51
+ func:
52
+ - lab0/dc1
53
+ - lab0/dc2
54
+ - funchost1
55
+
56
+ # lab0/dc1
57
+ dc1:
58
+ env1:
59
+ - lab0/dc1/util/admin
60
+ - host1.dc1
61
+ - host2.dc1
62
+ env2:
63
+ - lab0/dc1/util/tool
64
+ - host3.dc1
65
+ - host4.dc1
66
+ util:
67
+ admin:
68
+ - root@admin.example.com
69
+ tool:
70
+ - tools@tool.example.com:443
71
+
72
+ # lab0/dc2
73
+ dc2:
74
+ - host1.dc2
75
+ - host2.dc2
76
+
77
+ # ALL of it
78
+ all:
79
+ - env0/ # trailing slash is needed to indicate group names.
80
+ - lab0/
81
+ - dc0/
82
+
83
+ # vim: set et ts=2 sts=2 sw=2 si sta :
data/lib/pdo/host.rb ADDED
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'log4r'
4
+ include Log4r
5
+
6
+ require 'pp'
7
+ require 'yaml'
8
+
9
+ module Host
10
+
11
+ @@logger = Logger[self.class.to_s] || Logger['pdo']
12
+
13
+ @@recursive_level = 9
14
+ def self.recursive_level=(level)
15
+ @@recursive_level = level
16
+ end
17
+ def self.recursive_level
18
+ @@recursive_level
19
+ end
20
+
21
+ def self.load_file(def_file=nil)
22
+ if def_file then
23
+ files = [ def_file ]
24
+ else
25
+ files = [ "/etc/pdo/pdo.yaml", "#{ENV['HOME']}/.pdo/pdo.yaml", ]
26
+ end
27
+
28
+ hosts = {}
29
+ files.each do |f|
30
+ begin
31
+ hosts.update(YAML::load_file(f))
32
+ rescue => ex
33
+ @@logger.warn { "#{ex.class}: #{ex.message}" }
34
+ @@logger.debug { ex.backtrace.join "\n" }
35
+ end
36
+ end
37
+ return @@host_hash = hosts
38
+
39
+ end
40
+
41
+ def expand_group(group, level)
42
+ # given a group, recursively expand it into a list of hosts.
43
+ # if the group can't expand to a list of hosts, return that group.
44
+ # if there are exception expanding the group, return an empty list.
45
+
46
+ hosts = []
47
+
48
+ level += 1
49
+ if level > @@recursive_level then
50
+ @@logger.warn "circle detected in the host definition file."
51
+ return []
52
+ end
53
+
54
+ # get the hash value
55
+ keys = group.gsub(/^\//,'').split('/')
56
+ list_of_hosts = @@host_hash
57
+ keys.each do |k|
58
+ begin
59
+ if list_of_hosts.key? k then
60
+ list_of_hosts = list_of_hosts[k]
61
+ else
62
+ # if any part of the group name is not a key in the
63
+ # host hash, then return the group.
64
+ return [ group ]
65
+ end
66
+ rescue => ex
67
+ @@logger.warn {
68
+ "failed to get hash value for #{group.inspect}. "\
69
+ 'possibly an error in the yaml file.'
70
+ }
71
+ @@logger.debug { ex.backtrace.join "\n" }
72
+ return []
73
+ end
74
+ end
75
+
76
+ # return empty array if the group is empty.
77
+ return [] if list_of_hosts.nil?
78
+
79
+ if list_of_hosts.is_a? Hash then
80
+ list_of_hosts.keys.each do |key|
81
+ hosts += expand_group("#{group}/#{key}".squeeze('/'), level)
82
+ end
83
+ elsif list_of_hosts.is_a? Array then
84
+ list_of_hosts.each do |h|
85
+ if not h.is_a? String then
86
+ @@logger.warn {
87
+ "invalid host or group format: #{h.inspect}\n"\
88
+ 'possibly an error in the yaml file.'
89
+ }
90
+ next
91
+ end
92
+ if h.include? "/" then
93
+ hosts += expand_group(h, level)
94
+ else
95
+ hosts << h
96
+ end
97
+ end
98
+ else
99
+ @@logger.fatal "I don't know how to handle this. "\
100
+ 'possibly an error in the yaml file.'
101
+ exit 1
102
+ end
103
+ return hosts
104
+
105
+ end # expand_group
106
+ private :expand_group
107
+
108
+ def get_hosts(group, step)
109
+ hosts = []
110
+ hosts += expand_group(group, 0)
111
+ hosts.uniq!
112
+ hosts = stepping hosts, step
113
+ return hosts
114
+
115
+ end
116
+
117
+ def stepping(hosts, step)
118
+ unless hosts.is_a? Array or step.is_a? Array then
119
+ @@logger.warn "both hosts or step should be array."
120
+ return nil
121
+ end
122
+
123
+ start, stride = step[0].to_i, step[1].to_i
124
+ # if stride > 0, stepping forward
125
+ # if stride < 0, stepping backward
126
+ # if stride == 0, no stepping
127
+ # if start or stride makes no sense, no stepping either
128
+ index = case
129
+ when stride > 0 then
130
+ (start-1...hosts.size).step(stride).to_a
131
+ when stride < 0 then
132
+ (-start+1..0).step(-stride).to_a.map {|x| -x}
133
+ else []
134
+ end
135
+
136
+ unless index.empty? then
137
+ alist = []
138
+ index.each do |i|
139
+ alist << hosts[i]
140
+ end
141
+ hosts = alist
142
+ end
143
+
144
+ return hosts
145
+ end
146
+
147
+ def show_hosts
148
+ pp @@host_hash
149
+ end
150
+
151
+ def get_sub_groups(group)
152
+
153
+ keys = group.gsub(/^\//, '').split('/')
154
+ pointer = @@host_hash
155
+ keys.each do |k|
156
+ begin
157
+ if pointer.key? k then
158
+ pointer = pointer[k]
159
+ else
160
+ # if any part of the given group name is not a key in the
161
+ # host hash, then return an empty array.
162
+ return [ ]
163
+ end
164
+ rescue => ex
165
+ @@logger.warn {
166
+ "failed to get hash value for #{group.inspect}. "\
167
+ 'maybe a wrong key is specifid?'
168
+ }
169
+ @@logger.debug { ex.backtrace.join "\n" }
170
+ return []
171
+ end
172
+ end
173
+
174
+ # return empty array if the group is empty.
175
+ return [] if pointer.nil?
176
+
177
+ if pointer.is_a? Hash then
178
+ return pointer.keys
179
+ elsif pointer.is_a? Array then
180
+ return pointer
181
+ else
182
+ @@logger.fatal "I don't know how to handle this. "\
183
+ 'possibly an error in the yaml file.'
184
+ exit 1
185
+ end
186
+
187
+ end
188
+
189
+ end
190
+
191
+ # vim: set et ts=2 sts=2 sw=2 si sta :
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'log4r'
4
+ require 'log4r/yamlconfigurator'
5
+ require 'log4r/outputter/datefileoutputter'
6
+
7
+ conf_dir = File.expand_path("#{File.dirname(__FILE__)}/../../conf")
8
+
9
+ [conf_dir, '/etc/pdo', "#{ENV['HOME']}/.pdo"].each { |dir|
10
+ conf = "#{dir}/log4r.yaml"
11
+ Log4r::YamlConfigurator.load_yaml_file conf if File.exists? conf
12
+ }
13
+
14
+ # vim: set et ts=2 sts=2 sw=2 si sta :
@@ -0,0 +1,94 @@
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 :
data/lib/pdo/task.rb ADDED
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'log4r'
4
+ include Log4r
5
+
6
+ class SSHCmd
7
+ def initialize(local, sshopts)
8
+ @logger = Logger[self.class.to_s] || Logger['pdo']
9
+
10
+ @defaults = {
11
+ :user => 'root',
12
+ :port => '22',
13
+ :ssh => '/usr/bin/ssh',
14
+ :sshopts => {
15
+ :ConnectTimeout => '60',
16
+ :StrictHostKeyChecking => 'no',
17
+ },
18
+ }
19
+ @local = local
20
+ @sshopts = @defaults[:sshopts]
21
+ @sshopts = @defaults[:sshopts].update sshopts if sshopts
22
+ @logger.debug { @sshopts.inspect }
23
+ end
24
+
25
+ def form(host, cmd)
26
+ if host.match(/^(?:(\w+)@)?(\w+(?:\.\w+)*)(?::(\d+))?$/) then
27
+ user, host, port = $1, $2, $3
28
+ end
29
+
30
+ cmd = cmd.dup # without dup, all tasks refer to the same
31
+ cmd.strip!
32
+ cmd.gsub! '_USER_', user ? user : @defaults[:user]
33
+ cmd.gsub! '_HOST_', host
34
+ cmd.gsub! '_PORT_', port ? port : @defaults[:port]
35
+ # when opts.cmd == '-', the command is read from stdin.
36
+ # in this case multiple lines can be entered. here I'm doing some
37
+ # simple substitution for "\n".
38
+ cmd.gsub! "\n", '; '
39
+
40
+ # escape "`$ characters in cmd, unless they're already escaped.
41
+ if RUBY_VERSION.to_f < 1.9 then
42
+ # v1.8 dose not support negative look-behind assertion, thus
43
+ # doing it in 2 steps.
44
+ if cmd.match /["`$]/ then
45
+ if not cmd.match /['\\]["`$]/ then
46
+ cmd.gsub! /(["`$])/, '\\\\\1'
47
+ end
48
+ end
49
+ else
50
+ cmd.gsub! /(?<!['\\])(["`$])/, '\\\\\1'
51
+ end
52
+
53
+ if @local then
54
+ return cmd
55
+ else
56
+ ssh = @defaults[:ssh]
57
+ ssh = [ ssh, '-l', "#{user}"].join ' ' if user
58
+ ssh = [ ssh, '-p', "#{port}"].join ' ' if port
59
+ @sshopts.each do |k, v|
60
+ ssh = [ssh, '-o', "#{k}=#{v}"].join ' '
61
+ end
62
+ ssh = [ ssh, "#{host}", "\"#{cmd}\""].join ' '
63
+ return ssh
64
+ end
65
+ end
66
+ end
67
+
68
+ class Task
69
+
70
+ def initialize
71
+ @logger = Logger[self.class.to_s] || Logger['pdo']
72
+ @task_q = Queue.new
73
+ end
74
+
75
+ def add(task)
76
+ @task_q << task
77
+ end
78
+
79
+ def next
80
+ begin
81
+ @task_q.deq(non_block=true)
82
+ rescue ThreadError
83
+ nil
84
+ end
85
+ end
86
+
87
+ def size
88
+ @task_q.length
89
+ end
90
+
91
+ def print_all
92
+ tmp_q = Queue.new
93
+ while not @task_q.empty? do
94
+ task = @task_q.deq
95
+ printf "%s\n", task
96
+ tmp_q << task
97
+ end
98
+ @task_q = tmp_q
99
+ end
100
+
101
+ end
102
+
103
+ # vim: set et ts=2 sts=2 sw=2 si sta :
@@ -0,0 +1,3 @@
1
+ module Pdo
2
+ VERSION = "0.0.1"
3
+ end
data/lib/pdo.rb ADDED
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ include Process
4
+
5
+ class PDO
6
+ def initialize(tasks, thread_num)
7
+ @logger = Logger[self.class.to_s] || Logger['pdo']
8
+ @tasks = tasks
9
+ @thread_num = thread_num
10
+ end
11
+
12
+ def run
13
+ # run first spawns a set of threads, tells them to execute the task
14
+ # queue. it then loop through the current Thread list (note
15
+ # Thread.list returns all running and sleeping threads), and outputs
16
+ # those new data.
17
+ # for the spawned threads, they execute one task, then wait for their
18
+ # data to be picked up by the main thread, then do the next task.
19
+
20
+ return 1 if @tasks.size == 0
21
+
22
+ n = @thread_num < @tasks.size ? @thread_num : @tasks.size
23
+ 1.upto(n) do
24
+ Thread.new do
25
+ while true do
26
+ @logger.info "#{Thread.current.object_id} started."
27
+ begin
28
+ execute(@tasks.next)
29
+ # presumably new data is ready, thus i stop.
30
+ # main thread will read :output and then wakes me up.
31
+ Thread.stop
32
+ rescue
33
+ @logger.info "No more task for #{Thread.current.object_id}."
34
+ break
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ loop do
41
+ # break because the only left thread must be the main thread.
42
+ break if Thread.list.size == 1
43
+
44
+ Thread.list.each do |t|
45
+ next if t == Thread.main
46
+ if t.key? :output and t.key? :new_data and t[:new_data] then
47
+ @logger.info "#{t.object_id} finished."
48
+ puts "=== #{t[:target]} ==="
49
+ t[:output].each {|x| puts x}
50
+ # puts t[:output].join('').gsub("\n", ' | ').chomp(' | ')
51
+ t[:new_data] = false
52
+ # wakes up the thread
53
+ t.run
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def execute(task)
60
+ # execute forks a child process to run the task, then saves the
61
+ # output from the child process into a thread local variable, marks
62
+ # the :new_data tag.
63
+
64
+ raise "no task" if task.nil?
65
+
66
+ target, cmd = task
67
+
68
+ open("|-") do |child_io|
69
+ if child_io
70
+ # The waitpid statement below causes the program to hang when
71
+ # running some commands, such as wget.
72
+ # waitpid child_io.pid
73
+ Thread.current[:output] = child_io.readlines
74
+ Thread.current[:new_data] = true
75
+ Thread.current[:target] = target
76
+ else
77
+ STDIN.close
78
+ STDERR.reopen(STDOUT)
79
+ exec(*cmd)
80
+ end
81
+ end
82
+
83
+ end
84
+
85
+ end
86
+
87
+ # vim: set et ts=2 sts=2 sw=2 si sta :
data/pdo.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pdo/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "pdo"
8
+ gem.licenses = "MIT"
9
+ gem.version = Pdo::VERSION
10
+ gem.authors = ["bqbn"]
11
+ gem.email = ["bqbn@openken.com"]
12
+ gem.description = 'pdo is a wrapper for running commands on or '\
13
+ 'against multiple hosts at the same time.'
14
+ gem.summary = 'pdo is a wrapper for running commands on or '\
15
+ 'against multiple hosts at the same time.'
16
+ gem.homepage = "https://github.com/bqbn/pdo"
17
+
18
+ gem.files = `git ls-files`.split($/)
19
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
20
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
21
+ gem.require_paths = ["lib"]
22
+
23
+ gem.add_dependency('log4r', '> 1.1.9')
24
+ end
25
+ # vim: set et ts=2 sts=2 sw=2 si sta :
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pdo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - bqbn
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-03-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: log4r
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>'
18
+ - !ruby/object:Gem::Version
19
+ version: 1.1.9
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>'
25
+ - !ruby/object:Gem::Version
26
+ version: 1.1.9
27
+ description: pdo is a wrapper for running commands on or against multiple hosts at
28
+ the same time.
29
+ email:
30
+ - bqbn@openken.com
31
+ executables:
32
+ - pdo
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - .gitignore
37
+ - Gemfile
38
+ - LICENSE.txt
39
+ - README.md
40
+ - Rakefile
41
+ - bin/pdo
42
+ - conf/log4r.yaml
43
+ - examples/pdo.yaml.example
44
+ - lib/pdo.rb
45
+ - lib/pdo/host.rb
46
+ - lib/pdo/logging.rb
47
+ - lib/pdo/pdoopts.rb
48
+ - lib/pdo/task.rb
49
+ - lib/pdo/version.rb
50
+ - pdo.gemspec
51
+ homepage: https://github.com/bqbn/pdo
52
+ licenses:
53
+ - MIT
54
+ metadata: {}
55
+ post_install_message:
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubyforge_project:
71
+ rubygems_version: 2.0.2
72
+ signing_key:
73
+ specification_version: 4
74
+ summary: pdo is a wrapper for running commands on or against multiple hosts at the
75
+ same time.
76
+ test_files: []