Class: CfnDsl::OrchestrationTemplate

Inherits:
JSONable
  • Object
show all
Defined in:
lib/cfndsl/orchestration_template.rb

Overview

Handles the overall template object rubocop:disable Metrics/ClassLength

Direct Known Subclasses

CloudFormationTemplate

Defined Under Namespace

Classes: RefHash

Constant Summary collapse

GLOBAL_REFS =
{
  'AWS::NotificationARNs' => 1,
  'AWS::Region' => 1,
  'AWS::StackId' => 1,
  'AWS::StackName' => 1,
  'AWS::AccountId' => 1,
  'AWS::NoValue' => 1,
  'AWS::URLSuffix' => 1,
  'AWS::Partition' => 1
}.freeze

Constants included from Functions

Functions::FN_SUB_SCANNER

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from JSONable

#as_json, #declare, #external_parameters, external_parameters, #ref_children, #to_json

Methods included from Functions

#FnAnd, #FnBase64, #FnCidr, #FnEquals, #FnFindInMap, #FnGetAZs, #FnGetAtt, #FnIf, #FnImportValue, #FnJoin, #FnNot, #FnOr, #FnSelect, #FnSplit, #FnSub, #Ref

Methods included from RefCheck

#build_references, #ref_children

Constructor Details

#initialize(description = nil, &block) ⇒ OrchestrationTemplate

Returns a new instance of OrchestrationTemplate.



183
184
185
186
187
# File 'lib/cfndsl/orchestration_template.rb', line 183

def initialize(description = nil, &block)
  @AWSTemplateFormatVersion = '2010-09-09'
  @Description = description if description
  declare(&block) if block_given?
end

Class Method Details

.create_array_property_def(resource, pname, pclass, info) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/cfndsl/orchestration_template.rb', line 93

def create_array_property_def(resource, pname, pclass, info)
  singular_name = CfnDsl::Plurals.singularize pname
  plural_name = singular_name == pname ? CfnDsl::Plurals.pluralize(pname) : pname

  if singular_name == plural_name
    # Generate the extended list concat method
    plural_name = nil
  elsif pname == plural_name && info['Properties'].include?(singular_name)
    # The singlular name is a different property, do not redefine it here but rather use the extended form
    #  with the plural name. This allows construction of deep types, but no mechanism to overwrite a previous value
    # (eg CodePipeline::Pipeline ArtifactStores vs ArtifactStore)
    # Note is is also possible (but unlikely) for the spec to change in a way that triggers this condition where it did not
    # before which will result in breaking behaviour for existing apps.
    singular_name = plural_name
    plural_name = nil
  elsif pname == singular_name && info['Properties'].include?(plural_name)
    # The plural name is a different property, do not redefine it here
    # Note it is unlikely that a singular form is going to be a List property if the plural form also exists.
    plural_name = singular_name
  end

  # Plural form just a normal property definition expecting an Array type
  create_property_def(resource, pname, Array, plural_name) if plural_name

  # Singular form understands concatenation and Fn::If property
  create_singular_property_def(resource, pname, pclass, singular_name) if singular_name
end

.create_resource_accessor(accessor, resource, type) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/cfndsl/orchestration_template.rb', line 121

def create_resource_accessor(accessor, resource, type)
  class_eval do
    CfnDsl.method_names(accessor) do |method|
      define_method(method) do |name, *values, &block|
        name = name.to_s
        @Resources ||= {}
        instance = @Resources[name]
        if !instance
          instance = resource.new(*values)
          # Previously the type was set after the block was evaled
          # But now trying to reset Type on a specific subtype will raise exception
          instance.instance_variable_set('@Type', type)
          @Resources[name] = instance
        elsif type != (other_type = instance.instance_variable_get('@Type'))
          raise ArgumentError, "Resource #{name}<#{other_type}> exists, and is not a <#{type}>"
        elsif !values.empty?
          raise ArgumentError, "wrong number of arguments (given #{values.size + 1}, expected 1) as Resource #{name} already exists"
        end
        @Resources[name].instance_eval(&block) if block
        instance
      end
    end
  end
end

.create_resource_def(name, info) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/cfndsl/orchestration_template.rb', line 62

def create_resource_def(name, info)
  resource = Class.new ResourceDefinition do
    # do not allow Type to be respecified
    def Type(type = nil)
      return @Type unless type
      raise CfnDsl::Error, "Cannot override previously defined Type #{@Type} with #{type}" unless type == @Type

      super
    end
  end
  resource_name = name.gsub(/::/, '_')
  type_module.const_set(resource_name, resource)
  info['Properties'].each_pair do |pname, ptype|
    # handle bogus List defined as Type
    unless ptype.is_a?(Array)
      pclass = type_module.const_get ptype
      if pclass.is_a?(Array)
        ptype = pclass
      else
        create_property_def(resource, pname, pclass)
      end
    end

    if ptype.is_a? Array
      pclass = type_module.const_get ptype.first
      create_array_property_def(resource, pname, pclass, info)
    end
  end
  resource_name
end

.create_typesObject



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/cfndsl/orchestration_template.rb', line 40

