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
}.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.



177
178
179
180
181
# File 'lib/cfndsl/orchestration_template.rb', line 177

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

rubocop:disable Metrics/PerceivedComplexity



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/cfndsl/orchestration_template.rb', line 86

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

rubocop:enable Metrics/PerceivedComplexity



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/cfndsl/orchestration_template.rb', line 115

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



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/cfndsl/orchestration_template.rb', line 61

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|
    if ptype.is_a? Array
      pclass = type_module.const_get ptype.first
      create_array_property_def(resource, pname, pclass, info)
    else
      pclass = type_module.const_get ptype
      create_property_def(resource, pname, pclass)
    end
  end
  resource_name
end

.create_typesObject



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

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/PerceivedComplexity



248
249
250
251
252
253
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
# File 'lib/cfndsl/orchestration_template.rb', line 248

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



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

alias _Condition Condition

#check_condition_refsObject



206
207
208
209
210
211
212
213
214
# File 'lib/cfndsl/orchestration_template.rb', line 206

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



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

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



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

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

#check_resource_refsObject



216
217
218
219
220
221
222
# File 'lib/cfndsl/orchestration_template.rb', line 216

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



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

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



188
189
190
191
192
193
194
# File 'lib/cfndsl/orchestration_template.rb', line 188

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)


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

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/PerceivedComplexity

Raises:



283
284
285
286
287
288
# File 'lib/cfndsl/orchestration_template.rb', line 283

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

  self
end