cfndsl 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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