class Stackit::ManagedStack

Constants

DRY_RUN_RESPONSE
REDACTED_TEXT

Attributes

debug[RW]
depends[RW]
dry_run[RW]
file_parameters[RW]
force[RW]
notifier[RW]
parameter_map[RW]
template[RW]
template_path[RW]
user_defined_parameters[RW]
wait[RW]

Public Class Methods

new(options={}) click to toggle source
Calls superclass method Stackit::Stack::new
# File lib/stackit/stack/managed_stack.rb, line 18
def initialize(options={})
  super(options)
  self.template_path = options[:template]
  self.user_defined_parameters = symbolized_user_defined_parameters(options[:user_defined_parameters])
  self.parameter_map = symbolized_parameter_map(options[:parameter_map])
  self.stack_name = options[:stack_name] || default_stack_name
  self.depends = options[:depends] || []
  self.disable_rollback = self.debug ? true : !!options[:disable_rollback]
  self.debug = !!options[:debug] || Stackit.debug
  self.force = options[:force]
  self.wait = options[:wait]
  self.dry_run = options[:dry_run]
  self.notifier = options[:notifier] || Stackit::ThorNotifier.new
  parse_file_parameters(options[:parameters_file]) if options[:parameters_file]
  create_stack_policy(options[:stack_policy])
  create_stack_policy_during_update(options[:stack_policy_during_update])
end

Public Instance Methods

change_set!() click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 130
def change_set!
  begin
    response = cloudformation_request(:create_change_set)
    notifier.response(response)
  rescue ::Aws::CloudFormation::Errors::AlreadyExistsException => e
    notifier.backtrace(e) if Stackit.debug
    notifier.error(e.message)
  rescue ::Aws::CloudFormation::Errors::ValidationError => e
    notifier.backtrace(e) if Stackit.debug
    notifier.error(e.message)
  end
  response
end
create!() click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 36
def create!
  begin
    response = cloudformation_request(:create_stack)
    notifier.response(response)
  rescue ::Aws::CloudFormation::Errors::AlreadyExistsException => e
    notifier.backtrace(e) if Stackit.debug
    notifier.error(e.message)
  rescue ::Aws::CloudFormation::Errors::ValidationError => e
    notifier.backtrace(e) if Stackit.debug
    notifier.error(e.message)
  end
  response
end
delete!() click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 68
def delete!
  begin
    response = cloudformation_request(:delete_stack)
    notifier.response(response)
  rescue ::Aws::CloudFormation::Errors::AlreadyExistsException => e
    notifier.backtrace(e) if Stackit.debug
    notifier.error(e.message)
  rescue ::Aws::CloudFormation::Errors::ValidationError => e
    notifier.backtrace(e) if Stackit.debug
    notifier.error(e.message)
  end
  response
end
deploy!() click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 82
def deploy!
  if !File.exist?(template)
    delete!
    wait_for_stack_to_delete
    notifier.success('Delete successful')
  elsif exist?
    begin
      update!
      wait_for_stack
      notifier.success('Update successful')
    rescue ::Aws::CloudFormation::Errors::ValidationError => e 
      if e.message.include? "No updates are to be performed"
        Stackit.logger.info "No updates are to be performed"
      elsif e.message.include? "_FAILED state and can not be updated"
        Stackit.logger.info 'Stack is in a failed state and can\'t be updated. Deleting/creating a new stack.'
        delete!
        wait_for_stack_to_delete
        create!
        wait_for_stack
        notifier.success('Stack deleted and re-created')
      else
        raise e
      end
    end
  else
    begin
      create!
      wait_for_stack
      notifier.success('Created successfully')
    rescue ::Aws::CloudFormation::Errors::ValidationError => e 
      if e.message.include? "_FAILED state and can not be updated"
        Stackit.logger.info 'Stack already exists, is in a failed state, and can\'t be updated. Deleting and creating a new stack.'
        delete!
        wait_for_stack_to_delete
        create!
        wait_for_stack
        notifier.success('Stack deleted and re-created')
      else
        raise e
      end
    end
  end
end
describe() click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 144
def describe
  response = cloudformation_request(:describe_stacks)
  if response && response[:stacks]
    response[:stacks].first
  else
    nil
  end
rescue ::Aws::CloudFormation::Errors::ValidationError
  nil
end
exist?() click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 126
def exist?
  describe != nil
end
update!() click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 50
def update!
  begin
    response = cloudformation_request(:update_stack)
    notifier.response(response)
  rescue ::Aws::CloudFormation::Errors::AlreadyExistsException => e
    notifier.backtrace(e) if Stackit.debug
    notifier.error(e.message)
  rescue ::Aws::CloudFormation::Errors::ValidationError => e
    if e.message =~ /No updates are to be performed./
      notifier.success(e.message)
    else
      notifier.backtrace(e) if Stackit.debug
      notifier.error(e.message)
    end
  end
  response
end

Private Instance Methods

capabilities() click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 228
def capabilities
  template.needs_iam_capability? ? ['CAPABILITY_IAM'] : []
