diff options
author | elijah <elijah@riseup.net> | 2015-05-05 15:49:07 -0700 |
---|---|---|
committer | elijah <elijah@riseup.net> | 2015-05-05 15:49:07 -0700 |
commit | 6c6b5a88f18f714924530d64486cb88c02bd7ee4 (patch) | |
tree | 3a663822de4c279da8287db04f0a42cfb2c407fb /lib/leap_cli | |
parent | 51195b20531c4dcdf6a76e6a9a8ef7a771cf76be (diff) | |
parent | 61fdf41087b480db12720df5d5beadd32992475a (diff) |
Merge branch 'develop'
Conflicts:
lib/leap_cli/commands/db.rb
lib/leap_cli/commands/vagrant.rb
Diffstat (limited to 'lib/leap_cli')
-rw-r--r-- | lib/leap_cli/commands/ca.rb | 81 | ||||
-rw-r--r-- | lib/leap_cli/commands/compile.rb | 8 | ||||
-rw-r--r-- | lib/leap_cli/commands/db.rb | 47 | ||||
-rw-r--r-- | lib/leap_cli/commands/deploy.rb | 152 | ||||
-rw-r--r-- | lib/leap_cli/commands/pre.rb | 83 | ||||
-rw-r--r-- | lib/leap_cli/commands/ssh.rb | 100 | ||||
-rw-r--r-- | lib/leap_cli/commands/user.rb | 2 | ||||
-rw-r--r-- | lib/leap_cli/commands/vagrant.rb | 87 | ||||
-rw-r--r-- | lib/leap_cli/config/manager.rb | 9 | ||||
-rw-r--r-- | lib/leap_cli/config/object.rb | 43 | ||||
-rw-r--r-- | lib/leap_cli/config/object_list.rb | 2 | ||||
-rw-r--r-- | lib/leap_cli/config/secrets.rb | 25 | ||||
-rw-r--r-- | lib/leap_cli/core_ext/deep_dup.rb | 53 | ||||
-rw-r--r-- | lib/leap_cli/core_ext/time.rb | 86 | ||||
-rw-r--r-- | lib/leap_cli/leapfile.rb | 31 | ||||
-rw-r--r-- | lib/leap_cli/logger.rb | 10 | ||||
-rw-r--r-- | lib/leap_cli/remote/leap_plugin.rb | 11 | ||||
-rw-r--r-- | lib/leap_cli/remote/puppet_plugin.rb | 2 | ||||
-rw-r--r-- | lib/leap_cli/remote/tasks.rb | 5 | ||||
-rw-r--r-- | lib/leap_cli/util.rb | 54 | ||||
-rw-r--r-- | lib/leap_cli/version.rb | 4 |
21 files changed, 661 insertions, 234 deletions
diff --git a/lib/leap_cli/commands/ca.rb b/lib/leap_cli/commands/ca.rb index 357792f..d5c6240 100644 --- a/lib/leap_cli/commands/ca.rb +++ b/lib/leap_cli/commands/ca.rb @@ -28,26 +28,11 @@ module LeapCli; module Commands cert.command :update do |update| update.switch 'force', :desc => 'Always generate new certificates', :negatable => false update.action do |global_options,options,args| - assert_files_exist! :ca_cert, :ca_key, :msg => 'Run `leap cert ca` to create them' - assert_config! 'provider.ca.server_certificates.bit_size' - assert_config! 'provider.ca.server_certificates.digest' - assert_config! 'provider.ca.server_certificates.life_span' - assert_config! 'common.x509.use' - - nodes = manager.filter!(args) - nodes.each_node do |node| - warn_if_commercial_cert_will_soon_expire(node) - if !node.x509.use - remove_file!([:node_x509_key, node.name]) - remove_file!([:node_x509_cert, node.name]) - elsif options[:force] || cert_needs_updating?(node) - generate_cert_for_node(node) - end - end + update_certificates(manager.filter!(args), options) end end - cert.desc 'Creates a Diffie-Hellman parameter file.' # (needed for server-side of some TLS connections) + cert.desc 'Creates a Diffie-Hellman parameter file, needed for forward secret OpenVPN ciphers.' # (needed for server-side of some TLS connections) cert.command :dh do |dh| dh.action do |global_options,options,args| long_running do @@ -102,7 +87,11 @@ module LeapCli; module Commands assert_config! 'provider.ca.server_certificates.bit_size' assert_config! 'provider.ca.server_certificates.digest' domain = options[:domain] || provider.domain - assert_files_missing! [:commercial_key, domain], [:commercial_csr, domain], :msg => 'If you really want to create a new key and CSR, remove these files first.' + + unless global_options[:force] + assert_files_missing! [:commercial_key, domain], [:commercial_csr, domain], + :msg => 'If you really want to create a new key and CSR, remove these files first or run with --force.' + end server_certificates = provider.ca.server_certificates @@ -139,7 +128,7 @@ module LeapCli; module Commands cert = csr.to_cert cert.serial_number.number = cert_serial_number(domain) cert.not_before = yesterday - cert.not_after = years_from_yesterday(1) + cert.not_after = yesterday.advance(:years => 1) cert.parent = ca_root cert.sign! domain_test_signing_profile write_file! [:commercial_cert, domain], cert.to_pem @@ -158,6 +147,29 @@ module LeapCli; module Commands end end + protected + + # + # will generate new certificates for the specified nodes, if needed. + # + def update_certificates(nodes, options={}) + assert_files_exist! :ca_cert, :ca_key, :msg => 'Run `leap cert ca` to create them' + assert_config! 'provider.ca.server_certificates.bit_size' + assert_config! 'provider.ca.server_certificates.digest' + assert_config! 'provider.ca.server_certificates.life_span' + assert_config! 'common.x509.use' + + nodes.each_node do |node| + warn_if_commercial_cert_will_soon_expire(node) + if !node.x509.use + remove_file!([:node_x509_key, node.name]) + remove_file!([:node_x509_cert, node.name]) + elsif options[:force] || cert_needs_updating?(node) + generate_cert_for_node(node) + end + end + end + private def generate_new_certificate_authority(key_file, cert_file, common_name) @@ -179,7 +191,7 @@ module LeapCli; module Commands # set expiration root.not_before = yesterday - root.not_after = years_from_yesterday(provider.ca.life_span.to_i) + root.not_after = yesterday_advance(provider.ca.life_span) # generate private key root.serial_number.number = 1 @@ -203,12 +215,12 @@ module LeapCli; module Commands return true else cert = load_certificate_file([:node_x509_cert, node.name]) - if cert.not_after < months_from_yesterday(2) + if cert.not_after < Time.now.advance(:months => 2) log :updating, "cert for node '#{node.name}' because it will expire soon" return true end if cert.subject.common_name != node.domain.full - log :updating, "cert for node '#{node.name}' because domain.full has changed" + log :updating, "cert for node '#{node.name}' because domain.full has changed (was #{cert.subject.common_name}, now #{node.domain.full})" return true end cert.openssl_body.extensions.each do |ext| @@ -242,7 +254,7 @@ module LeapCli; module Commands if cert.not_after < Time.now.utc log :error, "the commercial certificate '#{path}' has EXPIRED! " + "You should renew it with `leap cert csr --domain #{domain}`." - elsif cert.not_after < months_from_yesterday(2) + elsif cert.not_after < Time.now.advance(:months => 2) log :warning, "the commercial certificate '#{path}' will expire soon. "+ "You should renew it with `leap cert csr --domain #{domain}`." end @@ -261,7 +273,7 @@ module LeapCli; module Commands # set expiration cert.not_before = yesterday - cert.not_after = years_from_yesterday(provider.ca.server_certificates.life_span.to_i) + cert.not_after = yesterday_advance(provider.ca.server_certificates.life_span) # generate key cert.key_material.generate_key(provider.ca.server_certificates.bit_size) @@ -283,7 +295,7 @@ module LeapCli; module Commands cert.serial_number.number = cert_serial_number(provider.domain) cert.subject.common_name = [prefix, random_common_name(provider.domain)].join cert.not_before = yesterday - cert.not_after = years_from_yesterday(1) + cert.not_after = yesterday.advance(:years => 1) cert.key_material.generate_key(1024) # just for testing, remember! cert.parent = client_ca_root cert.sign! client_test_signing_profile @@ -492,16 +504,15 @@ module LeapCli; module Commands Time.utc t.year, t.month, t.day end - def years_from_yesterday(num) - t = yesterday - Time.utc t.year + num, t.month, t.day - end - - def months_from_yesterday(num) - t = yesterday - date = Date.new t.year, t.month, t.day - date = date >> num # >> is months in the future operator - Time.utc date.year, date.month, date.day + def yesterday_advance(string) + number, unit = string.split(' ') + unless ['years', 'months', 'days', 'hours', 'minutes'].include? unit + bail!("The time property '#{string}' is missing a unit (one of: years, months, days, hours, minutes).") + end + unless number.to_i.to_s == number + bail!("The time property '#{string}' is missing a number.") + end + yesterday.advance(unit.to_sym => number.to_i) end end; end diff --git a/lib/leap_cli/commands/compile.rb b/lib/leap_cli/commands/compile.rb index 78d7520..cfafc74 100644 --- a/lib/leap_cli/commands/compile.rb +++ b/lib/leap_cli/commands/compile.rb @@ -12,8 +12,12 @@ module LeapCli if !LeapCli.leapfile.environment.nil? && !environment.nil? && environment != LeapCli.leapfile.environment bail! "You cannot specify an ENVIRONMENT argument while the environment is pinned." end - if environment && manager.environment_names.include?(environment) - compile_hiera_files(manager.filter([environment])) + if environment + if manager.environment_names.include?(environment) + compile_hiera_files(manager.filter([environment])) + else + bail! "There is no environment named `#{environment}`." + end else compile_hiera_files(manager.filter) end diff --git a/lib/leap_cli/commands/db.rb b/lib/leap_cli/commands/db.rb index fd50424..e4fd385 100644 --- a/lib/leap_cli/commands/db.rb +++ b/lib/leap_cli/commands/db.rb @@ -2,20 +2,30 @@ module LeapCli; module Commands desc 'Database commands.' command :db do |db| - db.desc 'Destroy all the databases. If present, limit to FILTER nodes.' + db.desc 'Destroy one or more databases. If present, limit to FILTER nodes. For example `leap db destroy --db sessions,tokens testing`.' db.arg_name 'FILTER', :optional => true db.command :destroy do |destroy| + destroy.flag :db, :arg_name => "DATABASES", :desc => 'Comma separated list of databases to destroy (no space). Use "--db all" to destroy all databases.', :optional => false destroy.action do |global_options,options,args| - say 'You are about to permanently destroy all database data.' - return unless agree("Continue? ") + dbs = (options[:db]||"").split(',') + bail!('No databases specified') if dbs.empty? nodes = manager.filter(args) if nodes.any? nodes = nodes[:services => 'couchdb'] end if nodes.any? - ssh_connect(nodes, connect_options(options)) do |ssh| - ssh.run('/etc/init.d/bigcouch stop && test ! -z "$(ls /opt/bigcouch/var/lib/ 2> /dev/null)" && rm -r /opt/bigcouch/var/lib/* && echo "db destroyed" || echo "db already destroyed"') - ssh.run('grep ^seq_file /etc/leap/tapicero.yaml | cut -f2 -d\" | xargs rm -v') + unless global_options[:yes] + if dbs.include?('all') + say 'You are about to permanently destroy all database data for nodes [%s].' % nodes.keys.join(', ') + else + say 'You are about to permanently destroy databases [%s] for nodes [%s].' % [dbs.join(', '), nodes.keys.join(', ')] + end + bail! unless agree("Continue? ") + end + if dbs.include?('all') + destroy_all_dbs(nodes) + else + destroy_dbs(nodes, dbs) end say 'You must run `leap deploy` in order to create the databases again.' else @@ -27,4 +37,29 @@ module LeapCli; module Commands private + def destroy_all_dbs(nodes) + ssh_connect(nodes) do |ssh| + ssh.run('/etc/init.d/bigcouch stop && test ! -z "$(ls /opt/bigcouch/var/lib/ 2> /dev/null)" && rm -r /opt/bigcouch/var/lib/* && echo "db destroyed" || echo "db already destroyed"') + ssh.run('grep ^seq_dir /etc/leap/tapicero.yaml | cut -f2 -d\" | xargs rm -rv') + end + end + + def destroy_dbs(nodes, dbs) + nodes.each_node do |node| + ssh_connect(node) do |ssh| + dbs.each do |db| + ssh.run(DESTROY_DB_COMMAND % {:db => db}) + end + end + end + end + + DESTROY_DB_COMMAND = %{ +if [ 200 = `curl -ns -w "%%{http_code}" -X GET "127.0.0.1:5984/%{db}" -o /dev/null` ]; then + echo "Result from DELETE /%{db}:" `curl -ns -X DELETE "127.0.0.1:5984/%{db}"`; +else + echo "Skipping db '%{db}': it does not exist or has already been deleted."; +fi +} + end; end diff --git a/lib/leap_cli/commands/deploy.rb b/lib/leap_cli/commands/deploy.rb index e413807..03240ce 100644 --- a/lib/leap_cli/commands/deploy.rb +++ b/lib/leap_cli/commands/deploy.rb @@ -1,3 +1,4 @@ +require 'etc' module LeapCli module Commands @@ -7,20 +8,17 @@ module LeapCli arg_name 'FILTER' command [:deploy, :d] do |c| - # --fast c.switch :fast, :desc => 'Makes the deploy command faster by skipping some slow steps. A "fast" deploy can be used safely if you recently completed a normal deploy.', :negatable => false - # --sync - c.switch :sync, :desc => "Sync files, but don't actually apply recipes." + c.switch :sync, :desc => "Sync files, but don't actually apply recipes.", :negatable => false - # --force c.switch :force, :desc => 'Deploy even if there is a lockfile.', :negatable => false - # --dev + c.switch :downgrade, :desc => 'Allows deploy to run with an older platform version.', :negatable => false + c.switch :dev, :desc => "Development mode: don't run 'git submodule update' before deploy.", :negatable => false - # --tags c.flag :tags, :desc => 'Specify tags to pass through to puppet (overriding the default).', :arg_name => 'TAG[,TAG]' @@ -49,11 +47,13 @@ module LeapCli environments = [nil] end environments.each do |env| - check_platform_pinning(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)) + # update server certificates if needed + update_certificates(nodes) ssh_connect(nodes, connect_options(options)) do |ssh| ssh.leap.log :checking, 'node' do @@ -69,7 +69,12 @@ module LeapCli 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]) + ssh.puppet.apply(:verbosity => [LeapCli.log_level,5].min, + :tags => tags(options), + :force => options[:force], + :info => deploy_info, + :downgrade => options[:downgrade] + ) end end end @@ -79,8 +84,34 @@ module LeapCli end end + desc 'Display recent deployment history for a set of nodes.' + long_desc 'The FILTER can be the name of a node, service, or tag.' + arg_name 'FILTER' + command [:history, :h] do |c| + c.flag :port, :desc => 'Override the default SSH port.', + :arg_name => 'PORT' + c.flag :ip, :desc => 'Override the default SSH IP address.', + :arg_name => 'IPADDRESS' + c.action do |global,options,args| + nodes = manager.filter!(args) + ssh_connect(nodes, connect_options(options)) do |ssh| + ssh.leap.history + end + end + end + private + def forcible_prompt(forced, msg, prompt) + say(msg) + if forced + log :warning, "continuing anyway because of --force" + else + say "hint: use --force to skip this prompt." + quit!("OK. Bye.") unless agree(prompt) + end + end + # # The currently activated provider.json could have loaded some pinning # information for the platform. If this is the case, refuse to deploy @@ -94,7 +125,7 @@ module LeapCli # "commit": "e1d6280e0a8c565b7fb1a4ed3969ea6fea31a5e2..HEAD" # } # - def check_platform_pinning(environment) + def check_platform_pinning(environment, global_options) provider = manager.env(environment).provider return unless provider['platform'] @@ -112,34 +143,46 @@ module LeapCli # check version if provider.platform['version'] if !Leap::Platform.version_in_range?(provider.platform.version) - say("The platform is pinned to a version range of '#{provider.platform.version}' "+ - "by the `platform.version` property in #{provider_json}, but the platform "+ - "(#{Path.platform}) has version #{Leap::Platform.version}.") - quit!("OK. Bye.") unless agree("Do you really want to deploy from the wrong version? ") + forcible_prompt( + global_options[:force], + "The platform is pinned to a version range of '#{provider.platform.version}' "+ + "by the `platform.version` property in #{provider_json}, but the platform "+ + "(#{Path.platform}) has version #{Leap::Platform.version}.", + "Do you really want to deploy from the wrong version? " + ) end end # check branch if provider.platform['branch'] if !is_git_directory?(Path.platform) - say("The platform is pinned to a particular branch by the `platform.branch` property "+ - "in #{provider_json}, but the platform directory (#{Path.platform}) is not a git repository.") - quit!("OK. Bye.") unless agree("Do you really want to deploy anyway? ") + forcible_prompt( + global_options[:force], + "The platform is pinned to a particular branch by the `platform.branch` property "+ + "in #{provider_json}, but the platform directory (#{Path.platform}) is not a git repository.", + "Do you really want to deploy anyway? " + ) end unless provider.platform.branch == current_git_branch(Path.platform) - say("The platform is pinned to branch '#{provider.platform.branch}' by the `platform.branch` property "+ - "in #{provider_json}, but the current branch is '#{current_git_branch(Path.platform)}' " + - "(for directory '#{Path.platform}')") - quit!("OK. Bye.") unless agree("Do you really want to deploy from the wrong branch? ") + forcible_prompt( + global_options[:force], + "The platform is pinned to branch '#{provider.platform.branch}' by the `platform.branch` property "+ + "in #{provider_json}, but the current branch is '#{current_git_branch(Path.platform)}' " + + "(for directory '#{Path.platform}')", + "Do you really want to deploy from the wrong branch? " + ) end end # check commit if provider.platform['commit'] if !is_git_directory?(Path.platform) - say("The platform is pinned to a particular commit range by the `platform.commit` property "+ - "in #{provider_json}, but the platform directory (#{Path.platform}) is not a git repository.") - quit!("OK. Bye.") unless agree("Do you really want to deploy anyway? ") + forcible_prompt( + global_options[:force], + "The platform is pinned to a particular commit range by the `platform.commit` property "+ + "in #{provider_json}, but the platform directory (#{Path.platform}) is not a git repository.", + "Do you really want to deploy anyway? " + ) end current_commit = current_git_commit(Path.platform) Dir.chdir(Path.platform) do @@ -150,10 +193,13 @@ module LeapCli commit_range = commit_range.split("\n") if !commit_range.include?(current_commit) && provider.platform.commit.split('..').first != current_commit - say("The platform is pinned via the `platform.commit` property in #{provider_json} " + - "to a commit in the range #{provider.platform.commit}, but the current HEAD " + - "(#{current_commit}) is not in that range.") - quit!("OK. Bye.") unless agree("Do you really want to deploy from the wrong commit? ") + forcible_prompt( + global_options[:force], + "The platform is pinned via the `platform.commit` property in #{provider_json} " + + "to a commit in the range #{provider.platform.commit}, but the current HEAD " + + "(#{current_commit}) is not in that range.", + "Do you really want to deploy from the wrong commit? " + ) end end end @@ -177,17 +223,11 @@ module LeapCli # def sync_support_files(ssh) dest_dir = Leap::Platform.files_dir - source_files = [] - if Path.defined?(:custom_puppet_dir) && file_exists?(:custom_puppet_dir) - source_files += [:custom_puppet_dir, :custom_puppet_modules_dir, :custom_puppet_manifests_dir].collect{|path| - Path.relative_path(path, Path.provider) + '/' # rsync needs trailing slash - } - ensure_dir :custom_puppet_modules_dir - end + custom_files = build_custom_file_list ssh.rsync.update do |server| node = manager.node(server.host) files_to_sync = node.file_paths.collect {|path| Path.relative_path(path, Path.provider) } - files_to_sync += source_files + files_to_sync += custom_files if files_to_sync.any? ssh.leap.log(files_to_sync.join(', ') + ' -> ' + node.name + ':' + dest_dir) { @@ -282,5 +322,47 @@ module LeapCli tags.join(',') end + # + # a provider might have various customization files that should be sync'ed to the server. + # this method builds that list of files to sync. + # + def build_custom_file_list + custom_files = [] + Leap::Platform.paths.keys.grep(/^custom_/).each do |path| + if file_exists?(path) + relative_path = Path.relative_path(path, Path.provider) + if dir_exists?(path) + custom_files << relative_path + '/' # rsync needs trailing slash + else + custom_files << relative_path + end + end + end + return custom_files + end + + def deploy_info + info = [] + info << "user: %s" % Etc.getpwuid(Process.euid).name + if is_git_directory?(Path.platform) && current_git_branch(Path.platform) != 'master' + info << "platform: %s (%s %s)" % [ + Leap::Platform.version, + current_git_branch(Path.platform), + current_git_commit(Path.platform)[0..4] + ] + else + info << "platform: %s" % Leap::Platform.version + end + if is_git_directory?(LEAP_CLI_BASE_DIR) + info << "leap_cli: %s (%s %s)" % [ + LeapCli::VERSION, + current_git_branch(LEAP_CLI_BASE_DIR), + current_git_commit(LEAP_CLI_BASE_DIR)[0..4] + ] + else + info << "leap_cli: %s" % LeapCli::VERSION + end + info.join(', ') + end end end diff --git a/lib/leap_cli/commands/pre.rb b/lib/leap_cli/commands/pre.rb index 055f3a1..3b316a4 100644 --- a/lib/leap_cli/commands/pre.rb +++ b/lib/leap_cli/commands/pre.rb @@ -9,76 +9,91 @@ module LeapCli; module Commands default_value '1' flag [:v, :verbose] - desc 'Override default log file' + desc 'Override default log file.' arg_name 'FILE' default_value nil flag :log - desc 'Display version number and exit' + desc 'Display version number and exit.' switch :version, :negatable => false - desc 'Skip prompts and assume "yes"' + desc 'Skip prompts and assume "yes".' switch :yes, :negatable => false + desc 'Like --yes, but also skip prompts that are potentially dangerous to skip.' + switch :force, :negatable => false + desc 'Print full stack trace for exceptions and load `debugger` gem if installed.' switch [:d, :debug], :negatable => false - desc 'Disable colors in output' + desc 'Disable colors in output.' default_value true switch 'color', :negatable => true pre do |global,command,options,args| - # + if global[:force] + global[:yes] = true + end + initialize_leap_cli(true, global) + true + end + + protected + + # + # available options: + # :verbose -- integer log verbosity level + # :log -- log file path + # :color -- true or false, to log in color or not. + # + def initialize_leap_cli(require_provider, options={}) # set verbosity - # - LeapCli.set_log_level(global[:verbose].to_i) + options[:verbose] ||= 1 + LeapCli.set_log_level(options[:verbose].to_i) - # # load Leapfile - # - unless LeapCli.leapfile.load + LeapCli.leapfile.load + if LeapCli.leapfile.valid? + Path.set_platform_path(LeapCli.leapfile.platform_directory_path) + Path.set_provider_path(LeapCli.leapfile.provider_directory_path) + if !Path.provider || !File.directory?(Path.provider) + bail! { log :missing, "provider directory '#{Path.provider}'" } + end + if !Path.platform || !File.directory?(Path.platform) + bail! { log :missing, "platform directory '#{Path.platform}'" } + end + elsif require_provider bail! { log :missing, 'Leapfile in directory tree' } end - Path.set_platform_path(LeapCli.leapfile.platform_directory_path) - Path.set_provider_path(LeapCli.leapfile.provider_directory_path) - if !Path.provider || !File.directory?(Path.provider) - bail! { log :missing, "provider directory '#{Path.provider}'" } - end - if !Path.platform || !File.directory?(Path.platform) - bail! { log :missing, "platform directory '#{Path.platform}'" } - end - # # set log file - # - LeapCli.log_file = global[:log] || LeapCli.leapfile.log + LeapCli.log_file = options[:log] || LeapCli.leapfile.log LeapCli::Util.log_raw(:log) { $0 + ' ' + ORIGINAL_ARGV.join(' ')} log_version - LeapCli.log_in_color = global[:color] - - true + LeapCli.log_in_color = options[:color] end - private - # # add a log entry for the leap command and leap platform versions # def log_version if LeapCli.log_level >= 2 str = "leap command v#{LeapCli::VERSION}" - cli_dir = File.dirname(__FILE__) - if Util.is_git_directory?(cli_dir) - str << " (%s %s)" % [Util.current_git_branch(cli_dir), Util.current_git_commit(cli_dir)] + if Util.is_git_directory?(LEAP_CLI_BASE_DIR) + str << " (%s %s)" % [Util.current_git_branch(LEAP_CLI_BASE_DIR), + Util.current_git_commit(LEAP_CLI_BASE_DIR)] + else + str << " (%s)" % LEAP_CLI_BASE_DIR end log 2, str - str = "leap platform v#{Leap::Platform.version}" - if Util.is_git_directory?(Path.platform) - str << " (%s %s)" % [Util.current_git_branch(Path.platform), Util.current_git_commit(Path.platform)] + if LeapCli.leapfile.valid? + str = "leap platform v#{Leap::Platform.version}" + if Util.is_git_directory?(Path.platform) + str << " (%s %s)" % [Util.current_git_branch(Path.platform), Util.current_git_commit(Path.platform)] + end + log 2, str end - log 2, str end end - end; end diff --git a/lib/leap_cli/commands/ssh.rb b/lib/leap_cli/commands/ssh.rb index 40d205e..1a81902 100644 --- a/lib/leap_cli/commands/ssh.rb +++ b/lib/leap_cli/commands/ssh.rb @@ -35,6 +35,33 @@ module LeapCli; module Commands end end + desc 'Secure copy from FILE1 to FILE2. Files are specified as NODE_NAME:FILE_PATH. For local paths, omit "NODE_NAME:".' + arg_name 'FILE1 FILE2' + command :scp do |c| + c.switch :r, :desc => 'Copy recursively' + c.action do |global_options, options, args| + if args.size != 2 + bail!('You must specificy both FILE1 and FILE2') + end + from, to = args + if (from !~ /:/ && to !~ /:/) || (from =~ /:/ && to =~ /:/) + bail!('One FILE must be remote and the other local.') + end + src_node_name = src_file_path = src_node = nil + dst_node_name = dst_file_path = dst_node = nil + if from =~ /:/ + src_node_name, src_file_path = from.split(':') + src_node = get_node_from_args([src_node_name], :include_disabled => true) + dst_file_path = to + else + dst_node_name, dst_file_path = to.split(':') + dst_node = get_node_from_args([dst_node_name], :include_disabled => true) + src_file_path = from + end + exec_scp(options, src_node, src_file_path, dst_node, dst_file_path) + end + end + protected # @@ -78,22 +105,7 @@ module LeapCli; module Commands def exec_ssh(cmd, cli_options, args) node = get_node_from_args(args, :include_disabled => true) port = node.ssh.port - options = [ - "-o 'HostName=#{node.ip_address}'", - # "-o 'HostKeyAlias=#{node.name}'", << oddly incompatible with ports in known_hosts file, so we must not use this or non-standard ports break. - "-o 'GlobalKnownHostsFile=#{path(:known_hosts)}'", - "-o 'UserKnownHostsFile=/dev/null'" - ] - if node.vagrant? - options << "-i #{vagrant_ssh_key_file}" # use the universal vagrant insecure key - options << "-o IdentitiesOnly=yes" # force the use of the insecure vagrant key - options << "-o 'StrictHostKeyChecking=no'" # blindly accept host key and don't save it (since userknownhostsfile is /dev/null) - else - options << "-o 'StrictHostKeyChecking=yes'" - end - if !node.supported_ssh_host_key_algorithms.empty? - options << "-o 'HostKeyAlgorithms=#{node.supported_ssh_host_key_algorithms}'" - end + options = ssh_config(node) username = 'root' if LeapCli.log_level >= 3 options << "-vv" @@ -133,6 +145,62 @@ module LeapCli; module Commands end end + def exec_scp(cli_options, src_node, src_file_path, dst_node, dst_file_path) + node = src_node || dst_node + options = ssh_config(node) + port = node.ssh.port + username = 'root' + options << "-r" if cli_options[:r] + scp = "scp -P #{port} #{options.join(' ')}" + if src_node + command = "#{scp} #{username}@#{src_node.domain.full}:#{src_file_path} #{dst_file_path}" + elsif dst_node + command = "#{scp} #{src_file_path} #{username}@#{dst_node.domain.full}:#{dst_file_path}" + end + log 2, command + + # exec the shell command in a subprocess + pid = fork { exec "#{command}" } + + Signal.trap("SIGINT") do + Process.kill("KILL", pid) + Process.wait(pid) + exit(0) + end + + # wait for shell to exit so we can grab the exit status + _, status = Process.waitpid2(pid) + exit(status.exitstatus) + end + + # + # SSH command line -o options. See `man ssh_config` + # + # NOTES: + # + # The option 'HostKeyAlias=#{node.name}' is oddly incompatible with ports in + # known_hosts file, so we must not use this or non-standard ports break. + # + def ssh_config(node) + options = [ + "-o 'HostName=#{node.ip_address}'", + "-o 'GlobalKnownHostsFile=#{path(:known_hosts)}'", + "-o 'UserKnownHostsFile=/dev/null'" + ] + if node.vagrant? + options << "-i #{vagrant_ssh_key_file}" # use the universal vagrant insecure key + options << "-o IdentitiesOnly=yes" # force the use of the insecure vagrant key + options << "-o 'StrictHostKeyChecking=no'" # blindly accept host key and don't save it + # (since userknownhostsfile is /dev/null) + else + options << "-o 'StrictHostKeyChecking=yes'" + end + if !node.supported_ssh_host_key_algorithms.empty? + options << "-o 'HostKeyAlgorithms=#{node.supported_ssh_host_key_algorithms}'" + end + return options + end + def parse_tunnel_arg(arg) if arg.count(':') == 1 node_name, remote = arg.split(':') diff --git a/lib/leap_cli/commands/user.rb b/lib/leap_cli/commands/user.rb index 6c33878..480e9a9 100644 --- a/lib/leap_cli/commands/user.rb +++ b/lib/leap_cli/commands/user.rb @@ -100,9 +100,9 @@ module LeapCli # def pick_pgp_key begin - return unless `which gpg`.strip.any? require 'gpgme' rescue LoadError + log "Skipping OpenPGP setup because gpgme is not installed." return end diff --git a/lib/leap_cli/commands/vagrant.rb b/lib/leap_cli/commands/vagrant.rb index b951c87..e652e8b 100644 --- a/lib/leap_cli/commands/vagrant.rb +++ b/lib/leap_cli/commands/vagrant.rb @@ -111,32 +111,23 @@ module LeapCli; module Commands def vagrant_setup assert_bin! 'vagrant', 'Vagrant is required for running local virtual machines. Run "sudo apt-get install vagrant".' - version = vagrant_version - case version - when 0..1 - gem_path = assert_run!('vagrant gem which sahara') - if gem_path.nil? || gem_path.empty? || gem_path =~ /^ERROR/ - log :installing, "vagrant plugin 'sahara'" - assert_run! 'vagrant gem install sahara -v 0.0.13' - # (sahara versions above 0.0.13 require vagrant > 1.0) - end - when 2 - unless assert_run!('vagrant plugin list | grep sahara | cat').chars.any? - log :installing, "vagrant plugin 'sahara'" - assert_run! 'vagrant plugin install sahara' - end + if vagrant_version <= Gem::Version.new('1.0.0') + gem_path = assert_run!('vagrant gem which sahara') + if gem_path.nil? || gem_path.empty? || gem_path =~ /^ERROR/ + log :installing, "vagrant plugin 'sahara'" + assert_run! 'vagrant gem install sahara -v 0.0.13' + end + else + unless assert_run!('vagrant plugin list | grep sahara | cat').chars.any? + log :installing, "vagrant plugin 'sahara'" + assert_run! 'vagrant plugin install sahara' + end end create_vagrant_file end def vagrant_version - minor_version = `vagrant --version | rev | cut -d'.' -f 2`.to_i - version = case minor_version - when 1..9 then 2 - when 0 then 1 - else 0 - end - return version + @vagrant_version ||= Gem::Version.new(assert_run!('vagrant --version').split(' ')[1]) end def execute(cmd) @@ -148,36 +139,34 @@ module LeapCli; module Commands lines = [] netmask = IPAddr.new('255.255.255.255').mask(LeapCli.leapfile.vagrant_network.split('/').last).to_s - version = vagrant_version - case version - when 0..1 - 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 = "LEAP/wheezy"] - lines << %[ config.vm.network :hostonly, "#{node.ip_address}", :netmask => "#{netmask}"] - lines << %[ config.vm.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]] - lines << %[ config.vm.customize ["modifyvm", :id, "--name", "#{node.name}"]] - lines << %[ #{leapfile.custom_vagrant_vm_line}] if leapfile.custom_vagrant_vm_line - lines << %[ end] - end + if vagrant_version <= Gem::Version.new('1.1.0') + 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 = "LEAP/wheezy"] + lines << %[ config.vm.network :hostonly, "#{node.ip_address}", :netmask => "#{netmask}"] + lines << %[ config.vm.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]] + lines << %[ config.vm.customize ["modifyvm", :id, "--name", "#{node.name}"]] + lines << %[ #{leapfile.custom_vagrant_vm_line}] if leapfile.custom_vagrant_vm_line + lines << %[ end] end - when 2 - lines << %[Vagrant.configure("2") do |config|] - manager.each_node do |node| - if node.vagrant? - lines << %[ config.vm.define :#{node.name} do |config|] - lines << %[ config.vm.box = "LEAP/wheezy"] - lines << %[ config.vm.network :private_network, ip: "#{node.ip_address}"] - lines << %[ config.vm.provider "virtualbox" do |v|] - lines << %[ v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]] - lines << %[ v.name = "#{node.name}"] - lines << %[ end] - lines << %[ #{leapfile.custom_vagrant_vm_line}] if leapfile.custom_vagrant_vm_line - lines << %[ end] - end + end + else + lines << %[Vagrant.configure("2") do |config|] + manager.each_node do |node| + if node.vagrant? + lines << %[ config.vm.define :#{node.name} do |config|] + lines << %[ config.vm.box = "LEAP/wheezy"] + lines << %[ config.vm.network :private_network, ip: "#{node.ip_address}"] + lines << %[ config.vm.provider "virtualbox" do |v|] + lines << %[ v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]] + lines << %[ v.name = "#{node.name}"] + lines << %[ end] + lines << %[ #{leapfile.custom_vagrant_vm_line}] if leapfile.custom_vagrant_vm_line + lines << %[ end] end + end end lines << %[end] diff --git a/lib/leap_cli/config/manager.rb b/lib/leap_cli/config/manager.rb index be95831..33b7f05 100644 --- a/lib/leap_cli/config/manager.rb +++ b/lib/leap_cli/config/manager.rb @@ -133,9 +133,9 @@ module LeapCli next unless ename log 3, :loading, '%s environment...' % ename env(ename) do |e| - e.services = load_all_json(Path.named_path([:service_env_config, '*', ename], @provider_dir), Config::Tag) - e.tags = load_all_json(Path.named_path([:tag_env_config, '*', ename], @provider_dir), Config::Tag) - e.provider = load_json( Path.named_path([:provider_env_config, ename], @provider_dir), Config::Provider) + e.services = load_all_json(Path.named_path([:service_env_config, '*', ename], @provider_dir), Config::Tag, :env => ename) + e.tags = load_all_json(Path.named_path([:tag_env_config, '*', ename], @provider_dir), Config::Tag, :env => ename) + e.provider = load_json( Path.named_path([:provider_env_config, ename], @provider_dir), Config::Provider, :env => ename) e.services.inherit_from! env('default').services e.tags.inherit_from! env('default').tags e.provider.inherit_from! env('default').provider @@ -315,6 +315,9 @@ module LeapCli if obj name = File.basename(filename).force_encoding('utf-8').sub(/^([^\.]+).*\.json$/,'\1') obj['name'] ||= name + if options[:env] + obj.environment = options[:env] + end results[name] = obj end end diff --git a/lib/leap_cli/config/object.rb b/lib/leap_cli/config/object.rb index a0d402b..6f71a08 100644 --- a/lib/leap_cli/config/object.rb +++ b/lib/leap_cli/config/object.rb @@ -10,6 +10,34 @@ require 'ya2yaml' # pure ruby yaml module LeapCli module Config + + # + # A proxy for Manager that binds to a particular object + # (so that we can bind to a particular environment) + # + class ManagerBinding + def initialize(manager, object) + @manager = manager + @object = object + end + + def services + @manager.env(@object.environment).services + end + + def tags + @manager.env(@object.environment).tags + end + + def provider + @manager.env(@object.environment).provider + end + + def method_missing(*args) + @manager.send(*args) + end + end + # # This class represents the configuration for a single node, service, or tag. # Also, all the nested hashes are also of this type. @@ -19,8 +47,6 @@ module LeapCli class Object < Hash attr_reader :node - attr_reader :manager - alias :global :manager def initialize(manager=nil, node=nil) # keep a global pointer around to the config manager. used a lot in the eval strings and templates @@ -31,6 +57,19 @@ module LeapCli @node = node || self end + def manager + ManagerBinding.new(@manager, self) + end + alias :global :manager + + def environment=(e) + self.store('environment', e) + end + + def environment + self['environment'] + end + # # export YAML # diff --git a/lib/leap_cli/config/object_list.rb b/lib/leap_cli/config/object_list.rb index 33ca4dd..afcc6a6 100644 --- a/lib/leap_cli/config/object_list.rb +++ b/lib/leap_cli/config/object_list.rb @@ -174,7 +174,7 @@ module LeapCli if self[name] self[name].inherit_from!(object) else - self[name] = object.dup + self[name] = object.deep_dup end end end diff --git a/lib/leap_cli/config/secrets.rb b/lib/leap_cli/config/secrets.rb index 366ffd3..184d11d 100644 --- a/lib/leap_cli/config/secrets.rb +++ b/lib/leap_cli/config/secrets.rb @@ -13,18 +13,29 @@ module LeapCli; module Config @discovered_keys = {} end - # we can't use fetch() or get(), since those already have special meanings - def retrieve(key, environment=nil) - self.fetch(environment||'default', {})[key.to_s] - end + # we can't use fetch() or get(), since those already have special meanings + def retrieve(key, environment) + self.fetch(environment, {})[key.to_s] + end + + def set(*args, &block) + if block_given? + set_with_block(*args, &block) + else + set_without_block(*args) + end + end + + def set_without_block(key, value, environment) + set_with_block(key, environment) {value} + end - def set(key, value, environment=nil) - environment ||= 'default' + def set_with_block(key, environment, &block) key = key.to_s @discovered_keys[environment] ||= {} @discovered_keys[environment][key] = true self[environment] ||= {} - self[environment][key] ||= value + self[environment][key] ||= yield end # diff --git a/lib/leap_cli/core_ext/deep_dup.rb b/lib/leap_cli/core_ext/deep_dup.rb new file mode 100644 index 0000000..b9cf0d3 --- /dev/null +++ b/lib/leap_cli/core_ext/deep_dup.rb @@ -0,0 +1,53 @@ +unless Hash.method_defined?(:deep_dup) + + class Array + def deep_dup + map { |it| it.deep_dup } + end + end + + class Hash + def deep_dup + each_with_object(dup) do |(key, value), hash| + hash[key.deep_dup] = value.deep_dup + end + end + end + + class String + def deep_dup + self.dup + end + end + + class Integer + def deep_dup + self + end + end + + class Float + def deep_dup + self + end + end + + class TrueClass + def deep_dup + self + end + end + + class FalseClass + def deep_dup + self + end + end + + class NilClass + def deep_dup + self + end + end + +end
\ No newline at end of file diff --git a/lib/leap_cli/core_ext/time.rb b/lib/leap_cli/core_ext/time.rb new file mode 100644 index 0000000..fef6c5d --- /dev/null +++ b/lib/leap_cli/core_ext/time.rb @@ -0,0 +1,86 @@ +# +# The following methods are copied from ActiveSupport's Time extension: +# activesupport/lib/active_support/core_ext/time/calculations.rb +# + +class Time + + # + # Uses Date to provide precise Time calculations for years, months, and days + # according to the proleptic Gregorian calendar. The options parameter takes + # a hash with any of these keys: :years, :months, :weeks, :days, :hours, + # :minutes, :seconds. + # + def advance(options) + unless options[:weeks].nil? + options[:weeks], partial_weeks = options[:weeks].divmod(1) + options[:days] = options.fetch(:days, 0) + 7 * partial_weeks + end + + unless options[:days].nil? + options[:days], partial_days = options[:days].divmod(1) + options[:hours] = options.fetch(:hours, 0) + 24 * partial_days + end + + d = to_date.advance(options) + d = d.gregorian if d.julian? + time_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day) + seconds_to_advance = options.fetch(:seconds, 0) + + options.fetch(:minutes, 0) * 60 + + options.fetch(:hours, 0) * 3600 + + if seconds_to_advance.zero? + time_advanced_by_date + else + time_advanced_by_date.since(seconds_to_advance) + end + end + + def since(seconds) + self + seconds + rescue + to_datetime.since(seconds) + end + + # + # Returns a new Time where one or more of the elements have been changed + # according to the options parameter. The time options (:hour, :min, :sec, + # :usec) reset cascadingly, so if only the hour is passed, then minute, sec, + # and usec is set to 0. If the hour and minute is passed, then sec and usec + # is set to 0. The options parameter takes a hash with any of these keys: + # :year, :month, :day, :hour, :min, :sec, :usec. + # + def change(options) + new_year = options.fetch(:year, year) + new_month = options.fetch(:month, month) + new_day = options.fetch(:day, day) + new_hour = options.fetch(:hour, hour) + new_min = options.fetch(:min, options[:hour] ? 0 : min) + new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) + new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) + + if utc? + ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec) + elsif zone + ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec) + else + ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset) + end + end + +end + +class Date + + # activesupport/lib/active_support/core_ext/date/calculations.rb + def advance(options) + options = options.dup + d = self + d = d >> options.delete(:years) * 12 if options[:years] + d = d >> options.delete(:months) if options[:months] + d = d + options.delete(:weeks) * 7 if options[:weeks] + d = d + options.delete(:days) if options[:days] + d + end + +end diff --git a/lib/leap_cli/leapfile.rb b/lib/leap_cli/leapfile.rb index 8895f4d..c2bc10b 100644 --- a/lib/leap_cli/leapfile.rb +++ b/lib/leap_cli/leapfile.rb @@ -47,6 +47,12 @@ module LeapCli # set up paths # @provider_directory_path = directory + begin + # load leaprc first, so that we can potentially access which environment is pinned in Leapfile + # but also load leaprc last, so that it can override what is set in Leapfile. + read_settings(leaprc_path) + rescue StandardError + end read_settings(directory + '/Leapfile') read_settings(leaprc_path) @platform_directory_path = File.expand_path(@platform_directory_path || '../leap_platform', @provider_directory_path) @@ -54,25 +60,26 @@ module LeapCli # # load the platform # - require "#{@platform_directory_path}/platform.rb" - if !Leap::Platform.compatible_with_cli?(LeapCli::VERSION) - Util.bail! "This leap command (v#{LeapCli::VERSION}) " + - "is not compatible with the platform #{@platform_directory_path} (v#{Leap::Platform.version}). " + - "You need leap command #{Leap::Platform.compatible_cli.first} to #{Leap::Platform.compatible_cli.last}." + platform_file = "#{@platform_directory_path}/platform.rb" + unless File.exists?(platform_file) + Util.bail! "ERROR: The file `#{platform_file}` does not exist. Please check the value of `@platform_directory_path` in `Leapfile` or `~/.leaprc`." end - if !Leap::Platform.version_in_range?(LeapCli::COMPATIBLE_PLATFORM_VERSION) + require "#{@platform_directory_path}/platform.rb" + if !Leap::Platform.compatible_with_cli?(LeapCli::VERSION) || + !Leap::Platform.version_in_range?(LeapCli::COMPATIBLE_PLATFORM_VERSION) Util.bail! "This leap command (v#{LeapCli::VERSION}) " + - "is not compatible with the platform #{@platform_directory_path} (v#{Leap::Platform.version}). " + - "You need platform version #{LeapCli::COMPATIBLE_PLATFORM_VERSION.first} to #{LeapCli::COMPATIBLE_PLATFORM_VERSION.last}." + "is not compatible with the platform #{@platform_directory_path} (v#{Leap::Platform.version}).\n " + + "You need either leap command #{Leap::Platform.compatible_cli.first} to #{Leap::Platform.compatible_cli.last} or " + + "platform version #{LeapCli::COMPATIBLE_PLATFORM_VERSION.first} to #{LeapCli::COMPATIBLE_PLATFORM_VERSION.last}" end - unless @allow_production_deploy.nil? Util::log 0, :warning, "in Leapfile: @allow_production_deploy is no longer supported." end unless @platform_branch.nil? Util::log 0, :warning, "in Leapfile: @platform_branch is no longer supported." end - return true + @valid = true + return @valid end end @@ -84,6 +91,10 @@ module LeapCli edit_leaprc(property) end + def valid? + !!@valid + end + private # diff --git a/lib/leap_cli/logger.rb b/lib/leap_cli/logger.rb index 3560d21..328dc27 100644 --- a/lib/leap_cli/logger.rb +++ b/lib/leap_cli/logger.rb @@ -113,8 +113,8 @@ module LeapCli { :match => /sh: .+: command not found/, :color => :magenta, :match_level => 1, :priority => -30 }, # IMPORTANT - { :match => /^err ::/, :color => :red, :match_level => 0, :priority => -10 }, - { :match => /^ERROR:/, :color => :red, :match_level => 0, :priority => -10 }, + { :match => /^err ::/, :color => :red, :match_level => 0, :priority => -10, :exit => 1}, + { :match => /^ERROR:/, :color => :red, :priority => -10, :exit => 1}, { :match => /.*/, :color => :blue, :match_level => 0, :priority => -20 }, # CLEANUP @@ -136,8 +136,8 @@ module LeapCli { :match => /^warning:/, :level => 0, :color => :yellow, :priority => -20}, { :match => /^Duplicate declaration:/, :level => 0, :color => :red, :priority => -20}, { :match => /Finished catalog run/, :level => 0, :color => :green, :priority => -10}, - { :match => /^Puppet apply complete \(changes made\)/, :level => 0, :color => :green, :priority => -10}, - { :match => /^Puppet apply complete \(no changes\)/, :level => 0, :color => :green, :priority => -10}, + { :match => /^APPLY COMPLETE \(changes made\)/, :level => 0, :color => :green, :priority => -10}, + { :match => /^APPLY COMPLETE \(no changes\)/, :level => 0, :color => :green, :priority => -10}, # PUPPET FATAL ERRORS { :match => /^err:/, :level => 0, :color => :red, :priority => -1, :exit => 1}, @@ -146,7 +146,7 @@ module LeapCli { :match => /^Syntax error/, :level => 0, :color => :red, :priority => -1, :exit => 1}, { :match => /^Cannot reassign variable/, :level => 0, :color => :red, :priority => -1, :exit => 1}, { :match => /^Could not find template/, :level => 0, :color => :red, :priority => -1, :exit => 1}, - { :match => /^Puppet apply complete.*fail/, :level => 0, :color => :red, :priority => -1, :exit => 1}, + { :match => /^APPLY COMPLETE.*fail/, :level => 0, :color => :red, :priority => -1, :exit => 1}, # TESTS { :match => /^PASS: /, :color => :green, :priority => -20}, diff --git a/lib/leap_cli/remote/leap_plugin.rb b/lib/leap_cli/remote/leap_plugin.rb index af88c2a..e425842 100644 --- a/lib/leap_cli/remote/leap_plugin.rb +++ b/lib/leap_cli/remote/leap_plugin.rb @@ -6,6 +6,10 @@ module LeapCli; module Remote; module LeapPlugin def required_packages + "puppet rsync lsb-release locales" + end + + def required_wheezy_packages "puppet ruby-hiera-puppet rsync lsb-release locales" end @@ -61,6 +65,13 @@ module LeapCli; module Remote; module LeapPlugin end # + # dumps the recent deploy history to the console + # + def history + run "(test -s /var/log/leap/deploy-summary.log && tail /var/log/leap/deploy-summary.log) || (test -s /var/log/leap/deploy-summary.log.1 && tail /var/log/leap/deploy-summary.log.1) || (echo 'no history')" + end + + # # This is a hairy ugly hack, exactly the kind of stuff that makes ruby # dangerous and too much fun for its own good. # diff --git a/lib/leap_cli/remote/puppet_plugin.rb b/lib/leap_cli/remote/puppet_plugin.rb index e3f6be2..77bb4a3 100644 --- a/lib/leap_cli/remote/puppet_plugin.rb +++ b/lib/leap_cli/remote/puppet_plugin.rb @@ -18,7 +18,7 @@ module LeapCli; module Remote; module PuppetPlugin elsif item[1] === true str << "--" + item[0].to_s else - str << "--" + item[0].to_s + " " + item[1].to_s + str << "--" + item[0].to_s + " " + item[1].inspect end }.join(' ') end diff --git a/lib/leap_cli/remote/tasks.rb b/lib/leap_cli/remote/tasks.rb index 7fd8d64..ab60a51 100644 --- a/lib/leap_cli/remote/tasks.rb +++ b/lib/leap_cli/remote/tasks.rb @@ -34,10 +34,11 @@ BAD_APT_GET_UPDATE = /(BADSIG|NO_PUBKEY|KEYEXPIRED|REVKEYSIG|NODATA)/ task :install_prerequisites, :max_hosts => MAX_HOSTS do apt_get = "DEBIAN_FRONTEND=noninteractive apt-get -q -y -o DPkg::Options::=--force-confold" + apt_get_update = "apt-get update -o Acquire::Languages=none" leap.mkdirs Leap::Platform.leap_dir run "echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen" leap.log :updating, "package list" do - run "apt-get update" do |channel, stream, data| + run apt_get_update do |channel, stream, data| # sadly exitcode is unreliable measure if apt-get update hit a failure. if data =~ BAD_APT_GET_UPDATE LeapCli::Util.bail! do @@ -57,7 +58,7 @@ task :install_prerequisites, :max_hosts => MAX_HOSTS do run "( test -f /etc/init.d/ntp && /etc/init.d/ntp start ) || true" end leap.log :installing, "required packages" do - run "#{apt_get} install #{leap.required_packages}" + run %[#{apt_get} install $( (grep -q wheezy /etc/debian_version && echo #{leap.required_wheezy_packages}) || echo #{leap.required_packages} )] end #run "locale-gen" leap.mkdirs("/etc/leap", "/srv/leap") diff --git a/lib/leap_cli/util.rb b/lib/leap_cli/util.rb index ce5471c..5014238 100644 --- a/lib/leap_cli/util.rb +++ b/lib/leap_cli/util.rb @@ -97,6 +97,23 @@ module LeapCli return output end + def assert_config!(conf_path) + value = nil + begin + value = manager.instance_eval(conf_path) + #rescue NoMethodError + #rescue NameError + ensure + assert! !value.nil? && value != "REQUIRED" do + log :missing, "required configuration value for #{conf_path}" + end + end + end + + ## + ## FILES AND DIRECTORIES + ## + def assert_files_missing!(*files) options = files.last.is_a?(Hash) ? files.pop : {} base = options[:base] || Path.provider @@ -117,19 +134,6 @@ module LeapCli end end - def assert_config!(conf_path) - value = nil - begin - value = manager.instance_eval(conf_path) - #rescue NoMethodError - #rescue NameError - ensure - assert! !value.nil? && value != "REQUIRED" do - log :missing, "required configuration value for #{conf_path}" - end - end - end - def assert_files_exist!(*files) options = files.last.is_a?(Hash) ? files.pop : {} file_list = files.collect { |file_path| @@ -149,6 +153,7 @@ module LeapCli end end + # takes a list of symbolic paths. returns true if all files exist or are directories. def file_exists?(*files) files.each do |file_path| file_path = Path.named_path(file_path) @@ -159,9 +164,16 @@ module LeapCli return true end - ## - ## FILES AND DIRECTORIES - ## + # takes a list of symbolic paths. returns true if all are directories. + def dir_exists?(*dirs) + dirs.each do |dir_path| + dir_path = Path.named_path(dir_path) + if !Dir.exists?(dir_path) + return false + end + end + return true + end # # creates a directory if it doesn't already exist @@ -343,16 +355,12 @@ module LeapCli end # - # compares md5 fingerprints to see if the contents of a file match the string we have in memory + # compares md5 fingerprints to see if the contents of a file match the + # string we have in memory # def file_content_equals?(filepath, contents) filepath = Path.named_path(filepath) - output = `md5sum '#{filepath}'`.strip - if $?.to_i == 0 - return output.split(" ").first == Digest::MD5.hexdigest(contents).to_s - else - return false - end + Digest::MD5.file(filepath).hexdigest == Digest::MD5.hexdigest(contents) end ## diff --git a/lib/leap_cli/version.rb b/lib/leap_cli/version.rb index 909131b..1c6801b 100644 --- a/lib/leap_cli/version.rb +++ b/lib/leap_cli/version.rb @@ -1,7 +1,7 @@ module LeapCli unless defined?(LeapCli::VERSION) - VERSION = '1.6.2' - COMPATIBLE_PLATFORM_VERSION = '0.6.0'..'1.99' + VERSION = '1.7.1' + COMPATIBLE_PLATFORM_VERSION = '0.7'..'0.99' SUMMARY = 'Command line interface to the LEAP platform' DESCRIPTION = 'The command "leap" can be used to manage a bevy of servers running the LEAP platform from the comfort of your own home.' LOAD_PATHS = ['lib', 'vendor/certificate_authority/lib', 'vendor/rsync_command/lib'] |