convection 0.0.1 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +26 -8
  4. data/.rubocop_todo.yml +77 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +10 -0
  7. data/Gemfile +9 -0
  8. data/README.md +27 -2
  9. data/Rakefile +11 -1
  10. data/bin/convection +49 -0
  11. data/convection.gemspec +5 -7
  12. data/example/.ruby-version +1 -0
  13. data/example/Cloudfile +13 -0
  14. data/example/deprecated/elb.rb +27 -0
  15. data/example/deprecated/iam_access_key.rb +18 -0
  16. data/example/deprecated/iam_group.rb +31 -0
  17. data/example/{iam_role.rb → deprecated/iam_role.rb} +21 -32
  18. data/example/deprecated/iam_user.rb +31 -0
  19. data/example/deprecated/rds.rb +70 -0
  20. data/example/{s3.rb → deprecated/s3.rb} +0 -0
  21. data/example/deprecated/sqs.rb +32 -0
  22. data/example/deprecated/vpc.rb +85 -0
  23. data/example/foobar.rb +22 -0
  24. data/example/output/vpc.json +335 -0
  25. data/example/security-groups.rb +40 -0
  26. data/example/trust_cloudtrail.rb +24 -0
  27. data/example/vpc.rb +63 -81
  28. data/ext/resource_generator.sh +21 -0
  29. data/lib/convection.rb +5 -4
  30. data/lib/convection/control/cloud.rb +59 -0
  31. data/lib/convection/control/stack.rb +261 -60
  32. data/lib/convection/dsl/helpers.rb +63 -5
  33. data/lib/convection/model/attributes.rb +60 -0
  34. data/lib/convection/model/cloudfile.rb +58 -0
  35. data/lib/convection/model/diff.rb +39 -0
  36. data/lib/convection/model/event.rb +62 -0
  37. data/lib/convection/model/exceptions.rb +18 -0
  38. data/lib/convection/model/mixin/cidr_block.rb +4 -4
  39. data/lib/convection/model/mixin/colorize.rb +20 -0
  40. data/lib/convection/model/mixin/conditional.rb +1 -3
  41. data/lib/convection/model/mixin/policy.rb +89 -0
  42. data/lib/convection/model/mixin/protocol.rb +29 -0
  43. data/lib/convection/model/mixin/taggable.rb +2 -2
  44. data/lib/convection/model/template.rb +248 -21
  45. data/lib/convection/model/template/condition.rb +56 -0
  46. data/lib/convection/model/template/mapping.rb +4 -3
  47. data/lib/convection/model/template/output.rb +9 -7
  48. data/lib/convection/model/template/parameter.rb +19 -4
  49. data/lib/convection/model/template/resource.rb +317 -23
  50. data/lib/convection/model/template/resource/aws_auto_scaling_auto_scaling_group.rb +39 -0
  51. data/lib/convection/model/template/resource/aws_auto_scaling_launch_configuration.rb +30 -0
  52. data/lib/convection/model/template/resource/aws_auto_scaling_scaling_policy.rb +20 -0
  53. data/lib/convection/model/template/resource/aws_cloud_watch_alarm.rb +31 -0
  54. data/lib/convection/model/template/resource/aws_ec2_instance.rb +10 -46
  55. data/lib/convection/model/template/resource/aws_ec2_internet_gateway.rb +3 -14
  56. data/lib/convection/model/template/resource/aws_ec2_network_acl.rb +45 -0
  57. data/lib/convection/model/template/resource/aws_ec2_network_acl_entry.rb +27 -0
  58. data/lib/convection/model/template/resource/aws_ec2_route.rb +7 -40
  59. data/lib/convection/model/template/resource/aws_ec2_route_table.rb +2 -17
  60. data/lib/convection/model/template/resource/aws_ec2_security_group.rb +24 -30
  61. data/lib/convection/model/template/resource/aws_ec2_security_group_ingres.rb +25 -0
  62. data/lib/convection/model/template/resource/aws_ec2_subnet.rb +21 -28
  63. data/lib/convection/model/template/resource/aws_ec2_subnet_network_acl_association.rb +18 -0
  64. data/lib/convection/model/template/resource/aws_ec2_subnet_route_table_association.rb +3 -24
  65. data/lib/convection/model/template/resource/aws_ec2_vpc.rb +20 -22
  66. data/lib/convection/model/template/resource/aws_ec2_vpc_gateway_attachment.rb +4 -28
  67. data/lib/convection/model/template/resource/aws_elasticache_cluster.rb +24 -0
  68. data/lib/convection/model/template/resource/aws_elasticache_parameter_group.rb +19 -0
  69. data/lib/convection/model/template/resource/aws_elasticache_security_group.rb +17 -0
  70. data/lib/convection/model/template/resource/aws_elasticache_security_group_ingress.rb +19 -0
  71. data/lib/convection/model/template/resource/aws_elb.rb +39 -0
  72. data/lib/convection/model/template/resource/aws_iam_access_key.rb +19 -0
  73. data/lib/convection/model/template/resource/aws_iam_group.rb +18 -0
  74. data/lib/convection/model/template/resource/aws_iam_instance_profile.rb +21 -0
  75. data/lib/convection/model/template/resource/aws_iam_policy.rb +28 -24
  76. data/lib/convection/model/template/resource/aws_iam_role.rb +88 -19
  77. data/lib/convection/model/template/resource/aws_iam_user.rb +53 -0
  78. data/lib/convection/model/template/resource/aws_logs_loggroup.rb +33 -0
  79. data/lib/convection/model/template/resource/aws_rds_db_instance.rb +59 -0
  80. data/lib/convection/model/template/resource/aws_rds_db_parameter_group.rb +27 -0
  81. data/lib/convection/model/template/resource/aws_rds_db_security_group.rb +40 -0
  82. data/lib/convection/model/template/resource/aws_rds_db_subnet_group.rb +26 -0
  83. data/lib/convection/model/template/resource/aws_route53_health_check.rb +17 -0
  84. data/lib/convection/model/template/resource/aws_route53_recordset.rb +30 -0
  85. data/lib/convection/model/template/resource/aws_s3_bucket.rb +8 -44
  86. data/lib/convection/model/template/resource/aws_s3_bucket_policy.rb +14 -19
  87. data/lib/convection/model/template/resource/aws_sns_topic.rb +19 -0
  88. data/lib/convection/model/template/resource/aws_sqs_queue.rb +31 -0
  89. data/lib/convection/model/template/resource/aws_sqs_queue_policy.rb +18 -0
  90. data/test/convection/model/test_conditions.rb +121 -0
  91. data/test/convection/model/test_elasticache.rb +97 -0
  92. data/test/convection/model/test_loggroups.rb +25 -0
  93. data/test/convection/model/test_rds.rb +76 -0
  94. data/test/convection/model/test_template.rb +64 -0
  95. data/test/convection/model/test_validation.rb +216 -0
  96. data/test/test_helper.rb +17 -0
  97. metadata +131 -50
