cfndsl 0.1.3 → 0.1.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.
data/bin/cfndsl CHANGED
@@ -1,4 +1,67 @@
1
- #!/usr/bin/env ruby
1
+ #!/usr/bin/env ruby
2
+
2
3
  require 'cfndsl'
3
- model = eval File.read ARGV[0]
4
- puts model.to_json
4
+ require 'optparse'
5
+
6
+ options = {}
7
+
8
+ optparse = OptionParser.new do|opts|
9
+ opts.banner = "Usage: cfndsl [options] FILE"
10
+
11
+ # Define the options, and what they do
12
+ options[:output] = '-'
13
+ opts.on( '-o', '--output FILE', 'Write output to file' ) do |file|
14
+ options[:output] = file
15
+ end
16
+
17
+ options[:extras] = []
18
+ opts.on( '-y', '--yaml FILE', 'Import yaml file as local variables' ) do |file|
19
+ options[:extras].push([:yaml,File.expand_path(file)])
20
+ end
21
+
22
+ opts.on( '-r', '--ruby FILE', 'Evaluate ruby file before template' ) do |file|
23
+ options[:extras].push([:ruby,File.expand_path(file)])
24
+ end
25
+
26
+ opts.on( '-j', '--json FILE', 'Import json file as local variables' ) do |file|
27
+ options[:extras].push([:json,File.expand_path(file)])
28
+ end
29
+
30
+
31
+ opts.on( '-D', '--define "VARIABLE=VALUE"', 'Directly set local VARIABLE as VALUE' ) do |file|
32
+ options[:extras].push([:raw,file])
33
+ end
34
+
35
+ options[:verbose] = false
36
+ opts.on('-v', '--verbose', "Turn on verbose ouptut") do
37
+ options[:verbose] = true
38
+ end
39
+
40
+ # This displays the help screen, all programs are
41
+ # assumed to have this option.
42
+ opts.on( '-h', '--help', 'Display this screen' ) do
43
+ puts opts
44
+ exit
45
+ end
46
+ end
47
+
48
+ optparse.parse!
49
+ unless ARGV[0] then
50
+ puts optparse.help
51
+ exit(1)
52
+ end
53
+
54
+ filename = File.expand_path(ARGV[0])
55
+ verbose = options[:verbose] && STDERR
56
+
57
+ model = CfnDsl::eval_file_with_extras(filename, options[:extras], verbose)
58
+
59
+ output = STDOUT
60
+ if options[:output] != '-' then
61
+ verbose.puts("Writing to #{options[:output]}") if verbose
62
+ output = File.open( File.expand_path(options[:output]), "w")
63
+ else
64
+ verbose.puts("Writing to STDOUT") if verbose
65
+ end
66
+
67
+ output.puts model.to_json
@@ -15,6 +15,75 @@ require 'cfndsl/Parameters'
15
15
  require 'cfndsl/Outputs'
16
16
  require 'cfndsl/CloudFormationTemplate'
17
17
 
