class KumoKeisei::CloudFormationStack

Constants

RECOVERABLE_STATUSES
UNRECOVERABLE_STATUSES
UPDATEABLE_STATUSES

Attributes

stack_name[R]

Public Class Methods

exists?(stack_name) click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 30
def self.exists?(stack_name)
  self.new(stack_name, nil).exists?
end
new(stack_name, stack_template, stack_params_filepath = nil, confirmation_timeout=30) click to toggle source

DEPRECATED: Please use Stack class instead of the CloudFormationStack class.

# File lib/kumo_keisei/cloud_formation_stack.rb, line 35
def initialize(stack_name, stack_template, stack_params_filepath = nil, confirmation_timeout=30)
  @stack_name = stack_name
  @stack_template = stack_template
  @stack_params_filepath = stack_params_filepath
  @confirmation_timeout = confirmation_timeout
  flash_message "Stack name: #{stack_name}"
end

Public Instance Methods

apply!(dynamic_params={}) click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 43
def apply!(dynamic_params={})
  if updatable?
    update!(dynamic_params)
  else
    ensure_deleted!
    ConsoleJockey.write_line "Creating your new stack #{@stack_name}"
    create!(dynamic_params)
  end
end
destroy!() click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 53
def destroy!
  return if get_stack.nil?

  flash_message "Warning! You are about to delete the CloudFormation Stack #{@stack_name}, enter 'yes' to continue."
  return unless ConsoleJockey.get_confirmation(@confirmation_timeout)

  wait_until_ready(false)
  ensure_deleted!
end
exists?() click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 75
def exists?
  !get_stack.nil?
end
logical_resource(resource_name) click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 69
def logical_resource(resource_name)
  response = cloudformation.describe_stack_resource(stack_name: @stack_name, logical_resource_id: resource_name)
  stack_resource = response.stack_resource_detail
  stack_resource.each_pair.reduce({}) {|acc, (k, v)| acc.merge(transform_logical_resource_id(k) => v) }
end
outputs(output) click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 63
def outputs(output)
  outputs_hash = get_stack.outputs.reduce({}) { |acc, o| acc.merge(o.output_key.to_s => o.output_value) }

  outputs_hash[output]
end

Private Instance Methods

cloudformation() click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 93
def cloudformation
  @cloudformation ||= Aws::CloudFormation::Client.new
end
create!(dynamic_params) click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 117
def create!(dynamic_params)
  cloudformation_params = ParameterBuilder.new(dynamic_params, @stack_params_filepath).params
  cloudformation.create_stack(
    stack_name: @stack_name,
    template_body: File.read(@stack_template),
    parameters: cloudformation_params,
    capabilities: ["CAPABILITY_IAM"],
    on_failure: "DELETE"
  )

  begin
    cloudformation.wait_until(:stack_create_complete, stack_name: @stack_name) { |waiter| waiter.delay = 20; waiter.max_attempts = 45 }
  rescue Aws::Waiters::Errors::UnexpectedError => ex
    handle_unexpected_error(ex)
  end
end
ensure_deleted!() click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 97
def ensure_deleted!
  stack = get_stack
  return if stack.nil?
  return if stack.stack_status == 'DELETE_COMPLETE'

  ConsoleJockey.write_line "There's a previous stack called #{@stack_name} that didn't create properly, I'll clean it up for you..."
  cloudformation.delete_stack(stack_name: @stack_name)
  cloudformation.wait_until(:stack_delete_complete, stack_name: @stack_name) { |waiter| waiter.delay = 20; waiter.max_attempts = 45 }
end
flash_message(message) click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 194
def flash_message(message)
  ConsoleJockey.flash_message(message)
end
get_stack(options={}) click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 85
def get_stack(options={})
  @stack = nil if options[:dump_cache]

  @stack ||= cloudformation.describe_stacks(stack_name: @stack_name).stacks.find { |stack| stack.stack_name == @stack_name }
rescue Aws::CloudFormation::Errors::ValidationError
  nil
end
handle_unexpected_error(error) click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 185
def handle_unexpected_error(error)
  if error.message =~ /does not exist/
    ConsoleJockey.write_line "There was an error during stack creation for #{@stack_name}, and the stack has been cleaned up."
    raise CreateError.new("There was an error during stack creation. The stack has been deleted.")
  else
    raise error
  end
end
stack_events_url() click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 155
def stack_events_url
  "https://console.aws.amazon.com/cloudformation/home?region=#{ENV['AWS_DEFAULT_REGION']}#/stacks?filter=active&tab=events&stackId=#{get_stack.stack_id}"
end
stack_operation_failed?(last_event_status) click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 181
def stack_operation_failed?(last_event_status)
  last_event_status =~ /ROLLBACK/
end
stack_ready?(last_event_status) click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 177
def stack_ready?(last_event_status)
  last_event_status =~ /COMPLETE/ || last_event_status =~ /ROLLBACK_FAILED/
end
transform_logical_resource_id(id) click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 81
def transform_logical_resource_id(id)
  id.to_s.split('_').map {|w| w.capitalize }.join
end
updatable?() click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 107
def updatable?
  stack = get_stack
  return false if stack.nil?

  return true if UPDATEABLE_STATUSES.include? stack.stack_status
  return false if RECOVERABLE_STATUSES.include? stack.stack_status
  raise UpdateError.new("Stack is in an unrecoverable state") if UNRECOVERABLE_STATUSES.include? stack.stack_status
  raise UpdateError.new("Stack is busy, try again soon")
end
update!(dynamic_params={}) click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 134
def update!(dynamic_params={})
  cloudformation_params = ParameterBuilder.new(dynamic_params, @stack_params_filepath).params
  wait_until_ready(false)

  cloudformation.update_stack(
    stack_name: @stack_name,
    template_body: File.read(@stack_template),
    parameters: cloudformation_params,
    capabilities: ["CAPABILITY_IAM"]
  )

  cloudformation.wait_until(:stack_update_complete, stack_name: @stack_name) { |waiter| waiter.delay = 20; waiter.max_attempts = 45 }
rescue Aws::CloudFormation::Errors::ValidationError => ex
  raise ex unless ex.message == "No updates are to be performed."
  ConsoleJockey.write_line "No changes need to be applied for #{@stack_name}."
rescue Aws::Waiters::Errors::FailureStateError
  ConsoleJockey.write_line "Failed to apply the environment update. The stack has been rolled back. It is still safe to apply updates."
  ConsoleJockey.write_line "Find error details in the AWS CloudFormation console: #{stack_events_url}"
  raise UpdateError.new("Stack update failed for #{@stack_name}.")
end
wait_until_ready(raise_on_error=true) click to toggle source
# File lib/kumo_keisei/cloud_formation_stack.rb, line 159
def wait_until_ready(raise_on_error=true)
  loop do
    stack = get_stack(dump_cache: true)

    if stack_ready?(stack.stack_status)
      if raise_on_error && stack_operation_failed?(stack.stack_status)
        raise stack.stack_status
      end

      break
    end
    puts "waiting for #{@stack_name} to be READY, current: #{last_event_status}"
    sleep 10
  end
rescue Aws::CloudFormation::Errors::ValidationError
  nil
end