leap_cli 1.7.4 → 1.8
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 +4 -4
- data/bin/leap +6 -13
- data/lib/leap/platform.rb +2 -0
- data/lib/leap_cli.rb +2 -1
- data/lib/leap_cli/bootstrap.rb +197 -0
- data/lib/leap_cli/commands/common.rb +61 -0
- data/lib/leap_cli/commands/new.rb +5 -1
- data/lib/leap_cli/commands/pre.rb +1 -66
- data/lib/leap_cli/config/environment.rb +180 -0
- data/lib/leap_cli/config/manager.rb +100 -197
- data/lib/leap_cli/config/node.rb +2 -2
- data/lib/leap_cli/config/object.rb +56 -43
- data/lib/leap_cli/config/object_list.rb +6 -3
- data/lib/leap_cli/config/provider.rb +11 -0
- data/lib/leap_cli/config/secrets.rb +14 -1
- data/lib/leap_cli/config/tag.rb +2 -2
- data/lib/leap_cli/leapfile.rb +1 -0
- data/lib/leap_cli/log.rb +1 -0
- data/lib/leap_cli/logger.rb +16 -12
- data/lib/leap_cli/markdown_document_listener.rb +3 -1
- data/lib/leap_cli/path.rb +12 -0
- data/lib/leap_cli/remote/leap_plugin.rb +9 -34
- data/lib/leap_cli/remote/puppet_plugin.rb +0 -40
- data/lib/leap_cli/remote/tasks.rb +9 -34
- data/lib/leap_cli/ssh_key.rb +5 -2
- data/lib/leap_cli/version.rb +2 -2
- metadata +5 -18
- data/lib/leap_cli/commands/ca.rb +0 -518
- data/lib/leap_cli/commands/clean.rb +0 -16
- data/lib/leap_cli/commands/compile.rb +0 -340
- data/lib/leap_cli/commands/db.rb +0 -65
- data/lib/leap_cli/commands/deploy.rb +0 -368
- data/lib/leap_cli/commands/env.rb +0 -76
- data/lib/leap_cli/commands/facts.rb +0 -100
- data/lib/leap_cli/commands/inspect.rb +0 -144
- data/lib/leap_cli/commands/list.rb +0 -132
- data/lib/leap_cli/commands/node.rb +0 -165
- data/lib/leap_cli/commands/node_init.rb +0 -169
- data/lib/leap_cli/commands/ssh.rb +0 -220
- data/lib/leap_cli/commands/test.rb +0 -74
- data/lib/leap_cli/commands/user.rb +0 -136
- data/lib/leap_cli/commands/util.rb +0 -50
- data/lib/leap_cli/commands/vagrant.rb +0 -197
@@ -1,16 +0,0 @@
|
|
1
|
-
module LeapCli
|
2
|
-
module Commands
|
3
|
-
|
4
|
-
desc 'Removes all files generated with the "compile" command.'
|
5
|
-
command :clean do |c|
|
6
|
-
c.action do |global_options,options,args|
|
7
|
-
Dir.glob(path([:hiera, '*'])).each do |file|
|
8
|
-
remove_file! file
|
9
|
-
end
|
10
|
-
remove_file! path(:authorized_keys)
|
11
|
-
remove_file! path(:known_hosts)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
end
|
16
|
-
end
|
@@ -1,340 +0,0 @@
|
|
1
|
-
require 'socket'
|
2
|
-
|
3
|
-
module LeapCli
|
4
|
-
module Commands
|
5
|
-
|
6
|
-
desc "Compile generated files."
|
7
|
-
command [:compile, :c] do |c|
|
8
|
-
c.desc 'Compiles node configuration files into hiera files used for deployment.'
|
9
|
-
c.arg_name 'ENVIRONMENT', :optional => true
|
10
|
-
c.command :all do |all|
|
11
|
-
all.action do |global_options,options,args|
|
12
|
-
environment = args.first
|
13
|
-
if !LeapCli.leapfile.environment.nil? && !environment.nil? && environment != LeapCli.leapfile.environment
|
14
|
-
bail! "You cannot specify an ENVIRONMENT argument while the environment is pinned."
|
15
|
-
end
|
16
|
-
if environment
|
17
|
-
if manager.environment_names.include?(environment)
|
18
|
-
compile_hiera_files(manager.filter([environment]), false)
|
19
|
-
else
|
20
|
-
bail! "There is no environment named `#{environment}`."
|
21
|
-
end
|
22
|
-
else
|
23
|
-
clean_export = LeapCli.leapfile.environment.nil?
|
24
|
-
compile_hiera_files(manager.filter, clean_export)
|
25
|
-
end
|
26
|
-
if file_exists?(:static_web_readme)
|
27
|
-
compile_provider_json(environment)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
c.desc "Compile a DNS zone file for your provider."
|
33
|
-
c.command :zone do |zone|
|
34
|
-
zone.action do |global_options, options, args|
|
35
|
-
compile_zone_file
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
c.desc "Compile provider.json bootstrap files for your provider."
|
40
|
-
c.command 'provider.json' do |provider|
|
41
|
-
provider.action do |global_options, options, args|
|
42
|
-
compile_provider_json
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
c.default_command :all
|
47
|
-
end
|
48
|
-
|
49
|
-
protected
|
50
|
-
|
51
|
-
#
|
52
|
-
# a "clean" export of secrets will also remove keys that are no longer used,
|
53
|
-
# but this should not be done if we are not examining all possible nodes.
|
54
|
-
#
|
55
|
-
def compile_hiera_files(nodes, clean_export)
|
56
|
-
update_compiled_ssh_configs # must come first
|
57
|
-
sanity_check(nodes)
|
58
|
-
manager.export_nodes(nodes)
|
59
|
-
manager.export_secrets(clean_export)
|
60
|
-
end
|
61
|
-
|
62
|
-
def update_compiled_ssh_configs
|
63
|
-
generate_monitor_ssh_keys
|
64
|
-
update_authorized_keys
|
65
|
-
update_known_hosts
|
66
|
-
end
|
67
|
-
|
68
|
-
def sanity_check(nodes)
|
69
|
-
# confirm that every node has a unique ip address
|
70
|
-
ips = {}
|
71
|
-
nodes.pick_fields('ip_address').each do |name, ip_address|
|
72
|
-
if ips.key?(ip_address)
|
73
|
-
bail! {
|
74
|
-
log(:fatal_error, "Every node must have its own IP address.") {
|
75
|
-
log "Nodes `#{name}` and `#{ips[ip_address]}` are both configured with `#{ip_address}`."
|
76
|
-
}
|
77
|
-
}
|
78
|
-
else
|
79
|
-
ips[ip_address] = name
|
80
|
-
end
|
81
|
-
end
|
82
|
-
# confirm that the IP address of this machine is not also used for a node.
|
83
|
-
Socket.ip_address_list.each do |addrinfo|
|
84
|
-
if !addrinfo.ipv4_private? && ips.key?(addrinfo.ip_address)
|
85
|
-
ip = addrinfo.ip_address
|
86
|
-
name = ips[ip]
|
87
|
-
bail! {
|
88
|
-
log(:fatal_error, "Something is very wrong. The `leap` command must only be run on your sysadmin machine, not on a provider node.") {
|
89
|
-
log "This machine has the same IP address (#{ip}) as node `#{name}`."
|
90
|
-
}
|
91
|
-
}
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
##
|
97
|
-
## SSH
|
98
|
-
##
|
99
|
-
|
100
|
-
#
|
101
|
-
# generates a ssh key pair that is used only by remote monitors
|
102
|
-
# to connect to nodes and run certain allowed commands.
|
103
|
-
#
|
104
|
-
# every node has the public monitor key added to their authorized
|
105
|
-
# keys, and every monitor node has a copy of the private monitor key.
|
106
|
-
#
|
107
|
-
def generate_monitor_ssh_keys
|
108
|
-
priv_key_file = path(:monitor_priv_key)
|
109
|
-
pub_key_file = path(:monitor_pub_key)
|
110
|
-
unless file_exists?(priv_key_file, pub_key_file)
|
111
|
-
ensure_dir(File.dirname(priv_key_file))
|
112
|
-
ensure_dir(File.dirname(pub_key_file))
|
113
|
-
cmd = %(ssh-keygen -N '' -C 'monitor' -t rsa -b 4096 -f '%s') % priv_key_file
|
114
|
-
assert_run! cmd
|
115
|
-
if file_exists?(priv_key_file, pub_key_file)
|
116
|
-
log :created, priv_key_file
|
117
|
-
log :created, pub_key_file
|
118
|
-
else
|
119
|
-
log :failed, 'to create monitor ssh keys'
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
#
|
125
|
-
# Compiles the authorized keys file, which gets installed on every during init.
|
126
|
-
# Afterwards, puppet installs an authorized keys file that is generated differently
|
127
|
-
# (see authorized_keys() in macros.rb)
|
128
|
-
#
|
129
|
-
def update_authorized_keys
|
130
|
-
buffer = StringIO.new
|
131
|
-
keys = Dir.glob(path([:user_ssh, '*']))
|
132
|
-
if keys.empty?
|
133
|
-
bail! "You must have at least one public SSH user key configured in order to proceed. See `leap help add-user`."
|
134
|
-
end
|
135
|
-
if file_exists?(path(:monitor_pub_key))
|
136
|
-
keys << path(:monitor_pub_key)
|
137
|
-
end
|
138
|
-
keys.sort.each do |keyfile|
|
139
|
-
ssh_type, ssh_key = File.read(keyfile).strip.split(" ")
|
140
|
-
buffer << ssh_type
|
141
|
-
buffer << " "
|
142
|
-
buffer << ssh_key
|
143
|
-
buffer << " "
|
144
|
-
buffer << Path.relative_path(keyfile)
|
145
|
-
buffer << "\n"
|
146
|
-
end
|
147
|
-
write_file!(:authorized_keys, buffer.string)
|
148
|
-
end
|
149
|
-
|
150
|
-
#
|
151
|
-
# generates the known_hosts file.
|
152
|
-
#
|
153
|
-
# we do a 'late' binding on the hostnames and ip part of the ssh pub key record in order to allow
|
154
|
-
# for the possibility that the hostnames or ip has changed in the node configuration.
|
155
|
-
#
|
156
|
-
def update_known_hosts
|
157
|
-
buffer = StringIO.new
|
158
|
-
buffer << "#\n"
|
159
|
-
buffer << "# This file is automatically generated by the command `leap`. You should NOT modify this file.\n"
|
160
|
-
buffer << "# Instead, rerun `leap node init` on whatever node is causing SSH problems.\n"
|
161
|
-
buffer << "#\n"
|
162
|
-
manager.nodes.keys.sort.each do |node_name|
|
163
|
-
node = manager.nodes[node_name]
|
164
|
-
hostnames = [node.name, node.domain.internal, node.domain.full, node.ip_address].join(',')
|
165
|
-
pub_key = read_file([:node_ssh_pub_key,node.name])
|
166
|
-
if pub_key
|
167
|
-
buffer << [hostnames, pub_key].join(' ')
|
168
|
-
buffer << "\n"
|
169
|
-
end
|
170
|
-
end
|
171
|
-
write_file!(:known_hosts, buffer.string)
|
172
|
-
end
|
173
|
-
|
174
|
-
##
|
175
|
-
## provider.json
|
176
|
-
##
|
177
|
-
|
178
|
-
#
|
179
|
-
# generates static provider.json files that can put into place
|
180
|
-
# (e.g. https://domain/provider.json) for the cases where the
|
181
|
-
# webapp domain does not match the provider's domain.
|
182
|
-
#
|
183
|
-
def compile_provider_json(environments=nil)
|
184
|
-
webapp_nodes = manager.nodes[:services => 'webapp']
|
185
|
-
write_file!(:static_web_readme, STATIC_WEB_README)
|
186
|
-
environments ||= manager.environment_names
|
187
|
-
environments.each do |env|
|
188
|
-
node = webapp_nodes[:environment => env].values.first
|
189
|
-
if node
|
190
|
-
env ||= 'default'
|
191
|
-
write_file!(
|
192
|
-
[:static_web_provider_json, env],
|
193
|
-
node['definition_files']['provider']
|
194
|
-
)
|
195
|
-
write_file!(
|
196
|
-
[:static_web_htaccess, env],
|
197
|
-
HTACCESS_FILE % {:min_version => manager.env(env).provider.client_version['min']}
|
198
|
-
)
|
199
|
-
end
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
HTACCESS_FILE = %[
|
204
|
-
<Location /provider.json>
|
205
|
-
Header set X-Minimum-Client-Version %{min_version}
|
206
|
-
</Location>
|
207
|
-
]
|
208
|
-
|
209
|
-
STATIC_WEB_README = %[
|
210
|
-
This directory contains statically rendered copies of the `provider.json` file
|
211
|
-
used by the client to "bootstrap" configure itself for use with your service
|
212
|
-
provider.
|
213
|
-
|
214
|
-
There is a separate provider.json file for each environment, although you
|
215
|
-
should only need 'production/provider.json' or, if you have no environments
|
216
|
-
configured, 'default/provider.json'.
|
217
|
-
|
218
|
-
To clarify, this is the public `provider.json` file used by the client, not the
|
219
|
-
`provider.json` file that is used to configure the provider.
|
220
|
-
|
221
|
-
The provider.json file must be available at `https://domain/provider.json`
|
222
|
-
(unless this provider is included in the list of providers which are pre-
|
223
|
-
seeded in client).
|
224
|
-
|
225
|
-
This provider.json file can be served correctly in one of three ways:
|
226
|
-
|
227
|
-
(1) If the property webapp.domain is not configured, then the web app will be
|
228
|
-
installed at https://domain/ and it will handle serving the provider.json file.
|
229
|
-
|
230
|
-
(2) If one or more nodes have the 'static' service configured for the provider's
|
231
|
-
domain, then these 'static' nodes will correctly serve provider.json.
|
232
|
-
|
233
|
-
(3) Otherwise, you must copy the provider.json file to your web
|
234
|
-
server and make it available at '/provider.json'. The example htaccess
|
235
|
-
file shows what header options should be sent by the web server
|
236
|
-
with the response.
|
237
|
-
|
238
|
-
This directory is needed for method (3), but not for methods (1) or (2).
|
239
|
-
|
240
|
-
This directory has been created by the command `leap compile provider.json`.
|
241
|
-
Once created, it will be kept up to date everytime you compile. You may safely
|
242
|
-
remove this directory if you don't use it.
|
243
|
-
]
|
244
|
-
|
245
|
-
##
|
246
|
-
##
|
247
|
-
## ZONE FILE
|
248
|
-
##
|
249
|
-
|
250
|
-
def relative_hostname(fqdn)
|
251
|
-
@domain_regexp ||= /\.?#{Regexp.escape(provider.domain)}$/
|
252
|
-
fqdn.sub(@domain_regexp, '')
|
253
|
-
end
|
254
|
-
|
255
|
-
#
|
256
|
-
# serial is any number less than 2^32 (4294967296)
|
257
|
-
#
|
258
|
-
def compile_zone_file
|
259
|
-
hosts_seen = {}
|
260
|
-
f = $stdout
|
261
|
-
f.puts ZONE_HEADER % {:domain => provider.domain, :ns => provider.domain, :contact => provider.contacts.default.first.sub('@','.')}
|
262
|
-
max_width = manager.nodes.values.inject(0) {|max, node| [max, relative_hostname(node.domain.full).length].max }
|
263
|
-
put_line = lambda do |host, line|
|
264
|
-
host = '@' if host == ''
|
265
|
-
f.puts("%-#{max_width}s %s" % [host, line])
|
266
|
-
end
|
267
|
-
|
268
|
-
f.puts ORIGIN_HEADER
|
269
|
-
# 'A' records for primary domain
|
270
|
-
manager.nodes[:environment => '!local'].each_node do |node|
|
271
|
-
if node.dns['aliases'] && node.dns.aliases.include?(provider.domain)
|
272
|
-
put_line.call "", "IN A #{node.ip_address}"
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
# NS records
|
277
|
-
if provider['dns'] && provider.dns['nameservers']
|
278
|
-
provider.dns.nameservers.each do |ns|
|
279
|
-
put_line.call "", "IN NS #{ns}."
|
280
|
-
end
|
281
|
-
end
|
282
|
-
|
283
|
-
# all other records
|
284
|
-
manager.environment_names.each do |env|
|
285
|
-
next if env == 'local'
|
286
|
-
nodes = manager.nodes[:environment => env]
|
287
|
-
next unless nodes.any?
|
288
|
-
f.puts ENV_HEADER % (env.nil? ? 'default' : env)
|
289
|
-
nodes.each_node do |node|
|
290
|
-
if node.dns.public
|
291
|
-
hostname = relative_hostname(node.domain.full)
|
292
|
-
put_line.call relative_hostname(node.domain.full), "IN A #{node.ip_address}"
|
293
|
-
end
|
294
|
-
if node.dns['aliases']
|
295
|
-
node.dns.aliases.each do |host_alias|
|
296
|
-
if host_alias != node.domain.full && host_alias != provider.domain
|
297
|
-
put_line.call relative_hostname(host_alias), "IN A #{node.ip_address}"
|
298
|
-
end
|
299
|
-
end
|
300
|
-
end
|
301
|
-
if node.services.include? 'mx'
|
302
|
-
put_line.call relative_hostname(node.domain.full_suffix), "IN MX 10 #{relative_hostname(node.domain.full)}"
|
303
|
-
end
|
304
|
-
end
|
305
|
-
end
|
306
|
-
end
|
307
|
-
|
308
|
-
ENV_HEADER = %[
|
309
|
-
;;
|
310
|
-
;; ENVIRONMENT %s
|
311
|
-
;;
|
312
|
-
|
313
|
-
]
|
314
|
-
|
315
|
-
ZONE_HEADER = %[
|
316
|
-
;;
|
317
|
-
;; BIND data file for %{domain}
|
318
|
-
;;
|
319
|
-
|
320
|
-
$TTL 600
|
321
|
-
$ORIGIN %{domain}.
|
322
|
-
|
323
|
-
@ IN SOA %{ns}. %{contact}. (
|
324
|
-
0000 ; serial
|
325
|
-
7200 ; refresh ( 24 hours)
|
326
|
-
3600 ; retry ( 2 hours)
|
327
|
-
1209600 ; expire (1000 hours)
|
328
|
-
600 ) ; minimum ( 2 days)
|
329
|
-
;
|
330
|
-
]
|
331
|
-
|
332
|
-
ORIGIN_HEADER = %[
|
333
|
-
;;
|
334
|
-
;; ZONE ORIGIN
|
335
|
-
;;
|
336
|
-
|
337
|
-
]
|
338
|
-
|
339
|
-
end
|
340
|
-
end
|
data/lib/leap_cli/commands/db.rb
DELETED
@@ -1,65 +0,0 @@
|
|
1
|
-
module LeapCli; module Commands
|
2
|
-
|
3
|
-
desc 'Database commands.'
|
4
|
-
command :db do |db|
|
5
|
-
db.desc 'Destroy one or more databases. If present, limit to FILTER nodes. For example `leap db destroy --db sessions,tokens testing`.'
|
6
|
-
db.arg_name 'FILTER', :optional => true
|
7
|
-
db.command :destroy do |destroy|
|
8
|
-
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
|
9
|
-
destroy.action do |global_options,options,args|
|
10
|
-
dbs = (options[:db]||"").split(',')
|
11
|
-
bail!('No databases specified') if dbs.empty?
|
12
|
-
nodes = manager.filter(args)
|
13
|
-
if nodes.any?
|
14
|
-
nodes = nodes[:services => 'couchdb']
|
15
|
-
end
|
16
|
-
if nodes.any?
|
17
|
-
unless global_options[:yes]
|
18
|
-
if dbs.include?('all')
|
19
|
-
say 'You are about to permanently destroy all database data for nodes [%s].' % nodes.keys.join(', ')
|
20
|
-
else
|
21
|
-
say 'You are about to permanently destroy databases [%s] for nodes [%s].' % [dbs.join(', '), nodes.keys.join(', ')]
|
22
|
-
end
|
23
|
-
bail! unless agree("Continue? ")
|
24
|
-
end
|
25
|
-
if dbs.include?('all')
|
26
|
-
destroy_all_dbs(nodes)
|
27
|
-
else
|
28
|
-
destroy_dbs(nodes, dbs)
|
29
|
-
end
|
30
|
-
say 'You must run `leap deploy` in order to create the databases again.'
|
31
|
-
else
|
32
|
-
say 'No nodes'
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def destroy_all_dbs(nodes)
|
41
|
-
ssh_connect(nodes) do |ssh|
|
42
|
-
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"')
|
43
|
-
ssh.run('grep ^seq_dir /etc/leap/tapicero.yaml | cut -f2 -d\" | xargs rm -rv')
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def destroy_dbs(nodes, dbs)
|
48
|
-
nodes.each_node do |node|
|
49
|
-
ssh_connect(node) do |ssh|
|
50
|
-
dbs.each do |db|
|
51
|
-
ssh.run(DESTROY_DB_COMMAND % {:db => db})
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
DESTROY_DB_COMMAND = %{
|
58
|
-
if [ 200 = `curl -ns -w "%%{http_code}" -X GET "127.0.0.1:5984/%{db}" -o /dev/null` ]; then
|
59
|
-
echo "Result from DELETE /%{db}:" `curl -ns -X DELETE "127.0.0.1:5984/%{db}"`;
|
60
|
-
else
|
61
|
-
echo "Skipping db '%{db}': it does not exist or has already been deleted.";
|
62
|
-
fi
|
63
|
-
}
|
64
|
-
|
65
|
-
end; end
|
@@ -1,368 +0,0 @@
|
|
1
|
-
require 'etc'
|
2
|
-
|
3
|
-
module LeapCli
|
4
|
-
module Commands
|
5
|
-
|
6
|
-
desc 'Apply recipes to a node or set of nodes.'
|
7
|
-
long_desc 'The FILTER can be the name of a node, service, or tag.'
|
8
|
-
arg_name 'FILTER'
|
9
|
-
command [:deploy, :d] do |c|
|
10
|
-
|
11
|
-
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.',
|
12
|
-
:negatable => false
|
13
|
-
|
14
|
-
c.switch :sync, :desc => "Sync files, but don't actually apply recipes.", :negatable => false
|
15
|
-
|
16
|
-
c.switch :force, :desc => 'Deploy even if there is a lockfile.', :negatable => false
|
17
|
-
|
18
|
-
c.switch :downgrade, :desc => 'Allows deploy to run with an older platform version.', :negatable => false
|
19
|
-
|
20
|
-
c.switch :dev, :desc => "Development mode: don't run 'git submodule update' before deploy.", :negatable => false
|
21
|
-
|
22
|
-
c.flag :tags, :desc => 'Specify tags to pass through to puppet (overriding the default).',
|
23
|
-
:arg_name => 'TAG[,TAG]'
|
24
|
-
|
25
|
-
c.flag :port, :desc => 'Override the default SSH port.',
|
26
|
-
:arg_name => 'PORT'
|
27
|
-
|
28
|
-
c.flag :ip, :desc => 'Override the default SSH IP address.',
|
29
|
-
:arg_name => 'IPADDRESS'
|
30
|
-
|
31
|
-
c.action do |global,options,args|
|
32
|
-
|
33
|
-
if options[:dev] != true
|
34
|
-
init_submodules
|
35
|
-
end
|
36
|
-
|
37
|
-
nodes = manager.filter!(args, :disabled => false)
|
38
|
-
if nodes.size > 1
|
39
|
-
say "Deploying to these nodes: #{nodes.keys.join(', ')}"
|
40
|
-
if !global[:yes] && !agree("Continue? ")
|
41
|
-
quit! "OK. Bye."
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
environments = nodes.field('environment').uniq
|
46
|
-
if environments.empty?
|
47
|
-
environments = [nil]
|
48
|
-
end
|
49
|
-
environments.each do |env|
|
50
|
-
check_platform_pinning(env, global)
|
51
|
-
end
|
52
|
-
# compile hiera files for all the nodes in every environment that is
|
53
|
-
# being deployed and only those environments.
|
54
|
-
compile_hiera_files(manager.filter(environments), false)
|
55
|
-
# update server certificates if needed
|
56
|
-
update_certificates(nodes)
|
57
|
-
|
58
|
-
ssh_connect(nodes, connect_options(options)) do |ssh|
|
59
|
-
ssh.leap.log :checking, 'node' do
|
60
|
-
ssh.leap.check_for_no_deploy
|
61
|
-
ssh.leap.assert_initialized
|
62
|
-
end
|
63
|
-
ssh.leap.log :synching, "configuration files" do
|
64
|
-
sync_hiera_config(ssh)
|
65
|
-
sync_support_files(ssh)
|
66
|
-
end
|
67
|
-
ssh.leap.log :synching, "puppet manifests" do
|
68
|
-
sync_puppet_files(ssh)
|
69
|
-
end
|
70
|
-
unless options[:sync]
|
71
|
-
ssh.leap.log :applying, "puppet" do
|
72
|
-
ssh.puppet.apply(:verbosity => [LeapCli.log_level,5].min,
|
73
|
-
:tags => tags(options),
|
74
|
-
:force => options[:force],
|
75
|
-
:info => deploy_info,
|
76
|
-
:downgrade => options[:downgrade]
|
77
|
-
)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
if !Util.exit_status.nil? && Util.exit_status != 0
|
82
|
-
log :warning, "puppet did not finish successfully."
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
desc 'Display recent deployment history for a set of nodes.'
|
88
|
-
long_desc 'The FILTER can be the name of a node, service, or tag.'
|
89
|
-
arg_name 'FILTER'
|
90
|
-
command [:history, :h] do |c|
|
91
|
-
c.flag :port, :desc => 'Override the default SSH port.',
|
92
|
-
:arg_name => 'PORT'
|
93
|
-
c.flag :ip, :desc => 'Override the default SSH IP address.',
|
94
|
-
:arg_name => 'IPADDRESS'
|
95
|
-
c.action do |global,options,args|
|
96
|
-
nodes = manager.filter!(args)
|
97
|
-
ssh_connect(nodes, connect_options(options)) do |ssh|
|
98
|
-
ssh.leap.history
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
private
|
104
|
-
|
105
|
-
def forcible_prompt(forced, msg, prompt)
|
106
|
-
say(msg)
|
107
|
-
if forced
|
108
|
-
log :warning, "continuing anyway because of --force"
|
109
|
-
else
|
110
|
-
say "hint: use --force to skip this prompt."
|
111
|
-
quit!("OK. Bye.") unless agree(prompt)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
#
|
116
|
-
# The currently activated provider.json could have loaded some pinning
|
117
|
-
# information for the platform. If this is the case, refuse to deploy
|
118
|
-
# if there is a mismatch.
|
119
|
-
#
|
120
|
-
# For example:
|
121
|
-
#
|
122
|
-
# "platform": {
|
123
|
-
# "branch": "develop"
|
124
|
-
# "version": "1.0..99"
|
125
|
-
# "commit": "e1d6280e0a8c565b7fb1a4ed3969ea6fea31a5e2..HEAD"
|
126
|
-
# }
|
127
|
-
#
|
128
|
-
def check_platform_pinning(environment, global_options)
|
129
|
-
provider = manager.env(environment).provider
|
130
|
-
return unless provider['platform']
|
131
|
-
|
132
|
-
if environment.nil? || environment == 'default'
|
133
|
-
provider_json = 'provider.json'
|
134
|
-
else
|
135
|
-
provider_json = 'provider.' + environment + '.json'
|
136
|
-
end
|
137
|
-
|
138
|
-
# can we have json schema verification already?
|
139
|
-
unless provider.platform.is_a? Hash
|
140
|
-
bail!('`platform` attribute in #{provider_json} must be a hash (was %s).' % provider.platform.inspect)
|
141
|
-
end
|
142
|
-
|
143
|
-
# check version
|
144
|
-
if provider.platform['version']
|
145
|
-
if !Leap::Platform.version_in_range?(provider.platform.version)
|
146
|
-
forcible_prompt(
|
147
|
-
global_options[:force],
|
148
|
-
"The platform is pinned to a version range of '#{provider.platform.version}' "+
|
149
|
-
"by the `platform.version` property in #{provider_json}, but the platform "+
|
150
|
-
"(#{Path.platform}) has version #{Leap::Platform.version}.",
|
151
|
-
"Do you really want to deploy from the wrong version? "
|
152
|
-
)
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
# check branch
|
157
|
-
if provider.platform['branch']
|
158
|
-
if !is_git_directory?(Path.platform)
|
159
|
-
forcible_prompt(
|
160
|
-
global_options[:force],
|
161
|
-
"The platform is pinned to a particular branch by the `platform.branch` property "+
|
162
|
-
"in #{provider_json}, but the platform directory (#{Path.platform}) is not a git repository.",
|
163
|
-
"Do you really want to deploy anyway? "
|
164
|
-
)
|
165
|
-
end
|
166
|
-
unless provider.platform.branch == current_git_branch(Path.platform)
|
167
|
-
forcible_prompt(
|
168
|
-
global_options[:force],
|
169
|
-
"The platform is pinned to branch '#{provider.platform.branch}' by the `platform.branch` property "+
|
170
|
-
"in #{provider_json}, but the current branch is '#{current_git_branch(Path.platform)}' " +
|
171
|
-
"(for directory '#{Path.platform}')",
|
172
|
-
"Do you really want to deploy from the wrong branch? "
|
173
|
-
)
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
# check commit
|
178
|
-
if provider.platform['commit']
|
179
|
-
if !is_git_directory?(Path.platform)
|
180
|
-
forcible_prompt(
|
181
|
-
global_options[:force],
|
182
|
-
"The platform is pinned to a particular commit range by the `platform.commit` property "+
|
183
|
-
"in #{provider_json}, but the platform directory (#{Path.platform}) is not a git repository.",
|
184
|
-
"Do you really want to deploy anyway? "
|
185
|
-
)
|
186
|
-
end
|
187
|
-
current_commit = current_git_commit(Path.platform)
|
188
|
-
Dir.chdir(Path.platform) do
|
189
|
-
commit_range = assert_run!("git log --pretty='format:%H' '#{provider.platform.commit}'",
|
190
|
-
"The platform is pinned to a particular commit range by the `platform.commit` property "+
|
191
|
-
"in #{provider_json}, but git was not able to find commits in the range specified "+
|
192
|
-
"(#{provider.platform.commit}).")
|
193
|
-
commit_range = commit_range.split("\n")
|
194
|
-
if !commit_range.include?(current_commit) &&
|
195
|
-
provider.platform.commit.split('..').first != current_commit
|
196
|
-
forcible_prompt(
|
197
|
-
global_options[:force],
|
198
|
-
"The platform is pinned via the `platform.commit` property in #{provider_json} " +
|
199
|
-
"to a commit in the range #{provider.platform.commit}, but the current HEAD " +
|
200
|
-
"(#{current_commit}) is not in that range.",
|
201
|
-
"Do you really want to deploy from the wrong commit? "
|
202
|
-
)
|
203
|
-
end
|
204
|
-
end
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
def sync_hiera_config(ssh)
|
209
|
-
ssh.rsync.update do |server|
|
210
|
-
node = manager.node(server.host)
|
211
|
-
hiera_file = Path.relative_path([:hiera, node.name])
|
212
|
-
ssh.leap.log hiera_file + ' -> ' + node.name + ':' + Leap::Platform.hiera_path
|
213
|
-
{
|
214
|
-
:source => hiera_file,
|
215
|
-
:dest => Leap::Platform.hiera_path,
|
216
|
-
:flags => "-rltp --chmod=u+rX,go-rwx"
|
217
|
-
}
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
#
|
222
|
-
# sync various support files.
|
223
|
-
#
|
224
|
-
def sync_support_files(ssh)
|
225
|
-
dest_dir = Leap::Platform.files_dir
|
226
|
-
custom_files = build_custom_file_list
|
227
|
-
ssh.rsync.update do |server|
|
228
|
-
node = manager.node(server.host)
|
229
|
-
files_to_sync = node.file_paths.collect {|path| Path.relative_path(path, Path.provider) }
|
230
|
-
files_to_sync += custom_files
|
231
|
-
if files_to_sync.any?
|
232
|
-
ssh.leap.log(files_to_sync.join(', ') + ' -> ' + node.name + ':' + dest_dir)
|
233
|
-
{
|
234
|
-
:chdir => Path.named_path(:files_dir),
|
235
|
-
:source => ".",
|
236
|
-
:dest => dest_dir,
|
237
|
-
:excludes => "*",
|
238
|
-
:includes => calculate_includes_from_files(files_to_sync, '/files'),
|
239
|
-
:flags => "-rltp --chmod=u+rX,go-rwx --relative --delete --delete-excluded --copy-links"
|
240
|
-
}
|
241
|
-
else
|
242
|
-
nil
|
243
|
-
end
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
def sync_puppet_files(ssh)
|
248
|
-
ssh.rsync.update do |server|
|
249
|
-
ssh.leap.log(Path.platform + '/[bin,tests,puppet] -> ' + server.host + ':' + Leap::Platform.leap_dir)
|
250
|
-
{
|
251
|
-
:dest => Leap::Platform.leap_dir,
|
252
|
-
:source => '.',
|
253
|
-
:chdir => Path.platform,
|
254
|
-
:excludes => '*',
|
255
|
-
:includes => ['/bin', '/bin/**', '/puppet', '/puppet/**', '/tests', '/tests/**'],
|
256
|
-
:flags => "-rlt --relative --delete --copy-links"
|
257
|
-
}
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
#
|
262
|
-
# ensure submodules are up to date, if the platform is a git
|
263
|
-
# repository.
|
264
|
-
#
|
265
|
-
def init_submodules
|
266
|
-
return unless is_git_directory?(Path.platform)
|
267
|
-
Dir.chdir Path.platform do
|
268
|
-
assert_run! "git submodule sync"
|
269
|
-
statuses = assert_run! "git submodule status"
|
270
|
-
statuses.strip.split("\n").each do |status_line|
|
271
|
-
if status_line =~ /^[\+-]/
|
272
|
-
submodule = status_line.split(' ')[1]
|
273
|
-
log "Updating submodule #{submodule}"
|
274
|
-
assert_run! "git submodule update --init #{submodule}"
|
275
|
-
end
|
276
|
-
end
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
#
|
281
|
-
# converts an array of file paths into an array
|
282
|
-
# suitable for --include of rsync
|
283
|
-
#
|
284
|
-
# if set, `prefix` is stripped off.
|
285
|
-
#
|
286
|
-
def calculate_includes_from_files(files, prefix=nil)
|
287
|
-
return nil unless files and files.any?
|
288
|
-
|
289
|
-
# prepend '/' (kind of like ^ for rsync)
|
290
|
-
includes = files.collect {|file| file =~ /^\// ? file : '/' + file }
|
291
|
-
|
292
|
-
# include all sub files of specified directories
|
293
|
-
includes.size.times do |i|
|
294
|
-
if includes[i] =~ /\/$/
|
295
|
-
includes << includes[i] + '**'
|
296
|
-
end
|
297
|
-
end
|
298
|
-
|
299
|
-
# include all parent directories (required because of --exclude '*')
|
300
|
-
includes.size.times do |i|
|
301
|
-
path = File.dirname(includes[i])
|
302
|
-
while(path != '/')
|
303
|
-
includes << path unless includes.include?(path)
|
304
|
-
path = File.dirname(path)
|
305
|
-
end
|
306
|
-
end
|
307
|
-
|
308
|
-
if prefix
|
309
|
-
includes.map! {|path| path.sub(/^#{Regexp.escape(prefix)}\//, '/')}
|
310
|
-
end
|
311
|
-
|
312
|
-
return includes
|
313
|
-
end
|
314
|
-
|
315
|
-
def tags(options)
|
316
|
-
if options[:tags]
|
317
|
-
tags = options[:tags].split(',')
|
318
|
-
else
|
319
|
-
tags = Leap::Platform.default_puppet_tags.dup
|
320
|
-
end
|
321
|
-
tags << 'leap_slow' unless options[:fast]
|
322
|
-
tags.join(',')
|
323
|
-
end
|
324
|
-
|
325
|
-
#
|
326
|
-
# a provider might have various customization files that should be sync'ed to the server.
|
327
|
-
# this method builds that list of files to sync.
|
328
|
-
#
|
329
|
-
def build_custom_file_list
|
330
|
-
custom_files = []
|
331
|
-
Leap::Platform.paths.keys.grep(/^custom_/).each do |path|
|
332
|
-
if file_exists?(path)
|
333
|
-
relative_path = Path.relative_path(path, Path.provider)
|
334
|
-
if dir_exists?(path)
|
335
|
-
custom_files << relative_path + '/' # rsync needs trailing slash
|
336
|
-
else
|
337
|
-
custom_files << relative_path
|
338
|
-
end
|
339
|
-
end
|
340
|
-
end
|
341
|
-
return custom_files
|
342
|
-
end
|
343
|
-
|
344
|
-
def deploy_info
|
345
|
-
info = []
|
346
|
-
info << "user: %s" % Etc.getpwuid(Process.euid).name
|
347
|
-
if is_git_directory?(Path.platform) && current_git_branch(Path.platform) != 'master'
|
348
|
-
info << "platform: %s (%s %s)" % [
|
349
|
-
Leap::Platform.version,
|
350
|
-
current_git_branch(Path.platform),
|
351
|
-
current_git_commit(Path.platform)[0..4]
|
352
|
-
]
|
353
|
-
else
|
354
|
-
info << "platform: %s" % Leap::Platform.version
|
355
|
-
end
|
356
|
-
if is_git_directory?(LEAP_CLI_BASE_DIR)
|
357
|
-
info << "leap_cli: %s (%s %s)" % [
|
358
|
-
LeapCli::VERSION,
|
359
|
-
current_git_branch(LEAP_CLI_BASE_DIR),
|
360
|
-
current_git_commit(LEAP_CLI_BASE_DIR)[0..4]
|
361
|
-
]
|
362
|
-
else
|
363
|
-
info << "leap_cli: %s" % LeapCli::VERSION
|
364
|
-
end
|
365
|
-
info.join(', ')
|
366
|
-
end
|
367
|
-
end
|
368
|
-
end
|