dev-lxc 3.2.0 → 3.3.0

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