dev-lxc 3.2.0 → 3.3.0

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.
data/lib/dev-lxc/cli.rb CHANGED
@@ -1,537 +1,542 @@
1
- require 'erb'
2
- require "yaml"
3
- require 'dev-lxc'
4
- require 'thor'
5
-
6
- module DevLXC::CLI
7
- class DevLXC < Thor
8
-
9
- no_commands{
10
- def get_cluster(config_file=nil)
11
- config_file ||= "dev-lxc.yml"
12
- if ! File.exists?(config_file)
13
- puts "ERROR: Cluster config file '#{config_file}' does not exist."
14
- puts " Create a `./dev-lxc.yml` file or specify the path using `--config`."
15
- exit 1
16
- end
17
- cluster_config = YAML.load(IO.read(config_file))
18
- ::DevLXC::Cluster.new(cluster_config)
19
- end
20
-
21
- def print_elapsed_time(elapsed_time)
22
- printf "dev-lxc is finished. (%im %.2fs)\n", elapsed_time / 60, elapsed_time % 60
23
- end
24
- }
25
-
26
- desc "show-config", "Show calculated configuration"
27
- option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
28
- option :include_products, :type => :boolean, :desc => "Calculate required products"
29
- def show_config
30
- get_cluster(options[:config]).show_config(options[:include_products])
31
- end
32
-
33
- desc "create-base-container [BASE_CONTAINER_NAME]", "Create a base container"
34
- option :options, :aliases => "-o", :desc => "Specify additional options for the lxc create"
35
- def create_base_container(base_container_name=nil)
36
- start_time = Time.now
37
- base_container_names = %w(b-ubuntu-1204 b-ubuntu-1404 b-ubuntu-1604 b-centos-5 b-centos-6 b-centos-7)
38
- if base_container_name.nil? || ! base_container_names.include?(base_container_name)
39
- base_container_names_with_index = base_container_names.map.with_index{ |a, i| [i+1, *a]}
40
- print_table base_container_names_with_index
41
- selection = ask("Which base container do you want to create?", :limited_to => base_container_names_with_index.map{|c| c[0].to_s})
42
- base_container_name = base_container_names[selection.to_i - 1]
43
- end
44
- ::DevLXC.create_base_container(base_container_name, options[:options])
45
- puts
46
- print_elapsed_time(Time.now - start_time)
47
- end
48
-
49
- desc "init", "Provide a cluster config file"
50
- option :chef, :type => :boolean, :desc => "Standalone Chef Server"
51
- option :chef_tier, :type => :boolean, :desc => "Chef Server using Tier topology with one backend"
52
- option :chef_backend, :type => :boolean, :desc => "Chef Server using Chef Backend HA topology with three backends"
53
- option :nodes, :type => :boolean, :desc => "Node Servers"
54
- option :analytics, :type => :boolean, :desc => "Analytics Server"
55
- option :compliance, :type => :boolean, :desc => "Compliance Server"
56
- option :supermarket, :type => :boolean, :desc => "Supermarket Server"
57
- option :automate, :type => :boolean, :desc => "Automate Server"
58
- option :build_nodes, :type => :boolean, :desc => "Build Nodes"
59
- option :runners, :type => :boolean, :desc => "Runners"
60
- option :adhoc, :type => :boolean, :desc => "Adhoc Servers"
61
- option :base_container, :default => 'b-ubuntu-1404', :desc => "Specify the base container name"
62
- option :product_versions, :type => :hash, :default => {}, :desc => "Specify channel and version (default 'latest') for products [product:channel,version ...]; [product:none] removes the product from the config"
63
- option :append, :aliases => "-a", :type => :boolean, :desc => "Do not generate the global config header"
64
- option :filename, :aliases => "-f", :desc => "Write generated content to FILE rather than standard output."
65
- def init
66
- product_versions = Hash.new { |hash, key| hash[key] = { channel: 'stable', version: 'latest' } }
67
- options[:product_versions].keys.each do |product|
68
- product_versions[product][:channel], product_versions[product][:version] = options[:product_versions][product].to_s.split(',', 2)
69
- product_versions[product][:version] ||= 'latest'
70
- end
71
-
72
- dev_lxc_template = %Q(<% unless options[:append] -%>
73
- # enable_build_snapshots automatically makes container snapshots at key times during the build process
74
- # default value is `true`
75
- #enable_build_snapshots: true
76
-
77
- # base_container must be the name of an existing container
78
- base_container: <%= options[:base_container] %>
79
-
80
- # memory_per_server sets the maximum amount of user memory (including file cache) for each server.
81
- # dev-lxc will set the `memory.limit_in_bytes` cgroup for each server to apply this limit.
82
- # If no units are specified, the value is interpreted as bytes.
83
- # You can use suffixes to represent larger units - k or K for kilobytes, m or M for megabytes, and g or G for gigabytes.
84
- # The default behavior is that no limit is set.
85
- #memory_per_server: 4G
86
-
87
- # list any host directories you want mounted into the servers
88
- #mounts:
89
- # - /root/clusters root/clusters
90
-
91
- # list any SSH public keys you want added to /home/dev-lxc/.ssh/authorized_keys
92
- #ssh-keys:
93
- # - /root/clusters/id_rsa.pub
94
-
95
- # DHCP reserved (static) IPs must be selected from the IP range 10.0.3.150 - 254
96
- <% end -%>
97
- <% if options[:chef] -%>
98
-
99
- chef-server:
100
- users: # a user's password will be the same as its username
101
- - mary-admin
102
- - joe-user
103
- orgs:
104
- demo:
105
- admins:
106
- - mary-admin
107
- non-admins:
108
- - joe-user
109
- servers:
110
- chef.lxc:
111
- ipaddress: 10.0.3.203
112
- products:
113
- <%- unless product_versions['chef-server'][:channel] == 'none' -%>
114
- chef-server:
115
- channel: <%= product_versions['chef-server'][:channel] %>
116
- version: <%= product_versions['chef-server'][:version] %>
117
- <%- end -%>
118
- <%- unless product_versions['manage'][:channel] == 'none' -%>
119
- manage:
120
- channel: <%= product_versions['manage'][:channel] %>
121
- version: <%= product_versions['manage'][:version] %>
122
- <%- end -%>
123
- <%- unless product_versions['push-jobs-server'][:channel] == 'none' -%>
124
- push-jobs-server:
125
- channel: <%= product_versions['push-jobs-server'][:channel] %>
126
- version: <%= product_versions['push-jobs-server'][:version] %>
127
- <%- end -%>
128
- <%- unless product_versions['reporting'][:channel] == 'none' -%>
129
- reporting:
130
- channel: <%= product_versions['reporting'][:channel] %>
131
- version: <%= product_versions['reporting'][:version] %>
132
- <%- end -%>
133
- <% end -%>
134
- <% if options[:chef_backend] -%>
135
-
136
- chef-backend:
137
- api_fqdn: chef-ha.lxc
138
- users: # a user's password will be the same as its username
139
- - mary-admin
140
- - joe-user
141
- orgs:
142
- demo:
143
- admins:
144
- - mary-admin
145
- non-admins:
146
- - joe-user
147
- servers:
148
- chef-backend1.lxc:
149
- ipaddress: 10.0.3.208
150
- role: backend
151
- leader: true
152
- products:
153
- <%- unless product_versions['chef-backend'][:channel] == 'none' -%>
154
- chef-backend:
155
- channel: <%= product_versions['chef-backend'][:channel] %>
156
- version: <%= product_versions['chef-backend'][:version] %>
157
- <%- end -%>
158
- chef-backend2.lxc:
159
- ipaddress: 10.0.3.209
160
- role: backend
161
- products:
162
- <%- unless product_versions['chef-backend'][:channel] == 'none' -%>
163
- chef-backend:
164
- channel: <%= product_versions['chef-backend'][:channel] %>
165
- version: <%= product_versions['chef-backend'][:version] %>
166
- <%- end -%>
167
- chef-backend3.lxc:
168
- ipaddress: 10.0.3.210
169
- role: backend
170
- products:
171
- <%- unless product_versions['chef-backend'][:channel] == 'none' -%>
172
- chef-backend:
173
- channel: <%= product_versions['chef-backend'][:channel] %>
174
- version: <%= product_versions['chef-backend'][:version] %>
175
- <%- end -%>
176
- chef-frontend1.lxc:
177
- ipaddress: 10.0.3.211
178
- role: frontend
179
- bootstrap: true
180
- products:
181
- <%- unless product_versions['chef-server'][:channel] == 'none' -%>
182
- chef-server:
183
- channel: <%= product_versions['chef-server'][:channel] %>
184
- version: <%= product_versions['chef-server'][:version] %>
185
- <%- end -%>
186
- <%- unless product_versions['manage'][:channel] == 'none' -%>
187
- manage:
188
- channel: <%= product_versions['manage'][:channel] %>
189
- version: <%= product_versions['manage'][:version] %>
190
- <%- end -%>
191
- <% end -%>
192
- <% if options[:chef_tier] -%>
193
-
194
- chef-server:
195
- topology: tier
196
- api_fqdn: chef-tier.lxc
197
- users: # a user's password will be the same as its username
198
- - mary-admin
199
- - joe-user
200
- orgs:
201
- demo:
202
- admins:
203
- - mary-admin
204
- non-admins:
205
- - joe-user
206
- servers:
207
- chef-be.lxc:
208
- ipaddress: 10.0.3.201
209
- role: backend
210
- bootstrap: true
211
- products:
212
- <%- unless product_versions['chef-server'][:channel] == 'none' -%>
213
- chef-server:
214
- channel: <%= product_versions['chef-server'][:channel] %>
215
- version: <%= product_versions['chef-server'][:version] %>
216
- <%- end -%>
217
- <%- unless product_versions['push-jobs-server'][:channel] == 'none' -%>
218
- push-jobs-server:
219
- channel: <%= product_versions['push-jobs-server'][:channel] %>
220
- version: <%= product_versions['push-jobs-server'][:version] %>
221
- <%- end -%>
222
- <%- unless product_versions['reporting'][:channel] == 'none' -%>
223
- reporting:
224
- channel: <%= product_versions['reporting'][:channel] %>
225
- version: <%= product_versions['reporting'][:version] %>
226
- <%- end -%>
227
- chef-fe1.lxc:
228
- ipaddress: 10.0.3.202
229
- role: frontend
230
- products:
231
- <%- unless product_versions['chef-server'][:channel] == 'none' -%>
232
- chef-server:
233
- channel: <%= product_versions['chef-server'][:channel] %>
234
- version: <%= product_versions['chef-server'][:version] %>
235
- <%- end -%>
236
- <%- unless product_versions['manage'][:channel] == 'none' -%>
237
- manage:
238
- channel: <%= product_versions['manage'][:channel] %>
239
- version: <%= product_versions['manage'][:version] %>
240
- <%- end -%>
241
- <%- unless product_versions['push-jobs-server'][:channel] == 'none' -%>
242
- push-jobs-server:
243
- channel: <%= product_versions['push-jobs-server'][:channel] %>
244
- version: <%= product_versions['push-jobs-server'][:version] %>
245
- <%- end -%>
246
- <%- unless product_versions['reporting'][:channel] == 'none' -%>
247
- reporting:
248
- channel: <%= product_versions['reporting'][:channel] %>
249
- version: <%= product_versions['reporting'][:version] %>
250
- <%- end -%>
251
- <% end -%>
252
- <% if options[:compliance] -%>
253
-
254
- compliance:
255
- admin_user: admin # the password will be the same as the username
256
- servers:
257
- compliance.lxc:
258
- ipaddress: 10.0.3.205
259
- products:
260
- <%- unless product_versions['compliance'][:channel] == 'none' -%>
261
- compliance:
262
- channel: <%= product_versions['compliance'][:channel] %>
263
- version: <%= product_versions['compliance'][:version] %>
264
- <%- end -%>
265
- <% end -%>
266
- <% if options[:supermarket] -%>
267
-
268
- supermarket:
269
- servers:
270
- supermarket.lxc:
271
- ipaddress: 10.0.3.206
272
- products:
273
- <%- unless product_versions['supermarket'][:channel] == 'none' -%>
274
- supermarket:
275
- channel: <%= product_versions['supermarket'][:channel] %>
276
- version: <%= product_versions['supermarket'][:version] %>
277
- <%- end -%>
278
- <% end -%>
279
- <% if options[:automate] -%>
280
-
281
- automate:
282
- servers:
283
- automate.lxc:
284
- ipaddress: 10.0.3.200
285
- products:
286
- <%- unless product_versions['automate'][:channel] == 'none' -%>
287
- automate:
288
- channel: <%= product_versions['automate'][:channel] %>
289
- version: <%= product_versions['automate'][:version] %>
290
- <%- end -%>
291
- license_path: ../delivery.license
292
- chef_org: delivery
293
- enterprise_name: demo-ent
294
- <% end -%>
295
- <% if options[:build_nodes] -%>
296
-
297
- build-nodes:
298
- servers:
299
- build-node-1.lxc:
300
- products:
301
- <%- unless product_versions['chefdk'][:channel] == 'none' -%>
302
- chefdk: # downloaded only
303
- channel: <%= product_versions['chefdk'][:channel] %>
304
- version: <%= product_versions['chefdk'][:version] %>
305
- <%- end -%>
306
- <% end -%>
307
- <% if options[:runners] -%>
308
-
309
- runners:
310
- servers:
311
- runner-1.lxc:
312
- products:
313
- <%- unless product_versions['chefdk'][:channel] == 'none' -%>
314
- chefdk: # downloaded only
315
- channel: <%= product_versions['chefdk'][:channel] %>
316
- version: <%= product_versions['chefdk'][:version] %>
317
- <%- end -%>
318
- <% end -%>
319
- <% if options[:nodes] -%>
320
-
321
- nodes:
322
- chef_server_url: https://chef.lxc/organizations/demo
323
- validation_client_name: demo-validator
324
- # comment out or remove the validation_key path to use chef-server keys generated by dev-lxc
325
- validation_key: # /path/for/ORG-validator.pem
326
- servers:
327
- node-1.lxc:
328
- products:
329
- <%- unless product_versions['chef'][:channel] == 'none' -%>
330
- chef:
331
- channel: <%= product_versions['chef'][:channel] %>
332
- version: <%= product_versions['chef'][:version] %>
333
- <%- end -%>
334
- <% end -%>
335
- <% if options[:analytics] -%>
336
-
337
- analytics:
338
- servers:
339
- analytics.lxc:
340
- ipaddress: 10.0.3.204
341
- products:
342
- <%- unless product_versions['analytics'][:channel] == 'none' -%>
343
- analytics:
344
- channel: <%= product_versions['analytics'][:channel] %>
345
- version: <%= product_versions['analytics'][:version] %>
346
- <%- end -%>
347
- <% end -%>
348
- <% if options[:adhoc] -%>
349
-
350
- adhoc:
351
- servers:
352
- adhoc.lxc:
353
- ipaddress: 10.0.3.207
354
- <% end -%>
355
- )
356
-
357
- dev_lxc_config = ERB.new(dev_lxc_template, nil, '-').result(binding)
358
- if options[:filename]
359
- mode = options[:append] ? 'a' : 'w'
360
- IO.write(options[:filename], dev_lxc_config, mode: mode)
361
- else
362
- puts dev_lxc_config
363
- end
364
- end
365
-
366
- desc "status [SERVER_NAME_REGEX]", "Show status of servers"
367
- option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
368
- def status(server_name_regex=nil)
369
- cluster = get_cluster(options[:config])
370
- if cluster.config['chef-server'][:topology] == "tier" && cluster.config['chef-server'][:fqdn]
371
- printf "Chef Server FQDN: %s\n\n", cluster.config['chef-server'][:fqdn]
372
- end
373
- if cluster.config['chef-backend'][:fqdn]
374
- printf "Chef Server FQDN: %s\n\n", cluster.config['chef-backend'][:fqdn]
375
- end
376
- if cluster.config['analytics'][:topology] == "tier" && cluster.config['analytics'][:fqdn]
377
- printf "Analytics FQDN: %s\n\n", cluster.config['analytics'][:fqdn]
378
- end
379
- servers = Array.new
380
- cluster.get_sorted_servers(server_name_regex).map { |s| servers << s.status }
381
- max_server_name_length = servers.max_by { |s| s['name'].length }['name'].length unless servers.empty?
382
- servers.each_with_index do |s, server_index|
383
- printf "%-#{max_server_name_length}s %-15s %s\n", s['name'], s['state'].upcase, s['ip_addresses']
384
- server = cluster.get_server(s['name'])
385
- server.snapshot_list.each do |snapname, snaptime, snap_comment|
386
- printf " |_ %s %s %s\n", snapname, snaptime, snap_comment
387
- end
388
- puts if server_index + 1 < servers.length
389
- end
390
- end
391
-
392
- desc "attach [SERVER_NAME_REGEX]", "Attach the terminal to a single server"
393
- option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
394
- def attach(server_name_regex)
395
- servers = get_cluster(options[:config]).get_sorted_servers(server_name_regex)
396
- if servers.length > 1
397
- puts "ERROR: The following servers matched '#{server_name_regex}'"
398
- servers.map { |s| puts " #{s.name}" }
399
- puts " Please specify a single server to attach to"
400
- exit 1
401
- elsif servers.empty?
402
- puts "ERROR: No servers matched '#{server_name_regex}'"
403
- puts " Please specify a single server to attach to"
404
- exit 1
405
- end
406
- container = servers.first.container
407
- if !container.defined? || !container.running?
408
- puts "ERROR: Server '#{container.name}' is not running"
409
- exit 1
410
- end
411
- attach_opts = {
412
- wait: true,
413
- env_policy: LXC::LXC_ATTACH_CLEAR_ENV,
414
- extra_env_vars: ["LANG=en_US.UTF-8", "TERM=linux", "HOME=#{ENV['HOME']}"]
415
- }
416
- shell = ENV['SHELL']
417
- container.attach(attach_opts) { system(shell) }
418
- end
419
-
420
- desc "chef-repo", "Creates a '.chef' directory in the current directory using files from the cluster's backend /root/chef-repo/.chef"
421
- option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
422
- option :force, :aliases => "-f", :type => :boolean, :desc => "Overwrite any existing knife.rb or pivotal.rb files"
423
- option :pivotal, :aliases => "-p", :type => :boolean, :desc => "Also copy pivotal.rb and pivotal.pem"
424
- def chef_repo
425
- get_cluster(options[:config]).chef_repo(options[:force], options[:pivotal])
426
- end
427
-
428
- desc "print-automate-credentials", "Print Automate credentials"
429
- option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
430
- def print_automate_credentials
431
- get_cluster(options[:config]).print_automate_credentials
432
- end
433
-
434
- desc "run-command [SERVER_NAME_REGEX] [COMMAND]", "Runs a command in each server"
435
- option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
436
- def run_command(server_name_regex=nil, command)
437
- start_time = Time.now
438
- get_cluster(options[:config]).get_sorted_servers(server_name_regex).each { |s| s.run_command(command); puts }
439
- print_elapsed_time(Time.now - start_time)
440
- end
441
-
442
- desc "prepare-product-cache [SERVER_NAME_REGEX]", "Download required product packages to cache"
443
- option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
444
- def prepare_product_cache(server_name_regex=nil)
445
- start_time = Time.now
446
- cluster = get_cluster(options[:config])
447
- servers = cluster.get_sorted_servers(server_name_regex)
448
- cluster.prep_product_cache(servers, true)
449
- print_elapsed_time(Time.now - start_time)
450
- end
451
-
452
- desc "up [SERVER_NAME_REGEX]", "Start servers - This is the default if no subcommand is given"
453
- option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
454
- def up(server_name_regex=nil)
455
- start_time = Time.now
456
- get_cluster(options[:config]).up(server_name_regex)
457
- print_elapsed_time(Time.now - start_time)
458
- end
459
-
460
- desc "halt [SERVER_NAME_REGEX]", "Shutdown servers"
461
- option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
462
- def halt(server_name_regex=nil)
463
- start_time = Time.now
464
- get_cluster(options[:config]).halt(server_name_regex)
465
- print_elapsed_time(Time.now - start_time)
466
- end
467
-
468
- desc "snapshot [SERVER_NAME_REGEX]", "Manage a cluster's snapshots"
469
- option :comment, :aliases => "-c", :desc => "Add snapshot comment"
470
- option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
471
- option :destroy, :aliases => "-d", :desc => "Destroy snapshot - use ALL to destroy all snapshots"
472
- option :list, :aliases => "-l", :type => :boolean, :desc => "List snapshots"
473
- option :restore, :aliases => "-r", :desc => "Restore snapshots"
474
- def snapshot(server_name_regex=nil)
475
- start_time = Time.now
476
- servers = get_cluster(options[:config]).get_sorted_servers(server_name_regex)
477
- if options[:list]
478
- servers.each_with_index do |s, server_index|
479
- puts s.name
480
- s.snapshot_list.each do |snapname, snaptime, snap_comment|
481
- printf " |_ %s %s %s\n", snapname, snaptime, snap_comment
482
- end
483
- puts if server_index + 1 < servers.length
484
- end
485
- return
486
- elsif options[:destroy]
487
- snapname = options[:destroy] == 'destroy' ? "LAST" : options[:destroy]
488
- servers.each { |s| s.snapshot_destroy(snapname); puts }
489
- elsif options[:restore]
490
- running_servers = Array.new
491
- servers.each do |s|
492
- running_servers << s.name if s.container.running?
493
- end
494
- unless running_servers.empty?
495
- puts "ERROR: Aborting snapshot restore because the following servers are running"
496
- puts running_servers
497
- exit 1
498
- end
499
- snapname = options[:restore] == 'restore' ? "LAST" : options[:restore]
500
- servers.each { |s| s.snapshot_restore(snapname); puts }
501
- else
502
- running_servers = Array.new
503
- servers.each do |s|
504
- running_servers << s.name if s.container.running?
505
- end
506
- unless running_servers.empty?
507
- puts "ERROR: Aborting snapshot because the following servers are running"
508
- puts running_servers
509
- exit 1
510
- end
511
- servers.each { |s| s.snapshot(options[:comment]); puts }
512
- end
513
- print_elapsed_time(Time.now - start_time)
514
- end
515
-
516
- desc "destroy [SERVER_NAME_REGEX]", "Destroy servers"
517
- option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
518
- option :force, :aliases => "-f", :type => :boolean, :desc => "Destroy servers without confirmation"
519
- def destroy(server_name_regex=nil)
520
- servers = get_cluster(options[:config]).get_sorted_servers(server_name_regex)
521
- if servers.empty?
522
- puts "No matching server names were found"
523
- exit
524
- end
525
- unless options[:force]
526
- confirmation_string = String.new
527
- servers.reverse_each { |s| confirmation_string += "#{s.name}\n" }
528
- confirmation_string += "Are you sure you want to destroy these servers? (y/N)\n"
529
- return unless yes?(confirmation_string)
530
- end
531
- start_time = Time.now
532
- get_cluster(options[:config]).destroy(server_name_regex)
533
- print_elapsed_time(Time.now - start_time)
534
- end
535
-
536
- end
537
- end
1
+ require 'erb'
2
+ require "yaml"
3
+ require 'dev-lxc'
4
+ require 'thor'
5
+
6
+ module DevLXC::CLI
7
+ class DevLXC < Thor
8
+
9
+ no_commands{
10
+ def get_cluster(config_file=nil)
11
+ config_file ||= "dev-lxc.yml"
12
+ if ! File.exists?(config_file)
13
+ puts "ERROR: Cluster config file '#{config_file}' does not exist."
14
+ puts " Create a `./dev-lxc.yml` file or specify the path using `--config`."
15
+ exit 1
16
+ end
17
+ begin
18
+ cluster_config = YAML.load(IO.read(config_file))
19
+ rescue Psych::SyntaxError => e
20
+ puts "ERROR: A YAML syntax error was found at line #{e.line} column #{e.column}"
21
+ exit 1
22
+ end
23
+ ::DevLXC::Cluster.new(cluster_config)
24
+ end
25
+
26
+ def print_elapsed_time(elapsed_time)
27
+ printf "dev-lxc is finished. (%im %.2fs)\n", elapsed_time / 60, elapsed_time % 60
28
+ end
29
+ }
30
+
31
+ desc "show-config", "Show calculated configuration"
32
+ option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
33
+ option :include_products, :type => :boolean, :desc => "Calculate required products"
34
+ def show_config
35
+ get_cluster(options[:config]).show_config(options[:include_products])
36
+ end
37
+
38
+ desc "create-base-container [BASE_CONTAINER_NAME]", "Create a base container"
39
+ option :options, :aliases => "-o", :desc => "Specify additional options for the lxc create"
40
+ def create_base_container(base_container_name=nil)
41
+ start_time = Time.now
42
+ base_container_names = %w(b-ubuntu-1204 b-ubuntu-1404 b-ubuntu-1604 b-centos-5 b-centos-6 b-centos-7)
43
+ if base_container_name.nil? || ! base_container_names.include?(base_container_name)
44
+ base_container_names_with_index = base_container_names.map.with_index{ |a, i| [i+1, *a]}
45
+ print_table base_container_names_with_index
46
+ selection = ask("Which base container do you want to create?", :limited_to => base_container_names_with_index.map{|c| c[0].to_s})
47
+ base_container_name = base_container_names[selection.to_i - 1]
48
+ end
49
+ ::DevLXC.create_base_container(base_container_name, options[:options])
50
+ puts
51
+ print_elapsed_time(Time.now - start_time)
52
+ end
53
+
54
+ desc "init", "Provide a cluster config file"
55
+ option :chef, :type => :boolean, :desc => "Standalone Chef Server"
56
+ option :chef_tier, :type => :boolean, :desc => "Chef Server using Tier topology with one backend"
57
+ option :chef_backend, :type => :boolean, :desc => "Chef Server using Chef Backend HA topology with three backends"
58
+ option :nodes, :type => :boolean, :desc => "Node Servers"
59
+ option :analytics, :type => :boolean, :desc => "Analytics Server"
60
+ option :compliance, :type => :boolean, :desc => "Compliance Server"
61
+ option :supermarket, :type => :boolean, :desc => "Supermarket Server"
62
+ option :automate, :type => :boolean, :desc => "Automate Server"
63
+ option :build_nodes, :type => :boolean, :desc => "Build Nodes"
64
+ option :runners, :type => :boolean, :desc => "Runners"
65
+ option :adhoc, :type => :boolean, :desc => "Adhoc Servers"
66
+ option :base_container, :default => 'b-ubuntu-1404', :desc => "Specify the base container name"
67
+ option :product_versions, :type => :hash, :default => {}, :desc => "Specify channel and version (default 'latest') for products [product:channel,version ...]; [product:none] removes the product from the config"
68
+ option :append, :aliases => "-a", :type => :boolean, :desc => "Do not generate the global config header"
69
+ option :filename, :aliases => "-f", :desc => "Write generated content to FILE rather than standard output."
70
+ def init
71
+ product_versions = Hash.new { |hash, key| hash[key] = { channel: 'stable', version: 'latest' } }
72
+ options[:product_versions].keys.each do |product|
73
+ product_versions[product][:channel], product_versions[product][:version] = options[:product_versions][product].to_s.split(',', 2)
74
+ product_versions[product][:version] ||= 'latest'
75
+ end
76
+
77
+ dev_lxc_template = %Q(<% unless options[:append] -%>
78
+ # enable_build_snapshots automatically makes container snapshots at key times during the build process
79
+ # default value is `true`
80
+ #enable_build_snapshots: true
81
+
82
+ # base_container must be the name of an existing container
83
+ base_container: <%= options[:base_container] %>
84
+
85
+ # memory_per_server sets the maximum amount of user memory (including file cache) for each server.
86
+ # dev-lxc will set the `memory.limit_in_bytes` cgroup for each server to apply this limit.
87
+ # If no units are specified, the value is interpreted as bytes.
88
+ # You can use suffixes to represent larger units - k or K for kilobytes, m or M for megabytes, and g or G for gigabytes.
89
+ # The default behavior is that no limit is set.
90
+ #memory_per_server: 4G
91
+
92
+ # list any host directories you want mounted into the servers
93
+ #mounts:
94
+ # - /root/clusters root/clusters
95
+
96
+ # list any SSH public keys you want added to /home/dev-lxc/.ssh/authorized_keys
97
+ #ssh-keys:
98
+ # - /root/clusters/id_rsa.pub
99
+
100
+ # DHCP reserved (static) IPs must be selected from the IP range 10.0.3.150 - 254
101
+ <% end -%>
102
+ <% if options[:chef] -%>
103
+
104
+ chef-server:
105
+ users: # a user's password will be the same as its username
106
+ - mary-admin
107
+ - joe-user
108
+ orgs:
109
+ demo:
110
+ admins:
111
+ - mary-admin
112
+ non-admins:
113
+ - joe-user
114
+ servers:
115
+ chef.lxc:
116
+ ipaddress: 10.0.3.203
117
+ products:
118
+ <%- unless product_versions['chef-server'][:channel] == 'none' -%>
119
+ chef-server:
120
+ channel: <%= product_versions['chef-server'][:channel] %>
121
+ version: <%= product_versions['chef-server'][:version] %>
122
+ <%- end -%>
123
+ <%- unless product_versions['manage'][:channel] == 'none' -%>
124
+ manage:
125
+ channel: <%= product_versions['manage'][:channel] %>
126
+ version: <%= product_versions['manage'][:version] %>
127
+ <%- end -%>
128
+ <%- unless product_versions['push-jobs-server'][:channel] == 'none' -%>
129
+ push-jobs-server:
130
+ channel: <%= product_versions['push-jobs-server'][:channel] %>
131
+ version: <%= product_versions['push-jobs-server'][:version] %>
132
+ <%- end -%>
133
+ <%- unless product_versions['reporting'][:channel] == 'none' -%>
134
+ reporting:
135
+ channel: <%= product_versions['reporting'][:channel] %>
136
+ version: <%= product_versions['reporting'][:version] %>
137
+ <%- end -%>
138
+ <% end -%>
139
+ <% if options[:chef_backend] -%>
140
+
141
+ chef-backend:
142
+ api_fqdn: chef-ha.lxc
143
+ users: # a user's password will be the same as its username
144
+ - mary-admin
145
+ - joe-user
146
+ orgs:
147
+ demo:
148
+ admins:
149
+ - mary-admin
150
+ non-admins:
151
+ - joe-user
152
+ servers:
153
+ chef-backend1.lxc:
154
+ ipaddress: 10.0.3.208
155
+ role: backend
156
+ leader: true
157
+ products:
158
+ <%- unless product_versions['chef-backend'][:channel] == 'none' -%>
159
+ chef-backend:
160
+ channel: <%= product_versions['chef-backend'][:channel] %>
161
+ version: <%= product_versions['chef-backend'][:version] %>
162
+ <%- end -%>
163
+ chef-backend2.lxc:
164
+ ipaddress: 10.0.3.209
165
+ role: backend
166
+ products:
167
+ <%- unless product_versions['chef-backend'][:channel] == 'none' -%>
168
+ chef-backend:
169
+ channel: <%= product_versions['chef-backend'][:channel] %>
170
+ version: <%= product_versions['chef-backend'][:version] %>
171
+ <%- end -%>
172
+ chef-backend3.lxc:
173
+ ipaddress: 10.0.3.210
174
+ role: backend
175
+ products:
176
+ <%- unless product_versions['chef-backend'][:channel] == 'none' -%>
177
+ chef-backend:
178
+ channel: <%= product_versions['chef-backend'][:channel] %>
179
+ version: <%= product_versions['chef-backend'][:version] %>
180
+ <%- end -%>
181
+ chef-frontend1.lxc:
182
+ ipaddress: 10.0.3.211
183
+ role: frontend
184
+ bootstrap: true
185
+ products:
186
+ <%- unless product_versions['chef-server'][:channel] == 'none' -%>
187
+ chef-server:
188
+ channel: <%= product_versions['chef-server'][:channel] %>
189
+ version: <%= product_versions['chef-server'][:version] %>
190
+ <%- end -%>
191
+ <%- unless product_versions['manage'][:channel] == 'none' -%>
192
+ manage:
193
+ channel: <%= product_versions['manage'][:channel] %>
194
+ version: <%= product_versions['manage'][:version] %>
195
+ <%- end -%>
196
+ <% end -%>
197
+ <% if options[:chef_tier] -%>
198
+
199
+ chef-server:
200
+ topology: tier
201
+ api_fqdn: chef-tier.lxc
202
+ users: # a user's password will be the same as its username
203
+ - mary-admin
204
+ - joe-user
205
+ orgs:
206
+ demo:
207
+ admins:
208
+ - mary-admin
209
+ non-admins:
210
+ - joe-user
211
+ servers:
212
+ chef-be.lxc:
213
+ ipaddress: 10.0.3.201
214
+ role: backend
215
+ bootstrap: true
216
+ products:
217
+ <%- unless product_versions['chef-server'][:channel] == 'none' -%>
218
+ chef-server:
219
+ channel: <%= product_versions['chef-server'][:channel] %>
220
+ version: <%= product_versions['chef-server'][:version] %>
221
+ <%- end -%>
222
+ <%- unless product_versions['push-jobs-server'][:channel] == 'none' -%>
223
+ push-jobs-server:
224
+ channel: <%= product_versions['push-jobs-server'][:channel] %>
225
+ version: <%= product_versions['push-jobs-server'][:version] %>
226
+ <%- end -%>
227
+ <%- unless product_versions['reporting'][:channel] == 'none' -%>
228
+ reporting:
229
+ channel: <%= product_versions['reporting'][:channel] %>
230
+ version: <%= product_versions['reporting'][:version] %>
231
+ <%- end -%>
232
+ chef-fe1.lxc:
233
+ ipaddress: 10.0.3.202
234
+ role: frontend
235
+ products:
236
+ <%- unless product_versions['chef-server'][:channel] == 'none' -%>
237
+ chef-server:
238
+ channel: <%= product_versions['chef-server'][:channel] %>
239
+ version: <%= product_versions['chef-server'][:version] %>
240
+ <%- end -%>
241
+ <%- unless product_versions['manage'][:channel] == 'none' -%>
242
+ manage:
243
+ channel: <%= product_versions['manage'][:channel] %>
244
+ version: <%= product_versions['manage'][:version] %>
245
+ <%- end -%>
246
+ <%- unless product_versions['push-jobs-server'][:channel] == 'none' -%>
247
+ push-jobs-server:
248
+ channel: <%= product_versions['push-jobs-server'][:channel] %>
249
+ version: <%= product_versions['push-jobs-server'][:version] %>
250
+ <%- end -%>
251
+ <%- unless product_versions['reporting'][:channel] == 'none' -%>
252
+ reporting:
253
+ channel: <%= product_versions['reporting'][:channel] %>
254
+ version: <%= product_versions['reporting'][:version] %>
255
+ <%- end -%>
256
+ <% end -%>
257
+ <% if options[:compliance] -%>
258
+
259
+ compliance:
260
+ admin_user: admin # the password will be the same as the username
261
+ servers:
262
+ compliance.lxc:
263
+ ipaddress: 10.0.3.205
264
+ products:
265
+ <%- unless product_versions['compliance'][:channel] == 'none' -%>
266
+ compliance:
267
+ channel: <%= product_versions['compliance'][:channel] %>
268
+ version: <%= product_versions['compliance'][:version] %>
269
+ <%- end -%>
270
+ <% end -%>
271
+ <% if options[:supermarket] -%>
272
+
273
+ supermarket:
274
+ servers:
275
+ supermarket.lxc:
276
+ ipaddress: 10.0.3.206
277
+ products:
278
+ <%- unless product_versions['supermarket'][:channel] == 'none' -%>
279
+ supermarket:
280
+ channel: <%= product_versions['supermarket'][:channel] %>
281
+ version: <%= product_versions['supermarket'][:version] %>
282
+ <%- end -%>
283
+ <% end -%>
284
+ <% if options[:automate] -%>
285
+
286
+ automate:
287
+ servers:
288
+ automate.lxc:
289
+ ipaddress: 10.0.3.200
290
+ products:
291
+ <%- unless product_versions['automate'][:channel] == 'none' -%>
292
+ automate:
293
+ channel: <%= product_versions['automate'][:channel] %>
294
+ version: <%= product_versions['automate'][:version] %>
295
+ <%- end -%>
296
+ license_path: ../delivery.license
297
+ chef_org: delivery
298
+ enterprise_name: demo-ent
299
+ <% end -%>
300
+ <% if options[:build_nodes] -%>
301
+
302
+ build-nodes:
303
+ servers:
304
+ build-node-1.lxc:
305
+ products:
306
+ <%- unless product_versions['chefdk'][:channel] == 'none' -%>
307
+ chefdk: # downloaded only
308
+ channel: <%= product_versions['chefdk'][:channel] %>
309
+ version: <%= product_versions['chefdk'][:version] %>
310
+ <%- end -%>
311
+ <% end -%>
312
+ <% if options[:runners] -%>
313
+
314
+ runners:
315
+ servers:
316
+ runner-1.lxc:
317
+ products:
318
+ <%- unless product_versions['chefdk'][:channel] == 'none' -%>
319
+ chefdk: # downloaded only
320
+ channel: <%= product_versions['chefdk'][:channel] %>
321
+ version: <%= product_versions['chefdk'][:version] %>
322
+ <%- end -%>
323
+ <% end -%>
324
+ <% if options[:nodes] -%>
325
+
326
+ nodes:
327
+ chef_server_url: https://chef.lxc/organizations/demo
328
+ validation_client_name: demo-validator
329
+ # comment out or remove the validation_key path to use chef-server keys generated by dev-lxc
330
+ validation_key: # /path/for/ORG-validator.pem
331
+ servers:
332
+ node-1.lxc:
333
+ products:
334
+ <%- unless product_versions['chef'][:channel] == 'none' -%>
335
+ chef:
336
+ channel: <%= product_versions['chef'][:channel] %>
337
+ version: <%= product_versions['chef'][:version] %>
338
+ <%- end -%>
339
+ <% end -%>
340
+ <% if options[:analytics] -%>
341
+
342
+ analytics:
343
+ servers:
344
+ analytics.lxc:
345
+ ipaddress: 10.0.3.204
346
+ products:
347
+ <%- unless product_versions['analytics'][:channel] == 'none' -%>
348
+ analytics:
349
+ channel: <%= product_versions['analytics'][:channel] %>
350
+ version: <%= product_versions['analytics'][:version] %>
351
+ <%- end -%>
352
+ <% end -%>
353
+ <% if options[:adhoc] -%>
354
+
355
+ adhoc:
356
+ servers:
357
+ adhoc.lxc:
358
+ ipaddress: 10.0.3.207
359
+ <% end -%>
360
+ )
361
+
362
+ dev_lxc_config = ERB.new(dev_lxc_template, nil, '-').result(binding)
363
+ if options[:filename]
364
+ mode = options[:append] ? 'a' : 'w'
365
+ IO.write(options[:filename], dev_lxc_config, mode: mode)
366
+ else
367
+ puts dev_lxc_config
368
+ end
369
+ end
370
+
371
+ desc "status [SERVER_NAME_REGEX]", "Show status of servers"
372
+ option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
373
+ def status(server_name_regex=nil)
374
+ cluster = get_cluster(options[:config])
375
+ if cluster.config['chef-server'][:topology] == "tier" && cluster.config['chef-server'][:fqdn]
376
+ printf "Chef Server FQDN: %s\n\n", cluster.config['chef-server'][:fqdn]
377
+ end
378
+ if cluster.config['chef-backend'][:fqdn]
379
+ printf "Chef Server FQDN: %s\n\n", cluster.config['chef-backend'][:fqdn]
380
+ end
381
+ if cluster.config['analytics'][:topology] == "tier" && cluster.config['analytics'][:fqdn]
382
+ printf "Analytics FQDN: %s\n\n", cluster.config['analytics'][:fqdn]
383
+ end
384
+ servers = Array.new
385
+ cluster.get_sorted_servers(server_name_regex).map { |s| servers << s.status }
386
+ max_server_name_length = servers.max_by { |s| s['name'].length }['name'].length unless servers.empty?
387
+ servers.each_with_index do |s, server_index|
388
+ printf "%-#{max_server_name_length}s %-15s %s\n", s['name'], s['state'].upcase, s['ip_addresses']
389
+ server = cluster.get_server(s['name'])
390
+ server.snapshot_list.each do |snapname, snaptime, snap_comment|
391
+ printf " |_ %s %s %s\n", snapname, snaptime, snap_comment
392
+ end
393
+ puts if server_index + 1 < servers.length
394
+ end
395
+ end
396
+
397
+ desc "attach [SERVER_NAME_REGEX]", "Attach the terminal to a single server"
398
+ option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
399
+ def attach(server_name_regex)
400
+ servers = get_cluster(options[:config]).get_sorted_servers(server_name_regex)
401
+ if servers.length > 1
402
+ puts "ERROR: The following servers matched '#{server_name_regex}'"
403
+ servers.map { |s| puts " #{s.name}" }
404
+ puts " Please specify a single server to attach to"
405
+ exit 1
406
+ elsif servers.empty?
407
+ puts "ERROR: No servers matched '#{server_name_regex}'"
408
+ puts " Please specify a single server to attach to"
409
+ exit 1
410
+ end
411
+ container = servers.first.container
412
+ if !container.defined? || !container.running?
413
+ puts "ERROR: Server '#{container.name}' is not running"
414
+ exit 1
415
+ end
416
+ attach_opts = {
417
+ wait: true,
418
+ env_policy: LXC::LXC_ATTACH_CLEAR_ENV,
419
+ extra_env_vars: ["LANG=en_US.UTF-8", "TERM=linux", "HOME=#{ENV['HOME']}"]
420
+ }
421
+ shell = ENV['SHELL']
422
+ container.attach(attach_opts) { system(shell) }
423
+ end
424
+
425
+ desc "chef-repo", "Creates a '.chef' directory in the current directory using files from the cluster's backend /root/chef-repo/.chef"
426
+ option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
427
+ option :force, :aliases => "-f", :type => :boolean, :desc => "Overwrite any existing knife.rb or pivotal.rb files"
428
+ option :pivotal, :aliases => "-p", :type => :boolean, :desc => "Also copy pivotal.rb and pivotal.pem"
429
+ def chef_repo
430
+ get_cluster(options[:config]).chef_repo(options[:force], options[:pivotal])
431
+ end
432
+
433
+ desc "print-automate-credentials", "Print Automate credentials"
434
+ option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
435
+ def print_automate_credentials
436
+ get_cluster(options[:config]).print_automate_credentials
437
+ end
438
+
439
+ desc "run-command [SERVER_NAME_REGEX] [COMMAND]", "Runs a command in each server"
440
+ option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
441
+ def run_command(server_name_regex=nil, command)
442
+ start_time = Time.now
443
+ get_cluster(options[:config]).get_sorted_servers(server_name_regex).each { |s| s.run_command(command); puts }
444
+ print_elapsed_time(Time.now - start_time)
445
+ end
446
+
447
+ desc "prepare-product-cache [SERVER_NAME_REGEX]", "Download required product packages to cache"
448
+ option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
449
+ def prepare_product_cache(server_name_regex=nil)
450
+ start_time = Time.now
451
+ cluster = get_cluster(options[:config])
452
+ servers = cluster.get_sorted_servers(server_name_regex)
453
+ cluster.prep_product_cache(servers, true)
454
+ print_elapsed_time(Time.now - start_time)
455
+ end
456
+
457
+ desc "up [SERVER_NAME_REGEX]", "Start servers - This is the default if no subcommand is given"
458
+ option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
459
+ def up(server_name_regex=nil)
460
+ start_time = Time.now
461
+ get_cluster(options[:config]).up(server_name_regex)
462
+ print_elapsed_time(Time.now - start_time)
463
+ end
464
+
465
+ desc "halt [SERVER_NAME_REGEX]", "Shutdown servers"
466
+ option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
467
+ def halt(server_name_regex=nil)
468
+ start_time = Time.now
469
+ get_cluster(options[:config]).halt(server_name_regex)
470
+ print_elapsed_time(Time.now - start_time)
471
+ end
472
+
473
+ desc "snapshot [SERVER_NAME_REGEX]", "Manage a cluster's snapshots"
474
+ option :comment, :aliases => "-c", :desc => "Add snapshot comment"
475
+ option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
476
+ option :destroy, :aliases => "-d", :desc => "Destroy snapshot - use ALL to destroy all snapshots"
477
+ option :list, :aliases => "-l", :type => :boolean, :desc => "List snapshots"
478
+ option :restore, :aliases => "-r", :desc => "Restore snapshots"
479
+ def snapshot(server_name_regex=nil)
480
+ start_time = Time.now
481
+ servers = get_cluster(options[:config]).get_sorted_servers(server_name_regex)
482
+ if options[:list]
483
+ servers.each_with_index do |s, server_index|
484
+ puts s.name
485
+ s.snapshot_list.each do |snapname, snaptime, snap_comment|
486
+ printf " |_ %s %s %s\n", snapname, snaptime, snap_comment
487
+ end
488
+ puts if server_index + 1 < servers.length
489
+ end
490
+ return
491
+ elsif options[:destroy]
492
+ snapname = options[:destroy] == 'destroy' ? "LAST" : options[:destroy]
493
+ servers.each { |s| s.snapshot_destroy(snapname); puts }
494
+ elsif options[:restore]
495
+ running_servers = Array.new
496
+ servers.each do |s|
497
+ running_servers << s.name if s.container.running?
498
+ end
499
+ unless running_servers.empty?
500
+ puts "ERROR: Aborting snapshot restore because the following servers are running"
501
+ puts running_servers
502
+ exit 1
503
+ end
504
+ snapname = options[:restore] == 'restore' ? "LAST" : options[:restore]
505
+ servers.each { |s| s.snapshot_restore(snapname); puts }
506
+ else
507
+ running_servers = Array.new
508
+ servers.each do |s|
509
+ running_servers << s.name if s.container.running?
510
+ end
511
+ unless running_servers.empty?
512
+ puts "ERROR: Aborting snapshot because the following servers are running"
513
+ puts running_servers
514
+ exit 1
515
+ end
516
+ servers.each { |s| s.snapshot(options[:comment]); puts }
517
+ end
518
+ print_elapsed_time(Time.now - start_time)
519
+ end
520
+
521
+ desc "destroy [SERVER_NAME_REGEX]", "Destroy servers"
522
+ option :config, :desc => "Specify a cluster's YAML config file. `./dev-lxc.yml` will be used by default"
523
+ option :force, :aliases => "-f", :type => :boolean, :desc => "Destroy servers without confirmation"
524
+ def destroy(server_name_regex=nil)
525
+ servers = get_cluster(options[:config]).get_sorted_servers(server_name_regex)
526
+ if servers.empty?
527
+ puts "No matching server names were found"
528
+ exit
529
+ end
530
+ unless options[:force]
531
+ confirmation_string = String.new
532
+ servers.reverse_each { |s| confirmation_string += "#{s.name}\n" }
533
+ confirmation_string += "Are you sure you want to destroy these servers? (y/N)\n"
534
+ return unless yes?(confirmation_string)
535
+ end
536
+ start_time = Time.now
537
+ get_cluster(options[:config]).destroy(server_name_regex)
538
+ print_elapsed_time(Time.now - start_time)
539
+ end
540
+
541
+ end
542
+ end