class Convection::Model::Template

Template container class

Constants

DEFAULT_VERSION

Attributes

attribute_mappings[R]
conditions[R]
mappings[R]
outputs[R]
parameters[R]
resource_collections[R]
resources[R]
stack[R]

Public Class Methods

new(stack = nil, &block) click to toggle source
# File lib/convection/model/template.rb, line 239
def initialize(stack = nil, &block)
  @definition = block

  @stack = stack
  @attribute_mappings = {}

  @version = DEFAULT_VERSION
  @description = ''

  @parameters = Collection.new
  @mappings = Collection.new
  @conditions = Collection.new
  @resources = Collection.new
  @resource_collections = Collection.new
  @outputs = Collection.new
  @metadata = Collection.new
end

Public Instance Methods

all_resources() click to toggle source
# File lib/convection/model/template.rb, line 293
def all_resources
  resource_collections.reduce(resources) do |result, (_name, resource_collection)|
    result.merge(resource_collection.resources)
  end
end
clone(stack_) click to toggle source
# File lib/convection/model/template.rb, line 257
def clone(stack_)
  Template.new(stack_, &@definition)
end
diff(other, stack_ = nil, retain: false) click to toggle source
# File lib/convection/model/template.rb, line 299
def diff(other, stack_ = nil, retain: false)
  # We want to accurately show when the DeletionPolicy is getting deleted and also when resources are going to be retained.
  # Sample DeletionPolicy Removal output
  # us-east-1      compare  Compare local state of stack test-logs-deletion with remote template
  # us-east-1       delete  Resources.sgtestConvectionDeletion.DeletionPolicy
  #
  # Sample Mixed Retain/Delete Resources
  # us-east-1       retain  Resources.ELBLoggingPolicy.DependsOn.AWS::S3::BucketPolicy.0
  # us-east-1       retain  Resources.ELBLoggingPolicy.DeletionPolicy
  # us-east-1       delete  Resources.sgtestConvectionDeletion.Type
  # us-east-1       delete  Resources.sgtestConvectionDeletion.Properties.AWS::EC2::SecurityGroup.GroupDescription
  # us-east-1       delete  Resources.sgtestConvectionDeletion.Properties.AWS::EC2::SecurityGroup.VpcId
  #
  events = render(stack_, retain: retain).diff(other).map { |diff| Diff.new(diff[0], *diff[1]) }

  # Top level events (changes to the resource directly) have keys with a format "Resources.{NAME}.{KEY}".
  # So we can count the number of separators to find them.
  top_level_events = events.select { |event| event.key.count('.') <= 2 }

  # We know something's a deleted resource when it has a top level "Type" attribute.
  type_suffix = '.Type'.freeze
  deleted_resources = top_level_events.select do |event|
    event.action == :delete && event.key.end_with?(type_suffix)
  end
  deleted_resources.map! { |event| event.key[0...-type_suffix.length] }

  # We know something's a retainable resource when it has a top level "DeletionPolicy" attribute.
  delete_policy_suffix = '.DeletionPolicy'.freeze
  retainable_resources = top_level_events.select do |event|
    event.action == :delete && event.key.end_with?(delete_policy_suffix) && event.theirs == 'Retain'
  end
  retainable_resources.map! { |event| event.key[0...-delete_policy_suffix.length] }
  retainable_resources.keep_if { |name| deleted_resources.include?(name) }

  events.each do |event|
    retained = retainable_resources.any? { |name| event.action == :delete && event.key.start_with?(name) }
    event.action = :retain if retained
  end

  events
end
execute() click to toggle source
# File lib/convection/model/template.rb, line 261
def execute
  instance_exec(&@definition)

  resource_collections.each do |_, group|
    group.run_definition
    group.execute
  end
end
render(stack_ = nil, retain: false) click to toggle source
# File lib/convection/model/template.rb, line 270
def render(stack_ = nil, retain: false)
  ## Instantiate a new template with the definition block and an other stack
  return clone(stack_).render unless stack_.nil?

  execute ## Process the template document

  {
    'AWSTemplateFormatVersion' => version,
    'Description' => description,
    'Parameters' => parameters.map(&:render),
    'Mappings' => mappings.map(&:render),
    'Conditions' => conditions.map(&:render),
    'Resources' => all_resources.map do |resource|
      if retain && resource.deletion_policy.nil?
        resource.deletion_policy('Retain')
      end
      resource.render
    end,
    'Outputs' => outputs.map(&:render),
    'Metadata' => metadata.map(&:render)
  }
