cfndsl 0.15.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +3 -0
  3. data/.rubocop.yml +6 -6
  4. data/.travis.yml +10 -5
  5. data/CHANGELOG.md +721 -400
  6. data/Gemfile +2 -0
  7. data/README.md +85 -23
  8. data/Rakefile +28 -6
  9. data/TODO.md +18 -0
  10. data/UPGRADING.md +22 -0
  11. data/cfndsl.gemspec +17 -16
  12. data/exe/cfndsl +5 -0
  13. data/lib/cfndsl.rb +3 -113
  14. data/lib/cfndsl/aws/cloud_formation_template.rb +10 -1
  15. data/lib/cfndsl/aws/patches/000_CloudFormationResourceSpecification.json +51726 -0
  16. data/lib/cfndsl/aws/patches/000_sam.spec.json +1242 -0
  17. data/lib/cfndsl/aws/patches/100_sam.spec_DeploymentPreference_patch.json +64 -0
  18. data/lib/cfndsl/aws/patches/200_Scrutinies_patch.json +86 -0
  19. data/lib/cfndsl/aws/patches/500_Cognito_IdentityPoolRoleAttachment_patches.json +25 -0
  20. data/lib/cfndsl/aws/patches/500_IoT1Click_patch_PlacementTemplate_DeviceTemplates.json +20 -0
  21. data/lib/cfndsl/aws/patches/500_NetworkAclEntry_patch.json +16 -0
  22. data/lib/cfndsl/aws/patches/500_SAM_Serverless_Function_S3Event_Events_patch.json +16 -0
  23. data/lib/cfndsl/aws/patches/500_SAM_Serverless_Function_S3Location_Version_patch.json +16 -0
  24. data/lib/cfndsl/aws/patches/500_SSM_AssociationName_patch.json +16 -0
  25. data/lib/cfndsl/aws/patches/500_VPCEndpoint_patch.json +17 -0
  26. data/lib/cfndsl/aws/patches/510_ElasticSearch_Domain_patches.json +15 -0
  27. data/lib/cfndsl/aws/patches/520_ServiceDiscovery_InstanceAttributes_patch.json +16 -0
  28. data/lib/cfndsl/aws/patches/600_RefKinds_patch.json +3654 -0
  29. data/lib/cfndsl/aws/patches/700_SAM_Serverless_Function_InlineCode_patch.json +20 -0
  30. data/lib/cfndsl/aws/patches/800_List_types_patch.json +115 -0
  31. data/lib/cfndsl/aws/resource_specification.json +39955 -9293
  32. data/lib/cfndsl/aws/types.rb +5 -3
  33. data/lib/cfndsl/cfnlego.rb +34 -0
  34. data/lib/{cfnlego → cfndsl/cfnlego}/cloudformation.erb +0 -0
  35. data/lib/{cfnlego → cfndsl/cfnlego}/cloudformation.rb +3 -1
  36. data/lib/{cfnlego → cfndsl/cfnlego}/resource.rb +5 -8
  37. data/lib/cfndsl/cloudformation.rb +114 -0
  38. data/lib/cfndsl/conditions.rb +13 -1
  39. data/lib/cfndsl/creation_policy.rb +3 -1
  40. data/lib/cfndsl/deep_merge.rb +4 -0
  41. data/lib/cfndsl/external_parameters.rb +12 -13
  42. data/lib/cfndsl/globals.rb +51 -10
  43. data/lib/cfndsl/json_serialisable_object.rb +4 -2
  44. data/lib/cfndsl/jsonable.rb +51 -68
  45. data/lib/cfndsl/mappings.rb +3 -1
  46. data/lib/cfndsl/module.rb +18 -5
  47. data/lib/cfndsl/names.rb +2 -0
  48. data/lib/cfndsl/orchestration_template.rb +193 -73
  49. data/lib/cfndsl/outputs.rb +7 -1
  50. data/lib/cfndsl/parameters.rb +3 -1
  51. data/lib/cfndsl/plurals.rb +23 -10
  52. data/lib/cfndsl/properties.rb +3 -1
  53. data/lib/cfndsl/rake_task.rb +217 -15
  54. data/lib/cfndsl/ref_check.rb +21 -11
  55. data/lib/cfndsl/resources.rb +8 -19
  56. data/lib/cfndsl/rules.rb +46 -0
  57. data/lib/cfndsl/runner.rb +143 -0
  58. data/lib/cfndsl/specification.rb +82 -84
  59. data/lib/cfndsl/types.rb +212 -95
  60. data/lib/cfndsl/update_policy.rb +3 -1
  61. data/lib/cfndsl/version.rb +3 -1
  62. data/lib/deep_merge/core.rb +10 -6
  63. data/lib/deep_merge/deep_merge.rb +3 -1
  64. data/sample/autoscale.rb +1 -1
  65. data/sample/autoscale2.rb +4 -3
  66. data/sample/circular.rb +2 -0
  67. data/sample/codedeploy.rb +3 -1
  68. data/sample/config_service.rb +5 -3
  69. data/sample/ecs.rb +3 -1
  70. data/sample/export.rb +5 -3
  71. data/sample/iam_policies.rb +2 -0
  72. data/sample/import.rb +4 -2
  73. data/sample/lambda.rb +3 -1
  74. data/sample/s3.rb +2 -0
  75. data/sample/t1.rb +3 -1
  76. data/sample/vpc_example.rb +3 -1
  77. data/sample/vpc_with_vpn_example.rb +3 -1
  78. data/spec/aws/ec2_security_group_spec.rb +2 -0
  79. data/spec/aws/ecs_task_definition_spec.rb +2 -0
  80. data/spec/aws/iam_managed_policy_spec.rb +2 -0
  81. data/spec/aws/kms_alias_spec.rb +2 -0
  82. data/spec/aws/list_type_patches_spec.rb +35 -0
  83. data/spec/aws/logs_log_group_spec.rb +2 -0
  84. data/spec/aws/nested_arrays_spec.rb +194 -0
  85. data/spec/aws/rds_db_instance_spec.rb +2 -0
  86. data/spec/aws/serverless_spec.rb +2 -2
  87. data/spec/cfndsl_spec.rb +102 -63
  88. data/spec/cli_spec.rb +52 -49
  89. data/spec/cloud_formation_template_spec.rb +235 -0
  90. data/spec/condition_spec.rb +24 -0
  91. data/spec/deep_merge_spec.rb +2 -0
  92. data/spec/direct_ruby_spec.rb +19 -0
  93. data/spec/external_parameters_spec.rb +25 -20
  94. data/spec/fixtures/condition-assertion.json +1 -0
  95. data/spec/fixtures/params.json +1 -0
  96. data/spec/fixtures/params.yaml +2 -0
  97. data/spec/fixtures/params_struct1.yaml +4 -0
  98. data/spec/fixtures/params_struct2.yaml +4 -0
  99. data/spec/fixtures/rule-assertion.json +1 -0
  100. data/spec/fixtures/test.rb +12 -4
  101. data/spec/generate_spec.rb +4 -0
  102. data/spec/jsonable_spec.rb +2 -0
  103. data/spec/metadata_spec.rb +2 -0
  104. data/spec/names_spec.rb +2 -0
  105. data/spec/output_spec.rb +2 -0
  106. data/spec/plurals_spec.rb +2 -0
  107. data/spec/resource_name_spec.rb +21 -0
  108. data/spec/resources_spec.rb +2 -7
  109. data/spec/rule_spec.rb +17 -0
  110. data/spec/spec_helper.rb +4 -7
  111. data/spec/support/shared_examples/orchestration_template.rb +17 -2
  112. data/spec/transform_spec.rb +2 -0
  113. data/spec/types_definition_spec.rb +6 -7
  114. metadata +79 -25
  115. data/bin/cfndsl +0 -143
  116. data/lib/cfndsl/errors.rb +0 -29
  117. data/lib/cfndsl/os/heat_template.rb +0 -16
  118. data/lib/cfndsl/os/types.rb +0 -12
  119. data/lib/cfndsl/os/types.yaml +0 -2423
  120. data/lib/cfndsl/patches.rb +0 -98
  121. data/lib/cfnlego.rb +0 -42
  122. data/spec/fixtures/heattest.rb +0 -22
  123. data/spec/heat_template_spec.rb +0 -5
