class KumoKeisei::Stack
Constants
- RECOVERABLE_STATUSES
- TERMINATED_STATUSES
- UNRECOVERABLE_STATUSES
- UPDATEABLE_STATUSES
Attributes
env_name[R]
stack_name[R]
Public Class Methods
exists?(app_name, environment_name)
click to toggle source
# File lib/kumo_keisei/stack.rb, line 31 def self.exists?(app_name, environment_name) self.new(app_name, environment_name).exists? end
new(app_name, environment_name, options = { confirmation_timeout: 30, waiter_delay: 20, waiter_attempts: 90 })
click to toggle source
# File lib/kumo_keisei/stack.rb, line 35 def initialize(app_name, environment_name, options = { confirmation_timeout: 30, waiter_delay: 20, waiter_attempts: 90 }) @env_name = environment_name @app_name = app_name @stack_name = "#{app_name}-#{ environment_name }" @confirmation_timeout = options[:confirmation_timeout] @waiter_delay = options[:waiter_delay] @waiter_attempts = options[:waiter_attempts] end
Public Instance Methods
apply!(stack_config)
click to toggle source
# File lib/kumo_keisei/stack.rb, line 44 def apply!(stack_config) stack_config.merge!(env_name: @env_name) raise UsageError.new('You must provide a :template_path in the stack config hash for an apply! operation') unless stack_config.has_key?(:template_path) if updatable? update!(stack_config) else ensure_deleted! ConsoleJockey.write_line "Creating your new stack #{@stack_name}" create!(stack_config) end end
config(stack_config)
click to toggle source
# File lib/kumo_keisei/stack.rb, line 81 def config(stack_config) raise UsageError.new('You must provide a :config_path in the stack config hash to retrieve the stack\'s config') unless stack_config.has_key?(:config_path) environment_config(stack_config).config end
destroy!()
click to toggle source
# File lib/kumo_keisei/stack.rb, line 58 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/stack.rb, line 77 def exists? !get_stack.nil? end
logical_resource(resource_name)
click to toggle source
# File lib/kumo_keisei/stack.rb, line 71 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(name)
click to toggle source
# File lib/kumo_keisei/stack.rb, line 67 def outputs(name) return GetStackOutput.new(get_stack).output(name) end
params_template_path(stack_config)
click to toggle source
# File lib/kumo_keisei/stack.rb, line 91 def params_template_path(stack_config) stack_config.has_key?(:template_path) ? File.absolute_path(File.join(File.dirname(stack_config[:template_path]), "#{File.basename(stack_config[:template_path], '.*')}.yml.erb")) : nil end
plain_text_secrets(stack_config)
click to toggle source
# File lib/kumo_keisei/stack.rb, line 86 def plain_text_secrets(stack_config) raise UsageError.new('You must provide a :config_path in the stack config hash to retrieve the stack\'s plain_text_secrets') unless stack_config.has_key?(:config_path) environment_config(stack_config).plain_text_secrets end
Private Instance Methods
cf_params(stack_config, environment_config)
click to toggle source
# File lib/kumo_keisei/stack.rb, line 97 def cf_params(stack_config, environment_config) erb = params_template_erb(stack_config) return [] unless erb stack_params = YAML.load(erb.result(environment_config.get_binding)) KumoKeisei::ParameterBuilder.new(stack_params).params end
cloudformation()
click to toggle source
# File lib/kumo_keisei/stack.rb, line 124 def cloudformation @cloudformation ||= Aws::CloudFormation::Client.new end
create!(stack_config)
click to toggle source
# File lib/kumo_keisei/stack.rb, line 154 def create!(stack_config) cloudformation.create_stack( stack_name: @stack_name, template_body: File.read(stack_config[:template_path]), parameters: cf_params(stack_config, environment_config(stack_config)), capabilities: ["CAPABILITY_IAM"], on_failure: "DELETE" ) begin cloudformation.wait_until(:stack_create_complete, stack_name: @stack_name) { |waiter| waiter.delay = @waiter_delay; waiter.max_attempts = @waiter_attempts } rescue Aws::Waiters::Errors::UnexpectedError => ex handle_unexpected_error(ex) end end
ensure_deleted!()
click to toggle source
# File lib/kumo_keisei/stack.rb, line 128 def ensure_deleted! stack = get_stack return if stack.nil? return if TERMINATED_STATUSES.include? stack.stack_status cloudformation.delete_stack(stack_name: @stack_name) cloudformation.wait_until(:stack_delete_complete, stack_name: @stack_name) { |waiter| waiter.delay = @waiter_delay; waiter.max_attempts = @waiter_attempts } end
environment_config(stack_config)
click to toggle source
# File lib/kumo_keisei/stack.rb, line 191 def environment_config(stack_config) KumoConfig::EnvironmentConfig.new(stack_config) end
flash_message(message)
click to toggle source
# File lib/kumo_keisei/stack.rb, line 234 def flash_message(message) ConsoleJockey.flash_message(message) end
get_stack(options={})
click to toggle source
# File lib/kumo_keisei/stack.rb, line 116 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/stack.rb, line 225 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
params_template_erb(stack_config)
click to toggle source
# File lib/kumo_keisei/stack.rb, line 105 def params_template_erb(stack_config) template_path = params_template_path(stack_config) return nil unless template_path && File.exist?(template_path) ERB.new(File.read(template_path)) end
stack_events_url()
click to toggle source
# File lib/kumo_keisei/stack.rb, line 195 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/stack.rb, line 221 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/stack.rb, line 217 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/stack.rb, line 112 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/stack.rb, line 137 def updatable? stack = get_stack return false if stack.nil? return true if UPDATEABLE_STATUSES.include? stack.stack_status return false if TERMINATED_STATUSES.include? stack.stack_status if RECOVERABLE_STATUSES.include? stack.stack_status ConsoleJockey.write_line "There's a previous stack called #{@stack_name} that didn't create properly, it will be deleted and rebuilt." return false end 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!(stack_config)
click to toggle source
# File lib/kumo_keisei/stack.rb, line 171 def update!(stack_config) wait_until_ready(false) cloudformation.update_stack( stack_name: @stack_name, template_body: File.read(stack_config[:template_path]), parameters: cf_params(stack_config, environment_config(stack_config)), capabilities: ["CAPABILITY_IAM"] ) cloudformation.wait_until(:stack_update_complete, stack_name: @stack_name) { |waiter| waiter.delay = @waiter_delay; waiter.max_attempts = @waiter_attempts } 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/stack.rb, line 199 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