diff options
author | elijah <elijah@riseup.net> | 2012-10-23 03:53:06 -0700 |
---|---|---|
committer | elijah <elijah@riseup.net> | 2012-10-23 03:53:06 -0700 |
commit | 628165fd0a4e03bb7bbef3a464447924195e10b8 (patch) | |
tree | 746280b6f4d6d488fcece4fff41b4addfb77d0c1 /lib/leap_cli/commands | |
parent | 4f38e99c629f60d9524d1cf23efa7ab927ac9cf4 (diff) |
added a bunch of new commands, including init-node and deploy
Diffstat (limited to 'lib/leap_cli/commands')
-rw-r--r-- | lib/leap_cli/commands/clean.rb | 6 | ||||
-rw-r--r-- | lib/leap_cli/commands/compile.rb | 16 | ||||
-rw-r--r-- | lib/leap_cli/commands/deploy.rb | 21 | ||||
-rw-r--r-- | lib/leap_cli/commands/node.rb (renamed from lib/leap_cli/commands/bootstrap.rb) | 100 | ||||
-rw-r--r-- | lib/leap_cli/commands/pre.rb | 5 | ||||
-rw-r--r-- | lib/leap_cli/commands/shell.rb | 12 | ||||
-rw-r--r-- | lib/leap_cli/commands/user.rb | 19 | ||||
-rw-r--r-- | lib/leap_cli/commands/util.rb | 159 |
8 files changed, 262 insertions, 76 deletions
diff --git a/lib/leap_cli/commands/clean.rb b/lib/leap_cli/commands/clean.rb index ed9c901..8847b7d 100644 --- a/lib/leap_cli/commands/clean.rb +++ b/lib/leap_cli/commands/clean.rb @@ -4,11 +4,11 @@ module LeapCli desc 'Removes all files generated with the "compile" command' command :clean do |c| c.action do |global_options,options,args| - Dir.glob(named_path(:hiera, '*')).each do |file| + Dir.glob(path([:hiera, '*'])).each do |file| remove_file! file end - remove_file! named_path(:authorized_keys) - remove_file! named_path(:known_hosts) + remove_file! path(:authorized_keys) + remove_file! path(:known_hosts) end end diff --git a/lib/leap_cli/commands/compile.rb b/lib/leap_cli/commands/compile.rb index 429d1c5..c5bb93e 100644 --- a/lib/leap_cli/commands/compile.rb +++ b/lib/leap_cli/commands/compile.rb @@ -5,20 +5,14 @@ module LeapCli desc 'Compile json files to hiera configs' command :compile do |c| c.action do |global_options,options,args| - manager.load(Path.provider) - ensure_dir(Path.hiera) - manager.export(Path.hiera) - update_authorized_keys - update_known_hosts + update_compiled_ssh_configs # this must come first, hiera configs import these files. + manager.export Path.named_path(:hiera_dir) # generate a hiera .yaml config for each node end end - def update_authorized_keys - buffer = StringIO.new - Dir.glob(named_path(:user_ssh, '*')).each do |keyfile| - buffer << File.read(keyfile) - end - write_file!(:authorized_keys, buffer.string) + def update_compiled_ssh_configs + update_authorized_keys + update_known_hosts end end diff --git a/lib/leap_cli/commands/deploy.rb b/lib/leap_cli/commands/deploy.rb index 9ec984c..c5efed5 100644 --- a/lib/leap_cli/commands/deploy.rb +++ b/lib/leap_cli/commands/deploy.rb @@ -6,12 +6,21 @@ module LeapCli arg_name '<node filter>' command :deploy do |c| c.action do |global_options,options,args| - nodes = manager.filter(args) - say "Deploying to these nodes: #{nodes.keys.join(', ')}" - if agree "Continue? " - say "deploy not yet implemented" - else - say "OK. Bye." + nodes = manager.filter!(args) + if nodes.size > 1 + say "Deploying to these nodes: #{nodes.keys.join(', ')}" + unless agree "Continue? " + quit! "OK. Bye." + end + end + leap_root = '/root/leap' + ssh_connect(nodes) do |ssh| + ssh.leap.mkdir_leap leap_root + ssh.leap.rsync_update do |server| + node = manager.node(server.host) + {:source => Path.named_path([:hiera, node.name]), :dest => "#{leap_root}/config/#{node.name}.yaml"} + end + ssh.apply_puppet end end end diff --git a/lib/leap_cli/commands/bootstrap.rb b/lib/leap_cli/commands/node.rb index 11188fb..46c8fb6 100644 --- a/lib/leap_cli/commands/bootstrap.rb +++ b/lib/leap_cli/commands/node.rb @@ -3,6 +3,10 @@ require 'tempfile' module LeapCli; module Commands + ## + ## COMMANDS + ## + #desc 'Create a new configuration for a node' #command :'new-node' do |c| # c.action do |global_options,options,args| @@ -13,12 +17,14 @@ module LeapCli; module Commands arg_name '<node-name>', :optional => false, :multiple => false command :'init-node' do |c| c.action do |global_options,options,args| - node_name = args.first - node = manager.node(node_name) - assert!(node, "Node '#{node_name}' not found.") - progress("Pinging #{node.name}") - assert_run!("ping -W 1 -c 1 #{node.ip_address}", "Could not ping #{node_name} (address #{node.ip_address}). Try again, we only send a single ping.") - install_public_host_key(node) + node = get_node_from_args(args) + ping_node(node) + save_public_host_key(node) + update_compiled_ssh_configs + ssh_connect(node, :bootstrap => true) do |ssh| + ssh.install_authorized_keys + ssh.install_prerequisites + end end end @@ -29,21 +35,65 @@ module LeapCli; module Commands end desc 'not yet implemented' + arg_name '<node-name>', :optional => false, :multiple => false command :'rm-node' do |c| c.action do |global_options,options,args| + remove_file!() end end + ## + ## PUBLIC HELPERS + ## + + # + # generates the known_hosts file. + # + # we do a 'late' binding on the hostnames and ip part of the ssh pub key record in order to allow + # for the possibility that the hostnames or ip has changed in the node configuration. + # + def update_known_hosts + buffer = StringIO.new + manager.nodes.values.each do |node| + hostnames = [node.name, node.domain.internal, node.domain.full, node.ip_address].join(',') + pub_key = read_file([:node_ssh_pub_key,node.name]) + if pub_key + buffer << [hostnames, pub_key].join(' ') + end + end + write_file!(:known_hosts, buffer.string) + end + + def get_node_from_args(args) + node_name = args.first + node = manager.node(node_name) + assert!(node, "Node '#{node_name}' not found.") + node + end + + private + + ## + ## PRIVATE HELPERS + ## + # # saves the public ssh host key for node into the provider directory. # # see `man sshd` for the format of known_hosts # - def install_public_host_key(node) + def save_public_host_key(node) progress("Fetching public SSH host key for #{node.name}") public_key, key_type = get_public_key_for_ip(node.ip_address) - if key_in_known_hosts?(public_key, [node.name, node.ip_address, node.domain.name]) - progress("Public ssh host key for #{node.name} is already trusted (key found in known_hosts)") + pub_key_path = Path.named_path([:node_ssh_pub_key, node.name]) + if Path.exists?(pub_key_path) + if file_content_equals?(pub_key_path, node_pub_key_contents(key_type, public_key)) + progress("Public SSH host key for #{node.name} has not changed") + else + bail!("WARNING: The public SSH host key we just fetched for #{node.name} doesn't match what we have saved previously. Remove the file #{pub_key_path} if you really want to change it.") + end + elsif key_in_known_hosts?(public_key, [node.name, node.ip_address, node.domain.name]) + progress("Public SSH host key for #{node.name} is trusted (key found in your ~/.ssh/known_hosts)") else fingerprint, bits = ssh_key_fingerprint(key_type, public_key) puts @@ -55,12 +105,9 @@ module LeapCli; module Commands bail! else puts - # we write the file without ipaddress or hostname, because these might change later, but we want to keep the same key. - write_file!([:node_ssh_pub_key, node.name], [key_type, public_key].join(' ')) - update_known_hosts + write_file!([:node_ssh_pub_key, node.name], node_pub_key_contents(key_type, public_key)) end end - end def get_public_key_for_ip(address) @@ -93,6 +140,10 @@ module LeapCli; module Commands # # gets a fingerprint for a key string # + # i think this could better be done this way: + # blob = Net::SSH::Buffer.from(:key, key).to_s + # fingerprint = OpenSSL::Digest::MD5.hexdigest(blob).scan(/../).join(":") + # def ssh_key_fingerprint(type, key) assert_bin!('ssh-keygen') file = Tempfile.new('leap_cli_public_key_') @@ -110,22 +161,19 @@ module LeapCli; module Commands end end + def ping_node(node) + progress("Pinging #{node.name}") + assert_run!("ping -W 1 -c 1 #{node.ip_address}", "Could not ping #{node.name} (address #{node.ip_address}). Try again, we only send a single ping.") + end + # - # generates the known_hosts file. + # returns a string that can be used for the contents of the files/nodes/x/x_ssh_key.pub file # - # we do a 'late' binding on the hostnames and ip part of the ssh pub key record in order to allow - # for the possibility that the hostnames or ip has changed in the node configuration. + # We write the file without ipaddress or hostname, because these might change later. + # The ip and host is added at when compiling the combined known_hosts file. # - def update_known_hosts - buffer = StringIO.new - manager.nodes.values.each do |node| - hostnames = [node.name, node.domain.internal, node.domain.full, node.ip_address].join(',') - pub_key = read_file([:node_ssh_pub_key,node.name]) - if pub_key - buffer << [hostnames, pub_key].join(' ') - end - end - write_file!(:known_hosts, buffer.string) + def node_pub_key_contents(key_type, public_key) + [key_type, public_key].join(' ') end end; end
\ No newline at end of file diff --git a/lib/leap_cli/commands/pre.rb b/lib/leap_cli/commands/pre.rb index ada6a6a..d80a9c2 100644 --- a/lib/leap_cli/commands/pre.rb +++ b/lib/leap_cli/commands/pre.rb @@ -20,6 +20,11 @@ module LeapCli # set verbosity # LeapCli.log_level = global[:verbose].to_i + if LeapCli.log_level > 1 + ENV['GLI_DEBUG'] = "true" + else + ENV['GLI_DEBUG'] = "false" + end # # require a root directory diff --git a/lib/leap_cli/commands/shell.rb b/lib/leap_cli/commands/shell.rb new file mode 100644 index 0000000..df392bd --- /dev/null +++ b/lib/leap_cli/commands/shell.rb @@ -0,0 +1,12 @@ +module LeapCli; module Commands + + desc 'Log in to the specified node with an interactive shell' + arg_name '<node-name>', :optional => false, :multiple => false + command :shell, :ssh do |c| + c.action do |global_options,options,args| + node = get_node_from_args(args) + exec "ssh -l root -o 'HostName=#{node.ip_address}' -o 'HostKeyAlias=#{node.name}' -o 'UserKnownHostsFile=#{path(:known_hosts)}' -o 'StrictHostKeyChecking=yes' #{node.name}" + end + end + +end; end
\ No newline at end of file diff --git a/lib/leap_cli/commands/user.rb b/lib/leap_cli/commands/user.rb index 00c4b62..7be91c8 100644 --- a/lib/leap_cli/commands/user.rb +++ b/lib/leap_cli/commands/user.rb @@ -1,13 +1,14 @@ require 'gpgme' # -# notes: +# perhaps we want to verify that the key files are actually the key files we expect. +# we could use 'file' for this: # -# file ~/.gnupg/00440025.asc -# /home/elijah/.gnupg/00440025.asc: PGP public key block +# > file ~/.gnupg/00440025.asc +# ~/.gnupg/00440025.asc: PGP public key block # -# file ~/.ssh/id_rsa.pub -# /home/elijah/.ssh/id_rsa.pub: OpenSSH RSA public key +# > file ~/.ssh/id_rsa.pub +# ~/.ssh/id_rsa.pub: OpenSSH RSA public key # module LeapCli @@ -103,5 +104,13 @@ module LeapCli return `gpg --armor --export-options export-minimal --export #{key_id}`.strip end + def update_authorized_keys + buffer = StringIO.new + Dir.glob(path([:user_ssh, '*'])).each do |keyfile| + buffer << File.read(keyfile) + end + write_file!(:authorized_keys, buffer.string) + end + end end
\ No newline at end of file diff --git a/lib/leap_cli/commands/util.rb b/lib/leap_cli/commands/util.rb index b5a102f..803fe88 100644 --- a/lib/leap_cli/commands/util.rb +++ b/lib/leap_cli/commands/util.rb @@ -1,34 +1,143 @@ -module LeapCli - module Commands - extend self - extend LeapCli::Util +module LeapCli; module Commands - # - # keeps prompting the user for a numbered choice, until they pick a good one or bail out. - # - # block is yielded and is responsible for rendering the choices. - # - def numbered_choice_menu(msg, items, &block) - while true - say("\n" + msg + ':') - items.each_with_index &block - say("q. quit") - index = ask("number 1-#{items.length}> ") - if index.empty? - next - elsif index =~ /q/ + extend self + extend LeapCli::Util + + def path(name) + Path.named_path(name) + end + + # + # keeps prompting the user for a numbered choice, until they pick a good one or bail out. + # + # block is yielded and is responsible for rendering the choices. + # + def numbered_choice_menu(msg, items, &block) + while true + say("\n" + msg + ':') + items.each_with_index &block + say("q. quit") + index = ask("number 1-#{items.length}> ") + if index.empty? + next + elsif index =~ /q/ + bail! + else + i = index.to_i - 1 + if i < 0 || i >= items.length bail! else - i = index.to_i - 1 - if i < 0 || i >= items.length - bail! - else - return i - end + return i end end end + end + + # + # + # + # FYI + # Capistrano::Logger::IMPORTANT = 0 + # Capistrano::Logger::INFO = 1 + # Capistrano::Logger::DEBUG = 2 + # Capistrano::Logger::TRACE = 3 + # + def ssh_connect(nodes, options={}, &block) + node_list = parse_node_list(nodes) + + cap = new_capistrano + cap.logger.level = LeapCli.log_level + user = options[:user] || 'root' + cap.set :user, user + cap.set :ssh_options, ssh_options + cap.set :use_sudo, false # we may want to change this in the future + + # supply drop options + cap.set :puppet_source, [Path.platform, 'puppet'].join('/') + cap.set :puppet_destination, '/root/leap' + #cap.set :puppet_command, 'puppet apply' + cap.set :puppet_lib, "puppet/modules" + cap.set :puppet_parameters, '--confdir=puppet puppet/manifests/site.pp' + #cap.set :puppet_stream_output, false + #puppet apply --confdir=puppet puppet/manifests/site.pp | grep -v 'warning:.*is deprecated' + #puppet_cmd = "cd #{puppet_destination} && #{sudo_cmd} #{puppet_command} --modulepath=#{puppet_lib} #{puppet_parameters}" + + # + # allow password authentication when we are bootstraping a single node. + # + if options[:bootstrap] && node_list.size == 1 + hostname = node_list.values.first.name + cap.set(:password) { ask("SSH password for #{user}@#{hostname}> ") } # only called if needed + # this can be used instead to hide echo -- Capistrano::CLI.password_prompt + end + + node_list.each do |name, node| + cap.server node.name, :dummy_arg, node_options(node) + end + yield cap + end + + + private + + + # + # For available options, see http://net-ssh.github.com/net-ssh/classes/Net/SSH.html#method-c-start + # + def ssh_options + { + :config => false, + :user_known_hosts_file => path(:known_hosts), + :paranoid => true + } + end + + # + # For notes on advanced ways to set server-specific options, see + # http://railsware.com/blog/2011/11/02/advanced-server-definitions-in-capistrano/ + # + def node_options(node) + password_proc = Proc.new {Capistrano::CLI.password_prompt "Root SSH password for #{node.name}"} # only called if needed + { + :password => password_proc, + :ssh_options => { + :host_key_alias => node.name, + :host_name => node.ip_address, + :port => node.ssh.port + } + } + end + def new_capistrano + # load once the library files + @capistrano_enabled ||= begin + require 'capistrano' + #require 'capistrano/cli' + require 'leap_cli/remote/plugin' + Capistrano.plugin :leap, LeapCli::Remote::Plugin + true + end + + # create capistrano instance + cap = Capistrano::Configuration.new + + # add tasks to capistrano instance + cap.load File.dirname(__FILE__) + '/../remote/tasks.rb' + + return cap end -end + + def parse_node_list(nodes) + if nodes.is_a? Config::Object + Config::ObjectList.new(node_list) + elsif nodes.is_a? Config::ObjectList + nodes + elsif nodes.is_a? String + manager.filter!(nodes) + else + bail! "argument error" + end + end + +end; end |