end
cloudformation_request(action) click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 232
def cloudformation_request(action)
  Stackit.logger.debug "ManagedStack CloudFormation API request: #{action}"
  self.template = create_template(template_path, action);
  cloudformation_options = create_cloudformation_options(action)
  if debug
    Stackit.logger.debug "#{action} request parameters: "
    opts = cloudformation_options.clone
    if opts[:parameters]
      opts[:parameters].each do |param|
        key = param['parameter_key']
        param['parameter_value'] = REDACTED_TEXT if key =~ /username|password/i && !debug
      end
    end
    opts[:template_body] = REDACTED_TEXT if opts[:template_body]
    opts[:stack_policy_body] = JSON.parse(opts[:stack_policy_body]) if opts[:stack_policy_body]
    opts[:stack_policy_during_update_body] = 
      JSON.parse(opts[:stack_policy_during_update_body]) if opts[:stack_policy_during_update_body]
    pp opts
  end
  response = dry_run ? dry_run_response : cloudformation.send(action, cloudformation_options)
  wait_for_stack if wait
  response
end
create_cloudformation_options(action) click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 256
def create_cloudformation_options(action)
  case action
  when :create_stack
    create_stack_request_params.merge(template.options).merge(
      parameters: to_request_parameters(merged_parameters),
      capabilities: capabilities
    )
  when :update_stack
    update_stack_request_params.merge(template.options).merge(
      parameters: to_request_parameters(merged_parameters),
      capabilities: capabilities,
      use_previous_template: template.options[:template_body].nil? && template.options[:template_url].nil?
    )
  when :delete_stack
    delete_stack_request_params
  when :create_change_set
    change_set_request_params.merge(template.options).merge(
      parameters: to_request_parameters(merged_parameters),
      capabilities: capabilities,
      use_previous_template: template.options[:template_body].nil? && template.options[:template_url].nil?
    )
  end
end
create_stack_policy(f) click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 193
def create_stack_policy(f)
  policy_path = f ? f : File.join(Stackit.home, 'stackit', 'stack', 'default-stack-policy.json')
  if policy_path =~ /^http/
    self.stack_policy_url = policy_path
  else
    raise "Unable to locate policy: #{policy_path}" unless File.exist?(policy_path)
    self.stack_policy_body = File.read(policy_path)
  end
end
create_stack_policy_during_update(f) click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 203
def create_stack_policy_during_update(f)
  policy_path = f ? f : File.join(Dir.pwd, "#{stack_name}-update-policy.json")
  if !File.exist?(policy_path)
    Stackit.logger.warn "Unable to locate stack policy during update file: #{policy_path}"
    return
  end
  if policy_path =~ /^http/
    self.stack_policy_during_update_url = policy_path
  else
    raise "Unable to locate update policy: #{policy_path}" unless File.exist?(policy_path)
    self.stack_policy_during_update_body = File.read(policy_path)
  end
end
create_template(t, action) click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 180
def create_template(t, action)
  template_path = t ? t : File.join(Dir.pwd, 'cloudformation', "#{stack_name.underscore.dasherize}.json")
  if !File.exist?(template_path)
    return if action == :delete_stack
    raise "Unable to locate template: #{template_path}"
  end
  template = Template.new(
    :cloudformation => cloudformation,
    :template_path => template_path
  )
  template.parse!
end
default_stack_name() click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 176
def default_stack_name
  self.class.name.demodulize
end
dry_run_response() click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 308
def dry_run_response
  DRY_RUN_RESPONSE.new('arn:stackit:dry-run:complete')
end
mapped_key(param) click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 300
def mapped_key(param)
  if parameter_map.has_key?(param.to_sym)
    parameter_map[param.to_sym]
  else
    param.to_sym
  end
end
merged_parameters() click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 280
def merged_parameters
  merged_params = template.parsed_parameters
  merged_params.merge!(file_parameters) if file_parameters
  depends.each do |dependent_stack_name|        
    this_stack = Stack.new({
      cloudformation: cloudformation,
      stack_name: dependent_stack_name
    })
    merged_params.select { |param|
      !this_stack[mapped_key(param.to_s)].nil?
    }.each do | param_name, param_value |
      merged_params.merge!(param_name => this_stack[mapped_key(param_name)])
    end
  end
  user_defined_parameters.each { |k, v|
    user_defined_parameters[k] = v.to_s
  }
  merged_params.merge!(user_defined_parameters)
end
parse_file_parameters(parameters_file) click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 217
def parse_file_parameters(parameters_file)
  if File.exist?(parameters_file)
    Stackit.logger.info "Parsing cloudformation parameters file: #{parameters_file}"
    @file_parameters = {}
    file_params = JSON.parse(File.read(parameters_file))
    file_params.inject(@file_parameters) do |hash, param|
      hash.merge!(param['ParameterKey'].to_sym => param['ParameterValue'])
    end
  end
end
symbolized_parameter_map(param_map) click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 168
def symbolized_parameter_map(param_map)
  if param_map
    param_map.symbolize_keys!
  else
    {}
  end
end
symbolized_user_defined_parameters(params) click to toggle source
# File lib/stackit/stack/managed_stack.rb, line 160
def symbolized_user_defined_parameters(params)
  if params
    params.symbolize_keys!
  else
    {}
  end
end