summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorelijah <elijah@riseup.net>2015-05-05 15:49:07 -0700
committerelijah <elijah@riseup.net>2015-05-05 15:49:07 -0700
commit6c6b5a88f18f714924530d64486cb88c02bd7ee4 (patch)
tree3a663822de4c279da8287db04f0a42cfb2c407fb
parent51195b20531c4dcdf6a76e6a9a8ef7a771cf76be (diff)
parent61fdf41087b480db12720df5d5beadd32992475a (diff)
Merge branch 'develop'
Conflicts: lib/leap_cli/commands/db.rb lib/leap_cli/commands/vagrant.rb
-rw-r--r--Gemfile30
-rw-r--r--README.md28
-rw-r--r--RELEASES.md8
-rwxr-xr-xbin/leap26
-rw-r--r--doc/leap.md2
-rw-r--r--leap_cli.gemspec21
-rw-r--r--lib/leap/platform.rb22
-rw-r--r--lib/leap_cli.rb6
-rw-r--r--lib/leap_cli/commands/ca.rb81
-rw-r--r--lib/leap_cli/commands/compile.rb8
-rw-r--r--lib/leap_cli/commands/db.rb47
-rw-r--r--lib/leap_cli/commands/deploy.rb152
-rw-r--r--lib/leap_cli/commands/pre.rb83
-rw-r--r--lib/leap_cli/commands/ssh.rb100
-rw-r--r--lib/leap_cli/commands/user.rb2
-rw-r--r--lib/leap_cli/commands/vagrant.rb87
-rw-r--r--lib/leap_cli/config/manager.rb9
-rw-r--r--lib/leap_cli/config/object.rb43
-rw-r--r--lib/leap_cli/config/object_list.rb2
-rw-r--r--lib/leap_cli/config/secrets.rb25
-rw-r--r--lib/leap_cli/core_ext/deep_dup.rb53
-rw-r--r--lib/leap_cli/core_ext/time.rb86
-rw-r--r--lib/leap_cli/leapfile.rb31
-rw-r--r--lib/leap_cli/logger.rb10
-rw-r--r--lib/leap_cli/remote/leap_plugin.rb11
-rw-r--r--lib/leap_cli/remote/puppet_plugin.rb2
-rw-r--r--lib/leap_cli/remote/tasks.rb5
-rw-r--r--lib/leap_cli/util.rb54
-rw-r--r--lib/leap_cli/version.rb4
-rw-r--r--test/leap_platform/platform.rb4
-rw-r--r--vendor/certificate_authority/lib/certificate_authority/certificate.rb4
-rw-r--r--vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb4
-rw-r--r--vendor/certificate_authority/lib/certificate_authority/signing_request.rb2
33 files changed, 749 insertions, 303 deletions
diff --git a/Gemfile b/Gemfile
index fe076b0..06618ce 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,33 +1,3 @@
source 'https://rubygems.org'
gemspec
-# #
-# # Specify support gems used that we might also develop locally.
-# #
-# # Available options:
-# #
-# # :dev_path - the development path of the gem. this path is used if running in 'development mode'.
-# #
-# # :vendor_path - where this gem is vendored. this path is used if it exists and we are running in 'production mode'
-# #
-# development_gems = {
-# 'supply_drop' => {:dev_path => '../gems/supply_drop', :vendor_path => 'vendor/supply_drop'},
-# 'certificate_authority' => {:dev_path => '../gems/certificate_authority', :vendor_path => 'vendor/certificate_authority'}
-# }
-
-# #
-# # A little bit of code to magically pick the correct gem
-# #
-
-# mode = :production
-
-# gem_root = File.dirname(__FILE__)
-# path_key = mode == :development ? :dev_path : :vendor_path
-# development_gems.each do |gem_name, options|
-# path = File.expand_path(options[path_key], gem_root)
-# if File.directory?(path)
-# gem gem_name, :path => path
-# else
-# gem gem_name
-# end
-# end
diff --git a/README.md b/README.md
index 90bce52..73613b0 100644
--- a/README.md
+++ b/README.md
@@ -100,3 +100,31 @@ other places, it is easier to create the symlink. If you run ``leap`` directly,
the command launcher that rubygems installs, leap will run in a mode that simulates
``bundle exec leap`` (i.e. only gems included in Gemfile are allowed to be loaded).
+Changes
+====================================================
+
+1.7
+
+* requires platform 0.7
+* deployment logging (see /var/log/leap)
+* compatible with new tapicero
+* selectively destroy some dbs with `leap db destroy`
+* faster apt-get update
+* added `leap scp` command
+* bug fixes
+
+1.6.2
+
+* auto generate certs on compile
+* use internal ruby md5sum for compatibility on mac
+* may override or customize tests by putting tests in `files/tests`
+* bug fixes
+
+1.6.1
+
+* requires platform 0.6
+* better `leap test run`
+* added `leap tunnel` command
+* only print stack trace if `--debug` flag was specified
+* prompt user to upgrade host ssh key if a better one exists
+* bug fixes
diff --git a/RELEASES.md b/RELEASES.md
index f8407c0..53ba0f6 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -1,3 +1,11 @@
+Version 1.7.1
+ - added 'leap scp' and 'leap tunnel'
+ - support for deploy logging and 'leap history'
+ - added --force
+ - server certs are now autogenerated as needed
+ - better mac, debian, and ruby 2.0 compatibility.
+ - many bug fixes
+
Version 1.6.1
- add environment pinning, see `leap help env`
- support both rsa and ecdsa host keys
diff --git a/bin/leap b/bin/leap
index 47dfdf9..e42b8c2 100755
--- a/bin/leap
+++ b/bin/leap
@@ -3,13 +3,20 @@
if ARGV.include?('--debug') || ARGV.include?('-d')
DEBUG=true
begin
- require 'debugger'
+ if RUBY_VERSION =~ /^2/
+ require 'byebug'
+ else
+ require 'debugger'
+ end
rescue LoadError
end
else
DEBUG=false
end
+LEAP_CLI_BASE_DIR = File.expand_path('..', File.dirname(File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__))
+ORIGINAL_ARGV = ARGV.dup
+
begin
require 'leap_cli'
rescue LoadError
@@ -24,8 +31,7 @@ rescue LoadError
# This allows you to run the command directly while developing the gem, and also lets you
# run from anywhere (I like to link 'bin/leap' to /usr/local/bin/leap).
#
- base_dir = File.expand_path('..', File.dirname(File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__))
- require File.join(base_dir, 'lib','leap_cli','load_paths')
+ require File.join(LEAP_CLI_BASE_DIR, 'lib','leap_cli','load_paths')
require 'leap_cli'
end
@@ -77,9 +83,16 @@ module LeapCli::Commands
program_desc LeapCli::SUMMARY
program_long_desc LeapCli::DESCRIPTION
- # handle --version ourselves
+ # handle --version ourselves (and not GLI)
if ARGV.grep(/--version/).any?
puts "leap #{LeapCli::VERSION}, ruby #{RUBY_VERSION}"
+ begin
+ commands_from('leap_cli/commands')
+ initialize_leap_cli(false, {:verbose => 2})
+ rescue StandardError => exc
+ puts exc.to_s
+ raise exc if DEBUG
+ end
exit(0)
end
@@ -88,9 +101,10 @@ module LeapCli::Commands
def error_message(msg)
end
- # load commands and run
+ # load commands
commands_from('leap_cli/commands')
- ORIGINAL_ARGV = ARGV.dup
+
+ # run command
begin
exit_status = run(ARGV)
exit(LeapCli::Util.exit_status || exit_status)
diff --git a/doc/leap.md b/doc/leap.md
index d735fef..f531d32 100644
--- a/doc/leap.md
+++ b/doc/leap.md
@@ -77,7 +77,7 @@ Default Value: None
## leap cert dh
-Creates a Diffie-Hellman parameter file.
+Creates a Diffie-Hellman parameter file, needed for forward secret OpenVPN ciphers. You don't need this file if you don't provide the VPN service.
diff --git a/leap_cli.gemspec b/leap_cli.gemspec
index e9215fb..6f8f8de 100644
--- a/leap_cli.gemspec
+++ b/leap_cli.gemspec
@@ -43,17 +43,16 @@ spec = Gem::Specification.new do |s|
##
# test
- s.add_development_dependency('minitest')
+ s.add_development_dependency('minitest', '~> 5.0')
#s.add_development_dependency('rdoc')
#s.add_development_dependency('aruba')
# console gems
- s.add_runtime_dependency('gli','~> 2.12.0')
- s.add_runtime_dependency('command_line_reporter')
- s.add_runtime_dependency('highline')
- s.add_runtime_dependency('paint')
- s.add_runtime_dependency('tee')
+ s.add_runtime_dependency('gli','~> 2.12')
+ s.add_runtime_dependency('command_line_reporter', '~> 3.3')
+ s.add_runtime_dependency('highline', '~> 1.6')
+ s.add_runtime_dependency('paint', '~> 0.9')
# network gems
s.add_runtime_dependency('net-ssh', '~> 2.7.0')
@@ -64,13 +63,13 @@ spec = Gem::Specification.new do |s|
# crypto gems
#s.add_runtime_dependency('certificate_authority', '>= 0.2.0')
# ^^ currently vendored
- s.add_runtime_dependency('gpgme') # not essential, but used for some minor stuff in adding sysadmins
+ # s.add_runtime_dependency('gpgme') # << does not build on debian jessie, so now optional.
+ # also, there is a ruby-gpgme package anyway.
# misc gems
- s.add_runtime_dependency('ya2yaml') # pure ruby yaml, so we can better control output. see https://github.com/afunai/ya2yaml
- s.add_runtime_dependency('json_pure') # pure ruby json, so we can better control output.
- s.add_runtime_dependency('versionomy') # compare version strings
- s.add_runtime_dependency('base32') # base32 encoding
+ s.add_runtime_dependency('ya2yaml', '~> 0.31') # pure ruby yaml, so we can better control output. see https://github.com/afunai/ya2yaml
+ s.add_runtime_dependency('json_pure', '~> 1.8') # pure ruby json, so we can better control output.
+ s.add_runtime_dependency('base32', '~> 0.3') # base32 encoding
##
## DEPENDENCIES for VENDORED GEMS
diff --git a/lib/leap/platform.rb b/lib/leap/platform.rb
index 3e1d138..36a13f8 100644
--- a/lib/leap/platform.rb
+++ b/lib/leap/platform.rb
@@ -1,5 +1,3 @@
-require 'versionomy'
-
module Leap
class Platform
@@ -34,24 +32,24 @@ module Leap
self.instance_eval(&block)
- @version ||= Versionomy.parse("0.0")
+ @version ||= Gem::Version.new("0.0")
end
def version=(version)
- @version = Versionomy.parse(version)
+ @version = Gem::Version.new(version)
end
def compatible_cli=(range)
@compatible_cli = range
- @minimum_cli_version = Versionomy.parse(range.first)
- @maximum_cli_version = Versionomy.parse(range.last)
+ @minimum_cli_version = Gem::Version.new(range.first)
+ @maximum_cli_version = Gem::Version.new(range.last)
end
#
# return true if the cli_version is compatible with this platform.
#
def compatible_with_cli?(cli_version)
- cli_version = Versionomy.parse(cli_version)
+ cli_version = Gem::Version.new(cli_version)
cli_version >= @minimum_cli_version && cli_version <= @maximum_cli_version
end
@@ -62,16 +60,16 @@ module Leap
if range.is_a? String
range = range.split('..')
end
- minimum_platform_version = Versionomy.parse(range.first)
- maximum_platform_version = Versionomy.parse(range.last)
+ minimum_platform_version = Gem::Version.new(range.first)
+ maximum_platform_version = Gem::Version.new(range.last)
@version >= minimum_platform_version && @version <= maximum_platform_version
end
def major_version
- if @version.major == 0
- "#{@version.major}.#{@version.minor}"
+ if @version.segments.first == 0
+ @version.segments[0..1].join('.')
else
- @version.major
+ @version.segments.first
end
end
diff --git a/lib/leap_cli.rb b/lib/leap_cli.rb
index f07fd25..7b22913 100644
--- a/lib/leap_cli.rb
+++ b/lib/leap_cli.rb
@@ -20,11 +20,13 @@ require 'leap_cli/version'
require 'leap_cli/exceptions'
require 'leap_cli/leapfile'
-require 'leap_cli/core_ext/hash'
require 'leap_cli/core_ext/boolean'
+require 'leap_cli/core_ext/deep_dup'
+require 'leap_cli/core_ext/hash'
+require 'leap_cli/core_ext/json'
require 'leap_cli/core_ext/nil'
require 'leap_cli/core_ext/string'
-require 'leap_cli/core_ext/json'
+require 'leap_cli/core_ext/time'
require 'leap_cli/core_ext/yaml'
require 'leap_cli/log'
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']
diff --git a/test/leap_platform/platform.rb b/test/leap_platform/platform.rb
index d93f59c..57ea980 100644
--- a/test/leap_platform/platform.rb
+++ b/test/leap_platform/platform.rb
@@ -4,8 +4,8 @@
#
Leap::Platform.define do
- self.version = "0.6.0"
- self.compatible_cli = "1.5.8".."1.99"
+ self.version = "0.7.0"
+ self.compatible_cli = "1.7.0".."1.7.99"
#
# the facter facts that should be gathered
diff --git a/vendor/certificate_authority/lib/certificate_authority/certificate.rb b/vendor/certificate_authority/lib/certificate_authority/certificate.rb
index 3fcae90..496d91e 100644
--- a/vendor/certificate_authority/lib/certificate_authority/certificate.rb
+++ b/vendor/certificate_authority/lib/certificate_authority/certificate.rb
@@ -108,9 +108,9 @@ module CertificateAuthority
end
if signing_profile["digest"].nil?
- digest = OpenSSL::Digest::Digest.new("SHA512")
+ digest = OpenSSL::Digest.new("SHA512")
else
- digest = OpenSSL::Digest::Digest.new(signing_profile["digest"])
+ digest = OpenSSL::Digest.new(signing_profile["digest"])
end
self.openssl_body = openssl_cert.sign(parent.key_material.private_key, digest)
diff --git a/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb b/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb
index e222e26..c84d588 100644
--- a/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb
+++ b/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb
@@ -59,9 +59,9 @@ module CertificateAuthority
signing_cert = OpenSSL::X509::Certificate.new(self.parent.to_pem)
if signing_profile["digest"].nil?
- digest = OpenSSL::Digest::Digest.new("SHA512")
+ digest = OpenSSL::Digest.new("SHA512")
else
- digest = OpenSSL::Digest::Digest.new(signing_profile["digest"])
+ digest = OpenSSL::Digest.new(signing_profile["digest"])
end
crl.issuer = signing_cert.subject
self.crl_body = crl.sign(self.parent.key_material.private_key, digest)
diff --git a/vendor/certificate_authority/lib/certificate_authority/signing_request.rb b/vendor/certificate_authority/lib/certificate_authority/signing_request.rb
index 72d9e2b..3584dac 100644
--- a/vendor/certificate_authority/lib/certificate_authority/signing_request.rb
+++ b/vendor/certificate_authority/lib/certificate_authority/signing_request.rb
@@ -62,7 +62,7 @@ module CertificateAuthority
opensslcsr.subject = @distinguished_name.to_x509_name
opensslcsr.public_key = @key_material.public_key
opensslcsr.attributes = @attributes unless @attributes.nil?
- opensslcsr.sign @key_material.private_key, OpenSSL::Digest::Digest.new(@digest || "SHA512")
+ opensslcsr.sign @key_material.private_key, OpenSSL::Digest.new(@digest || "SHA512")
opensslcsr
end