end
template() click to toggle source
# File lib/convection/model/template.rb, line 235
def template
  self
end
to_json(stack_ = nil, pretty = false, retain: false) click to toggle source
# File lib/convection/model/template.rb, line 341
def to_json(stack_ = nil, pretty = false, retain: false)
  rendered_stack = render(stack_, retain: retain)
  validate(rendered_stack)
  return JSON.generate(rendered_stack) unless pretty
  JSON.pretty_generate(rendered_stack)
end
validate(rendered_stack = nil) click to toggle source
# File lib/convection/model/template.rb, line 348
def validate(rendered_stack = nil)
  %w(resources mappings parameters outputs description bytesize).map do |method|
    send("validate_#{method}", rendered_stack)
  end
end
validate_bytesize(rendered_stack) click to toggle source
# File lib/convection/model/template.rb, line 437
def validate_bytesize(rendered_stack)
  json = JSON.generate(rendered_stack)
  validate_compare(json.bytesize,
                   CF_MAX_BYTESIZE,
                   ExcessiveTemplateSizeError)
end
validate_compare(value, cf_max, error) click to toggle source
# File lib/convection/model/template.rb, line 354
def validate_compare(value, cf_max, error)
  limit_exceeded_error(value, cf_max, error) if value > cf_max
end
validate_description(rendered_stack) click to toggle source
# File lib/convection/model/template.rb, line 431
def validate_description(rendered_stack)
  validate_compare(rendered_stack['Description'].bytesize,
                   CF_MAX_DESCRIPTION_BYTESIZE,
                   ExcessiveDescriptionError)
end
validate_mappings(rendered_stack) click to toggle source
# File lib/convection/model/template.rb, line 369
def validate_mappings(rendered_stack)
  mappings = rendered_stack ['Mappings']
  validate_compare(mappings.count,
                   CF_MAX_MAPPINGS,
                   ExcessiveMappingsError)
  mappings.each do |_, value|
    validate_compare(
      value.count,
      CF_MAX_MAPPING_ATTRIBUTES,
      ExcessiveMappingAttributesError
    )
  end

  mappings.keys.each do |key|
    validate_compare(key.length,
                     CF_MAX_MAPPING_NAME,
                     ExcessiveMappingNameError)
  end

  ## XXX What are we trying to do here @aburke
  mapping_attributes = mappings.values.flat_map do |inner_hash|
    inner_hash.keys.select do |key|
      value = inner_hash[key]
    end
  end

  mapping_attributes.each do |attribute|
    validate_compare(attribute.length,
                     CF_MAX_MAPPING_ATTRIBUTE_NAME,
                     ExcessiveMappingAttributeNameError)
  end
end
validate_outputs(rendered_stack) click to toggle source
# File lib/convection/model/template.rb, line 419
def validate_outputs(rendered_stack)
  outputs = rendered_stack['Outputs']
  validate_compare(outputs.count,
                   CF_MAX_OUTPUTS,
                   ExcessiveOutputsError)
  largest_output_name = outputs.keys.max
  largest_output_name ||= ''
  validate_compare(largest_output_name.length,
                   CF_MAX_OUTPUT_NAME_CHARACTERS,
                   ExcessiveOutputNameError)
end
validate_parameters(rendered_stack) click to toggle source
# File lib/convection/model/template.rb, line 402
def validate_parameters(rendered_stack)
  parameters = rendered_stack['Parameters']
  validate_compare(parameters.count,
                   CF_MAX_PARAMETERS,
                   ExcessiveParametersError)
  largest_parameter_name = parameters.keys.max
  largest_parameter_name ||= ''
  validate_compare(largest_parameter_name.length,
                   CF_MAX_PARAMETER_NAME_CHARACTERS,
                   ExcessiveParameterNameError)
  parameters.values.each do |value|
    validate_compare(JSON.generate(value).bytesize,
                     CF_MAX_PARAMETER_VALUE_BYTESIZE,
                     ExcessiveParameterBytesizeError)
  end
end
validate_resources(rendered_stack) click to toggle source
# File lib/convection/model/template.rb, line 358
def validate_resources(rendered_stack)
  validate_compare(rendered_stack['Resources'].count,
                   CF_MAX_RESOURCES,
                   ExcessiveResourcesError)

  largest_resource_name = resources.keys.max || ''
  validate_compare(largest_resource_name.length,
                   CF_MAX_RESOURCE_NAME,
                   ExcessiveResourceNameError)
end