hashicorptools 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +23 -0
- data/Gemfile.lock +119 -0
- data/LICENSE +22 -0
- data/README.md +21 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/bin/ec2_host +7 -0
- data/data/standard-ami.json +48 -0
- data/hashicorptools.gemspec +97 -0
- data/lib/hashicorptools/auto_scaling_group.rb +149 -0
- data/lib/hashicorptools/code_deploy.rb +58 -0
- data/lib/hashicorptools/ec2_utilities.rb +37 -0
- data/lib/hashicorptools/host.rb +114 -0
- data/lib/hashicorptools/packer.rb +129 -0
- data/lib/hashicorptools/terraform.rb +283 -0
- data/lib/hashicorptools/update_launch_configuration.rb +22 -0
- data/lib/hashicorptools/variables.rb +12 -0
- data/lib/hashicorptools.rb +19 -0
- data/spec/hashicorptools_spec.rb +7 -0
- data/spec/spec_helper.rb +29 -0
- metadata +248 -0
@@ -0,0 +1,114 @@
|
|
1
|
+
module Hashicorptools
|
2
|
+
class Host < Thor
|
3
|
+
|
4
|
+
desc 'hosts', 'list running instances'
|
5
|
+
option :environment, required: false
|
6
|
+
option :role, required: false
|
7
|
+
option :name, required: false
|
8
|
+
def hosts
|
9
|
+
ec2 = Aws::EC2::Client.new(region: 'us-east-1')
|
10
|
+
|
11
|
+
resp = ec2.describe_instances(filters: filters)
|
12
|
+
resp.reservations.each do |reservation|
|
13
|
+
reservation.instances.each do |instance|
|
14
|
+
name = instance.tags.find{|t| t.key == 'Name'}.value
|
15
|
+
puts "#{name} #{instance.public_dns_name}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
desc 'ssh', 'ssh to the first matching instance'
|
21
|
+
option :environment, required: false
|
22
|
+
option :role, required: false
|
23
|
+
option :name, required: false
|
24
|
+
def ssh(role = '')
|
25
|
+
ec2 = Aws::EC2::Client.new(region: 'us-east-1')
|
26
|
+
|
27
|
+
resp = ec2.describe_instances(filters: filters(role))
|
28
|
+
if resp.reservations.any?
|
29
|
+
instance = resp.reservations.first.instances.first
|
30
|
+
dns = if instance.public_dns_name.present?
|
31
|
+
instance.public_dns_name
|
32
|
+
else
|
33
|
+
instance.private_dns_name
|
34
|
+
end
|
35
|
+
|
36
|
+
exec "ssh #{ssh_user_fragment}#{dns}"
|
37
|
+
else
|
38
|
+
puts "no instances with #{role} role found"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
desc 'console', 'run the agra rails console'
|
43
|
+
option :environment, required: false
|
44
|
+
def console
|
45
|
+
ec2 = Aws::EC2::Client.new(region: 'us-east-1')
|
46
|
+
|
47
|
+
bastion_dns = dns_from_reservations(ec2.describe_instances(filters: filters('console', 'maintenance')))
|
48
|
+
agra_console_dns = dns_from_reservations(ec2.describe_instances(filters: filters('console', 'agra')))
|
49
|
+
|
50
|
+
|
51
|
+
if bastion_dns && agra_console_dns
|
52
|
+
exec "ssh -t #{ssh_user_fragment}#{bastion_dns} 'ssh -t #{ssh_user_fragment}#{agra_console_dns}'"
|
53
|
+
else
|
54
|
+
puts "no instances found"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def dns_from_reservations(resp)
|
61
|
+
if resp.reservations.any?
|
62
|
+
instance = resp.reservations.first.instances.first
|
63
|
+
if instance.public_dns_name.present?
|
64
|
+
instance.public_dns_name
|
65
|
+
else
|
66
|
+
instance.private_dns_name
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
def application_environment
|
73
|
+
if options[:environment].present?
|
74
|
+
options[:environment]
|
75
|
+
elsif ENV['CHANGESPROUT_APP_ENVIRONMENT'].present?
|
76
|
+
ENV['CHANGESPROUT_APP_ENVIRONMENT']
|
77
|
+
else
|
78
|
+
'staging'
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def ssh_user_fragment
|
83
|
+
ENV['AWS_SSH_USERNAME'].present? ? "#{ENV['AWS_SSH_USERNAME']}@" : ''
|
84
|
+
end
|
85
|
+
|
86
|
+
def filters(role = '', kind='')
|
87
|
+
filters = []
|
88
|
+
|
89
|
+
filters << {name: 'instance-state-name', values: ['running']}
|
90
|
+
|
91
|
+
if application_environment.present?
|
92
|
+
filters << {name: 'tag:environment', values: [ application_environment ]}
|
93
|
+
end
|
94
|
+
|
95
|
+
if options[:name].present?
|
96
|
+
filters << {name: 'tag:Name', values: [ options[:name] ]}
|
97
|
+
end
|
98
|
+
|
99
|
+
if role.blank?
|
100
|
+
role = options[:role].present?
|
101
|
+
end
|
102
|
+
|
103
|
+
if role.present?
|
104
|
+
filters << {name: 'tag:role', values: [ role ]}
|
105
|
+
end
|
106
|
+
|
107
|
+
if kind.present?
|
108
|
+
filters << {name: 'tag:kind', values: [ kind ]}
|
109
|
+
end
|
110
|
+
|
111
|
+
filters
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Hashicorptools
|
2
|
+
NUMBER_OF_AMIS_TO_KEEP = 2
|
3
|
+
|
4
|
+
class Packer < Thor
|
5
|
+
include Ec2Utilities
|
6
|
+
include Variables
|
7
|
+
|
8
|
+
desc "build", "creates an AMI from the current config"
|
9
|
+
option :debug, :required => false
|
10
|
+
def build
|
11
|
+
_build
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "validate", "validates the packer config"
|
15
|
+
def validate
|
16
|
+
system "packer validate #{ami_config_path}"
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "console", "interactive session"
|
20
|
+
def console
|
21
|
+
require 'byebug'
|
22
|
+
byebug
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "list", "list all available telize amis"
|
26
|
+
def list
|
27
|
+
amis.each do |ami|
|
28
|
+
puts ami.image_id
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "clean", "clean old AMIs that are no longer needed"
|
33
|
+
def clean
|
34
|
+
clean_amis
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "boot", "start up an instance of the latest version of AMI"
|
38
|
+
def boot
|
39
|
+
run_instances_resp = ec2.run_instances(image_id: current_ami('base-image').image_id,
|
40
|
+
min_count: 1,
|
41
|
+
max_count: 1,
|
42
|
+
instance_type: "t2.micro")
|
43
|
+
|
44
|
+
ec2.create_tags( resources: run_instances_resp.instances.collect{|i| i.instance_id },
|
45
|
+
tags: [ {key: 'Name', value: "packer test boot #{tag_name}"}, {key: 'environment', value: 'packer-development'}, {key: 'temporary', value: 'kill me'}])
|
46
|
+
|
47
|
+
require 'byebug'
|
48
|
+
byebug
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
|
53
|
+
def _build(settings_overrides={})
|
54
|
+
settings_overrides.merge!({source_ami: source_ami_id, ami_tag: tag_name, cookbook_name: cookbook_name})
|
55
|
+
|
56
|
+
if options[:debug]
|
57
|
+
puts "[DEBUG] Executing 'packer build -debug #{variables(settings_overrides)} #{ami_config_path}'"
|
58
|
+
system "packer build -debug \
|
59
|
+
#{variables(settings_overrides)} \
|
60
|
+
#{ami_config_path}"
|
61
|
+
else
|
62
|
+
system "packer build \
|
63
|
+
#{variables(settings_overrides)} \
|
64
|
+
#{ami_config_path}"
|
65
|
+
end
|
66
|
+
|
67
|
+
clean_amis
|
68
|
+
end
|
69
|
+
|
70
|
+
def source_ami_id
|
71
|
+
current_ami('base-image').image_id
|
72
|
+
end
|
73
|
+
|
74
|
+
def tag_name
|
75
|
+
raise 'implement me'
|
76
|
+
end
|
77
|
+
|
78
|
+
def cookbook_name
|
79
|
+
raise 'implement me'
|
80
|
+
end
|
81
|
+
|
82
|
+
def ami_config_path
|
83
|
+
datadir_path = Gem.datadir('hashicorptools')
|
84
|
+
File.join(datadir_path, 'standard-ami.json')
|
85
|
+
end
|
86
|
+
|
87
|
+
def auto_scaling
|
88
|
+
@auto_scaling ||= Aws::AutoScaling::Client.new(region: 'us-east-1')
|
89
|
+
end
|
90
|
+
|
91
|
+
def ec2_v2
|
92
|
+
@ec2 ||= Aws::EC2::Client.new(region: 'us-east-1')
|
93
|
+
end
|
94
|
+
|
95
|
+
def amis_in_use
|
96
|
+
launch_configs = auto_scaling.describe_launch_configurations
|
97
|
+
image_ids = launch_configs.data['launch_configurations'].collect{|lc| lc.image_id}.flatten
|
98
|
+
|
99
|
+
ec2_reservations = ec2_v2.describe_instances
|
100
|
+
image_ids << ec2_reservations.reservations.collect{|res| res.instances.collect{|r| r.image_id}}.flatten
|
101
|
+
image_ids.flatten
|
102
|
+
end
|
103
|
+
|
104
|
+
def clean_amis
|
105
|
+
ami_ids = amis.collect{|a| a.image_id}
|
106
|
+
ami_ids_to_remove = ami_ids - amis_in_use
|
107
|
+
potential_amis_to_remove = amis
|
108
|
+
potential_amis_to_remove.keep_if {|a| ami_ids_to_remove.include?(a.image_id) }
|
109
|
+
|
110
|
+
if potential_amis_to_remove.size > NUMBER_OF_AMIS_TO_KEEP
|
111
|
+
amis_to_remove = potential_amis_to_remove[NUMBER_OF_AMIS_TO_KEEP..-1]
|
112
|
+
amis_to_keep = potential_amis_to_remove[0..(NUMBER_OF_AMIS_TO_KEEP-1)]
|
113
|
+
|
114
|
+
puts "Deregistering old AMIs..."
|
115
|
+
amis_to_remove.each do |ami|
|
116
|
+
puts "Deregistering #{ami.image_id}"
|
117
|
+
ami.deregister
|
118
|
+
end
|
119
|
+
|
120
|
+
puts "Currently active AMIs..."
|
121
|
+
amis_to_keep.each do |ami|
|
122
|
+
puts ami.image_id
|
123
|
+
end
|
124
|
+
else
|
125
|
+
puts "no AMIs to clean."
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,283 @@
|
|
1
|
+
module Hashicorptools
|
2
|
+
class Terraform < Thor
|
3
|
+
TERRAFORM_VERSION = '0.6.8'
|
4
|
+
|
5
|
+
include Ec2Utilities
|
6
|
+
include Variables
|
7
|
+
|
8
|
+
desc 'bootstrap', 'terraform a new infrastructure from scratch'
|
9
|
+
option :environment, :required => true
|
10
|
+
def bootstrap
|
11
|
+
apply
|
12
|
+
end
|
13
|
+
|
14
|
+
[:apply, :plan, :destroy, :pull, :refresh].each do |cmd|
|
15
|
+
desc cmd, "terraform #{cmd}"
|
16
|
+
option :environment, :required => true
|
17
|
+
option :debug, :required => false
|
18
|
+
|
19
|
+
define_method cmd do
|
20
|
+
send("_#{cmd}")
|
21
|
+
end
|
22
|
+
|
23
|
+
no_commands do
|
24
|
+
define_method "_#{cmd}" do |settings_overrides = {}|
|
25
|
+
enforce_version!
|
26
|
+
raise 'invalid environment' unless ['staging', 'production'].include?(options[:environment])
|
27
|
+
|
28
|
+
settings_overrides
|
29
|
+
.merge!({ app_environment: options[:environment] }
|
30
|
+
.merge(env_variable_keys)
|
31
|
+
.merge(settings)
|
32
|
+
.merge(shared_plan_variables))
|
33
|
+
|
34
|
+
decrypt_file(state_path)
|
35
|
+
decrypt_file(var_file_path)
|
36
|
+
|
37
|
+
|
38
|
+
begin
|
39
|
+
send("before_#{cmd}")
|
40
|
+
|
41
|
+
terraform_command = "terraform #{cmd} #{variables(settings_overrides)} -state #{state_path} #{var_file_param} #{config_directory}"
|
42
|
+
|
43
|
+
if (options[:debug])
|
44
|
+
puts "[DEBUG] running command: '#{terraform_command}"
|
45
|
+
end
|
46
|
+
if system terraform_command
|
47
|
+
send("after_#{cmd}")
|
48
|
+
end
|
49
|
+
rescue StandardError => e
|
50
|
+
puts e.message
|
51
|
+
puts e.backtrace
|
52
|
+
ensure
|
53
|
+
# need to always ensure the most recent tfstate is encrypted again.
|
54
|
+
encrypt_file(state_path)
|
55
|
+
delete_decrypted_var_file
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
define_method "before_#{cmd}" do
|
61
|
+
# no-op
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
no_commands do
|
66
|
+
define_method "after_#{cmd}" do
|
67
|
+
# no-op
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
desc cmd, "terraform #{cmd} for shared plan"
|
72
|
+
define_method "shared_#{cmd}" do
|
73
|
+
enforce_version!
|
74
|
+
|
75
|
+
decrypt_file(shared_state_path)
|
76
|
+
|
77
|
+
begin
|
78
|
+
system "terraform #{cmd} #{variables(env_variable_keys.merge(settings))} -state #{shared_state_path} #{shared_config_directory}"
|
79
|
+
rescue StandardError => e
|
80
|
+
puts e.message
|
81
|
+
puts e.backtrace
|
82
|
+
ensure
|
83
|
+
# need to always ensure the most recent tfstate is encrypted again.
|
84
|
+
encrypt_file(shared_state_path)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
[:shared_apply, :shared_plan, :shared_destroy, :shared_pull, :shared_refresh].each do |cmd|
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
desc 'output', 'terraform output'
|
94
|
+
option :environment, :required => true
|
95
|
+
option :name, :required => true
|
96
|
+
def output
|
97
|
+
system output_cmd(state_path, options[:name])
|
98
|
+
end
|
99
|
+
|
100
|
+
desc 'taint', 'terraform taint'
|
101
|
+
option :environment, :required => true
|
102
|
+
option :name, :required => true
|
103
|
+
def taint
|
104
|
+
system "terraform taint -state #{state_path} #{options[:name]}"
|
105
|
+
end
|
106
|
+
|
107
|
+
desc 'show', 'terraform show'
|
108
|
+
option :environment, :required => true
|
109
|
+
def show
|
110
|
+
system "terraform show #{state_path}"
|
111
|
+
end
|
112
|
+
|
113
|
+
[ {commands: [:decrypt, :encrypt], file_path_method: :state_path, desc: 'upstream terraform changes'},
|
114
|
+
{commands: [:shared_decrypt, :shared_encrypt], file_path_method: :shared_state_path, desc: 'upstream shared terraform changes'},
|
115
|
+
{commands: [:var_file_decrypt, :var_file_encrypt], file_path_method: :var_file_path, desc: 'tfvars file'} ].each do |crypto_commands|
|
116
|
+
|
117
|
+
desc crypto_commands[:commands][0], "decrypt #{crypto_commands[:desc]}"
|
118
|
+
option :environment, :required => true
|
119
|
+
define_method crypto_commands[:commands][0] do
|
120
|
+
file_path = send(crypto_commands[:file_path_method])
|
121
|
+
decrypt_file(file_path)
|
122
|
+
end
|
123
|
+
|
124
|
+
desc crypto_commands[:commands][1], "encrypt #{crypto_commands[:desc]}"
|
125
|
+
option :environment, :required => true
|
126
|
+
define_method crypto_commands[:commands][1] do
|
127
|
+
file_path = send(crypto_commands[:file_path_method])
|
128
|
+
encrypt_file(file_path)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
desc "console", "interactive session"
|
133
|
+
def console
|
134
|
+
require 'pry-byebug'
|
135
|
+
binding.pry
|
136
|
+
end
|
137
|
+
|
138
|
+
protected
|
139
|
+
|
140
|
+
def var_file_param
|
141
|
+
File.exist?(var_file_path) ?
|
142
|
+
"-var-file #{var_file_path}" :
|
143
|
+
""
|
144
|
+
end
|
145
|
+
|
146
|
+
def delete_decrypted_var_file
|
147
|
+
return unless File.exist?(var_file_path)
|
148
|
+
|
149
|
+
enforce_cryptography_dependencies
|
150
|
+
if !File.exist?("#{var_file_path}.enc")
|
151
|
+
encrypt_file(var_file_path)
|
152
|
+
end
|
153
|
+
|
154
|
+
File.delete(var_file_path)
|
155
|
+
end
|
156
|
+
|
157
|
+
def encrypt_file(file_path)
|
158
|
+
enforce_cryptography_dependencies
|
159
|
+
if File.exist?(file_path)
|
160
|
+
system "openssl enc -aes-256-cbc -salt -in #{file_path} -out #{file_path}.enc -k #{ENV['TFSTATE_ENCRYPTION_PASSWORD']}"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def decrypt_file(file_path, enforce_file_existence=false)
|
165
|
+
enforce_cryptography_dependencies
|
166
|
+
if File.exist?("#{file_path}.enc")
|
167
|
+
system "openssl enc -aes-256-cbc -d -in #{file_path}.enc -out #{file_path} -k #{ENV['TFSTATE_ENCRYPTION_PASSWORD']}"
|
168
|
+
elsif enforce_file_existence
|
169
|
+
raise "Could not find #{file_path}.enc"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def state_path
|
174
|
+
"#{config_environment_path}/#{options[:environment]}.tfstate"
|
175
|
+
end
|
176
|
+
|
177
|
+
def shared_state_path
|
178
|
+
"#{shared_config_directory}/shared.tfstate"
|
179
|
+
end
|
180
|
+
|
181
|
+
def var_file_path
|
182
|
+
"#{config_environment_path}/variables.tfvars"
|
183
|
+
end
|
184
|
+
|
185
|
+
def config_directory
|
186
|
+
"config/infrastructure/#{infrastructure}"
|
187
|
+
end
|
188
|
+
|
189
|
+
def shared_config_directory
|
190
|
+
"config/infrastructure/#{infrastructure}/shared"
|
191
|
+
end
|
192
|
+
|
193
|
+
def config_environment_path
|
194
|
+
"#{config_directory}/environments/#{options[:environment]}"
|
195
|
+
end
|
196
|
+
|
197
|
+
def infrastructure
|
198
|
+
raise 'implement me'
|
199
|
+
end
|
200
|
+
|
201
|
+
def output_cmd(state_file_path, name=nil)
|
202
|
+
"terraform output -state=#{state_file_path} #{name}"
|
203
|
+
end
|
204
|
+
|
205
|
+
def output_variable(state_file_path, name)
|
206
|
+
`#{output_cmd(state_file_path, name)}`.chomp
|
207
|
+
end
|
208
|
+
|
209
|
+
def output_variables(state_file_path)
|
210
|
+
raw_plan_output = `#{output_cmd(state_file_path)}`
|
211
|
+
output_vars = {}
|
212
|
+
raw_plan_output.split("\n").each do |output_var|
|
213
|
+
key, value = output_var.split("=")
|
214
|
+
output_vars[key.strip] = value.strip
|
215
|
+
end
|
216
|
+
|
217
|
+
output_vars
|
218
|
+
end
|
219
|
+
|
220
|
+
def terraform_version
|
221
|
+
version_string = `terraform version`.chomp
|
222
|
+
version = /(\d+.\d+.\d+)/.match(version_string)
|
223
|
+
version[0]
|
224
|
+
end
|
225
|
+
|
226
|
+
def enforce_version!
|
227
|
+
if Gem::Version.new(terraform_version) < Gem::Version.new(TERRAFORM_VERSION)
|
228
|
+
raise "Terraform #{terraform_version} is out of date, please upgrade"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def enforce_cryptography_dependencies
|
233
|
+
raise "must supply TFSTATE_ENCRYPTION_PASSWORD environmental variable" if ENV['TFSTATE_ENCRYPTION_PASSWORD'].blank?
|
234
|
+
end
|
235
|
+
|
236
|
+
def settings
|
237
|
+
{} # override me to pass more variables into the terraform plan.
|
238
|
+
end
|
239
|
+
|
240
|
+
def asg_launch_config_name(asg_name)
|
241
|
+
asg_client = Aws::AutoScaling::Client.new(region: 'us-east-1')
|
242
|
+
group = asg_client.describe_auto_scaling_groups(auto_scaling_group_names: [asg_name]).auto_scaling_groups.first
|
243
|
+
group.try(:launch_configuration_name)
|
244
|
+
end
|
245
|
+
|
246
|
+
def env_variable_keys
|
247
|
+
{} # override me to pass environmental variables into the terraform plan
|
248
|
+
end
|
249
|
+
|
250
|
+
def shared_plan_variables
|
251
|
+
decrypt_file(shared_state_path, false)
|
252
|
+
if File.exist?(shared_state_path)
|
253
|
+
output_variables(shared_state_path)
|
254
|
+
else
|
255
|
+
{}
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def fetch_terraform_modules
|
260
|
+
system "terraform get -update=true #{config_directory}"
|
261
|
+
end
|
262
|
+
|
263
|
+
def current_tfstate
|
264
|
+
return @current_tfstate if defined?(@current_tfstate)
|
265
|
+
raw_conf = File.read(state_path)
|
266
|
+
@current_tfstate = JSON.parse(raw_conf)
|
267
|
+
end
|
268
|
+
|
269
|
+
def read_config_file(path)
|
270
|
+
File.new('config/' + path).read
|
271
|
+
template = ERB.new File.new("config/#{path}").read, nil, "%"
|
272
|
+
template.result(OpenStruct.new(options).instance_eval { binding })
|
273
|
+
end
|
274
|
+
|
275
|
+
def dynect
|
276
|
+
@dynect ||= DynectRest.new("controlshiftlabs", ENV['DYNECT_USERNAME'], ENV['DYNECT_PASSWORD'], "controlshiftlabs.com")
|
277
|
+
end
|
278
|
+
|
279
|
+
def dns_record_exists?(parent_node_fqdn, record)
|
280
|
+
dynect.node_list(nil, parent_node_fqdn).include?(record.fqdn)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Hashicorptools
|
2
|
+
class UpdateLaunchConfiguration < Thor
|
3
|
+
desc 'deploy ASG_NAME', 'recycle instances in the ASG with no downtime'
|
4
|
+
def deploy(asg_name)
|
5
|
+
asg = AutoScalingGroup.new(name: asg_name)
|
6
|
+
if asg.group.nil?
|
7
|
+
raise "could not find asg #{asg_name}"
|
8
|
+
end
|
9
|
+
current_count = asg.group.instances.size || 1
|
10
|
+
|
11
|
+
if asg.group.max_size < (current_count * 2)
|
12
|
+
raise "max size must be more than twice current count to deploy a new AMI"
|
13
|
+
else
|
14
|
+
# first doulbe the instance count to get new launch config live.
|
15
|
+
asg.set_desired_instances(current_count * 2)
|
16
|
+
|
17
|
+
# then bring the instance count back down again.
|
18
|
+
asg.set_desired_instances(current_count)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Hashicorptools
|
2
|
+
module Variables
|
3
|
+
def aws_credentials_settings(settings_overrides = {})
|
4
|
+
{aws_access_key: ENV['AWS_ACCESS_KEY_ID'],
|
5
|
+
aws_secret_key: ENV['AWS_SECRET_ACCESS_KEY']}.merge(settings_overrides)
|
6
|
+
end
|
7
|
+
|
8
|
+
def variables(settings_overrides = {})
|
9
|
+
aws_credentials_settings(settings_overrides).collect{|key,value| "-var '#{key}=#{value}'" }.join(' ')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'dotenv'
|
3
|
+
require 'thor'
|
4
|
+
require 'active_support/all'
|
5
|
+
require 'aws-sdk-v1'
|
6
|
+
require 'aws-sdk'
|
7
|
+
require 'dynect_rest'
|
8
|
+
|
9
|
+
module Hashicorptools
|
10
|
+
end
|
11
|
+
|
12
|
+
require_relative 'hashicorptools/variables'
|
13
|
+
require_relative 'hashicorptools/ec2_utilities'
|
14
|
+
require_relative 'hashicorptools/auto_scaling_group'
|
15
|
+
require_relative 'hashicorptools/packer'
|
16
|
+
require_relative 'hashicorptools/terraform'
|
17
|
+
require_relative 'hashicorptools/host'
|
18
|
+
require_relative 'hashicorptools/update_launch_configuration'
|
19
|
+
require_relative 'hashicorptools/code_deploy'
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
|
3
|
+
module SimpleCov::Configuration
|
4
|
+
def clean_filters
|
5
|
+
@filters = []
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
SimpleCov.configure do
|
10
|
+
clean_filters
|
11
|
+
load_adapter 'test_frameworks'
|
12
|
+
end
|
13
|
+
|
14
|
+
ENV["COVERAGE"] && SimpleCov.start do
|
15
|
+
add_filter "/.rvm/"
|
16
|
+
end
|
17
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
18
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
19
|
+
|
20
|
+
require 'rspec'
|
21
|
+
require 'hashicorptools'
|
22
|
+
|
23
|
+
# Requires supporting files with custom matchers and macros, etc,
|
24
|
+
# in ./support/ and its subdirectories.
|
25
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
26
|
+
|
27
|
+
RSpec.configure do |config|
|
28
|
+
|
29
|
+
end
|