knife-undev 0.0.8 → 0.0.9
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.
- checksums.yaml +4 -4
- data/lib/knife/undev.rb +1 -1
- data/lib/knife/undev/monkey_patches/node_presenter.rb +19 -22
- data/lib/knife/undev/plugins/cloud_ssh.rb +325 -0
- data/lib/knife/undev/version.rb +1 -1
- metadata +2 -2
- data/lib/knife/undev/monkey_patches/ssh.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2ba84740385210e9d5e64a67a37f43919da6539f
|
4
|
+
data.tar.gz: 527e5ae42dbe266d9903d749c6c4189d214d5b28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78e2afbe003643a7906208f7d3c8ebe4472cfa5522b1d66a5b090eae68b439903edf34ebc35367ce43c55bd08b8f2f3ad7567e95a55540db3bd8e90790b64dc1
|
7
|
+
data.tar.gz: 44fd9445f1badce11fb05991c3d5747eca43c726bba015a2a88c1eb666df8d8b7fdf5ecbead9a643890325247f9b0f51ec5b9fe7e9439d4ad515794d9f711bab
|
data/lib/knife/undev.rb
CHANGED
@@ -1,17 +1,14 @@
|
|
1
|
-
require 'chef/knife'
|
2
1
|
require 'chef/knife/core/node_presenter'
|
3
2
|
|
4
|
-
class Chef
|
5
|
-
class Knife
|
6
|
-
module Core
|
7
3
|
|
8
|
-
|
9
|
-
if data.kind_of?(Chef::Node)
|
10
|
-
node = data
|
11
|
-
# special case ec2 with their split horizon whatsis.
|
12
|
-
ip = (node[:private_ipaddress]) || node[:ipaddress]
|
4
|
+
class Chef::Knife::Core::NodePresenter
|
13
5
|
|
14
|
-
|
6
|
+
def summarize(data)
|
7
|
+
if data.kind_of?(Chef::Node)
|
8
|
+
node = data
|
9
|
+
ip = node[:ipaddress]
|
10
|
+
|
11
|
+
summarized=<<-SUMMARY
|
15
12
|
#{ui.color('Node Name:', :bold)} #{ui.color(node.name, :bold)}
|
16
13
|
#{key('Environment:')} #{node.chef_environment}
|
17
14
|
#{key('FQDN:')} #{node[:fqdn]}
|
@@ -23,14 +20,15 @@ class Chef
|
|
23
20
|
#{key('Platform:')} #{node[:platform]} #{node[:platform_version]}
|
24
21
|
#{key('Tags:')} #{Array(node[:tags]).join(', ')}
|
25
22
|
SUMMARY
|
26
|
-
|
27
|
-
|
23
|
+
if config[:medium_output] || config[:long_output]
|
24
|
+
summarized +=<<-MORE
|
28
25
|
#{key('Attributes:')}
|
29
26
|
#{text_format(node.normal_attrs)}
|
30
27
|
MORE
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
end
|
29
|
+
|
30
|
+
if config[:long_output]
|
31
|
+
summarized +=<<-MOST
|
34
32
|
#{key('Default Attributes:')}
|
35
33
|
#{text_format(node.default_attrs)}
|
36
34
|
#{key('Override Attributes:')}
|
@@ -38,13 +36,12 @@ MORE
|
|
38
36
|
#{key('Automatic Attributes (Ohai Data):')}
|
39
37
|
#{text_format(node.automatic_attrs)}
|
40
38
|
MOST
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
47
|
-
|
39
|
+
end
|
40
|
+
|
41
|
+
summarized
|
42
|
+
else # not Chef::Node
|
43
|
+
super
|
48
44
|
end
|
49
45
|
end
|
46
|
+
|
50
47
|
end
|
@@ -0,0 +1,325 @@
|
|
1
|
+
require 'chef/knife'
|
2
|
+
|
3
|
+
# отличается от ssh:
|
4
|
+
# 1. host_key_verify => false
|
5
|
+
# 2. concurrency => 50
|
6
|
+
# 3. коннектиться по дефолту к private_ip
|
7
|
+
# 4. выдает exit status не нулевой если есть сфеленные по коннекту
|
8
|
+
# 5. выводит список всех сфейленных машин в самом конце
|
9
|
+
|
10
|
+
class Chef
|
11
|
+
class Knife
|
12
|
+
class CloudSsh < Knife
|
13
|
+
|
14
|
+
deps do
|
15
|
+
require 'net/ssh'
|
16
|
+
require 'net/ssh/multi'
|
17
|
+
require 'chef/monkey_patches/net-ssh-multi'
|
18
|
+
require 'readline'
|
19
|
+
require 'chef/exceptions'
|
20
|
+
require 'chef/search/query'
|
21
|
+
require 'chef/mixin/shell_out'
|
22
|
+
require 'mixlib/shellout'
|
23
|
+
end
|
24
|
+
|
25
|
+
include Chef::Mixin::ShellOut
|
26
|
+
|
27
|
+
attr_writer :password
|
28
|
+
|
29
|
+
banner "knife private_ssh QUERY COMMAND (options)"
|
30
|
+
|
31
|
+
option :concurrency,
|
32
|
+
:short => "-C NUM",
|
33
|
+
:long => "--concurrency NUM",
|
34
|
+
:description => "The number of concurrent connections",
|
35
|
+
:default => 50,
|
36
|
+
:proc => lambda { |o| o.to_i }
|
37
|
+
|
38
|
+
option :attribute,
|
39
|
+
:short => "-a ATTR",
|
40
|
+
:long => "--attribute ATTR",
|
41
|
+
:description => "The attribute to use for opening the connection - default depends on the context",
|
42
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:ssh_attribute] = key.strip }
|
43
|
+
|
44
|
+
option :manual,
|
45
|
+
:short => "-m",
|
46
|
+
:long => "--manual-list",
|
47
|
+
:boolean => true,
|
48
|
+
:description => "QUERY is a space separated list of servers",
|
49
|
+
:default => false
|
50
|
+
|
51
|
+
option :ssh_user,
|
52
|
+
:short => "-x USERNAME",
|
53
|
+
:long => "--ssh-user USERNAME",
|
54
|
+
:description => "The ssh username"
|
55
|
+
|
56
|
+
option :ssh_password,
|
57
|
+
:short => "-P PASSWORD",
|
58
|
+
:long => "--ssh-password PASSWORD",
|
59
|
+
:description => "The ssh password"
|
60
|
+
|
61
|
+
option :ssh_port,
|
62
|
+
:short => "-p PORT",
|
63
|
+
:long => "--ssh-port PORT",
|
64
|
+
:description => "The ssh port",
|
65
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key.strip }
|
66
|
+
|
67
|
+
option :ssh_gateway,
|
68
|
+
:short => "-G GATEWAY",
|
69
|
+
:long => "--ssh-gateway GATEWAY",
|
70
|
+
:description => "The ssh gateway",
|
71
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key.strip }
|
72
|
+
|
73
|
+
option :forward_agent,
|
74
|
+
:short => "-A",
|
75
|
+
:long => "--forward-agent",
|
76
|
+
:description => "Enable SSH agent forwarding",
|
77
|
+
:boolean => true
|
78
|
+
|
79
|
+
option :identity_file,
|
80
|
+
:short => "-i IDENTITY_FILE",
|
81
|
+
:long => "--identity-file IDENTITY_FILE",
|
82
|
+
:description => "The SSH identity file used for authentication"
|
83
|
+
|
84
|
+
option :host_key_verify,
|
85
|
+
:long => "--[no-]host-key-verify",
|
86
|
+
:description => "Verify host key, disabled by default.",
|
87
|
+
:boolean => true,
|
88
|
+
:default => false
|
89
|
+
|
90
|
+
def session
|
91
|
+
config[:on_error] ||= :skip
|
92
|
+
ssh_error_handler = Proc.new do |server|
|
93
|
+
@failed_connect_nodes << { "node" => server.host , "message" => "#{$!.class.name}: #{$!.message}" }
|
94
|
+
if config[:manual]
|
95
|
+
node_name = server.host
|
96
|
+
else
|
97
|
+
@action_nodes.each do |n|
|
98
|
+
node_name = n if format_for_display(n)[config[:attribute]] == server.host
|
99
|
+
end
|
100
|
+
end
|
101
|
+
case config[:on_error]
|
102
|
+
when :skip
|
103
|
+
ui.warn "Failed to connect to #{node_name} -- #{$!.class.name}: #{$!.message}"
|
104
|
+
$!.backtrace.each { |l| Chef::Log.debug(l) }
|
105
|
+
when :raise
|
106
|
+
#Net::SSH::Multi magic to force exception to be re-raised.
|
107
|
+
throw :go, :raise
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
@session ||= Net::SSH::Multi.start(:concurrent_connections => config[:concurrency], :on_error => ssh_error_handler)
|
112
|
+
end
|
113
|
+
|
114
|
+
def configure_gateway
|
115
|
+
config[:ssh_gateway] ||= Chef::Config[:knife][:ssh_gateway]
|
116
|
+
if config[:ssh_gateway]
|
117
|
+
gw_host, gw_user = config[:ssh_gateway].split('@').reverse
|
118
|
+
gw_host, gw_port = gw_host.split(':')
|
119
|
+
gw_opts = gw_port ? { :port => gw_port } : {}
|
120
|
+
|
121
|
+
session.via(gw_host, gw_user || config[:ssh_user], gw_opts)
|
122
|
+
end
|
123
|
+
rescue Net::SSH::AuthenticationFailed
|
124
|
+
user = gw_user || config[:ssh_user]
|
125
|
+
prompt = "Enter the password for #{user}@#{gw_host}: "
|
126
|
+
gw_opts.merge!(:password => prompt_for_password(prompt))
|
127
|
+
session.via(gw_host, user, gw_opts)
|
128
|
+
end
|
129
|
+
|
130
|
+
def configure_session
|
131
|
+
list = case config[:manual]
|
132
|
+
when true
|
133
|
+
@name_args[0].split(" ")
|
134
|
+
when false
|
135
|
+
r = Array.new
|
136
|
+
q = Chef::Search::Query.new
|
137
|
+
@action_nodes = q.search(:node, @name_args[0])[0]
|
138
|
+
@action_nodes.each do |item|
|
139
|
+
# we should skip the loop to next iteration if the item returned by the search is nil
|
140
|
+
next if item.nil?
|
141
|
+
# if a command line attribute was not passed, and we have a cloud public_hostname, use that.
|
142
|
+
# see #configure_attribute for the source of config[:attribute] and config[:override_attribute]
|
143
|
+
if !config[:override_attribute] && item[:cloud] and item[:cloud][:public_hostname]
|
144
|
+
i = item[:cloud][:public_hostname]
|
145
|
+
elsif config[:override_attribute]
|
146
|
+
i = extract_nested_value(item, config[:override_attribute])
|
147
|
+
else
|
148
|
+
i = extract_nested_value(item, config[:attribute])
|
149
|
+
end
|
150
|
+
# next if we couldn't find the specified attribute in the returned node object
|
151
|
+
next if i.nil?
|
152
|
+
r.push(i)
|
153
|
+
end
|
154
|
+
r
|
155
|
+
end
|
156
|
+
if list.length == 0
|
157
|
+
if @action_nodes.length == 0
|
158
|
+
ui.fatal("No nodes returned from search!")
|
159
|
+
else
|
160
|
+
ui.fatal("#{@action_nodes.length} #{@action_nodes.length > 1 ? "nodes":"node"} found, " +
|
161
|
+
"but does not have the required attribute to establish the connection. " +
|
162
|
+
"Try setting another attribute to open the connection using --attribute.")
|
163
|
+
end
|
164
|
+
exit 10
|
165
|
+
end
|
166
|
+
session_from_list(list)
|
167
|
+
end
|
168
|
+
|
169
|
+
def session_from_list(list)
|
170
|
+
list.each do |item|
|
171
|
+
Chef::Log.debug("Adding #{item}")
|
172
|
+
session_opts = {}
|
173
|
+
|
174
|
+
ssh_config = Net::SSH.configuration_for(item)
|
175
|
+
|
176
|
+
# Chef::Config[:knife][:ssh_user] is parsed in #configure_user and written to config[:ssh_user]
|
177
|
+
user = config[:ssh_user] || ssh_config[:user]
|
178
|
+
hostspec = user ? "#{user}@#{item}" : item
|
179
|
+
session_opts[:keys] = File.expand_path(config[:identity_file]) if config[:identity_file]
|
180
|
+
session_opts[:keys_only] = true if config[:identity_file]
|
181
|
+
session_opts[:password] = config[:ssh_password] if config[:ssh_password]
|
182
|
+
session_opts[:forward_agent] = config[:forward_agent]
|
183
|
+
session_opts[:port] = config[:ssh_port] || Chef::Config[:knife][:ssh_port] || ssh_config[:port]
|
184
|
+
session_opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug
|
185
|
+
|
186
|
+
if !config[:host_key_verify]
|
187
|
+
session_opts[:paranoid] = false
|
188
|
+
session_opts[:user_known_hosts_file] = "/dev/null"
|
189
|
+
end
|
190
|
+
|
191
|
+
session.use(hostspec, session_opts)
|
192
|
+
|
193
|
+
@longest = item.length if item.length > @longest
|
194
|
+
end
|
195
|
+
|
196
|
+
session
|
197
|
+
end
|
198
|
+
|
199
|
+
def fixup_sudo(command)
|
200
|
+
command.sub(/^sudo/, 'sudo -p \'knife sudo password: \'')
|
201
|
+
end
|
202
|
+
|
203
|
+
def print_data(host, data)
|
204
|
+
@buffers ||= {}
|
205
|
+
if leftover = @buffers[host]
|
206
|
+
@buffers[host] = nil
|
207
|
+
print_data(host, leftover + data)
|
208
|
+
else
|
209
|
+
if newline_index = data.index("\n")
|
210
|
+
line = data.slice!(0...newline_index)
|
211
|
+
data.slice!(0)
|
212
|
+
print_line(host, line)
|
213
|
+
print_data(host, data)
|
214
|
+
else
|
215
|
+
@buffers[host] = data
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def print_line(host, data)
|
221
|
+
padding = @longest - host.length
|
222
|
+
str = ui.color(host, :cyan) + (" " * (padding + 1)) + data
|
223
|
+
ui.msg(str)
|
224
|
+
end
|
225
|
+
|
226
|
+
def ssh_command(command, subsession=nil)
|
227
|
+
exit_status = 0
|
228
|
+
subsession ||= session
|
229
|
+
command = fixup_sudo(command)
|
230
|
+
command.force_encoding('binary') if command.respond_to?(:force_encoding)
|
231
|
+
subsession.open_channel do |ch|
|
232
|
+
ch.request_pty
|
233
|
+
ch.exec command do |ch, success|
|
234
|
+
raise ArgumentError, "Cannot execute #{command}" unless success
|
235
|
+
ch.on_data do |ichannel, data|
|
236
|
+
print_data(ichannel[:host], data)
|
237
|
+
if data =~ /^knife sudo password: /
|
238
|
+
print_data(ichannel[:host], "\n")
|
239
|
+
ichannel.send_data("#{get_password}\n")
|
240
|
+
end
|
241
|
+
end
|
242
|
+
ch.on_request "exit-status" do |ichannel, data|
|
243
|
+
exit_status = [exit_status, data.read_long].max
|
244
|
+
@not_zerro_nodes << { 'node' => ichannel[:host], "message" => "exit status: #{exit_status}" } if exit_status != 0
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
session.loop
|
249
|
+
exit_status
|
250
|
+
end
|
251
|
+
|
252
|
+
def get_password
|
253
|
+
@password ||= prompt_for_password
|
254
|
+
end
|
255
|
+
|
256
|
+
def prompt_for_password(prompt = "Enter your password: ")
|
257
|
+
ui.ask(prompt) { |q| q.echo = false }
|
258
|
+
end
|
259
|
+
|
260
|
+
def configure_attribute
|
261
|
+
config[:override_attribute] = config[:attribute] || Chef::Config[:knife][:ssh_attribute]
|
262
|
+
config[:attribute] = (Chef::Config[:knife][:ssh_attribute] ||
|
263
|
+
config[:attribute] ||
|
264
|
+
"private_ipaddress").strip
|
265
|
+
end
|
266
|
+
|
267
|
+
|
268
|
+
def get_stripped_unfrozen_value(value)
|
269
|
+
return nil if value.nil?
|
270
|
+
value.strip
|
271
|
+
end
|
272
|
+
|
273
|
+
def configure_user
|
274
|
+
config[:ssh_user] = get_stripped_unfrozen_value(config[:ssh_user] ||
|
275
|
+
Chef::Config[:knife][:ssh_user])
|
276
|
+
end
|
277
|
+
|
278
|
+
def configure_identity_file
|
279
|
+
config[:identity_file] = get_stripped_unfrozen_value(config[:identity_file] ||
|
280
|
+
Chef::Config[:knife][:ssh_identity_file])
|
281
|
+
end
|
282
|
+
|
283
|
+
def extract_nested_value(data_structure, path_spec)
|
284
|
+
ui.presenter.extract_nested_value(data_structure, path_spec)
|
285
|
+
end
|
286
|
+
|
287
|
+
def print_failed(arr = [{"node" => "ip", "meesage" => "message"}])
|
288
|
+
return if arr.compact.empty?
|
289
|
+
arr.each do |status|
|
290
|
+
ui.error("#{status['node']} #{status['message']}")
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def run
|
295
|
+
extend Chef::Mixin::Command
|
296
|
+
|
297
|
+
@longest = 0
|
298
|
+
|
299
|
+
@failed_connect_nodes = []
|
300
|
+
@not_zerro_nodes = []
|
301
|
+
|
302
|
+
configure_attribute
|
303
|
+
configure_user
|
304
|
+
configure_identity_file
|
305
|
+
configure_gateway
|
306
|
+
configure_session
|
307
|
+
|
308
|
+
exit_status = ssh_command(@name_args[1..-1].join(" "))
|
309
|
+
|
310
|
+
session.close
|
311
|
+
|
312
|
+
print_failed(@failed_connect_nodes)
|
313
|
+
print_failed(@not_zerro_nodes)
|
314
|
+
|
315
|
+
if (@not_zerro_nodes + @failed_connect_nodes).compact.empty?
|
316
|
+
exit_status
|
317
|
+
else
|
318
|
+
my_status = exit_status == 0 ? 1 : exit_status
|
319
|
+
exit my_status
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
data/lib/knife/undev/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: knife-undev
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vasiliev Dmitry
|
@@ -101,7 +101,7 @@ files:
|
|
101
101
|
- lib/knife/undev/converter.rb
|
102
102
|
- lib/knife/undev/monkey_patches/node_presenter.rb
|
103
103
|
- lib/knife/undev/monkey_patches/object_loader.rb
|
104
|
-
- lib/knife/undev/
|
104
|
+
- lib/knife/undev/plugins/cloud_ssh.rb
|
105
105
|
- lib/knife/undev/version.rb
|
106
106
|
homepage: ''
|
107
107
|
licenses:
|
@@ -1,17 +0,0 @@
|
|
1
|
-
require 'chef/knife'
|
2
|
-
require 'chef/knife/ssh'
|
3
|
-
|
4
|
-
class Chef
|
5
|
-
class Knife
|
6
|
-
class Ssh
|
7
|
-
|
8
|
-
def configure_attribute
|
9
|
-
config[:override_attribute] = config[:attribute] || Chef::Config[:knife][:ssh_attribute]
|
10
|
-
config[:attribute] = (Chef::Config[:knife][:ssh_attribute] ||
|
11
|
-
config[:attribute] || "private_ipaddress").strip
|
12
|
-
end
|
13
|
-
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|