openstax_aws 1.0.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/.travis.yml +12 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +120 -0
- data/LICENSE.txt +1 -0
- data/README.md +927 -0
- data/Rakefile +6 -0
- data/TODO.md +1 -0
- data/assets/secrets_sequence_diagram.png +0 -0
- data/bin/console +14 -0
- data/bin/create_development_environment +26 -0
- data/bin/get_latest_ubuntu_ami +31 -0
- data/bin/setup +8 -0
- data/bin/templates/aws_ruby_development.yml +221 -0
- data/examples/deployment.rb +90 -0
- data/ideas.md +15 -0
- data/lib/openstax/aws/auto_scaling_group.rb +28 -0
- data/lib/openstax/aws/auto_scaling_instance.rb +96 -0
- data/lib/openstax/aws/build_image_command_1.rb +53 -0
- data/lib/openstax/aws/change_set.rb +100 -0
- data/lib/openstax/aws/deployment_base.rb +372 -0
- data/lib/openstax/aws/distribution.rb +56 -0
- data/lib/openstax/aws/ec2_instance_data.rb +18 -0
- data/lib/openstax/aws/extensions.rb +19 -0
- data/lib/openstax/aws/git_helper.rb +18 -0
- data/lib/openstax/aws/image.rb +34 -0
- data/lib/openstax/aws/msk_cluster.rb +19 -0
- data/lib/openstax/aws/packer_1_2_5.rb +63 -0
- data/lib/openstax/aws/packer_1_4_1.rb +72 -0
- data/lib/openstax/aws/packer_factory.rb +25 -0
- data/lib/openstax/aws/rds_instance.rb +25 -0
- data/lib/openstax/aws/s3_text_file.rb +50 -0
- data/lib/openstax/aws/sam_stack.rb +85 -0
- data/lib/openstax/aws/secrets.rb +302 -0
- data/lib/openstax/aws/secrets_factory.rb +126 -0
- data/lib/openstax/aws/secrets_set.rb +21 -0
- data/lib/openstax/aws/secrets_specification.rb +68 -0
- data/lib/openstax/aws/stack.rb +465 -0
- data/lib/openstax/aws/stack_event.rb +28 -0
- data/lib/openstax/aws/stack_factory.rb +153 -0
- data/lib/openstax/aws/stack_parameters.rb +19 -0
- data/lib/openstax/aws/stack_status.rb +125 -0
- data/lib/openstax/aws/system.rb +21 -0
- data/lib/openstax/aws/tag.rb +31 -0
- data/lib/openstax/aws/template.rb +129 -0
- data/lib/openstax/aws/version.rb +5 -0
- data/lib/openstax/aws/wait_message.rb +20 -0
- data/lib/openstax_aws.rb +154 -0
- data/openstax_aws.gemspec +58 -0
- metadata +350 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
module OpenStax::Aws
|
5
|
+
class SecretsSpecification
|
6
|
+
|
7
|
+
attr_reader :data
|
8
|
+
|
9
|
+
def self.from_file_name(file_name:, format:, top_key: nil, preparser: nil)
|
10
|
+
file = File.open(file_name, "r")
|
11
|
+
content = file.read
|
12
|
+
file.close
|
13
|
+
new(content: content, format: format, top_key: top_key, preparser: preparser)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.from_git(org_slash_repo:, sha:, path:, format:, top_key: nil, preparser: nil)
|
17
|
+
content = OpenStax::Aws::GitHelper.file_content_at_sha(
|
18
|
+
org_slash_repo: org_slash_repo,
|
19
|
+
sha: sha,
|
20
|
+
path: path
|
21
|
+
)
|
22
|
+
new(content: content, format: format, top_key: top_key, preparser: preparser)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.from_content(content:, format: nil, top_key: nil, preparser: nil)
|
26
|
+
new(content: content, format: format, top_key: top_key, preparser: preparser)
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(content:, format: nil, top_key: nil, preparser: nil)
|
30
|
+
case content
|
31
|
+
when Hash
|
32
|
+
@data = content.dup
|
33
|
+
when String
|
34
|
+
raise "#{format} is not yet handled" if :yml != format
|
35
|
+
|
36
|
+
case (preparser || 'none').to_sym
|
37
|
+
when :erb
|
38
|
+
content = parse_erb(content)
|
39
|
+
end
|
40
|
+
|
41
|
+
@data = YAML.load(content)
|
42
|
+
else
|
43
|
+
raise "Unknown secrets specification inline content type: #{content.class}"
|
44
|
+
end
|
45
|
+
|
46
|
+
@data = data.with_indifferent_access
|
47
|
+
@data = data[top_key.to_s] if top_key
|
48
|
+
end
|
49
|
+
|
50
|
+
def expanded_data
|
51
|
+
flat_hash(@data).map{|k,v| [k.join('/'), v]}.to_h
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
# https://stackoverflow.com/a/23861946
|
57
|
+
def flat_hash(h,f=[],g={})
|
58
|
+
return g.update({ f=>h }) unless h.is_a? Hash
|
59
|
+
h.each { |k,r| flat_hash(r,f+[k],g) }
|
60
|
+
g
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_erb(string)
|
64
|
+
(ERB.new string).result(binding)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,465 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module OpenStax::Aws
|
4
|
+
class Stack
|
5
|
+
attr_reader :name, :tags, :id, :absolute_template_path, :dry_run,
|
6
|
+
:enable_termination_protection, :region, :parameter_defaults,
|
7
|
+
:volatile_parameters_block, :secrets_blocks
|
8
|
+
|
9
|
+
delegate :failed?, :updating?, :creating?, :deleting?, :exists?, to: :status
|
10
|
+
|
11
|
+
def initialize(id: nil, name:, tags: {},
|
12
|
+
region:, enable_termination_protection: false,
|
13
|
+
absolute_template_path: nil,
|
14
|
+
capabilities: nil, parameter_defaults: {},
|
15
|
+
volatile_parameters_block: nil,
|
16
|
+
secrets_blocks: [], secrets_context: nil, secrets_namespace: nil,
|
17
|
+
shared_secrets_substitutions_block: nil,
|
18
|
+
cycle_if_different_parameter: nil,
|
19
|
+
dry_run: true)
|
20
|
+
@id = id
|
21
|
+
|
22
|
+
raise "Stack name must not be blank" if name.blank?
|
23
|
+
@name = name
|
24
|
+
|
25
|
+
raise "`tags` must be a hash" if !tags.is_a?(Hash)
|
26
|
+
|
27
|
+
@tags = tags.map{|key, value| OpenStax::Aws::Tag.new(key, value)}
|
28
|
+
|
29
|
+
@region = region || raise("region is not set for stack #{name}")
|
30
|
+
@enable_termination_protection = enable_termination_protection
|
31
|
+
|
32
|
+
@absolute_template_path = absolute_template_path
|
33
|
+
|
34
|
+
set_capabilities(capabilities)
|
35
|
+
@parameter_defaults = parameter_defaults.dup.freeze
|
36
|
+
@volatile_parameters_block = volatile_parameters_block
|
37
|
+
|
38
|
+
@secrets_blocks = [secrets_blocks].flatten.compact
|
39
|
+
@secrets_context = secrets_context
|
40
|
+
@secrets_namespace = secrets_namespace
|
41
|
+
@shared_secrets_substitutions_block = shared_secrets_substitutions_block
|
42
|
+
|
43
|
+
@cycle_if_different_parameter = (
|
44
|
+
cycle_if_different_parameter ||
|
45
|
+
OpenStax::Aws.configuration.default_cycle_if_different_parameter
|
46
|
+
).underscore.to_sym
|
47
|
+
|
48
|
+
@dry_run = dry_run
|
49
|
+
end
|
50
|
+
|
51
|
+
def template
|
52
|
+
@template ||= begin
|
53
|
+
if absolute_template_path.present?
|
54
|
+
OpenStax::Aws::Template.from_absolute_file_path(absolute_template_path)
|
55
|
+
else
|
56
|
+
body = client.get_template({stack_name: name}).template_body
|
57
|
+
OpenStax::Aws::Template.from_body(body)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def create(params: {}, wait: false, skip_if_exists: false)
|
63
|
+
logger.info("**** DRY RUN ****") if dry_run
|
64
|
+
|
65
|
+
check_for_required_tags
|
66
|
+
|
67
|
+
if skip_if_exists && exists?
|
68
|
+
logger.info("Skipping #{name} stack - exists...")
|
69
|
+
return
|
70
|
+
end
|
71
|
+
|
72
|
+
params = parameter_defaults.merge(params)
|
73
|
+
|
74
|
+
if defines_secrets?
|
75
|
+
logger.info("Creating #{name} stack secrets...")
|
76
|
+
secrets(parameters: params, for_create_or_update: true).create
|
77
|
+
end
|
78
|
+
|
79
|
+
options = {
|
80
|
+
stack_name: name,
|
81
|
+
template_url: template.s3_url,
|
82
|
+
capabilities: capabilities,
|
83
|
+
parameters: self.class.format_hash_as_stack_parameters(params),
|
84
|
+
enable_termination_protection: enable_termination_protection,
|
85
|
+
tags: self.class.format_hash_as_tag_parameters(@tags),
|
86
|
+
}
|
87
|
+
|
88
|
+
logger.info("Creating #{name} stack...")
|
89
|
+
client.create_stack(options) if !dry_run
|
90
|
+
|
91
|
+
wait_for_creation if wait
|
92
|
+
end
|
93
|
+
|
94
|
+
def parameters_for_update(overrides: {})
|
95
|
+
parameters = {}
|
96
|
+
|
97
|
+
# Start populating the parameters hash by using `:use_previous_value` for any
|
98
|
+
# parameter that is currently in the template that is also currently on the stack,
|
99
|
+
# and using the defined default value for any other parameter.
|
100
|
+
|
101
|
+
continuing_parameter_keys.each do |continuing_parameter_key|
|
102
|
+
parameters[continuing_parameter_key] = :use_previous_value
|
103
|
+
end
|
104
|
+
|
105
|
+
new_parameter_keys.each do |new_parameter_key|
|
106
|
+
parameters[new_parameter_key] = parameter_defaults[new_parameter_key]
|
107
|
+
end
|
108
|
+
|
109
|
+
# Volatile parameters can be changed outside of cloudformation updates. Here
|
110
|
+
# we get their current values by executing the block in the context of this
|
111
|
+
# stack, and then we merge them in (overwriting any values already in the
|
112
|
+
# parameters hash).
|
113
|
+
|
114
|
+
parameters.merge!(volatile_parameters)
|
115
|
+
|
116
|
+
# Lastly, we merge in the overrides hash (e.g. things purposefully set
|
117
|
+
# by an outside caller) -- they take precendence over all previous values.
|
118
|
+
|
119
|
+
parameters.merge!(overrides)
|
120
|
+
|
121
|
+
# Leave out nil-valued parameters as they are not valid (and likely not
|
122
|
+
# intentional)
|
123
|
+
|
124
|
+
parameters.compact
|
125
|
+
end
|
126
|
+
|
127
|
+
def volatile_parameters
|
128
|
+
return {} if volatile_parameters_block.nil?
|
129
|
+
|
130
|
+
volatile_parameters_factory = StackFactory::VolatileParametersFactory.new(self)
|
131
|
+
volatile_parameters_factory.instance_eval(&volatile_parameters_block)
|
132
|
+
volatile_parameters_factory.attributes
|
133
|
+
end
|
134
|
+
|
135
|
+
def deployed_parameters
|
136
|
+
begin
|
137
|
+
@deployed_parameters ||= aws_stack.parameters.each_with_object({}) do |parameter, hash|
|
138
|
+
hash[parameter.parameter_key.underscore.to_sym] = parameter.parameter_value
|
139
|
+
end
|
140
|
+
rescue Aws::CloudFormation::Errors::ValidationError => ee
|
141
|
+
if ee.message =~ /Stack.*does not exist/
|
142
|
+
{}
|
143
|
+
else
|
144
|
+
raise
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def secrets(parameters: {}, for_create_or_update: false)
|
150
|
+
SecretsSet.new(
|
151
|
+
secrets_blocks.map do |secrets_block|
|
152
|
+
secrets_factory = SecretsFactory.new(
|
153
|
+
region: region,
|
154
|
+
namespace: @secrets_namespace,
|
155
|
+
context: @secrets_context,
|
156
|
+
dry_run: dry_run,
|
157
|
+
for_create_or_update: for_create_or_update,
|
158
|
+
shared_substitutions_block: @shared_secrets_substitutions_block
|
159
|
+
)
|
160
|
+
|
161
|
+
secrets_factory.namespace(id)
|
162
|
+
secrets_factory.instance_exec parameters, &secrets_block
|
163
|
+
secrets_factory.instance
|
164
|
+
end
|
165
|
+
)
|
166
|
+
end
|
167
|
+
|
168
|
+
def create_change_set(options)
|
169
|
+
OpenStax::Aws::ChangeSet.new(client: client).create(options: options)
|
170
|
+
end
|
171
|
+
|
172
|
+
def apply_change_set(params: {}, wait: false)
|
173
|
+
logger.info("**** DRY RUN ****") if dry_run
|
174
|
+
|
175
|
+
check_for_required_tags
|
176
|
+
|
177
|
+
logger.info("Updating #{name} stack...")
|
178
|
+
|
179
|
+
params = parameters_for_update(overrides: params)
|
180
|
+
|
181
|
+
if defines_secrets?
|
182
|
+
logger.info("Updating #{name} stack secrets...")
|
183
|
+
|
184
|
+
secrets_changed = secrets(
|
185
|
+
parameters: StackParameters.new(params: params, stack: self),
|
186
|
+
for_create_or_update: true
|
187
|
+
).update
|
188
|
+
|
189
|
+
if secrets_changed && template_parameter_keys.include?(@cycle_if_different_parameter)
|
190
|
+
logger.info("Secrets changed, setting stack parameter to trigger server cycling")
|
191
|
+
params[@cycle_if_different_parameter] = SecureRandom.hex(10)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
options = {
|
196
|
+
stack_name: name,
|
197
|
+
template_url: template.s3_url,
|
198
|
+
capabilities: capabilities,
|
199
|
+
parameters: self.class.format_hash_as_stack_parameters(params),
|
200
|
+
change_set_name: "#{name}-#{Time.now.utc.strftime("%Y%m%d-%H%M%S")}",
|
201
|
+
tags: self.class.format_hash_as_tag_parameters(@tags),
|
202
|
+
}
|
203
|
+
|
204
|
+
change_set = create_change_set(options)
|
205
|
+
|
206
|
+
if change_set.created?
|
207
|
+
resource_changes = change_set.resource_change_summaries
|
208
|
+
|
209
|
+
logger.info("#{resource_changes.size} resource change(s)#{':' if !resource_changes.empty?}")
|
210
|
+
resource_changes.each do |resource_change|
|
211
|
+
logger.debug(resource_change)
|
212
|
+
end
|
213
|
+
|
214
|
+
if dry_run
|
215
|
+
logger.info("Deleting change set (because this is a dry run)")
|
216
|
+
change_set.delete
|
217
|
+
else
|
218
|
+
logger.info("Executing change set")
|
219
|
+
change_set.execute
|
220
|
+
reset_cached_remote_state
|
221
|
+
end
|
222
|
+
|
223
|
+
wait_for_update if wait
|
224
|
+
end
|
225
|
+
|
226
|
+
change_set
|
227
|
+
end
|
228
|
+
|
229
|
+
def delete(retain_resources: [], wait: false)
|
230
|
+
logger.info("**** DRY RUN ****") if dry_run
|
231
|
+
|
232
|
+
if defines_secrets?
|
233
|
+
logger.info("Deleting #{name} stack secrets...")
|
234
|
+
secrets.delete
|
235
|
+
end
|
236
|
+
|
237
|
+
logger.info("Deleting #{name} stack...")
|
238
|
+
|
239
|
+
if exists?
|
240
|
+
client.delete_stack(stack_name: name, retain_resources: retain_resources) if !dry_run
|
241
|
+
else
|
242
|
+
logger.info("Cannot delete #{name} stack as it does not exist")
|
243
|
+
end
|
244
|
+
|
245
|
+
wait_for_deletion if wait
|
246
|
+
end
|
247
|
+
|
248
|
+
def output_value(key:)
|
249
|
+
if dry_run
|
250
|
+
"undefined-in-dry-run"
|
251
|
+
else
|
252
|
+
output = aws_stack.outputs.find {|output| output.output_key == key}
|
253
|
+
raise "No output with key #{key} in stack #{name}" if output.nil?
|
254
|
+
output.output_value
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def wait_for_creation
|
259
|
+
if !dry_run
|
260
|
+
return if !creating?
|
261
|
+
wait_for_stack_event(waiter_class: Aws::CloudFormation::Waiters::StackCreateComplete,
|
262
|
+
word: "created")
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def wait_for_update
|
267
|
+
if !dry_run
|
268
|
+
return if !updating? # if not updating, waiting for an updated message will thrash until timeout
|
269
|
+
wait_for_stack_event(waiter_class: Aws::CloudFormation::Waiters::StackUpdateComplete,
|
270
|
+
word: "updated")
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def wait_for_deletion
|
275
|
+
if !dry_run
|
276
|
+
begin
|
277
|
+
return if !deleting?
|
278
|
+
wait_for_stack_event(waiter_class: Aws::CloudFormation::Waiters::StackDeleteComplete,
|
279
|
+
word: "deleted")
|
280
|
+
rescue Aws::CloudFormation::Errors::ValidationError => ee
|
281
|
+
if ee.message =~ /Stack.*does not exist/
|
282
|
+
logger.warn("Waiting for stack #{name} to be deleted failed because it does not exist")
|
283
|
+
else
|
284
|
+
raise
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def resource(logical_id)
|
291
|
+
stack_resource = aws_stack.resource(logical_id)
|
292
|
+
|
293
|
+
case stack_resource.resource_type
|
294
|
+
when "AWS::AutoScaling::AutoScalingGroup"
|
295
|
+
name = stack_resource.physical_resource_id
|
296
|
+
client = Aws::AutoScaling::Client.new(region: region)
|
297
|
+
Aws::AutoScaling::AutoScalingGroup.new(name: name, client: client)
|
298
|
+
when "AWS::RDS::DBInstance"
|
299
|
+
db_instance_identifier = stack_resource.physical_resource_id
|
300
|
+
OpenStax::Aws::RdsInstance.new(db_instance_identifier: db_instance_identifier, region: region)
|
301
|
+
when "AWS::MSK::Cluster"
|
302
|
+
msk_cluster_arn = stack_resource.physical_resource_id
|
303
|
+
OpenStax::Aws::MskCluster.new(cluster_arn: msk_cluster_arn, region: region)
|
304
|
+
else
|
305
|
+
raise "'#{stack_resource.resource_type}' is not yet implemented in `Stack#resource`"
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def capabilities
|
310
|
+
set_capabilities(default_capabilities) if @capabilities.nil?
|
311
|
+
@capabilities
|
312
|
+
end
|
313
|
+
|
314
|
+
def default_capabilities
|
315
|
+
if OpenStax::Aws.configuration.infer_stack_capabilities
|
316
|
+
template.required_capabilities
|
317
|
+
else
|
318
|
+
[]
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def self.format_hash_as_stack_parameters(params={})
|
323
|
+
params.map do |key, value|
|
324
|
+
{
|
325
|
+
parameter_key: key.to_s.split('_').collect(&:capitalize).join,
|
326
|
+
}.tap do |hash|
|
327
|
+
if value == :use_previous_value
|
328
|
+
hash[:use_previous_value] = true
|
329
|
+
else
|
330
|
+
hash[:parameter_value] = value.to_s
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def self.format_hash_as_tag_parameters(tags)
|
337
|
+
tags.map{|tag| {key: tag.key, value: tag.value}}
|
338
|
+
end
|
339
|
+
|
340
|
+
def status(reload: false)
|
341
|
+
@status = nil if reload
|
342
|
+
@status ||= Status.new(self)
|
343
|
+
end
|
344
|
+
|
345
|
+
def events
|
346
|
+
(aws_stack&.events || []).map{|aws_event| Event.new(aws_event)}
|
347
|
+
end
|
348
|
+
|
349
|
+
def defines_secrets?
|
350
|
+
!secrets_blocks.empty?
|
351
|
+
end
|
352
|
+
|
353
|
+
def self.query(regex: /.*/, regions: %w(us-east-1 us-east-2 us-west-1 us-west-2), active: true, reload: false)
|
354
|
+
stack_status_filter = active ? Status.active_status_texts : nil
|
355
|
+
|
356
|
+
if reload
|
357
|
+
@all_stacks = {}
|
358
|
+
else
|
359
|
+
@all_stacks ||= {}
|
360
|
+
end
|
361
|
+
|
362
|
+
# Memoize the query results to speed up subsequent queries
|
363
|
+
@all_stacks[stack_status_filter + regions] ||= regions.map do |region|
|
364
|
+
client = Aws::CloudFormation::Client.new(region: region)
|
365
|
+
client.list_stacks(stack_status_filter: stack_status_filter).map do |response|
|
366
|
+
response.stack_summaries.map do |summary|
|
367
|
+
OpenStax::Aws.configuration.without_required_stack_tags do
|
368
|
+
new(name: summary.stack_name, region: region)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end.flatten
|
373
|
+
|
374
|
+
@all_stacks[stack_status_filter + regions].select{|stack| stack.name.match(regex)}
|
375
|
+
end
|
376
|
+
|
377
|
+
def aws_stack
|
378
|
+
::Aws::CloudFormation::Stack.new(name: name, client: client)
|
379
|
+
end
|
380
|
+
|
381
|
+
protected
|
382
|
+
|
383
|
+
def wait_for_stack_event(waiter_class:, word:)
|
384
|
+
wait_message = OpenStax::Aws::WaitMessage.new(
|
385
|
+
message: "Waiting for #{name} stack to be #{word}"
|
386
|
+
)
|
387
|
+
|
388
|
+
begin
|
389
|
+
waiter_class.new(
|
390
|
+
client: client,
|
391
|
+
before_attempt: ->(*) { wait_message.say_it },
|
392
|
+
delay: OpenStax::Aws.configuration.stack_waiter_delay,
|
393
|
+
max_attempts: OpenStax::Aws.configuration.stack_waiter_max_attempts
|
394
|
+
).wait(stack_name: name)
|
395
|
+
rescue Aws::Waiters::Errors::WaiterFailed => error
|
396
|
+
logger.error("Waiting failed: #{error.message}")
|
397
|
+
raise
|
398
|
+
end
|
399
|
+
logger.info "#{name} has been #{word}!"
|
400
|
+
end
|
401
|
+
|
402
|
+
SHORT_CAPABILITIES = {
|
403
|
+
iam: "CAPABILITY_IAM",
|
404
|
+
named_iam: "CAPABILITY_NAMED_IAM",
|
405
|
+
auto_expand: "CAPABILITY_AUTO_EXPAND"
|
406
|
+
}.freeze
|
407
|
+
|
408
|
+
def set_capabilities(capabilities)
|
409
|
+
return if capabilities.nil?
|
410
|
+
|
411
|
+
capabilities = [capabilities].flatten.compact
|
412
|
+
|
413
|
+
valid_capabilities = SHORT_CAPABILITIES.keys + SHORT_CAPABILITIES.values
|
414
|
+
|
415
|
+
capabilities.each do |capability|
|
416
|
+
if !valid_capabilities.include?(capability)
|
417
|
+
raise "Capabilities must be in #{valid_capabilities}"
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
@capabilities = capabilities.map do |capability|
|
422
|
+
SHORT_CAPABILITIES[capability] || capability
|
423
|
+
end.freeze
|
424
|
+
end
|
425
|
+
|
426
|
+
def template_parameter_keys
|
427
|
+
@tpks ||= template.parameter_names.map(&:underscore).map(&:to_sym)
|
428
|
+
end
|
429
|
+
|
430
|
+
def continuing_parameter_keys
|
431
|
+
template_parameter_keys & deployed_parameters.keys
|
432
|
+
end
|
433
|
+
|
434
|
+
def new_parameter_keys
|
435
|
+
template_parameter_keys - continuing_parameter_keys
|
436
|
+
end
|
437
|
+
|
438
|
+
def reset_cached_remote_state
|
439
|
+
@deployed_parameters = nil
|
440
|
+
end
|
441
|
+
|
442
|
+
def logger
|
443
|
+
OpenStax::Aws.configuration.logger
|
444
|
+
end
|
445
|
+
|
446
|
+
def client
|
447
|
+
@client ||= ::Aws::CloudFormation::Client.new(region: region)
|
448
|
+
end
|
449
|
+
|
450
|
+
def tag(name)
|
451
|
+
tags.select{|tag| tag.key == name}.first
|
452
|
+
end
|
453
|
+
|
454
|
+
def check_for_required_tags
|
455
|
+
OpenStax::Aws.configuration.required_stack_tags.each do |required_tag|
|
456
|
+
tag = tag(required_tag)
|
457
|
+
if tag.nil? || tag.value.blank?
|
458
|
+
raise "The '#{required_tag}' tag is required on the '#{name}' stack but is blank or missing"
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
end
|
463
|
+
|
464
|
+
end
|
465
|
+
end
|