vominator 0.0.1
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 +25 -0
- data/.rspec +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +116 -0
- data/Rakefile +12 -0
- data/bin/vominate +12 -0
- data/circle.yml +5 -0
- data/lib/ec2.rb +12 -0
- data/lib/ec2/instances.rb +362 -0
- data/lib/ec2/security_groups.rb +314 -0
- data/lib/ec2/ssm.rb +81 -0
- data/lib/vominator/aws.rb +15 -0
- data/lib/vominator/constants.rb +53 -0
- data/lib/vominator/ec2.rb +308 -0
- data/lib/vominator/instances.rb +34 -0
- data/lib/vominator/route53.rb +125 -0
- data/lib/vominator/security_groups.rb +26 -0
- data/lib/vominator/ssm.rb +57 -0
- data/lib/vominator/version.rb +3 -0
- data/lib/vominator/vominator.rb +74 -0
- data/lib/vominator/vpc.rb +82 -0
- data/lib/vpc.rb +8 -0
- data/lib/vpc/create.rb +188 -0
- data/spec/lib/instances_spec.rb +2 -0
- data/spec/lib/vominator/aws_spec.rb +6 -0
- data/spec/lib/vominator/ec2_spec.rb +783 -0
- data/spec/lib/vominator/instances_spec.rb +96 -0
- data/spec/lib/vominator/route53_spec.rb +64 -0
- data/spec/lib/vominator/ssm_spec.rb +95 -0
- data/spec/lib/vominator/vominator_spec.rb +209 -0
- data/spec/spec_helper.rb +103 -0
- data/spec/support/matchers/exit_with_code.rb +24 -0
- data/test/puke/cloud-configs/.gitkeep +0 -0
- data/test/puke/cloud-configs/cloud-config-example.erb +63 -0
- data/test/puke/config.yaml +16 -0
- data/test/puke/products/sample-api/instances.yaml +37 -0
- data/test/puke/products/sample-api/security_groups.yaml +19 -0
- data/test/vominator.yaml +7 -0
- data/vominator.gemspec +34 -0
- metadata +259 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require_relative 'vominator'
|
3
|
+
require_relative 'constants'
|
4
|
+
|
5
|
+
module Vominator
|
6
|
+
class SecurityGroups
|
7
|
+
|
8
|
+
def self.get_security_groups(environment, product, filter=false)
|
9
|
+
if PUKE_CONFIG[environment]['products'].include? product
|
10
|
+
config_file = File.expand_path("#{VOMINATOR_CONFIG['configuration_path']}/products/#{product}/security_groups.yaml")
|
11
|
+
if filter
|
12
|
+
security_groups = Array.new
|
13
|
+
YAML.load(File.read(config_file)).each do |security_group|
|
14
|
+
if filter.include? security_group.keys[0]
|
15
|
+
security_groups.push security_group
|
16
|
+
end
|
17
|
+
end
|
18
|
+
else
|
19
|
+
security_groups = YAML.load(File.read(config_file))
|
20
|
+
end
|
21
|
+
return security_groups if security_groups.kind_of?(Array)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require_relative 'constants'
|
3
|
+
|
4
|
+
module Vominator
|
5
|
+
class SSM
|
6
|
+
|
7
|
+
def self.get_documents(client,max_results=25)
|
8
|
+
resp = client.list_documents(:max_results => max_results)
|
9
|
+
documents = resp[:document_identifiers]
|
10
|
+
while resp[:next_token]
|
11
|
+
resp = client.list_documents(:next_token => resp[:next_token])
|
12
|
+
documents += resp[:document_identifiers]
|
13
|
+
end
|
14
|
+
|
15
|
+
return documents.map {|doc| doc.name }
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.describe_document(client,name)
|
19
|
+
return client.describe_document(:name => name).document
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.get_document(client,name)
|
23
|
+
return client.get_document(:name => name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.put_document(client,name,data)
|
27
|
+
client.create_document(:name => name, :content => data)
|
28
|
+
sleep 2 until Vominator::SSM.describe_document(client,name).status == 'active'
|
29
|
+
|
30
|
+
if Vominator::SSM.describe_document(client,name).status == 'active'
|
31
|
+
return true
|
32
|
+
else
|
33
|
+
return false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.associated?(client,name,instance_id)
|
38
|
+
begin
|
39
|
+
client.describe_association(:name => name, :instance_id => instance_id)
|
40
|
+
return true
|
41
|
+
rescue Aws::SSM::Errors::AssociationDoesNotExist
|
42
|
+
return false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.create_association(client,name,instance_id)
|
47
|
+
client.create_association(:name => name, :instance_id => instance_id)
|
48
|
+
sleep 2 until Vominator::SSM.associated?(client,name,instance_id)
|
49
|
+
|
50
|
+
if Vominator::SSM.associated?(client,name,instance_id)
|
51
|
+
return true
|
52
|
+
else
|
53
|
+
return false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'colored'
|
3
|
+
require 'highline/import'
|
4
|
+
require_relative 'version'
|
5
|
+
|
6
|
+
module Vominator
|
7
|
+
def self.get_config(file='~/.vominator.yaml')
|
8
|
+
config_file = ENV['VOMINATOR_CONFIG'] || File.expand_path(file)
|
9
|
+
if File.exist?(config_file)
|
10
|
+
vominator_config = YAML.load(File.read(config_file))
|
11
|
+
return vominator_config if vominator_config.kind_of?(Hash)
|
12
|
+
else
|
13
|
+
LOGGER.fatal("Unable to load vominator configuration file from #{config_file}")
|
14
|
+
return false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.get_puke_config(puke_dir)
|
19
|
+
if File.exist?(puke_dir)
|
20
|
+
config_file = "#{puke_dir}/config.yaml"
|
21
|
+
puke_config = YAML.load(File.read(config_file))
|
22
|
+
else
|
23
|
+
raise("Unable to open puke configuration at #{puke_dir}")
|
24
|
+
end
|
25
|
+
return puke_config if puke_config.kind_of?(Hash)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.get_key_pair(vominator_config)
|
29
|
+
return vominator_config['key_pair_name']
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.get_puke_variables(environment)
|
33
|
+
data = PUKE_CONFIG[environment]
|
34
|
+
return data
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.yesno?(prompt: 'Continue?', default: true)
|
38
|
+
a = ''
|
39
|
+
s = default ? '[Y/n]' : '[y/N]'
|
40
|
+
d = default ? 'y' : 'n'
|
41
|
+
until %w[y n].include? a
|
42
|
+
a = ask("#{prompt} #{s} ") { |q| q.limit = 1; q.case = :downcase }
|
43
|
+
a = d if a.length == 0
|
44
|
+
end
|
45
|
+
a == 'y'
|
46
|
+
end
|
47
|
+
|
48
|
+
class Logger
|
49
|
+
def self.info(message)
|
50
|
+
puts message
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.test(message)
|
54
|
+
puts message.cyan
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.error(message)
|
58
|
+
puts message.red
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.fatal(message)
|
62
|
+
puts message.red
|
63
|
+
exit(1)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.success(message)
|
67
|
+
puts message.green
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.warning(message)
|
71
|
+
puts message.yellow
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require_relative 'constants'
|
3
|
+
|
4
|
+
module Vominator
|
5
|
+
class VPC
|
6
|
+
def self.get_vpc(client, vpc_id)
|
7
|
+
return client.describe_vpcs(filters: [{name: 'vpc-id', values: [vpc_id]}]).vpcs.first
|
8
|
+
end
|
9
|
+
def self.get_vpc_by_cidr(client, cidr_block)
|
10
|
+
return client.describe_vpcs(filters: [{name: 'cidr', values: [cidr_block]}]).vpcs.first
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.create_vpc(client, cidr_block, tenancy='default')
|
14
|
+
resp = client.create_vpc(:cidr_block => cidr_block, :instance_tenancy => tenancy)
|
15
|
+
sleep 2 until Vominator::VPC.get_vpc(client,resp.vpc.vpc_id).state == 'available'
|
16
|
+
return resp.vpc
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.get_internet_gateway(client, gateway_id)
|
20
|
+
return client.describe_internet_gateways(filters: [{name: 'internet-gateway-id', values: [gateway_id]}]).internet_gateways.first
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.create_internet_gateway(client)
|
24
|
+
resp = client.create_internet_gateway
|
25
|
+
return resp.internet_gateway
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.attach_internet_gateway(client, gateway_id, vpc_id)
|
29
|
+
resp = client.attach_internet_gateway(internet_gateway_id: gateway_id, vpc_id: vpc_id)
|
30
|
+
sleep 2 until Vominator::VPC.get_internet_gateway(client, gateway_id).attachments.first.state == 'available'
|
31
|
+
return true
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.get_nat_gateway(client, gateway_id)
|
35
|
+
return client.describe_nat_gateways(filter: [{name: 'nat-gateway-id', values: [gateway_id]}]).nat_gateways.first
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.create_nat_gateway(client, subnet_id, allocation_id)
|
39
|
+
resp = client.create_nat_gateway(subnet_id: subnet_id, allocation_id: allocation_id).nat_gateway
|
40
|
+
sleep 2 until Vominator::VPC.get_nat_gateway(client, resp.nat_gateway_id).state == 'available'
|
41
|
+
return resp
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.get_route_tables(client, vpc_id)
|
45
|
+
return client.describe_route_tables(filters: [{name: 'vpc-id', values: [vpc_id]}]).route_tables
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.get_route_table(client, route_table_id)
|
49
|
+
return client.describe_route_tables(filters: [{name: 'route-table-id', values: [route_table_id]}]).route_tables.first
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.create_internet_gateway_route(client, route_table_id, destination_cidr_block, gateway_id)
|
53
|
+
return client.create_route(route_table_id: route_table_id, destination_cidr_block: destination_cidr_block, gateway_id: gateway_id)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.create_nat_gateway_route(client, route_table_id, destination_cidr_block, nat_gateway_id)
|
57
|
+
return client.create_route(route_table_id: route_table_id, destination_cidr_block: destination_cidr_block, nat_gateway_id: nat_gateway_id)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.create_route_table(client, vpc_id)
|
61
|
+
resp = client.create_route_table(vpc_id: vpc_id).route_table
|
62
|
+
sleep 2 until Vominator::VPC.get_route_table(client, resp.route_table_id)
|
63
|
+
return resp
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.get_subnet(client, subnet_id)
|
67
|
+
return client.describe_subnets(filters: [{name: 'subnet-id', values: [subnet_id]}]).subnets.first
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.create_subnet(client, vpc_id, cidr_block, availability_zone)
|
71
|
+
resp = client.create_subnet(vpc_id: vpc_id, cidr_block: cidr_block, availability_zone: availability_zone).subnet
|
72
|
+
sleep 2 until Vominator::VPC.get_subnet(client, resp.subnet_id).state == 'available'
|
73
|
+
return resp
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.associate_route_table(client, subnet_id, route_table_id)
|
77
|
+
return client.associate_route_table(subnet_id: subnet_id, route_table_id: route_table_id)
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
data/lib/vpc.rb
ADDED
data/lib/vpc/create.rb
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
require 'colored'
|
4
|
+
require_relative '../vominator/constants'
|
5
|
+
require_relative '../vominator/aws'
|
6
|
+
require_relative '../vominator/vpc'
|
7
|
+
require_relative '../vominator/ec2'
|
8
|
+
require_relative '../vominator/route53'
|
9
|
+
|
10
|
+
options = {}
|
11
|
+
|
12
|
+
OptionParser.new do |opts|
|
13
|
+
opts.banner = 'Usage: vominate vpc create [options]'.yellow
|
14
|
+
|
15
|
+
opts.on('-eENVIRONMENT', '--environment ENVIRONMENT', 'REQUIRED: The environment which you want to create a VPC for. IE foo') do |value|
|
16
|
+
options[:environment] = value
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on('--region Region', 'REQUIRED: The AWS Region that you want to create the VPC in. IE us-east-1') do |value|
|
20
|
+
options[:region] = value
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on('--availability-zones AVAILABILITY ZONES', 'OPTIONAL: A comma delimited list of specific availability zones that you want to prepare. If you don\'t specify then we will use all that are available. IE us-east-1c,us-east-1d,us-east-1e') do |value|
|
24
|
+
options[:availability_zones] = value
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on('--parent-domain PARENT DOMAIN', 'REQUIRED: The parent domain name that will be used to create a seperate subdomain zone file for the new environment. IE, if you provide foo.org and your environment as bar, this will yield a new Route 53 zone file called bar.foo.org') do |value|
|
28
|
+
options[:parent_domain] = value
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on('--cidr-block CIDR Block', 'REQUIRED: The network block for the new environment. This must be a /16 and the second octet should be unique for this environment. IE. 10.123.0.0/16') do |value|
|
32
|
+
options[:cidr_block] = value
|
33
|
+
end
|
34
|
+
|
35
|
+
#opts.on('-t', '--test', 'OPTIONAL: Test run. Show what would be changed without making any actual changes') do
|
36
|
+
# options[:test] = true
|
37
|
+
#end
|
38
|
+
|
39
|
+
#opts.on('-l', '--list', 'OPTIONAL: List out Vominator aware VPCs') do
|
40
|
+
# options[:list] = true
|
41
|
+
#end
|
42
|
+
|
43
|
+
opts.on('-d', '--debug', 'OPTIONAL: debug output') do
|
44
|
+
options[:debug] = true
|
45
|
+
end
|
46
|
+
|
47
|
+
opts.on_tail(:NONE, '-h', '--help', 'OPTIONAL: Display this screen') do
|
48
|
+
puts opts
|
49
|
+
exit
|
50
|
+
end
|
51
|
+
|
52
|
+
begin
|
53
|
+
opts.parse!
|
54
|
+
## Validate Data Inputs
|
55
|
+
throw Exception unless ((options.include? :environment) && (options.include? :region) && (options.include? :parent_domain) && (options.include? :cidr_block)) || options[:list]
|
56
|
+
rescue
|
57
|
+
puts opts
|
58
|
+
exit
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
TEST = options[:test]
|
63
|
+
def test?(message)
|
64
|
+
LOGGER.test(message) if TEST
|
65
|
+
TEST
|
66
|
+
end
|
67
|
+
|
68
|
+
unless test?('Vominator is running in test mode. It will NOT make any changes.')
|
69
|
+
LOGGER.warning('WARNING: Vominator will make changes to your environment. Please run test mode first if you are unsure.')
|
70
|
+
unless Vominator.yesno?(prompt: 'Do you wish to proceed?', default: false)
|
71
|
+
exit(1)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
puke_config = Vominator.get_puke_variables(options[:environment])
|
76
|
+
|
77
|
+
if puke_config
|
78
|
+
LOGGER.fatal("An environment with the name of #{options[:environment]} is already defined. Please choose a different name")
|
79
|
+
else
|
80
|
+
puke_config = Hash.new
|
81
|
+
puke_config['region_name'] = options[:region]
|
82
|
+
end
|
83
|
+
|
84
|
+
ec2_client = Aws::EC2::Client.new(region: puke_config['region_name'])
|
85
|
+
r53_client = Aws::Route53::Client.new(region: puke_config['region_name'])
|
86
|
+
|
87
|
+
|
88
|
+
fqdn = "#{options[:environment]}.#{options[:parent_domain]}"
|
89
|
+
|
90
|
+
# Couple of sanity checks before we get started....
|
91
|
+
if Vominator::VPC.get_vpc_by_cidr(ec2_client, options[:cidr_block])
|
92
|
+
LOGGER.warning("A VPC already exists with a netblock of #{options[:cidr_block]}. Generally you want these to be unique")
|
93
|
+
unless Vominator.yesno?(prompt: 'Do you wish to proceed?', default: false)
|
94
|
+
exit(1)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
if Vominator::Route53.get_zone_by_domain(r53_client, fqdn)
|
99
|
+
LOGGER.fatal("A Zonefile already exists for #{fqdn}. Please choose a different environment name")
|
100
|
+
end
|
101
|
+
|
102
|
+
if options[:availability_zones]
|
103
|
+
availability_zones = options[:availability_zones].split(',')
|
104
|
+
else
|
105
|
+
availability_zones = Vominator::AWS.get_availability_zones(ec2_client)
|
106
|
+
end
|
107
|
+
|
108
|
+
parent_zone = Vominator::Route53.get_zone_by_domain(r53_client, options[:parent_domain])
|
109
|
+
|
110
|
+
unless parent_zone
|
111
|
+
LOGGER.warning("We could not find the parent zone of #{options[:parent_domain]}. You can proceed and we will provide the settings so you can manually create the entry.")
|
112
|
+
unless Vominator.yesno?(prompt: 'Do you wish to proceed?', default: false)
|
113
|
+
exit(1)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
LOGGER.info("Starting the creation process. This may take a couple of minutes")
|
119
|
+
|
120
|
+
environment_zone = Vominator::Route53.create_zone(r53_client, fqdn)
|
121
|
+
|
122
|
+
if parent_zone
|
123
|
+
Vominator::Route53.create_nameserver_records(r53_client,parent_zone.id, fqdn, environment_zone.delegation_set.name_servers)
|
124
|
+
end
|
125
|
+
|
126
|
+
vpc = Vominator::VPC.create_vpc(ec2_client,options[:cidr_block])
|
127
|
+
|
128
|
+
gateway = Vominator::VPC.create_internet_gateway(ec2_client)
|
129
|
+
|
130
|
+
Vominator::VPC.attach_internet_gateway(ec2_client, gateway.internet_gateway_id, vpc.vpc_id)
|
131
|
+
|
132
|
+
public_route_table = Vominator::VPC.get_route_tables(ec2_client, vpc.vpc_id).first
|
133
|
+
Vominator::EC2.tag_resource(ec2_client, public_route_table.route_table_id,[{key: 'Name', value: "dmz-#{options[:environment]}-#{options[:region]}"}])
|
134
|
+
|
135
|
+
Vominator::VPC.create_internet_gateway_route(ec2_client, public_route_table.route_table_id, '0.0.0.0/0', gateway.internet_gateway_id)
|
136
|
+
|
137
|
+
third_octet = 1
|
138
|
+
|
139
|
+
route_tables = Hash.new
|
140
|
+
route_tables['public'] = public_route_table.route_table_id
|
141
|
+
|
142
|
+
availability_zones.each do |zone|
|
143
|
+
|
144
|
+
private_route_table = Vominator::VPC.create_route_table(ec2_client, vpc.vpc_id)
|
145
|
+
route_tables[zone] = private_route_table.route_table_id
|
146
|
+
|
147
|
+
Vominator::EC2.tag_resource(ec2_client, private_route_table.route_table_id,[{key: 'Name', value: "nat-#{options[:environment]}-#{zone}"}])
|
148
|
+
|
149
|
+
public_subnet_cidr_block = "#{options[:cidr_block].split('.')[0]}.#{options[:cidr_block].split('.')[1]}.#{third_octet}.0/24"
|
150
|
+
public_subnet = Vominator::VPC.create_subnet(ec2_client, vpc.vpc_id, public_subnet_cidr_block, zone)
|
151
|
+
|
152
|
+
Vominator::VPC.associate_route_table(ec2_client, public_subnet.subnet_id, public_route_table.route_table_id)
|
153
|
+
|
154
|
+
public_ip = Vominator::EC2.allocate_public_ip(ec2_client)
|
155
|
+
nat_gateway = Vominator::VPC.create_nat_gateway(ec2_client, public_subnet.subnet_id, public_ip.allocation_id)
|
156
|
+
|
157
|
+
Vominator::VPC.create_nat_gateway_route(ec2_client, private_route_table.route_table_id, '0.0.0.0/0', nat_gateway.nat_gateway_id)
|
158
|
+
|
159
|
+
third_octet += 1
|
160
|
+
end
|
161
|
+
|
162
|
+
unless parent_zone
|
163
|
+
LOGGER.warning("Parent zone not was not found in Route53. Use the below information to create the proper entries in your parent zone.")
|
164
|
+
LOGGER.warning("FQDN: #{fqdn}")
|
165
|
+
LOGGER.warning("Record Type: NS")
|
166
|
+
LOGGER.warning("Nameservers: #{environment_zone.delegation_set.name_servers}")
|
167
|
+
end
|
168
|
+
|
169
|
+
config = {
|
170
|
+
options[:environment] => {
|
171
|
+
'vpc_id' => vpc.vpc_id,
|
172
|
+
'route_tables' => route_tables,
|
173
|
+
'region_name' => options[:region],
|
174
|
+
'zone' => environment_zone.hosted_zone.id.split('/')[2],
|
175
|
+
'octet' => options[:cidr_block].split('.')[1],
|
176
|
+
'domain' => fqdn,
|
177
|
+
'linux_cloud_config_template' => 'EDIT_ME',
|
178
|
+
'linux_paravirtual_base_image' => 'EDIT_ME',
|
179
|
+
'linux_hvm_base_image' => 'EDIT_ME',
|
180
|
+
'windows_cloud_config_template' => 'EDIT_ME',
|
181
|
+
'windows_paravirtual_base_image' => 'EDIT_ME',
|
182
|
+
'windows_hvm_base_image' => 'EDIT_ME',
|
183
|
+
'chef_host' => 'EDIT_ME',
|
184
|
+
'products' => []
|
185
|
+
}}
|
186
|
+
|
187
|
+
LOGGER.success("Append the below to your config.yaml file in your puke directory")
|
188
|
+
LOGGER.info(config.to_yaml)
|