summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorelijah <elijah@riseup.net>2016-06-23 15:49:03 -0700
committerelijah <elijah@riseup.net>2016-07-01 14:48:42 -0700
commitc77cace5225eb16d35865664754e88f4d67bba7f (patch)
tree2198b0e79cc7c397971e5466d07e7eb83ebfa68d /lib
parentcfb91a199c8c205b99c4424df77b0b6ed20e4288 (diff)
migrate commands to use new ssh system: node init, test, add-user
Diffstat (limited to 'lib')
-rw-r--r--lib/leap_cli/commands/deploy.rb201
-rw-r--r--lib/leap_cli/commands/facts.rb13
-rw-r--r--lib/leap_cli/commands/info.rb15
-rw-r--r--lib/leap_cli/commands/node_init.rb87
-rw-r--r--lib/leap_cli/commands/run.rb47
-rw-r--r--lib/leap_cli/commands/ssh.rb14
-rw-r--r--lib/leap_cli/commands/test.rb41
-rw-r--r--lib/leap_cli/commands/user.rb73
-rw-r--r--lib/leap_cli/commands/util.rb1
-rw-r--r--lib/leap_cli/ssh.rb7
-rw-r--r--lib/leap_cli/ssh/backend.rb154
-rw-r--r--lib/leap_cli/ssh/formatter.rb75
-rw-r--r--lib/leap_cli/ssh/key.rb199
-rw-r--r--lib/leap_cli/ssh/options.rb93
-rw-r--r--lib/leap_cli/ssh/remote_command.rb107
-rw-r--r--lib/leap_cli/ssh/scripts.rb136
16 files changed, 1054 insertions, 209 deletions
diff --git a/lib/leap_cli/commands/deploy.rb b/lib/leap_cli/commands/deploy.rb
index 9dd190ab..165ce588 100644
--- a/lib/leap_cli/commands/deploy.rb
+++ b/lib/leap_cli/commands/deploy.rb
@@ -29,57 +29,7 @@ module LeapCli
:arg_name => 'IPADDRESS'
c.action do |global,options,args|
-
- if options[:dev] != true
- init_submodules
- end
-
- nodes = manager.filter!(args, :disabled => false)
- if nodes.size > 1
- say "Deploying to these nodes: #{nodes.keys.join(', ')}"
- if !global[:yes] && !agree("Continue? ")
- quit! "OK. Bye."
- end
- end
-
- environments = nodes.field('environment').uniq
- if environments.empty?
- environments = [nil]
- end
- environments.each do |env|
- check_platform_pinning(env, global)
- end
-
- # compile hiera files for all the nodes in every environment that is
- # being deployed and only those environments.
- compile_hiera_files(manager.filter(environments), false)
-
- ssh_connect(nodes, connect_options(options)) do |ssh|
- ssh.leap.log :checking, 'node' do
- ssh.leap.check_for_no_deploy
- ssh.leap.assert_initialized
- end
- ssh.leap.log :synching, "configuration files" do
- sync_hiera_config(ssh)
- sync_support_files(ssh)
- end
- ssh.leap.log :synching, "puppet manifests" do
- sync_puppet_files(ssh)
- end
- unless options[:sync]
- ssh.leap.log :applying, "puppet" do
- ssh.puppet.apply(:verbosity => [LeapCli.log_level,5].min,
- :tags => tags(options),
- :force => options[:force],
- :info => deploy_info,
- :downgrade => options[:downgrade]
- )
- end
- end
- end
- if !Util.exit_status.nil? && Util.exit_status != 0
- log :warning, "puppet did not finish successfully."
- end
+ run_deploy(global, options, args)
end
end
@@ -94,19 +44,87 @@ module LeapCli
c.switch :last, :desc => 'Show last deploy only',
:negatable => false
c.action do |global,options,args|
- if options[:last] == true
- lines = 1
- else
- lines = 10
+ run_history(global, options, args)
+ end
+ end
+
+ private
+
+ def run_deploy(global, options, args)
+ require 'leap_cli/ssh'
+
+ if options[:dev] != true
+ init_submodules
+ end
+
+ nodes = manager.filter!(args, :disabled => false)
+ if nodes.size > 1
+ say "Deploying to these nodes: #{nodes.keys.join(', ')}"
+ if !global[:yes] && !agree("Continue? ")
+ quit! "OK. Bye."
end
- nodes = manager.filter!(args)
- ssh_connect(nodes, connect_options(options)) do |ssh|
- ssh.leap.history(lines)
+ end
+
+ environments = nodes.field('environment').uniq
+ if environments.empty?
+ environments = [nil]
+ end
+ environments.each do |env|
+ check_platform_pinning(env, global)
+ end
+
+ # compile hiera files for all the nodes in every environment that is
+ # being deployed and only those environments.
+ compile_hiera_files(manager.filter(environments), false)
+
+ log :checking, 'nodes' do
+ SSH.remote_command(nodes, options) do |ssh, host|
+ begin
+ ssh.scripts.check_for_no_deploy
+ ssh.scripts.assert_initialized
+ rescue SSH::ExecuteError
+ # skip nodes with errors, but run others
+ nodes.delete(host.hostname)
+ end
+ end
+ end
+
+ log :synching, "configuration files" do
+ sync_hiera_config(nodes, options)
+ sync_support_files(nodes, options)
+ end
+ log :synching, "puppet manifests" do
+ sync_puppet_files(nodes, options)
+ end
+
+ unless options[:sync]
+ log :applying, "puppet" do
+ SSH.remote_command(nodes, options) do |ssh, host|
+ ssh.scripts.puppet_apply(
+ :verbosity => [LeapCli.log_level,5].min,
+ :tags => tags(options),
+ :force => options[:force],
+ :info => deploy_info,
+ :downgrade => options[:downgrade]
+ )
+ end
end
end
end
- private
+ def run_history(global, options, args)
+ require 'leap_cli/ssh'
+
+ if options[:last] == true
+ lines = 1
+ else
+ lines = 10
+ end
+ nodes = manager.filter!(args)
+ SSH.remote_command(nodes, options) do |ssh, host|
+ ssh.scripts.history(lines)
+ end
+ end
def forcible_prompt(forced, msg, prompt)
say(msg)
@@ -211,56 +229,51 @@ module LeapCli
end
end
- def sync_hiera_config(ssh)
- ssh.rsync.update do |server|
- node = manager.node(server.host)
+ def sync_hiera_config(nodes, options)
+ SSH.remote_sync(nodes, options) do |sync, host|
+ node = manager.node(host.hostname)
hiera_file = Path.relative_path([:hiera, node.name])
- ssh.leap.log hiera_file + ' -> ' + node.name + ':' + Leap::Platform.hiera_path
- {
- :source => hiera_file,
- :dest => Leap::Platform.hiera_path,
- :flags => "-rltp --chmod=u+rX,go-rwx"
- }
+ sync.log hiera_file + ' -> ' + node.name + ':' + Leap::Platform.hiera_path
+ sync.source = hiera_file
+ sync.dest = Leap::Platform.hiera_path
+ sync.flags = "-rltp --chmod=u+rX,go-rwx"
+ sync.exec
end
end
#
# sync various support files.
#
- def sync_support_files(ssh)
- dest_dir = Leap::Platform.files_dir
+ def sync_support_files(nodes, options)
+ dest_dir = Leap::Platform.files_dir
custom_files = build_custom_file_list
- ssh.rsync.update do |server|
- node = manager.node(server.host)
+ SSH.remote_sync(nodes, options) do |sync, host|
+ node = manager.node(host.hostname)
files_to_sync = node.file_paths.collect {|path| Path.relative_path(path, Path.provider) }
files_to_sync += custom_files
if files_to_sync.any?
- ssh.leap.log(files_to_sync.join(', ') + ' -> ' + node.name + ':' + dest_dir)
- {
- :chdir => Path.named_path(:files_dir),
- :source => ".",
- :dest => dest_dir,
- :excludes => "*",
- :includes => calculate_includes_from_files(files_to_sync, '/files'),
- :flags => "-rltp --chmod=u+rX,go-rwx --relative --delete --delete-excluded --copy-links"
- }
- else
- nil
+ sync.log(files_to_sync.join(', ') + ' -> ' + node.name + ':' + dest_dir)
+ sync.chdir = Path.named_path(:files_dir)
+ sync.source = "."
+ sync.dest = dest_dir
+ sync.excludes = "*"
+ sync.includes = calculate_includes_from_files(files_to_sync, '/files')
+ sync.flags = "-rltp --chmod=u+rX,go-rwx --relative --delete --delete-excluded --copy-links"
+ sync.exec
end
end
end
- def sync_puppet_files(ssh)
- ssh.rsync.update do |server|
- ssh.leap.log(Path.platform + '/[bin,tests,puppet] -> ' + server.host + ':' + Leap::Platform.leap_dir)
- {
- :dest => Leap::Platform.leap_dir,
- :source => '.',
- :chdir => Path.platform,
- :excludes => '*',
- :includes => ['/bin', '/bin/**', '/puppet', '/puppet/**', '/tests', '/tests/**'],
- :flags => "-rlt --relative --delete --copy-links"
- }
+ def sync_puppet_files(nodes, options)
+ SSH.remote_sync(nodes, options) do |sync, host|
+ sync.log(Path.platform + '/[bin,tests,puppet] -> ' + host.hostname + ':' + Leap::Platform.leap_dir)
+ sync.dest = Leap::Platform.leap_dir
+ sync.source = '.'
+ sync.chdir = Path.platform
+ sync.excludes = '*'
+ sync.includes = ['/bin', '/bin/**', '/puppet', '/puppet/**', '/tests', '/tests/**']
+ sync.flags = "-rlt --relative --delete --copy-links"
+ sync.exec
end
end
diff --git a/lib/leap_cli/commands/facts.rb b/lib/leap_cli/commands/facts.rb
index 11329ccc..6c954ee8 100644
--- a/lib/leap_cli/commands/facts.rb
+++ b/lib/leap_cli/commands/facts.rb
@@ -79,15 +79,18 @@ module LeapCli; module Commands
private
def update_facts(global_options, options, args)
+ require 'leap_cli/ssh'
nodes = manager.filter(args, :local => false, :disabled => false)
new_facts = {}
- ssh_connect(nodes) do |ssh|
- ssh.leap.run_with_progress(facter_cmd) do |response|
- node = manager.node(response[:host])
+ SSH.remote_command(nodes) do |ssh, host|
+ response = ssh.capture(facter_cmd, :log_output => false)
+ if response
+ log 'done', :host => host
+ node = manager.node(host)
if node
- new_facts[node.name] = response[:data].strip
+ new_facts[node.name] = response.strip
else
- log :warning, 'Could not find node for hostname %s' % response[:host]
+ log :warning, 'Could not find node for hostname %s' % host
end
end
end
diff --git a/lib/leap_cli/commands/info.rb b/lib/leap_cli/commands/info.rb
index 52225a94..a49c20c9 100644
--- a/lib/leap_cli/commands/info.rb
+++ b/lib/leap_cli/commands/info.rb
@@ -5,10 +5,17 @@ module LeapCli; module Commands
arg_name 'FILTER'
command [:info] do |c|
c.action do |global,options,args|
- nodes = manager.filter!(args)
- ssh_connect(nodes, connect_options(options)) do |ssh|
- ssh.leap.debug
- end
+ run_info(global, options, args)
+ end
+ end
+
+ private
+
+ def run_info(global, options, args)
+ require 'leap_cli/ssh'
+ nodes = manager.filter!(args)
+ SSH.remote_command(nodes, options) do |ssh, host|
+ ssh.scripts.debug
end
end
diff --git a/lib/leap_cli/commands/node_init.rb b/lib/leap_cli/commands/node_init.rb
index 9698a789..62a57496 100644
--- a/lib/leap_cli/commands/node_init.rb
+++ b/lib/leap_cli/commands/node_init.rb
@@ -14,50 +14,55 @@ module LeapCli; module Commands
"This command only needs to be run once, but there is no harm in running it multiple times."
cmd.arg_name 'FILTER'
cmd.command :init do |init|
- init.switch 'echo', :desc => 'If set, passwords are visible as you type them (default is hidden)', :negatable => false
+ #init.switch 'echo', :desc => 'If set, passwords are visible as you type them (default is hidden)', :negatable => false
+ # ^^ i am not sure how to get this working with sshkit
init.flag :port, :desc => 'Override the default SSH port.', :arg_name => 'PORT'
init.flag :ip, :desc => 'Override the default SSH IP address.', :arg_name => 'IPADDRESS'
init.action do |global,options,args|
- assert! args.any?, 'You must specify a FILTER'
- finished = []
- manager.filter!(args).each_node do |node|
- is_node_alive(node, options)
- save_public_host_key(node, global, options) unless node.vagrant?
- update_compiled_ssh_configs
- ssh_connect_options = connect_options(options).merge({:bootstrap => true, :echo => options[:echo]})
- ssh_connect(node, ssh_connect_options) do |ssh|
- if node.vagrant?
- ssh.install_insecure_vagrant_key
- end
- ssh.install_authorized_keys
- ssh.install_prerequisites
- unless node.vagrant?
- ssh.leap.log(:checking, "SSH host keys") do
- ssh.leap.capture(get_ssh_keys_cmd) do |response|
- update_local_ssh_host_keys(node, response[:data]) if response[:exitcode] == 0
- end
- end
- end
- ssh.leap.log(:updating, "facts") do
- ssh.leap.capture(facter_cmd) do |response|
- if response[:exitcode] == 0
- update_node_facts(node.name, response[:data])
- else
- log :failed, "to run facter on #{node.name}"
- end
- end
+ run_node_init(global, options, args)
+ end
+ end
+ end
+
+ private
+
+ def run_node_init(global, options, args)
+ require 'leap_cli/ssh'
+ assert! args.any?, 'You must specify a FILTER'
+ finished = []
+ manager.filter!(args).each_node do |node|
+ is_node_alive(node, options)
+ save_public_host_key(node, global, options) unless node.vagrant?
+ update_compiled_ssh_configs
+ # allow password auth for new nodes:
+ options[:auth_methods] = ["publickey", "password"]
+ SSH.remote_command(node, options) do |ssh, host|
+ if node.vagrant?
+ ssh.scripts.install_insecure_vagrant_key
+ end
+ ssh.scripts.install_authorized_keys
+ ssh.scripts.install_prerequisites
+ unless node.vagrant?
+ ssh.log(:checking, "SSH host keys") do
+ response = ssh.capture(get_ssh_keys_cmd, :log_output => false)
+ if response
+ update_local_ssh_host_keys(node, response)
end
end
- finished << node.name
end
- log :completed, "initialization of nodes #{finished.join(', ')}"
+ ssh.log(:updating, "facts") do
+ response = ssh.capture(facter_cmd)
+ if response
+ update_node_facts(node.name, response)
+ end
+ end
end
+ finished << node.name
end
+ log :completed, "initialization of nodes #{finished.join(', ')}"
end
- private
-
##
## PRIVATE HELPERS
##
@@ -83,7 +88,7 @@ module LeapCli; module Commands
pub_key_path = Path.named_path([:node_ssh_pub_key, node.name])
if Path.exists?(pub_key_path)
- if host_keys.include? SshKey.load(pub_key_path)
+ if host_keys.include? SSH::Key.load(pub_key_path)
log :trusted, "- Public SSH host key for #{node.name} matches previously saved key", :indent => 1
else
bail! do
@@ -96,7 +101,7 @@ module LeapCli; module Commands
if known_key
log :trusted, "- Public SSH host key for #{node.name} is trusted (key found in your ~/.ssh/known_hosts)"
else
- public_key = SshKey.pick_best_key(host_keys)
+ public_key = SSH::Key.pick_best_key(host_keys)
if public_key.nil?
bail!("We got back #{host_keys.size} host keys from #{node.name}, but we can't support any of them.")
else
@@ -118,7 +123,7 @@ module LeapCli; module Commands
#
# Get the public host keys for a host using ssh-keyscan.
- # Return an array of SshKey objects, one for each key.
+ # Return an array of SSH::Key objects, one for each key.
#
def get_public_keys_for_ip(address, port=22)
assert_bin!('ssh-keyscan')
@@ -130,7 +135,7 @@ module LeapCli; module Commands
if output =~ /No route to host/
bail! :failed, 'ssh-keyscan: no route to %s' % address
else
- keys = SshKey.parse_keys(output)
+ keys = SSH::Key.parse_keys(output)
if keys.empty?
bail! "ssh-keyscan got zero host keys back (that we understand)! Output was: #{output}"
else
@@ -139,7 +144,7 @@ module LeapCli; module Commands
end
end
- # run on the server to generate a string suitable for passing to SshKey.parse_keys()
+ # run on the server to generate a string suitable for passing to SSH::Key.parse_keys()
def get_ssh_keys_cmd
"/bin/grep ^HostKey /etc/ssh/sshd_config | /usr/bin/awk '{print $2 \".pub\"}' | /usr/bin/xargs /bin/cat"
end
@@ -149,10 +154,10 @@ module LeapCli; module Commands
# stored locally. In these cases, ask the user if they want to upgrade.
#
def update_local_ssh_host_keys(node, remote_keys_string)
- remote_keys = SshKey.parse_keys(remote_keys_string)
+ remote_keys = SSH::Key.parse_keys(remote_keys_string)
return unless remote_keys.any?
- current_key = SshKey.load(Path.named_path([:node_ssh_pub_key, node.name]))
- best_key = SshKey.pick_best_key(remote_keys)
+ current_key = SSH::Key.load(Path.named_path([:node_ssh_pub_key, node.name]))
+ best_key = SSH::Key.pick_best_key(remote_keys)
return unless best_key && current_key
if current_key != best_key
say(" One of the SSH host keys for node '#{node.name}' is better than what you currently have trusted.")
diff --git a/lib/leap_cli/commands/run.rb b/lib/leap_cli/commands/run.rb
new file mode 100644
index 00000000..52121035
--- /dev/null
+++ b/lib/leap_cli/commands/run.rb
@@ -0,0 +1,47 @@
+module LeapCli; module Commands
+
+ desc 'runs the specified command on each node.'
+ arg_name 'FILTER'
+ command :run do |c|
+ c.flag 'cmd', :arg_name => 'COMMAND', :desc => 'The command to run.'
+ c.switch 'stream', :default => false, :desc => 'If set, stream the output as it arrives. (default: --no-stream)'
+ c.flag 'port', :arg_name => 'SSH_PORT', :desc => 'Override default SSH port used when trying to connect to the server.'
+ c.action do |global, options, args|
+ run_shell_command(global, options, args)
+ end
+ end
+
+ private
+
+ def run_shell_command(global, options, args)
+ require 'leap_cli/ssh'
+ cmd = global[:force] ? options[:cmd] : LeapCli::SSH::Options.sanitize_command(options[:cmd])
+ nodes = manager.filter!(args)
+ if options[:stream]
+ stream_command(nodes, cmd, options)
+ else
+ capture_command(nodes, cmd, options)
+ end
+ end
+
+ def capture_command(nodes, cmd, options)
+ SSH.remote_command(nodes, options) do |ssh, host|
+ output = ssh.capture(cmd, :log_output => false)
+ if output
+ logger = LeapCli.new_logger
+ logger.log(:ran, "`" + cmd + "`", host: host.hostname, color: :green) do
+ logger.log(output, wrap: true)
+ end
+ end
+ end
+ end
+
+ def stream_command(nodes, cmd, options)
+ SSH.remote_command(nodes, options) do |ssh, host|
+ ssh.stream(cmd, :log_cmd => true, :log_finish => true, :fail_msg => 'oops')
+ end
+ end
+
+end; end
+
+
diff --git a/lib/leap_cli/commands/ssh.rb b/lib/leap_cli/commands/ssh.rb
index 3887618e..695812b8 100644
--- a/lib/leap_cli/commands/ssh.rb
+++ b/lib/leap_cli/commands/ssh.rb
@@ -69,20 +69,6 @@ module LeapCli; module Commands
protected
- #
- # allow for ssh overrides of all commands that use ssh_connect
- #
- def connect_options(options)
- connect_options = {:ssh_options=>{}}
- if options[:port]
- connect_options[:ssh_options][:port] = options[:port]
- end
- if options[:ip]
- connect_options[:ssh_options][:host_name] = options[:ip]
- end
- return connect_options
- end
-
def ssh_config_help_message
puts ""
puts "Are 'too many authentication failures' getting you down?"
diff --git a/lib/leap_cli/commands/test.rb b/lib/leap_cli/commands/test.rb
index f506d849..70eb00fd 100644
--- a/lib/leap_cli/commands/test.rb
+++ b/lib/leap_cli/commands/test.rb
@@ -7,24 +7,7 @@ module LeapCli; module Commands
test.command :run do |run|
run.switch 'continue', :desc => 'Continue over errors and failures (default is --no-continue).', :negatable => true
run.action do |global_options,options,args|
- test_order = File.join(Path.platform, 'tests/order.rb')
- if File.exist?(test_order)
- require test_order
- end
- manager.filter!(args).names_in_test_dependency_order.each do |node_name|
- node = manager.nodes[node_name]
- begin
- ssh_connect(node) do |ssh|
- ssh.run(test_cmd(options))
- end
- rescue Capistrano::CommandError
- if options[:continue]
- exit_status(1)
- else
- bail!
- end
- end
- end
+ do_test_run(global_options, options, args)
end
end
@@ -40,6 +23,28 @@ module LeapCli; module Commands
private
+ def do_test_run(global_options, options, args)
+ require 'leap_cli/ssh'
+ test_order = File.join(Path.platform, 'tests/order.rb')
+ if File.exist?(test_order)
+ require test_order
+ end
+ manager.filter!(args).names_in_test_dependency_order.each do |node_name|
+ node = manager.nodes[node_name]
+ begin
+ SSH::remote_command(node, options) do |ssh, host|
+ ssh.stream(test_cmd(options), :raise_error => true, :log_wrap => true)
+ end
+ rescue LeapCli::SSH::ExecuteError
+ if options[:continue]
+ exit_status(1)
+ else
+ bail!
+ end
+ end
+ end
+ end
+
def test_cmd(options)
if options[:continue]
"#{Leap::Platform.leap_dir}/bin/run_tests --continue"
diff --git a/lib/leap_cli/commands/user.rb b/lib/leap_cli/commands/user.rb
index b842e854..68f42059 100644
--- a/lib/leap_cli/commands/user.rb
+++ b/lib/leap_cli/commands/user.rb
@@ -22,58 +22,67 @@ module LeapCli
c.flag 'pgp-pub-key', :desc => 'OpenPGP public key file for this new user'
c.action do |global_options,options,args|
- username = args.first
- if !username.any?
- if options[:self]
- username ||= `whoami`.strip
- else
- help! "Either USERNAME argument or --self flag is required."
- end
- end
- if Leap::Platform.reserved_usernames.include? username
- bail! %(The username "#{username}" is reserved. Sorry, pick another.)
- end
+ do_add_user(global_options, optinos, args)
+ end
+ end
- ssh_pub_key = nil
- pgp_pub_key = nil
+ private
- if options['ssh-pub-key']
- ssh_pub_key = read_file!(options['ssh-pub-key'])
- end
- if options['pgp-pub-key']
- pgp_pub_key = read_file!(options['pgp-pub-key'])
- end
+ def do_add_user(global, options, args)
+ require 'leap_cli/ssh'
+ username = args.first
+ if !username.any?
if options[:self]
- ssh_pub_key ||= pick_ssh_key.to_s
- pgp_pub_key ||= pick_pgp_key
+ username ||= `whoami`.strip
+ else
+ help! "Either USERNAME argument or --self flag is required."
end
+ end
+ if Leap::Platform.reserved_usernames.include? username
+ bail! %(The username "#{username}" is reserved. Sorry, pick another.)
+ end
- assert!(ssh_pub_key, 'Sorry, could not find SSH public key.')
+ ssh_pub_key = nil
+ pgp_pub_key = nil
- if ssh_pub_key
- write_file!([:user_ssh, username], ssh_pub_key)
- end
- if pgp_pub_key
- write_file!([:user_pgp, username], pgp_pub_key)
- end
+ if options['ssh-pub-key']
+ ssh_pub_key = read_file!(options['ssh-pub-key'])
+ end
+ if options['pgp-pub-key']
+ pgp_pub_key = read_file!(options['pgp-pub-key'])
+ end
- update_authorized_keys
+ if options[:self]
+ ssh_pub_key ||= pick_ssh_key.to_s
+ pgp_pub_key ||= pick_pgp_key
end
+
+ assert!(ssh_pub_key, 'Sorry, could not find SSH public key.')
+
+ if ssh_pub_key
+ write_file!([:user_ssh, username], ssh_pub_key)
+ end
+ if pgp_pub_key
+ write_file!([:user_pgp, username], pgp_pub_key)
+ end
+
+ update_authorized_keys
end
#
- # let the the user choose among the ssh public keys that we encounter, or just pick the key if there is only one.
+ # let the the user choose among the ssh public keys that we encounter, or
+ # just pick the key if there is only one.
#
def pick_ssh_key
ssh_keys = []
Dir.glob("#{ENV['HOME']}/.ssh/*.pub").each do |keyfile|
- ssh_keys << SshKey.load(keyfile)
+ ssh_keys << SSH::Key.load(keyfile)
end
if `which ssh-add`.strip.any?
`ssh-add -L 2> /dev/null`.split("\n").compact.each do |line|
- key = SshKey.load(line)
+ key = SSH::Key.load(line)
if key
key.comment = 'ssh-agent'
ssh_keys << key unless ssh_keys.include?(key)
diff --git a/lib/leap_cli/commands/util.rb b/lib/leap_cli/commands/util.rb
index c1da570e..e2dc03a0 100644
--- a/lib/leap_cli/commands/util.rb
+++ b/lib/leap_cli/commands/util.rb
@@ -2,7 +2,6 @@ module LeapCli; module Commands
extend self
extend LeapCli::Util
- extend LeapCli::Util::RemoteCommand
def path(name)
Path.named_path(name)
diff --git a/lib/leap_cli/ssh.rb b/lib/leap_cli/ssh.rb
new file mode 100644
index 00000000..8b604d1d
--- /dev/null
+++ b/lib/leap_cli/ssh.rb
@@ -0,0 +1,7 @@
+require 'sshkit'
+require_relative 'ssh/options'
+require_relative 'ssh/backend'
+require_relative 'ssh/formatter'
+require_relative 'ssh/scripts'
+require_relative 'ssh/remote_command'
+require_relative 'ssh/key'
diff --git a/lib/leap_cli/ssh/backend.rb b/lib/leap_cli/ssh/backend.rb
new file mode 100644
index 00000000..35277039
--- /dev/null
+++ b/lib/leap_cli/ssh/backend.rb
@@ -0,0 +1,154 @@
+#
+# A custome SSHKit backend, derived from the default netssh backend.
+# Our custom backend modifies the logging behavior and gracefully captures
+# common exceptions.
+#
+
+require 'sshkit'
+require 'leap_cli/ssh/formatter'
+require 'leap_cli/ssh/scripts'
+
+module SSHKit
+ class Command
+ #
+ # override exit_status in order to be less verbose
+ #
+ def exit_status=(new_exit_status)
+ @finished_at = Time.now
+ @exit_status = new_exit_status
+ if options[:raise_on_non_zero_exit] && exit_status > 0
+ message = ""
+ message += "exit status: " + exit_status.to_s + "\n"
+ message += "stdout: " + (full_stdout.strip.empty? ? "Nothing written" : full_stdout.strip) + "\n"
+ message += "stderr: " + (full_stderr.strip.empty? ? 'Nothing written' : full_stderr.strip) + "\n"
+ raise Failed, message
+ end
+ end
+ end
+end
+
+module LeapCli
+ module SSH
+ class Backend < SSHKit::Backend::Netssh
+
+ # since the @pool is a class instance variable, we need to copy
+ # the code from the superclass that initializes it. boo
+ @pool = SSHKit::Backend::ConnectionPool.new
+
+ # modify to pass itself to the block, instead of relying on instance_exec.
+ def run
+ Thread.current["sshkit_backend"] = self
+ # was: instance_exec(@host, &@block)
+ @block.call(self, @host)
+ ensure
+ Thread.current["sshkit_backend"] = nil
+ end
+
+ #
+ # like default capture, but gracefully logs failures for us
+ # last argument can be an options hash.
+ #
+ # available options:
+ #
+ # :fail_msg - [nil] if set, log this instead of the default
+ # fail message.
+ #
+ # :raise_error - [nil] if true, then reraise failed command exception.
+ #
+ # :log_cmd - [false] if true, log what the command is that gets run.
+ #
+ # :log_output - [true] if true, log each output from the command as
+ # it is received.
+ #
+ # :log_finish - [false] if true, log the exit status and time
+ # to completion
+ #
+ # :log_wrap - [nil] passed to log method as :wrap option.
+ #
+ def capture(*args)
+ extract_options(args)
+ initialize_logger(:log_output => false)
+ rescue_ssh_errors(*args) do
+ return super(*args)
+ end
+ end
+
+ #
+ # like default execute, but log the results as they come in.
+ #
+ # see capture() for available options
+ #
+ def stream(*args)
+ extract_options(args)
+ initialize_logger
+ rescue_ssh_errors(*args) do
+ execute(*args)
+ end
+ end
+
+ def log(*args, &block)
+ @logger ||= LeapCli.new_logger
+ @logger.log(*args, &block)
+ end
+
+ # some prewritten servers-side scripts
+ def scripts
+ @scripts ||= LeapCli::SSH::Scripts.new(self, @host)
+ end
+
+ private
+
+ #
+ # creates a new logger instance for this specific ssh command.
+ # by doing this, each ssh session has its own logger and its own
+ # indentation.
+ #
+ # potentially modifies 'args' array argument.
+ #
+ def initialize_logger(default_options={})
+ @logger ||= LeapCli.new_logger
+ @output = LeapCli::SSH::Formatter.new(@logger, @host, default_options.merge(@options))
+ end
+
+ def extract_options(args)
+ if args.last.is_a? Hash
+ @options = args.pop
+ else
+ @options = {}
+ end
+ end
+
+ #
+ # capture common exceptions
+ #
+ def rescue_ssh_errors(*args, &block)
+ yield
+ rescue StandardError => exc
+ if exc.is_a?(SSHKit::Command::Failed) || exc.is_a?(SSHKit::Runner::ExecuteError)
+ if @options[:raise_error]
+ raise LeapCli::SSH::ExecuteError, exc.to_s
+ elsif @options[:fail_msg]
+ @logger.log(@options[:fail_msg], host: @host.hostname, :color => :red)
+ else
+ @logger.log(:failed, args.join(' '), host: @host.hostname) do
+ @logger.log(exc.to_s.strip, wrap: true)
+ end
+ end
+ elsif exc.is_a?(Timeout::Error)
+ @logger.log(:failed, args.join(' '), host: @host.hostname) do
+ @logger.log("Connection timed out")
+ end
+ else
+ @logger.log(:error, "unknown exception: " + exc.to_s)
+ end
+ return nil
+ end
+
+ def output
+ @output ||= LeapCli::SSH::Formatter.new(@logger, @host)
+ end
+
+ end
+ end
+end
+
diff --git a/lib/leap_cli/ssh/formatter.rb b/lib/leap_cli/ssh/formatter.rb
new file mode 100644
index 00000000..84a8e797
--- /dev/null
+++ b/lib/leap_cli/ssh/formatter.rb
@@ -0,0 +1,75 @@
+#
+# A custom SSHKit formatter that uses LeapLogger.
+#
+
+require 'sshkit'
+
+module LeapCli
+ module SSH
+
+ class Formatter < SSHKit::Formatter::Abstract
+
+ DEFAULT_OPTIONS = {
+ :log_cmd => false, # log what the command is that gets run.
+ :log_output => true, # log each output from the command as it is received.
+ :log_finish => false # log the exit status and time to completion.
+ }
+
+ def initialize(logger, host, options={})
+ @logger = logger
+ @host = host
+ @options = DEFAULT_OPTIONS.merge(options)
+ end
+
+ def write(obj)
+ @logger.log(obj.to_s, :host => @host)
+ end
+
+ def log_command_start(command)
+ if @options[:log_cmd]
+ @logger.log(:running, "`" + command.to_s + "`", :host => @host)
+ end
+ end
+
+ def log_command_data(command, stream_type, stream_data)
+ if @options[:log_output]
+ color = \
+ case stream_type
+ when :stdout then :cyan
+ when :stderr then :red
+ else raise "Unrecognised stream_type #{stream_type}, expected :stdout or :stderr"
+ end
+ @logger.log(stream_data.to_s.chomp,
+ :color => color, :host => @host, :wrap => options[:log_wrap])
+ end
+ end
+
+ def log_command_exit(command)
+ if @options[:log_finish]
+ runtime = sprintf('%5.3fs', command.runtime)
+ if command.failure?
+ message = "in #{runtime} with status #{command.exit_status}."
+ @logger.log(:failed, message, :host => @host)
+ else
+ message = "in #{runtime}."
+ @logger.log(:completed, message, :host => @host)
+ end
+ end
+ end
+ end
+
+ end
+end
+
+ #
+ # A custom InteractionHandler that will output the results as they come in.
+ #
+ #class LoggingInteractionHandler
+ # def initialize(hostname, logger=nil)
+ # @hostname = hostname
+ # @logger = logger || LeapCli.new_logger
+ # end
+ # def on_data(command, stream_name, data, channel)
+ # @logger.log(data, host: @hostname, wrap: true)
+ # end
+ #end
diff --git a/lib/leap_cli/ssh/key.rb b/lib/leap_cli/ssh/key.rb
new file mode 100644
index 00000000..ad1ecf15
--- /dev/null
+++ b/lib/leap_cli/ssh/key.rb
@@ -0,0 +1,199 @@
+#
+# A wrapper around OpenSSL::PKey::RSA instances to provide a better api for
+# dealing with SSH keys.
+#
+# NOTE: cipher 'ssh-ed25519' not supported yet because we are waiting
+# for support in Net::SSH
+#
+
+require 'net/ssh'
+require 'forwardable'
+
+module LeapCli
+ module SSH
+ class Key
+ extend Forwardable
+
+ attr_accessor :filename
+ attr_accessor :comment
+
+ # supported ssh key types, in order of preference
+ SUPPORTED_TYPES = ['ssh-rsa', 'ecdsa-sha2-nistp256']
+ SUPPORTED_TYPES_RE = /(#{SUPPORTED_TYPES.join('|')})/
+
+ ##
+ ## CLASS METHODS
+ ##
+
+ def self.load(arg1, arg2=nil)
+ key = nil
+ if arg1.is_a? OpenSSL::PKey::RSA
+ key = Key.new arg1
+ elsif arg1.is_a? String
+ if arg1 =~ /^ssh-/
+ type, data = arg1.split(' ')
+ key = Key.new load_from_data(data, type)
+ elsif File.exist? arg1
+ key = Key.new load_from_file(arg1)
+ key.filename = arg1
+ else
+ key = Key.new load_from_data(arg1, arg2)
+ end
+ end
+ return key
+ rescue StandardError
+ end
+
+ def self.load_from_file(filename)
+ public_key = nil
+ private_key = nil
+ begin
+ public_key = Net::SSH::KeyFactory.load_public_key(filename)
+ rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError
+ begin
+ private_key = Net::SSH::KeyFactory.load_private_key(filename)
+ rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError
+ end
+ end
+ public_key || private_key
+ end
+
+ def self.load_from_data(data, type='ssh-rsa')
+ public_key = nil
+ private_key = nil
+ begin
+ public_key = Net::SSH::KeyFactory.load_data_public_key("#{type} #{data}")
+ rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError
+ begin
+ private_key = Net::SSH::KeyFactory.load_data_private_key("#{type} #{data}")
+ rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError
+ end
+ end
+ public_key || private_key
+ end
+
+ #
+ # Picks one key out of an array of keys that we think is the "best",
+ # based on the order of preference in SUPPORTED_TYPES
+ #
+ # Currently, this does not take bitsize into account.
+ #
+ def self.pick_best_key(keys)
+ keys.select {|k|
+ SUPPORTED_TYPES.include?(k.type)
+ }.sort {|a,b|
+ SUPPORTED_TYPES.index(a.type) <=> SUPPORTED_TYPES.index(b.type)
+ }.first
+ end
+
+ #
+ # takes a string with one or more ssh keys, one key per line,
+ # and returns an array of Key objects.
+ #
+ # the lines should be in one of these formats:
+ #
+ # 1. <hostname> <key-type> <key>
+ # 2. <key-type> <key>
+ #
+ def self.parse_keys(string)
+ keys = []
+ lines = string.split("\n").grep(/^[^#]/)
+ lines.each do |line|
+ if line =~ / #{Key::SUPPORTED_TYPES_RE} /
+ # <hostname> <key-type> <key>
+ keys << line.split(' ')[1..2]
+ elsif line =~ /^#{Key::SUPPORTED_TYPES_RE} /
+ # <key-type> <key>
+ keys << line.split(' ')
+ end
+ end
+ return keys.map{|k| Key.load(k[1], k[0])}
+ end
+
+ #
+ # takes a string with one or more ssh keys, one key per line,
+ # and returns a string that specified the ssh key algorithms
+ # that are supported by the keys, in order of preference.
+ #
+ # eg: ecdsa-sha2-nistp256,ssh-rsa,ssh-ed25519
+ #
+ def self.supported_host_key_algorithms(string)
+ if string
+ self.parse_keys(string).map {|key|
+ key.type
+ }.join(',')
+ else
+ ""
+ end
+ end
+
+ ##
+ ## INSTANCE METHODS
+ ##
+
+ public
+
+ def initialize(rsa_key)
+ @key = rsa_key
+ end
+
+ def_delegator :@key, :fingerprint, :fingerprint
+ def_delegator :@key, :public?, :public?
+ def_delegator :@key, :private?, :private?
+ def_delegator :@key, :ssh_type, :type
+ def_delegator :@key, :public_encrypt, :public_encrypt
+ def_delegator :@key, :public_decrypt, :public_decrypt
+ def_delegator :@key, :private_encrypt, :private_encrypt
+ def_delegator :@key, :private_decrypt, :private_decrypt
+ def_delegator :@key, :params, :params
+ def_delegator :@key, :to_text, :to_text
+
+ def public_key
+ Key.new(@key.public_key)
+ end
+
+ def private_key
+ Key.new(@key.private_key)
+ end
+
+ #
+ # not sure if this will always work, but is seems to for now.
+ #
+ def bits
+ Net::SSH::Buffer.from(:key, @key).to_s.split("\001\000").last.size * 8
+ end
+
+ def summary
+ if self.filename
+ "%s %s %s (%s)" % [self.type, self.bits, self.fingerprint, File.basename(self.filename)]
+ else
+ "%s %s %s" % [self.type, self.bits, self.fingerprint]
+ end
+ end
+
+ def to_s
+ self.type + " " + self.key
+ end
+
+ def key
+ [Net::SSH::Buffer.from(:key, @key).to_s].pack("m*").gsub(/\s/, "")
+ end
+
+ def ==(other_key)
+ return false if other_key.nil?
+ return false if self.class != other_key.class
+ return self.to_text == other_key.to_text
+ end
+
+ def in_known_hosts?(*identifiers)
+ identifiers.each do |identifier|
+ Net::SSH::KnownHosts.search_for(identifier).each do |key|
+ return true if self == key
+ end
+ end
+ return false
+ end
+
+ end
+ end
+end
diff --git a/lib/leap_cli/ssh/options.rb b/lib/leap_cli/ssh/options.rb
new file mode 100644
index 00000000..0bbaa36f
--- /dev/null
+++ b/lib/leap_cli/ssh/options.rb
@@ -0,0 +1,93 @@
+#
+# Options for passing to the ruby gem ssh-net
+#
+
+module LeapCli
+ module SSH
+ module Options
+
+ def self.global_options
+ {
+ #:keys_only => true,
+ :global_known_hosts_file => Path.named_path(:known_hosts),
+ :user_known_hosts_file => '/dev/null',
+ :paranoid => true,
+ :verbose => net_ssh_log_level,
+ :auth_methods => ["publickey"],
+ :timeout => 5
+ }
+ end
+
+ def self.node_options(node, ssh_options_override=nil)
+ {
+ # :host_key_alias => node.name, << incompatible with ports in known_hosts
+ :host_name => node.ip_address,
+ :port => node.ssh.port
+ }.merge(
+ contingent_ssh_options_for_node(node)
+ ).merge(
+ ssh_options_override||{}
+ )
+ end
+
+ def self.options_from_args(args)
+ ssh_options = {}
+ if args[:port]
+ ssh_options[:port] = args[:port]
+ end
+ if args[:ip]
+ ssh_options[:host_name] = args[:ip]
+ end
+ if args[:auth_methods]
+ ssh_options[:auth_methods] = args[:auth_methods]
+ end
+ return ssh_options
+ end
+
+ def self.sanitize_command(cmd)
+ if cmd =~ /(^|\/| )rm / || cmd =~ /(^|\/| )unlink /
+ LeapCli.log :warning, "You probably don't want to do that. Run with --force if you are really sure."
+ exit(1)
+ else
+ cmd
+ end
+ end
+
+ private
+
+ def self.contingent_ssh_options_for_node(node)
+ opts = {}
+ if node.vagrant?
+ opts[:keys] = [vagrant_ssh_key_file]
+ opts[:keys_only] = true # only use the keys specified above, and
+ # ignore whatever keys the ssh-agent is aware of.
+ opts[:paranoid] = false # we skip host checking for vagrant nodes,
+ # because fingerprint is different for everyone.
+ if LeapCli.logger.log_level <= 1
+ opts[:verbose] = :error # suppress all the warnings about adding
+ # host keys to known_hosts, since it is
+ # not actually doing that.
+ end
+ end
+ if !node.supported_ssh_host_key_algorithms.empty?
+ opts[:host_key] = node.supported_ssh_host_key_algorithms
+ end
+ return opts
+ end
+
+ def self.net_ssh_log_level
+ if DEBUG
+ case LeapCli.logger.log_level
+ when 1 then 3
+ when 2 then 2
+ when 3 then 1
+ else 0
+ end
+ else
+ nil
+ end
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/ssh/remote_command.rb b/lib/leap_cli/ssh/remote_command.rb
new file mode 100644
index 00000000..fe9a344a
--- /dev/null
+++ b/lib/leap_cli/ssh/remote_command.rb
@@ -0,0 +1,107 @@
+#
+# Provides SSH.remote_command for running commands in parallel or in sequence
+# on remote servers.
+#
+# The gem sshkit is used for this.
+#
+
+require 'sshkit'
+require 'leap_cli/ssh/options'
+require 'leap_cli/ssh/backend'
+
+SSHKit.config.backend = LeapCli::SSH::Backend
+LeapCli::SSH::Backend.config.ssh_options = LeapCli::SSH::Options.global_options
+
+#
+# define remote_command
+#
+module LeapCli
+ module SSH
+
+ class ExecuteError < StandardError
+ end
+
+ # override default runner mode
+ class CustomCoordinator < SSHKit::Coordinator
+ private
+ def default_options
+ { in: :groups, limit: 10, wait: 0 }
+ end
+ end
+
+ #
+ # Available options:
+ #
+ # :port -- ssh port
+ # :ip -- ssh ip
+ # :auth_methods -- e.g. ["pubkey", "password"]
+ #
+ def self.remote_command(nodes, options={}, &block)
+ CustomCoordinator.new(
+ host_list(
+ nodes,
+ SSH::Options.options_from_args(options)
+ )
+ ).each do |ssh, host|
+ LeapCli.log 2, "ssh options for #{host.hostname}: #{host.ssh_options.inspect}"
+ yield ssh, host
+ end
+ end
+
+ #
+ # For example:
+ #
+ # SSH.remote_sync(nodes) do |sync, host|
+ # sync.source = '/from'
+ # sync.dest = '/to'
+ # sync.flags = ''
+ # sync.includes = []
+ # sync.excludes = []
+ # sync.exec
+ # end
+ #
+ def self.remote_sync(nodes, options={}, &block)
+ require 'rsync_command'
+ hosts = host_list(
+ nodes,
+ SSH::Options.options_from_args(options)
+ )
+ rsync = RsyncCommand.new(:logger => LeapCli::logger)
+ rsync.asynchronously(hosts) do |sync, host|
+ sync.logger = LeapCli.new_logger
+ sync.user = host.user || fetch(:user, ENV['USER'])
+ sync.host = host.hostname
+ sync.ssh = SSH::Options.global_options.merge(host.ssh_options)
+ sync.chdir = Path.provider
+ yield(sync, host)
+ end
+ if rsync.failed?
+ LeapCli::Util.bail! do
+ LeapCli.log :failed, "to rsync to #{rsync.failures.map{|f|f[:dest][:host]}.join(' ')}"
+ end
+ end
+ end
+
+ private
+
+ def self.host_list(nodes, ssh_options_override={})
+ if nodes.is_a?(Config::ObjectList)
+ list = nodes.values
+ elsif nodes.is_a?(Config::Node)
+ list = [nodes]
+ else
+ raise ArgumentError, "I don't understand the type of argument `nodes`"
+ end
+ list.collect do |node|
+ SSHKit::Host.new(
+ :hostname => node.name,
+ :user => 'root',
+ :ssh_options => SSH::Options.node_options(node, ssh_options_override)
+ )
+ end
+ end
+
+ end
+end
+
+
diff --git a/lib/leap_cli/ssh/scripts.rb b/lib/leap_cli/ssh/scripts.rb
new file mode 100644
index 00000000..3d8b6570
--- /dev/null
+++ b/lib/leap_cli/ssh/scripts.rb
@@ -0,0 +1,136 @@
+#
+# Common commands that we would like to run on remote servers.
+#
+# These scripts are available via:
+#
+# SSH.remote_command(nodes) do |ssh, host|
+# ssh.script.custom_script_name
+# end
+#
+
+module LeapCli
+ module SSH
+ class Scripts
+
+ REQUIRED_PACKAGES = "puppet rsync lsb-release locales"
+
+ attr_reader :ssh, :host
+ def initialize(backend, host)
+ @ssh = backend
+ @host = host
+ end
+
+ #
+ # creates directories that are owned by root and 700 permissions
+ #
+ def mkdirs(*dirs)
+ raise ArgumentError.new('illegal dir name') if dirs.grep(/[\' ]/).any?
+ ssh.stream dirs.collect{|dir| "mkdir -m 700 -p #{dir}; "}.join
+ end
+
+ #
+ # echos "ok" if the node has been initialized and the required packages are installed, bails out otherwise.
+ #
+ def assert_initialized
+ begin
+ test_initialized_file = "test -f #{Leap::Platform.init_path}"
+ check_required_packages = "! dpkg-query -W --showformat='${Status}\n' #{REQUIRED_PACKAGES} 2>&1 | grep -q -E '(deinstall|no packages)'"
+ ssh.stream "#{test_initialized_file} && #{check_required_packages} && echo ok", :raise_error => true
+ rescue SSH::ExecuteError
+ ssh.log :error, "running deploy: node not initialized. Run `leap node init #{host}`.", :host => host
+ raise # will skip further action on this node
+ end
+ end
+
+ #
+ # bails out the deploy if the file /etc/leap/no-deploy exists.
+ #
+ def check_for_no_deploy
+ begin
+ ssh.stream "test ! -f /etc/leap/no-deploy", :raise_error => true, :log_output => false
+ rescue SSH::ExecuteError
+ ssh.log :warning, "can't continue because file /etc/leap/no-deploy exists", :host => host
+ raise # will skip further action on this node
+ end
+ end
+
+ #
+ # dumps debugging information
+ #
+ def debug
+ output = ssh.capture "#{Leap::Platform.leap_dir}/bin/debug.sh"
+ ssh.log(output, :wrap => true, :host => host.hostname, :color => :cyan)
+ end
+
+ #
+ # dumps the recent deploy history to the console
+ #
+ def history(lines)
+ cmd = "(test -s /var/log/leap/deploy-summary.log && tail -n #{lines} /var/log/leap/deploy-summary.log) || (test -s /var/log/leap/deploy-summary.log.1 && tail -n #{lines} /var/log/leap/deploy-summary.log.1) || (echo 'no history')"
+ history = ssh.capture(cmd, :log_output => false)
+ if history
+ ssh.log host.hostname, :color => :cyan, :style => :bold do
+ ssh.log history, :wrap => true
+ end
+ end
+ end
+
+ #
+ # apply puppet! weeeeeee
+ #
+ def puppet_apply(options)
+ cmd = "#{Leap::Platform.leap_dir}/bin/puppet_command set_hostname apply #{flagize(options)}"
+ ssh.stream cmd, :log_finish => true
+ end
+
+ def install_authorized_keys
+ ssh.log :updating, "authorized_keys" do
+ mkdirs '/root/.ssh'
+ ssh.upload! LeapCli::Path.named_path(:authorized_keys), '/root/.ssh/authorized_keys', :mode => '600'
+ end
+ end
+
+ #
+ # for vagrant nodes, we install insecure vagrant key to authorized_keys2, since deploy
+ # will overwrite authorized_keys.
+ #
+ # why force the insecure vagrant key?
+ # if we don't do this, then first time initialization might fail if the user has many keys
+ # (ssh will bomb out before it gets to the vagrant key).
+ # and it really doesn't make sense to ask users to pin the insecure vagrant key in their
+ # .ssh/config files.
+ #
+ def install_insecure_vagrant_key
+ ssh.log :installing, "insecure vagrant key" do
+ mkdirs '/root/.ssh'
+ ssh.upload! LeapCli::Path.vagrant_ssh_pub_key_file, '/root/.ssh/authorized_keys2', :mode => '600'
+ end
+ end
+
+ def install_prerequisites
+ bin_dir = File.join(Leap::Platform.leap_dir, 'bin')
+ node_init_path = File.join(bin_dir, 'node_init')
+ ssh.log :running, "node_init script" do
+ mkdirs bin_dir
+ ssh.upload! LeapCli::Path.node_init_script, node_init_path, :mode => '500'
+ ssh.stream node_init_path
+ end
+ end
+
+ private
+
+ def flagize(hsh)
+ hsh.inject([]) {|str, item|
+ if item[1] === false
+ str
+ elsif item[1] === true
+ str << "--" + item[0].to_s
+ else
+ str << "--" + item[0].to_s + " " + item[1].inspect
+ end
+ }.join(' ')
+ end
+
+ end
+ end
+end \ No newline at end of file