cfndsl 0.10.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MTg3ZjhiNWQyYWZjY2Q5NmJiMWYzMGY4NjAzMmYwMTY1YTBjYjdkNg==
4
+ N2U4ZWRiYzQ2MTQ2OTAzMGFlNzM0YmY3N2Q2YWVmNDEzNDk3M2JiOQ==
5
5
  data.tar.gz: !binary |-
6
- ZGI5NmUyMDczZmI3ZTE3NTI0ZjBiZTgxYTEzNmVjNWZlZDE2ZTdmYQ==
6
+ MzczODNhYWYzOWZkNjMyZTYyZDQyNTExMmE4OGY3NDk3YTk1ZDVhOA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NmQ2NDBmNTRjYjU2NWJjNzhmMjQwM2QxN2ZhMjBlMDZhNTk1YTllMDFiN2E0
10
- MDRmOTIzMzcyZWQxMTlhOWUwZTYyMDU3YWY2MzQ0MzY5ODBkZTg1YTI2NGMx
11
- ODQ1ZjBiYWMxMzRiOWVmMzAyZjFkZGI5OTFjODhhZGU4NGFmMWE=
9
+ ODc4ODAyNjQ2NDYzMTBjMWIzYjk5NTMzYWJjMWRlZTI2MjcxN2UzMzNmZTVh
10
+ NmE5ZjY3MzI2NmUxMGZhZjE0ZTZhOTM4NDZjZmM1NGI5ZTFiMjdkY2E4MWVm
11
+ ZDU0MWE3MzhkZjU2ZWZjMTA2MWM1ZTM4NjM4NGIxN2EwMTNkNmU=
12
12
  data.tar.gz: !binary |-
13
- ZDBjZTVhYjdjNDFmNzAwYzcxMDdjOWE2M2JhYTliOWU1MWU5OWRmNTkzMTBm
14
- NjA0MzM5ZjE2YjMxNTI4MWU0NmJmMDUyZTliM2E3ZjAyYWQ5MDNmMmI0NzE0
15
- NjZjZjcxMWVhZWQyNjUyNmNlMjIxZmQwMzJiZTNmODA2MDUyYTg=
13
+ MDI2OThlM2M4NDQ3Njg4NmQ0ZjY2ODZlYjgxMTUzYzA3MGNmZjgzNWE4YjNk
14
+ MjczOTQwOGI4MjBjYTUyN2IyYTIyNDhlZGE0NTVmNDViNjRmZDA1NjhhY2Ix
15
+ ZTUyMzM5MjdmYzg1NWRlOTZjNTdkMmVjNWYyMTBhODk1MWM4MjE=
data/README.md CHANGED
@@ -85,6 +85,333 @@ chris@raspberrypi:~/git/cfndsl$ cfndsl test.rb | json_pp
85
85
  *Aside: that is correct - a significant amount of the development for
