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