@@ -1,4 +1,6 @@
1
- require 'cfndsl/jsonable'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'jsonable'
2
4
 
3
5
  module CfnDsl
4
6
  # Handles mapping objects
data/lib/cfndsl/module.rb CHANGED
@@ -1,5 +1,7 @@
1
- require 'cfndsl/plurals'
2
- require 'cfndsl/names'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'plurals'
4
+ require_relative 'names'
3
5
 
4
6
  # Adds some dsl module helpers
5
7
  class Module
@@ -51,6 +53,7 @@ class Module
51
53
  # on the main object, and the block is then evaluated in the context
52
54
  # of the new object.
53
55
  #
56
+ # rubocop:disable Metrics/MethodLength
54
57
  def dsl_content_object(*symbols)
55
58
  symbols.each do |symbol|
56
59
  plural = CfnDsl::Plurals.pluralize(symbol) # @@plurals[symbol] || "#{symbol}s"
@@ -65,12 +68,22 @@ class Module
65
68
  hash = {}
66
69
  instance_variable_set(pluralvar, hash)
67
70
  end
68
- hash[name] ||= definition_class.new(*values)
69
- hash[name].instance_eval(&block) if block
70
- return hash[name]
71
+ instance = hash[name]
72
+
73
+ if !instance
74
+ instance = definition_class.new(*values)
75
+ hash[name] = instance
76
+ elsif !instance.is_a?(definition_class)
77
+ raise ArgumentError, "#{method}(#{name}) already exists and is not a #{definition_class}"
78
+ elsif !values.empty?
79
+ raise ArgumentError, "wrong number of arguments (given #{values.size + 1}, expected 1) as #{method}(#{name}) already exists"
80
+ end
81
+ instance.instance_eval(&block) if block
82
+ return instance
71
83
  end