18
+ module CfnDsl
19
+ def self.eval_file_with_extras(filename, extras = [], logstream = nil)
20
+ # This function handles the eval of the template file and returns the
21
+ # results. It does this with a ruby "eval", but it builds up a customized
22
+ # binding environment before it calls eval. The environment can be
23
+ # customized by passing a list of customizations in the extras parameter.
24
+ #
25
+ # These customizations are expressed as an array of pairs of
26
+ # (type,filename). They are evaluated in the order they appear in the
27
+ # extras array. The types are as follows
28
+ #
29
+ # :yaml - the second element is treated as a file name, which is loaded
30
+ # as a yaml file. The yaml file should contain a top level
31
+ # dictionary. Each of the keys of the top level dictionary is
32
+ # used as a local variable in the evalation context.
33
+ #
34
+ # :json - the second element is treated as a file name, which is loaded
35
+ # as a json file. The yaml file should contain a top level
36
+ # dictionary. Each of the keys of the top level dictionary is
37
+ # used as a local variable in the evalation context.
38
+ #
39
+ # :ruby - the second element is treated as a file name which is evaluated
40
+ # as a ruby file. Any assigments (or other binding affecting
41
+ # side effects) will persist into the context where the template
42
+ # is evaluated
43
+ #
44
+ # :raw - the seccond elements is treated as a ruby statement and is
45
+ # evaluated in the binding context, similar to the contents of
46
+ # a ruby file.
47
+ #
48
+ # Note that the order is important, as later extra sections can overwrite
49
+ # or even undo things that were done by earlier sections.
50
+
51
+ b = binding
52
+ extras.each do |pair|
53
+ type,file = pair
54
+ case type
55
+ when :yaml
56
+ logstream.puts("Loading YAML file #{file}") if logstream
57
+ parameters = YAML.load(File.read(file))
58
+ parameters.each do |k,v|
59
+ logstream.puts("Setting local variable #{k} to #{v}") if logstream
60
+ b.eval("#{k} = #{v.inspect}")
61
+ end
62
+
63
+ when :json
64
+ logstream.puts("Loading YAML file #{file}") if logstream
65
+ parameters = JSON.load(File.read(file))
66
+ parameters.each do |k,v|
67
+ logstream.puts("Setting local variable #{k} to #{v}") if logstream
68
+ b.eval("#{k} = #{v.inspect}")
69
+ end
70
+
71
+ when :ruby
72
+ logstream("Runnning ruby file #{file}") if logstream
73
+ b.eval(File.read(file), file)
74
+
75
+ when :raw
76
+ logstrame("Running raw ruby code #{file}") if logstream
77
+ b.eval(file, "raw code")
78
+ end
79
+ end
80
+
81
+ logstream.puts("Loading template file #{filename}") if logstream
82
+ model = b.eval(File.read(filename), filename)
83
+ return model
84
+ end
85
+ end
86
+
18
87
  def CloudFormation(&block)
19
88
  x = CfnDsl::CloudFormationTemplate.new
20
89
  x.declare(&block)
@@ -28,3 +97,16 @@ def CloudFormation(&block)
28
97
  end
29
98
  end
30
99
 
100
+ def Heat(&block)
101
+ x = CfnDsl::HeatTemplate.new
102
+ x.declare(&block)
103
+ invalid_references = x.checkRefs()
104
+ if( invalid_references ) then
105
+ abort invalid_references.join("\n")
106
+ elsif( CfnDsl::Errors.errors? ) then
107
+ abort CfnDsl::Errors.errors.join("\n")
108
+ else
109
+ return x
110
+ end
111
+ end
112
+
@@ -2,7 +2,7 @@ require 'cfndsl/JSONable'
2
2
  require 'cfndsl/names'
3
3
 
4
4
  module CfnDsl
5
- class CloudFormationTemplate < JSONable
5
+ class OrchestrationTemplate < JSONable
6
6
  ##
7
7
  # Handles the overall template object
8
8
  dsl_attr_setter :AWSTemplateFormatVersion, :Description
@@ -62,18 +62,26 @@ module CfnDsl
62
62
  end
63
63
  return invalids.length>0 ? invalids : nil
64
64
  end
65
+ end
65
66
 
67
+ class CloudFormationTemplate < OrchestrationTemplate
68
+ def self.template_types
69
+ CfnDsl::AWSTypes::AWS_Types
70
+ end
71
+ def self.type_module
72
+ CfnDsl::AWSTypes
73
+ end
66
74
 
67
75
  names = {}
68
76
  nametypes = {}
69
- CfnDsl::Types::AWS_Types["Resources"].each_pair do |name, type|
77
+ self.template_types["Resources"].each_pair do |name, type|
70
78
  # Subclass ResourceDefintion and generate property methods
71
79
  klass = Class.new(CfnDsl::ResourceDefinition)
72
80
  klassname = name.split("::").join("_")
73
- CfnDsl::Types.const_set( klassname, klass )
81
+ type_module.const_set( klassname, klass )
74
82
  type["Properties"].each_pair do |pname, ptype|
75
83
  if( ptype.instance_of? String )
76
- create_klass = CfnDsl::Types.const_get( ptype );
84
+ create_klass = type_module.const_get( ptype );
77
85
 
78
86
  klass.class_eval do
79
87
  CfnDsl::methodNames(pname) do |method|
