cfndsl 0.0.2 → 0.0.4

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.
Files changed (2) hide show
  1. data/lib/cfndsl.rb +226 -33
  2. metadata +2 -2
data/lib/cfndsl.rb CHANGED
@@ -2,7 +2,21 @@ require 'json';
2
2
 
3
3
  class Module
4
4
  private
5
- def attr_setter(*symbols)
5
+ def dsl_attr_setter(*symbols)
6
+ ##
7
+ # Create setter methods
8
+ #
9
+ # Usage:
10
+ # class Something
11
+ # dsl_attr_setter :Thing
12
+ # end
13
+ #
14
+ # Generates a setter method like this one for each symbol in *symbols:
15
+ #
16
+ # def Thing(value)
17
+ # @Thing = value
18
+ # end
19
+ #
6
20
  symbols.each do |symbol|
7
21
  class_eval do
8
22
  define_method(symbol) do |value|
@@ -12,16 +26,43 @@ class Module
12
26
  end
13
27
  end
14
28
 
29
+ ##
30
+ # Plural names for lists of content objects
31
+ #
32
+
15
33
  @@plurals = {
16
34
  :Metadata => :Metadata,
17
35
  :Property => :Properties
18
36
  }
19
37
 
20
- def content_object(*symbols)
38
+ def dsl_content_object(*symbols)
39
+ ##
40
+ # Create object declaration methods.
41
+ #
42
+ # Usage:
43
+ # Class Something
44
+ # dsl_content_object :Stuff
45
+ # end
46
+ #
47
+ # Generates methods like this:
48
+ #
49
+ # def Stuff(name, *values, &block)
50
+ # @Stuffs ||= {}
51
+ # @Stuffs[name] ||= CfnDsl::#{symbol}Definition.new(*values)
52
+ # @Stuffs[name].instance_eval &block if block_given?
53
+ # return @Stuffs[name]
54
+ # end
55
+ #
56
+ # The effect of this is that you can then create named sub-objects
57
+ # from the main object. The sub objects get stuffed into a container
58
+ # on the main object, and the block is then evaluated in the context
59
+ # of the new object.
60
+ #
21
61
  symbols.each do |symbol|
22
62
  plural = @@plurals[symbol] || "#{symbol}s"
23
63
  class_eval %Q/
24
64
  def #{symbol} (name,*values,&block)
65
+ name = name.to_s
25
66
  @#{plural} ||= {}
26
67
  @#{plural}[name] ||= CfnDsl::#{symbol}Definition.new(*values)
27
68
  @#{plural}[name].instance_eval &block if block_given?
@@ -33,17 +74,31 @@ end
33
74
 
34
75
 
35
76
  module RefCheck
77
+ ##
78
+ # This module defines some methods for walking the reference tree
79
+ # of various objects.
80
+ #
36
81
  def references(refs)
37
- raise "Circular reference" if @visited
38
-
39
- @visited = true
82
+ ##
83
+ # Build up a set of references.
84
+ #
85
+ raise "Circular reference" if @_visited
40
86
 
41
- self.get_references(refs) if self.respond_to?(:get_references)
87
+ @_visited = true
88
+
89
+ if( self.respond_to?(:get_references ) ) then
90
+ self.get_references.each do |ref|
91
+ refs[ref.to_s] = 1
92
+ end
93
+ end
42
94
 
43
95
  self.ref_children.each do |elem|
44
96
  elem.references(refs) if elem.respond_to?(:references)
45
97
  end
46
- @visited = nil
98
+
99
+ @_visited = nil
100
+
101
+ return refs
47
102
  end
48
103
 
49
104
  def ref_children
@@ -70,41 +125,100 @@ module CfnDsl
70
125
  module Functions
71
126
 
72
127
  def Ref(value)
128
+ ##
129
+ # Equivalent to the CloudFormation template built in function Ref
73
130
  RefDefinition.new(value)
74
131
  end
75
132
 
76
133
  def FnBase64( value )
