Class: Chef::Knife::Ssh
Constant Summary
Constants inherited
from Chef::Knife
DEFAULT_SUBCOMMAND_FILES
Instance Attribute Summary collapse
Attributes inherited from Chef::Knife
#name_args
Instance Method Summary
collapse
Methods inherited from Chef::Knife
#ask_question, #bulk_delete, category, #configure_chef, #confirm, #create_object, #delete_object, #edit_data, #edit_object, #file_exists_and_is_readable?, #format_for_display, #format_list_for_display, guess_category, inherited, #initialize, list_commands, load_commands, #load_from_file, #msg, msg, #output, #parse_options, #pretty_print, #rest, run, #show_usage, snake_case_name, #stdin, #stdout, subcommand_category, subcommand_class_from, subcommands, subcommands_by_category, unnamed?
#convert_to_class_name, #convert_to_snake_case, #filename_to_qualified_string, #snake_case_basename
Constructor Details
This class inherits a constructor from Chef::Knife
Instance Attribute Details
#password=(value) ⇒ Object
Sets the attribute password
31
32
33
|
# File 'lib/chef/knife/ssh.rb', line 31
def password=(value)
@password = value
end
|
Instance Method Details
#assert_net_ssh_version_acceptable! ⇒ Object
:nodoc: TODO: remove this stuff entirely and package knife ssh as a knife plugin. (Dan - 08 Jul 2010)
The correct way to specify version deps is in the gemspec or other packaging. However, we don’t want to have a gem dep on net-ssh, because it’s a hassle when you only need the chef-client (e.g., on a managed node). So we have to check here that you have a decent version of Net::SSH.
net-ssh of lower versions has a bug that causes ‘knife ssh (searchterm) (commandname)“ to loop infinitely and consume all the CPU of one core.
326
327
328
329
330
331
332
333
|
# File 'lib/chef/knife/ssh.rb', line 326
def assert_net_ssh_version_acceptable!
netssh_version = Net::SSH::Version
unless (netssh_version::MAJOR == 2) && (netssh_version::TINY >= 23 || netssh_version::MINOR >= 1)
STDERR.puts "ERROR: Please install net-ssh version 2.0.23 or higher, as lower versions cause issues."
exit 1
end
end
|
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
# File 'lib/chef/knife/ssh.rb', line 90
def configure_session
list = case config[:manual]
when true
@name_args[0].split(" ")
when false
r = Array.new
q = Chef::Search::Query.new
@action_nodes = q.search(:node, @name_args[0])[0]
@action_nodes.each do |item|
i = format_for_display(item)[config[:attribute]]
r.push(i) unless i.nil?
end
r
end
(Chef::Log.fatal("No nodes returned from search!"); exit 10) if list.length == 0
session_from_list(list)
end
|
#fixup_sudo(command) ⇒ Object
125
126
127
|
# File 'lib/chef/knife/ssh.rb', line 125
def fixup_sudo(command)
command.sub(/^sudo/, 'sudo -p \'knife sudo password: \'')
end
|
#get_password ⇒ Object
158
159
160
|
# File 'lib/chef/knife/ssh.rb', line 158
def get_password
@password ||= h.ask("Enter your password: ") { |q| q.echo = false }
end
|
#h ⇒ Object
86
87
88
|
# File 'lib/chef/knife/ssh.rb', line 86
def h
@highline ||= HighLine.new
end
|
#interactive ⇒ Object
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
|
# File 'lib/chef/knife/ssh.rb', line 187
def interactive
puts "Connected to #{h.list(session.servers_for.collect { |s| h.color(s.host, :cyan) }, :inline, " and ")}"
puts
puts "To run a command on a list of servers, do:"
puts " on SERVER1 SERVER2 SERVER3; COMMAND"
puts " Example: on latte foamy; echo foobar"
puts
puts "To exit interactive mode, use 'quit!'"
puts
while 1
command = read_line
case command
when 'quit!'
puts 'Bye!'
break
when /^on (.+?); (.+)$/
raw_list = $1.split(" ")
server_list = Array.new
session.servers.each do |session_server|
server_list << session_server if raw_list.include?(session_server.host)
end
command = $2
ssh_command(command, session.on(*server_list))
else
ssh_command(command)
end
end
end
|
#load_late_dependencies ⇒ Object
308
309
310
311
312
313
314
|
# File 'lib/chef/knife/ssh.rb', line 308
def load_late_dependencies
require 'readline'
%w[net/ssh/multi highline].each do |dep|
load_late_dependency dep
end
assert_net_ssh_version_acceptable!
end
|
#macterm ⇒ Object
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
|
# File 'lib/chef/knife/ssh.rb', line 261
def macterm
begin
require 'appscript'
rescue LoadError
STDERR.puts "you need the rb-appscript gem to use knife ssh macterm. `(sudo) gem install rb-appscript` to install"
raise
end
Appscript.app("/Applications/Utilities/Terminal.app").windows.first.activate
Appscript.app("System Events").application_processes["Terminal.app"].keystroke("n", :using=>:command_down)
term = Appscript.app('Terminal')
window = term.windows.first.get
(session.servers_for.size - 1).times do |i|
window.activate
Appscript.app("System Events").application_processes["Terminal.app"].keystroke("t", :using=>:command_down)
end
session.servers_for.each_with_index do |server, tab_number|
cmd = "unset PROMPT_COMMAND; echo -e \"\\033]0;#{server.host}\\007\"; ssh #{server.user ? "#{server.user}@#{server.host}" : server.host}"
Appscript.app('Terminal').do_script(cmd, :in => window.tabs[tab_number + 1].get)
end
end
|
#print_data(host, data) ⇒ Object
129
130
131
132
133
134
135
136
137
138
|
# File 'lib/chef/knife/ssh.rb', line 129
def print_data(host, data)
if data =~ /\n/
data.split(/\n/).each { |d| print_data(host, d) }
else
padding = @longest - host.length
print h.color(host, :cyan)
padding.downto(0) { print " " }
puts data
end
end
|
#read_line ⇒ Object
Present the prompt and read a single line from the console. It also detects ^D and returns “exit” in that case. Adds the input to the history, unless the input is empty. Loops repeatedly until a non-empty line is input.
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
|
# File 'lib/chef/knife/ssh.rb', line 166
def read_line
loop do
command = reader.readline("#{h.color('knife-ssh>', :bold)} ", true)
if command.nil?
command = "exit"
puts(command)
else
command.strip!
end
unless command.empty?
return command
end
end
end
|
#reader ⇒ Object
183
184
185
|
# File 'lib/chef/knife/ssh.rb', line 183
def reader
Readline
end
|
#run ⇒ Object
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
|
# File 'lib/chef/knife/ssh.rb', line 285
def run
@longest = 0
load_late_dependencies
configure_session
case @name_args[1]
when "interactive"
interactive
when "screen"
screen
when "tmux"
tmux
when "macterm"
macterm
else
ssh_command(@name_args[1..-1].join(" "))
end
session.close
end
|
#screen ⇒ Object
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
|
# File 'lib/chef/knife/ssh.rb', line 216
def screen
tf = Tempfile.new("knife-ssh-screen")
if File.exist? "#{ENV["HOME"]}/.screenrc"
tf.puts("source #{ENV["HOME"]}/.screenrc")
end
tf.puts("caption always '%-Lw%{= BW}%50>%n%f* %t%{-}%+Lw%<'")
tf.puts("hardstatus alwayslastline 'knife ssh #{@name_args[0]}'")
window = 0
session.servers_for.each do |server|
tf.print("screen -t \"#{server.host}\" #{window} ssh ")
server.user ? tf.puts("#{server.user}@#{server.host}") : tf.puts(server.host)
window += 1
end
tf.close
exec("screen -c #{tf.path}")
end
|
#session ⇒ Object
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
# File 'lib/chef/knife/ssh.rb', line 70
def session
ssh_error_handler = Proc.new do |server|
if config[:manual]
node_name = server.host
else
@action_nodes.each do |n|
node_name = n if format_for_display(n)[config[:attribute]] == server.host
end
end
Chef::Log.warn "Failed to connect to #{node_name} -- #{$!.class.name}: #{$!.message}"
$!.backtrace.each { |l| Chef::Log.debug(l) }
end
@session ||= Net::SSH::Multi.start(:concurrent_connections => config[:concurrency], :on_error => ssh_error_handler)
end
|
#session_from_list(list) ⇒ Object
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
# File 'lib/chef/knife/ssh.rb', line 108
def session_from_list(list)
list.each do |item|
Chef::Log.debug("Adding #{item}")
hostspec = config[:ssh_user] ? "#{config[:ssh_user]}@#{item}" : item
session_opts = {}
session_opts[:keys] = File.expand_path(config[:identity_file]) if config[:identity_file]
session_opts[:password] = config[:ssh_password] if config[:ssh_password]
session_opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug
session.use(hostspec, session_opts)
@longest = item.length if item.length > @longest
end
session
end
|
#ssh_command(command, subsession = nil) ⇒ Object
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
# File 'lib/chef/knife/ssh.rb', line 140
def ssh_command(command, subsession=nil)
subsession ||= session
command = fixup_sudo(command)
subsession.open_channel do |ch|
ch.request_pty
ch.exec command do |ch, success|
raise ArgumentError, "Cannot execute #{command}" unless success
ch.on_data do |ichannel, data|
print_data(ichannel[:host], data)
if data =~ /^knife sudo password: /
ichannel.send_data("#{get_password}\n")
end
end
end
end
session.loop
end
|
#tmux ⇒ Object
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
|
# File 'lib/chef/knife/ssh.rb', line 233
def tmux
ssh_dest = lambda do |server|
prefix = server.user ? "#{server.user}@" : ""
"'ssh #{prefix}#{server.host}'"
end
new_window_cmds = lambda do
if session.servers_for.size > 1
[""] + session.servers_for[1..-1].map do |server|
"new-window -a -n '#{server.host}' #{ssh_dest.call(server)}"
end
else
[]
end.join(" \\; ")
end
tmux_name = "'knife ssh #{@name_args[0]}'"
begin
server = session.servers_for.first
cmd = ["tmux new-session -d -s #{tmux_name}",
"-n '#{server.host}'", ssh_dest.call(server),
new_window_cmds.call].join(" ")
Chef::Mixin::Command.run_command(:command => cmd)
exec("tmux attach-session -t #{tmux_name}")
rescue Chef::Exceptions::Exec
end
end
|