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
@@ -1,3 +1,4 @@
1
+ require 'json'
1
2
  require_relative '../../dsl/intrinsic_functions'
2
3
  require_relative '../mixin/conditional'
3
4
 
@@ -8,16 +9,17 @@ module Convection
8
9
  # Resource
9
10
  ##
10
11
  class Output
11
- extend DSL::Helpers
12
- include DSL::IntrinsicFunctions
12
+ include DSL::Helpers
13
13
  include Model::Mixin::Conditional
14
14
 
15
+ attribute :name
15
16
  attribute :value
16
17
  attribute :description
18
+ attr_reader :template
17
19
 
18
- def initialize(name, template)
20
+ def initialize(name, parent)
19
21
  @name = name
20
- @template = template
22
+ @template = parent.template
21
23
 
22
24
  @type = ''
23
25
  @properties = {}
@@ -25,10 +27,10 @@ module Convection
25
27
 
26
28
  def render
27
29
  {
28
- 'Value' => value,
30
+ 'Value' => value.respond_to?(:render) ? value.render : value,
29
31
  'Description' => description
30
- }.tap do |resource|
31
- render_condition(resource)
32
+ }.tap do |output|
33
+ render_condition(output)
32
34
  end
33
35
  end
34
36
  end
@@ -7,17 +7,25 @@ module Convection
7
7
  # Template Parameter
8
8
  ##
9
9
  class Parameter
10
- extend DSL::Helpers
11
- include DSL::IntrinsicFunctions
10
+ include DSL::Helpers
12
11
 
13
12
  attribute :type
14
13
  attribute :default
15
14
  attribute :description
15
+ attr_reader :template
16
+
16
17
  attr_reader :allowed_values
18
+ attribute :allowed_pattern
19
+ attribute :no_echo
20
+ attribute :max_length
21
+ attribute :min_length
22
+ attribute :max_value
23
+ attribute :min_value
24
+ attribute :constraint_description
17
25
 
18
- def initialize(name, template)
26
+ def initialize(name, parent)
19
27
  @name = name
20
- @template = template
28
+ @template = parent.template
21
29
 
22
30
  @type = 'String'
23
31
  @default = ''
@@ -36,6 +44,13 @@ module Convection
36
44
  'Description' => description
37
45
  }.tap do |resource|
38
46
  resource['AllowedValues'] = allowed_values unless allowed_values.empty?
47
+ resource['AllowedPattern'] = allowed_pattern unless allowed_pattern.nil?
48
+ resource['MaxLength'] = max_length unless max_length.nil?
49
+ resource['MinLength'] = min_length unless min_length.nil?
50
+ resource['MaxValue'] = max_value unless max_value.nil?
51
+ resource['MinValue'] = min_value unless min_value.nil?
52
+ resource['NoEcho'] = no_echo unless no_echo.nil?
53
+ resource['ConstraintDescription'] = constraint_description unless constraint_description.nil?
39
54
  end
40
55
  end
41
56
  end
@@ -1,7 +1,10 @@
1
1
  require_relative '../../dsl/intrinsic_functions'
2
2
  require_relative '../mixin/cidr_block'
3
3
  require_relative '../mixin/conditional'
4
+ require_relative '../mixin/policy'
5
+ require_relative '../mixin/protocol'
4
6
  require_relative '../mixin/taggable'
7
+ require_relative './output'
5
8
 
6
9
  module Convection
7
10
  module Model
@@ -10,24 +13,307 @@ module Convection
10
13
  # Resource
11
14
  ##
12
15
  class Resource
