cfndsl 0.1.3 → 0.1.4

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