134
+ ##
135
+ # Equivalent to the CloudFormation template built in function Fn::Base64
77
136
  Fn.new("Base64", value);
78
137
  end
79
138
 
80
139
  def FnFindInMap( map, key, value)
140
+ ##
141
+ # Equivalent to the CloudFormation template built in function Fn::FindInMap
81
142
  Fn.new("FindInMap", [map,key,value] )
82
143
  end
83
144
 
84
145
  def FnGetAtt(logicalResource, attribute)
85
- Fn.new( "GetAtt", [logicalResource, attribute] )
146
+ ##
147
+ # Equivalent to the CloudFormation template built in function Fn::GetAtt
148
+ Fn.new( "GetAtt", [logicalResource, attribute], [logicalResource] )
86
149
  end
87
150
 
88
151
  def FnGetAZs(region)
152
+ ##
153
+ # Equivalent to the CloudFormation template built in function Fn::GetAZs
89
154
  Fn.new("GetAZs", region)
90
155
  end
91
156
 
92
157
  def FnJoin(string, array)
158
+ ##
159
+ # Equivalent to the CloudFormation template built in function Fn::Join
93
160
  Fn.new("Join", [ string, array] )
94
161
  end
162
+
163
+ def FnFormat(string, *arguments)
164
+ ##
165
+ # Usage
166
+ # FnFormat( "This is a %0. It is 100%% %1","test", "effective")
167
+ #
168
+ # This will generate a call to Fn::Join that when evaluated will produce
169
+ # the string "This is a test. It is 100% effective."
170
+ #
171
+ # Think of this as %0,%1, etc in the format string being replaced by the
172
+ # corresponding arguments given after the format string. '%%' is replaced
173
+ # by the '%' character.
174
+ #
175
+ # The actual Fn::Join call corresponding to the above FnFormat call would be
176
+ # {"Fn::Join": ["",["This is a ","test",". It is 100","%"," ","effective"]]}
177
+ array = [];
178
+ string.scan( /(.*?)(%(%|\d+)|\Z)/m ) do |x,y|
179
+ array.push $1 if $1 && $1 != ""
180
+ if( $3 == '%' ) then
181
+ array.push '%'
182
+ elsif( $3 ) then
183
+ array.push arguments[ $3.to_i ]
184
+ end
185
+ end
186
+
187
+ Fn.new("Join", ["", array])
188
+ end
95
189
  end
96
190
 
97
191
 
98
192
  class JSONable
193
+ ##
194
+ # This is the base class for just about everything useful in the
195
+ # DSL. It knows how to turn DSL Objects into the corresponding
196
+ # json, and it lets you create new built in function objects
197
+ # from inside the context of a dsl object.
198
+
99
199
  include Functions
100
200
  extend Functions
101
201
  include RefCheck
102
202
 
103
203
  def to_json(*a)
204
+ ##
205
+ # Use instance variables to build a json object. Instance
206
+ # variables that begin with a single underscore are elided.
207
+ # Instance variables that begin with two underscores have one of
208
+ # them removed.
104
209
  hash = {}
105
210
  self.instance_variables.each do |var|
106
211
  name = var[1..-1]
107
- hash[name] = self.instance_variable_get var
212
+
213
+ if( name =~ /^__/ ) then
214
+ # if a variable starts with double underscore, strip one off
215
+ name = name[1..-1]
216
+ elsif( name =~ /^_/ ) then
217
+ # Hide variables that start with single underscore
218
+ name = nil
219
+ end
220
+
221
+ hash[name] = self.instance_variable_get var if name
108
222
  end
109
223
  hash.to_json(*a)
110
224
  end
@@ -120,9 +234,12 @@ module CfnDsl
120
234
  end
121
235
 
122
236
  class Fn < JSONable
123
- def initialize( function, argument )
237
+ ##
238
+ # Handles all of the Fn:: objects
239
+ def initialize( function, argument, refs=[] )
124
240
  @function = function
125
241
  @argument = argument
242
+ @_refs = refs
126
243
  end
