Class: OpenStax::Aws::Stack
- Inherits:
-
Object
- Object
- OpenStax::Aws::Stack
- Defined in:
- lib/openstax/aws/stack.rb,
lib/openstax/aws/stack_event.rb,
lib/openstax/aws/stack_status.rb
Direct Known Subclasses
Defined Under Namespace
Instance Attribute Summary collapse
-
#absolute_template_path ⇒ Object
readonly
Returns the value of attribute absolute_template_path.
-
#dry_run ⇒ Object
readonly
Returns the value of attribute dry_run.
-
#enable_termination_protection ⇒ Object
readonly
Returns the value of attribute enable_termination_protection.
-
#id ⇒ Object
readonly
Returns the value of attribute id.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#parameter_defaults ⇒ Object
readonly
Returns the value of attribute parameter_defaults.
-
#region ⇒ Object
readonly
Returns the value of attribute region.
-
#secrets_blocks ⇒ Object
readonly
Returns the value of attribute secrets_blocks.
-
#tags ⇒ Object
readonly
Returns the value of attribute tags.
-
#volatile_parameters_block ⇒ Object
readonly
Returns the value of attribute volatile_parameters_block.
Class Method Summary collapse
- .format_hash_as_stack_parameters(params = {}) ⇒ Object
- .format_hash_as_tag_parameters(tags) ⇒ Object
- .query(regex: /.*/, regions: %w(us-east-1 us-east-2 us-west-1 us-west-2),, active: true, reload: false) ⇒ Object
Instance Method Summary collapse
- #apply_change_set(params: {}, wait: false) ⇒ Object
- #aws_stack ⇒ Object
- #capabilities ⇒ Object
- #create(params: {}, wait: false, skip_if_exists: false) ⇒ Object
- #create_change_set(options) ⇒ Object
- #default_capabilities ⇒ Object
- #defines_secrets? ⇒ Boolean
- #delete(retain_resources: [], wait: false) ⇒ Object
- #deployed_parameters ⇒ Object
- #events ⇒ Object
-
#initialize(id: nil, name:, tags: {}, region:, enable_termination_protection: false, absolute_template_path: nil, capabilities: nil, parameter_defaults: {}, volatile_parameters_block: nil, secrets_blocks: [], secrets_context: nil, secrets_namespace: nil, shared_secrets_substitutions_block: nil, cycle_if_different_parameter: nil, dry_run: true) ⇒ Stack
constructor
A new instance of Stack.
- #output_value(key:) ⇒ Object
- #parameters_for_update(overrides: {}) ⇒ Object
- #resource(logical_id) ⇒ Object
- #resources(types = nil) ⇒ Object
- #secrets(parameters: {}, for_create_or_update: false) ⇒ Object
- #status(reload: false) ⇒ Object
- #tag_resources_not_handled_by_cloudformation ⇒ Object
- #template ⇒ Object
- #volatile_parameters ⇒ Object
- #wait_for_creation ⇒ Object
- #wait_for_deletion ⇒ Object
- #wait_for_update ⇒ Object
Constructor Details
#initialize(id: nil, name:, tags: {}, region:, enable_termination_protection: false, absolute_template_path: nil, capabilities: nil, parameter_defaults: {}, volatile_parameters_block: nil, secrets_blocks: [], secrets_context: nil, secrets_namespace: nil, shared_secrets_substitutions_block: nil, cycle_if_different_parameter: nil, dry_run: true) ⇒ Stack
Returns a new instance of Stack.
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/openstax/aws/stack.rb', line 11 def initialize(id: nil, name:, tags: {}, region:, enable_termination_protection: false, absolute_template_path: nil, capabilities: nil, parameter_defaults: {}, volatile_parameters_block: nil, secrets_blocks: [], secrets_context: nil, secrets_namespace: nil, shared_secrets_substitutions_block: nil, cycle_if_different_parameter: nil, dry_run: true) @id = id raise "Stack name must not be blank" if name.blank? @name = name raise "`tags` must be a hash" if !.is_a?(Hash) @tags = .map{|key, value| OpenStax::Aws::Tag.new(key, value)} @region = region || raise("region is not set for stack #{name}") @enable_termination_protection = enable_termination_protection @absolute_template_path = absolute_template_path set_capabilities(capabilities) @parameter_defaults = parameter_defaults.dup.freeze @volatile_parameters_block = volatile_parameters_block @secrets_blocks = [secrets_blocks].flatten.compact @secrets_context = secrets_context @secrets_namespace = secrets_namespace @shared_secrets_substitutions_block = shared_secrets_substitutions_block @cycle_if_different_parameter = ( cycle_if_different_parameter || OpenStax::Aws.configuration.default_cycle_if_different_parameter ).underscore.to_sym @dry_run = dry_run end |
Instance Attribute Details
#absolute_template_path ⇒ Object (readonly)
Returns the value of attribute absolute_template_path.
5 6 7 |
# File 'lib/openstax/aws/stack.rb', line 5 def absolute_template_path @absolute_template_path end |
#dry_run ⇒ Object (readonly)
Returns the value of attribute dry_run.
5 6 7 |
# File 'lib/openstax/aws/stack.rb', line 5 def dry_run @dry_run end |
#enable_termination_protection ⇒ Object (readonly)
Returns the value of attribute enable_termination_protection.
5 6 7 |
# File 'lib/openstax/aws/stack.rb', line 5 def enable_termination_protection @enable_termination_protection end |
#id ⇒ Object (readonly)
Returns the value of attribute id.
5 6 7 |
# File 'lib/openstax/aws/stack.rb', line 5 def id @id end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
5 6 7 |
# File 'lib/openstax/aws/stack.rb', line 5 def name @name end |
#parameter_defaults ⇒ Object (readonly)
Returns the value of attribute parameter_defaults.
5 6 7 |
# File 'lib/openstax/aws/stack.rb', line 5 def parameter_defaults @parameter_defaults end |
#region ⇒ Object (readonly)
Returns the value of attribute region.
5 6 7 |
# File 'lib/openstax/aws/stack.rb', line 5 def region @region end |
#secrets_blocks ⇒ Object (readonly)
Returns the value of attribute secrets_blocks.
5 6 7 |
# File 'lib/openstax/aws/stack.rb', line 5 def secrets_blocks @secrets_blocks end |
#tags ⇒ Object (readonly)
Returns the value of attribute tags.
5 6 7 |
# File 'lib/openstax/aws/stack.rb', line 5 def @tags end |
#volatile_parameters_block ⇒ Object (readonly)
Returns the value of attribute volatile_parameters_block.
5 6 7 |
# File 'lib/openstax/aws/stack.rb', line 5 def volatile_parameters_block @volatile_parameters_block end |
Class Method Details
.format_hash_as_stack_parameters(params = {}) ⇒ Object
338 339 340 341 342 343 344 345 346 347 348 349 350 |
# File 'lib/openstax/aws/stack.rb', line 338 def self.format_hash_as_stack_parameters(params={}) params.map do |key, value| { parameter_key: key.to_s.split('_').collect(&:capitalize).join, }.tap do |hash| if value == :use_previous_value hash[:use_previous_value] = true else hash[:parameter_value] = value.to_s end end end end |
.format_hash_as_tag_parameters(tags) ⇒ Object
352 353 354 |
# File 'lib/openstax/aws/stack.rb', line 352 def self.format_hash_as_tag_parameters() .map{|tag| {key: tag.key, value: tag.value}} end |
.query(regex: /.*/, regions: %w(us-east-1 us-east-2 us-west-1 us-west-2),, active: true, reload: false) ⇒ Object
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 |
# File 'lib/openstax/aws/stack.rb', line 369 def self.query(regex: /.*/, regions: %w(us-east-1 us-east-2 us-west-1 us-west-2), active: true, reload: false) stack_status_filter = active ? Status.active_status_texts : nil if reload @all_stacks = {} else @all_stacks ||= {} end # Memoize the query results to speed up subsequent queries @all_stacks[stack_status_filter + regions] ||= regions.map do |region| client = Aws::CloudFormation::Client.new(region: region) client.list_stacks(stack_status_filter: stack_status_filter).map do |response| response.stack_summaries.map do |summary| OpenStax::Aws.configuration. do new(name: summary.stack_name, region: region) end end end end.flatten @all_stacks[stack_status_filter + regions].select{|stack| stack.name.match(regex)} end |
Instance Method Details
#apply_change_set(params: {}, wait: false) ⇒ Object
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/openstax/aws/stack.rb', line 178 def apply_change_set(params: {}, wait: false) logger.info("**** DRY RUN ****") if dry_run logger.info("Updating #{name} stack...") params = parameters_for_update(overrides: params) if defines_secrets? logger.info("Updating #{name} stack secrets...") secrets_changed = secrets( parameters: StackParameters.new(params: params, stack: self), for_create_or_update: true ).update if secrets_changed && template_parameter_keys.include?(@cycle_if_different_parameter) logger.info("Secrets changed, setting stack parameter to trigger server cycling") params[@cycle_if_different_parameter] = SecureRandom.hex(10) end end = { stack_name: name, template_url: template.s3_url, capabilities: capabilities, parameters: self.class.format_hash_as_stack_parameters(params), change_set_name: "#{name}-#{Time.now.utc.strftime("%Y%m%d-%H%M%S")}", tags: self.class.format_hash_as_tag_parameters(@tags), } change_set = create_change_set() if change_set.created? resource_changes = change_set.resource_change_summaries logger.info("#{resource_changes.size} resource change(s)#{':' if !resource_changes.empty?}") resource_changes.each do |resource_change| logger.debug(resource_change) end if dry_run logger.info("Deleting change set (because this is a dry run)") change_set.delete else logger.info("Executing change set") change_set.execute reset_cached_remote_state wait_for_update if wait end end # We can still tag resources even if there are no changes according to CloudFormation tag_resources_not_handled_by_cloudformation if wait && !dry_run change_set end |
#aws_stack ⇒ Object
393 394 395 |
# File 'lib/openstax/aws/stack.rb', line 393 def aws_stack ::Aws::CloudFormation::Stack.new(name: name, client: client) end |
#capabilities ⇒ Object
325 326 327 328 |
# File 'lib/openstax/aws/stack.rb', line 325 def capabilities set_capabilities(default_capabilities) if @capabilities.nil? @capabilities end |
#create(params: {}, wait: false, skip_if_exists: false) ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/openstax/aws/stack.rb', line 62 def create(params: {}, wait: false, skip_if_exists: false) logger.info("**** DRY RUN ****") if dry_run if skip_if_exists && exists? logger.info("Skipping #{name} stack - exists...") return end params = parameter_defaults.merge(params) if defines_secrets? logger.info("Creating #{name} stack secrets...") secrets(parameters: params, for_create_or_update: true).create end = { stack_name: name, template_url: template.s3_url, capabilities: capabilities, parameters: self.class.format_hash_as_stack_parameters(params), enable_termination_protection: enable_termination_protection, tags: self.class.format_hash_as_tag_parameters(@tags), } logger.info("Creating #{name} stack...") if !dry_run client.create_stack() if wait wait_for_creation tag_resources_not_handled_by_cloudformation end end end |
#create_change_set(options) ⇒ Object
174 175 176 |
# File 'lib/openstax/aws/stack.rb', line 174 def create_change_set() OpenStax::Aws::ChangeSet.new(client: client).create(options: ) end |
#default_capabilities ⇒ Object
330 331 332 333 334 335 336 |
# File 'lib/openstax/aws/stack.rb', line 330 def default_capabilities if OpenStax::Aws.configuration.infer_stack_capabilities template.required_capabilities else [] end end |
#defines_secrets? ⇒ Boolean
365 366 367 |
# File 'lib/openstax/aws/stack.rb', line 365 def defines_secrets? !secrets_blocks.empty? end |
#delete(retain_resources: [], wait: false) ⇒ Object
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/openstax/aws/stack.rb', line 245 def delete(retain_resources: [], wait: false) logger.info("**** DRY RUN ****") if dry_run if defines_secrets? logger.info("Deleting #{name} stack secrets...") secrets.delete end logger.info("Deleting #{name} stack...") if exists? client.delete_stack(stack_name: name, retain_resources: retain_resources) if !dry_run else logger.info("Cannot delete #{name} stack as it does not exist") end wait_for_deletion if wait end |
#deployed_parameters ⇒ Object
141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/openstax/aws/stack.rb', line 141 def deployed_parameters begin @deployed_parameters ||= aws_stack.parameters.each_with_object({}) do |parameter, hash| hash[parameter.parameter_key.underscore.to_sym] = parameter.parameter_value end rescue Aws::CloudFormation::Errors::ValidationError => ee if ee. =~ /Stack.*does not exist/ {} else raise end end end |
#events ⇒ Object
361 362 363 |
# File 'lib/openstax/aws/stack.rb', line 361 def events (aws_stack&.events || []).map{|aws_event| Event.new(aws_event)} end |
#output_value(key:) ⇒ Object
264 265 266 267 268 269 270 271 272 |
# File 'lib/openstax/aws/stack.rb', line 264 def output_value(key:) if dry_run "undefined-in-dry-run" else output = aws_stack.outputs.find {|output| output.output_key == key} raise "No output with key #{key} in stack #{name}" if output.nil? output.output_value end end |
#parameters_for_update(overrides: {}) ⇒ Object
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/openstax/aws/stack.rb', line 100 def parameters_for_update(overrides: {}) parameters = {} # Start populating the parameters hash by using `:use_previous_value` for any # parameter that is currently in the template that is also currently on the stack, # and using the defined default value for any other parameter. continuing_parameter_keys.each do |continuing_parameter_key| parameters[continuing_parameter_key] = :use_previous_value end new_parameter_keys.each do |new_parameter_key| parameters[new_parameter_key] = parameter_defaults[new_parameter_key] end # Volatile parameters can be changed outside of cloudformation updates. Here # we get their current values by executing the block in the context of this # stack, and then we merge them in (overwriting any values already in the # parameters hash). parameters.merge!(volatile_parameters) # Lastly, we merge in the overrides hash (e.g. things purposefully set # by an outside caller) -- they take precendence over all previous values. parameters.merge!(overrides) # Leave out nil-valued parameters as they are not valid (and likely not # intentional) parameters.compact end |
#resource(logical_id) ⇒ Object
306 307 308 309 310 |
# File 'lib/openstax/aws/stack.rb', line 306 def resource(logical_id) stack_resource = aws_stack.resource(logical_id) resource_factory = OpenStax::Aws::ResourceFactory.new region: region resource_factory.from_stack_resource_or_summary! stack_resource end |
#resources(types = nil) ⇒ Object
312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/openstax/aws/stack.rb', line 312 def resources(types = nil) resource_factory = OpenStax::Aws::ResourceFactory.new region: region, types: types Enumerator.new do |yielder| client.list_stack_resources(stack_name: name).each do |response| response.stack_resource_summaries.each do |stack_resource_summary| resource = resource_factory.from_stack_resource_or_summary stack_resource_summary yielder << resource unless resource.nil? end end end end |
#secrets(parameters: {}, for_create_or_update: false) ⇒ Object
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/openstax/aws/stack.rb', line 155 def secrets(parameters: {}, for_create_or_update: false) SecretsSet.new( secrets_blocks.map do |secrets_block| secrets_factory = SecretsFactory.new( region: region, namespace: @secrets_namespace, context: @secrets_context, dry_run: dry_run, for_create_or_update: for_create_or_update, shared_substitutions_block: @shared_secrets_substitutions_block ) secrets_factory.namespace(id) secrets_factory.instance_exec parameters, &secrets_block secrets_factory.instance end ) end |
#status(reload: false) ⇒ Object
356 357 358 359 |
# File 'lib/openstax/aws/stack.rb', line 356 def status(reload: false) @status = nil if reload @status ||= Status.new(self) end |
#tag_resources_not_handled_by_cloudformation ⇒ Object
238 239 240 241 242 243 |
# File 'lib/openstax/aws/stack.rb', line 238 def tag_resources_not_handled_by_cloudformation = self.class.format_hash_as_tag_parameters @tags resources( [ 'AWS::CloudWatch::Alarm', 'AWS::Events::Rule', 'AWS::AutoScaling::AutoScalingGroup' ] ).each { |resource| resource.() } end |
#template ⇒ Object
51 52 53 54 55 56 57 58 59 60 |
# File 'lib/openstax/aws/stack.rb', line 51 def template @template ||= begin if absolute_template_path.present? OpenStax::Aws::Template.from_absolute_file_path(absolute_template_path) else body = client.get_template({stack_name: name}).template_body OpenStax::Aws::Template.from_body(body) end end end |
#volatile_parameters ⇒ Object
133 134 135 136 137 138 139 |
# File 'lib/openstax/aws/stack.rb', line 133 def volatile_parameters return {} if volatile_parameters_block.nil? volatile_parameters_factory = StackFactory::VolatileParametersFactory.new(self) volatile_parameters_factory.instance_eval(&volatile_parameters_block) volatile_parameters_factory.attributes end |
#wait_for_creation ⇒ Object
274 275 276 277 278 279 280 |
# File 'lib/openstax/aws/stack.rb', line 274 def wait_for_creation if !dry_run return if !creating? wait_for_stack_event(waiter_class: Aws::CloudFormation::Waiters::StackCreateComplete, word: "created") end end |
#wait_for_deletion ⇒ Object
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'lib/openstax/aws/stack.rb', line 290 def wait_for_deletion if !dry_run begin return if !deleting? wait_for_stack_event(waiter_class: Aws::CloudFormation::Waiters::StackDeleteComplete, word: "deleted") rescue Aws::CloudFormation::Errors::ValidationError => ee if ee. =~ /Stack.*does not exist/ logger.warn("Waiting for stack #{name} to be deleted failed because it does not exist") else raise end end end end |
#wait_for_update ⇒ Object
282 283 284 285 286 287 288 |
# File 'lib/openstax/aws/stack.rb', line 282 def wait_for_update if !dry_run return if !updating? # if not updating, waiting for an updated message will thrash until timeout wait_for_stack_event(waiter_class: Aws::CloudFormation::Waiters::StackUpdateComplete, word: "updated") end end |