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'] | 
