leap_cli 1.6.2 → 1.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/leap +20 -6
- data/lib/leap/platform.rb +10 -12
- data/lib/leap_cli.rb +5 -3
- data/lib/leap_cli/commands/ca.rb +46 -35
- data/lib/leap_cli/commands/compile.rb +6 -2
- data/lib/leap_cli/commands/db.rb +42 -6
- data/lib/leap_cli/commands/deploy.rb +117 -35
- data/lib/leap_cli/commands/pre.rb +49 -34
- data/lib/leap_cli/commands/ssh.rb +84 -16
- data/lib/leap_cli/commands/user.rb +1 -1
- data/lib/leap_cli/commands/vagrant.rb +38 -51
- data/lib/leap_cli/config/manager.rb +6 -3
- data/lib/leap_cli/config/object.rb +41 -2
- data/lib/leap_cli/config/object_list.rb +1 -1
- data/lib/leap_cli/config/secrets.rb +18 -7
- data/lib/leap_cli/config/sources.rb +11 -0
- data/lib/leap_cli/core_ext/deep_dup.rb +53 -0
- data/lib/leap_cli/core_ext/time.rb +86 -0
- data/lib/leap_cli/leapfile.rb +21 -10
- data/lib/leap_cli/logger.rb +5 -5
- data/lib/leap_cli/remote/leap_plugin.rb +11 -0
- data/lib/leap_cli/remote/puppet_plugin.rb +1 -1
- data/lib/leap_cli/remote/tasks.rb +3 -2
- data/lib/leap_cli/util.rb +31 -23
- data/lib/leap_cli/util/secret.rb +1 -1
- data/lib/leap_cli/version.rb +2 -2
- data/vendor/certificate_authority/lib/certificate_authority/certificate.rb +2 -2
- data/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb +2 -2
- data/vendor/certificate_authority/lib/certificate_authority/signing_request.rb +1 -1
- metadata +105 -168
@@ -9,76 +9,91 @@ module LeapCli; module Commands
|
|
9
9
|
default_value '1'
|
10
10
|
flag [:v, :verbose]
|
11
11
|
|
12
|
-
desc 'Override default log file'
|
12
|
+
desc 'Override default log file.'
|
13
13
|
arg_name 'FILE'
|
14
14
|
default_value nil
|
15
15
|
flag :log
|
16
16
|
|
17
|
-
desc 'Display version number and exit'
|
17
|
+
desc 'Display version number and exit.'
|
18
18
|
switch :version, :negatable => false
|
19
19
|
|
20
|
-
desc 'Skip prompts and assume "yes"'
|
20
|
+
desc 'Skip prompts and assume "yes".'
|
21
21
|
switch :yes, :negatable => false
|
22
22
|
|
23
|
+
desc 'Like --yes, but also skip prompts that are potentially dangerous to skip.'
|
24
|
+
switch :force, :negatable => false
|
25
|
+
|
23
26
|
desc 'Print full stack trace for exceptions and load `debugger` gem if installed.'
|
24
27
|
switch [:d, :debug], :negatable => false
|
25
28
|
|
26
|
-
desc 'Disable colors in output'
|
29
|
+
desc 'Disable colors in output.'
|
27
30
|
default_value true
|
28
31
|
switch 'color', :negatable => true
|
29
32
|
|
30
33
|
pre do |global,command,options,args|
|
31
|
-
|
34
|
+
if global[:force]
|
35
|
+
global[:yes] = true
|
36
|
+
end
|
37
|
+
initialize_leap_cli(true, global)
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
#
|
44
|
+
# available options:
|
45
|
+
# :verbose -- integer log verbosity level
|
46
|
+
# :log -- log file path
|
47
|
+
# :color -- true or false, to log in color or not.
|
48
|
+
#
|
49
|
+
def initialize_leap_cli(require_provider, options={})
|
32
50
|
# set verbosity
|
33
|
-
|
34
|
-
LeapCli.set_log_level(
|
51
|
+
options[:verbose] ||= 1
|
52
|
+
LeapCli.set_log_level(options[:verbose].to_i)
|
35
53
|
|
36
|
-
#
|
37
54
|
# load Leapfile
|
38
|
-
|
39
|
-
|
55
|
+
LeapCli.leapfile.load
|
56
|
+
if LeapCli.leapfile.valid?
|
57
|
+
Path.set_platform_path(LeapCli.leapfile.platform_directory_path)
|
58
|
+
Path.set_provider_path(LeapCli.leapfile.provider_directory_path)
|
59
|
+
if !Path.provider || !File.directory?(Path.provider)
|
60
|
+
bail! { log :missing, "provider directory '#{Path.provider}'" }
|
61
|
+
end
|
62
|
+
if !Path.platform || !File.directory?(Path.platform)
|
63
|
+
bail! { log :missing, "platform directory '#{Path.platform}'" }
|
64
|
+
end
|
65
|
+
elsif require_provider
|
40
66
|
bail! { log :missing, 'Leapfile in directory tree' }
|
41
67
|
end
|
42
|
-
Path.set_platform_path(LeapCli.leapfile.platform_directory_path)
|
43
|
-
Path.set_provider_path(LeapCli.leapfile.provider_directory_path)
|
44
|
-
if !Path.provider || !File.directory?(Path.provider)
|
45
|
-
bail! { log :missing, "provider directory '#{Path.provider}'" }
|
46
|
-
end
|
47
|
-
if !Path.platform || !File.directory?(Path.platform)
|
48
|
-
bail! { log :missing, "platform directory '#{Path.platform}'" }
|
49
|
-
end
|
50
68
|
|
51
|
-
#
|
52
69
|
# set log file
|
53
|
-
|
54
|
-
LeapCli.log_file = global[:log] || LeapCli.leapfile.log
|
70
|
+
LeapCli.log_file = options[:log] || LeapCli.leapfile.log
|
55
71
|
LeapCli::Util.log_raw(:log) { $0 + ' ' + ORIGINAL_ARGV.join(' ')}
|
56
72
|
log_version
|
57
|
-
LeapCli.log_in_color =
|
58
|
-
|
59
|
-
true
|
73
|
+
LeapCli.log_in_color = options[:color]
|
60
74
|
end
|
61
75
|
|
62
|
-
private
|
63
|
-
|
64
76
|
#
|
65
77
|
# add a log entry for the leap command and leap platform versions
|
66
78
|
#
|
67
79
|
def log_version
|
68
80
|
if LeapCli.log_level >= 2
|
69
81
|
str = "leap command v#{LeapCli::VERSION}"
|
70
|
-
|
71
|
-
|
72
|
-
|
82
|
+
if Util.is_git_directory?(LEAP_CLI_BASE_DIR)
|
83
|
+
str << " (%s %s)" % [Util.current_git_branch(LEAP_CLI_BASE_DIR),
|
84
|
+
Util.current_git_commit(LEAP_CLI_BASE_DIR)]
|
85
|
+
else
|
86
|
+
str << " (%s)" % LEAP_CLI_BASE_DIR
|
73
87
|
end
|
74
88
|
log 2, str
|
75
|
-
|
76
|
-
|
77
|
-
|
89
|
+
if LeapCli.leapfile.valid?
|
90
|
+
str = "leap platform v#{Leap::Platform.version}"
|
91
|
+
if Util.is_git_directory?(Path.platform)
|
92
|
+
str << " (%s %s)" % [Util.current_git_branch(Path.platform), Util.current_git_commit(Path.platform)]
|
93
|
+
end
|
94
|
+
log 2, str
|
78
95
|
end
|
79
|
-
log 2, str
|
80
96
|
end
|
81
97
|
end
|
82
98
|
|
83
|
-
|
84
99
|
end; end
|
@@ -35,6 +35,33 @@ module LeapCli; module Commands
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
+
desc 'Secure copy from FILE1 to FILE2. Files are specified as NODE_NAME:FILE_PATH. For local paths, omit "NODE_NAME:".'
|
39
|
+
arg_name 'FILE1 FILE2'
|
40
|
+
command :scp do |c|
|
41
|
+
c.switch :r, :desc => 'Copy recursively'
|
42
|
+
c.action do |global_options, options, args|
|
43
|
+
if args.size != 2
|
44
|
+
bail!('You must specificy both FILE1 and FILE2')
|
45
|
+
end
|
46
|
+
from, to = args
|
47
|
+
if (from !~ /:/ && to !~ /:/) || (from =~ /:/ && to =~ /:/)
|
48
|
+
bail!('One FILE must be remote and the other local.')
|
49
|
+
end
|
50
|
+
src_node_name = src_file_path = src_node = nil
|
51
|
+
dst_node_name = dst_file_path = dst_node = nil
|
52
|
+
if from =~ /:/
|
53
|
+
src_node_name, src_file_path = from.split(':')
|
54
|
+
src_node = get_node_from_args([src_node_name], :include_disabled => true)
|
55
|
+
dst_file_path = to
|
56
|
+
else
|
57
|
+
dst_node_name, dst_file_path = to.split(':')
|
58
|
+
dst_node = get_node_from_args([dst_node_name], :include_disabled => true)
|
59
|
+
src_file_path = from
|
60
|
+
end
|
61
|
+
exec_scp(options, src_node, src_file_path, dst_node, dst_file_path)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
38
65
|
protected
|
39
66
|
|
40
67
|
#
|
@@ -78,22 +105,7 @@ module LeapCli; module Commands
|
|
78
105
|
def exec_ssh(cmd, cli_options, args)
|
79
106
|
node = get_node_from_args(args, :include_disabled => true)
|
80
107
|
port = node.ssh.port
|
81
|
-
options =
|
82
|
-
"-o 'HostName=#{node.ip_address}'",
|
83
|
-
# "-o 'HostKeyAlias=#{node.name}'", << oddly incompatible with ports in known_hosts file, so we must not use this or non-standard ports break.
|
84
|
-
"-o 'GlobalKnownHostsFile=#{path(:known_hosts)}'",
|
85
|
-
"-o 'UserKnownHostsFile=/dev/null'"
|
86
|
-
]
|
87
|
-
if node.vagrant?
|
88
|
-
options << "-i #{vagrant_ssh_key_file}" # use the universal vagrant insecure key
|
89
|
-
options << "-o IdentitiesOnly=yes" # force the use of the insecure vagrant key
|
90
|
-
options << "-o 'StrictHostKeyChecking=no'" # blindly accept host key and don't save it (since userknownhostsfile is /dev/null)
|
91
|
-
else
|
92
|
-
options << "-o 'StrictHostKeyChecking=yes'"
|
93
|
-
end
|
94
|
-
if !node.supported_ssh_host_key_algorithms.empty?
|
95
|
-
options << "-o 'HostKeyAlgorithms=#{node.supported_ssh_host_key_algorithms}'"
|
96
|
-
end
|
108
|
+
options = ssh_config(node)
|
97
109
|
username = 'root'
|
98
110
|
if LeapCli.log_level >= 3
|
99
111
|
options << "-vv"
|
@@ -133,6 +145,62 @@ module LeapCli; module Commands
|
|
133
145
|
end
|
134
146
|
end
|
135
147
|
|
148
|
+
def exec_scp(cli_options, src_node, src_file_path, dst_node, dst_file_path)
|
149
|
+
node = src_node || dst_node
|
150
|
+
options = ssh_config(node)
|
151
|
+
port = node.ssh.port
|
152
|
+
username = 'root'
|
153
|
+
options << "-r" if cli_options[:r]
|
154
|
+
scp = "scp -P #{port} #{options.join(' ')}"
|
155
|
+
if src_node
|
156
|
+
command = "#{scp} #{username}@#{src_node.domain.full}:#{src_file_path} #{dst_file_path}"
|
157
|
+
elsif dst_node
|
158
|
+
command = "#{scp} #{src_file_path} #{username}@#{dst_node.domain.full}:#{dst_file_path}"
|
159
|
+
end
|
160
|
+
log 2, command
|
161
|
+
|
162
|
+
# exec the shell command in a subprocess
|
163
|
+
pid = fork { exec "#{command}" }
|
164
|
+
|
165
|
+
Signal.trap("SIGINT") do
|
166
|
+
Process.kill("KILL", pid)
|
167
|
+
Process.wait(pid)
|
168
|
+
exit(0)
|
169
|
+
end
|
170
|
+
|
171
|
+
# wait for shell to exit so we can grab the exit status
|
172
|
+
_, status = Process.waitpid2(pid)
|
173
|
+
exit(status.exitstatus)
|
174
|
+
end
|
175
|
+
|
176
|
+
#
|
177
|
+
# SSH command line -o options. See `man ssh_config`
|
178
|
+
#
|
179
|
+
# NOTES:
|
180
|
+
#
|
181
|
+
# The option 'HostKeyAlias=#{node.name}' is oddly incompatible with ports in
|
182
|
+
# known_hosts file, so we must not use this or non-standard ports break.
|
183
|
+
#
|
184
|
+
def ssh_config(node)
|
185
|
+
options = [
|
186
|
+
"-o 'HostName=#{node.ip_address}'",
|
187
|
+
"-o 'GlobalKnownHostsFile=#{path(:known_hosts)}'",
|
188
|
+
"-o 'UserKnownHostsFile=/dev/null'"
|
189
|
+
]
|
190
|
+
if node.vagrant?
|
191
|
+
options << "-i #{vagrant_ssh_key_file}" # use the universal vagrant insecure key
|
192
|
+
options << "-o IdentitiesOnly=yes" # force the use of the insecure vagrant key
|
193
|
+
options << "-o 'StrictHostKeyChecking=no'" # blindly accept host key and don't save it
|
194
|
+
# (since userknownhostsfile is /dev/null)
|
195
|
+
else
|
196
|
+
options << "-o 'StrictHostKeyChecking=yes'"
|
197
|
+
end
|
198
|
+
if !node.supported_ssh_host_key_algorithms.empty?
|
199
|
+
options << "-o 'HostKeyAlgorithms=#{node.supported_ssh_host_key_algorithms}'"
|
200
|
+
end
|
201
|
+
return options
|
202
|
+
end
|
203
|
+
|
136
204
|
def parse_tunnel_arg(arg)
|
137
205
|
if arg.count(':') == 1
|
138
206
|
node_name, remote = arg.split(':')
|
@@ -111,32 +111,23 @@ module LeapCli; module Commands
|
|
111
111
|
def vagrant_setup
|
112
112
|
assert_bin! 'vagrant', 'Vagrant is required for running local virtual machines. Run "sudo apt-get install vagrant".'
|
113
113
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
log :installing, "vagrant plugin 'sahara'"
|
126
|
-
assert_run! 'vagrant plugin install sahara'
|
127
|
-
end
|
114
|
+
if vagrant_version <= Gem::Version.new('1.0.0')
|
115
|
+
gem_path = assert_run!('vagrant gem which sahara')
|
116
|
+
if gem_path.nil? || gem_path.empty? || gem_path =~ /^ERROR/
|
117
|
+
log :installing, "vagrant plugin 'sahara'"
|
118
|
+
assert_run! 'vagrant gem install sahara -v 0.0.13'
|
119
|
+
end
|
120
|
+
else
|
121
|
+
unless assert_run!('vagrant plugin list | grep sahara | cat').chars.any?
|
122
|
+
log :installing, "vagrant plugin 'sahara'"
|
123
|
+
assert_run! 'vagrant plugin install sahara'
|
124
|
+
end
|
128
125
|
end
|
129
126
|
create_vagrant_file
|
130
127
|
end
|
131
128
|
|
132
129
|
def vagrant_version
|
133
|
-
|
134
|
-
version = case minor_version
|
135
|
-
when 1..9 then 2
|
136
|
-
when 0 then 1
|
137
|
-
else 0
|
138
|
-
end
|
139
|
-
return version
|
130
|
+
@vagrant_version ||= Gem::Version.new(assert_run!('vagrant --version').split(' ')[1])
|
140
131
|
end
|
141
132
|
|
142
133
|
def execute(cmd)
|
@@ -148,38 +139,34 @@ module LeapCli; module Commands
|
|
148
139
|
lines = []
|
149
140
|
netmask = IPAddr.new('255.255.255.255').mask(LeapCli.leapfile.vagrant_network.split('/').last).to_s
|
150
141
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
lines << %[ config.vm.customize ["modifyvm", :id, "--name", "#{node.name}"]]
|
163
|
-
lines << %[ #{leapfile.custom_vagrant_vm_line}] if leapfile.custom_vagrant_vm_line
|
164
|
-
lines << %[ end]
|
165
|
-
end
|
142
|
+
if vagrant_version <= Gem::Version.new('1.1.0')
|
143
|
+
lines << %[Vagrant::Config.run do |config|]
|
144
|
+
manager.each_node do |node|
|
145
|
+
if node.vagrant?
|
146
|
+
lines << %[ config.vm.define :#{node.name} do |config|]
|
147
|
+
lines << %[ config.vm.box = "LEAP/wheezy"]
|
148
|
+
lines << %[ config.vm.network :hostonly, "#{node.ip_address}", :netmask => "#{netmask}"]
|
149
|
+
lines << %[ config.vm.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]]
|
150
|
+
lines << %[ config.vm.customize ["modifyvm", :id, "--name", "#{node.name}"]]
|
151
|
+
lines << %[ #{leapfile.custom_vagrant_vm_line}] if leapfile.custom_vagrant_vm_line
|
152
|
+
lines << %[ end]
|
166
153
|
end
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
end
|
154
|
+
end
|
155
|
+
else
|
156
|
+
lines << %[Vagrant.configure("2") do |config|]
|
157
|
+
manager.each_node do |node|
|
158
|
+
if node.vagrant?
|
159
|
+
lines << %[ config.vm.define :#{node.name} do |config|]
|
160
|
+
lines << %[ config.vm.box = "LEAP/wheezy"]
|
161
|
+
lines << %[ config.vm.network :private_network, ip: "#{node.ip_address}"]
|
162
|
+
lines << %[ config.vm.provider "virtualbox" do |v|]
|
163
|
+
lines << %[ v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]]
|
164
|
+
lines << %[ v.name = "#{node.name}"]
|
165
|
+
lines << %[ end]
|
166
|
+
lines << %[ #{leapfile.custom_vagrant_vm_line}] if leapfile.custom_vagrant_vm_line
|
167
|
+
lines << %[ end]
|
182
168
|
end
|
169
|
+
end
|
183
170
|
end
|
184
171
|
|
185
172
|
lines << %[end]
|
@@ -133,9 +133,9 @@ module LeapCli
|
|
133
133
|
next unless ename
|
134
134
|
log 3, :loading, '%s environment...' % ename
|
135
135
|
env(ename) do |e|
|
136
|
-
e.services = load_all_json(Path.named_path([:service_env_config, '*', ename], @provider_dir), Config::Tag)
|
137
|
-
e.tags = load_all_json(Path.named_path([:tag_env_config, '*', ename], @provider_dir), Config::Tag)
|
138
|
-
e.provider = load_json( Path.named_path([:provider_env_config, ename], @provider_dir), Config::Provider)
|
136
|
+
e.services = load_all_json(Path.named_path([:service_env_config, '*', ename], @provider_dir), Config::Tag, :env => ename)
|
137
|
+
e.tags = load_all_json(Path.named_path([:tag_env_config, '*', ename], @provider_dir), Config::Tag, :env => ename)
|
138
|
+
e.provider = load_json( Path.named_path([:provider_env_config, ename], @provider_dir), Config::Provider, :env => ename)
|
139
139
|
e.services.inherit_from! env('default').services
|
140
140
|
e.tags.inherit_from! env('default').tags
|
141
141
|
e.provider.inherit_from! env('default').provider
|
@@ -315,6 +315,9 @@ module LeapCli
|
|
315
315
|
if obj
|
316
316
|
name = File.basename(filename).force_encoding('utf-8').sub(/^([^\.]+).*\.json$/,'\1')
|
317
317
|
obj['name'] ||= name
|
318
|
+
if options[:env]
|
319
|
+
obj.environment = options[:env]
|
320
|
+
end
|
318
321
|
results[name] = obj
|
319
322
|
end
|
320
323
|
end
|
@@ -10,6 +10,34 @@ require 'ya2yaml' # pure ruby yaml
|
|
10
10
|
|
11
11
|
module LeapCli
|
12
12
|
module Config
|
13
|
+
|
14
|
+
#
|
15
|
+
# A proxy for Manager that binds to a particular object
|
16
|
+
# (so that we can bind to a particular environment)
|
17
|
+
#
|
18
|
+
class ManagerBinding
|
19
|
+
def initialize(manager, object)
|
20
|
+
@manager = manager
|
21
|
+
@object = object
|
22
|
+
end
|
23
|
+
|
24
|
+
def services
|
25
|
+
@manager.env(@object.environment).services
|
26
|
+
end
|
27
|
+
|
28
|
+
def tags
|
29
|
+
@manager.env(@object.environment).tags
|
30
|
+
end
|
31
|
+
|
32
|
+
def provider
|
33
|
+
@manager.env(@object.environment).provider
|
34
|
+
end
|
35
|
+
|
36
|
+
def method_missing(*args)
|
37
|
+
@manager.send(*args)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
13
41
|
#
|
14
42
|
# This class represents the configuration for a single node, service, or tag.
|
15
43
|
# Also, all the nested hashes are also of this type.
|
@@ -19,8 +47,6 @@ module LeapCli
|
|
19
47
|
class Object < Hash
|
20
48
|
|
21
49
|
attr_reader :node
|
22
|
-
attr_reader :manager
|
23
|
-
alias :global :manager
|
24
50
|
|
25
51
|
def initialize(manager=nil, node=nil)
|
26
52
|
# keep a global pointer around to the config manager. used a lot in the eval strings and templates
|
@@ -31,6 +57,19 @@ module LeapCli
|
|
31
57
|
@node = node || self
|
32
58
|
end
|
33
59
|
|
60
|
+
def manager
|
61
|
+
ManagerBinding.new(@manager, self)
|
62
|
+
end
|
63
|
+
alias :global :manager
|
64
|
+
|
65
|
+
def environment=(e)
|
66
|
+
self.store('environment', e)
|
67
|
+
end
|
68
|
+
|
69
|
+
def environment
|
70
|
+
self['environment']
|
71
|
+
end
|
72
|
+
|
34
73
|
#
|
35
74
|
# export YAML
|
36
75
|
#
|