Class: Convection::Control::Stack
- Inherits:
-
Object
- Object
- Convection::Control::Stack
- Defined in:
- lib/convection/control/stack.rb
Overview
The Stack class provides a state wrapper for CloudFormation Stacks. It tracks the state of the managed stack, and creates/updates accordingly. Stack is also region-aware, and can be used within a template to define resources that depend upon availability-zones or other region-specific neuances that cannot be represented as maps or require iteration.
Constant Summary
- CREATE_COMPLETE =
Represents a stack that has successfully been converged.
'CREATE_COMPLETE'.freeze
- CREATE_FAILED =
Represents a stack that has not successfully been converged.
'CREATE_FAILED'.freeze
- CREATE_IN_PROGRESS =
Represents a stack that is currently being converged for the first time.
'CREATE_IN_PROGRESS'.freeze
- DELETE_COMPLETE =
Represents a stack that has successfully been deleted.
'DELETE_COMPLETE'.freeze
- DELETE_FAILED =
Represents a stack that has not successfully been deleted.
'DELETE_FAILED'.freeze
- DELETE_IN_PROGRESS =
Represents a stack that is currently being deleted.
'DELETE_IN_PROGRESS'.freeze
- ROLLBACK_COMPLETE =
Represents a stack that has successfully been rolled back.
'ROLLBACK_COMPLETE'.freeze
- ROLLBACK_FAILED =
Represents a stack that has not successfully been rolled back.
'ROLLBACK_FAILED'.freeze
- ROLLBACK_IN_PROGRESS =
Represents a stack that is currently being rolled back.
'ROLLBACK_IN_PROGRESS'.freeze
- UPDATE_COMPLETE =
Represents a stack that has successfully been updated (re-converged).
'UPDATE_COMPLETE'.freeze
- UPDATE_COMPLETE_CLEANUP_IN_PROGRESS =
Represents a stack that is currently performing post-update cleanup.
'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS'.freeze
- UPDATE_FAILED =
Represents a stack that has not successfully been updated.
'UPDATE_FAILED'.freeze
- UPDATE_IN_PROGRESS =
Represents a stack that is currently being updated (re-converged).
'UPDATE_IN_PROGRESS'.freeze
- UPDATE_ROLLBACK_COMPLETE =
Represents a stack that has successfully rolled back an update (re-converge).
'UPDATE_ROLLBACK_COMPLETE'.freeze
- UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS =
Represents a stack that is currently performing post-update-rollback cleanup.
'UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS'.freeze
- UPDATE_ROLLBACK_FAILED =
Represents a stack that has successfully been rolled back after an update.
'UPDATE_ROLLBACK_FAILED'.freeze
- UPDATE_ROLLBACK_IN_PROGRESS =
Represents a stack that is currently performing a update rollback.
'UPDATE_ROLLBACK_IN_PROGRESS'.freeze
- NOT_CREATED =
Represents a stack that has not been created. The default state for a convection stack before getting its status.
'NOT_CREATED'.freeze
- TASK_COMPLETE =
Represents a stack task being completed.
'TASK_COMPLETE'.freeze
- TASK_FAILED =
Represents a stack task having failed.
'TASK_FAILED'.freeze
- TASK_IN_PROGRESS =
Represents a stack task that is currently in progress.
'TASK_IN_PROGRESS'.freeze
Instance Attribute Summary collapse
-
#attribute_mapping_values ⇒ Object
readonly
Returns the value of attribute attribute_mapping_values.
-
#attributes ⇒ Object
readonly
Returns the value of attribute attributes.
-
#capabilities ⇒ Object
readonly
Returns the value of attribute capabilities.
-
#cloud ⇒ Object
Returns the value of attribute cloud.
-
#credentials ⇒ Object
Returns the value of attribute credentials.
-
#errors ⇒ Object
readonly
Returns the value of attribute errors.
-
#exist ⇒ Boolean
(also: #exist?)
readonly
Whether the stack exists and has a status other than DELETED.
-
#id ⇒ Object
readonly
Returns the value of attribute id.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#on_failure ⇒ Object
Returns the value of attribute on_failure.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#outputs ⇒ Object
readonly
Returns the value of attribute outputs.
-
#parameters ⇒ Object
readonly
Returns the value of attribute parameters.
-
#region ⇒ Object
AWS-SDK.
-
#resources ⇒ Object
readonly
Returns the value of attribute resources.
-
#status ⇒ String
readonly
CloudFormation Stack status.
-
#tags ⇒ Object
readonly
Returns the value of attribute tags.
-
#tasks ⇒ Object
readonly
Returns the value of attribute tasks.
-
#template ⇒ Object
Returns the value of attribute template.
Attribute accessors collapse
- #[](key) ⇒ Object
- #[]=(key, value) ⇒ Object
- #get(*args) ⇒ Object
-
#include?(stack, key = nil) ⇒ Boolean
Whether the stack includes the specified key.
Stack state methods collapse
-
#complete? ⇒ Boolean
Whether the CloudFormation Stack is in one of the several *_COMPLETE states.
-
#error? ⇒ Boolean
Whether any errors occurred modifying the stack.
-
#fail? ⇒ Boolean
Whether the CloudFormation Stack is in one of the several *_FAILED states.
-
#in_progress? ⇒ Boolean
Whether or not the CloudFormation Stack is in one of several *IN_PROGRESS states.
-
#success? ⇒ Boolean
Whether the Stack state is now #complete? (with no errors present).
Render/diff methods collapse
-
#diff ⇒ Hash
A set of differences between the current template (in CloudFormation) and the state of the rendered template (what would be converged).
- #render ⇒ Object
-
#resource_changes? ⇒ Boolean
Whether the Resources section of the rendered template has any changes compared to the current template (in CloudFormation).
-
#resource_dependent_changes? ⇒ Boolean
Whether the any template sections dependent on the Resources section of the rendered template has any changes compared to the current template (in CloudFormation).
-
#to_json(pretty = false) ⇒ Object
The renedered CloudFormation Template JSON.
Controllers collapse
-
#apply(&block) ⇒ Object
Render the CloudFormation template and apply the Stack using the CloudFormation client.
-
#delete(&block) ⇒ Object
Delete the CloudFormation Stack using the CloudFormation client.
Task methods collapse
-
#after_create_task(task) ⇒ Object
Register a given task to run after creation of a stack.
-
#after_delete_task(task) ⇒ Object
Register a given task to run after deletion of a stack.
-
#after_update_task(task) ⇒ Object
Register a given task to run after an update of a stack.
-
#before_create_task(task) ⇒ Object
Register a given task to run before creation of a stack.
-
#before_delete_task(task) ⇒ Object
Register a given task to run before deletion of a stack.
-
#before_update_task(task) ⇒ Object
Register a given task to run before an update of a stack.
Instance Method Summary collapse
-
#availability_zones(&block) ⇒ Array<String>
The list of availability zones found by the call to ec2 client's describe availability zones.
-
#cloud_name ⇒ Object
rubocop:enable Metrics/LineLength.
-
#initialize(name, template, options = {}, &block) ⇒ Stack
constructor
rubocop:disable Metrics/LineLength.
-
#validate ⇒ Object
Validates a rendered template against the CloudFormation API.
-
#watch(poll = 2, &block) ⇒ Object
Loops through current events until the CloudFormation Stack has finished being modified.
Constructor Details
#initialize(name, template, options = {}, &block) ⇒ Stack
rubocop:disable Metrics/LineLength
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 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/convection/control/stack.rb', line 101 def initialize(name, template, = {}, &block) @name = name @template = template.clone(self) @errors = [] @cloud = .delete(:cloud) @cloud_name = .delete(:cloud_name) @region = .delete(:region) { |_| 'us-east-1' } @credentials = .delete(:credentials) @parameters = .delete(:parameters) { |_| {} } # Default empty hash @tags = .delete(:tags) { |_| {} } # Default empty hash .delete(:disable_rollback) # There can be only one... @on_failure = .delete(:on_failure) { |_| 'DELETE' } @capabilities = .delete(:capabilities) { |_| ['CAPABILITY_IAM'] } @attributes = .delete(:attributes) { |_| Model::Attributes.new } @options = = {}.tap do |opt| opt[:region] = @region opt[:credentials] = @credentials unless @credentials.nil? end @ec2_client = Aws::EC2::Client.new() @cf_client = Aws::CloudFormation::Client.new() ## Remote state @exist = false @status = NOT_CREATED @id = nil @outputs = {} @resources = {} @tasks = { after_create: [], after_delete: [], after_update: [], before_create: [], before_delete: [], before_update: [] } instance_exec(&block) if block @current_template = {} @last_event_seen = nil # First pass evaluation of stack # This is important because it: # * Catches syntax errors before starting a converge # * Builds a list of all resources that allows stacks early in # the dependency tree to know about later stacks. Some # clouds use this, for example, to create security groups early # in the dependency tree to avoid the chicken-and-egg problem. @template.execute ## Get initial state get_status(cloud_name) return unless exist? get_resources get_template resource_attributes get_events(1) # Get the latest page of events (Set @last_event_seen before starting) rescue Aws::Errors::ServiceError => e @errors << e end |
Instance Attribute Details
#attribute_mapping_values ⇒ Object (readonly)
Returns the value of attribute attribute_mapping_values
29 30 31 |
# File 'lib/convection/control/stack.rb', line 29 def attribute_mapping_values @attribute_mapping_values end |
#attributes ⇒ Object (readonly)
Returns the value of attribute attributes
25 26 27 |
# File 'lib/convection/control/stack.rb', line 25 def attributes @attributes end |
#capabilities ⇒ Object (readonly)
Returns the value of attribute capabilities
36 37 38 |
# File 'lib/convection/control/stack.rb', line 36 def capabilities @capabilities end |
#cloud ⇒ Object
Returns the value of attribute cloud
35 36 37 |
# File 'lib/convection/control/stack.rb', line 35 def cloud @cloud end |
#credentials ⇒ Object
Returns the value of attribute credentials
37 38 39 |
# File 'lib/convection/control/stack.rb', line 37 def credentials @credentials end |
#errors ⇒ Object (readonly)
Returns the value of attribute errors
26 27 28 |
# File 'lib/convection/control/stack.rb', line 26 def errors @errors end |
#exist ⇒ Boolean (readonly) Also known as: exist?
Returns whether the stack exists and has a status other than DELETED.
20 21 22 |
# File 'lib/convection/control/stack.rb', line 20 def exist @exist end |
#id ⇒ Object (readonly)
Returns the value of attribute id
15 16 17 |
# File 'lib/convection/control/stack.rb', line 15 def id @id end |
#name ⇒ Object (readonly)
Returns the value of attribute name
16 17 18 |
# File 'lib/convection/control/stack.rb', line 16 def name @name end |
#on_failure ⇒ Object
Returns the value of attribute on_failure
40 41 42 |
# File 'lib/convection/control/stack.rb', line 40 def on_failure @on_failure end |
#options ⇒ Object (readonly)
Returns the value of attribute options
27 28 29 |
# File 'lib/convection/control/stack.rb', line 27 def @options end |
#outputs ⇒ Object (readonly)
Returns the value of attribute outputs
30 31 32 |
# File 'lib/convection/control/stack.rb', line 30 def outputs @outputs end |
#parameters ⇒ Object (readonly)
Returns the value of attribute parameters
38 39 40 |
# File 'lib/convection/control/stack.rb', line 38 def parameters @parameters end |
#region ⇒ Object
AWS-SDK
34 35 36 |
# File 'lib/convection/control/stack.rb', line 34 def region @region end |
#resources ⇒ Object (readonly)
Returns the value of attribute resources
28 29 30 |
# File 'lib/convection/control/stack.rb', line 28 def resources @resources end |
#status ⇒ String (readonly)
Returns CloudFormation Stack status
22 23 24 |
# File 'lib/convection/control/stack.rb', line 22 def status @status end |
#tags ⇒ Object (readonly)
Returns the value of attribute tags
39 40 41 |
# File 'lib/convection/control/stack.rb', line 39 def @tags end |
#tasks ⇒ Object (readonly)
Returns the value of attribute tasks
31 32 33 |
# File 'lib/convection/control/stack.rb', line 31 def tasks @tasks end |
#template ⇒ Object
Returns the value of attribute template
17 18 19 |
# File 'lib/convection/control/stack.rb', line 17 def template @template end |
Instance Method Details
#[](key) ⇒ Object
179 180 181 |
# File 'lib/convection/control/stack.rb', line 179 def [](key) @attributes.get(name, key) end |
#[]=(key, value) ⇒ Object
184 185 186 |
# File 'lib/convection/control/stack.rb', line 184 def []=(key, value) @attributes.set(name, key, value) end |
#after_create_task(task) ⇒ Object
Register a given task to run after creation of a stack.
422 423 424 |
# File 'lib/convection/control/stack.rb', line 422 def after_create_task(task) @tasks[:after_create] << task end |
#after_delete_task(task) ⇒ Object
Register a given task to run after deletion of a stack.
427 428 429 |
# File 'lib/convection/control/stack.rb', line 427 def after_delete_task(task) @tasks[:after_delete] << task end |
#after_update_task(task) ⇒ Object
Register a given task to run after an update of a stack.
432 433 434 |
# File 'lib/convection/control/stack.rb', line 432 def after_update_task(task) @tasks[:after_update] << task end |
#apply(&block) ⇒ Object
Render the CloudFormation template and apply the Stack using the CloudFormation client.
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 |
# File 'lib/convection/control/stack.rb', line 292 def apply(&block) = @options.clone.tap do |o| o[:template_body] = to_json o[:parameters] = cf_parameters o[:capabilities] = capabilities end # Get the state of existence before creation existing_stack = exist? if existing_stack if diff.empty? ## No Changes. Just get resources and move on block.call(Model::Event.new(:complete, "Stack #{ name } has no changes", :info)) if block get_status return elsif !resource_changes? && resource_dependent_changes? = "Stack #{ name } has no convergable changes (you must update Resources to update Conditions, Metadata, or Outputs)" block.call(Model::Event.new(UPDATE_FAILED, , :warn)) if block get_status return end ## Execute before update tasks @tasks[:before_update].delete_if do |task| run_task(:before_update, task, &block) end ## Update @cf_client.update_stack(.tap do |o| o[:stack_name] = id end) else ## Execute before create tasks @tasks[:before_create].delete_if do |task| run_task(:before_create, task, &block) end ## Create @cf_client.create_stack(.tap do |o| o[:stack_name] = cloud_name o[:tags] = o[:on_failure] = on_failure end) get_status(cloud_name) # Get ID of new stack end watch(&block) if block # Block execution on stack status ## Execute after create tasks after_task_type = existing_stack ? :after_update : :after_create @tasks[after_task_type].delete_if do |task| run_task(after_task_type, task, &block) end rescue Aws::Errors::ServiceError => e @errors << e end |
#availability_zones(&block) ⇒ Array<String>
Returns the list of availability zones found by the call to ec2 client's describe availability zones
402 403 404 405 406 407 408 |
# File 'lib/convection/control/stack.rb', line 402 def availability_zones(&block) @availability_zones ||= @ec2_client.describe_availability_zones.availability_zones.map(&:zone_name).sort @availability_zones.each_with_index(&block) if block @availability_zones end |
#before_create_task(task) ⇒ Object
Register a given task to run before creation of a stack.
437 438 439 |
# File 'lib/convection/control/stack.rb', line 437 def before_create_task(task) @tasks[:before_create] << task end |
#before_delete_task(task) ⇒ Object
Register a given task to run before deletion of a stack.
442 443 444 |
# File 'lib/convection/control/stack.rb', line 442 def before_delete_task(task) @tasks[:before_delete] << task end |
#before_update_task(task) ⇒ Object
Register a given task to run before an update of a stack.
447 448 449 |
# File 'lib/convection/control/stack.rb', line 447 def before_update_task(task) @tasks[:before_update] << task end |
#cloud_name ⇒ Object
rubocop:enable Metrics/LineLength
159 160 161 162 163 |
# File 'lib/convection/control/stack.rb', line 159 def cloud_name return @cloud_name unless @cloud_name.nil? return name if cloud.nil? "#{ cloud }-#{ name }" end |
#complete? ⇒ Boolean
Returns whether the CloudFormation Stack is in one of the several *_COMPLETE states.
208 209 210 |
# File 'lib/convection/control/stack.rb', line 208 def complete? [CREATE_COMPLETE, ROLLBACK_COMPLETE, UPDATE_COMPLETE, UPDATE_ROLLBACK_COMPLETE].include?(status) end |
#delete(&block) ⇒ Object
Delete the CloudFormation Stack using the CloudFormation client.
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
# File 'lib/convection/control/stack.rb', line 354 def delete(&block) ## Execute before delete tasks @tasks[:before_delete].delete_if do |task| run_task(:before_delete, task, &block) end @cf_client.delete_stack( :stack_name => id ) ## Block execution on stack status watch(&block) if block get_status ## Execute after delete tasks @tasks[:after_delete].delete_if do |task| run_task(:after_delete, task, &block) end rescue Aws::Errors::ServiceError => e @errors << e end |
#diff ⇒ Hash
Returns a set of differences between the current template (in CloudFormation) and the state of the rendered template (what would be converged).
249 250 251 |
# File 'lib/convection/control/stack.rb', line 249 def diff @template.diff(@current_template) end |
#error? ⇒ Boolean
Returns whether any errors occurred modifying the stack.
220 221 222 |
# File 'lib/convection/control/stack.rb', line 220 def error? !errors.empty? end |
#fail? ⇒ Boolean
Returns whether the CloudFormation Stack is in one of the several *_FAILED states.
214 215 216 |
# File 'lib/convection/control/stack.rb', line 214 def fail? [CREATE_FAILED, ROLLBACK_FAILED, DELETE_FAILED, UPDATE_ROLLBACK_FAILED].include?(status) end |
#get(*args) ⇒ Object
189 190 191 |
# File 'lib/convection/control/stack.rb', line 189 def get(*args) @attributes.get(*args) end |
#in_progress? ⇒ Boolean
Returns whether or not the CloudFormation Stack is in one of several *IN_PROGRESS states.
199 200 201 202 203 204 |
# File 'lib/convection/control/stack.rb', line 199 def in_progress? [CREATE_IN_PROGRESS, ROLLBACK_IN_PROGRESS, DELETE_IN_PROGRESS, UPDATE_IN_PROGRESS, UPDATE_COMPLETE_CLEANUP_IN_PROGRESS, UPDATE_ROLLBACK_IN_PROGRESS, UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS].include?(status) end |
#include?(key) ⇒ Boolean #include?(stack_name, key) ⇒ Boolean
Returns whether the stack includes the specified key.
173 174 175 176 |
# File 'lib/convection/control/stack.rb', line 173 def include?(stack, key = nil) return @attributes.include?(name, stack) if key.nil? @attributes.include?(stack, key) end |
#render ⇒ Object
234 235 236 |
# File 'lib/convection/control/stack.rb', line 234 def render @template.render end |
#resource_changes? ⇒ Boolean
Returns whether the Resources section of the rendered template has any changes compared to the current template (in CloudFormation).
256 257 258 259 260 261 |
# File 'lib/convection/control/stack.rb', line 256 def resource_changes? ours = { 'Resources' => @template.resources.map(&:render) } thiers = { 'Resources' => @current_template['Resources'] } ours.diff(thiers).any? end |
#resource_dependent_changes? ⇒ Boolean
Returns whether the any template sections dependent on the Resources section of the rendered template has any changes compared to the current template (in CloudFormation). For example Conditions, Metadata, and Outputs depend on changes to resources to be able to converge. See also rapid7/convection#140.
270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'lib/convection/control/stack.rb', line 270 def resource_dependent_changes? ours = { 'Conditions' => @template.conditions.map(&:render), 'Outputs' => @template.outputs.map(&:render) } theirs = { 'Conditions' => @current_template['Conditions'], 'Outputs' => @current_template['Outputs'] } ours.diff(theirs).any? end |
#success? ⇒ Boolean
Returns whether the Stack state is now #complete? (with no errors present).
225 226 227 |
# File 'lib/convection/control/stack.rb', line 225 def success? !error? && complete? end |
#to_json(pretty = false) ⇒ Object
Returns the renedered CloudFormation Template JSON.
241 242 243 |
# File 'lib/convection/control/stack.rb', line 241 def to_json(pretty = false) @template.to_json(nil, pretty) end |
#validate ⇒ Object
Validates a rendered template against the CloudFormation API.
413 414 415 416 417 |
# File 'lib/convection/control/stack.rb', line 413 def validate result = @cf_client.validate_template(:template_body => template.to_json) fail result.context.http_response.inspect unless result.successful? puts "\nTemplate validated successfully" end |
#watch(poll = 2, &block) ⇒ Object
Loops through current events until the CloudFormation Stack has finished being modified.
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 |
# File 'lib/convection/control/stack.rb', line 381 def watch(poll = 2, &block) get_status loop do get_events.reverse_each do |event| block.call(Model::Event.from_cf(event)) end if block break unless in_progress? sleep poll get_status end rescue Aws::Errors::ServiceError => e @errors << e end |