@@ -91,7 +99,7 @@ module CfnDsl
91
99
  else
92
100
  #Array version
93
101
  sing_name = CfnDsl::Plurals.singularize( pname )
94
- create_klass = CfnDsl::Types.const_get( ptype[0] )
102
+ create_klass = type_module.const_get( ptype[0] )
95
103
  klass.class_eval do
96
104
  CfnDsl::methodNames(pname) do |method|
97
105
  define_method(method) do |*values, &block|
@@ -128,7 +136,7 @@ module CfnDsl
128
136
  # this only happens if there is an ambiguity
129
137
  names[abreve_name] = nil
130
138
  else
131
- names[abreve_name] = CfnDsl::Types.const_get(klassname)
139
+ names[abreve_name] = self.type_module.const_get(klassname)
132
140
  nametypes[abreve_name] = name
133
141
  end
134
142
  parts.shift
@@ -154,5 +162,106 @@ module CfnDsl
154
162
  end
155
163
  end
156
164
  end
165
+
166
+
167
+ end
168
+
169
+ class HeatTemplate < OrchestrationTemplate
170
+ def self.template_types
171
+ CfnDsl::OSTypes::OS_Types
172
+ end
173
+ def self.type_module
174
+ CfnDsl::OSTypes
175
+ end
176
+
177
+ names = {}
178
+ nametypes = {}
179
+ self.template_types["Resources"].each_pair do |name, type|
180
+ # Subclass ResourceDefintion and generate property methods
181
+ klass = Class.new(CfnDsl::ResourceDefinition)
182
+ klassname = name.split("::").join("_")
183
+ type_module.const_set( klassname, klass )
184
+ type["Properties"].each_pair do |pname, ptype|
185
+ if( ptype.instance_of? String )
186
+ create_klass = type_module.const_get( ptype );
187
+
188
+ klass.class_eval do
189
+ CfnDsl::methodNames(pname) do |method|
190
+ define_method(method) do |*values, &block|
191
+ if( values.length <1 ) then
192
+ values.push create_klass.new
193
+ end
194
+ @Properties ||= {}
195
+ @Properties[pname] ||= CfnDsl::PropertyDefinition.new( *values )
196
+ @Properties[pname].value.instance_eval &block if block
197
+ @Properties[pname].value
198
+ end
199
+ end
200
+ end
201
+ else
202
+ #Array version
203
+ sing_name = CfnDsl::Plurals.singularize( pname )
204
+ create_klass = type_module.const_get( ptype[0] )
205
+ klass.class_eval do
206
+ CfnDsl::methodNames(pname) do |method|
207
+ define_method(method) do |*values, &block|
208
+ if( values.length < 1 ) then
209
+ values.push []
210
+ end
211
+ @Properties ||= {}
212
+ @Properties[pname] ||= PropertyDefinition.new( *values )
213
+ @Properties[pname].value.instance_eval &block if block
214
+ @Properties[pname].value
215
+ end
216
+ end
217
+
218
+ CfnDsl::methodNames(sing_name) do |method|
219
+ define_method(method) do |value=nil, &block|
220
+ @Properties ||= {}
221
+ @Properties[pname] ||= PropertyDefinition.new( [] )
222
+ if( !value ) then
223
+ value = create_klass.new
224
+ end
225
+ @Properties[pname].value.push value
226
+ value.instance_eval &block if block
227
+ value
228
+ end
229
+ end
230
+ end
231
+ end
232
+
233
+ end
234
+ parts = name.split "::"
235
+ while( parts.length > 0)
236
+ abreve_name = parts.join "_"
237
+ if( names.has_key? abreve_name ) then
238
+ # this only happens if there is an ambiguity
239
+ names[abreve_name] = nil
240
+ else
241
+ names[abreve_name] = self.type_module.const_get(klassname)
242
+ nametypes[abreve_name] = name
243
+ end
244
+ parts.shift
245
+ end
246
+ end
247
+
248
+ #Define property setter methods for each of the unambiguous type names
249
+ names.each_pair do |typename,type|
250
+ if(type) then
251
+ class_eval do
252
+ CfnDsl::methodNames(typename) do |method|
253
+ define_method(method) do |name,*values,&block|
254
+ name = name.to_s
255
+ @Resources ||= {}
256
+ resource = @Resources[name] ||= type.new(*values)
257
+ resource.instance_eval &block if block
258
+ resource.instance_variable_set( "@Type", nametypes[typename] )
259
+ resource
260
+ end
261
+ end
262
+ end
263
+ end
264
+ end
157
265
  end
