diff options
Diffstat (limited to 'lib/leap_cli/commands')
| -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 | 
8 files changed, 382 insertions, 178 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] | 