def create_types
  accessors = {}
  types_mapping = {}
  template_types['Resources'].each_pair do |resource, info|
    resource_name = create_resource_def(resource, info)
    parts = resource.split('::')
    until parts.empty?
      break if CfnDsl.reserved_items.include? parts.first

      abreve_name = parts.join('_')
      if accessors.key? abreve_name
        accessors[abreve_name] = :duplicate # Delete potentially ambiguous names
      else
        accessors[abreve_name] = type_module.const_get resource_name
        types_mapping[abreve_name] = resource
      end
      parts.shift
    end
  end
  accessors.each_pair { |acc, res| create_resource_accessor(acc, res, types_mapping[acc]) unless res == :duplicate }
end

Instance Method Details

#_check_refs(container_name, method, source_containers) ⇒ Object

rubocop:disable Metrics/CyclomaticComplexity



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/cfndsl/orchestration_template.rb', line 254

def _check_refs(container_name, method, source_containers)
  container = instance_variable_get("@#{container_name}s")
  return [] unless container

  invalids = []
  referred_by = RefHash.new { |h, k| h[k] = [] }
  self_check = source_containers.first.eql?(container)

  container.each_pair do |name, entry|
    name = name.to_s
    begin
      refs = entry.build_references([], self_check && name, method)
      refs.each { |r| referred_by[r.to_s] << name }
    rescue RefCheck::SelfReference, RefCheck::NullReference => e
      # Topological sort will not detect self or null references
      invalids.push("#{container_name} #{e.message}")
    end
  end

  referred_by.each_pair do |ref, names|
    unless valid_ref?(ref, source_containers)
      invalids.push "Invalid Reference: #{container_name}s #{names} refer to unknown #{method == :condition_refs ? 'Condition' : 'Reference'} #{ref}"
    end
  end

  begin
    referred_by.tsort if self_check && invalids.empty? # Check for cycles
  rescue TSort::Cyclic => e
    invalids.push "Cyclic references found in #{container_name}s #{referred_by} - #{e.message}"
  end

  invalids
end

#_ConditionObject



189
# File 'lib/cfndsl/orchestration_template.rb', line 189

alias _Condition Condition

#check_condition_refsObject



212
213
214
215
216
217
218
219
220
# File 'lib/cfndsl/orchestration_template.rb', line 212

def check_condition_refs
  invalids = []

  # Conditions can refer to other conditions in Fn::And, Fn::Or and Fn::Not
  invalids.concat(_check_refs(:Condition, :condition_refs, [@Conditions]))

  # They can also Ref Globals and Parameters (but not Resources))
  invalids.concat(_check_refs(:Condition, :all_refs, [GLOBAL_REFS, @Parameters]))
end

#check_output_refsObject



230
231
232
233
234
# File 'lib/cfndsl/orchestration_template.rb', line 230

def check_output_refs
  invalids = []
  invalids.concat(_check_refs(:Output, :all_refs, [@Resources, GLOBAL_REFS, @Parameters]))
  invalids.concat(_check_refs(:Output, :condition_refs, [@Conditions]))
end

#check_refsObject



202
203
204
205
# File 'lib/cfndsl/orchestration_template.rb', line 202

def check_refs
  invalids = check_condition_refs + check_resource_refs + check_output_refs + check_rule_refs
  invalids unless invalids.empty?
end

#check_resource_refsObject



222
223
224
225
226
227
228
# File 'lib/cfndsl/orchestration_template.rb', line 222

def check_resource_refs
  invalids = []
  invalids.concat(_check_refs(:Resource, :all_refs, [@Resources, GLOBAL_REFS, @Parameters]))

  # DependsOn and conditions in Fn::If expressions
  invalids.concat(_check_refs(:Resource, :condition_refs, [@Conditions]))
end

#check_rule_refsObject



236
237
238
239
240
241
# File 'lib/cfndsl/orchestration_template.rb', line 236

def check_rule_refs
  invalids = []
  invalids.concat(_check_refs(:Rule, :all_refs, [@Resources, GLOBAL_REFS, @Parameters]))
  invalids.concat(_check_refs(:Rule, :condition_refs, [@Conditions]))
  invalids
end

#Condition(name, expression) ⇒ Object #Condition(name) ⇒ Object

Condition has two usages at this level



194
195
196
197
198
199
200
# File 'lib/cfndsl/orchestration_template.rb', line 194

def Condition(name, expression = nil)
  if expression
    _Condition(name, expression)
  else
    { Condition: ConditionDefinition.new(name) }
  end
end

#valid_ref?(ref, ref_containers = [GLOBAL_REFS, @Resources, @Parameters]) ⇒ Boolean

Returns:

  • (Boolean)


207
208
209
210
# File 'lib/cfndsl/orchestration_template.rb', line 207

def valid_ref?(ref, ref_containers = [GLOBAL_REFS, @Resources, @Parameters])
  ref = ref.to_s
  ref_containers.any? { |c| c && c.key?(ref) }
end

#validateObject

rubocop:enable Metrics/CyclomaticComplexity

Raises:



289
290
291
292
293
294
# File 'lib/cfndsl/orchestration_template.rb', line 289

def validate
  errors = check_refs || []
  raise CfnDsl::Error, "#{errors.size} errors in template\n#{errors.join("\n")}" unless errors.empty?

  self
end