266
+
158
267
  end
@@ -4,15 +4,15 @@ require 'cfndsl/Plurals'
4
4
  require 'cfndsl/names'
5
5
 
6
6
  module CfnDsl
7
- module Types
8
-
7
+ module AWSTypes
9
8
  aws_types = YAML::load( File.open( "#{File.dirname(__FILE__)}/aws_types.yaml") );
10
- Types.const_set( "AWS_Types", aws_types);
11
-
9
+ AWSTypes.const_set( "AWS_Types", aws_types);
10
+
12
11
  # Do a little sanity checking - all of the types referenced in Resources
13
12
  # should be represented in Types
14
13
  aws_types["Resources"].keys.each do |resource_name|
15
14
  #puts resource_name
15
+
16
16
  resource = aws_types["Resources"][resource_name]
17
17
  resource.values.each do |thing|
18
18
  thing.values.each do |type|
@@ -36,9 +36,7 @@ module CfnDsl
36
36
  end
37
37
  end
38
38
  end
39
-
40
-
41
-
39
+
42
40
  # declare classes for all of the types with named methods for setting the values
43
41
  class AWSType < JSONable
44
42
  end
@@ -47,11 +45,11 @@ module CfnDsl
47
45
 
48
46
  # Go through and declare all of the types first
49
47
  aws_types["Types"].each_key do |typename|
50
- if( ! Types.const_defined? typename ) then
51
- klass = Types.const_set( typename, Class.new(AWSType ) )
48
+ if( ! AWSTypes.const_defined? typename ) then
49
+ klass = AWSTypes.const_set( typename, Class.new(AWSType ) )
52
50
  classes[typename] = klass
53
51
  else
54
- classes[typename] = Types.const_get(typename)
52
+ classes[typename] = AWSTypes.const_get(typename)
55
53
  end
56
54
  end
57
55
 
@@ -62,7 +60,157 @@ module CfnDsl
62
60
  if( typeval.respond_to? :each_pair ) then
63
61
  typeval.each_pair do |attr_name, attr_type|
64
62
  if( attr_type.kind_of? Array ) then
