module References

this is a placeholder for anything related to resolving references

not sure if we are going to be able to have a useful generic set of code for references yet… in the meantime pile things up here and hope a pattern becomes clear

Public Class Methods

is_security_group_id_external(group_id) click to toggle source
# File lib/cfn-model/model/references.rb, line 85
def self.is_security_group_id_external(group_id)
  resolve_security_group_id(group_id).nil?
end
resolve_map(cfn_model, find_in_map) click to toggle source

Try to compute the FindInMap against a real Mapping

If anything doesn't match up - either a syntax error, a missing Mapping, or some other kind Cfn evaluation we don't understand, just return the call to FindInMap

# File lib/cfn-model/model/references.rb, line 99
def self.resolve_map(cfn_model, find_in_map)
  map_name = find_in_map['Fn::FindInMap'][0]
  top_level_key = find_in_map['Fn::FindInMap'][1]
  second_level_key = find_in_map['Fn::FindInMap'][2]

  map = cfn_model.mappings[map_name]

  return find_in_map if map.nil?

  top_level_resolved = resolve_value(cfn_model, top_level_key)

  return find_in_map if !map.has_key?(top_level_resolved)

  top_level = map[top_level_resolved]
  second_level = top_level[resolve_value(cfn_model, second_level_key)]

  return find_in_map if second_level.nil?

  second_level
end
resolve_reference(cfn_model, value) click to toggle source

For a !Ref to another resource.… just returns the !REf For a !Ref to a Parameter, then try to synthesize the value

# File lib/cfn-model/model/references.rb, line 53
def self.resolve_reference(cfn_model, value)
  ref_id = value['Ref']
  if ref_id.is_a? String
    if cfn_model.parameters.has_key?(ref_id)
      return value if cfn_model.parameters[ref_id].synthesized_value.nil?
      return cfn_model.parameters[ref_id].synthesized_value
    else
      return value
    end
  else
    value
  end
end
resolve_resource_id(reference, attr = nil) click to toggle source
# File lib/cfn-model/model/references.rb, line 67
def self.resolve_resource_id(reference, attr = nil)
  return nil if reference.is_a? String

  # an imported value can only yield a literal to an external resource vs. referencing something local
  if !reference['Ref'].nil?
    reference['Ref']
  elsif !reference['Fn::GetAtt'].nil?
    logical_resource_id_from_get_att reference['Fn::GetAtt'], attr
  else
    # anything else will be string manipulation functions
    # which again leads us back to a string which must be an external resource known out of band
    # so don't/can't link it up
    return nil
  end
rescue NoMethodError => e
  raise ParserError, e.inspect
end
resolve_security_group_id(group_id) click to toggle source
# File lib/cfn-model/model/references.rb, line 89
def self.resolve_security_group_id(group_id)
  resolve_resource_id group_id, 'GroupId'
end
resolve_value(cfn_model, value) click to toggle source
# File lib/cfn-model/model/references.rb, line 16
def self.resolve_value(cfn_model, value)
  if value.is_a? Hash
    if value.has_key?('Ref')
      resolve_reference(cfn_model, value)
    elsif value.has_key?('Fn::FindInMap')
      resolve_map(cfn_model, value)
    elsif value.has_key?('Fn::If')
      resolve_if(cfn_model, value)
    elsif value.has_key?('Fn::Sub')
      resolve_sub(cfn_model, value)
    elsif value.has_key?('Fn::GetAZs')
      resolve_getazs(cfn_model, value)
    elsif value.has_key?('Fn::Split')
      resolve_split(cfn_model, value)
    elsif value.has_key?('Fn::Join')
      resolve_join(cfn_model, value)
    elsif value.has_key?('Fn::Base64')
      resolve_base64(cfn_model, value)
    elsif value.has_key?('Fn::Select')
      resolve_select(cfn_model, value)
    elsif unsupported_passthru?(value)
      value
    else # another mapping
      value.map do |k,v|
        [k, resolve_value(cfn_model, v)]
      end.to_h
    end
  elsif value.is_a? Array
    value.map { |item| resolve_value(cfn_model, item) }
  else
    value
  end
end
unsupported_passthru?(value) click to toggle source
# File lib/cfn-model/model/references.rb, line 12
def self.unsupported_passthru?(value)
  value.has_key?('Fn::GetAtt') || value.has_key?('Fn::ImportValue') || value.has_key?('Fn::Transform') || value.has_key?('Fn::Cidr')
end

Private Class Methods

logical_resource_id_from_get_att(attribute_spec, attr_to_retrieve=nil) click to toggle source
# File lib/cfn-model/model/references.rb, line 233
def self.logical_resource_id_from_get_att(attribute_spec, attr_to_retrieve=nil)
  if attribute_spec.is_a? Array
    if !attr_to_retrieve || attribute_spec[1] == attr_to_retrieve
      return attribute_spec.first
    else
      # this could be a reference to a nested stack output so treat it as external
      # and presume the ingress is freestanding.
      return nil
    end
  elsif attribute_spec.is_a? String
    if !attr_to_retrieve || attribute_spec.split('.')[1] == attr_to_retrieve
      return attribute_spec.split('.').first
    else
      # this could be a reference to a nested stack output so treat it as external
      # and presume the ingress is freestanding.
      return nil
    end
  end
