lethalpaga/cfncli

View on GitHub
lib/cfncli/cloudformation.rb

Summary

Maintainability
A
1 hr
Test Coverage
require 'cfncli/cfn_client'
require 'cfncli/stack'
require 'cfncli/logger'
require 'cfncli/event_poller'

require 'waiting'
require 'pp'

module CfnCli
  class CloudFormation
    include CfnClient
    include Loggable

    # A list of options that only apply for stack creation
    CREATE_ONLY_OPTIONS = %w(on_failure).freeze

    def initialize
    end

    # Creates a stack and wait for the creation to be finished
    # @param options [Hash] Options for the stack creation
    #                       (@see http://docs.aws.amazon.com/sdkforruby/api/Aws/CloudFormation/Client.html)
    def create_stack(options, config = nil)
      create_or_update_stack(options, config)
    end

    # Creates a stack if it doesn't exist otherwise update it
    def create_or_update_stack(options, config = nil)
      opts = process_params(options.dup)

      stack_name = opts['stack_name']
      stack = create_stack_obj(stack_name, config)

      if stack.exists?
        CREATE_ONLY_OPTIONS.each { |key| opts.delete key }
        stack.update(opts)
      else
        stack.create(opts)
      end

      stack
    end

    # Creates or update the stack and list events
    def apply_and_list_events(options, config = nil)
      # Create/update the stack
      logger.debug "Creating stack #{options['stack_name']}"
      list_nested_events = options['list_nested_events']
      options.delete 'list_nested_events'
      stack = create_or_update_stack(options, config)

      events(stack, config, list_nested_events)
      Waiting.wait(interval: config.interval || default_config.interval, max_attempts: config.retries || default_config.retries) do |waiter|
        waiter.done if stack.finished? && !stack.listing_events?
      end

      stack
    end

    # List stack events
    def events(stack_or_name, config = nil, list_nested_events = true, reset_events = true, poller = nil, streamer = nil)
      stack = stack_or_name
      stack = create_stack_obj(stack_or_name, config) unless stack_or_name.is_a? CfnCli::Stack

      poller ||= EventPoller.new
      streamer ||= EventStreamer.new(stack, config)

      streamer.reset_events if reset_events

      logger.debug "Listing events for stack #{stack.stack_name}"
      stack.list_events(poller, streamer, config, nil, list_nested_events)
    end

    # Delete a stack
    def delete_stack(options, config = nil)
      stack = create_stack_obj(options['stack_name'], config)
      options['stack_name'] = stack.stack_id
      stack.delete(options, config)
      stack
    end

    def delete_and_list_events(options, config = nil)
      # Create/update the stack
      logger.debug "Deleting stack #{options['stack_name']}"
      list_nested_events = options['list_nested_events']
      options.delete 'list_nested_events'
      stack = delete_stack(options, config)

      events(stack.stack_id, config, list_nested_events)

      interval = config.interval if config
      interval = default_config.interval unless interval
      Waiting.wait(interval: interval, max_attempts: config.retries || default_config.retries) do |waiter|
        waiter.done if stack.finished? && !stack.listing_events?
        sleep 1
      end

      stack
    end

    # Returns the stack status
    def stack_successful?(stack_name)
      Stack.new(stack_name).succeeded?
    end

    # Converts the 'standard' json stack parameters format to the format
    # expected by the API
    # (see https://blogs.aws.amazon.com/application-management/post/Tx1A23GYVMVFKFD/Passing-Parameters-to-CloudFormation-Stacks-with-the-AWS-CLI-and-Powershell)
    def self.parse_json_params(params)
      params.map do |param|
        {
          parameter_key: param['ParameterKey'],
          parameter_value: param['ParameterValue']
        }
      end
    end

    def default_config
      Config::CfnClient.new
    end

    private

    # Creates a new stack object
    # Mainly useful to mock it in unit tests
    def create_stack_obj(stack_name, config)
      stack = CfnCli::Stack.new(stack_name, config)
      stack.fetch_stack_id if stack.exists?
      stack
    end

    # Process the parameters
    def process_params(opts)
      opts.delete('disable_rollback')
      opts
    end
  end
end