@@ -0,0 +1,29 @@
1
+ require 'netaddr'
2
+
3
+ module Convection
4
+ module Model
5
+ module Mixin
6
+ ##
7
+ # Map IP protocol names to numbers
8
+ ##
9
+ module Protocol
10
+ class << self
11
+ def lookup(value)
12
+ case value
13
+ when :any then -1
14
+ when :icmp then 1
15
+ when :tcp then 6
16
+ when :udp then 17
17
+ else value
18
+ end
19
+ end
20
+ end
21
+
22
+ def protocol_property(name = :protocol, property_name = 'IpProtocol')
23
+ property(name, property_name,
24
+ :transform => Mixin::Protocol.method(:lookup))
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -9,8 +9,8 @@ module Convection
9
9
  def render
10
10
  map do |t|
11
11
  {
12
- :Key => t[0].to_s,
13
- :Value => t[1]
12
+ 'Key' => t[0].to_s,
13
+ 'Value' => t[1]
14
14
  }
15
15
  end
16
16
  end
@@ -1,5 +1,7 @@
1
1
  require_relative '../dsl/helpers'
2
2
  require_relative '../dsl/intrinsic_functions'
3
+ require_relative './diff'
4
+ require_relative './exceptions.rb'
3
5
  require 'json'
4
6
 
5
7
  module Convection
@@ -8,6 +10,44 @@ module Convection
8
10
  # Template DSL
9
11
  ##
10
12
  module Template
