wombat-cli 0.2.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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/DESIGN.md +40 -0
- data/Gemfile +3 -0
- data/README.md +132 -0
- data/Rakefile +52 -0
- data/Vagrantfile +121 -0
- data/bin/wombat +24 -0
- data/cookbooks/automate/.gitignore +16 -0
- data/cookbooks/automate/.kitchen.ec2.yml +27 -0
- data/cookbooks/automate/.kitchen.yml +25 -0
- data/cookbooks/automate/Berksfile +6 -0
- data/cookbooks/automate/README.md +4 -0
- data/cookbooks/automate/chefignore +102 -0
- data/cookbooks/automate/libraries/_helper.rb +52 -0
- data/cookbooks/automate/libraries/delivery_api.rb +204 -0
- data/cookbooks/automate/libraries/delivery_project.rb +31 -0
- data/cookbooks/automate/libraries/dsl.rb +5 -0
- data/cookbooks/automate/metadata.rb +13 -0
- data/cookbooks/automate/recipes/default.rb +111 -0
- data/cookbooks/automate/recipes/update-users.rb +48 -0
- data/cookbooks/automate/templates/delivery.erb +5 -0
- data/cookbooks/automate/test/fixtures/cookbooks/mock_data/files/automate.crt +26 -0
- data/cookbooks/automate/test/fixtures/cookbooks/mock_data/files/automate.key +27 -0
- data/cookbooks/automate/test/fixtures/cookbooks/mock_data/files/chef.crt +25 -0
- data/cookbooks/automate/test/fixtures/cookbooks/mock_data/files/chef.key +27 -0
- data/cookbooks/automate/test/fixtures/cookbooks/mock_data/files/compliance.crt +26 -0
- data/cookbooks/automate/test/fixtures/cookbooks/mock_data/files/compliance.key +27 -0
- data/cookbooks/automate/test/fixtures/cookbooks/mock_data/files/private.pem +27 -0
- data/cookbooks/automate/test/fixtures/cookbooks/mock_data/files/public.pub +1 -0
- data/cookbooks/automate/test/fixtures/cookbooks/mock_data/metadata.rb +3 -0
- data/cookbooks/automate/test/fixtures/cookbooks/mock_data/recipes/default.rb +27 -0
- data/cookbooks/automate/test/integration/default/automate_spec.rb +55 -0
- data/cookbooks/build_node/.gitignore +16 -0
- data/cookbooks/build_node/.kitchen.ec2.yml +30 -0
- data/cookbooks/build_node/.kitchen.yml +23 -0
- data/cookbooks/build_node/Berksfile +8 -0
- data/cookbooks/build_node/README.md +4 -0
- data/cookbooks/build_node/chefignore +102 -0
- data/cookbooks/build_node/metadata.rb +15 -0
- data/cookbooks/build_node/recipes/default.rb +35 -0
- data/cookbooks/build_node/templates/client.erb +3 -0
- data/cookbooks/build_node/test/fixtures/cookbooks/mock_data/files/automate.crt +26 -0
- data/cookbooks/build_node/test/fixtures/cookbooks/mock_data/files/automate.key +27 -0
- data/cookbooks/build_node/test/fixtures/cookbooks/mock_data/files/chef.crt +25 -0
- data/cookbooks/build_node/test/fixtures/cookbooks/mock_data/files/chef.key +27 -0
- data/cookbooks/build_node/test/fixtures/cookbooks/mock_data/files/compliance.crt +26 -0
- data/cookbooks/build_node/test/fixtures/cookbooks/mock_data/files/compliance.key +27 -0
- data/cookbooks/build_node/test/fixtures/cookbooks/mock_data/files/private.pem +27 -0
- data/cookbooks/build_node/test/fixtures/cookbooks/mock_data/files/public.pub +1 -0
- data/cookbooks/build_node/test/fixtures/cookbooks/mock_data/metadata.rb +2 -0
- data/cookbooks/build_node/test/fixtures/cookbooks/mock_data/recipes/default.rb +18 -0
- data/cookbooks/build_node/test/integration/default/build-node_spec.rb +39 -0
- data/cookbooks/chef_server/.gitignore +16 -0
- data/cookbooks/chef_server/.kitchen.ec2.yml +26 -0
- data/cookbooks/chef_server/.kitchen.yml +25 -0
- data/cookbooks/chef_server/Berksfile +6 -0
- data/cookbooks/chef_server/README.md +4 -0
- data/cookbooks/chef_server/chefignore +102 -0
- data/cookbooks/chef_server/metadata.rb +13 -0
- data/cookbooks/chef_server/recipes/cheffish.rb +91 -0
- data/cookbooks/chef_server/recipes/default.rb +79 -0
- data/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/files/automate.crt +26 -0
- data/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/files/automate.key +27 -0
- data/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/files/chef.crt +25 -0
- data/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/files/chef.key +27 -0
- data/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/files/compliance.crt +26 -0
- data/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/files/compliance.key +27 -0
- data/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/files/private.pem +27 -0
- data/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/files/public.pub +1 -0
- data/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/metadata.rb +2 -0
- data/cookbooks/chef_server/test/fixtures/cookbooks/mock_data/recipes/default.rb +23 -0
- data/cookbooks/chef_server/test/integration/default/chef_server_spec.rb +47 -0
- data/cookbooks/compliance/.gitignore +16 -0
- data/cookbooks/compliance/.kitchen.ec2.yml +26 -0
- data/cookbooks/compliance/.kitchen.yml +24 -0
- data/cookbooks/compliance/Berksfile +7 -0
- data/cookbooks/compliance/README.md +4 -0
- data/cookbooks/compliance/chefignore +102 -0
- data/cookbooks/compliance/metadata.rb +12 -0
- data/cookbooks/compliance/recipes/default.rb +59 -0
- data/cookbooks/compliance/spec/spec_helper.rb +2 -0
- data/cookbooks/compliance/spec/unit/recipes/default_spec.rb +20 -0
- data/cookbooks/compliance/templates/default/chef-compliance.rb.erb +1 -0
- data/cookbooks/compliance/test/fixtures/cookbooks/mock_data/files/automate.crt +26 -0
- data/cookbooks/compliance/test/fixtures/cookbooks/mock_data/files/automate.key +27 -0
- data/cookbooks/compliance/test/fixtures/cookbooks/mock_data/files/chef.crt +25 -0
- data/cookbooks/compliance/test/fixtures/cookbooks/mock_data/files/chef.key +27 -0
- data/cookbooks/compliance/test/fixtures/cookbooks/mock_data/files/compliance.crt +26 -0
- data/cookbooks/compliance/test/fixtures/cookbooks/mock_data/files/compliance.key +27 -0
- data/cookbooks/compliance/test/fixtures/cookbooks/mock_data/files/private.pem +27 -0
- data/cookbooks/compliance/test/fixtures/cookbooks/mock_data/files/public.pub +1 -0
- data/cookbooks/compliance/test/fixtures/cookbooks/mock_data/metadata.rb +4 -0
- data/cookbooks/compliance/test/fixtures/cookbooks/mock_data/recipes/default.rb +21 -0
- data/cookbooks/compliance/test/integration/default/compliance.rb +27 -0
- data/cookbooks/infranodes/.gitignore +16 -0
- data/cookbooks/infranodes/.kitchen.ec2.yml +27 -0
- data/cookbooks/infranodes/.kitchen.yml +21 -0
- data/cookbooks/infranodes/Berksfile +6 -0
- data/cookbooks/infranodes/README.md +4 -0
- data/cookbooks/infranodes/attributes/default.rb +3 -0
- data/cookbooks/infranodes/chefignore +102 -0
- data/cookbooks/infranodes/metadata.rb +13 -0
- data/cookbooks/infranodes/recipes/default.rb +41 -0
- data/cookbooks/infranodes/spec/spec_helper.rb +2 -0
- data/cookbooks/infranodes/spec/unit/recipes/default_spec.rb +20 -0
- data/cookbooks/infranodes/templates/default/client.rb.erb +5 -0
- data/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/files/automate.crt +26 -0
- data/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/files/automate.key +27 -0
- data/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/files/chef.crt +25 -0
- data/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/files/chef.key +27 -0
- data/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/files/compliance.crt +26 -0
- data/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/files/compliance.key +27 -0
- data/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/files/private.pem +27 -0
- data/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/files/public.pub +1 -0
- data/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/metadata.rb +3 -0
- data/cookbooks/infranodes/test/fixtures/cookbooks/mock_data/recipes/default.rb +21 -0
- data/cookbooks/infranodes/test/integration/default/infranodes_spec.rb +20 -0
- data/cookbooks/infranodes/test/integration/helpers/serverspec/spec_helper.rb +8 -0
- data/cookbooks/wombat/.gitignore +16 -0
- data/cookbooks/wombat/.kitchen.yml +43 -0
- data/cookbooks/wombat/Berksfile +6 -0
- data/cookbooks/wombat/README.md +4 -0
- data/cookbooks/wombat/attributes/default.rb +71 -0
- data/cookbooks/wombat/attributes/packer.rb +18 -0
- data/cookbooks/wombat/chefignore +102 -0
- data/cookbooks/wombat/metadata.rb +11 -0
- data/cookbooks/wombat/recipes/authorized-keys.rb +10 -0
- data/cookbooks/wombat/recipes/default.rb +112 -0
- data/cookbooks/wombat/recipes/etc-hosts.rb +51 -0
- data/cookbooks/workstation/.gitignore +16 -0
- data/cookbooks/workstation/.kitchen.ec2.yml +29 -0
- data/cookbooks/workstation/.kitchen.yml +22 -0
- data/cookbooks/workstation/Berksfile +7 -0
- data/cookbooks/workstation/README.md +3 -0
- data/cookbooks/workstation/chefignore +102 -0
- data/cookbooks/workstation/files/atom.apm.list +7 -0
- data/cookbooks/workstation/files/atom.config.cson +3 -0
- data/cookbooks/workstation/files/cmder.xml +605 -0
- data/cookbooks/workstation/metadata.rb +14 -0
- data/cookbooks/workstation/recipes/browser.rb +45 -0
- data/cookbooks/workstation/recipes/certs-keys.rb +44 -0
- data/cookbooks/workstation/recipes/chef.rb +29 -0
- data/cookbooks/workstation/recipes/default.rb +16 -0
- data/cookbooks/workstation/recipes/dotnet.rb +17 -0
- data/cookbooks/workstation/recipes/editor.rb +19 -0
- data/cookbooks/workstation/recipes/profile.rb +42 -0
- data/cookbooks/workstation/recipes/terminal.rb +13 -0
- data/cookbooks/workstation/templates/default/bookmarks.html.erb +23 -0
- data/cookbooks/workstation/templates/default/data_collector.rb.erb +2 -0
- data/cookbooks/workstation/templates/default/ise_profile.ps1.erb +11 -0
- data/cookbooks/workstation/templates/default/knife.rb.erb +10 -0
- data/cookbooks/workstation/templates/default/master_preferences.json.erb +28 -0
- data/cookbooks/workstation/templates/default/ssh_config.erb +16 -0
- data/cookbooks/workstation/test/fixtures/cookbooks/mock_data/files/chef-server.crt +26 -0
- data/cookbooks/workstation/test/fixtures/cookbooks/mock_data/files/chef-server.key +27 -0
- data/cookbooks/workstation/test/fixtures/cookbooks/mock_data/files/compliance.crt +26 -0
- data/cookbooks/workstation/test/fixtures/cookbooks/mock_data/files/compliance.key +27 -0
- data/cookbooks/workstation/test/fixtures/cookbooks/mock_data/files/delivery.crt +26 -0
- data/cookbooks/workstation/test/fixtures/cookbooks/mock_data/files/delivery.key +27 -0
- data/cookbooks/workstation/test/fixtures/cookbooks/mock_data/files/private.pem +27 -0
- data/cookbooks/workstation/test/fixtures/cookbooks/mock_data/files/public.pub +1 -0
- data/cookbooks/workstation/test/fixtures/cookbooks/mock_data/metadata.rb +2 -0
- data/cookbooks/workstation/test/fixtures/cookbooks/mock_data/recipes/default.rb +21 -0
- data/cookbooks/workstation/test/integration/default/workstation_spec.rb +37 -0
- data/keys/.gitkeep +0 -0
- data/lib/wombat/build.rb +195 -0
- data/lib/wombat/cli.rb +169 -0
- data/lib/wombat/common.rb +163 -0
- data/lib/wombat/delete.rb +28 -0
- data/lib/wombat/deploy.rb +105 -0
- data/lib/wombat/output.rb +45 -0
- data/lib/wombat/version.rb +3 -0
- data/lib/wombat.rb +8 -0
- data/logs/.gitkeep +0 -0
- data/packer/automate.json +108 -0
- data/packer/build-node.json +114 -0
- data/packer/chef-server.json +106 -0
- data/packer/compliance.json +106 -0
- data/packer/files/.gitkeep +0 -0
- data/packer/infranodes.json +108 -0
- data/packer/mock-data/.gitignore +16 -0
- data/packer/mock-data/.kitchen.yml +21 -0
- data/packer/mock-data/Berksfile +3 -0
- data/packer/mock-data/README.md +4 -0
- data/packer/mock-data/chefignore +102 -0
- data/packer/mock-data/metadata.rb +7 -0
- data/packer/mock-data/recipes/default.rb +69 -0
- data/packer/mock-data/spec/spec_helper.rb +2 -0
- data/packer/mock-data/spec/unit/recipes/default_spec.rb +20 -0
- data/packer/mock-data/test/integration/default/serverspec/default_spec.rb +9 -0
- data/packer/mock-data/test/integration/helpers/serverspec/spec_helper.rb +8 -0
- data/packer/workstation.json +97 -0
- data/templates/bootstrap-aws.erb +36 -0
- data/templates/cfn.json.erb +661 -0
- data/terraform/README.md +13 -0
- data/terraform/templates/terraform.tfvars.erb +12 -0
- data/terraform/wombat.tf +328 -0
- data/wombat-cli.gemspec +32 -0
- data/wombat.example.yml +52 -0
- metadata +331 -0
data/lib/wombat/build.rb
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
|
|
2
|
+
require 'wombat/common'
|
|
3
|
+
require 'mixlib/shellout'
|
|
4
|
+
require 'parallel'
|
|
5
|
+
|
|
6
|
+
class BuildRunner
|
|
7
|
+
|
|
8
|
+
include Common
|
|
9
|
+
|
|
10
|
+
attr_reader :templates, :builder, :config, :parallel
|
|
11
|
+
|
|
12
|
+
def initialize(opts)
|
|
13
|
+
@templates = opts.templates
|
|
14
|
+
@builder = opts.builder.nil? ? "amazon-ebs" : opts.builder
|
|
15
|
+
@config = opts.config
|
|
16
|
+
@parallel = opts.parallel
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def start
|
|
20
|
+
banner("Generating keys (if necessary)")
|
|
21
|
+
wombat['certs'].each do |hostname|
|
|
22
|
+
gen_x509_cert(hostname)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
gen_ssh_key
|
|
26
|
+
|
|
27
|
+
if parallel
|
|
28
|
+
time = Benchmark.measure do
|
|
29
|
+
banner("Starting parallel build for templates: #{templates}")
|
|
30
|
+
parallel_pack(templates, builder)
|
|
31
|
+
end
|
|
32
|
+
else
|
|
33
|
+
time = Benchmark.measure do
|
|
34
|
+
banner("Starting sequential build for templates: #{templates}")
|
|
35
|
+
templates.each do |template|
|
|
36
|
+
options = {}
|
|
37
|
+
# TODO: this needs to be abstracted badly
|
|
38
|
+
case template
|
|
39
|
+
when 'build-node', 'build-nodes'
|
|
40
|
+
vendor_cookbooks('build-node')
|
|
41
|
+
build_nodes.each do |name, num|
|
|
42
|
+
template = 'build-node'
|
|
43
|
+
options = {'node-number' => num}
|
|
44
|
+
packer_cmd = Mixlib::ShellOut.new(packer_build(template, builder, options), :timeout => 3600, live_stream: STDOUT)
|
|
45
|
+
packer_cmd.run_command
|
|
46
|
+
end
|
|
47
|
+
when 'workstation', 'workstations'
|
|
48
|
+
bootstrap_aws
|
|
49
|
+
vendor_cookbooks('workstation')
|
|
50
|
+
workstations.each do |name, num|
|
|
51
|
+
template = 'workstation'
|
|
52
|
+
options = {'workstation-number' => num}
|
|
53
|
+
packer_cmd = Mixlib::ShellOut.new(packer_build(template, builder, options), :timeout => 3600, live_stream: STDOUT)
|
|
54
|
+
packer_cmd.run_command
|
|
55
|
+
end
|
|
56
|
+
when 'infranode', 'infranodes'
|
|
57
|
+
vendor_cookbooks('infranodes')
|
|
58
|
+
infranodes.each do |name, num|
|
|
59
|
+
template = 'infranodes'
|
|
60
|
+
options = {'node-name' => name}
|
|
61
|
+
packer_cmd = Mixlib::ShellOut.new(packer_build(template, builder, options), :timeout => 3600, live_stream: STDOUT)
|
|
62
|
+
packer_cmd.run_command
|
|
63
|
+
end
|
|
64
|
+
else
|
|
65
|
+
vendor_cookbooks(template)
|
|
66
|
+
packer_cmd = Mixlib::ShellOut.new(packer_build(template, builder, options), :timeout => 3600, live_stream: STDOUT)
|
|
67
|
+
packer_cmd.run_command
|
|
68
|
+
puts packer_cmd
|
|
69
|
+
puts packer_cmd.stdout
|
|
70
|
+
puts packer_cmd.stderr unless packer_cmd.stderr.empty?
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
banner("Build finished in #{duration(time.real)}.")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def vendor_cookbooks(template)
|
|
81
|
+
base = template.split('.json')[0].tr('-', '_')
|
|
82
|
+
rm = Mixlib::ShellOut.new("rm -rf vendored-cookbooks/#{base} cookbooks/#{base}/Berksfile.lock", live_stream: STDOUT)
|
|
83
|
+
rm.run_command
|
|
84
|
+
puts "Vendoring cookbooks for #{template}"
|
|
85
|
+
vendor = Mixlib::ShellOut.new("berks vendor -q -b cookbooks/#{base}/Berksfile vendored-cookbooks/#{base}", live_stream: STDOUT)
|
|
86
|
+
vendor.run_command
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def packer_build(template, builder, options={})
|
|
90
|
+
# TODO: this is gross and feels gross so maybe we should do it more better
|
|
91
|
+
create_infranodes_json
|
|
92
|
+
case template
|
|
93
|
+
when 'build-node'
|
|
94
|
+
log_name = "build-node-#{options['node-number']}"
|
|
95
|
+
when 'workstation'
|
|
96
|
+
log_name = "workstation-#{options['workstation-number']}"
|
|
97
|
+
when 'infranodes'
|
|
98
|
+
log_name = "infranodes-#{options['node-name']}"
|
|
99
|
+
else
|
|
100
|
+
log_name = template
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
case builder
|
|
104
|
+
when 'amazon-ebs'
|
|
105
|
+
if template == 'workstation'
|
|
106
|
+
source_ami = wombat['aws']['source_ami']['windows']
|
|
107
|
+
else
|
|
108
|
+
source_ami = wombat['aws']['source_ami']['ubuntu']
|
|
109
|
+
end
|
|
110
|
+
if ENV['AWS_REGION']
|
|
111
|
+
puts "Region set by environment: #{ENV['AWS_REGION']}"
|
|
112
|
+
else
|
|
113
|
+
banner("$AWS_REGION not set, setting to #{wombat['aws']['region']}")
|
|
114
|
+
ENV['AWS_REGION'] = wombat['aws']['region']
|
|
115
|
+
end
|
|
116
|
+
log_prefix = "aws"
|
|
117
|
+
when 'googlecompute'
|
|
118
|
+
if template == 'workstation'
|
|
119
|
+
source_image = wombat['gce']['source_image']['windows']
|
|
120
|
+
else
|
|
121
|
+
source_image = wombat['gce']['source_image']['ubuntu']
|
|
122
|
+
end
|
|
123
|
+
log_prefix = "gce"
|
|
124
|
+
end
|
|
125
|
+
# TODO: fail if packer isn't found in a graceful way
|
|
126
|
+
cmd = %W(packer build #{packer_dir}/#{template}.json | tee #{log_dir}/#{log_prefix}-#{log_name}.log)
|
|
127
|
+
cmd.insert(2, "--only #{builder}")
|
|
128
|
+
cmd.insert(2, "--var org=#{wombat['org']}")
|
|
129
|
+
cmd.insert(2, "--var domain=#{wombat['domain']}")
|
|
130
|
+
cmd.insert(2, "--var domain_prefix=#{wombat['domain_prefix']}")
|
|
131
|
+
cmd.insert(2, "--var enterprise=#{wombat['enterprise']}")
|
|
132
|
+
cmd.insert(2, "--var chefdk=#{wombat['products']['chefdk']}")
|
|
133
|
+
cmd.insert(2, "--var chef_ver=#{wombat['products']['chef'].split('-')[1]}")
|
|
134
|
+
cmd.insert(2, "--var chef_channel=#{wombat['products']['chef'].split('-')[0]}")
|
|
135
|
+
cmd.insert(2, "--var automate=#{wombat['products']['automate']}")
|
|
136
|
+
cmd.insert(2, "--var compliance=#{wombat['products']['compliance']}")
|
|
137
|
+
cmd.insert(2, "--var chef-server=#{wombat['products']['chef-server']}")
|
|
138
|
+
cmd.insert(2, "--var node-name=#{options['node-name']}") if template =~ /infranodes/
|
|
139
|
+
cmd.insert(2, "--var node-number=#{options['node-number']}") if template =~ /build-node/
|
|
140
|
+
cmd.insert(2, "--var build-nodes=#{wombat['build-nodes']}")
|
|
141
|
+
cmd.insert(2, "--var winrm_password=#{wombat['workstation-passwd']}") if template =~ /workstation/
|
|
142
|
+
cmd.insert(2, "--var workstation-number=#{options['workstation-number']}") if template =~ /workstation/
|
|
143
|
+
cmd.insert(2, "--var workstations=#{wombat['workstations']}")
|
|
144
|
+
cmd.insert(2, "--var aws_source_ami=#{source_ami}")
|
|
145
|
+
cmd.insert(2, "--var gce_source_image=#{source_image}")
|
|
146
|
+
cmd.join(' ')
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def parallel_pack(templates, builder)
|
|
150
|
+
proc_hash = {}
|
|
151
|
+
templates.each do |template_name|
|
|
152
|
+
if template_name == 'infranodes'
|
|
153
|
+
infranodes.each do |name, _rl|
|
|
154
|
+
next if name.empty?
|
|
155
|
+
proc_hash[name] = {
|
|
156
|
+
'template' => 'infranodes',
|
|
157
|
+
'options' => {
|
|
158
|
+
'node-name' => name
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
end
|
|
162
|
+
elsif template_name == 'build-node'
|
|
163
|
+
build_nodes.each do |name, num|
|
|
164
|
+
proc_hash[name] = {
|
|
165
|
+
'template' => 'build-node',
|
|
166
|
+
'options' => {
|
|
167
|
+
'node-number' => num
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
end
|
|
171
|
+
elsif template_name == 'workstation'
|
|
172
|
+
workstations.each do |name, num|
|
|
173
|
+
proc_hash[name] = {
|
|
174
|
+
'template' => 'workstation',
|
|
175
|
+
'options' => {
|
|
176
|
+
'workstation-number' => num
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
end
|
|
180
|
+
else
|
|
181
|
+
proc_hash[template_name] = {
|
|
182
|
+
'template' => template_name,
|
|
183
|
+
'options' => {}
|
|
184
|
+
}
|
|
185
|
+
end
|
|
186
|
+
vendor_cookbooks(template_name)
|
|
187
|
+
end
|
|
188
|
+
puts proc_hash
|
|
189
|
+
Parallel.map(proc_hash.keys, in_threads: proc_hash.count) do |name|
|
|
190
|
+
cmd = packer_build(proc_hash[name]['template'], builder, proc_hash[name]['options'])
|
|
191
|
+
packer_cmd = Mixlib::ShellOut.new(cmd, :timeout => 3600, live_stream: STDOUT)
|
|
192
|
+
packer_cmd.run_command
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
data/lib/wombat/cli.rb
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
require 'optparse'
|
|
2
|
+
require 'ostruct'
|
|
3
|
+
|
|
4
|
+
require 'wombat/version'
|
|
5
|
+
require 'wombat/common'
|
|
6
|
+
require 'wombat/build'
|
|
7
|
+
require 'wombat/deploy'
|
|
8
|
+
require 'wombat/output'
|
|
9
|
+
require 'wombat/delete'
|
|
10
|
+
|
|
11
|
+
class Options
|
|
12
|
+
|
|
13
|
+
NAME = File.basename($0).freeze
|
|
14
|
+
|
|
15
|
+
def self.parse(args)
|
|
16
|
+
options = OpenStruct.new
|
|
17
|
+
options.templates = calculate_templates("*.json")
|
|
18
|
+
|
|
19
|
+
global = OptionParser.new do |opts|
|
|
20
|
+
opts.banner = "Usage: #{NAME} [SUBCOMMAND [options]]"
|
|
21
|
+
opts.separator ""
|
|
22
|
+
opts.version = Wombat::VERSION
|
|
23
|
+
opts.separator <<-COMMANDS.gsub(/^ {8}/, "")
|
|
24
|
+
build : build one or more templates
|
|
25
|
+
help : prints this help message
|
|
26
|
+
list : list all templates in project
|
|
27
|
+
deploy : deploy a stack
|
|
28
|
+
outputs : get outputs for a stack
|
|
29
|
+
delete : delete a stack
|
|
30
|
+
COMMANDS
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
templates_argv_proc = proc { |options|
|
|
34
|
+
options.templates = calculate_templates(args) unless args.empty?
|
|
35
|
+
|
|
36
|
+
options.templates.each do |t|
|
|
37
|
+
if !File.exists?("packer/#{t}.json")
|
|
38
|
+
$stderr.puts "File packer/#{t}.json does not exist for template '#{t}'"
|
|
39
|
+
exit(1)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
box_version_argv_proc = proc { |options|
|
|
45
|
+
options.box = ARGV[0]
|
|
46
|
+
options.version = ARGV[1]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
stack_argv_proc = proc { |options|
|
|
50
|
+
options.stack = ARGV[0]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
subcommand = {
|
|
54
|
+
help: {
|
|
55
|
+
parser: OptionParser.new {},
|
|
56
|
+
argv: proc { |options|
|
|
57
|
+
puts global
|
|
58
|
+
exit(0)
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
build: {
|
|
62
|
+
class: BuildRunner,
|
|
63
|
+
parser: OptionParser.new { |opts|
|
|
64
|
+
opts.banner = "Usage: #{NAME} build [options] TEMPLATE[ TEMPLATE ...]"
|
|
65
|
+
|
|
66
|
+
opts.on("-c CONFIG", "--config CONFIG", "Use config file") do |opt|
|
|
67
|
+
options.config = opt
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
opts.on("-o BUILDER", "--only BUILDER", "Use config file") do |opt|
|
|
71
|
+
options.builder = opt
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
opts.on("--parallel", "Build in parallel") do |opt|
|
|
75
|
+
options.parallel = opt
|
|
76
|
+
end
|
|
77
|
+
},
|
|
78
|
+
argv: templates_argv_proc
|
|
79
|
+
},
|
|
80
|
+
list: {
|
|
81
|
+
class: ListRunner,
|
|
82
|
+
parser: OptionParser.new { |opts|
|
|
83
|
+
opts.banner = "Usage: #{NAME} list [TEMPLATE ...]"
|
|
84
|
+
},
|
|
85
|
+
argv: templates_argv_proc
|
|
86
|
+
},
|
|
87
|
+
outputs: {
|
|
88
|
+
class: OutputRunner,
|
|
89
|
+
parser: OptionParser.new { |opts|
|
|
90
|
+
opts.banner = "Usage: #{NAME} outputs [TEMPLATE ...]"
|
|
91
|
+
},
|
|
92
|
+
argv: stack_argv_proc
|
|
93
|
+
},
|
|
94
|
+
deploy: {
|
|
95
|
+
class: DeployRunner,
|
|
96
|
+
parser: OptionParser.new { |opts|
|
|
97
|
+
opts.banner = "Usage: #{NAME} deploy STACK"
|
|
98
|
+
|
|
99
|
+
opts.on("-c CLOUD", "--cloud CLOUD", "Select cloud") do |opt|
|
|
100
|
+
options.cloud = opt
|
|
101
|
+
end
|
|
102
|
+
},
|
|
103
|
+
argv: stack_argv_proc
|
|
104
|
+
},
|
|
105
|
+
delete: {
|
|
106
|
+
class: DeleteRunner,
|
|
107
|
+
parser: OptionParser.new { |opts|
|
|
108
|
+
opts.banner = "Usage: #{NAME} delete STACK"
|
|
109
|
+
|
|
110
|
+
opts.on("-c CLOUD", "--cloud CLOUD", "Select cloud") do |opt|
|
|
111
|
+
options.cloud = opt
|
|
112
|
+
end
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
argv: stack_argv_proc
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
global.order!
|
|
120
|
+
|
|
121
|
+
command = args.empty? ? :help : ARGV.shift.to_sym
|
|
122
|
+
subcommand.fetch(command).fetch(:parser).order!
|
|
123
|
+
subcommand.fetch(command).fetch(:argv).call(options)
|
|
124
|
+
|
|
125
|
+
options.command = command
|
|
126
|
+
options.klass = subcommand.fetch(command).fetch(:class)
|
|
127
|
+
|
|
128
|
+
options
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.calculate_templates(globs)
|
|
132
|
+
Dir.chdir('packer') do
|
|
133
|
+
Array(globs).
|
|
134
|
+
map { |glob| result = Dir.glob("#{glob}"); result.empty? ? glob : result }.
|
|
135
|
+
flatten.
|
|
136
|
+
sort.
|
|
137
|
+
delete_if { |file| file =~ /\.variables\./ }.
|
|
138
|
+
map { |template| template.sub(/\.json$/, '') }
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class ListRunner
|
|
144
|
+
|
|
145
|
+
include Common
|
|
146
|
+
|
|
147
|
+
attr_reader :templates
|
|
148
|
+
|
|
149
|
+
def initialize(opts)
|
|
150
|
+
@templates = opts.templates
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def start
|
|
154
|
+
templates.each { |template| puts template }
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
class Runner
|
|
159
|
+
|
|
160
|
+
attr_reader :options
|
|
161
|
+
|
|
162
|
+
def initialize(options)
|
|
163
|
+
@options = options
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def start
|
|
167
|
+
options.klass.new(options).start
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'erb'
|
|
4
|
+
require 'openssl'
|
|
5
|
+
require 'net/ssh'
|
|
6
|
+
require 'benchmark'
|
|
7
|
+
|
|
8
|
+
module Common
|
|
9
|
+
|
|
10
|
+
def banner(msg)
|
|
11
|
+
puts "==> #{msg}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def info(msg)
|
|
15
|
+
puts " #{msg}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def warn(msg)
|
|
19
|
+
puts ">>> #{msg}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def duration(total)
|
|
23
|
+
total = 0 if total.nil?
|
|
24
|
+
minutes = (total / 60).to_i
|
|
25
|
+
seconds = (total - (minutes * 60))
|
|
26
|
+
format("%dm%.2fs", minutes, seconds)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def wombat
|
|
30
|
+
if !File.exists?('wombat.yml')
|
|
31
|
+
File.open('wombat.yml', 'w') do |f|
|
|
32
|
+
f.puts File.read('wombat.example.yml')
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
YAML.load(File.read('wombat.yml'))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def lock
|
|
39
|
+
JSON.parse(File.read('wombat.lock'))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def bootstrap_aws
|
|
43
|
+
puts 'Generating bootstrap script from template'
|
|
44
|
+
@workstation_passwd = wombat['workstation-passwd']
|
|
45
|
+
rendered = ERB.new(File.read('templates/bootstrap-aws.erb'), nil, '-').result(binding)
|
|
46
|
+
File.open("#{packer_dir}/scripts/bootstrap-aws.txt", 'w') { |file| file.puts rendered }
|
|
47
|
+
puts "#{packer_dir}/scripts/bootstrap-aws.txt"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def gen_x509_cert(hostname)
|
|
51
|
+
rsa_key = OpenSSL::PKey::RSA.new(2048)
|
|
52
|
+
public_key = rsa_key.public_key
|
|
53
|
+
|
|
54
|
+
subject = "/C=AU/ST=New South Wales/L=Sydney/O=#{wombat['org']}/OU=wombats/CN=#{wombat['domain_prefix']}#{hostname}.#{wombat['domain']}"
|
|
55
|
+
|
|
56
|
+
cert = OpenSSL::X509::Certificate.new
|
|
57
|
+
cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject)
|
|
58
|
+
cert.not_before = Time.now
|
|
59
|
+
cert.not_after = Time.now + 365 * 24 * 60 * 60
|
|
60
|
+
cert.public_key = public_key
|
|
61
|
+
cert.serial = 0x0
|
|
62
|
+
cert.version = 2
|
|
63
|
+
|
|
64
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
|
65
|
+
ef.subject_certificate = cert
|
|
66
|
+
ef.issuer_certificate = cert
|
|
67
|
+
cert.extensions = [
|
|
68
|
+
ef.create_extension('basicConstraints', 'CA:TRUE', true),
|
|
69
|
+
ef.create_extension('subjectKeyIdentifier', 'hash'),
|
|
70
|
+
# ef.create_extension("keyUsage", "cRLSign,keyCertSign", true),
|
|
71
|
+
]
|
|
72
|
+
cert.add_extension ef.create_extension('authorityKeyIdentifier',
|
|
73
|
+
'keyid:always,issuer:always')
|
|
74
|
+
|
|
75
|
+
cert.sign(rsa_key, OpenSSL::Digest::SHA256.new)
|
|
76
|
+
|
|
77
|
+
if File.exist?("#{key_dir}/#{hostname}.crt") && File.exist?("#{key_dir}/#{hostname}.key")
|
|
78
|
+
puts "An x509 certificate already exists for #{hostname}"
|
|
79
|
+
else
|
|
80
|
+
File.open("#{key_dir}/#{hostname}.crt", 'w') { |file| file.puts cert.to_pem }
|
|
81
|
+
File.open("#{key_dir}/#{hostname}.key", 'w') { |file| file.puts rsa_key.to_pem }
|
|
82
|
+
puts "Certificate created for #{wombat['domain_prefix']}#{hostname}.#{wombat['domain']}"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def gen_ssh_key
|
|
87
|
+
rsa_key = OpenSSL::PKey::RSA.new 2048
|
|
88
|
+
|
|
89
|
+
type = rsa_key.ssh_type
|
|
90
|
+
data = [rsa_key.to_blob].pack('m0')
|
|
91
|
+
|
|
92
|
+
openssh_format = "#{type} #{data}"
|
|
93
|
+
|
|
94
|
+
if File.exist?("#{key_dir}/public.pub") && File.exist?("#{key_dir}/private.pem")
|
|
95
|
+
puts 'An SSH keypair already exists'
|
|
96
|
+
else
|
|
97
|
+
File.open("#{key_dir}/public.pub", 'w') { |file| file.puts openssh_format }
|
|
98
|
+
File.open("#{key_dir}/private.pem", 'w') { |file| file.puts rsa_key.to_pem }
|
|
99
|
+
puts 'SSH Keypair created'
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def parse_log(instance, cloud)
|
|
104
|
+
case cloud
|
|
105
|
+
when "aws", "amazon", "jeffbezosband", "cfn"
|
|
106
|
+
File.read("#{log_dir}/aws-#{instance}.log").split("\n").grep(/#{wombat['aws']['region']}:/) {|x| x.split[1]}.last
|
|
107
|
+
when "gce", "gcp", "google", "gdm"
|
|
108
|
+
File.read("#{log_dir}/gce-#{instance}.log").split("\n").grep(/A disk image was created:/) {|x| x.split[1]}.last
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def infranodes
|
|
113
|
+
unless wombat['infranodes'].nil?
|
|
114
|
+
wombat['infranodes'].sort
|
|
115
|
+
else
|
|
116
|
+
puts 'No infranodes listed in wombat.yml'
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def build_nodes
|
|
121
|
+
build_nodes = {}
|
|
122
|
+
1.upto(wombat['build-nodes'].to_i) do |i|
|
|
123
|
+
build_nodes["build-node-#{i}"] = i
|
|
124
|
+
end
|
|
125
|
+
build_nodes
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def workstations
|
|
129
|
+
workstations = {}
|
|
130
|
+
1.upto(wombat['workstations'].to_i) do |i|
|
|
131
|
+
workstations["workstation-#{i}"] = i
|
|
132
|
+
end
|
|
133
|
+
workstations
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def create_infranodes_json
|
|
137
|
+
if File.exists?("#{packer_dir}/file/infranodes-info.json")
|
|
138
|
+
current_state = JSON(File.read('files/infranodes-info.json'))
|
|
139
|
+
else
|
|
140
|
+
current_state = nil
|
|
141
|
+
end
|
|
142
|
+
return if current_state == infranodes # yay idempotence
|
|
143
|
+
File.open("#{packer_dir}/files/infranodes-info.json", 'w') do |f|
|
|
144
|
+
f.puts JSON.pretty_generate(infranodes)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def key_dir
|
|
149
|
+
wombat['conf'].nil? ? 'keys' : wombat['conf']['key_dir']
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def cookbook_dir
|
|
153
|
+
wombat['conf'].nil? ? 'cookbooks' : wombat['conf']['cookbook_dir']
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def packer_dir
|
|
157
|
+
wombat['conf'].nil? ? 'packer' : wombat['conf']['packer_dir']
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def log_dir
|
|
161
|
+
wombat['conf'].nil? ? 'logs' : wombat['conf']['log_dir']
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'wombat/common'
|
|
2
|
+
require 'aws-sdk'
|
|
3
|
+
|
|
4
|
+
class DeleteRunner
|
|
5
|
+
include Common
|
|
6
|
+
|
|
7
|
+
attr_reader :stack, :cloud
|
|
8
|
+
|
|
9
|
+
def initialize(opts)
|
|
10
|
+
@stack = opts.stack
|
|
11
|
+
@cloud = opts.cloud.nil? ? "aws" : opts.cloud
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def start
|
|
15
|
+
cfn_delete_stack(stack)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def cfn_delete_stack(stack)
|
|
21
|
+
cfn = Aws::CloudFormation::Client.new(region: lock['aws']['region'])
|
|
22
|
+
|
|
23
|
+
resp = cfn.delete_stack({
|
|
24
|
+
stack_name: stack,
|
|
25
|
+
})
|
|
26
|
+
banner("Deleted #{stack}")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
require 'wombat/common'
|
|
2
|
+
require 'aws-sdk'
|
|
3
|
+
|
|
4
|
+
class DeployRunner
|
|
5
|
+
include Common
|
|
6
|
+
|
|
7
|
+
attr_reader :stack, :cloud
|
|
8
|
+
|
|
9
|
+
def initialize(opts)
|
|
10
|
+
@stack = opts.stack
|
|
11
|
+
@cloud = opts.cloud.nil? ? "aws" : opts.cloud
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def start
|
|
15
|
+
case cloud
|
|
16
|
+
when 'aws'
|
|
17
|
+
update_lock(cloud)
|
|
18
|
+
create_template
|
|
19
|
+
create_stack(stack)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def create_stack(stack)
|
|
26
|
+
template_file = File.read("#{stack}.json")
|
|
27
|
+
cfn = Aws::CloudFormation::Client.new(region: lock['aws']['region'])
|
|
28
|
+
|
|
29
|
+
banner("Creating CloudFormation stack")
|
|
30
|
+
resp = cfn.create_stack({
|
|
31
|
+
stack_name: "#{stack}",
|
|
32
|
+
template_body: template_file,
|
|
33
|
+
capabilities: ["CAPABILITY_IAM"],
|
|
34
|
+
on_failure: "DELETE",
|
|
35
|
+
parameters: [
|
|
36
|
+
{
|
|
37
|
+
parameter_key: "KeyName",
|
|
38
|
+
parameter_value: lock['aws']['keypair'],
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
})
|
|
42
|
+
puts "Created: #{resp.stack_id}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def create_template
|
|
46
|
+
region = lock['aws']['region']
|
|
47
|
+
@chef_server_ami = lock['amis'][region]['chef-server']
|
|
48
|
+
@automate_ami = lock['amis'][region]['automate']
|
|
49
|
+
@compliance_ami = lock['amis'][region]['compliance']
|
|
50
|
+
@build_nodes = lock['build-nodes'].to_i
|
|
51
|
+
@build_node_ami = {}
|
|
52
|
+
1.upto(@build_nodes) do |i|
|
|
53
|
+
@build_node_ami[i] = lock['amis'][region]['build-node'][i.to_s]
|
|
54
|
+
end
|
|
55
|
+
@infra = {}
|
|
56
|
+
infranodes.each do |name, _rl|
|
|
57
|
+
@infra[name] = lock['amis'][region]['infranodes'][name]
|
|
58
|
+
end
|
|
59
|
+
@workstations = lock['workstations'].to_i
|
|
60
|
+
@workstation_ami = {}
|
|
61
|
+
1.upto(@workstations) do |i|
|
|
62
|
+
@workstation_ami[i] = lock['amis'][region]['workstation'][i.to_s]
|
|
63
|
+
end
|
|
64
|
+
@availability_zone = lock['aws']['az']
|
|
65
|
+
@demo = lock['name']
|
|
66
|
+
@version = lock['version']
|
|
67
|
+
@ttl = lock['ttl']
|
|
68
|
+
rendered_cfn = ERB.new(File.read('templates/cfn.json.erb'), nil, '-').result(binding)
|
|
69
|
+
File.open("#{@demo}.json", 'w') { |file| file.puts rendered_cfn }
|
|
70
|
+
banner("Generate CloudFormation JSON: #{@demo}.json")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def update_lock(cloud)
|
|
74
|
+
copy = {}
|
|
75
|
+
copy = wombat
|
|
76
|
+
region = copy[cloud]['region']
|
|
77
|
+
banner('Updating wombat.lock')
|
|
78
|
+
copy['amis'] = { region => {} }
|
|
79
|
+
Dir.glob("#{log_dir}/#{cloud}*.log") do |log|
|
|
80
|
+
instance = log.match('aws-(.*)\.log')[1]
|
|
81
|
+
if instance =~ /build-node/
|
|
82
|
+
copy['amis'][region].store('build-node', {})
|
|
83
|
+
1.upto(wombat['build-nodes'].to_i) do |i|
|
|
84
|
+
copy['amis'][region]['build-node'].store(i.to_s, parse_log("build-node-#{i}", "aws"))
|
|
85
|
+
end
|
|
86
|
+
elsif instance =~ /workstation/
|
|
87
|
+
copy['amis'][region].store('workstation', {})
|
|
88
|
+
1.upto(wombat['workstations'].to_i) do |i|
|
|
89
|
+
copy['amis'][region]['workstation'].store(i.to_s, parse_log("workstation-#{i}", "aws"))
|
|
90
|
+
end
|
|
91
|
+
elsif instance =~ /infranodes/
|
|
92
|
+
copy['amis'][region].store('infranodes', {})
|
|
93
|
+
infranodes.each do |name, _rl|
|
|
94
|
+
copy['amis'][region]['infranodes'].store(name, parse_log("infranodes-#{name}", "aws"))
|
|
95
|
+
end
|
|
96
|
+
else
|
|
97
|
+
copy['amis'][region].store(instance, parse_log(instance, "aws"))
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
copy['last_updated'] = Time.now.gmtime.strftime('%Y%m%d%H%M%S')
|
|
101
|
+
File.open('wombat.lock', 'w') do |f|
|
|
102
|
+
f.write(JSON.pretty_generate(copy))
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'wombat/common'
|
|
2
|
+
require 'aws-sdk'
|
|
3
|
+
|
|
4
|
+
class OutputRunner
|
|
5
|
+
|
|
6
|
+
include Common
|
|
7
|
+
|
|
8
|
+
attr_reader :stack
|
|
9
|
+
|
|
10
|
+
def initialize(opts)
|
|
11
|
+
@stack = opts.stack
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def start
|
|
15
|
+
cfn_workstation_ips(stack)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def cfn_workstation_ips(stack)
|
|
21
|
+
ec2 = Aws::EC2::Resource.new
|
|
22
|
+
instances = cfn_stack_instances(stack)
|
|
23
|
+
instances.each do |name, id|
|
|
24
|
+
instance = ec2.instance(id)
|
|
25
|
+
if /Workstation/.match(name)
|
|
26
|
+
puts "#{name} (#{id}) => #{instance.public_ip_address}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def cfn_stack_instances(stack)
|
|
32
|
+
cfn = Aws::CloudFormation::Client.new
|
|
33
|
+
resp = cfn.describe_stack_resources({
|
|
34
|
+
stack_name: stack,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
instances = {}
|
|
38
|
+
resp.stack_resources.map do |resource|
|
|
39
|
+
if resource.resource_type == 'AWS::EC2::Instance'
|
|
40
|
+
instances[resource.logical_resource_id] = resource.physical_resource_id
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
instances
|
|
44
|
+
end
|
|
45
|
+
end
|