knife-batch 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in knife-batch.gemspec
4
+ gemspec
data/README.markdown ADDED
@@ -0,0 +1,15 @@
1
+ # knife batch
2
+
3
+ `knife batch` is a wonderful little plugin for executing commands a la `knife ssh`, but doing it in groups of `n` with a sleep between execution iterations.
4
+
5
+ # Installation
6
+
7
+ `gem install knife-batch`
8
+
9
+ # Usage
10
+
11
+ `knife batch` works exactly like `knife ssh` but with a couple of additional options.
12
+ `knife batch "role:cluster" "whoami" -B 10 -W 5` will execute `whoami` against 10 servers with a sleep of 5 seconds in between.
13
+
14
+ `-B INTEGER` defines how many servers at max will be batched.
15
+ `-W INTEGER` defines the time to sleep in between executions.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "knife-batch/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "knife-batch"
7
+ s.version = Knife::Batch::VERSION
8
+ s.authors = ["Ian Meyer"]
9
+ s.email = ["ianmmeyer@gmail.com"]
10
+ s.homepage = "http://github.com/imeyer/knife-batch"
11
+ s.summary = %q{Knife plugin to run ssh commands against batches of servers}
12
+ s.description = %q{`knife batch` is a wonderful little plugin for executing commands a la `knife ssh`, but doing it in groups of `n` with a sleep between execution iterations.}
13
+
14
+ s.rubyforge_project = "knife-batch"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ s.add_runtime_dependency "chef"
24
+ end
@@ -0,0 +1,213 @@
1
+ #
2
+ # Author:: Ian Meyer <ianmmeyer@gmail.com>
3
+ # Plugin name:: batch
4
+ #
5
+ # Copyright 2011, Ian Meyer
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ class Batch < Chef::Knife
21
+ banner "knife batch [QUERY] [CMD]"
22
+
23
+ deps do
24
+ require 'net/ssh'
25
+ require 'net/ssh/multi'
26
+ require 'readline'
27
+ require 'chef/search/query'
28
+ require 'chef/mixin/command'
29
+ end
30
+
31
+ option :wait,
32
+ :short => "-W SECONDS",
33
+ :long => "--wait SECONDS",
34
+ :description => "The number of seconds between batches.",
35
+ :default => 0.5
36
+
37
+ option :batch_size,
38
+ :short => "-B NODES",
39
+ :long => "--batch-size NODES",
40
+ :description => "The number of nodes to run per batch.",
41
+ :default => 5
42
+
43
+ option :stop_on_failure,
44
+ :short => "-S",
45
+ :long => "--stop-on-failure",
46
+ :description => "Stop on first failure of remote command",
47
+ :default => false
48
+
49
+ option :manual,
50
+ :short => "-m",
51
+ :long => "--manual-list",
52
+ :boolean => true,
53
+ :description => "QUERY is a space separated list of servers",
54
+ :default => false
55
+
56
+ option :ssh_user,
57
+ :short => "-x USERNAME",
58
+ :long => "--ssh-user USERNAME",
59
+ :description => "The ssh username"
60
+
61
+ option :ssh_password,
62
+ :short => "-P PASSWORD",
63
+ :long => "--ssh-password PASSWORD",
64
+ :description => "The ssh password"
65
+
66
+ option :ssh_port,
67
+ :short => "-p PORT",
68
+ :long => "--ssh-port PORT",
69
+ :description => "The ssh port",
70
+ :default => "22",
71
+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
72
+
73
+ option :identity_file,
74
+ :short => "-i IDENTITY_FILE",
75
+ :long => "--identity-file IDENTITY_FILE",
76
+ :description => "The SSH identity file used for authentication"
77
+
78
+ option :no_host_key_verify,
79
+ :long => "--no-host-key-verify",
80
+ :description => "Disable host key verification",
81
+ :boolean => true,
82
+ :default => false
83
+
84
+ option :attribute,
85
+ :short => "-a ATTR",
86
+ :long => "--attribute ATTR",
87
+ :description => "The attribute to use for opening the connection - default is fqdn",
88
+ :default => "fqdn"
89
+
90
+ def session(nodes)
91
+ ssh_error_handler = Proc.new do |server|
92
+ if config[:manual]
93
+ node_name = server.host
94
+ else
95
+ nodes.each do |n|
96
+ node_name = n if format_for_display(n)[config[:attribute]] == server.host
97
+ end
98
+ end
99
+ ui.warn "Failed to connect to #{node_name} -- #{$!.class.name}: #{$!.message}"
100
+ $!.backtrace.each { |l| Chef::Log.debug(l) }
101
+ end
102
+
103
+ @ssh_session ||= Net::SSH::Multi.start(:concurrent_connections => config[:concurrency], :on_error => ssh_error_handler)
104
+ end
105
+
106
+ def get_nodes
107
+ list = case config[:manual]
108
+ when true
109
+ @name_args[0].split(" ")
110
+ when false
111
+ r = Array.new
112
+ q = Chef::Search::Query.new
113
+ @action_nodes = q.search(:node, @name_args[0])[0]
114
+ @action_nodes.each do |item|
115
+ i = format_for_display(item)[config[:attribute]]
116
+ r.push(i) unless i.nil?
117
+ end
118
+ r
119
+ end
120
+ (ui.fatal("No nodes returned from search!"); exit 10) if list.length == 0
121
+
122
+ parent_ary = Array.new
123
+ child_ary = Array.new
124
+ iter = 0
125
+ list.each do |item|
126
+ if (iter +=1) <= config[:batch_size].to_i
127
+ child_ary << item
128
+ else
129
+ parent_ary << child_ary
130
+ child_ary = Array.new
131
+ iter = 0
132
+ end
133
+ end
134
+
135
+ parent_ary
136
+ end
137
+
138
+ def print_data(host, data)
139
+ if data =~ /\n/
140
+ data.split(/\n/).each { |d| print_data(host, d) }
141
+ else
142
+ padding = @longest - host.length
143
+ print ui.color(host, :cyan)
144
+ padding.downto(0) { print " " }
145
+ puts data
146
+ end
147
+ end
148
+
149
+ def session_from_list(nodes)
150
+ nodes.each do |item|
151
+ Chef::Log.debug("Adding #{item}")
152
+
153
+ hostspec = config[:ssh_user] ? "#{config[:ssh_user]}@#{item}" : item
154
+ session_opts = {}
155
+ session_opts[:keys] = File.expand_path(config[:identity_file]) if config[:identity_file]
156
+ session_opts[:password] = config[:ssh_password] if config[:ssh_password]
157
+ session_opts[:port] = Chef::Config[:knife][:ssh_port] || config[:ssh_port]
158
+ session_opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug
159
+
160
+ if config[:no_host_key_verify]
161
+ session_opts[:paranoid] = false
162
+ session_opts[:user_known_hosts_file] = "/dev/null"
163
+ end
164
+ session(nodes).use(hostspec, session_opts)
165
+
166
+ @longest = item.length if item.length > @longest
167
+ end
168
+
169
+ session(nodes)
170
+ end
171
+
172
+ def ssh_command(command, subsession=nil, nodes)
173
+ subsession ||= session(nodes)
174
+ subsession.open_channel do |channel|
175
+ host = channel[:host]
176
+ channel.request_pty
177
+ channel.exec command do |ch, success|
178
+ exit_code = nil
179
+ raise ArgumentError, "Cannot execute #{command}" unless success
180
+ channel.on_data do |ch, data|
181
+ print_data(host, data)
182
+ end
183
+
184
+ if config[:stop_on_failure]
185
+ channel.on_request("exit-status") do |ch, data|
186
+ exit_code = data.read_long
187
+ if not exit_code.nil?
188
+ exit 1 if exit_code.to_i > 0
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+ @ssh_session.loop
195
+ @ssh_session = nil
196
+ end
197
+
198
+ def run
199
+ extend Chef::Mixin::Command
200
+
201
+ @longest = 0
202
+ all_nodes = get_nodes
203
+ all_nodes.each do |nodes|
204
+ session_from_list(nodes)
205
+
206
+ ssh_command(@name_args[1..-1].join(" "), nodes)
207
+ puts "*" * 80
208
+ puts "Taking a nap for #{config[:wait]} seconds..."
209
+ puts "*" * 80
210
+ sleep config[:wait].to_f
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,213 @@
1
+ #
2
+ # Author:: Ian Meyer <ianmmeyer@gmail.com>
3
+ # Plugin name:: batch
4
+ #
5
+ # Copyright 2011, Ian Meyer
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ class Batch < Chef::Knife
21
+ banner "knife batch [QUERY] [CMD]"
22
+
23
+ deps do
24
+ require 'net/ssh'
25
+ require 'net/ssh/multi'
26
+ require 'readline'
27
+ require 'chef/search/query'
28
+ require 'chef/mixin/command'
29
+ end
30
+
31
+ option :wait,
32
+ :short => "-W SECONDS",
33
+ :long => "--wait SECONDS",
34
+ :description => "The number of seconds between batches.",
35
+ :default => 0.5
36
+
37
+ option :batch_size,
38
+ :short => "-B NODES",
39
+ :long => "--batch-size NODES",
40
+ :description => "The number of nodes to run per batch.",
41
+ :default => 5
42
+
43
+ option :stop_on_failure,
44
+ :short => "-S",
45
+ :long => "--stop-on-failure",
46
+ :description => "Stop on first failure of remote command",
47
+ :default => false
48
+
49
+ option :manual,
50
+ :short => "-m",
51
+ :long => "--manual-list",
52
+ :boolean => true,
53
+ :description => "QUERY is a space separated list of servers",
54
+ :default => false
55
+
56
+ option :ssh_user,
57
+ :short => "-x USERNAME",
58
+ :long => "--ssh-user USERNAME",
59
+ :description => "The ssh username"
60
+
61
+ option :ssh_password,
62
+ :short => "-P PASSWORD",
63
+ :long => "--ssh-password PASSWORD",
64
+ :description => "The ssh password"
65
+
66
+ option :ssh_port,
67
+ :short => "-p PORT",
68
+ :long => "--ssh-port PORT",
69
+ :description => "The ssh port",
70
+ :default => "22",
71
+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
72
+
73
+ option :identity_file,
74
+ :short => "-i IDENTITY_FILE",
75
+ :long => "--identity-file IDENTITY_FILE",
76
+ :description => "The SSH identity file used for authentication"
77
+
78
+ option :no_host_key_verify,
79
+ :long => "--no-host-key-verify",
80
+ :description => "Disable host key verification",
81
+ :boolean => true,
82
+ :default => false
83
+
84
+ option :attribute,
85
+ :short => "-a ATTR",
86
+ :long => "--attribute ATTR",
87
+ :description => "The attribute to use for opening the connection - default is fqdn",
88
+ :default => "fqdn"
89
+
90
+ def session(nodes)
91
+ ssh_error_handler = Proc.new do |server|
92
+ if config[:manual]
93
+ node_name = server.host
94
+ else
95
+ nodes.each do |n|
96
+ node_name = n if format_for_display(n)[config[:attribute]] == server.host
97
+ end
98
+ end
99
+ ui.warn "Failed to connect to #{node_name} -- #{$!.class.name}: #{$!.message}"
100
+ $!.backtrace.each { |l| Chef::Log.debug(l) }
101
+ end
102
+
103
+ @ssh_session ||= Net::SSH::Multi.start(:concurrent_connections => config[:concurrency], :on_error => ssh_error_handler)
104
+ end
105
+
106
+ def get_nodes
107
+ list = case config[:manual]
108
+ when true
109
+ @name_args[0].split(" ")
110
+ when false
111
+ r = Array.new
112
+ q = Chef::Search::Query.new
113
+ @action_nodes = q.search(:node, @name_args[0])[0]
114
+ @action_nodes.each do |item|
115
+ i = format_for_display(item)[config[:attribute]]
116
+ r.push(i) unless i.nil?
117
+ end
118
+ r
119
+ end
120
+ (ui.fatal("No nodes returned from search!"); exit 10) if list.length == 0
121
+
122
+ parent_ary = Array.new
123
+ child_ary = Array.new
124
+ iter = 0
125
+ list.each do |item|
126
+ if (iter +=1) <= config[:batch_size].to_i
127
+ child_ary << item
128
+ else
129
+ parent_ary << child_ary
130
+ child_ary = Array.new
131
+ iter = 0
132
+ end
133
+ end
134
+
135
+ parent_ary
136
+ end
137
+
138
+ def print_data(host, data)
139
+ if data =~ /\n/
140
+ data.split(/\n/).each { |d| print_data(host, d) }
141
+ else
142
+ padding = @longest - host.length
143
+ print ui.color(host, :cyan)
144
+ padding.downto(0) { print " " }
145
+ puts data
146
+ end
147
+ end
148
+
149
+ def session_from_list(nodes)
150
+ nodes.each do |item|
151
+ Chef::Log.debug("Adding #{item}")
152
+
153
+ hostspec = config[:ssh_user] ? "#{config[:ssh_user]}@#{item}" : item
154
+ session_opts = {}
155
+ session_opts[:keys] = File.expand_path(config[:identity_file]) if config[:identity_file]
156
+ session_opts[:password] = config[:ssh_password] if config[:ssh_password]
157
+ session_opts[:port] = Chef::Config[:knife][:ssh_port] || config[:ssh_port]
158
+ session_opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug
159
+
160
+ if config[:no_host_key_verify]
161
+ session_opts[:paranoid] = false
162
+ session_opts[:user_known_hosts_file] = "/dev/null"
163
+ end
164
+ session(nodes).use(hostspec, session_opts)
165
+
166
+ @longest = item.length if item.length > @longest
167
+ end
168
+
169
+ session(nodes)
170
+ end
171
+
172
+ def ssh_command(command, subsession=nil, nodes)
173
+ subsession ||= session(nodes)
174
+ subsession.open_channel do |channel|
175
+ host = channel[:host]
176
+ channel.request_pty
177
+ channel.exec command do |ch, success|
178
+ exit_code = nil
179
+ raise ArgumentError, "Cannot execute #{command}" unless success
180
+ channel.on_data do |ch, data|
181
+ print_data(host, data)
182
+ end
183
+
184
+ if config[:stop_on_failure]
185
+ channel.on_request("exit-status") do |ch, data|
186
+ exit_code = data.read_long
187
+ if not exit_code.nil?
188
+ exit 1 if exit_code.to_i > 0
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+ @ssh_session.loop
195
+ @ssh_session = nil
196
+ end
197
+
198
+ def run
199
+ extend Chef::Mixin::Command
200
+
201
+ @longest = 0
202
+ all_nodes = get_nodes
203
+ all_nodes.each do |nodes|
204
+ session_from_list(nodes)
205
+
206
+ ssh_command(@name_args[1..-1].join(" "), nodes)
207
+ puts "*" * 80
208
+ puts "Taking a nap for #{config[:wait]} seconds..."
209
+ puts "*" * 80
210
+ sleep config[:wait].to_f
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,5 @@
1
+ module Knife
2
+ module Batch
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: knife-batch
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ian Meyer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-10-15 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: chef
16
+ requirement: &70191053580660 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70191053580660
25
+ description: ! '`knife batch` is a wonderful little plugin for executing commands
26
+ a la `knife ssh`, but doing it in groups of `n` with a sleep between execution iterations.'
27
+ email:
28
+ - ianmmeyer@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - .gitignore
34
+ - Gemfile
35
+ - README.markdown
36
+ - Rakefile
37
+ - knife-batch.gemspec
38
+ - lib/chef/knife/batch.rb
39
+ - lib/chef/knife/knife-batch.rb
40
+ - lib/knife-batch/version.rb
41
+ homepage: http://github.com/imeyer/knife-batch
42
+ licenses: []
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project: knife-batch
61
+ rubygems_version: 1.8.5
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: Knife plugin to run ssh commands against batches of servers
65
+ test_files: []