From febeb64a801f3b4c72193bc93ee0400dee3a2a0a Mon Sep 17 00:00:00 2001 From: elijah Date: Fri, 9 Nov 2012 01:22:48 -0800 Subject: vagrant support --- .gitignore | 2 + lib/leap_cli.rb | 1 + lib/leap_cli/commands/shell.rb | 13 ++++- lib/leap_cli/commands/util.rb | 100 +----------------------------------- lib/leap_cli/commands/vagrant.rb | 72 ++++++++++++++++++++++++++ lib/leap_cli/config/manager.rb | 15 ++++++ lib/leap_cli/config/object.rb | 14 +++++ lib/leap_cli/config/object_list.rb | 5 ++ lib/leap_cli/path.rb | 3 +- lib/leap_cli/util/remote_command.rb | 98 +++++++++++++++++++++++++++++++++++ test/provider/common.json | 3 +- test/provider/nodes/couch1.json | 2 +- test/provider/nodes/vpn1.json | 2 +- test/provider/provider.json | 3 ++ vendor/vagrant_ssh_keys/README | 5 ++ vendor/vagrant_ssh_keys/vagrant.key | 27 ++++++++++ vendor/vagrant_ssh_keys/vagrant.pub | 1 + 17 files changed, 261 insertions(+), 105 deletions(-) create mode 100644 lib/leap_cli/commands/vagrant.rb create mode 100644 lib/leap_cli/util/remote_command.rb create mode 100644 vendor/vagrant_ssh_keys/README create mode 100644 vendor/vagrant_ssh_keys/vagrant.key create mode 100644 vendor/vagrant_ssh_keys/vagrant.pub diff --git a/.gitignore b/.gitignore index 61a56c8..5038bad 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ Gemfile.lock pkg junk test/provider/hiera +.vagrant +Vagrantfile diff --git a/lib/leap_cli.rb b/lib/leap_cli.rb index 728e501..5ed5033 100644 --- a/lib/leap_cli.rb +++ b/lib/leap_cli.rb @@ -9,6 +9,7 @@ require 'leap_cli/init' require 'leap_cli/path' require 'leap_cli/util' require 'leap_cli/util/secret' +require 'leap_cli/util/remote_command' require 'leap_cli/log' require 'leap_cli/ssh_key' diff --git a/lib/leap_cli/commands/shell.rb b/lib/leap_cli/commands/shell.rb index a84c671..f73a706 100644 --- a/lib/leap_cli/commands/shell.rb +++ b/lib/leap_cli/commands/shell.rb @@ -2,10 +2,19 @@ module LeapCli; module Commands desc 'Log in to the specified node with an interactive shell' arg_name '', :optional => false, :multiple => false - command :shell, :ssh do |c| + command :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 'GlobalKnownHostsFile=#{path(:known_hosts)}' -o 'StrictHostKeyChecking=yes' -p #{node.ssh.port} #{node.name}" + options = [ + "-o 'HostName=#{node.ip_address}'", + "-o 'HostKeyAlias=#{node.name}'", + "-o 'GlobalKnownHostsFile=#{path(:known_hosts)}'", + "-o 'StrictHostKeyChecking=yes'" + ] + if node.vagrant? + options << "-i #{vagrant_ssh_key_file}" + end + exec "ssh -l root -p #{node.ssh.port} #{options.join(' ')} {node.name}" end end diff --git a/lib/leap_cli/commands/util.rb b/lib/leap_cli/commands/util.rb index 164ce0d..c1da570 100644 --- a/lib/leap_cli/commands/util.rb +++ b/lib/leap_cli/commands/util.rb @@ -2,6 +2,7 @@ module LeapCli; module Commands extend self extend LeapCli::Util + extend LeapCli::Util::RemoteCommand def path(name) Path.named_path(name) @@ -33,105 +34,6 @@ module LeapCli; module Commands 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 - - # the 'password' block is only called if key auth fails - if options[:echo] - cap.set(:password) { ask "Root SSH password for #{user}@#{hostname}> " } - else - cap.set(:password) { Capistrano::CLI.password_prompt " * Typed password will be hidden (use --echo to make it visible)\nRoot SSH password for #{user}@#{hostname}> " } - end - 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, - :global_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 def parse_node_list(nodes) if nodes.is_a? Config::Object diff --git a/lib/leap_cli/commands/vagrant.rb b/lib/leap_cli/commands/vagrant.rb new file mode 100644 index 0000000..587e5e1 --- /dev/null +++ b/lib/leap_cli/commands/vagrant.rb @@ -0,0 +1,72 @@ +require 'ipaddr' + +module LeapCli; module Commands + + desc 'Bring up one or more local virtual machines' + arg_name '[node-filter]', :optional => true, :multiple => false + command :'local-up' do |c| + c.action do |global_options,options,args| + vagrant_command("up", args) + end + end + + desc 'Halt one or more local virtual machines' + arg_name '[node-filter]', :optional => true, :multiple => false + command :'local-down' do |c| + c.action do |global_options,options,args| + vagrant_command("halt", args) + end + end + + desc 'Destroy one or more local virtual machines' + arg_name '[node-filter]', :optional => true, :multiple => false + command :'local-reset' do |c| + c.action do |global_options,options,args| + vagrant_command("destroy", args) + end + end + + public + + def vagrant_ssh_key_file + file = File.expand_path('../../../vendor/vagrant_ssh_keys/vagrant.key', File.dirname(__FILE__)) + Util.assert_files_exist! file + return file + end + + private + + def vagrant_command(cmd, args) + create_vagrant_file + nodes = manager.filter(args)[:local => true].field(:name) + if nodes.any? + execute "cd #{File.dirname(Path.named_path(:vagrantfile))}; vagrant #{cmd} #{nodes.join(' ')}" + else + bail! "No nodes found" + end + end + + def execute(cmd) + progress2 "Running: #{cmd}" + exec cmd + end + + def create_vagrant_file + lines = [] + netmask = IPAddr.new('255.255.255.255').mask(manager.provider.vagrant.network.split('/').last).to_s + lines << %[Vagrant::Config.run do |config|] + manager.each_node do |node| + if node.vagrant? + lines << %[ config.vm.define :#{node.name} do |config|] + lines << %[ config.vm.box = "minimal-wheezy"] + lines << %[ config.vm.box_url = "http://cloud.github.com/downloads/leapcode/minimal-debian-vagrant/minimal-wheezy.box"] + lines << %[ config.vm.network :hostonly, "#{node.ip_address}", :netmask => "#{netmask}"] + lines << %[ end] + end + end + lines << %[end] + lines << "" + write_file! :vagrantfile, lines.join("\n") + end + +end; end \ No newline at end of file diff --git a/lib/leap_cli/config/manager.rb b/lib/leap_cli/config/manager.rb index 88f21be..00b4ec5 100644 --- a/lib/leap_cli/config/manager.rb +++ b/lib/leap_cli/config/manager.rb @@ -113,6 +113,13 @@ module LeapCli nodes[name] end + # + # yields each node, in sorted order + # + def each_node(&block) + nodes.each_node &block + end + private def load_all_json(pattern) @@ -232,6 +239,14 @@ module LeapCli end end + # + # TODO: apply JSON spec + # + PRIVATE_IP_RANGES = /(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)/ + def validate_provider(provider) + Util::assert! provider.vagrant.network =~ PRIVATE_IP_RANGES, 'provider.json error: vagrant.network is not a local private network' + end + end end end diff --git a/lib/leap_cli/config/object.rb b/lib/leap_cli/config/object.rb index ad32f54..731f3ff 100644 --- a/lib/leap_cli/config/object.rb +++ b/lib/leap_cli/config/object.rb @@ -144,6 +144,20 @@ module LeapCli self end + ## + ## NODE SPECIFIC + ## maybe these should be moved to a Node class. + ## + + # + # returns true if this node has an ip address in the range of the vagrant network + # + def vagrant? + vagrant_range = IPAddr.new @manager.provider.vagrant.network + ip_address = IPAddr.new @node.ip_address + vagrant_range.include?(ip_address) + end + ## ## MACROS ## these are methods used when eval'ing a value in the .json configuration diff --git a/lib/leap_cli/config/object_list.rb b/lib/leap_cli/config/object_list.rb index 708afc1..b0839ca 100644 --- a/lib/leap_cli/config/object_list.rb +++ b/lib/leap_cli/config/object_list.rb @@ -46,6 +46,11 @@ module LeapCli end end + def each_node(&block) + self.keys.sort.each do |node_name| + yield self[node_name] + end + end # def <<(object) # if object.is_a? Config::ObjectList diff --git a/lib/leap_cli/path.rb b/lib/leap_cli/path.rb index 6d68546..48b0d11 100644 --- a/lib/leap_cli/path.rb +++ b/lib/leap_cli/path.rb @@ -29,7 +29,8 @@ module LeapCli; module Path :ca_cert => 'files/ca/ca.crt', :dh_params => 'files/ca/dh.pem', :node_x509_key => 'files/nodes/#{arg}/#{arg}.key', - :node_x509_cert => 'files/nodes/#{arg}/#{arg}.crt' + :node_x509_cert => 'files/nodes/#{arg}/#{arg}.crt', + :vagrantfile => 'test/Vagrantfile' } # diff --git a/lib/leap_cli/util/remote_command.rb b/lib/leap_cli/util/remote_command.rb new file mode 100644 index 0000000..118a65e --- /dev/null +++ b/lib/leap_cli/util/remote_command.rb @@ -0,0 +1,98 @@ +module LeapCli; module Util; module RemoteCommand + extend self + + # + # 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 # ssh options common to all nodes + cap.set :use_sudo, false # we may want to change this in the future + + # Allow password authentication when we are bootstraping a single node + # (and key authentication fails). + if options[:bootstrap] && node_list.size == 1 + hostname = node_list.values.first.name + if options[:echo] + cap.set(:password) { ask "Root SSH password for #{user}@#{hostname}> " } + else + cap.set(:password) { Capistrano::CLI.password_prompt " * Typed password will be hidden (use --echo to make it visible)\nRoot SSH password for #{user}@#{hostname}> " } + end + 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, + :global_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/ + # + # if, in the future, we want to do per-node password options, it would be done like so: + # + # password_proc = Proc.new {Capistrano::CLI.password_prompt "Root SSH password for #{node.name}"} + # return {:password => password_proc} + # + def node_options(node) + { + :ssh_options => { + :host_key_alias => node.name, + :host_name => node.ip_address, + :port => node.ssh.port + }.merge(contingent_ssh_options_for_node(node)) + } + 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 + + def contingent_ssh_options_for_node(node) + if node.vagrant? + {:keys => [vagrant_ssh_key_file]} + else + {} + end + end + +end; end; end \ No newline at end of file diff --git a/test/provider/common.json b/test/provider/common.json index 7504e86..85a93cb 100644 --- a/test/provider/common.json +++ b/test/provider/common.json @@ -21,5 +21,6 @@ "use": false, "cert": "= x509.use ? file(:node_x509_cert) : nil", "key": "= x509.use ? file(:node_x509_key) : nil" - } + }, + "local": "= self.vagrant?" } diff --git a/test/provider/nodes/couch1.json b/test/provider/nodes/couch1.json index fe5d7e5..d246b99 100644 --- a/test/provider/nodes/couch1.json +++ b/test/provider/nodes/couch1.json @@ -1,4 +1,4 @@ { "services": "couchdb", - "ip_address": "245.2.45.42" + "ip_address": "10.5.5.2" } \ No newline at end of file diff --git a/test/provider/nodes/vpn1.json b/test/provider/nodes/vpn1.json index 1c58a1b..5115cb2 100644 --- a/test/provider/nodes/vpn1.json +++ b/test/provider/nodes/vpn1.json @@ -1,6 +1,6 @@ { "services": "openvpn", - "ip_address": "2.2.2.2", + "ip_address": "10.5.5.3", "tags": "production", "openvpn": { "gateway_address": "3.3.3.3", diff --git a/test/provider/provider.json b/test/provider/provider.json index e65eebe..6e7618f 100644 --- a/test/provider/provider.json +++ b/test/provider/provider.json @@ -23,5 +23,8 @@ "bit_size": 3248, "life_span": "1y" } + }, + "vagrant":{ + "network":"10.5.5.0/24" } } \ No newline at end of file diff --git a/vendor/vagrant_ssh_keys/README b/vendor/vagrant_ssh_keys/README new file mode 100644 index 0000000..905756d --- /dev/null +++ b/vendor/vagrant_ssh_keys/README @@ -0,0 +1,5 @@ +# Insecure Keypair + +These keys are the "insecure" public/private keypair commonly used by vagrant +base boxes so that vagrant installations can automatically SSH into the boxes. + diff --git a/vendor/vagrant_ssh_keys/vagrant.key b/vendor/vagrant_ssh_keys/vagrant.key new file mode 100644 index 0000000..7d6a083 --- /dev/null +++ b/vendor/vagrant_ssh_keys/vagrant.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI +w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP +kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 +hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO +Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW +yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd +ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 +Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf +TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK +iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A +sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf +4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP +cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk +EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN +CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX +3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG +YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj +3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ +dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz +6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC +P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF +llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ +kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH ++vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ +NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= +-----END RSA PRIVATE KEY----- diff --git a/vendor/vagrant_ssh_keys/vagrant.pub b/vendor/vagrant_ssh_keys/vagrant.pub new file mode 100644 index 0000000..18a9c00 --- /dev/null +++ b/vendor/vagrant_ssh_keys/vagrant.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key -- cgit v1.2.3