aboutsummaryrefslogtreecommitdiff
path: root/lib/leap_cli/commands
diff options
context:
space:
mode:
Diffstat (limited to 'lib/leap_cli/commands')
-rw-r--r--lib/leap_cli/commands/ca.rb106
-rw-r--r--lib/leap_cli/commands/compile.rb11
-rw-r--r--lib/leap_cli/commands/deploy.rb163
-rw-r--r--lib/leap_cli/commands/env.rb53
-rw-r--r--lib/leap_cli/commands/facts.rb4
-rw-r--r--lib/leap_cli/commands/inspect.rb2
-rw-r--r--lib/leap_cli/commands/list.rb18
-rw-r--r--lib/leap_cli/commands/node.rb44
-rw-r--r--lib/leap_cli/commands/pre.rb29
-rw-r--r--lib/leap_cli/commands/shell.rb17
-rw-r--r--lib/leap_cli/commands/test.rb4
-rw-r--r--lib/leap_cli/commands/user.rb16
-rw-r--r--lib/leap_cli/commands/vagrant.rb2
13 files changed, 362 insertions, 107 deletions
diff --git a/lib/leap_cli/commands/ca.rb b/lib/leap_cli/commands/ca.rb
index 46e3494..579e305 100644
--- a/lib/leap_cli/commands/ca.rb
+++ b/lib/leap_cli/commands/ca.rb
@@ -1,6 +1,6 @@
-require 'openssl'
-require 'certificate_authority'
-require 'date'
+autoload :OpenSSL, 'openssl'
+autoload :CertificateAuthority, 'certificate_authority'
+autoload :Date, 'date'
require 'digest/md5'
module LeapCli; module Commands
@@ -36,6 +36,7 @@ module LeapCli; module Commands
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])
@@ -81,9 +82,19 @@ module LeapCli; module Commands
# http://www.redkestrel.co.uk/Articles/CSR.html
#
cert.desc "Creates a CSR for use in buying a commercial X.509 certificate."
- cert.long_desc "Unless specified, the CSR is created for the provider's primary domain. The properties used for this CSR come from `provider.ca.server_certificates`."
+ cert.long_desc "Unless specified, the CSR is created for the provider's primary domain. "+
+ "The properties used for this CSR come from `provider.ca.server_certificates`, "+
+ "but may be overridden here."
cert.command :csr do |csr|
csr.flag 'domain', :arg_name => 'DOMAIN', :desc => 'Specify what domain to create the CSR for.'
+ csr.flag ['organization', 'O'], :arg_name => 'ORGANIZATION', :desc => "Override default O in distinguished name."
+ csr.flag ['unit', 'OU'], :arg_name => 'UNIT', :desc => "Set OU in distinguished name."
+ csr.flag 'email', :arg_name => 'EMAIL', :desc => "Set emailAddress in distinguished name."
+ csr.flag ['locality', 'L'], :arg_name => 'LOCALITY', :desc => "Set L in distinguished name."
+ csr.flag ['state', 'ST'], :arg_name => 'STATE', :desc => "Set ST in distinguished name."
+ csr.flag ['country', 'C'], :arg_name => 'COUNTRY', :desc => "Set C in distinguished name."
+ csr.flag :bits, :arg_name => 'BITS', :desc => "Override default certificate bit length"
+ csr.flag :digest, :arg_name => 'DIGEST', :desc => "Override default signature digest"
csr.action do |global_options,options,args|
assert_config! 'provider.domain'
assert_config! 'provider.name'
@@ -97,24 +108,25 @@ module LeapCli; module Commands
# RSA key
keypair = CertificateAuthority::MemoryKeyMaterial.new
- log :generating, "%s bit RSA key" % server_certificates.bit_size do
- keypair.generate_key(server_certificates.bit_size)
+ bit_size = (options[:bits] || server_certificates.bit_size).to_i
+ log :generating, "%s bit RSA key" % bit_size do
+ keypair.generate_key(bit_size)
write_file! [:commercial_key, domain], keypair.private_key.to_pem
end
# CSR
dn = CertificateAuthority::DistinguishedName.new
- csr = CertificateAuthority::SigningRequest.new
- dn.common_name = domain
- dn.organization = provider.name[provider.default_language]
- dn.country = server_certificates['country'] # optional
- dn.state = server_certificates['state'] # optional
- dn.locality = server_certificates['locality'] # optional
-
- log :generating, "CSR with commonName => '%s', organization => '%s'" % [dn.common_name, dn.organization] do
- csr.distinguished_name = dn
- csr.key_material = keypair
- csr.digest = server_certificates.digest
+ dn.common_name = domain
+ dn.organization = options[:organization] || provider.name[provider.default_language]
+ dn.ou = options[:organizational_unit] # optional
+ dn.email_address = options[:email] # optional
+ dn.country = options[:country] || server_certificates['country'] # optional
+ dn.state = options[:state] || server_certificates['state'] # optional
+ dn.locality = options[:locality] || server_certificates['locality'] # optional
+
+ digest = options[:digest] || server_certificates.digest
+ log :generating, "CSR with #{digest} digest and #{print_dn(dn)}" do
+ csr = create_csr(dn, keypair, digest)
request = csr.to_x509_csr
write_file! [:commercial_csr, domain], csr.to_pem
end
@@ -191,7 +203,7 @@ module LeapCli; module Commands
return true
else
cert = load_certificate_file([:node_x509_cert, node.name])
- if cert.not_after < months_from_yesterday(1)
+ if cert.not_after < months_from_yesterday(2)
log :updating, "cert for node '#{node.name}' because it will expire soon"
return true
end
@@ -222,6 +234,18 @@ module LeapCli; module Commands
return false
end
+ def warn_if_commercial_cert_will_soon_expire(node)
+ dns_names_for_node(node).each do |domain|
+ if file_exists?([:commercial_cert, domain])
+ cert = load_certificate_file([:commercial_cert, domain])
+ if cert.not_after < months_from_yesterday(2)
+ log :warning, "the commercial certificate '#{Path.relative_path([:commercial_cert, domain])}' will expire soon. "+
+ "You should renew it with `leap cert csr --domain #{domain}`."
+ end
+ end
+ end
+ end
+
def generate_cert_for_node(node)
return if node.x509.use == false
@@ -262,6 +286,43 @@ module LeapCli; module Commands
yield cert.key_material.private_key.to_pem, cert.to_pem
end
+ #
+ # creates a CSR and returns it.
+ # with the correct extReq attribute so that the CA
+ # doens't generate certs with extensions we don't want.
+ #
+ def create_csr(dn, keypair, digest)
+ csr = CertificateAuthority::SigningRequest.new
+ csr.distinguished_name = dn
+ csr.key_material = keypair
+ csr.digest = digest
+
+ # define extensions manually (library doesn't support setting these on CSRs)
+ extensions = []
+ extensions << CertificateAuthority::Extensions::BasicConstraints.new.tap {|basic|
+ basic.ca = false
+ }
+ extensions << CertificateAuthority::Extensions::KeyUsage.new.tap {|keyusage|
+ keyusage.usage = ["digitalSignature", "keyEncipherment"]
+ }
+ extensions << CertificateAuthority::Extensions::ExtendedKeyUsage.new.tap {|extkeyusage|
+ extkeyusage.usage = [ "serverAuth"]
+ }
+
+ # convert extensions to attribute 'extReq'
+ # aka "Requested Extensions"
+ factory = OpenSSL::X509::ExtensionFactory.new
+ attrval = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(
+ extensions.map{|e| factory.create_ext(e.openssl_identifier, e.to_s, e.critical)}
+ )])
+ attrs = [
+ OpenSSL::X509::Attribute.new("extReq", attrval),
+ ]
+ csr.attributes = attrs
+
+ return csr
+ end
+
def ca_root
@ca_root ||= begin
load_certificate_file(:ca_cert, :ca_key)
@@ -406,6 +467,15 @@ module LeapCli; module Commands
cert_serial_number(domain_name).to_s(36)
end
+ # prints CertificateAuthority::DistinguishedName fields
+ def print_dn(dn)
+ fields = {}
+ [:common_name, :locality, :state, :country, :organization, :organizational_unit, :email_address].each do |attr|
+ fields[attr] = dn.send(attr) if dn.send(attr)
+ end
+ fields.inspect
+ end
+
##
## TIME HELPERS
##
diff --git a/lib/leap_cli/commands/compile.rb b/lib/leap_cli/commands/compile.rb
index 63c2047..644ce2a 100644
--- a/lib/leap_cli/commands/compile.rb
+++ b/lib/leap_cli/commands/compile.rb
@@ -5,9 +5,18 @@ module LeapCli
desc "Compile generated files."
command :compile do |c|
c.desc 'Compiles node configuration files into hiera files used for deployment.'
+ c.arg_name 'ENVIRONMENT', :optional => true
c.command :all do |all|
all.action do |global_options,options,args|
- compile_hiera_files
+ environment = args.first
+ 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]))
+ else
+ compile_hiera_files(manager.filter)
+ end
end
end
diff --git a/lib/leap_cli/commands/deploy.rb b/lib/leap_cli/commands/deploy.rb
index 814407f..6589837 100644
--- a/lib/leap_cli/commands/deploy.rb
+++ b/lib/leap_cli/commands/deploy.rb
@@ -17,9 +17,12 @@ module LeapCli
# --force
c.switch :force, :desc => 'Deploy even if there is a lockfile.', :negatable => false
+ # --dev
+ 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).',
- :default_value => DEFAULT_TAGS.join(','), :arg_name => 'TAG[,TAG]'
+ :arg_name => 'TAG[,TAG]'
c.flag :port, :desc => 'Override the default SSH port.',
:arg_name => 'PORT'
@@ -28,9 +31,12 @@ module LeapCli
:arg_name => 'IPADDRESS'
c.action do |global,options,args|
- init_submodules
- nodes = filter_deploy_nodes(args)
+ if options[:dev] != true
+ init_submodules
+ end
+
+ nodes = manager.filter!(args)
if nodes.size > 1
say "Deploying to these nodes: #{nodes.keys.join(', ')}"
if !global[:yes] && !agree("Continue? ")
@@ -38,7 +44,16 @@ module LeapCli
end
end
- compile_hiera_files
+ environments = nodes.field('environment').uniq
+ if environments.empty?
+ environments = [nil]
+ end
+ environments.each do |env|
+ check_platform_pinning(env)
+ 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))
ssh_connect(nodes, connect_options(options)) do |ssh|
ssh.leap.log :checking, 'node' do
@@ -58,39 +73,128 @@ module LeapCli
end
end
end
+
end
end
private
+ #
+ # The currently activated provider.json could have loaded some pinning
+ # information for the platform. If this is the case, refuse to deploy
+ # if there is a mismatch.
+ #
+ # For example:
+ #
+ # "platform": {
+ # "branch": "develop"
+ # "version": "1.0..99"
+ # "commit": "e1d6280e0a8c565b7fb1a4ed3969ea6fea31a5e2..HEAD"
+ # }
+ #
+ def check_platform_pinning(environment)
+ provider = manager.env(environment).provider
+ return unless provider['platform']
+
+ if environment.nil? || environment == 'default'
+ provider_json = 'provider.json'
+ else
+ provider_json = 'provider.' + environment + '.json'
+ end
+
+ # can we have json schema verification already?
+ unless provider.platform.is_a? Hash
+ bail!('`platform` attribute in #{provider_json} must be a hash (was %s).' % provider.platform.inspect)
+ end
+
+ # 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? ")
+ 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? ")
+ 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? ")
+ 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? ")
+ end
+ current_commit = current_git_commit(Path.platform)
+ Dir.chdir(Path.platform) do
+ commit_range = assert_run!("git log --pretty='format:%H' '#{provider.platform.commit}'",
+ "The platform is pinned to a particular commit range by the `platform.commit` property "+
+ "in #{provider_json}, but git was not able to find commits in the range specified "+
+ "(#{provider.platform.commit}).")
+ 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? ")
+ end
+ end
+ end
+ end
+
def sync_hiera_config(ssh)
- dest_dir = provider.hiera_sync_destination
ssh.rsync.update do |server|
node = manager.node(server.host)
hiera_file = Path.relative_path([:hiera, node.name])
- ssh.leap.log hiera_file + ' -> ' + node.name + ':' + dest_dir + '/hiera.yaml'
+ ssh.leap.log hiera_file + ' -> ' + node.name + ':' + Leap::Platform.hiera_path
{
:source => hiera_file,
- :dest => dest_dir + '/hiera.yaml',
+ :dest => Leap::Platform.hiera_path,
:flags => "-rltp --chmod=u+rX,go-rwx"
}
end
end
+ #
+ # sync various support files.
+ #
def sync_support_files(ssh)
- dest_dir = provider.hiera_sync_destination
+ 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
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
if files_to_sync.any?
ssh.leap.log(files_to_sync.join(', ') + ' -> ' + node.name + ':' + dest_dir)
{
- :chdir => Path.provider,
+ :chdir => Path.named_path(:files_dir),
:source => ".",
:dest => dest_dir,
:excludes => "*",
- :includes => calculate_includes_from_files(files_to_sync),
- :flags => "-rltp --chmod=u+rX,go-rwx --relative --delete --delete-excluded --filter='protect hiera.yaml' --copy-links"
+ :includes => calculate_includes_from_files(files_to_sync, '/files'),
+ :flags => "-rltp --chmod=u+rX,go-rwx --relative --delete --delete-excluded --copy-links"
}
else
nil
@@ -100,9 +204,9 @@ module LeapCli
def sync_puppet_files(ssh)
ssh.rsync.update do |server|
- ssh.leap.log(Path.platform + '/[bin,tests,puppet] -> ' + server.host + ':' + LeapCli::PUPPET_DESTINATION)
+ ssh.leap.log(Path.platform + '/[bin,tests,puppet] -> ' + server.host + ':' + Leap::Platform.leap_dir)
{
- :dest => LeapCli::PUPPET_DESTINATION,
+ :dest => Leap::Platform.leap_dir,
:source => '.',
:chdir => Path.platform,
:excludes => '*',
@@ -112,7 +216,12 @@ module LeapCli
end
end
+ #
+ # ensure submodules are up to date, if the platform is a git
+ # repository.
+ #
def init_submodules
+ return unless is_git_directory?(Path.platform)
Dir.chdir Path.platform do
assert_run! "git submodule sync"
statuses = assert_run! "git submodule status"
@@ -126,11 +235,17 @@ module LeapCli
end
end
- def calculate_includes_from_files(files)
+ #
+ # converts an array of file paths into an array
+ # suitable for --include of rsync
+ #
+ # if set, `prefix` is stripped off.
+ #
+ def calculate_includes_from_files(files, prefix=nil)
return nil unless files and files.any?
# prepend '/' (kind of like ^ for rsync)
- includes = files.collect {|file| '/' + file}
+ includes = files.collect {|file| file =~ /^\// ? file : '/' + file }
# include all sub files of specified directories
includes.size.times do |i|
@@ -148,6 +263,10 @@ module LeapCli
end
end
+ if prefix
+ includes.map! {|path| path.sub(/^#{Regexp.escape(prefix)}\//, '/')}
+ end
+
return includes
end
@@ -155,23 +274,11 @@ module LeapCli
if options[:tags]
tags = options[:tags].split(',')
else
- tags = LeapCli::DEFAULT_TAGS.dup
+ tags = Leap::Platform.default_puppet_tags.dup
end
tags << 'leap_slow' unless options[:fast]
tags.join(',')
end
- #
- # for safety, we allow production deploys to be turned off in the Leapfile.
- #
- def filter_deploy_nodes(filter)
- nodes = manager.filter!(filter)
- if !leapfile.allow_production_deploy
- nodes = nodes[:environment => "!production"]
- assert! nodes.any?, "Skipping deploy because @allow_production_deploy is disabled."
- end
- nodes
- end
-
end
end
diff --git a/lib/leap_cli/commands/env.rb b/lib/leap_cli/commands/env.rb
new file mode 100644
index 0000000..b2f585d
--- /dev/null
+++ b/lib/leap_cli/commands/env.rb
@@ -0,0 +1,53 @@
+module LeapCli
+ module Commands
+
+ desc "Manipulate and query environment information."
+ long_desc "The 'environment' node property can be used to isolate sets of nodes into entirely separate environments. "+
+ "A node in one environment will never interact with a node from another environment. "+
+ "Environment pinning works by modifying your ~/.leaprc file and is dependent on the "+
+ "absolute file path of your provider directory (pins don't apply if you move the directory)"
+ command :env do |c|
+ c.desc "List the available environments. The pinned environment, if any, will be marked with '*'."
+ c.command :ls do |ls|
+ ls.action do |global_options, options, args|
+ envs = ["default"] + manager.environment_names.compact.sort
+ envs.each do |env|
+ if env
+ if LeapCli.leapfile.environment == env
+ puts "* #{env}"
+ else
+ puts " #{env}"
+ end
+ end
+ end
+ end
+ end
+
+ c.desc 'Pin the environment to ENVIRONMENT. All subsequent commands will only apply to nodes in this environment.'
+ c.arg_name 'ENVIRONMENT'
+ c.command :pin do |pin|
+ pin.action do |global_options,options,args|
+ environment = args.first
+ if environment == 'default' ||
+ (environment && manager.environment_names.include?(environment))
+ LeapCli.leapfile.set('environment', environment)
+ log 0, :saved, "~/.leaprc with environment set to #{environment}."
+ end
+ end
+ end
+
+ c.desc "Unpin the environment. All subsequent commands will apply to all nodes."
+ c.command :unpin do |unpin|
+ unpin.action do |global_options, options, args|
+ LeapCli.leapfile.unset('environment')
+ log 0, :saved, "~/.leaprc, removing environment property."
+ end
+ end
+
+ c.default_command :ls
+ end
+
+ protected
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/commands/facts.rb b/lib/leap_cli/commands/facts.rb
index d607086..65eda61 100644
--- a/lib/leap_cli/commands/facts.rb
+++ b/lib/leap_cli/commands/facts.rb
@@ -91,7 +91,9 @@ module LeapCli; module Commands
end
end
end
- overwrite_existing = args.empty?
+ # only overwrite the entire facts file if and only if we are gathering facts
+ # for all nodes in all environments.
+ overwrite_existing = args.empty? && LeapCli.leapfile.environment.nil?
update_facts_file(new_facts, overwrite_existing)
end
diff --git a/lib/leap_cli/commands/inspect.rb b/lib/leap_cli/commands/inspect.rb
index 746a80c..e8f5caf 100644
--- a/lib/leap_cli/commands/inspect.rb
+++ b/lib/leap_cli/commands/inspect.rb
@@ -109,7 +109,7 @@ module LeapCli; module Commands
if options[:base]
inspect_json manager.base_provider
elsif arg =~ /provider\.(.*)\.json/
- inspect_json manager.providers[$1]
+ inspect_json manager.env($1).provider
else
inspect_json manager.provider
end
diff --git a/lib/leap_cli/commands/list.rb b/lib/leap_cli/commands/list.rb
index 5b84113..b8d7739 100644
--- a/lib/leap_cli/commands/list.rb
+++ b/lib/leap_cli/commands/list.rb
@@ -15,24 +15,25 @@ module LeapCli; module Commands
c.flag 'print', :desc => 'What attributes to print (optional)'
c.switch 'disabled', :desc => 'Include disabled nodes in the list.', :negatable => false
c.action do |global_options,options,args|
+ # don't rely on default manager(), because we want to pass custom options to load()
+ manager = LeapCli::Config::Manager.new
if global_options[:color]
colors = ['cyan', 'white']
else
colors = [nil, nil]
end
puts
- if options['disabled']
- manager.load(:include_disabled => true) # reload, with disabled nodes
- end
+ manager.load(:include_disabled => options['disabled'], :continue_on_error => true)
if options['print']
print_node_properties(manager.filter(args), options['print'])
else
if args.any?
NodeTable.new(manager.filter(args), colors).run
else
- TagTable.new('SERVICES', manager.services, colors).run
- TagTable.new('TAGS', manager.tags, colors).run
- NodeTable.new(manager.nodes, colors).run
+ environment = LeapCli.leapfile.environment || '_all_'
+ TagTable.new('SERVICES', manager.env(environment).services, colors).run
+ TagTable.new('TAGS', manager.env(environment).tags, colors).run
+ NodeTable.new(manager.filter(), colors).run
end
end
end
@@ -41,11 +42,9 @@ module LeapCli; module Commands
private
def self.print_node_properties(nodes, properties)
- node_list = manager.nodes
properties = properties.split(',')
max_width = nodes.keys.inject(0) {|max,i| [i.size,max].max}
nodes.each_node do |node|
- node.evaluate
value = properties.collect{|prop|
if node[prop].nil?
"null"
@@ -68,7 +67,7 @@ module LeapCli; module Commands
@colors = colors
end
def run
- tags = @tag_list.keys.sort
+ tags = @tag_list.keys.select{|tag| tag !~ /^_/}.sort # sorted list of tags, excluding _partials
max_width = [20, (tags+[@heading]).inject(0) {|max,i| [i.size,max].max}].max
table :border => false do
row :color => @colors[0] do
@@ -76,6 +75,7 @@ module LeapCli; module Commands
column "NODES", :width => HighLine::SystemExtensions.terminal_size.first - max_width - 2, :padding => 2
end
tags.each do |tag|
+ next if @tag_list[tag].node_list.empty?
row :color => @colors[1] do
column tag
column @tag_list[tag].node_list.keys.sort.join(', ')
diff --git a/lib/leap_cli/commands/node.rb b/lib/leap_cli/commands/node.rb
index 304d86b..f1e1cf8 100644
--- a/lib/leap_cli/commands/node.rb
+++ b/lib/leap_cli/commands/node.rb
@@ -1,6 +1,4 @@
-require 'net/ssh/known_hosts'
-require 'tempfile'
-require 'ipaddr'
+autoload :IPAddr, 'ipaddr'
module LeapCli; module Commands
@@ -194,18 +192,40 @@ module LeapCli; module Commands
end
end
+ #
+ # get the public host key for a host.
+ # return SshKey object representation of the key.
+ #
+ # Only supports ecdsa or rsa host keys. ecdsa is preferred if both are available.
+ #
def get_public_key_for_ip(address, port=22)
assert_bin!('ssh-keyscan')
- output = assert_run! "ssh-keyscan -p #{port} -t ecdsa #{address}", "Could not get the public host key from #{address}:#{port}. Maybe sshd is not running?"
- line = output.split("\n").grep(/^[^#]/).first
- if line =~ /No route to host/
- bail! :failed, 'ssh-keyscan: no route to %s' % address
- elsif line =~ /no hostkey alg/
- bail! :failed, 'ssh-keyscan: no hostkey alg (must be missing an ecdsa public host key)'
+ output = assert_run! "ssh-keyscan -p #{port} #{address}", "Could not get the public host key from #{address}:#{port}. Maybe sshd is not running?"
+ if output.empty?
+ bail! :failed, "ssh-keyscan returned empty output."
+ end
+
+ # key arrays [ip, key_type, public_key]
+ rsa_key = nil
+ ecdsa_key = nil
+
+ lines = output.split("\n").grep(/^[^#]/)
+ lines.each do |line|
+ if line =~ /No route to host/
+ bail! :failed, 'ssh-keyscan: no route to %s' % address
+ elsif line =~ / ssh-rsa /
+ rsa_key = line.split(' ')
+ elsif line =~ / ecdsa-sha2-nistp256 /
+ ecdsa_key = line.split(' ')
+ end
+ end
+
+ if rsa_key.nil? && ecdsa_key.nil?
+ bail! "ssh-keyscan got zero host keys back! Output was: #{output}"
+ else
+ key = ecdsa_key || rsa_key
+ return SshKey.load(key[2], key[1])
end
- assert! line, "Got zero host keys back!"
- ip, key_type, public_key = line.split(' ')
- return SshKey.load(public_key, key_type)
end
def is_node_alive(node, options)
diff --git a/lib/leap_cli/commands/pre.rb b/lib/leap_cli/commands/pre.rb
index 4b62b5b..7a64c15 100644
--- a/lib/leap_cli/commands/pre.rb
+++ b/lib/leap_cli/commands/pre.rb
@@ -21,7 +21,7 @@ module LeapCli; module Commands
switch :yes, :negatable => false
desc 'Enable debugging library (leap_cli development only)'
- switch :debug, :negatable => false
+ switch [:d, :debug], :negatable => false
desc 'Disable colors in output'
default_value true
@@ -31,12 +31,7 @@ module LeapCli; module Commands
#
# set verbosity
#
- LeapCli.log_level = global[:verbose].to_i
- if LeapCli.log_level > 1
- ENV['GLI_DEBUG'] = "true"
- else
- ENV['GLI_DEBUG'] = "false"
- end
+ LeapCli.set_log_level(global[:verbose].to_i)
#
# load Leapfile
@@ -53,13 +48,6 @@ module LeapCli; module Commands
bail! { log :missing, "platform directory '#{Path.platform}'" }
end
- if LeapCli.leapfile.platform_branch && LeapCli::Util.is_git_directory?(Path.platform)
- branch = LeapCli::Util.current_git_branch(Path.platform)
- if branch != LeapCli.leapfile.platform_branch
- bail! "Wrong branch for #{Path.platform}. Was '#{branch}', should be '#{LeapCli.leapfile.platform_branch}'. Edit Leapfile to disable this check."
- end
- end
-
#
# set log file
#
@@ -68,18 +56,7 @@ module LeapCli; module Commands
log_version
LeapCli.log_in_color = global[:color]
- #
- # load all the nodes everything
- #
- manager
-
- #
- # check requirements
- #
- REQUIREMENTS.each do |key|
- assert_config! key
- end
-
+ true
end
private
diff --git a/lib/leap_cli/commands/shell.rb b/lib/leap_cli/commands/shell.rb
index 2ccb3de..2138e9d 100644
--- a/lib/leap_cli/commands/shell.rb
+++ b/lib/leap_cli/commands/shell.rb
@@ -3,8 +3,10 @@ module LeapCli; module Commands
desc 'Log in to the specified node with an interactive shell.'
arg_name 'NAME' #, :optional => false, :multiple => false
command :ssh do |c|
+ c.flag 'ssh', :desc => "Pass through raw options to ssh (e.g. --ssh '-F ~/sshconfig')"
+ c.flag 'port', :desc => 'Override ssh port for remote host'
c.action do |global_options,options,args|
- exec_ssh(:ssh, args)
+ exec_ssh(:ssh, options, args)
end
end
@@ -12,7 +14,7 @@ module LeapCli; module Commands
arg_name 'NAME'
command :mosh do |c|
c.action do |global_options,options,args|
- exec_ssh(:mosh, args)
+ exec_ssh(:mosh, options, args)
end
end
@@ -44,8 +46,9 @@ module LeapCli; module Commands
private
- def exec_ssh(cmd, args)
+ 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.
@@ -65,7 +68,13 @@ module LeapCli; module Commands
elsif LeapCli.log_level >= 2
options << "-v"
end
- ssh = "ssh -l #{username} -p #{node.ssh.port} #{options.join(' ')}"
+ if cli_options[:port]
+ port = cli_options[:port]
+ end
+ if cli_options[:ssh]
+ options << cli_options[:ssh]
+ end
+ ssh = "ssh -l #{username} -p #{port} #{options.join(' ')}"
if cmd == :ssh
command = "#{ssh} #{node.domain.full}"
elsif cmd == :mosh
diff --git a/lib/leap_cli/commands/test.rb b/lib/leap_cli/commands/test.rb
index 2584a69..2f146b7 100644
--- a/lib/leap_cli/commands/test.rb
+++ b/lib/leap_cli/commands/test.rb
@@ -33,9 +33,9 @@ module LeapCli; module Commands
def test_cmd(options)
if options[:continue]
- "#{PUPPET_DESTINATION}/bin/run_tests --continue"
+ "#{Leap::Platform.leap_dir}/bin/run_tests --continue"
else
- "#{PUPPET_DESTINATION}/bin/run_tests"
+ "#{Leap::Platform.leap_dir}/bin/run_tests"
end
end
diff --git a/lib/leap_cli/commands/user.rb b/lib/leap_cli/commands/user.rb
index d7c21db..6c33878 100644
--- a/lib/leap_cli/commands/user.rb
+++ b/lib/leap_cli/commands/user.rb
@@ -1,4 +1,3 @@
-require 'gpgme'
#
# perhaps we want to verify that the key files are actually the key files we expect.
@@ -75,8 +74,10 @@ module LeapCli
if `which ssh-add`.strip.any?
`ssh-add -L 2> /dev/null`.split("\n").compact.each do |line|
key = SshKey.load(line)
- key.comment = 'ssh-agent'
- ssh_keys << key unless ssh_keys.include?(key)
+ if key
+ key.comment = 'ssh-agent'
+ ssh_keys << key unless ssh_keys.include?(key)
+ end
end
end
ssh_keys.compact!
@@ -98,13 +99,20 @@ module LeapCli
# let the the user choose among the gpg public keys that we encounter, or just pick the key if there is only one.
#
def pick_pgp_key
+ begin
+ return unless `which gpg`.strip.any?
+ require 'gpgme'
+ rescue LoadError
+ return
+ end
+
secret_keys = GPGME::Key.find(:secret)
if secret_keys.empty?
log "Skipping OpenPGP setup because I could not find any OpenPGP keys for you"
return nil
end
- assert_bin! 'gpg'
+ secret_keys.select!{|key| !key.expired}
if secret_keys.length > 1
key_index = numbered_choice_menu('Choose your OpenPGP public key', secret_keys) do |key, i|
diff --git a/lib/leap_cli/commands/vagrant.rb b/lib/leap_cli/commands/vagrant.rb
index 5219161..41fda03 100644
--- a/lib/leap_cli/commands/vagrant.rb
+++ b/lib/leap_cli/commands/vagrant.rb
@@ -1,4 +1,4 @@
-require 'ipaddr'
+autoload :IPAddr, 'ipaddr'
require 'fileutils'
module LeapCli; module Commands