class CloudFormationTool::CloudFormation::Stack

Attributes

name[R]

Public Class Methods

new(name) click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 14
def initialize(name)
  @name = name
  @seenev = Set.new
  @watch_timeouts = 0
  @nested_stacks = Hash[]
end

Public Instance Methods

asgroups() click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 113
def asgroups
  output = []
  resources do |res|
    output << res if res.resource_type == 'AWS::AutoScaling::AutoScalingGroup'
  end
  output.collect do |res|
    res.extend(CloudFormationTool)
    res.instance_eval do
      def group
        Aws::AutoScaling::AutoScalingGroup.new(self.physical_resource_id, client: awsas).reload
      end
    end
    res
  end
end
cdns() click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 129
def cdns
  output = []
  resources do |res|
    output << res if res.resource_type == 'AWS::CloudFront::Distribution'
  end
  output.collect do |res|
    res.extend(CloudFrontDistribution)
  end
end
check_nested_stack(ev) click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 185
def check_nested_stack(ev)
  return unless ev.resource_type == "AWS::CloudFormation::Stack" and
    ev.logical_resource_id != self.name # not nested stack
  return if @nested_stacks.has_key? ev.logical_resource_id # seeing the first or last nested stack event - ignoring
  @nested_stacks[nested_stack_name(ev)] = ev.logical_resource_id
end
create(template, params = {}) click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 53
def create(template, params = {})
  @template = CloudFormation.parse(template).to_yaml(params)
  url = upload(make_filename('yaml'), @template, gzip: false)
  return update(url, template, params) if exist?
  log "Creating stack '#{name}' from '#{template}' params #{params.inspect}"
  valid_check do
    resp = awscf.create_stack({
      stack_name: @name,
      template_url: url,
      capabilities: %w(CAPABILITY_IAM CAPABILITY_NAMED_IAM),
      on_failure: "DO_NOTHING", ##"ROLLBACK",
      parameters: params.collect do |k,v|
        {
          parameter_key: k.to_s,
          parameter_value: v.to_s,
          use_previous_value: false,
        }
      end
    })
    @stack_id = resp.stack_id
  end
end
delete() click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 21
def delete
  awscf.delete_stack  stack_name: @name
end
each() { |ev| ... } click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 202
def each
  tracked_stacks.each do |name|
    events_for(name) do |ev|
      yield ev
    end
  end
end
events_for(stack_name) { |ev| ... } click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 210
def events_for(stack_name)
  token = nil
  sleep(if @_last_poll_time.nil?
    0
    else
      diff = Time.now - @_last_poll_time
      if diff < 1
        diff
      else
        0
      end
    end)
  begin
    resp = awscf.describe_stack_events stack_name: stack_name, next_token: token
    @watch_timeouts = 0
    resp.stack_events.each do |ev|
      yield ev
    end
  rescue Aws::CloudFormation::Errors::Throttling => e
    sleep 1
    retry
  rescue Seahorse::Client::NetworkingError => e # we get this when there's a timeout
    if (@watch_timeouts += 1) > 5
      raise CloudFormationTool::Errors::AppError, "Too many timeouts!"
    else
      retry
    end
  rescue Aws::CloudFormation::Errors::ValidationError => e
    if e.message =~ /does not exist/
      if stack_name == self.name
        raise CloudFormationTool::Errors::StackDoesNotExistError, "Stack does not exist"
      end
      # ignore "does not exist" errors on nested stacks - we may try to poll them before
      # they actually exist. We'll just try later
    else
      raise e
    end
  end
end
exist?() click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 25
def exist?
  begin
    awscf.describe_stacks stack_name: name
    true
  rescue Aws::CloudFormation::Errors::ValidationError => e
    false
  end
end
group() click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 121
def group
  Aws::AutoScaling::AutoScalingGroup.new(self.physical_resource_id, client: awsas).reload