86
86
  this gem was done on a [Raspberry Pi](http://www.raspberrypi.org).*
87
87
 
88
+ ## Syntax
89
+
90
+ `cfndsl` comes with a number of helper methods defined on _each_ resource and/or the stack as a whole.
91
+
92
+ ### Template Metadata
93
+
94
+ Metadata is a special template section described [here](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/metadata-section-structure.html). The argument supplied must be JSON-able. Some CloudFormation features reference special keys if included in the `Metadata`, check the AWS documentation for specifics.
95
+
96
+ ```ruby
97
+ CloudFormation do
98
+ Metadata(foo: 'bar')
99
+
100
+ EC2_Instance(:myInstance) do
101
+ ImageId 'ami-12345678'
102
+ Type 't1.micro'
103
+ end
104
+ end
105
+ ```
106
+
107
+ ### Template Parameters
108
+
109
+ At a bare minumum, parameters need a name, and default to having Type `String`. Specify the parameter in the singular, not plural:
110
+
111
+ ```ruby
112
+ CloudFormation do
113
+ Parameter 'foo'
114
+ end
115
+ ```
116
+
117
+ ```json
118
+ {
119
+ "AWSTemplateFormatVersion": "2010-09-09",
120
+ "Parameters": {
121
+ "foo": {
122
+ "Type": "String"
123
+ }
124
+ }
125
+ }
126
+ ```
127
+
128
+ However, they can accept all of the following additional keys per the [documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html):
129
+
130
+ ```ruby
131
+ Parameter('foo') do
132
+ Description 'This is a sample parameter definition'
133
+ Type 'String'
134
+ Default 'foo'
135
+ NoEcho true
136
+ AllowedValues %w(foo bar)
137
+ AllowedPattern '/pattern/'
138
+ MaxLength 5
139
+ MinLength 3
140
+ MaxValue 10
141
+ MinValue 2
142
+ ConstraintDescription 'The error message printed when a parameter outside the constraints is given'
143
+ end
144
+ ```
145
+
146
+ Parameters can be referenced later in your template:
147
+
148
+ ```ruby
149
+ EC2_Instance(:myInstance) do
150
+ InstanceType 'm3.xlarge'
151
+ UserData Ref('foo')
152
+ end
153
+ ```
154
+
155
+ ### Template Mappings
156
+
157
+ [Mappings](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/mappings-section-structure.html) are a hash-based lookup for your template. They can be specified in the singular or plural.
158
+
159
+ ```ruby
160
+ CloudFormation do
161
+ Mapping('foo', letters: { a: 'a', b: 'b' }, numbers: { 1: 1, 2: 2 })
162
+ end
163
+ ```
164
+
165
+ ```json
166
+ {
167
+ "AWSTemplateFormatVersion": "2010-09-09",
168
+ "Mappings": {
169
+ "foo": {
170
+ "letters": {
171
+ "a": "a",
172
+ "b": "b"
173
+ },
174
+ "numbers": {
175
+ "one": 1,
176
+ "two": 2
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ ```
183
+
184
+ You can then reference them later in your template using the `FnFindInMap` method:
185
+
186
+ ```ruby
187
+ EC2_Instance(:myInstance) do
188
+ InstanceType 'm3.xlarge'
189
+ UserData FnFindInMap('foo', :numbers, :one)
190
+ end
191
+ ```
192
+
193
+ ### Template Outputs
194
+
195
+ Outputs are declared one at a time and must be given a name and a value at a minimum, description is optional. Values are most typically obtained from other resources using `Ref` or `FnGetAtt`:
196
+
197
+ ```ruby
198
+ CloudFormation do
199
+ EC2_Instance(:myInstance) do
200
+ ImageId 'ami-12345678'
201
+ Type 't1.micro'
202
+ end
203
+
204
+ Output(:myInstanceId) do
205
+ Description 'My instance Id'
206
+ Value Ref(:myInstance)
207
+ end
208
+ end
209
+ ```
210
+
211
+ ```json
212
+ {
213
+ "AWSTemplateFormatVersion": "2010-09-09",
214
+ "Resources": {
215
+ "myInstance": {
216
+ "Properties": {
217
+ "ImageId": "ami-12345678"
218
+ },
219
+ "Type": "AWS::EC2::Instance"
220
+ }
221
+ },
222
+ "Outputs": {
223
+ "myInstanceId": {
224
+ "Description": "My instance Id",
225
+ "Value": {
226
+ "Ref": "myInstance"
227
+ }
228
+ }
229
+ }
230
+ }
231
+ ```
232
+
233
+ ### Template Conditions
234
+
235
+ Conditions must be created with statements in three [sections](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html): a variable entry as a `Parameter`, a template-level `Condition` that holds the logic based upon the value of that `Parameter`, and a resource-level `Condition` that references the template-level one by logical id.
236
+
237
+ ```ruby
238
+ CloudFormation do
239
+ Parameter(:environment) do
240
+ Default 'development'
241
+ AllowedValues %w(production development)
242
+ end
243
+
244
+ Condition(:createResource, FnEquals(Ref(:environment), 'production'))
245
+
246
+ EC2_Instance(:myInstance) do
247
+ Condition :createResource
248
+ ImageId 'ami-12345678'
249
+ Type 't1.micro'
250
+ end
251
+ end
252
+ ```
253
+
254
+ ```json
255
+ {
256
+ "AWSTemplateFormatVersion": "2010-09-09",
257
+ "Parameters": {
258
+ "environment": {
259
+ "Type": "String",
260
+ "Default": "development",
261
+ "AllowedValues": [
262
+ "production",
263
+ "development"
264
+ ]
265
+ }
266
+ },
267
+ "Conditions": {
268
+ "createResource": {
269
+ "Fn::Equals": [
270
+ {
271
+ "Ref": "environment"
272
+ },
273
+ "production"
274
+ ]
275
+ }
276
+ },
277
+ "Resources": {
278
+ "myInstance": {
279
+ "Condition": "createResource",
280
+ "Properties": {
281
+ "ImageId": "ami-12345678"
282
+ },
283
+ "Type": "AWS::EC2::Instance"
284
+ }
285
+ }
286
+ }
287
+ ```
288
+
289
+ ### Template Resources
290
+
291
+ Cfndsl creates accessor methods for all of the resources listed [here](https://github.com/stevenjack/cfndsl/blob/master/lib/cfndsl/aws/types.yaml) and [here](https://github.com/stevenjack/cfndsl/blob/master/lib/cfndsl/os/types.yaml). If a resource is missing, or if you prefer to explicitly enter a resource in a template, you can do so. Keep in mind that since you are using the generic `Resource` class, you will also need to explicitly set the `Type` and that you no longer have access to the helper methods defined on that particular class, so you will have to use the `Property` method to set them.
292
+
293
+ ```ruby
294
+ CloudFormation do
295
+ Resource(:myInstance) do
296
+ Type 'AWS::EC2::Instance'
297
+ Property('ImageId', 'ami-12345678')
298
+ Property('Type', 't1.micro')
299
+ end
300
+
301
+ # Will generate the same json as this
302
+ #
303
+ # EC2_Instance(:myInstance) do
304
+ # ImageId 'ami-12345678'
305
+ # Type 't1.micro'
306
+ # end
307
+ end
308
+ ```
309
+
310
+ ```json
311
+ {
312
+ "AWSTemplateFormatVersion": "2010-09-09",
313
+ "Resources": {
314
+ "myInstance": {
315
+ "Type": "AWS::ApiGateway::Resource",
316
+ "Properties": {
317
+ "ImageId": "ami-12345678",
318
+ "Type": "t1.micro"
319
+ }
320
+ }
321
+ }
322
+ }
323
+ ```
324
+
325
+ ### Resource Types
326
+
327
+ When using the generic `Resource` method, rather than the dsl methods, specify the type of resource using `Type` amd the properties using `Property`. See [Template Resources](#template-resources) for an example.
328
+
329
+ ### Resource Conditions
330
+
331
+ Resource conditions are specified singularly, referencing a template-level condition by logical id. See [Template Conditions](#template-conditions) for an example.
332
+
333
+ ### Resource DependsOn
334
+
335
+ Resources can depend upon other resources explicitly using `DependsOn`. It accepts one or more logical ids.
336
+
337
+ ```ruby
338
+ CloudFormation do
339
+ EC2_Instance(:database) do
340
+ ImageId 'ami-12345678'
341
+ Type 't1.micro'
342
+ end
343
+
344
+ EC2_Instance(:webserver) do
345
+ DependsOn :database
346
+ ImageId 'ami-12345678'
347
+ Type 't1.micro'
348
+ end
349
+ end
350
+ ```
351
+
352
+ ```json
353
+ {
354
+ "AWSTemplateFormatVersion": "2010-09-09",
355
+ "Resources": {
356
+ "database": {
357
+ "Properties": {
358
+ "ImageId": "ami-12345678"
359
+ },
360
+ "Type": "AWS::EC2::Instance"
361
+ },
362
+ "webserver": {
363
+ "Properties": {
364
+ "ImageId": "ami-12345678"
365
+ },
366
+ "Type": "AWS::EC2::Instance",
367
+ "DependsOn": "database"
368
+ }
369
+ }
370
+ }
371
+ ```
372
+
373
+ ### Resource DeletionPolicy
374
+
375
+ Resources can have [deletion policies](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html) associated with them. Specify them one per resource as an attribute:
376
+
377
+ ```ruby
378
+ CloudFormation do
379
+ EC2_Instance(:myInstance) do
380
+ DeletionPolicy 'Retain'
381
+ ImageId 'ami-12345678'
382
+ Type 't1.micro'
383
+ end
384
+ end
385
+ ```
386
+
387
+ ### Resource Metadata
388
+
389
+ You can attach arbitrary [metadata](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-metadata.html) as an attribute. Arguments provided must be able to be JSON-ified:
390
+
391
+ ```ruby
392
+ CloudFormation do
393
+ EC2_Instance(:myInstance) do
394
+ Metadata(foo: 'bar')
395
+ ImageId 'ami-12345678'
396
+ Type 't1.micro'
397
+ end
398
+ end
399
+ ```
400
+
401
+ ### Resource CreationPolicy/UpdatePolicy
402
+
403
+ These attributes are only usable on particular [resources](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-creationpolicy.html). The name of the attribute is not arbitrary, it must match the policy name you are trying to attach. Different policies have different parameters.
404
+
405
+ ```ruby
406
+ CloudFormation do
407
+ EC2_Instance(:myInstance) do
408
+ ImageId 'ami-12345678'
409
+ Type 't1.micro'
410
+ CreationPolicy(:ResourceSignal, { Count: 1, Timeout: 'PT1M' })
411
+ end
412
+ end
413
+ ```
414
+
88
415
  ## Samples
89
416
 
90
417
  There is a more detailed example in the samples directory. The file
data/lib/cfndsl.rb CHANGED
@@ -11,7 +11,6 @@ require 'cfndsl/creation_policy'
11
11
  require 'cfndsl/conditions'
12
12
  require 'cfndsl/mappings'
13
13
  require 'cfndsl/resources'
14
- require 'cfndsl/metadata'
15
14
  require 'cfndsl/parameters'
16
15
  require 'cfndsl/outputs'
17
16
  require 'cfndsl/aws/cloud_formation_template'
@@ -7,13 +7,9 @@ module CfnDsl
7
7
  # Handles the overall template object
8
8
  # rubocop:disable Metrics/ClassLength
9
9
  class OrchestrationTemplate < JSONable
10
- dsl_attr_setter :AWSTemplateFormatVersion, :Description
10
+ dsl_attr_setter :AWSTemplateFormatVersion, :Description, :Metadata
11
11
  dsl_content_object :Condition, :Parameter, :Output, :Resource, :Mapping
12
12
 
13
- def initialize
14
- @AWSTemplateFormatVersion = '2010-09-09'
15
- end
16
-
17
13
  GlobalRefs = {
18
14
  'AWS::NotificationARNs' => 1,
19
15
  'AWS::Region' => 1,
@@ -23,6 +19,99 @@ module CfnDsl
23
19
  'AWS::NoValue' => 1
24
20
  }.freeze
25
21
 
22
+ class << self
23
+ def create_types
24
+ accessors = {}
25
+ types_mapping = {}
26
+ template_types['Resources'].each_pair do |resource, info|
27
+ resource_name = create_resource_def(resource, info)
28
+ parts = resource.split('::')
29
+ until parts.empty?
30
+ break if parts.first == 'Resource' # Don't allow us to define Resource as different method
31
+ abreve_name = parts.join('_')
32
+ if accessors.key? abreve_name
33
+ accessors.delete abreve_name # Delete potentially ambiguous names
34
+ else
35
+ accessors[abreve_name] = type_module.const_get resource_name
36
+ types_mapping[abreve_name] = resource
37
+ end
38
+ parts.shift
39
+ end
40
+ end
41
+ accessors.each_pair { |acc, res| create_resource_accessor(acc, res, types_mapping[acc]) }
42
+ end
43
+
44
+ def create_resource_def(name, info)
45
+ resource = Class.new ResourceDefinition
46
+ resource_name = name.gsub(/::/, '_')
47
+ type_module.const_set(resource_name, resource)
48
+ info['Properties'].each_pair do |pname, ptype|
49
+ if ptype.is_a? Array
50
+ pclass = type_module.const_get ptype.first
51
+ create_array_property_def(resource, pname, pclass)
52
+ else
53
+ pclass = type_module.const_get ptype
54
+ create_property_def(resource, pname, pclass)
55
+ end
56
+ end
57
+ resource_name
58
+ end
59
+
60
+ def create_property_def(resource, pname, pclass)
61
+ resource.class_eval do
62
+ CfnDsl.method_names(pname) do |method|
63
+ define_method(method) do |*values, &block|
64
+ values.push pclass.new if values.empty?
65
+ @Properties ||= {}
66
+ @Properties[pname] = PropertyDefinition.new(*values)
67
+ @Properties[pname].value.instance_eval(&block) if block
68
+ @Properties[pname].value
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ def create_array_property_def(resource, pname, pclass)
75
+ create_property_def(resource, pname, Array)
76
+
77
+ sname = CfnDsl::Plurals.singularize pname
78
+
79
+ unless sname == pname
80
+ resource.class_eval do
81
+ CfnDsl.method_names(sname) do |method|
82
+ define_method(method) do |value = nil, &block|
83
+ @Properties ||= {}
84
+ @Properties[pname] ||= PropertyDefinition.new([])
85
+ value = pclass.new unless value
86
+ @Properties[pname].value.push value
87
+ value.instance_eval(&block) if block
88
+ value
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+
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
108
+ end
109
+ end
110
+
111
+ def initialize
112
+ @AWSTemplateFormatVersion = '2010-09-09'
113
+ end
114
+
26
115
  # rubocop:disable Metrics/PerceivedComplexity
27
116
  def valid_ref?(ref, origin = nil)
28
117
  ref = ref.to_s
@@ -42,13 +131,11 @@ module CfnDsl
42
131
 
43
132
  def check_refs
44
133
  invalids = check_resource_refs + check_output_refs
45
-
46
- invalids.empty? ? nil : invalids
134
+ invalids unless invalids.empty?
47
135
  end
48
136
 
49
137
  def check_resource_refs
50
138
  invalids = []
51
-
52
139
  @_resource_refs = {}
53
140
  if @Resources
54
141
  @Resources.keys.each do |resource|
@@ -60,13 +147,11 @@ module CfnDsl
60
147
  end
61
148
  end
62
149
  end
63
-
64
150
  invalids
65
151
  end
66
152
 
67
153
  def check_output_refs
68
154
  invalids = []
69
-
70
155
  output_refs = {}
71
156
  if @Outputs
72
157
  @Outputs.keys.each do |resource|
@@ -78,103 +163,8 @@ module CfnDsl
78
163
  end
79
164
  end
80
165
  end
81
-
82
166
  invalids
83
167
  end
84
-
85
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
86
- def self.create_types
87
- names = {}
88
- nametypes = {}
89
- template_types['Resources'].each_pair do |name, type|
90
- # Subclass ResourceDefintion and generate property methods
91
- klass = Class.new(CfnDsl::ResourceDefinition)
92
- klassname = name.split('::').join('_')
93
- type_module.const_set(klassname, klass)
94
- type['Properties'].each_pair do |pname, ptype|
95
- if ptype.instance_of?(String)
96
- create_klass = type_module.const_get(ptype)
97
-
98
- klass.class_eval do
99
- CfnDsl.method_names(pname) do |method|
100
- define_method(method) do |*values, &block|
101
- values.push create_klass.new if values.empty?
102
-
103
- @Properties ||= {}
104
- @Properties[pname] = CfnDsl::PropertyDefinition.new(*values)
105
- @Properties[pname].value.instance_eval(&block) if block
106
- @Properties[pname].value
107
- end
108
- end
109
- end
110
- else
111
- # Array version
112
- klass.class_eval do
113
- CfnDsl.method_names(pname) do |method|
114
- define_method(method) do |*values, &block|
115
- values.push [] if values.empty?
116
- @Properties ||= {}
117
- @Properties[pname] ||= PropertyDefinition.new(*values)
118
- @Properties[pname].value.instance_eval(&block) if block
119
- @Properties[pname].value
120
- end
121
- end
122
- end
123
-
124
- sing_name = CfnDsl::Plurals.singularize(pname)
125
- create_klass = type_module.const_get(ptype[0])
126
- sing_names = sing_name == pname ? [ptype[0]] : [ptype[0], sing_name]
127
-
128
- klass.class_eval do
129
- sing_names.each do |sname|
130
- CfnDsl.method_names(sname) do |method|
131
- define_method(method) do |value = nil, &block|
132
- @Properties ||= {}
133
- @Properties[pname] ||= PropertyDefinition.new([])
134
- value = create_klass.new unless value
135
- @Properties[pname].value.push value
136
- value.instance_eval(&block) if block
137
- value
138
- end
139
- end
140
- end
141
- end
142
- end
143
- end
144
- parts = name.split('::')
145
- until parts.empty?
146
- break if parts[0] == 'Resource'
147
- abreve_name = parts.join('_')
148
- if names.key?(abreve_name)
149
- # this only happens if there is an ambiguity
150
- names[abreve_name] = nil
151
- else
152
- names[abreve_name] = type_module.const_get(klassname)
153
- nametypes[abreve_name] = name
154
- end
155
- parts.shift
156
- end
157
- end
158
-
159
- # Define property setter methods for each of the unambiguous type names
160
- names.each_pair do |typename, type|
161
- next unless type
162
-
163
- class_eval do
164
- CfnDsl.method_names(typename) do |method|
165
- define_method(method) do |name, *values, &block|
166
- name = name.to_s
167
- @Resources ||= {}
168
- resource = @Resources[name] ||= type.new(*values)
169
- resource.instance_eval(&block) if block
170
- resource.instance_variable_set('@Type', nametypes[typename])
171
- resource
172
- end
173
- end
174
- end
175
- end
176
- end
177
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
178
168
  end
179
169
  # rubocop:enable Metrics/ClassLength
180
170
  end
@@ -1,13 +1,12 @@
1
1
  require 'cfndsl/jsonable'
2
- require 'cfndsl/metadata'
3
2
  require 'cfndsl/properties'
4
3
  require 'cfndsl/update_policy'
5
4
 
6
5
  module CfnDsl
7
6
  # Handles Resource objects
8
7
  class ResourceDefinition < JSONable
9
- dsl_attr_setter :Type, :DependsOn, :DeletionPolicy, :Condition
10
- dsl_content_object :Property, :Metadata, :UpdatePolicy, :CreationPolicy
8
+ dsl_attr_setter :Type, :DependsOn, :DeletionPolicy, :Condition, :Metadata
9
+ dsl_content_object :Property, :UpdatePolicy, :CreationPolicy
11
10
 
12
11
  def addTag(name, value, propagate = nil)
13
12
  add_tag(name, value, propagate)
@@ -1,3 +1,3 @@
1
1
  module CfnDsl
2
- VERSION = '0.10.2'.freeze
2
+ VERSION = '0.11.0'.freeze
3
3
  end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe CfnDsl::CloudFormationTemplate do
4
+ it_behaves_like 'an orchestration template'
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe CfnDsl::HeatTemplate do
4
+ it_behaves_like 'an orchestration template'
5
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Metadata' do
4
+ let(:template) { CfnDsl::OrchestrationTemplate.new }
5
+
6
+ it 'is settable for a template' do
7
+ template.Metadata(foo: 'bar')
8
+ expect(template.to_json).to match(/"Metadata":{"foo":"bar"}/)
9
+ end
10
+
11
+ it 'is settable for a resource' do
12
+ resource = template.Resource(:foo) { Metadata(foo: 'bar') }
13
+ expect(resource.to_json).to match(/"Metadata":{"foo":"bar"}/)
14
+ end
15
+ end
@@ -1,21 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe CfnDsl::ResourceDefinition do
4
- subject { CfnDsl::CloudFormationTemplate.new.EC2_Instance(:single_server) }
5
- context '#all_refs' do
6
- it 'checks that the type is AWS::EC2::Instance' do
7
- expect(subject.instance_variable_get('@Type')).to eq('AWS::EC2::Instance')
8
- end
9
- end
10
- end
11
-
12
3
  describe CfnDsl::ResourceDefinition do
13
4
  subject { CfnDsl::CloudFormationTemplate.new.AutoScalingGroup(:web_servers) }
14
- context '#all_refs' do
15
- it 'checks that the type is AWS::AutoScaling::AutoScalingGroup' do
16
- expect(subject.instance_variable_get('@Type')).to eq('AWS::AutoScaling::AutoScalingGroup')
17
- end
18
- end
5
+
19
6
  context '#addTag' do
20
7
  it 'is a pass-through method to add_tag' do
21
8
  expect(subject).to receive(:add_tag).with('role', 'web-server', true)
data/spec/spec_helper.rb CHANGED
@@ -3,3 +3,5 @@ require 'aruba/rspec'
3
3
 
4
4
  bindir = File.expand_path('../../bin', __FILE__)
5
5
  ENV['PATH'] = [ENV['PATH'], bindir].join(':')
6
+
7
+ Dir[File.expand_path('../support/**/*.rb', __FILE__)].each { |f| require f }
@@ -0,0 +1,97 @@
1
+ shared_examples 'an orchestration template' do
2
+ context '#valid_ref?' do
3
+ it 'returns true if ref is global' do
4
+ expect(subject.valid_ref?('AWS::Region')).to eq(true)
5
+ end
6
+
7
+ it 'returns true if ref is a parameter' do
8
+ subject.Parameter(:foo)
9
+ expect(subject.valid_ref?(:foo)).to eq(true)
10
+ end
11
+ end
12
+
13
+ context '#check_refs' do
14
+ it 'returns an array of invalid refs if present' do
15
+ subject.EC2_Instance(:foo) { UserData Ref(:bar) }
16
+ expect(subject.check_refs).to_not be_empty
17
+ end
18
+
19
+ it 'returns nil if no invalid refs are present' do
20
+ subject.EC2_Instance(:foo) { UserData Ref('AWS::Region') }
21
+ expect(subject.check_refs).to eq(nil)
22
+ end
23
+ end
24
+
25
+ context '#check_resource_refs' do
26
+ it 'returns an array with an error message if invalid refs are present' do
27
+ subject.EC2_Instance(:foo) { UserData Ref(:bar) }
28
+ expect(subject.check_resource_refs).to eq(['Invalid Reference: Resource foo refers to bar'])
29
+ end
30
+
31
+ it 'returns an empty array ' do
32
+ subject.EC2_Instance(:foo) { UserData Ref('AWS::AccountId') }
33
+ expect(subject.check_resource_refs).to eq([])
34
+ end
35
+ end
36
+
37
+ context '#check_output_refs' do
38
+ it 'returns an array with an error message if invalid refs are present' do
39
+ subject.EC2_Instance(:foo)
40
+ subject.Output(:baz) { Value Ref(:bar) }
41
+ expect(subject.check_output_refs).to eq(['Invalid Reference: Output baz refers to bar'])
42
+ end
43
+
44
+ it 'returns an empty array' do
45
+ subject.EC2_Instance(:foo)
46
+ subject.Output(:baz) { Value Ref(:foo) }
47
+ expect(subject.check_output_refs).to eq([])
48
+ end
49
+ end
50
+
51
+ context '.create_types' do
52
+ it 'creates a type class for each entry' do
53
+ expect(described_class.type_module).to be_const_defined('AWS_EC2_Instance')
54
+ expect(described_class.type_module.const_get('AWS_EC2_Instance')).to be < CfnDsl::ResourceDefinition
55
+ end
56
+
57
+ it 'defines case-insensitive properties for each type class' do
58
+ ec2_instance = described_class.type_module.const_get('AWS_EC2_Instance').new
59
+ expect(ec2_instance).to respond_to(:imageId)
60
+ expect(ec2_instance).to respond_to(:ImageId)
61
+ end
62
+
63
+ it 'defines singular and plural methods for array properties' do
64
+ ec2_instance = described_class.type_module.const_get('AWS_EC2_Instance').new
65
+ ec2_instance.SecurityGroup(foo: 'bar')
66
+ singular_value = ec2_instance.instance_variable_get('@Properties')['SecurityGroups'].value
67
+ expect(singular_value).to eq([{ foo: 'bar' }])
68
+ ec2_instance = described_class.type_module.const_get('AWS_EC2_Instance').new
69
+ ec2_instance.SecurityGroups([{ foo: 'bar' }])
70
+ plural_value = ec2_instance.instance_variable_get('@Properties')['SecurityGroups'].value
71
+ expect(plural_value).to eq([{ foo: 'bar' }])
72
+ end
73
+
74
+ it 'defines accessor methods for each of the entries' do
75
+ expect(subject).to respond_to(:AWS_EC2_Instance)
76
+ expect(subject).to respond_to(:EC2_Instance)
77
+ end
78
+
79
+ it 'avoids ambiguous accessor methods' do
80
+ expect(subject).to_not respond_to(:Instance)
81
+ end
82
+
83
+ it 'avoids duplicating singular and plural methods' do
84
+ security_group = described_class.type_module.const_get('AWS_EC2_SecurityGroup').new
85
+ security_group.SecurityGroupIngress([{ foo: 'bar' }])
86
+ plural_value = security_group.instance_variable_get('@Properties')['SecurityGroupIngress'].value
87
+ expect(plural_value).to eq([{ foo: 'bar' }])
88
+ end
89
+
90
+ it 'sets the type of each resource correctly' do
91
+ ec2_instance = subject.EC2_Instance(:foo)
92
+ expect(ec2_instance.instance_variable_get('@Type')).to eq('AWS::EC2::Instance')
93
+ ec2_instance = subject.Resource(:bar) { Type 'AWS::EC2::Instance' }
94
+ expect(ec2_instance.instance_variable_get('@Type')).to eq('AWS::EC2::Instance')
95
+ end
96
+ end
97
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cfndsl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.2
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steven Jack
@@ -55,7 +55,6 @@ files:
55
55
  - lib/cfndsl/json_serialisable_object.rb
56
56
  - lib/cfndsl/jsonable.rb
57
57
  - lib/cfndsl/mappings.rb
58
- - lib/cfndsl/metadata.rb
59
58
  - lib/cfndsl/module.rb
60
59
  - lib/cfndsl/names.rb
61
60
  - lib/cfndsl/orchestration_template.rb
@@ -88,14 +87,18 @@ files:
88
87
  - sample/vpc_with_vpn_example.rb
89
88
  - spec/cfndsl_spec.rb
90
89
  - spec/cli_spec.rb
90
+ - spec/cloud_formation_template_spec.rb
91
91
  - spec/external_parameters_spec.rb
92
92
  - spec/fixtures/heattest.rb
93
93
  - spec/fixtures/test.rb
94
+ - spec/heat_template_spec.rb
94
95
  - spec/jsonable_spec.rb
96
+ - spec/metadata_spec.rb
95
97
  - spec/names_spec.rb
96
98
  - spec/plurals_spec.rb
97
99
  - spec/resources_spec.rb
98
100
  - spec/spec_helper.rb
101
+ - spec/support/shared_examples/orchestration_template.rb
99
102
  homepage: https://github.com/stevenjack/cfndsl
100
103
  licenses:
101
104
  - MIT
@@ -124,11 +127,15 @@ summary: AWS Cloudformation DSL
124
127
  test_files:
125
128
  - spec/cfndsl_spec.rb
126
129
  - spec/cli_spec.rb
130
+ - spec/cloud_formation_template_spec.rb
127
131
  - spec/external_parameters_spec.rb
128
132
  - spec/fixtures/heattest.rb
129
133
  - spec/fixtures/test.rb
134
+ - spec/heat_template_spec.rb
130
135
  - spec/jsonable_spec.rb
136
+ - spec/metadata_spec.rb
131
137
  - spec/names_spec.rb
132
138
  - spec/plurals_spec.rb
133
139
  - spec/resources_spec.rb
134
140
  - spec/spec_helper.rb
141
+ - spec/support/shared_examples/orchestration_template.rb
@@ -1,14 +0,0 @@
1
- require 'cfndsl/jsonable'
2
-
3
- module CfnDsl
4
- # Handles Metadata objects
5
- class MetadataDefinition < JSONable
6
- include JSONSerialisableObject
7
-
8
- attr_reader :value
9
-
10
- def initialize(value)
11
- @value = value
12
- end
13
- end
14
- end