13
- extend DSL::Helpers
14
- include DSL::IntrinsicFunctions
15
- include Model::Mixin::Conditional
16
+ class << self
17
+ def properties
18
+ @properties ||= {}
19
+ end
20
+
21
+ def type(cf_type = nil, dsl_name = nil)
22
+ return @type if cf_type.nil?
23
+
24
+ @type = cf_type
25
+ @name = dsl_name || DSL::Helpers.method_name(cf_type)
26
+
27
+ DSL::Template::Resource.attach_resource(@name, self)
28
+ end
29
+
30
+ def property(accesor, property_name, options = {})
31
+ ## Handle usage of old property interface
32
+ options = {}.tap do |o|
33
+ o[:type] = options
34
+ end if options.is_a?(Symbol)
35
+
36
+ properties[accesor] = Property.create(accesor, property_name, options)
37
+ properties[accesor].attach(self)
38
+ end
39
+
40
+ def attach_method(name, &block)
41
+ define_method(name, &block)
42
+ end
43
+ end
44
+
45
+ ##
46
+ # Validation and intraspection for resource properties
47
+ ##
48
+ class Property
49
+ attr_reader :name
50
+ attr_reader :property_name
51
+ attr_reader :default
52
+ attr_reader :transform
53
+
54
+ attr_reader :immutable
55
+ alias_method :immutable?, :immutable
56
+ attr_reader :required
57
+ attr_reader :equal_to
58
+ attr_reader :kind_of
59
+ attr_reader :regex
60
+
61
+ class << self
62
+ ## Switch between Scalar and List
63
+ def create(name, property_name, options = {})
64
+ case options[:type]
65
+ when :string, :scalar, nil then ScalarProperty.new(name, property_name, options)
66
+ when :array, :list then ListProperty.new(name, property_name, options)
67
+ when :hash then HashProperty.new(name, property_name, options)
68
+ else fail TypeError, "Property must be defined with type `string` or `array`, not #{ options[:type] }"
69
+ end
70
+ end
71
+ end
72
+
73
+ def initialize(name, property_name, options = {})
74
+ @name = name
75
+ @property_name = property_name
76
+ @default = options[:default]
77
+ @transform = options.fetch(:transform, []).is_a?(Array) ? options.fetch(:transform, []) : [options[:transform]]
78
+
79
+ @immutable = options[:immutable].is_a?(TrueClass)
80
+ @required = options.fetch(:required, false)
81
+ @equal_to = options.fetch(:equal_to, []).is_a?(Array) ? options.fetch(:equal_to, []) : [options[:equal_to]]
82
+ @kind_of = options.fetch(:kind_of, []).is_a?(Array) ? options.fetch(:kind_of, []) : [options[:kind_of]]
83
+ @regex = options.fetch(:regex, false)
84
+ end
85
+ end
86
+
87
+ ##
88
+ # An instance of a poperty in a resoruce
89
+ ##
90
+ class PropertyInstance
91
+ attr_reader :resource
92
+ attr_reader :property
93
+ attr_reader :value
94
+ attr_accessor :current_value
95
+
96
+ def initialize(resource, property = nil)
97
+ @resource = resource
98
+ @property = property
99
+ end
100
+
101
+ def transform(value)
102
+ return value if property.nil?
103
+ property.transform.inject(value) { |a, e| resource.instance_exec(a, &e) }
104
+ end
105
+
106
+ def validate!(value)
107
+ return value if property.nil?
108
+
109
+ if resource.exist? && property.immutable && current_value != value
110
+ fail ArgumentError,
111
+ "Property #{ property.name } is immutable!"
112
+ end
113
+
114
+ if property.required && value.nil?
115
+ fail ArgumentError,
116
+ "Property #{ property.name } is required!"
117
+ end
118
+
119
+ unless property.equal_to.empty? || property.equal_to.include?(value)
120
+ fail ArgumentError,
121
+ "Property #{ property.name } must be one of #{ property.equal_to.join(', ') }!"
122
+ end
123
+
124
+ unless property.kind_of.empty? || property.kind_of.any? { |t| value.is_a?(t) }
125
+ fail ArgumentError,
126
+ "Property #{ property.name } must be one of #{ property.kind_of.join(', ') }!"
127
+ end
128
+
129
+ unless !property.regex || property.regex.match(value.to_s)
130
+ fail ArgumentError,
131
+ "Property #{ property.name } must match #{ property.regex.inspect }!"
132
+ end
133
+
134
+ value
135
+ end
136
+
137
+ def default
138
+ return if property.nil?
139
+ property.default
140
+ end
141
+
142
+ def current(val)
143
+ @current_value = @value = val
144
+ end
145
+ end
146
+
147
+ ##
148
+ # A Scalar Property
149
+ ##
150
+ class ScalarProperty < Property
151
+ def attach(resource)
152
+ definition = self ## Expose to resource instance closure
153
+
154
+ resource.attach_method(definition.name) do |value = nil|
155
+ return properties[definition.property_name].value if value.nil?
156
+ properties[definition.property_name].set(value)
157
+ end
158
+
159
+ resource.attach_method("#{ definition.name }=") do |value|
160
+ properties[definition.property_name].set(value)
161
+ end
162
+ end
163
+
164
+ def instance(resource)
165
+ ScalarPropertyInstance.new(resource, self)
166
+ end
167
+ end
168
+
169
+ ##
170
+ # Instance of a scalar property
171
+ ##
172
+ class ScalarPropertyInstance < PropertyInstance
173
+ def set(new_value)
174
+ @value = validate!(transform(new_value))
175
+ end
16
176
 