127
244
 
128
245
  def to_json(*a)
@@ -131,30 +248,38 @@ module CfnDsl
131
248
  hash.to_json(*a)
132
249
  end
133
250
 
134
- def get_references( refs )
135
- refs[ @argument[0] ] = 1 if @function=="GetAtt"
251
+ def get_references()
252
+ return @_refs
136
253
  end
137
254
 
138
255
  def ref_children
139
256
  return [@argument]
140
257
  end
141
-
142
-
143
258
  end
144
259
 
145
260
 
146
261
  class RefDefinition < JSONable
262
+ ##
263
+ # Handles the Ref objects
147
264
  def initialize( value )
148
265
  @Ref = value
149
266
  end
150
267
 
151
- def get_references( refs )
152
- refs[ @Ref ] = 1
268
+ def get_references()
269
+ [@Ref]
153
270
  end
154
271
  end
155
272
 
156
273
 
157
274
  class PropertyDefinition < JSONable
275
+ ##
276
+ # Handles property objects for Resources
277
+ #
278
+ # Usage
279
+ # Resource("aaa") {
280
+ # Property("propName", "propValue" )
281
+ # }
282
+ #
158
283
  def initialize(value)
159
284
  @value = value;
160
285
  end
@@ -165,6 +290,18 @@ module CfnDsl
165
290
  end
166
291
 
167
292
  class MappingDefinition < JSONable
293
+ ##
294
+ # Handles mapping objects
295
+ #
296
+ # Usage:
297
+ # Mapping("AWSRegionArch2AMI", {
298
+ # "us-east-1" => { "32" => "ami-6411e20d", "64" => "ami-7a11e213" },
299
+ # "us-west-1" => { "32" => "ami-c9c7978c", "64" => "ami-cfc7978a" },
300
+ # "eu-west-1" => { "32" => "ami-37c2f643", "64" => "ami-31c2f645" },
301
+ # "ap-southeast-1" => { "32" => "ami-66f28c34", "64" => "ami-60f28c32" },
302
+ # "ap-northeast-1" => { "32" => "ami-9c03a89d", "64" => "ami-a003a8a1" }
303
+ # })
304
+
168
305
  def initialize(value)
169
306
  @value = value
170
307
  end
@@ -175,30 +312,39 @@ module CfnDsl
175
312
  end
176
313
 
177
314
  class ResourceDefinition < JSONable
178
- attr_setter :Type, :DependsOn, :DeletionPolicy
179
- content_object :Property, :Metadata
315
+ ##
316
+ # Handles Resource objects
317
+ dsl_attr_setter :Type, :DependsOn, :DeletionPolicy
318
+ dsl_content_object :Property, :Metadata
180
319
 
181
- def get_references( refs )
320
+ def get_references()
321
+ refs = []
182
322
  if @DependsOn then
183
323
  if( @DependsOn.respond_to?(:each) ) then
184
324
  @DependsOn.each do |dep|
185
- refs[ dep ] = 1
325
+ refs.push dep
186
326
  end
187
327
  end
188
328
 
189
329
  if( @DependsOn.instance_of?(String) ) then
190
- refs[ @DependsOn ] = 1
330
+ refs.push @DependsOn
191
331
  end
192
332
  end
333
+
334
+ refs
193
335
  end
194
336
  end
195
337
 
196
338
  class MetadataDefinition < JSONable
339
+ ##
340
+ # Handles Metadata objects
197
341
  end
198
342
 
199
343
 
200
344
  class ParameterDefinition < JSONable
201
- attr_setter :Type, :Default, :NoEcho, :AllowedValues, :AllowedPattern, :MaxLength, :MinLength, :MaxValue, :MinValue, :Description, :ConstraintDescription
345
+ ##
346
+ # Handles input parameter objects
347
+ dsl_attr_setter :Type, :Default, :NoEcho, :AllowedValues, :AllowedPattern, :MaxLength, :MinLength, :MaxValue, :MinValue, :Description, :ConstraintDescription
202
348
  def initialize
