From 8572dfd59c21d2032b030adc9dc9a973c6e1c3f5 Mon Sep 17 00:00:00 2001 From: elijah Date: Sat, 8 Dec 2012 20:02:27 -0800 Subject: added commands 'node add' 'node rm' and 'node mv' --- lib/leap_cli/commands/deploy.rb | 4 ++ lib/leap_cli/commands/node.rb | 74 +++++++++++++++++++++++++--- lib/leap_cli/commands/pre.rb | 5 ++ lib/leap_cli/commands/vagrant.rb | 39 +++++++++------ lib/leap_cli/config/object.rb | 15 ++++-- lib/leap_cli/path.rb | 25 ++++++---- lib/leap_cli/util.rb | 57 ++++++++++++++++----- test/leap_platform/provider_base/common.json | 4 +- 8 files changed, 176 insertions(+), 47 deletions(-) diff --git a/lib/leap_cli/commands/deploy.rb b/lib/leap_cli/commands/deploy.rb index bee09a0..f94465f 100644 --- a/lib/leap_cli/commands/deploy.rb +++ b/lib/leap_cli/commands/deploy.rb @@ -16,6 +16,10 @@ module LeapCli end end + nodes.each_node do |node| + assert_files_exist! Path.named_path([:hiera, node.name]), :msg => 'try running `leap compile`' + end + ssh_connect(nodes) do |ssh| ssh.leap.assert_initialized diff --git a/lib/leap_cli/commands/node.rb b/lib/leap_cli/commands/node.rb index 9bf27e2..f208f87 100644 --- a/lib/leap_cli/commands/node.rb +++ b/lib/leap_cli/commands/node.rb @@ -10,16 +10,41 @@ module LeapCli; module Commands desc 'Node management' command :node do |node| node.desc 'Create a new configuration file for a node' + node.long_desc ["If specified, the optional argument seed-options can be used to seed values in the node configuration file.", + "The format is property_name:value.", + "For example: `leap node add web1 ip_address:1.2.3.4 services:webapp`.", + "To set nested properties, property name can contain '.', like so: `leap node add web1 ssh.port:44`", + "To set multiple values for a single property, use ',', like so: `leap node add mynode services:webapp,dns`"].join("\n\n") + node.arg_name ' [seed-options]' # , :optional => false, :multiple => false node.command :add do |add| + add.switch :local, :desc => 'Make a local testing node (by automatically assigning the next available local IP address). Local nodes are run as virtual machines on your computer.', :negatable => false add.action do |global_options,options,args| - log 'not yet implemented' + # argument sanity checks + name = args.first + assert! name, 'No specified.' + assert! name =~ /^[0-9a-z_-]+$/, "illegal characters used in node name '#{name}'" + assert_files_missing! [:node_config, node.name] + + # create and seed new node + node = Config::Object.new + if options[:local] + node['ip_address'] = pick_next_vagrant_ip_address + end + seed_node_data(node, args[1..-1]) + + # write the file + write_file! [:node_config, name], node.dump_json + "\n" end end - node.desc 'Bootstraps a node, setting up ssh keys and installing prerequisites' - node.arg_name 'node-name', :optional => false, :multiple => false + node.desc 'Bootstraps a node, setting up SSH keys and installing prerequisite packages' + node.long_desc "This command prepares a server to be used with the LEAP Platform by saving the server's SSH host key, " + + "copying the authorized_keys file, and installing packages that are required for deploying. " + + "Node init must be run before deploying to a server, and the server must be running and available via the network. " + + "This command only needs to be run once, but there is no harm in running it multiple times." + node.arg_name '' #, :optional => false, :multiple => false node.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 init.action do |global_options,options,args| node = get_node_from_args(args) ping_node(node) @@ -34,17 +59,30 @@ module LeapCli; module Commands end node.desc 'Renames a node file, and all its related files' + node.arg_name ' ' node.command :mv do |mv| mv.action do |global_options,options,args| - log 'not yet implemented' + node = get_node_from_args(args) + new_name = args.last + ensure_dir [:node_files_dir, new_name] + Path::NODE_PATHS.each do |path| + rename_file! [path, node.name], [path, new_name] + end + remove_directory! [:node_files_dir, node.name] end end node.desc 'Removes a node file, and all its related files' - node.arg_name '', :optional => false, :multiple => false + node.arg_name '' #:optional => false #, :multiple => false node.command :rm do |rm| rm.action do |global_options,options,args| - log 'not yet implemented' + node = get_node_from_args(args) + (Path::NODE_PATHS + [:node_files_dir]).each do |path| + remove_file! [path, node.name] + end + if node.vagrant? + vagrant_command("destroy --force", [node.name]) + end end end end @@ -135,4 +173,26 @@ module LeapCli; module Commands 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 + def seed_node_data(node, args) + args.each do |seed| + key, value = seed.split(':') + if value =~ /,/ + value = value.split(',') + end + assert! key =~ /^[0-9a-z\._]+$/, "illegal characters used in property '#{key}'" + if key =~ /\./ + key_parts = key.split('.') + final_key = key_parts.pop + current_object = node + key_parts.each do |key_part| + current_object[key_part] = Config::Object.new + current_object = current_object[key_part] + end + current_object[final_key] = value + else + node[key] = value + end + end + 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 cae787e..346814b 100644 --- a/lib/leap_cli/commands/pre.rb +++ b/lib/leap_cli/commands/pre.rb @@ -39,6 +39,11 @@ module LeapCli bail! { log :missing, "platform directory '#{Path.platform}'" } end + # + # load all the nodes everything + # + manager + # # check requirements # diff --git a/lib/leap_cli/commands/vagrant.rb b/lib/leap_cli/commands/vagrant.rb index cd3e71b..a9c2928 100644 --- a/lib/leap_cli/commands/vagrant.rb +++ b/lib/leap_cli/commands/vagrant.rb @@ -10,7 +10,6 @@ module LeapCli; module Commands local.arg_name 'node-filter', :optional => true #, :multiple => false local.command :start do |start| start.action do |global_options,options,args| - vagrant_setup vagrant_command(["up", "sandbox on"], args) end end @@ -19,7 +18,6 @@ module LeapCli; module Commands local.arg_name 'node-filter', :optional => true #, :multiple => false local.command :stop do |stop| stop.action do |global_options,options,args| - vagrant_setup vagrant_command("halt", args) end end @@ -28,7 +26,6 @@ module LeapCli; module Commands local.arg_name 'node-filter', :optional => true #, :multiple => false local.command :reset do |reset| reset.action do |global_options,options,args| - vagrant_setup vagrant_command("sandbox rollback", args) end end @@ -37,7 +34,6 @@ module LeapCli; module Commands local.arg_name 'node-filter', :optional => true #, :multiple => false local.command :destroy do |destroy| destroy.action do |global_options,options,args| - vagrant_setup vagrant_command("destroy", args) end end @@ -46,7 +42,6 @@ module LeapCli; module Commands local.arg_name 'node-filter', :optional => true #, :multiple => false local.command :status do |status| status.action do |global_options,options,args| - vagrant_setup vagrant_command("status", args) end end @@ -66,18 +61,10 @@ module LeapCli; module Commands return file_path end - private - - def vagrant_setup - assert_bin! 'vagrant', 'run "sudo gem install vagrant"' - unless `vagrant gem which sahara`.chars.any? - log :installing, "vagrant plugin 'sahara'" - assert_run! 'vagrant gem install sahara' - end - create_vagrant_file - end + protected def vagrant_command(cmds, args) + vagrant_setup cmds = cmds.to_a assert_config! 'provider.vagrant.network' if args.empty? @@ -99,6 +86,17 @@ module LeapCli; module Commands end end + private + + def vagrant_setup + assert_bin! 'vagrant', 'run "sudo gem install vagrant"' + unless `vagrant gem which sahara`.chars.any? + log :installing, "vagrant plugin 'sahara'" + assert_run! 'vagrant gem install sahara' + end + create_vagrant_file + end + def execute(cmd) log 2, :run, cmd exec cmd @@ -123,4 +121,15 @@ module LeapCli; module Commands write_file! :vagrantfile, lines.join("\n") end + def pick_next_vagrant_ip_address + taken_ips = manager.nodes[:local => true].field(:ip_address) + if taken_ips.any? + highest_ip = taken_ips.map{|ip| IPAddr.new(ip)}.max + new_ip = highest_ip.succ + else + new_ip = IPAddr.new(manager.provider.vagrant.network).succ.succ + end + return new_ip.to_s + end + end; end \ No newline at end of file diff --git a/lib/leap_cli/config/object.rb b/lib/leap_cli/config/object.rb index bbaa6f4..155b51f 100644 --- a/lib/leap_cli/config/object.rb +++ b/lib/leap_cli/config/object.rb @@ -199,9 +199,18 @@ module LeapCli # 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) + begin + vagrant_range = IPAddr.new @manager.provider.vagrant.network + rescue ArgumentError => exc + Util::bail! { Util::log :invalid, "ip address '#{@node.ip_address}' vagrant.network" } + end + + begin + ip_address = IPAddr.new @node.get('ip_address') + rescue ArgumentError => exc + Util::log :warning, "invalid ip address '#{@node.get('ip_address')}' for node '#{@node.name}'" + end + return vagrant_range.include?(ip_address) end ## diff --git a/lib/leap_cli/path.rb b/lib/leap_cli/path.rb index 20994f4..43f2edc 100644 --- a/lib/leap_cli/path.rb +++ b/lib/leap_cli/path.rb @@ -2,6 +2,9 @@ require 'fileutils' module LeapCli; module Path + # + # all the named paths, relative to provider directory. + # NAMED_PATHS = { # directories :hiera_dir => 'hiera', @@ -23,16 +26,9 @@ module LeapCli; module Path :provider_json_template => 'files/service-definitions/provider.json.erb', :eip_service_json_template => 'files/service-definitions/eip-service.json.erb', - # input data files - :commercial_cert => 'files/cert/#{arg}.crt', - :commercial_key => 'files/cert/#{arg}.key', - :commercial_csr => 'files/cert/#{arg}.csr', - # output files :user_ssh => 'users/#{arg}/#{arg}_ssh.pub', :user_pgp => 'users/#{arg}/#{arg}_pgp.pub', - :hiera => 'hiera/#{arg}.yaml', - :node_ssh_pub_key => 'files/nodes/#{arg}/#{arg}_ssh.pub', :known_hosts => 'files/ssh/known_hosts', :authorized_keys => 'files/ssh/authorized_keys', :ca_key => 'files/ca/ca.key', @@ -42,10 +38,14 @@ module LeapCli; module Path :commercial_csr => 'files/cert/#{arg}.csr', :commercial_cert => 'files/cert/#{arg}.crt', :commercial_ca_cert => 'files/cert/commercial_ca.crt', - :node_x509_key => 'files/nodes/#{arg}/#{arg}.key', - :node_x509_cert => 'files/nodes/#{arg}/#{arg}.crt', :vagrantfile => 'test/Vagrantfile', + # node output files + :hiera => 'hiera/#{arg}.yaml', + :node_ssh_pub_key => 'files/nodes/#{arg}/#{arg}_ssh.pub', + :node_x509_key => 'files/nodes/#{arg}/#{arg}.key', + :node_x509_cert => 'files/nodes/#{arg}/#{arg}.crt', + # testing files :test_client_key => 'test/cert/client.key', :test_client_cert => 'test/cert/client.crt', @@ -53,6 +53,13 @@ module LeapCli; module Path :test_client_openvpn_template => 'test/openvpn/client.ovpn.erb' } + # + # paths that take node name as the argument + # + NODE_PATHS = [ + :node_config, :hiera, :node_x509_cert, :node_x509_key, :node_ssh_pub_key + ] + def self.platform @platform end diff --git a/lib/leap_cli/util.rb b/lib/leap_cli/util.rb index 98c3002..d12c5a6 100644 --- a/lib/leap_cli/util.rb +++ b/lib/leap_cli/util.rb @@ -28,9 +28,9 @@ module LeapCli LeapCli.log_level = 3 yield elsif message - puts message + log 0, message end - log :bail, "" + log 0, :bail, "" raise SystemExit.new end @@ -105,11 +105,11 @@ module LeapCli def assert_config!(conf_path) value = nil - begin + #begin value = manager.instance_eval(conf_path) - rescue NoMethodError - rescue NameError - end + #rescue NoMethodError + #rescue NameError + #end assert! !value.nil? && value != "REQUIRED" do log :missing, "required configuration value for #{conf_path}" end @@ -199,19 +199,39 @@ module LeapCli def remove_file!(filepath) filepath = Path.named_path(filepath) if File.exists?(filepath) - File.unlink(filepath) - log :removed, filepath + if File.directory?(filepath) + remove_directory!(filepath) + else + begin + File.unlink(filepath) + log :removed, filepath + rescue Exception => exc + bail! do + log :failed, "to remove file #{filepath}" + log "error message: " + exc.to_s + end + end + end end end def remove_directory!(filepath) filepath = Path.named_path(filepath) if filepath !~ /^#{Regexp.escape(Path.provider)}/ || filepath =~ /\.\./ - raise "sanity check on rm -r did not pass for #{filepath}" + bail! "sanity check on rm -r did not pass for #{filepath}" end if File.directory?(filepath) - FileUtils.rm_r(filepath) - log :removed, filepath + begin + FileUtils.rm_r(filepath) + log :removed, filepath + rescue Exception => exc + bail! do + log :failed, "to remove directory #{filepath}" + log "error message: " + exc.to_s + end + end + else + log :failed, "to remove '#{filepath}', it is not a directory" end end @@ -237,6 +257,21 @@ module LeapCli end end + def rename_file!(oldpath, newpath) + oldpath = Path.named_path(oldpath) + newpath = Path.named_path(newpath) + if File.exists? newpath + log :skipping, "#{Path.relative_path(newpath)}, file already exists" + return + end + if !File.exists? oldpath + log :skipping, "#{Path.relative_path(oldpath)}, file is missing" + return + end + FileUtils.mv oldpath, newpath + log :moved, "#{Path.relative_path(oldpath)} to #{Path.relative_path(newpath)}" + end + def cmd_exists?(cmd) `which #{cmd}`.strip.chars.any? end diff --git a/test/leap_platform/provider_base/common.json b/test/leap_platform/provider_base/common.json index 3d44269..f5093a5 100644 --- a/test/leap_platform/provider_base/common.json +++ b/test/leap_platform/provider_base/common.json @@ -18,8 +18,8 @@ }, "x509": { "use": false, - "cert": "= x509.use ? file(:node_x509_cert, :missing => 'x509 certificate for node $node. Run `leap update-cert`') : nil", - "key": "= x509.use ? file(:node_x509_key, :missing => 'x509 key for node $node. Run `leap update-cert`') : nil" + "cert": "= x509.use ? file(:node_x509_cert, :missing => 'x509 certificate for node $node. Run `leap cert update`') : nil", + "key": "= x509.use ? file(:node_x509_key, :missing => 'x509 key for node $node. Run `leap cert update`') : nil" }, "local": false } -- cgit v1.2.3