13
+ ##
14
+ # Container for DSL interfaces
15
+ ##
16
+ module Resource
17
+ class << self
18
+ ## Wrap private define_method
19
+ def attach_resource(name, klass)
20
+ define_method(name) do |rname, &block|
21
+ resource = klass.new(rname, self)
22
+ resource.instance_exec(&block) if block
23
+
24
+ resources[rname] = resource
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ include DSL::Helpers
31
+ include DSL::Template::Resource
32
+
33
+ CF_MAX_BYTESIZE = 51_200
34
+ CF_MAX_DESCRIPTION_BYTESIZE = 1_024
35
+ CF_MAX_MAPPING_ATTRIBUTE_NAME = 255
36
+ CF_MAX_MAPPING_ATTRIBUTES = 30
37
+ CF_MAX_MAPPING_NAME = 25
38
+ CF_MAX_MAPPINGS = 100
39
+ CF_MAX_OUTPUT_NAME_CHARACTERS = 255
40
+ CF_MAX_OUTPUTS = 60
41
+ CF_MAX_PARAMETER_NAME_CHARACTERS = 255
42
+ CF_MAX_PARAMETERS = 60
43
+ CF_MAX_PARAMETER_VALUE_BYTESIZE = 4_086
44
+ CF_MAX_RESOURCE_NAME = 255
45
+ CF_MAX_RESOURCES = 200
46
+
47
+ attribute :name
48
+ attribute :version
49
+ attribute :description
50
+
11
51
  def parameter(name, &block)
12
52
  pa = Model::Template::Parameter.new(name, self)
13
53
 
@@ -22,12 +62,12 @@ module Convection
22
62
  mappings[name] = m
23
63
  end
24
64
 
25
- # def condition(name, &block)
26
- # c = Model::Template::Condition.new
27
- # c.instance_exec(&block) if block
28
- #
29
- # conditions[name] = c
30
- # end
65
+ def condition(name, &block)
66
+ c = Model::Template::Condition.new(name, self)
67
+
68
+ c.instance_exec(&block) if block
69
+ conditions[name] = c
70
+ end
31
71
 
32
72
  def resource(name, &block)
33
73
  r = Model::Template::Resource.new(name, self)
@@ -50,42 +90,106 @@ module Convection
50
90
  # Mapable hash
51
91
  ##
52
92
  class Collection < Hash
53
- def map(&block)
93
+ def map(no_nil = false, &block)
54
94
  result = {}
55
95
 
56
96
  each do |key, value|
57
- result[key] = block.call(value)
97
+ res = block.call(value)
98
+
99
+ next if no_nil && res.nil?
100
+ next if no_nil && res.is_a?(Array) && res.empty?
101
+ next if no_nil && res.is_a?(Hash) && res.empty?
102
+
103
+ result[key] = res
58
104
  end
59
105
 
60
106
  result
61
107
  end
62
108
  end
63
109
 
110
+ ##
111
+ # HACK: Add generic diff(other) and properties to Hash and Array
112
+ ##
113
+ class ::Array
114
+ ## Recursivly flatten an array into 1st order key/value pairs
115
+ def properties(memo = {}, path = '')
116
+ each_with_index do |elm, i|
117
+ if elm.is_a?(Hash) || elm.is_a?(Array)
118
+ elm.properties(memo, "#{path}.#{i}")
119
+ else
120
+ memo["#{path}.#{i}"] = elm
121
+ end
122
+ end
123
+
124
+ memo
125
+ end
126
+ end
127
+
128
+ ##
129
+ # HACK: Add generic diff(other) and properties to Hash and Array
130
+ ##
131
+ class ::Hash
132
+ ## Use flattened properties to calculate a diff
133
+ def diff(other = {})
134
+ our_properties = properties
135
+ their_properties = other.properties
136
+
137
+ (our_properties.keys + their_properties.keys).uniq.each_with_object({}) do |key, memo|
138
+ next if (our_properties[key] == their_properties[key] rescue false)
139
+
140
+ ## HACK: String/Number/Symbol comparison
141
+ if our_properties[key].is_a?(Numeric) ||
142
+ their_properties[key].is_a?(Numeric) ||
143
+ our_properties[key].is_a?(Symbol) ||
144
+ their_properties[key].is_a?(Symbol)
145
+ next if our_properties[key].to_s == their_properties[key].to_s
146
+ end
147
+
148
+ memo[key] = [our_properties[key], their_properties[key]]
149
+ end
150
+ end
151
+
152
+ ## Recursivly flatten a hash into 1st order key/value pairs
153
+ def properties(memo = {}, path = '')
154
+ keys.each do |key|
155
+ if self[key].is_a?(Hash) || self[key].is_a?(Array)
156
+ self[key].properties(memo, "#{path}.#{key}")
157
+ else
158
+ memo["#{path}.#{key}"] = self[key]
159
+ end
160
+ end
161
+
162
+ memo
163
+ end
164
+ end
165
+
64
166
  ##
65
167
  # Template container class
66
168
  ##
67
169
  class Template