203
349
  @Type = :String
204
350
  end
@@ -223,7 +369,9 @@ module CfnDsl
223
369
  end
224
370
 
225
371
  class OutputDefinition < JSONable
226
- attr_setter :Value, :Description
372
+ ##
373
+ # Handles Output objects
374
+ dsl_attr_setter :Value, :Description
227
375
 
228
376
  def initialize( value=nil)
229
377
  @Value = value if value
@@ -231,8 +379,10 @@ module CfnDsl
231
379
  end
232
380
 
233
381
  class CloudFormationTemplate < JSONable
234
- attr_setter :AWSTemplateFormatVersion, :Description
235
- content_object :Parameter, :Output, :Resource, :Mapping
382
+ ##
383
+ # Handles the overall template object
384
+ dsl_attr_setter :AWSTemplateFormatVersion, :Description
385
+ dsl_content_object :Parameter, :Output, :Resource, :Mapping
236
386
 
237
387
  def initialize
238
388
  @AWSTemplateFormatVersion = "2010-09-09"
@@ -241,21 +391,64 @@ module CfnDsl
241
391
  def generateOutput()
242
392
  puts self.to_json # uncomment for pretty printing # {:space => ' ', :indent => ' ', :object_nl => "\n", :array_nl => "\n" }
243
393
  end
244
-
245
- def dsl(&block)
246
- self.instance_eval &block
394
+
395
+ @@globalRefs = { "AWS::Region" => 1 }
396
+
397
+ def isValidRef( ref, origin=nil)
398
+ ref = ref.to_s
399
+ origin = origin.to_s if origin
400
+
401
+ return true if @@globalRefs.has_key?( ref )
402
+
403
+ return true if @Parameters && @Parameters.has_key?( ref )
404
+
405
+ if( @Resources.has_key?( ref ) ) then
406
+ return !origin || !@_ResourceRefs || !@_ResourceRefs[ref] || !@_ResourceRefs[ref].has_key?(origin)
407
+ end
408
+
409
+ return false
410
+ end
411
+
412
+ def checkRefs()
413
+ invalids = []
414
+ @_ResourceRefs = {}
415
+ if(@Resources) then
416
+ @Resources.keys.each do |resource|
417
+ @_ResourceRefs[resource.to_s] = @Resources[resource].references({})
418
+ end
419
+ @_ResourceRefs.keys.each do |origin|
420
+ @_ResourceRefs[origin].keys.each do |ref|
421
+ invalids.push "Invalid Reference: Resource #{origin} refers to #{ref}" unless isValidRef(ref,origin)
422
+ end
423
+ end
424
+ end
425
+ outputRefs = {}
426
+ if(@Outputs) then
427
+ @Outputs.keys.each do |resource|
428
+ outputRefs[resource.to_s] = @Outputs[resource].references({})
429
+ end
430
+ outputRefs.keys.each do |origin|
431
+ outputRefs[origin].keys.each do |ref|
432
+ invalids.push "Invalid Reference: Output #{origin} refers to #{ref}" unless isValidRef(ref,nil)
433
+ end
434
+ end
435
+ end
436
+ return invalids.length>0 ? invalids : nil
247
437
  end
248
438
 
249
439
  end
250
-
251
-
252
440
  end
253
441
 
254
442
  def CloudFormation(&block)
255
443
  x = CfnDsl::CloudFormationTemplate.new
256
- x.dsl(&block)
257
- x.generateOutput
258
-
444
+ x.declare(&block)
445
+ invalid_references = x.checkRefs()
446
+ if( invalid_references ) then
447
+ puts invalid_references.join("\n");
448
+ exit(-1)
449
+ else
450
+ x.generateOutput
451
+ end
259
452
  end
260
453
 
261
454
 
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.0.2
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-15 00:00:00.000000000 Z
12
+ date: 2012-12-16 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: DSL for creating AWS Cloudformation templates
15
15
  email: chris@howeville.com