class Stackr::CloudFormation

Public Instance Methods

create_change_set(stack_name, template, change_set_name, options) click to toggle source

template is a Stackr::Template

# File lib/stackr/cloudformation.rb, line 164
def create_change_set(stack_name, template, change_set_name, options)
  cfn = Aws::CloudFormation::Resource.new
  stack = cfn.stack(stack_name)
  if !stack
    raise Stackr::StackMissingError, "Stack #{stack_name} does not exist."
  end

  opts = {
    stack_name:      stack_name,
    change_set_name: change_set_name,
    parameters:      stack_parameters(template.parameter_map),
    capabilities:    template.capabilities
  }

  # are we using template_body or template_url?
  opts.merge!( template_argument(template) )

  begin
    # stack.update(opts)
    resp = cfn.client.create_change_set(opts)
  rescue Aws::CloudFormation::Errors::ValidationError => e
    case e.message
    when 'No updates are to be performed.'
      raise Stackr::StackUpdateNotRequiredError, "Stack [#{stack_name}] requires no updates."
    when "Stack [#{stack_name}] does not exist"
      raise Stackr::StackMissingError, e.message
    else
      raise e
    end
  rescue Aws::CloudFormation::Errors::InsufficientCapabilitiesException => e
    raise Stackr::InsufficientCapabilitiesError, "#{e.message}\nPlease add them to your template and run update again."
  end
  say "Change set #{change_set_name}, id: #{resp.id}"
end
create_stack(template, options) click to toggle source

Takes a Stackr::Template

# File lib/stackr/cloudformation.rb, line 93
def create_stack(template, options)
  cfn = Aws::CloudFormation::Resource.new
  stack_name = options[:name] || template.name

  opts = {
    stack_name:       options[:name] || template.name,
    parameters:       stack_parameters(template.parameter_map),
    disable_rollback: options[:disable_rollback],
    capabilities:     template.capabilities
  }

  # are we using template_body or template_url?
  opts.merge!( template_argument(template) )

  # Trap error raised when stack already exists
  begin
    cfn.create_stack(opts)
  rescue Aws::CloudFormation::Errors::AlreadyExistsException => e
    raise Stackr::StackAlreadyExistsError, e.message
  end
end
delete_change_set(change_set_name, stack_name) click to toggle source
# File lib/stackr/cloudformation.rb, line 227
def delete_change_set(change_set_name, stack_name)
  if stack_name.nil? && !change_set_name.start_with?('arn')
    raise Stackr::StackNameMissingError, "If change_set_name is not an ARN, you must specify stack_name"
  end

  cfn = Aws::CloudFormation::Client.new
  opts = {
    change_set_name: change_set_name,
    stack_name: stack_name
  }
  begin
    resp = cfn.delete_change_set(opts)
  rescue Aws::CloudFormation::Errors::ChangeSetNotFound => e
    raise Stackr::ChangeSetMissingError, e.message
  end
end
delete_stack(stack_name) click to toggle source
# File lib/stackr/cloudformation.rb, line 148
def delete_stack(stack_name)
  cfn = Aws::CloudFormation::Resource.new
  stack = cfn.stack(stack_name)
  if stack.exists?
    stack.delete
  else
    raise Stackr::StackMissingError, "Stack [#{stack_name}] does not exist."
  end
end
execute_change_set(change_set_name, stack_name) click to toggle source
# File lib/stackr/cloudformation.rb, line 244
def execute_change_set(change_set_name, stack_name)
  if stack_name.nil? && !change_set_name.start_with?('arn')
    raise Stackr::StackNameMissingError, "If change_set_name is not an ARN, you must specify stack_name"
  end

  cfn = Aws::CloudFormation::Client.new
  opts = {
    change_set_name: change_set_name,
    stack_name: stack_name
  }
  begin
    cfn.execute_change_set(opts)
  rescue Aws::CloudFormation::Errors::ChangeSetNotFound => e
    raise Stackr::ChangeSetMissingError, e.message
  end
end
is_too_big?(template_str) click to toggle source

Is this template too big to include in API calls? If so, we better upload it to S3

# File lib/stackr/cloudformation.rb, line 9
def is_too_big?(template_str)
  template_str.bytesize > 51200
  true
end
is_way_too_big?(template_str) click to toggle source

Is this template too big for CloudFormation

# File lib/stackr/cloudformation.rb, line 15
def is_way_too_big?(template_str)
  template_str.bytesize > 460800
end
list_change_sets(stack_name) click to toggle source
# File lib/stackr/cloudformation.rb, line 199
def list_change_sets(stack_name)
  cfn = Aws::CloudFormation::Resource.new
  stack = cfn.stack(stack_name)
  if !stack
    raise Stackr::StackMissingError, "Stack #{stack_name} does not exist."
  end
  resp = cfn.client.list_change_sets({stack_name: stack_name})
  return resp.summaries
