cfndsl 0.10.2 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/README.md +327 -0
- data/lib/cfndsl.rb +0 -1
- data/lib/cfndsl/orchestration_template.rb +95 -105
- data/lib/cfndsl/resources.rb +2 -3
- data/lib/cfndsl/version.rb +1 -1
- data/spec/cloud_formation_template_spec.rb +5 -0
- data/spec/heat_template_spec.rb +5 -0
- data/spec/metadata_spec.rb +15 -0
- data/spec/resources_spec.rb +1 -14
- data/spec/spec_helper.rb +2 -0
- data/spec/support/shared_examples/orchestration_template.rb +97 -0
- metadata +9 -2
- data/lib/cfndsl/metadata.rb +0 -14
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
N2U4ZWRiYzQ2MTQ2OTAzMGFlNzM0YmY3N2Q2YWVmNDEzNDk3M2JiOQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MzczODNhYWYzOWZkNjMyZTYyZDQyNTExMmE4OGY3NDk3YTk1ZDVhOA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ODc4ODAyNjQ2NDYzMTBjMWIzYjk5NTMzYWJjMWRlZTI2MjcxN2UzMzNmZTVh
|
10
|
+
NmE5ZjY3MzI2NmUxMGZhZjE0ZTZhOTM4NDZjZmM1NGI5ZTFiMjdkY2E4MWVm
|
11
|
+
ZDU0MWE3MzhkZjU2ZWZjMTA2MWM1ZTM4NjM4NGIxN2EwMTNkNmU=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
@@ -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
|
data/lib/cfndsl/resources.rb
CHANGED
@@ -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, :
|
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)
|
data/lib/cfndsl/version.rb
CHANGED
@@ -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
|
data/spec/resources_spec.rb
CHANGED
@@ -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
|
-
|
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
@@ -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.
|
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
|
data/lib/cfndsl/metadata.rb
DELETED