72
84
  end
73
85
  end
74
86
  end
87
+ # rubocop:enable Metrics/MethodLength
75
88
  end
76
89
  end
data/lib/cfndsl/names.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Method name helper
2
4
  module CfnDsl
3
5
  module_function
@@ -1,17 +1,31 @@
1
- require 'cfndsl/jsonable'
2
- require 'cfndsl/names'
3
- require 'cfndsl/aws/types'
4
- require 'cfndsl/os/types'
5
- require 'cfndsl/globals'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'globals'
4
+ require_relative 'module'
5
+ require_relative 'jsonable'
6
+ require_relative 'names'
7
+ require_relative 'plurals'
8
+ require_relative 'ref_check'
9
+ require_relative 'properties'
10
+ require_relative 'update_policy'
11
+ require_relative 'creation_policy'
12
+ require_relative 'conditions'
13
+ require_relative 'mappings'
14
+ require_relative 'resources'
15
+ require_relative 'rules'
16
+ require_relative 'parameters'
17
+ require_relative 'outputs'
18
+
19
+ require 'tsort'
6
20
 
7
21
  module CfnDsl
8
22
  # Handles the overall template object
9
23
  # rubocop:disable Metrics/ClassLength
10
24
  class OrchestrationTemplate < JSONable
11
25
  dsl_attr_setter :AWSTemplateFormatVersion, :Description, :Metadata, :Transform
12
- dsl_content_object :Condition, :Parameter, :Output, :Resource, :Mapping
26
+ dsl_content_object :Condition, :Parameter, :Output, :Resource, :Mapping, :Rule
13
27
 