end
is_final_event(ev) click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 192
def is_final_event(ev)
  ev.resource_type == "AWS::CloudFormation::Stack" and
  ev.resource_status =~ /(_COMPLETE|_FAILED)$/ and
  ev.logical_resource_id == self.name
end
logical_nested_stack_name(phys_name) click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 177
def logical_nested_stack_name(phys_name)
  @nested_stacks[phys_name] || 'unknown'
end
monitor(start_time = nil) click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 143
def monitor(start_time = nil)
  @nested_stacks = Hash[]
  done = false
  begin
    until done
      reverse_each do |ev|
        next if @seenev.add?(ev.event_id).nil?
        text = "#{ev.timestamp.strftime "%Y-%m-%d %H:%M:%S"}| " + %w(
          resource_type:40
          logical_resource_id:42
          resource_status
        ).collect { |field|
          (name,size) = field.split(":")
          size ||= 1
          (if name == 'logical_resource_id' and ev.stack_name != self.name
            logical_nested_stack_name(ev.stack_name) + "|"
          else
            ''
          end + ev.send(name.to_sym)).ljust(size.to_i, ' ')
        }.join("  ")
        text += " " + ev.resource_status_reason if ev.resource_status =~ /_FAILED/
        if start_time.nil? or start_time < ev.timestamp
          puts text
        end
        check_nested_stack(ev)
        done = is_final_event(ev)
      end
      sleep 1
    end
  rescue CloudFormationTool::Errors::StackDoesNotExistError => e
    puts "Stack #{name} does not exist"
  end
end
nested_stack_name(ev) click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 181
def nested_stack_name(ev)
  ev.physical_resource_id.split('/')[1]
end
output() click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 80
def output
  begin
    key_width = 0
    resp = awscf.list_exports
    resp.exports.select do |exp|
      exp.exporting_stack_id == stack_id
    end.each do |exp|
      key_width = [ key_width, exp.name.length ].max
    end.collect do |exp|
      "%-#{key_width}s: %s" % [ exp.name, exp.value ]
    end
  rescue Aws::CloudFormation::Errors::ValidationError => e
    raise CloudFormationTool::Errors::AppError, "Failed to get resources: #{e.message}"
  end
end
resources() { |res| ... } click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 96
def resources
  begin
    awscf.list_stack_resources(stack_name: @name).each do |resp|
      resp.stack_resource_summaries.each do |res|
        yield res
        if res.resource_type == 'AWS::CloudFormation::Stack'
          Stack.new(res.physical_resource_id).resources do |nested_res|
            yield nested_res
          end
        end
      end
    end
  rescue Aws::CloudFormation::Errors::ValidationError => e
    raise CloudFormationTool::Errors::AppError, "Failed to get resources: #{e.message}"
  end
end
see_events() click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 139
def see_events
  each { |e| @seenev << e.event_id }
end
stack_id() click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 76
def stack_id
  @stack_id ||= awscf.describe_stacks(stack_name: @name).stacks.first.stack_id
end
tracked_stacks() click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 198
def tracked_stacks
  [ self.name ] + @nested_stacks.keys.compact
end
update(url, filepath, params = {}) click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 34
def update(url, filepath, params = {})
  log "Updating existing stack '#{name}' from '#{filepath}' params #{params.inspect}"
  valid_check do
    resp = awscf.update_stack({
      stack_name: @name,
      template_url: url,
      capabilities: %w(CAPABILITY_IAM CAPABILITY_NAMED_IAM),
      parameters: params.collect do |k,v|
        {
          parameter_key: k.to_s,
          parameter_value: v.to_s,
          use_previous_value: false,
        }
      end
    })
    resp.stack_id
  end
end

Private Instance Methods

valid_check() { || ... } click to toggle source
# File lib/cloud_formation_tool/cloud_formation/stack.rb, line 252
def valid_check
  begin
    yield
  rescue Aws::CloudFormation::Errors::ValidationError => e
    raise CloudFormationTool::Errors::ValidationError, "Stack validation error: #{e.message}"
  end  
end