68
- extend DSL::Helpers
69
-
70
170
  include DSL::IntrinsicFunctions
71
171
  include DSL::Template
72
172
 
73
173
  DEFAULT_VERSION = '2010-09-09'
74
174
 
75
- attribute :version
76
- attribute :description
77
- attribute :region
78
-
79
175
  attr_reader :stack
176
+ attr_reader :attribute_mappings
177
+
80
178
  attr_reader :parameters
81
179
  attr_reader :mappings
82
180
  attr_reader :conditions
83
181
  attr_reader :resources
84
182
  attr_reader :outputs
85
183
 
86
- def initialize(stack = Control::Stack.new('default', self), &block)
184
+ def template
185
+ self
186
+ end
187
+
188
+ def initialize(stack = nil, &block)
87
189
  @definition = block
190
+
88
191
  @stack = stack
192
+ @attribute_mappings = {}
89
193
 
90
194
  @version = DEFAULT_VERSION
91
195
  @description = ''
@@ -97,11 +201,20 @@ module Convection
97
201
  @outputs = Collection.new
98
202
  end
99
203
 
100
- def render(stack = nil)
101
- ## Instantiate a new template with the definition block and an other stack
102
- return Template.new(stack, &@definition).render unless stack.nil?
204
+ def clone(stack_)
205
+ Template.new(stack_, &@definition)
206
+ end
103
207
 
208
+ def execute
104
209
  instance_exec(&@definition)
210
+ end
211
+
212
+ def render(stack_ = nil)
213
+ ## Instantiate a new template with the definition block and an other stack
214
+ return clone(stack_).render unless stack_.nil?
215
+
216
+ execute ## Process the template document
217
+
105
218
  {
106
219
  'AWSTemplateFormatVersion' => version,
107
220
  'Description' => description,
@@ -113,8 +226,122 @@ module Convection
113
226
  }
114
227
  end
115
228
 
116
- def to_json(stack = nil)
117
- JSON.pretty_generate(render(stack))
229
+ def diff(other, stack_ = nil)
230
+ render(stack_).diff(other).map { |diff| Diff.new(diff[0], *diff[1]) }
231
+ end
232
+
233
+ def to_json(stack_ = nil, pretty = false)
234
+ rendered_stack = render(stack_)
235
+ validate(rendered_stack)
236
+ return JSON.generate(rendered_stack) unless pretty
237
+ JSON.pretty_generate(rendered_stack)
238
+ end
239
+
240
+ def validate(rendered_stack = nil)
241
+ %w(resources mappings parameters outputs description bytesize).map do |method|
242
+ send("validate_#{method}", rendered_stack)
243
+ end
244
+ end
245
+
246
+ def validate_compare(value, cf_max, error)
247
+ limit_exceeded_error(value, cf_max, error) if value > cf_max
248
+ end
249
+
250
+ def validate_resources(rendered_stack)
251
+ validate_compare(
252
+ rendered_stack['Resources'].count,
253
+ CF_MAX_RESOURCES,
254
+ ExcessiveResourcesError)
255
+
256
+ largest_resource_name = resources.keys.max || ''
257
+ validate_compare(
258
+ largest_resource_name.length,
259
+ CF_MAX_RESOURCE_NAME,
260
+ ExcessiveResourceNameError)
261
+ end
262
+
263
+ def validate_mappings(rendered_stack)
264
+ mappings = rendered_stack ['Mappings']
265
+ validate_compare(
266
+ mappings.count,
267
+ CF_MAX_MAPPINGS,
268
+ ExcessiveMappingsError)
269
+ mappings.each do |_, value|
270
+ validate_compare(
271
+ value.count,
272
+ CF_MAX_MAPPING_ATTRIBUTES,
273
+ ExcessiveMappingAttributesError)
274
+ end
275
+
276
+ mappings.keys.each do |key|
277
+ validate_compare(
278
+ key.length,
279
+ CF_MAX_MAPPING_NAME,
280
+ ExcessiveMappingNameError)
281
+ end
282
+
283
+ ## XXX What are we trying to do here @aburke
284
+ mapping_attributes = mappings.values.flat_map do |inner_hash|
285
+ inner_hash.keys.select do |key|
286
+ value = inner_hash[key]
287
+ end
288
+ end
289
+
290
+ mapping_attributes.each do |attribute|
291
+ validate_compare(
292
+ attribute.length,
293
+ CF_MAX_MAPPING_ATTRIBUTE_NAME,
294
+ ExcessiveMappingAttributeNameError)
295
+ end
296
+ end
297
+
298
+ def validate_parameters(rendered_stack)
299
+ parameters = rendered_stack['Parameters']
300
+ validate_compare(
301
+ parameters.count,
302
+ CF_MAX_PARAMETERS,
303
+ ExcessiveParametersError)
304
+ largest_parameter_name = parameters.keys.max
305
+ largest_parameter_name ||= ''
306
+ validate_compare(
307
+ largest_parameter_name.length,
308
+ CF_MAX_PARAMETER_NAME_CHARACTERS,
309
+ ExcessiveParameterNameError)
310
+ parameters.values.each do |value|
311
+ validate_compare(
312
+ JSON.generate(value).bytesize,
313
+ CF_MAX_PARAMETER_VALUE_BYTESIZE,
314
+ ExcessiveParameterBytesizeError)
315
+ end
316
+ end
317
+
318
+ def validate_outputs(rendered_stack)
319
+ outputs = rendered_stack['Outputs']
320
+ validate_compare(
321
+ outputs.count,
322
+ CF_MAX_OUTPUTS,
323
+ ExcessiveOutputsError)
324
+ largest_output_name = outputs.keys.max
325
+ largest_output_name ||= ''
326
+ validate_compare(
327
+ largest_output_name.length,
328
+ CF_MAX_OUTPUT_NAME_CHARACTERS,
329
+ ExcessiveOutputNameError)
330
+ end
331
+
332
+ def validate_description(rendered_stack)
333
+ validate_compare(
334
+ rendered_stack['Description'].bytesize,
335
+ CF_MAX_DESCRIPTION_BYTESIZE,
336
+ ExcessiveDescriptionError)
337
+ end
338
+
339
+ def validate_bytesize(rendered_stack)
340
+ json = JSON.generate(rendered_stack)
341
+ validate_compare(
342
+ json.bytesize,
343
+ CF_MAX_BYTESIZE,
344
+ ExcessiveTemplateSizeError)
118
345
  end