177
+ def render
178
+ return default if value.nil?
179
+ return value.reference if value.is_a?(Resource)
180
+ value.respond_to?(:render) ? value.render : value
181
+ end
182
+ end
183
+
184
+ ##
185
+ # A Hash Property
186
+ ##
187
+ class HashProperty < Property
188
+ def attach(resource)
189
+ definition = self ## Expose to resource instance closure
190
+
191
+ resource.attach_method(definition.name) do |key, value = nil|
192
+ properties[definition.property_name].set(key, value)
193
+ end
194
+ end
195
+
196
+ def instance(resource)
197
+ HashPropertyInstance.new(resource, self)
198
+ end
199
+ end
200
+
201
+ ##
202
+ # Instance of a hash property
203
+ ##
204
+ class HashPropertyInstance < PropertyInstance
205
+ def initialize(*_)
206
+ super
207
+
208
+ @value = {}
209
+ @current_value = {}
210
+ end
211
+
212
+ def set(key, new_value)
213
+ @value[key] = validate!(transform(new_value))
214
+ end
215
+
216
+ def render
217
+ value.keys.each_with_object({}) do |i, memo|
218
+ memo[i] = if value[i].is_a?(Resource)
219
+ value[i].reference
220
+ elsif value[i].respond_to?(:render)
221
+ value[i].render
222
+ else
223
+ value[i]
224
+ end
225
+ end
226
+ end
227
+ end
228
+
229
+ ##
230
+ # A List Property
231
+ ##
232
+ class ListProperty < Property
233
+ def attach(resource)
234
+ definition = self ## Expose to resource instance closure
235
+
236
+ resource.attach_method(definition.name) do |*values|
237
+ properties[definition.property_name].set(values.flatten) unless values.empty?
238
+
239
+ ## Return the list
240
+ properties[definition.property_name].value
241
+ end
242
+ end
243
+
244
+ def instance(resource)
245
+ ListPropertyInstance.new(resource, self)
246
+ end
247
+ end
248
+
249
+ ##
250
+ # Instance of a list property
251
+ ##
252
+ class ListPropertyInstance < PropertyInstance
253
+ def initialize(*_)
254
+ super
255
+
256
+ @value = []
257
+ @current_value = []
258
+ end
259
+
260
+ def set(values)
261
+ values.map! do |new_value|
262
+ validate!(transform(new_value))
263
+ end
264
+
265
+ @value.push(*values)
266
+ end
267
+ alias_method :<<, :set
268
+ alias_method :push, :set
269
+
270
+ def render
271
+ value.map do |val|
272
+ next val.reference if val.is_a?(Resource)
273
+ val.respond_to?(:render) ? val.render : val
274
+ end
275
+ end
276
+ end
277
+
278
+ include DSL::Helpers
279
+ include DSL::Template::Resource
280
+ include Mixin::Conditional
281
+
282
+ ##
283
+ # Resource Instance Methods
284
+ ##
17
285
  attribute :type
