Class: Ufo::Cfn::Stack

Inherits:
Base show all
Extended by:
Memoist
Includes:
Hooks::Concern, TaskDefinition::Helpers::AwsHelper
Defined in:
lib/ufo/cfn/stack/status.rb,
lib/ufo/cfn/stack.rb,
lib/ufo/cfn/stack/vpc.rb,
lib/ufo/cfn/stack/vars.rb,
lib/ufo/cfn/stack/params.rb,
lib/ufo/cfn/stack/builder.rb,
lib/ufo/cfn/stack/template.rb,
lib/ufo/cfn/stack/custom_properties.rb

Overview

CloudFormation status codes, full list:

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-describing-stacks.html

CREATE_COMPLETE
ROLLBACK_COMPLETE
DELETE_COMPLETE
UPDATE_COMPLETE
UPDATE_ROLLBACK_COMPLETE

CREATE_FAILED
DELETE_FAILED
ROLLBACK_FAILED
UPDATE_ROLLBACK_FAILED

CREATE_IN_PROGRESS
DELETE_IN_PROGRESS
REVIEW_IN_PROGRESS
ROLLBACK_IN_PROGRESS
UPDATE_COMPLETE_CLEANUP_IN_PROGRESS
UPDATE_IN_PROGRESS
UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS
UPDATE_ROLLBACK_IN_PROGRESS

Defined Under Namespace

Classes: Builder, CustomProperties, Params, Status, Template, Vars, Vpc

Instance Attribute Summary

Attributes inherited from Ufo::CLI::Base

#task_definition

Instance Method Summary collapse

Methods included from Hooks::Concern

#run_hooks

Methods included from TaskDefinition::Helpers::AwsHelper

#aws

Methods included from Ufo::Concerns

#info, #ps

Methods included from Ufo::Concerns::Names

#names

Methods included from AwsServices

#acm, #applicationautoscaling, #aws_options, #cfn, #cloudwatchlogs, #ec2, #ecr, #ecs, #elb, #s3, #ssm_client, #waf_client

Methods included from AwsServices::Concerns

#find_stack, #find_stack_resources, #stack_resources, #status, #task_definition_arns

Methods inherited from Ufo::CLI::Base

#initialize

Methods included from Utils::Sure

#sure?

Methods included from Utils::Pretty

#pretty_home, #pretty_path, #pretty_time

Methods included from Utils::Logging

#logger

Constructor Details

This class inherits a constructor from Ufo::CLI::Base

Instance Method Details

#buildObject

Run hooks here so both ufo docker and ufo ship runs it

ufo docker => CLI::Build#build => Cfn::Stack#build


106
107
108
109
110
111
112
113
114
115
# File 'lib/ufo/cfn/stack.rb', line 106

def build
  run_hooks(name: "build", file: "ufo.rb") do
    vars = Vars.new(@options).values
    options_with_vars = @options.dup.merge(vars: vars)
    params = Params.new(options_with_vars)
    @parameters = params.build
    template = Template.new(options_with_vars)
    @template_body = template.body
  end
end

#cancelObject



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/ufo/cfn/stack.rb', line 174

def cancel
  stack = find_stack(@stack_name)
  unless stack
    logger.error "No #{@stack_name} stack to cancel".color(:red)
    exit 1
  end

  if stack.stack_status == "CREATE_IN_PROGRESS"
    cfn.delete_stack(stack_name: @stack_name)
    logger.info "Canceling stack creation"
  elsif stack.stack_status == "UPDATE_IN_PROGRESS"
    cfn.cancel_update_stack(stack_name: @stack_name)
    logger.info "Canceling stack update"
  else
    logger.info "The stack is not in a state to that is cancelable: #{stack.stack_status}"
  end
end

#continue_update_rollback(e) ⇒ Object

Super edge case where stack is in UPDATE_ROLLBACK_FAILED. Can reproduce by:

1. spinning ECS cluster down to 0 and deploying with ufo ship
2. after 3h will timeout and fail and goes into UPDATE_ROLLBACK_FAILED

Screenshot: capture.dropbox.com/Pdr8gijnaQvoMp2y

Will auto-retry once



72
73
74
75
76
77
78
79
80
81
82
# File 'lib/ufo/cfn/stack.rb', line 72

def continue_update_rollback(e)
  if e.message.include?('UPDATE_ROLLBACK_FAILED') && !@continue_update_rollback_tried
    logger.info "Stack in UPDATE_ROLLBACK_FAILED"
    logger.info "Trying a continue_update_rollback and will retry again once"
    cfn.continue_update_rollback(stack_name: @stack_name)
    status.wait
    @continue_update_rollback_tried ||= true
  else
    false
  end
end

#deployObject



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/ufo/cfn/stack.rb', line 30