119
346
  end
120
347
  end
@@ -122,6 +349,6 @@ end
122
349
 
123
350
  require_relative 'template/parameter'
124
351
  require_relative 'template/mapping'
125
- # require_relative 'template/condition'
352
+ require_relative 'template/condition'
126
353
  require_relative 'template/resource'
127
354
  require_relative 'template/output'
@@ -0,0 +1,56 @@
1
+ require_relative '../../dsl/intrinsic_functions'
2
+
3
+ module Convection
4
+ module Model
5
+ class Template
6
+ class Condition
7
+ include DSL::Helpers
8
+
9
+ CONDITIONAL_FUNCTION_SYNTAX_MAP =
10
+ { fn_and: 'Fn::And',
11
+ fn_equals: 'Fn::Equals',
12
+ fn_if: 'Fn::If',
13
+ fn_not: 'Fn::Not',
14
+ fn_or: 'Fn::Or' }
15
+
16
+ attr_reader :condition
17
+ attr_reader :template
18
+
19
+ CONDITIONAL_FUNCTION_SYNTAX_MAP.keys.each do |conditional_function|
20
+ define_method(conditional_function) do |*args|
21
+ @condition = ConditionalFunction.new conditional_function, args
22
+ end
23
+ end
24
+
25
+ def initialize(name, parent)
26
+ @name = name
27
+ @template = parent.template
28
+ end
29
+
30
+ def render
31
+ condition.render
32
+ end
33
+
34
+ class ConditionalFunction
35
+ def initialize(function_name, arg_array)
36
+ @function_name = function_name
37
+ @function_arguments = arg_array
38
+ end
39
+
40
+ def render
41
+ rendered_values = []
42
+ @function_arguments.each do |function_arg|
43
+ if function_arg.respond_to? :render # the argument is another conditional function
44
+ rendered_values << function_arg.render
45
+ else
46
+ rendered_values << function_arg
47
+ end
48
+ end
49
+
50
+ { CONDITIONAL_FUNCTION_SYNTAX_MAP[@function_name] => rendered_values }
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -18,13 +18,14 @@ module Convection
18
18
  # Mapping
19
19
  ##
20
20
  class Mapping
21
- include DSL::IntrinsicFunctions
21
+ include DSL::Helpers
22
22
 
23
23
  attr_reader :items
24
+ attr_reader :template
24
25
 
25
- def initialize(name, template)
26
+ def initialize(name, parent)
26
27
  @name = name
27
- @template = template
28
+ @template = parent.template
28
29
 
29
30
  @items = Smash.new
30
31
  end