65
- klass = CfnDsl::Types.const_get( attr_type[0] )
63
+ klass = CfnDsl::AWSTypes.const_get( attr_type[0] )
64
+ variable = "@#{attr_name}".to_sym
65
+
66
+ method = CfnDsl::Plurals::singularize(attr_name)
67
+ methods = attr_name
68
+ all_methods = CfnDsl::methodNames(method) +
69
+ CfnDsl::methodNames(methods)
70
+ type.class_eval do
71
+ all_methods.each do |method_name|
72
+ define_method(method_name) do | value=nil, *rest, &block|
73
+ existing = instance_variable_get( variable )
74
+ # For no-op invocations, get out now
75
+ return existing if value.nil? and rest.length == 0 and ! block
76
+
77
+ # We are going to modify the value in some
78
+ # way, make sure that we have an array to mess
79
+ # with if we start with nothing
80
+ if( !existing ) then
81
+ existing = instance_variable_set( variable, [] )
82
+ end
83
+
84
+ # special case for just a block, no args
85
+ if( value.nil? and rest.length == 0 and block ) then
86
+ val = klass.new
87
+ existing.push val
88
+ value.instance_eval &block(val)
89
+ return existin
90
+ end
91
+
92
+ # Glue all of our parameters together into
93
+ # a giant array - flattening one level deep, if needed
94
+ array_params = []
95
+ if( value.kind_of? Array) then
96
+ value.each {|x| array_params.push x}
97
+ else
98
+ array_params.push value
99
+ end
100
+ if( rest.length > 0) then
101
+ rest.each do |v|
102
+ if( v.kind_of? Array ) then
103
+ array_params += rest
104
+ else
105
+ array_params.push v
106
+ end
107
+ end
108
+ end
109
+
110
+ # Here, if we were given multiple arguments either
111
+ # as method [a,b,c], method(a,b,c), or even
112
+ # method( a, [b], c) we end up with
113
+ # array_params = [a,b,c]
114
+ #
115
+ # array_params will have at least one item
116
+ # unless the user did something like pass in
117
+ # a bunch of empty arrays.
118
+ if block then
119
+ array_params.each do |val|
120
+ value = klass.new
121
+ existing.push value
122
+ value.instance_eval &block(val) if block
123
+ end
124
+ else
125
+ # List of parameters with no block -
126
+ # hope that the user knows what he is
127
+ # doing and stuff them into our existing
128
+ # array
129
+ array_params.each do |val|
130
+ existing.push value
131
+ end
132
+ end
133
+ return existing
134
+ end
135
+ end
136
+ end
137
+ else
138
+ klass = CfnDsl::AWSTypes.const_get( attr_type );
139
+ variable = "@#{attr_name}".to_sym
140
+
141
+ type.class_eval do
142
+ CfnDsl::methodNames(attr_name) do |method|
143
+ define_method(method) do | value=nil, *rest, &block |
144
+ value ||= klass.new
145
+ instance_variable_set( variable, value )
146
+ value.instance_eval &block if block
147
+ value
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+ module OSTypes
158
+ os_types = YAML::load( File.open( "#{File.dirname(__FILE__)}/os_types.yaml") );
159
+ OSTypes.const_set( "OS_Types", os_types);
160
+
161
+ # Do a little sanity checking - all of the types referenced in Resources
162
+ # should be represented in Types
163
+ os_types["Resources"].keys.each do |resource_name|
164
+ #puts resource_name
165
+
166
+ resource = os_types["Resources"][resource_name]
167
+ resource.values.each do |thing|
168
+ thing.values.each do |type|
169
+ if( type.kind_of? Array ) then
170
+ type.each do | type |
171
+ puts "unknown type #{type}" unless os_types["Types"].has_key? type
172
+ end
173
+ else
174
+ puts "unknown type #{type}" unless os_types["Types"].has_key? type
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+ # All of the type values should also be references
181
+
182
+ os_types["Types"].values do |type|
183
+ if( type.respond_to? :values) then
184
+ type.values.each do |tv|
185
+ puts "unknown type #{tv}" unless os_types["Types"].has_key? tv
186
+ end
187
+ end
188
+ end
189
+
190
+ # declare classes for all of the types with named methods for setting the values
191
+ class OSType < JSONable
192
+ end
193
+
194
+ classes = {}
195
+
196
+ # Go through and declare all of the types first
197
+ os_types["Types"].each_key do |typename|
198
+ if( ! OSTypes.const_defined? typename ) then
199
+ klass = OSTypes.const_set( typename, Class.new(OSType ) )
200
+ classes[typename] = klass
201
+ else
202
+ classes[typename] = OSTypes.const_get(typename)
203
+ end
204
+ end
205
+
206
+ # Now go through them again and define attribute setter methods
207
+ classes.each_pair do |typename,type|
208
+ #puts typename
209
+ typeval = os_types["Types"][typename]
210
+ if( typeval.respond_to? :each_pair ) then
211
+ typeval.each_pair do |attr_name, attr_type|
212
+ if( attr_type.kind_of? Array ) then
213
+ klass = CfnDsl::OSTypes.const_get( attr_type[0] )
66
214
  variable = "@#{attr_name}".to_sym
67
215
 
68
216
  method = CfnDsl::Plurals::singularize(attr_name)
@@ -137,7 +285,7 @@ module CfnDsl
137
285
  end
138
286
  end
139
287
  else
140
- klass = CfnDsl::Types.const_get( attr_type );
288
+ klass = CfnDsl::OSTypes.const_get( attr_type );
141
289
  variable = "@#{attr_name}".to_sym
142
290
 
143
291
  type.class_eval do
@@ -155,5 +303,7 @@ module CfnDsl
155
303
  end
156
304
  end
157
305
  end
306
+
307
+
158
308
  end
159
309