def deploy
  build
  @stack = find_stack(@stack_name)
  if @stack && rollback_complete?(@stack)
    logger.info "Existing stack in ROLLBACK_COMPLETE state. Deleting stack before continuing."
    cfn.delete_stack(stack_name: @stack_name)
    status.wait
    status.reset
    @stack = nil # at this point stack has been deleted
  end

  exit_with_message(@stack) if @stack && !updatable?(@stack)

  run_hooks(name: "ship", file: "ufo.rb") do
    @stack ? perform(:update) : perform(:create)
    stop_old_tasks if @options[:stop_old_task]
    return unless @options[:wait]
    status.wait
  end

  logger.info status.rollback_error_message if status.update_rollback?
  status.success?
end

#exit_with_message(stack) ⇒ Object



122
123
124
125
126
127
# File 'lib/ufo/cfn/stack.rb', line 122

def exit_with_message(stack)
  url = "https://console.aws.amazon.com/cloudformation/home?region=#{region}#/stacks"
  logger.info "The stack is not in an updateable state: #{stack.stack_status.color(:yellow)}."
  logger.info "Here's the CloudFormation url to check for more details #{url}"
  exit 1
end

#handle_stack_error(e) ⇒ Object

Assume only first container_definition to get the container info. Stack:arn:aws:cloudformation:… is in ROLLBACK_COMPLETE state and can not be updated.



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/ufo/cfn/stack.rb', line 131

def handle_stack_error(e)
  case e.message
  when /state and can not be updated/
    logger.info "The #{@stack_name} stack is in a state that cannot be updated. Deleted the stack and try again."
    logger.info "ERROR: #{e.message}"
    if e.message.include?('UPDATE_ROLLBACK_FAILED')
      logger.info "You might be able to do a 'Continue Update Rollback' and skip some resources to get the stack back into a good state."
    end
    url = "https://console.aws.amazon.com/cloudformation/home?region=#{region}"
    logger.info "Here's the CloudFormation console url: #{url}"
    exit 1
  when /No updates are to be performed/
    logger.info "There are no updates to be performed. Exiting.".color(:yellow)
    exit 1
  when /YAML not well-formed/ # happens if a value is a serialize Ruby Object. See: https://gist.github.com/tongueroo/737531d0bc8c92d92b5cd00493e15d9e
    # e.message: Template format error: YAML not well-formed. (line 207, column 9)
    print_code(e)
  else
    raise
  end
end

#perform(action) ⇒ Object



54
55
56
57
58
59
60
61
# File 'lib/ufo/cfn/stack.rb', line 54

def perform(action)
  logger.info "#{action[0..-2].capitalize}ing stack #{@stack_name.color(:green)}"
  cfn.send("#{action}_stack", stack_options) # Example: cfn.send("update_stack", stack_options)
rescue Aws::CloudFormation::Errors::ValidationError => e
  try_continue_update_rollback = continue_update_rollback(e)
  try_continue_update_rollback && retry
  handle_stack_error(e)
end


153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/ufo/cfn/stack.rb', line 153

def print_code(exception)
  path = ".ufo/output/template.yml"
  md = exception.message.match(/line (\d+),/)
  line_number = md[1]
  logger.error "Template for debugging: #{path}"
  if md
    DslEvaluator.print_code(path, line_number)
    exit 1
  else
    raise
  end
end

#rollback_complete?(stack) ⇒ Boolean

Returns:

  • (Boolean)


166
167
168
# File 'lib/ufo/cfn/stack.rb', line 166

def rollback_complete?(stack)
  stack.stack_status == 'ROLLBACK_COMPLETE'
end

#scheduling_strategyObject



117
118
119
120
# File 'lib/ufo/cfn/stack.rb', line 117

def scheduling_strategy
  scheduling_strategy = Ufo.config.ecs.scheduling_strategy
  scheduling_strategy.upcase if scheduling_strategy
end

#stack_optionsObject



84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/ufo/cfn/stack.rb', line 84

def stack_options
  options = {
    capabilities: ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"],
    parameters: @parameters,
    stack_name: @stack_name,
    template_body: @template_body,
  }
  cfn = Ufo.config.cfn
  options[:notification_arns] = cfn.notification_arns if cfn.notification_arns
  options[:disable_rollback] = cfn.disable_rollback unless cfn.disable_rollback.nil?
  options[:tags] = tags(cfn.tags) if cfn.tags
  options
end

#tags(hash) ⇒ Object



98
99
100
101
102
# File 'lib/ufo/cfn/stack.rb', line 98

def tags(hash)
  hash.map do |k,v|
    { key: v, value: v }
  end
end

#updatable?(stack) ⇒ Boolean

Returns:

  • (Boolean)


170
171
172
# File 'lib/ufo/cfn/stack.rb', line 170

def updatable?(stack)
  stack.stack_status =~ /_COMPLETE$/ || stack.stack_status == 'UPDATE_ROLLBACK_FAILED'
end