leap_cli 1.6.2 → 1.7.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
#
|