# File lib/chef/knife/ssh.rb, line 414 def configure_attribute # Setting 'knife[:ssh_attribute] = "foo"' in knife.rb => Chef::Config[:knife][:ssh_attribute] == 'foo' # Running 'knife ssh -a foo' => both Chef::Config[:knife][:ssh_attribute] && config[:attribute] == foo # Thus we can differentiate between a config file value and a command line override at this point by checking config[:attribute] # We can tell here if fqdn was passed from the command line, rather than being the default, by checking config[:attribute] # However, after here, we cannot tell these things, so we must preserve config[:attribute] config[:attribute_from_cli] = config[:attribute] config[:attribute] = (config[:attribute_from_cli] || Chef::Config[:knife][:ssh_attribute] || "fqdn").strip end
# File lib/chef/knife/ssh.rb, line 129 def configure_gateway config[:ssh_gateway] ||= Chef::Config[:knife][:ssh_gateway] if config[:ssh_gateway] gw_host, gw_user = config[:ssh_gateway].split('@').reverse gw_host, gw_port = gw_host.split(':') gw_opts = gw_port ? { :port => gw_port } : {} session.via(gw_host, gw_user || config[:ssh_user], gw_opts) end rescue Net::SSH::AuthenticationFailed user = gw_user || config[:ssh_user] prompt = "Enter the password for #{user}@#{gw_host}: " gw_opts.merge!(:password => prompt_for_password(prompt)) session.via(gw_host, user, gw_opts) end
# File lib/chef/knife/ssh.rb, line 483 def configure_identity_file config[:identity_file] = get_stripped_unfrozen_value(config[:identity_file] || Chef::Config[:knife][:ssh_identity_file]) end
This is a bit overly complicated because of the way we want knife ssh to work with -P causing a password prompt for the user, but we have to be conscious that this code gets included in knife bootstrap and knife * server create as well. We want to change the semantics so that the default is false and ‘nil’ means -P without an argument on the command line. But the other utilities expect nil to be the default and we can’t prompt in that case. So we effectively use ssh_password_ng to determine if we’re coming from knife ssh or from the other utilities. The other utilties can also be patched to use ssh_password_ng easily as long they follow the convention that the default is false.
# File lib/chef/knife/ssh.rb, line 464 def configure_password if config.has_key?(:ssh_password_ng) && config[:ssh_password_ng].nil? # If the parameter is called on the command line with no value # it will set :ssh_password_ng = nil # This is where we want to trigger a prompt for password config[:ssh_password] = get_password else # if ssh_password_ng is false then it has not been set at all, and we may be in knife ec2 and still # using an old config[:ssh_password]. this is backwards compatibility. all knife cloud plugins should # be updated to use ssh_password_ng with a default of false and ssh_password should be retired, (but # we'll still need to use the ssh_password out of knife.rb if we find that). ssh_password = config.has_key?(:ssh_password_ng) ? config[:ssh_password_ng] : config[:ssh_password] # Otherwise, the password has either been specified on the command line, # in knife.rb, or key based auth will be attempted config[:ssh_password] = get_stripped_unfrozen_value(ssh_password || Chef::Config[:knife][:ssh_password]) end end
# File lib/chef/knife/ssh.rb, line 145 def configure_session list = config[:manual] ? @name_args[0].split(" ") : search_nodes if list.length == 0 if @action_nodes.length == 0 ui.fatal("No nodes returned from search!") else ui.fatal("#{@action_nodes.length} #{@action_nodes.length > 1 ? "nodes":"node"} found, " + "but does not have the required attribute to establish the connection. " + "Try setting another attribute to open the connection using --attribute.") end exit 10 end session_from_list(list) end
# File lib/chef/knife/ssh.rb, line 453 def configure_user config[:ssh_user] = get_stripped_unfrozen_value(config[:ssh_user] || Chef::Config[:knife][:ssh_user]) end
# File lib/chef/knife/ssh.rb, line 424 def cssh cssh_cmd = nil ]csshX cssh].each do |cmd| begin # Unix and Mac only cssh_cmd = shell_out!("which #{cmd}").stdout.strip break rescue Mixlib::ShellOut::ShellCommandFailed end end raise Chef::Exceptions::Exec, "no command found for cssh" unless cssh_cmd # pass in the consolidated itentity file option to cssh(X) if config[:identity_file] cssh_cmd << " --ssh_args '-i #{File.expand_path(config[:identity_file])}'" end session.servers_for.each do |server| cssh_cmd << " #{server.user ? "#{server.user}@#{server.host}" : server.host}" end Chef::Log.debug("starting cssh session with command: #{cssh_cmd}") exec(cssh_cmd) end
# File lib/chef/knife/ssh.rb, line 488 def extract_nested_value(data_structure, path_spec) ui.presenter.extract_nested_value(data_structure, path_spec) end
# File lib/chef/knife/ssh.rb, line 229 def fixup_sudo(command) command.sub(/^sudo/, 'sudo -p \knife sudo password: \') end
# File lib/chef/knife/ssh.rb, line 281 def get_password @password ||= prompt_for_password end
# File lib/chef/knife/ssh.rb, line 448 def get_stripped_unfrozen_value(value) return nil if value.nil? value.strip end
# File lib/chef/knife/ssh.rb, line 314 def interactive puts "Connected to #{ui.list(session.servers_for.collect { |s| ui.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
# File lib/chef/knife/ssh.rb, line 390 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
# File lib/chef/knife/ssh.rb, line 233 def print_data(host, data) @buffers ||= {} if leftover = @buffers[host] @buffers[host] = nil print_data(host, leftover + data) else if newline_index = data.index("\n") line = data.slice!(0...newline_index) data.slice!(0) print_line(host, line) print_data(host, data) else @buffers[host] = data end end end
# File lib/chef/knife/ssh.rb, line 250 def print_line(host, data) padding = @longest - host.length str = ui.color(host, :cyan) + (" " * (padding + 1)) + data ui.msg(str) end
# File lib/chef/knife/ssh.rb, line 285 def prompt_for_password(prompt = "Enter your password: ") ui.ask(prompt) { |q| q.echo = false } end
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.
# File lib/chef/knife/ssh.rb, line 293 def read_line loop do command = reader.readline("#{ui.color('knife-ssh>', :bold)} ", true) if command.nil? command = "exit" puts(command) else command.strip! end unless command.empty? return command end end end
# File lib/chef/knife/ssh.rb, line 492 def run extend Chef::Mixin::Command @longest = 0 configure_attribute configure_user configure_password configure_identity_file configure_gateway configure_session exit_status = case @name_args[1] when "interactive" interactive when "screen" screen when "tmux" tmux when "macterm" macterm when "cssh" cssh when "csshx" Chef::Log.warn("knife ssh csshx will be deprecated in a future release") Chef::Log.warn("please use knife ssh cssh instead") cssh else ssh_command(@name_args[1..-1].join(" ")) end session.close if exit_status != 0 exit exit_status else exit_status end end
# File lib/chef/knife/ssh.rb, line 343 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 ") tf.print("-i #{config[:identity_file]} ") if config[:identity_file] server.user ? tf.puts("#{server.user}@#{server.host}") : tf.puts(server.host) window += 1 end tf.close exec("screen -c #{tf.path}") end
# File lib/chef/knife/ssh.rb, line 106 def session config[:on_error] ||= :skip 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 case config[:on_error] when :skip ui.warn "Failed to connect to #{server.host} -- #{$!.class.name}: #{$!.message}" $!.backtrace.each { |l| Chef::Log.debug(l) } when :raise #Net::SSH::Multi magic to force exception to be re-raised. throw :go, :raise end end @session ||= Net::SSH::Multi.start(:concurrent_connections => config[:concurrency], :on_error => ssh_error_handler) end
# File lib/chef/knife/ssh.rb, line 195 def session_from_list(list) list.each do |item| host, ssh_port = item Chef::Log.debug("Adding #{host}") session_opts = {} ssh_config = Net::SSH.configuration_for(host) # Chef::Config[:knife][:ssh_user] is parsed in #configure_user and written to config[:ssh_user] user = config[:ssh_user] || ssh_config[:user] hostspec = user ? "#{user}@#{host}" : host session_opts[:keys] = File.expand_path(config[:identity_file]) if config[:identity_file] session_opts[:keys_only] = true if config[:identity_file] session_opts[:password] = config[:ssh_password] if config[:ssh_password] session_opts[:forward_agent] = config[:forward_agent] session_opts[:port] = config[:ssh_port] || ssh_port || # Use cloud port if available Chef::Config[:knife][:ssh_port] || ssh_config[:port] session_opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug if !config[:host_key_verify] session_opts[:paranoid] = false session_opts[:user_known_hosts_file] = "/dev/null" end session.use(hostspec, session_opts) @longest = host.length if host.length > @longest end session end
# File lib/chef/knife/ssh.rb, line 256 def ssh_command(command, subsession=nil) exit_status = 0 subsession ||= session command = fixup_sudo(command) command.force_encoding('binary') if command.respond_to?(:force_encoding) 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: / print_data(ichannel[:host], "\n") ichannel.send_data("#{get_password}\n") end end ch.on_request "exit-status" do |ichannel, data| exit_status = [exit_status, data.read_long].max end end end session.loop exit_status end
# File lib/chef/knife/ssh.rb, line 361 def tmux ssh_dest = lambda do |server| identity = "-i #{config[:identity_file]} " if config[:identity_file] prefix = server.user ? "#{server.user}@" : "" "'ssh #{identity}#{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].gsub(/:/,'=')}'" 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(" ") shell_out!(cmd) exec("tmux attach-session -t #{tmux_name}") rescue Chef::Exceptions::Exec end end
Generated with the Darkfish Rdoc Generator 2.