14
- GlobalRefs = {
28
+ GLOBAL_REFS = {
15
29
  'AWS::NotificationARNs' => 1,
16
30
  'AWS::Region' => 1,
17
31
  'AWS::StackId' => 1,
@@ -29,9 +43,10 @@ module CfnDsl
29
43
  parts = resource.split('::')
30
44
  until parts.empty?
31
45
  break if CfnDsl.reserved_items.include? parts.first
46
+
32
47
  abreve_name = parts.join('_')
33
48
  if accessors.key? abreve_name
34
- accessors.delete abreve_name # Delete potentially ambiguous names
49
+ accessors[abreve_name] = :duplicate # Delete potentially ambiguous names
35
50
  else
36
51
  accessors[abreve_name] = type_module.const_get resource_name
37
52
  types_mapping[abreve_name] = resource
@@ -39,17 +54,25 @@ module CfnDsl
39
54
  parts.shift
40
55
  end
41
56
  end
42
- accessors.each_pair { |acc, res| create_resource_accessor(acc, res, types_mapping[acc]) }
57
+ accessors.each_pair { |acc, res| create_resource_accessor(acc, res, types_mapping[acc]) unless res == :duplicate }
43
58
  end
44
59
 
45
60
  def create_resource_def(name, info)
46
- resource = Class.new ResourceDefinition
61
+ resource = Class.new ResourceDefinition do
62
+ # do not allow Type to be respecified
63
+ def Type(type = nil)
64
+ return @Type unless type
65
+ raise CfnDsl::Error, "Cannot override previously defined Type #{@Type} with #{type}" unless type == @Type
66
+
67
+ super
68
+ end
69
+ end
47
70
  resource_name = name.gsub(/::/, '_')
48
71
  type_module.const_set(resource_name, resource)
49
72
  info['Properties'].each_pair do |pname, ptype|
50
73
  if ptype.is_a? Array
51
74
  pclass = type_module.const_get ptype.first
52
- create_array_property_def(resource, pname, pclass)
75
+ create_array_property_def(resource, pname, pclass, info)
53
76
  else
54
77
  pclass = type_module.const_get ptype
55
78
  create_property_def(resource, pname, pclass)
@@ -58,9 +81,66 @@ module CfnDsl
58
81
  resource_name
59
82
  end
60
83
 
61
- def create_property_def(resource, pname, pclass)
84
+ # rubocop:disable Metrics/PerceivedComplexity
85
+ def create_array_property_def(resource, pname, pclass, info)
86
+ singular_name = CfnDsl::Plurals.singularize pname
87
+ plural_name = singular_name == pname ? CfnDsl::Plurals.pluralize(pname) : pname
88
+
89
+ if singular_name == plural_name
90
+ # Generate the extended list concat method
91
+ plural_name = nil
92
+ elsif pname == plural_name && info['Properties'].include?(singular_name)
93
+ # The singlular name is a different property, do not redefine it here but rather use the extended form
94
+ # with the plural name. This allows construction of deep types, but no mechanism to overwrite a previous value
95
+ # (eg CodePipeline::Pipeline ArtifactStores vs ArtifactStore)
96
+ # Note is is also possible (but unlikely) for the spec to change in a way that triggers this condition where it did not
97
+ # before which will result in breaking behaviour for existing apps.
98
+ singular_name = plural_name
99
+ plural_name = nil
100
+ elsif pname == singular_name && info['Properties'].include?(plural_name)
101
+ # The plural name is a different property, do not redefine it here
102
+ # Note it is unlikely that a singular form is going to be a List property if the plural form also exists.
103
+ plural_name = singular_name
104
+ end
105
+
106
+ # Plural form just a normal property definition expecting an Array type
107
+ create_property_def(resource, pname, Array, plural_name) if plural_name
108
+
109
+ # Singular form understands concatenation and Fn::If property
110
+ create_singular_property_def(resource, pname, pclass, singular_name) if singular_name
111
+ end
112
+ # rubocop:enable Metrics/PerceivedComplexity
113
+
114
+ def create_resource_accessor(accessor, resource, type)
115
+ class_eval do
116
+ CfnDsl.method_names(accessor) do |method|
117
+ define_method(method) do |name, *values, &block|
118
+ name = name.to_s
119
+ @Resources ||= {}
120
+ instance = @Resources[name]
121
+ if !instance
122
+ instance = resource.new(*values)
123
+ # Previously the type was set after the block was evaled
124
+ # But now trying to reset Type on a specific subtype will raise exception
125
+ instance.instance_variable_set('@Type', type)
126
+ @Resources[name] = instance
127
+ elsif type != (other_type = instance.instance_variable_get('@Type'))
128
+ raise ArgumentError, "Resource #{name}<#{other_type}> exists, and is not a <#{type}>"
129
+ elsif !values.empty?
130
+ raise ArgumentError, "wrong number of arguments (given #{values.size + 1}, expected 1) as Resource #{name} already exists"
131
+ end
132
+ @Resources[name].instance_eval(&block) if block
133
+ instance
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ private
140
+
141
+ def create_property_def(resource, pname, pclass, method_name = pname)
62
142
  resource.class_eval do
63
- CfnDsl.method_names(pname) do |method|
143
+ CfnDsl.method_names(method_name) do |method|
64
144
  define_method(method) do |*values, &block|
65
145
  values.push pclass.new if values.empty?
66
146
  @Properties ||= {}
@@ -72,99 +152,139 @@ module CfnDsl
72
152
  end
73
153
  end
74
154
 
75
- def create_array_property_def(resource, pname, pclass)
76
- create_property_def(resource, pname, Array)
77
-
78
- sname = CfnDsl::Plurals.singularize pname
79
-
80
- return if sname == pname
155
+ def create_singular_property_def(resource, pname, pclass, singular_name)
81
156
  resource.class_eval do
82
- CfnDsl.method_names(sname) do |method|
83
- define_method(method) do |value = nil, &block|
157
+ CfnDsl.method_names(singular_name) do |method|
158
+ define_method(method) do |value = nil, fn_if: nil, **hash_value, &block|
159
+ value = hash_value unless value || hash_value.empty?
84
160
  @Properties ||= {}
85
161
  @Properties[pname] ||= PropertyDefinition.new([])
86
- value = pclass.new unless value
87
- @Properties[pname].value.push value
88
- value.instance_eval(&block) if block
162
+ if value.is_a?(Array)
163
+ @Properties[pname].value.concat(value)
164
+ else
165
+ value ||= pclass.new
166
+ @Properties[pname].value.push fn_if ? FnIf(fn_if, value, Ref('AWS::NoValue')) : value
167
+ value.instance_eval(&block) if block
168
+ end
89
169
  value
90
170
  end
91
171
  end
92
172
  end
93
173
  end
174
+ end
94
175
 
95
- def create_resource_accessor(accessor, resource, type)
96
- class_eval do
97
- CfnDsl.method_names(accessor) do |method|
98
- define_method(method) do |name, *values, &block|
99
- name = name.to_s
100
- @Resources ||= {}
101
- @Resources[name] ||= resource.new(*values)
102
- @Resources[name].instance_eval(&block) if block
103
- @Resources[name].instance_variable_set('@Type', type)
104
- @Resources[name]
105
- end
106
- end
107
- end
176
+ def initialize(description = nil, &block)
177
+ @AWSTemplateFormatVersion = '2010-09-09'
178
+ @Description = description if description
179
+ declare(&block) if block_given?
180
+ end
181
+
182
+ alias _Condition Condition
183
+
184
+ # Condition has two usages at this level
185
+ # @overload Condition(name,expression)
186
+ # @overload Condition(name) - referencing a condition in a condition expression
187
+ def Condition(name, expression = nil)
188
+ if expression
189
+ _Condition(name, expression)
190
+ else
191
+ { Condition: ConditionDefinition.new(name) }
108
192
  end
109
193
  end
110
194
 
111
- def initialize
112
- @AWSTemplateFormatVersion = '2010-09-09'
195
+ def check_refs
196
+ invalids = check_condition_refs + check_resource_refs + check_output_refs + check_rule_refs
197
+ invalids unless invalids.empty?
113
198
  end
114
199
 
115
- # rubocop:disable Metrics/PerceivedComplexity
116
- def valid_ref?(ref, origin = nil)
200
+ def valid_ref?(ref, ref_containers = [GLOBAL_REFS, @Resources, @Parameters])
117
201
  ref = ref.to_s
118
- origin = origin.to_s if origin
202
+ ref_containers.any? { |c| c && c.key?(ref) }
203
+ end
119
204
 
120
- return true if GlobalRefs.key?(ref)
205
+ def check_condition_refs
206
+ invalids = []
121
207
 
122
- return true if @Parameters && @Parameters.key?(ref)
208
+ # Conditions can refer to other conditions in Fn::And, Fn::Or and Fn::Not
209
+ invalids.concat(_check_refs(:Condition, :condition_refs, [@Conditions]))
123
210
 
124
- if @Resources.key?(ref)
125
- return !origin || !@_resource_refs || !@_resource_refs[ref] || !@_resource_refs[ref].key?(origin)
126
- end
211
+ # They can also Ref Globals and Parameters (but not Resources))
212
+ invalids.concat(_check_refs(:Condition, :all_refs, [GLOBAL_REFS, @Parameters]))
213
+ end
214
+
215
+ def check_resource_refs
216
+ invalids = []
217
+ invalids.concat(_check_refs(:Resource, :all_refs, [@Resources, GLOBAL_REFS, @Parameters]))
127
218
 
128
- false
219
+ # DependsOn and conditions in Fn::If expressions
220
+ invalids.concat(_check_refs(:Resource, :condition_refs, [@Conditions]))
129
221
  end
130
- # rubocop:enable Metrics/PerceivedComplexity
131
222
 
132
- def check_refs
133
- invalids = check_resource_refs + check_output_refs
134
- invalids unless invalids.empty?
223
+ def check_output_refs
224
+ invalids = []
225
+ invalids.concat(_check_refs(:Output, :all_refs, [@Resources, GLOBAL_REFS, @Parameters]))
226
+ invalids.concat(_check_refs(:Output, :condition_refs, [@Conditions]))
135
227
  end
136
228
 
137
- def check_resource_refs
229
+ def check_rule_refs
138
230
  invalids = []
139
- @_resource_refs = {}
140
- if @Resources
141
- @Resources.keys.each do |resource|
142
- @_resource_refs[resource.to_s] = @Resources[resource].build_references({})
143
- end
144
- @_resource_refs.keys.each do |origin|
145
- @_resource_refs[origin].keys.each do |ref|
146
- invalids.push "Invalid Reference: Resource #{origin} refers to #{ref}" unless valid_ref?(ref, origin)
147
- end
148
- end
149
- end
231
+ invalids.concat(_check_refs(:Rule, :all_refs, [@Resources, GLOBAL_REFS, @Parameters]))
232
+ invalids.concat(_check_refs(:Rule, :condition_refs, [@Conditions]))
150
233
  invalids
151
234
  end
152
235
 
153
- def check_output_refs
236
+ # For testing for cycles
237
+ class RefHash < Hash
238
+ include TSort
239
+
240
+ alias tsort_each_node each_key
241
+ def tsort_each_child(node, &block)
242
+ fetch(node, []).each(&block)
243
+ end
244
+ end
245
+
246
+ # rubocop:disable Metrics/PerceivedComplexity
247
+ def _check_refs(container_name, method, source_containers)
248
+ container = instance_variable_get("@#{container_name}s")
249
+ return [] unless container
250
+
154
251
  invalids = []
155
- output_refs = {}
156
- if @Outputs
157
- @Outputs.keys.each do |resource|
158
- output_refs[resource.to_s] = @Outputs[resource].build_references({})
252
+ referred_by = RefHash.new { |h, k| h[k] = [] }
253
+ self_check = source_containers.first.eql?(container)
254
+
255
+ container.each_pair do |name, entry|
256
+ name = name.to_s
257
+ begin
258
+ refs = entry.build_references([], self_check && name, method)
259
+ refs.each { |r| referred_by[r.to_s] << name }
260
+ rescue RefCheck::SelfReference, RefCheck::NullReference => e
261
+ # Topological sort will not detect self or null references
262
+ invalids.push("#{container_name} #{e.message}")
159
263
  end
160
- output_refs.keys.each do |origin|
161
- output_refs[origin].keys.each do |ref|
162
- invalids.push "Invalid Reference: Output #{origin} refers to #{ref}" unless valid_ref?(ref)
163
- end
264
+ end
265
+
266
+ referred_by.each_pair do |ref, names|
267
+ unless valid_ref?(ref, source_containers)
268
+ invalids.push "Invalid Reference: #{container_name}s #{names} refer to unknown #{method == :condition_refs ? 'Condition' : 'Reference'} #{ref}"
164
269
  end
165
270
  end
271
+
272
+ begin
273
+ referred_by.tsort if self_check && invalids.empty? # Check for cycles
274
+ rescue TSort::Cyclic => e
275
+ invalids.push "Cyclic references found in #{container_name}s #{referred_by} - #{e.message}"
276
+ end
277
+
166
278
  invalids
167
279
  end
280
+ # rubocop:enable Metrics/PerceivedComplexity
281
+
282
+ def validate
283
+ errors = check_refs || []
284
+ raise CfnDsl::Error, "#{errors.size} errors in template\n#{errors.join("\n")}" unless errors.empty?
285
+
286
+ self
287
+ end
168
288
  end
169
289
  # rubocop:enable Metrics/ClassLength
170
290
  end
@@ -1,4 +1,6 @@
1
- require 'cfndsl/jsonable'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'jsonable'
2
4
 
3
5
  module CfnDsl
4
6
  # Handles Output objects
@@ -12,5 +14,9 @@ module CfnDsl
12
14
  def initialize(value = nil)
13
15
  @Value = value if value
14
16
  end
17
+
18
+ def condition_refs
19
+ [@Condition].flatten.compact.map(&:to_s)
20
+ end
15
21
  end
16
22
  end
@@ -1,4 +1,6 @@
1
- require 'cfndsl/jsonable'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'jsonable'
2
4
 
3
5
  module CfnDsl
4
6
  # Handles input parameter objects
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CfnDsl
2
4
  # Plural names for lists of content objects
3
5
  module Plurals
@@ -5,15 +7,15 @@ module CfnDsl
5
7
 
6
8
  @plurals = {
7
9
  'AssumeRolePolicyDocument' => 'AssumeRolePolicyDocument',
8
- 'CreationPolicy' => 'CreationPolicy',
9
- 'DBSecurityGroupIngress' => 'DBSecurityGroupIngress',
10
- 'Metadata' => 'Metadata',
11
- 'Policy' => 'Policies',
12
- 'PolicyDocument' => 'PolicyDocument',
13
- 'Property' => 'Properties',
14
- 'SecurityGroupEgress' => 'SecurityGroupEgress',
15
- 'SecurityGroupIngress' => 'SecurityGroupIngress',
16
- 'UpdatePolicy' => 'UpdatePolicy'
10
+ 'CreationPolicy' => 'CreationPolicy',
11
+ 'DBSecurityGroupIngress' => 'DBSecurityGroupIngress',
12
+ 'Metadata' => 'Metadata',
13
+ 'Policy' => 'Policies',
14
+ 'PolicyDocument' => 'PolicyDocument',
15
+ 'Property' => 'Properties',
16
+ 'SecurityGroupEgress' => 'SecurityGroupEgress',
17
+ 'SecurityGroupIngress' => 'SecurityGroupIngress',
18
+ 'UpdatePolicy' => 'UpdatePolicy'
17
19
  }
18
20
  @singles = @plurals.invert
19
21
 
@@ -22,7 +24,18 @@ module CfnDsl
22
24
  end
23
25
 
24
26
  def singularize(name)
25
- @singles.fetch(name.to_s) { |key| key[0..-2] }
27
+ @singles.fetch(name.to_s) do |key|
28
+ case key
29
+ when /List$/
30
+ key[0..-5]
31
+ when /ies$/
32
+ key[0..-4] + 'y'
33
+ when /s$/
34
+ key[0..-2]
35
+ else
36
+ key
37
+ end
38
+ end
26
39
  end
27
40
  end
28
41
  end