end
references_in_sub(string_value) click to toggle source
# File lib/cfn-model/model/references.rb, line 175
def self.references_in_sub(string_value)
  # ignore ${!foo} as cfn interprets that as the literal ${foo}
  references = string_value.scan /\${[^!].*?}/
  references.map { |reference| strip_cfn_interpolation(reference) }
end
resolvable_reference?(cfn_model, reference) click to toggle source
# File lib/cfn-model/model/references.rb, line 181
def self.resolvable_reference?(cfn_model, reference)
  resolved_value = References.resolve_value(cfn_model, {'Ref'=>reference})
  resolved_value != {'Ref'=>reference}
end
resolve_base64(cfn_model, reference) click to toggle source
# File lib/cfn-model/model/references.rb, line 142
def self.resolve_base64(cfn_model, reference)
  References.resolve_value(cfn_model, reference['Fn::Base64'])
end
resolve_getazs(cfn_model, reference) click to toggle source
# File lib/cfn-model/model/references.rb, line 160
def self.resolve_getazs(cfn_model, reference)
  number_azs = References.resolve_value(cfn_model, { 'Ref' => 'AWS::NumberAZs' })
  region = reference['Fn::GetAZs']
  if region == '' || region == { 'Ref' => 'AWS::Region' }
    region = References.resolve_value(cfn_model, { 'Ref' => 'AWS::Region' })
  end
  (('a'.ord)..('a'.ord+number_azs)).map do |az_number|
    "#{region}#{az_number.chr}"
  end
end
resolve_if(cfn_model, expression) click to toggle source
# File lib/cfn-model/model/references.rb, line 223
def self.resolve_if(cfn_model, expression)
  if_expression = expression['Fn::If']
  condition_name = if_expression[0]
  if cfn_model.conditions[condition_name]
    resolve_value(cfn_model, if_expression[1])
  else
    resolve_value(cfn_model, if_expression[2])
  end
end
resolve_join(cfn_model, reference) click to toggle source
# File lib/cfn-model/model/references.rb, line 146
def self.resolve_join(cfn_model, reference)
  delimiter = reference['Fn::Join'][0]
  items = References.resolve_value(cfn_model, reference['Fn::Join'][1])
  return reference unless items.is_a?(Array)
  items.join(delimiter)
end
resolve_longform_sub(cfn_model, expression) click to toggle source
# File lib/cfn-model/model/references.rb, line 201
def self.resolve_longform_sub(cfn_model, expression)
  array_value = expression['Fn::Sub']
  subbed_string_value = array_value[0]
  substitution_mapping = array_value[1]
  references_in_sub(subbed_string_value).each do |reference|
    if substitution_mapping.has_key? reference
      if References.resolve_value(cfn_model, substitution_mapping[reference]).is_a?(String)
        subbed_string_value = subbed_string_value.gsub(
          "${#{reference}}",
          References.resolve_value(cfn_model, substitution_mapping[reference])
        )
      end
    elsif resolvable_reference?(cfn_model, reference)
      subbed_string_value = subbed_string_value.gsub(
        "${#{reference}}",
        References.resolve_value(cfn_model, {'Ref'=>reference})
      )
    end
  end
  subbed_string_value
end
resolve_select(cfn_model, reference) click to toggle source
# File lib/cfn-model/model/references.rb, line 132
def self.resolve_select(cfn_model, reference)
  index = reference['Fn::Select'][0]
  collection = References.resolve_value(cfn_model, reference['Fn::Select'][1])
  if collection.is_a? Array
    collection[index]
  else
    reference
  end
end
resolve_shorthand_sub(cfn_model, expression) click to toggle source
# File lib/cfn-model/model/references.rb, line 186
def self.resolve_shorthand_sub(cfn_model, expression)
  string_value = expression['Fn::Sub']
  subbed_string_value = string_value
  has_unresolved_references = false
  references_in_sub(string_value).each do |reference|
    if resolvable_reference?(cfn_model, reference)
      subbed_string_value = subbed_string_value.gsub(
        "${#{reference}}",
        References.resolve_value(cfn_model, {'Ref'=>reference})
      )
    end
  end
  subbed_string_value
end
resolve_split(cfn_model, reference) click to toggle source
# File lib/cfn-model/model/references.rb, line 153
def self.resolve_split(cfn_model, reference)
  delimiter = reference['Fn::Split'][0]
  target_string = References.resolve_value(cfn_model, reference['Fn::Split'][1])
  return reference unless target_string.is_a?(String)
  target_string.split(delimiter)
end
resolve_sub(cfn_model, expression) click to toggle source
# File lib/cfn-model/model/references.rb, line 122
def self.resolve_sub(cfn_model, expression)
  if expression['Fn::Sub'].is_a? String
    resolve_shorthand_sub(cfn_model, expression)
  elsif expression['Fn::Sub'].is_a? Array
    resolve_longform_sub(cfn_model, expression)
  else
    expression
  end
end
strip_cfn_interpolation(reference) click to toggle source
# File lib/cfn-model/model/references.rb, line 171
def self.strip_cfn_interpolation(reference)
  reference[2..-2]
end