aboutsummaryrefslogtreecommitdiff
path: root/lib/leap_cli/commands
diff options
context:
space:
mode:
authorelijah <elijah@riseup.net>2012-10-23 03:53:06 -0700
committerelijah <elijah@riseup.net>2012-10-23 03:53:06 -0700
commit628165fd0a4e03bb7bbef3a464447924195e10b8 (patch)
tree746280b6f4d6d488fcece4fff41b4addfb77d0c1 /lib/leap_cli/commands
parent4f38e99c629f60d9524d1cf23efa7ab927ac9cf4 (diff)
downloadleap_cli-628165fd0a4e03bb7bbef3a464447924195e10b8.tar.gz
leap_cli-628165fd0a4e03bb7bbef3a464447924195e10b8.tar.bz2
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.rb6
-rw-r--r--lib/leap_cli/commands/compile.rb16
-rw-r--r--lib/leap_cli/commands/deploy.rb21
-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.rb5
-rw-r--r--lib/leap_cli/commands/shell.rb12
-rw-r--r--lib/leap_cli/commands/user.rb19
-rw-r--r--lib/leap_cli/commands/util.rb159
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