end
list_stacks() click to toggle source
# File lib/stackr/cloudformation.rb, line 158
def list_stacks
  cfn = Aws::CloudFormation::Resource.new
  cfn.stacks
end
show_change_set(change_set_name, stack_name) click to toggle source
# File lib/stackr/cloudformation.rb, line 209
def show_change_set(change_set_name, stack_name)
  if stack_name.nil? && !change_set_name.start_with?('arn')
    raise Stackr::StackNameMissingError, "If change_set_name is not an ARN, you must specify stack_name"
  end

  cfn = Aws::CloudFormation::Client.new
  opts = {
    change_set_name: change_set_name,
    stack_name: stack_name
  }
  begin
    resp = cfn.describe_change_set(opts)
  rescue Aws::CloudFormation::Errors::ChangeSetNotFound => e
    raise Stackr::ChangeSetMissingError, e.message
  end
  return resp.data
end
stack_parameters(parameter_map) click to toggle source
# File lib/stackr/cloudformation.rb, line 64
def stack_parameters(parameter_map)
  parameter_map.map { |k,v| {parameter_key: k, parameter_value: ENV[v]} }
end
template_argument(template) click to toggle source

Return proper argument for CloudFormation api calls. If template is too big, upload it to s3 first and return {template_url: s3_url} otherwise return {template_body: template_contents} But if we've already uploaded the template to s3 this run, (because we validated it and then ran a create-stack), don't upload it a second time, just return the s3 url.

# File lib/stackr/cloudformation.rb, line 48
def template_argument(template)

  if is_way_too_big? template.body
    raise Stackr::TemplateTooBigError, "Template #{template.name} is too big for CloudFormation."
  end

  if is_too_big? template.body
    if template.url.nil?
      template.url = upload_to_s3(template.body, template.name)
    end
    return {template_url: template.url}
  end

  return {template_body: template.body}
end
update_stack(template, options) click to toggle source

Takes a Stackr::Template

# File lib/stackr/cloudformation.rb, line 116
def update_stack(template, options)
  cfn = Aws::CloudFormation::Resource.new
  stack_name = options[:name] || template.name
  stack = cfn.stack(stack_name)
  if !stack
    raise Stackr::StackMissingError, "Stack #{stack_name} does not exist."
  end

  opts = {
    parameters:    stack_parameters(template.parameter_map),
    capabilities:  template.capabilities
  }

  # are we using template_body or template_url?
  opts.merge!( template_argument(template) )

  begin
    stack.update(opts)
  rescue Aws::CloudFormation::Errors::ValidationError => e
    case e.message
    when 'No updates are to be performed.'
      raise Stackr::StackUpdateNotRequiredError, "Stack [#{stack_name}] requires no updates."
    when "Stack [#{stack_name}] does not exist"
      raise Stackr::StackMissingError, e.message
    else
      raise e
    end
  rescue Aws::CloudFormation::Errors::InsufficientCapabilitiesException => e
    raise Stackr::InsufficientCapabilitiesError, "#{e.message}\nPlease add them to your template and run update again."
  end
end
upload_to_s3(template_str, name) click to toggle source

Requires TEMPLATE_BUCKET environment variable to be set. If TEMPLATE_PREFIX environment variable is set, templates will be uploaded using that prefix.

# File lib/stackr/cloudformation.rb, line 22
def upload_to_s3(template_str, name)
  s3 = Aws::S3::Resource.new
  if ENV['TEMPLATE_BUCKET'].nil?
    raise Stackr::TemplateBucketMissingError, 'Please set TEMPLATE_BUCKET environment variable before uploading templates to S3.'
  end
  if ENV['ENVIRONMENT'].nil?
    raise Stackr::EnvironmentMissingError, 'Please set ENVIRONMENT environment variable before uploading templates to S3.'
  end
  bucket = s3.bucket(ENV['TEMPLATE_BUCKET'])
  key = "#{ENV['ENVIRONMENT']}/#{name}.json"
  if ENV['TEMPLATE_PREFIX']
    key = "#{ENV['TEMPLATE_PREFIX']}/#{key}"
  end
  s3_object = bucket.object(key)
  s3_object.put(body: template_str)
  return s3_object.public_url
end
validate_template(template) click to toggle source

Raise an error if the template does not validate takes a Stackr::Template

# File lib/stackr/cloudformation.rb, line 70
def validate_template(template)
  cfn = Aws::CloudFormation::Client.new

  opts = template_argument(template)
  begin
    resp = cfn.validate_template(opts)
  rescue Aws::CloudFormation::Errors::ValidationError => e
    raise Stackr::TemplateValidationError, e.message
  end

  # validate parameters
  # Make sure each parameter w/o a default has a value
  resp.parameters.each do |param|
    param_name = param[:parameter_key]
    env_var    = template.parameter_map[param_name]

    if param[:default_value].nil? && ENV[env_var].nil?
      raise Stackr::ParameterMissingError, "Required parameter #{param_name} (#{env_var}) not specified."
    end
  end
end