18
286
  attr_reader :name
287
+ attr_reader :template
19
288
  attr_reader :properties
289
+ attr_reader :exist
290
+ alias_method :exist?, :exist
20
291
 
21
- def initialize(name, template)
292
+ def initialize(name, parent)
22
293
  @name = name
23
- @template = template
294
+ @template = parent.template
295
+ @type = self.class.type
296
+ @depends_on = []
297
+ @exist = false
24
298
 
25
- @type = ''
26
- @properties = {}
299
+ ## Instantiate properties
300
+ @properties = Model::Collection.new
301
+ resource = self
302
+ resource.class.properties.each do |_, property|
303
+ @properties[property.property_name] = property.instance(resource)
304
+ end
305
+ end
306
+
307
+ def property(key, *value)
308
+ return properties[key].value if value.empty?
309
+
310
+ ## Define a property instance on the fly
311
+ properties[key] = ScalarPropertyInstance.new(self) unless properties.include?(key)
312
+ properties[key].set(*value)
27
313
  end
28
314
 
29
- def property(key, value)
30
- properties[key] = value.is_a?(Model::Template::Resource) ? value.reference : value
315
+ def depends_on(resource)
316
+ @depends_on << (resource.is_a?(Resource) ? resource.name : resource)
31
317
  end
32
318
 
33
319
  def reference
@@ -36,11 +322,28 @@ module Convection
36
322
  }
37
323
  end
38
324
 
325
+ def with_output(output_name = name, value = reference, &block)
326
+ o = Model::Template::Output.new(output_name, @template)
327
+ o.value = value
328
+ o.description = "Resource #{ type }/#{ name }"
329
+
330
+ o.instance_exec(&block) if block
331
+ @template.outputs[output_name] = o
332
+ end
333
+
334
+ def as_attribute(attr_name, attr_type = :string)
335
+ @template.attribute_mappings[name] = {
336
+ :name => attr_name,
337
+ :type => attr_type
338
+ }
339
+ end
340
+
39
341
  def render
40
342
  {
41
343
  'Type' => type,
42
- 'Properties' => properties
344
+ 'Properties' => properties.map(true, &:render)
43
345
  }.tap do |resource|
346
+ resource['DependsOn'] = @depends_on unless @depends_on.empty?
44
347
  render_condition(resource)
45
348
  end
46
349
  end
@@ -49,16 +352,7 @@ module Convection
49
352
  end
50
353
  end
51
354
 
52
- require_relative 'resource/aws_ec2_instance'
53
- require_relative 'resource/aws_ec2_internet_gateway'
54
- require_relative 'resource/aws_ec2_route'
55
- require_relative 'resource/aws_ec2_route_table'
56
- require_relative 'resource/aws_ec2_security_group'
57
- require_relative 'resource/aws_ec2_subnet'
58
- require_relative 'resource/aws_ec2_subnet_route_table_association'
59
- require_relative 'resource/aws_ec2_vpc'
60
- require_relative 'resource/aws_ec2_vpc_gateway_attachment'
61
- require_relative 'resource/aws_s3_bucket'
62
- require_relative 'resource/aws_s3_bucket_policy'
63
- require_relative 'resource/aws_iam_role'
64
- require_relative 'resource/aws_iam_policy'
355
+ ## Require all resources
356
+ Dir.glob(File.expand_path('../resource/*.rb', __FILE__)) do |r|
357
+ require_relative r
358
+ end