active_aws 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-gemset +1 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +32 -0
  5. data/Gemfile.lock +153 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.md +19 -0
  8. data/Rakefile +44 -0
  9. data/VERSION +1 -0
  10. data/active_aws.gemspec +120 -0
  11. data/config/cucumber.yml +2 -0
  12. data/features/cloud_formation/cloud_formation.feature +160 -0
  13. data/features/cloud_formation/dsl/parameterized_initializer.feature +15 -0
  14. data/features/cloud_formation/dsl/properties.feature +2 -0
  15. data/features/cloud_formation/dsl/resource_definition.feature +0 -0
  16. data/features/cloud_formation/templates/ec2_instances.feature +79 -0
  17. data/features/ec2/client/ec2_client.feature +19 -0
  18. data/features/step_definitions/common_steps.rb +79 -0
  19. data/features/step_definitions/ec2_client_steps.rb +7 -0
  20. data/features/support/env.rb +15 -0
  21. data/features/support/vcr.rb +11 -0
  22. data/fixtures/vcr_cassettes/ActiveAws_EC2_client_feature/It_has_availability_zones_.yml +68 -0
  23. data/fixtures/vcr_cassettes/ActiveAws_EC2_client_feature/It_has_instances_.yml +143 -0
  24. data/fixtures/vcr_cassettes/ActiveAws_EC2_client_feature/It_has_regions_.yml +92 -0
  25. data/lib/active_aws.rb +13 -0
  26. data/lib/active_aws/cloud_formation.rb +11 -0
  27. data/lib/active_aws/cloud_formation/template.rb +111 -0
  28. data/lib/active_aws/cloud_formation/template/dsl_block.rb +48 -0
  29. data/lib/active_aws/cloud_formation/template/mappings.rb +22 -0
  30. data/lib/active_aws/cloud_formation/template/parameter.rb +50 -0
  31. data/lib/active_aws/cloud_formation/template/properties.rb +80 -0
  32. data/lib/active_aws/cloud_formation/template/resource.rb +218 -0
  33. data/lib/active_aws/cloud_formation/template/resource/auto_scaling_group.rb +11 -0
  34. data/lib/active_aws/cloud_formation/template/resource/ec2_instance.rb +21 -0
  35. data/lib/active_aws/cloud_formation/template/resource/launch_config.rb +11 -0
  36. data/lib/active_aws/cloud_formation/template/resource/load_balancer.rb +40 -0
  37. data/lib/active_aws/cloud_formation/template/resource/metadata.rb +11 -0
  38. data/lib/active_aws/ec2.rb +27 -0
  39. data/lib/active_aws/parameterized_initializer.rb +15 -0
  40. metadata +294 -0
@@ -0,0 +1,92 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://ec2.ap-southeast-1.amazonaws.com/
6
+ body:
7
+ encoding: UTF-8
8
+ string: Action=DescribeRegions&Version=2015-04-15
9
+ headers:
10
+ Content-Type:
11
+ - application/x-www-form-urlencoded; charset=utf-8
12
+ Accept-Encoding:
13
+ - ''
14
+ User-Agent:
15
+ - aws-sdk-ruby2/2.0.43 ruby/2.2.2 x86_64-darwin14
16
+ X-Amz-Date:
17
+ - 20150607T012352Z
18
+ Host:
19
+ - ec2.ap-southeast-1.amazonaws.com
20
+ X-Amz-Content-Sha256:
21
+ - edf9b7000580977b273a959c1e9a3cd5eb9ca9254b5013800d6b27423d480047
22
+ Authorization:
23
+ - AWS4-HMAC-SHA256 Credential=AKIAIO3PFOLM7KKTQZJQ/20150607/ap-southeast-1/ec2/aws4_request,
24
+ SignedHeaders=content-type;host;user-agent;x-amz-content-sha256;x-amz-date,
25
+ Signature=5e4c4df11e86b9a5404bbee684d884c890c2be700ed74454573b1581e3e1d853
26
+ Content-Length:
27
+ - '41'
28
+ Accept:
29
+ - "*/*"
30
+ response:
31
+ status:
32
+ code: 200
33
+ message: OK
34
+ headers:
35
+ Content-Type:
36
+ - text/xml;charset=UTF-8
37
+ Transfer-Encoding:
38
+ - chunked
39
+ Vary:
40
+ - Accept-Encoding
41
+ Date:
42
+ - Sun, 07 Jun 2015 01:23:54 GMT
43
+ Server:
44
+ - AmazonEC2
45
+ body:
46
+ encoding: UTF-8
47
+ string: |-
48
+ <?xml version="1.0" encoding="UTF-8"?>
49
+ <DescribeRegionsResponse xmlns="http://ec2.amazonaws.com/doc/2015-04-15/">
50
+ <requestId>83b5ad40-91e1-4fab-9574-eab4a819a8ef</requestId>
51
+ <regionInfo>
52
+ <item>
53
+ <regionName>eu-central-1</regionName>
54
+ <regionEndpoint>ec2.eu-central-1.amazonaws.com</regionEndpoint>
55
+ </item>
56
+ <item>
57
+ <regionName>sa-east-1</regionName>
58
+ <regionEndpoint>ec2.sa-east-1.amazonaws.com</regionEndpoint>
59
+ </item>
60
+ <item>
61
+ <regionName>ap-northeast-1</regionName>
62
+ <regionEndpoint>ec2.ap-northeast-1.amazonaws.com</regionEndpoint>
63
+ </item>
64
+ <item>
65
+ <regionName>eu-west-1</regionName>
66
+ <regionEndpoint>ec2.eu-west-1.amazonaws.com</regionEndpoint>
67
+ </item>
68
+ <item>
69
+ <regionName>us-east-1</regionName>
70
+ <regionEndpoint>ec2.us-east-1.amazonaws.com</regionEndpoint>
71
+ </item>
72
+ <item>
73
+ <regionName>us-west-1</regionName>
74
+ <regionEndpoint>ec2.us-west-1.amazonaws.com</regionEndpoint>
75
+ </item>
76
+ <item>
77
+ <regionName>us-west-2</regionName>
78
+ <regionEndpoint>ec2.us-west-2.amazonaws.com</regionEndpoint>
79
+ </item>
80
+ <item>
81
+ <regionName>ap-southeast-2</regionName>
82
+ <regionEndpoint>ec2.ap-southeast-2.amazonaws.com</regionEndpoint>
83
+ </item>
84
+ <item>
85
+ <regionName>ap-southeast-1</regionName>
86
+ <regionEndpoint>ec2.ap-southeast-1.amazonaws.com</regionEndpoint>
87
+ </item>
88
+ </regionInfo>
89
+ </DescribeRegionsResponse>
90
+ http_version:
91
+ recorded_at: Sun, 07 Jun 2015 01:23:55 GMT
92
+ recorded_with: VCR 2.9.3
data/lib/active_aws.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'active_support/dependencies/autoload'
2
+ require 'active_support/deprecation'
3
+ require 'active_support/core_ext'
4
+
5
+ require 'aws-sdk'
6
+
7
+ module ActiveAws
8
+
9
+ autoload :ParameterizedInitializer, 'active_aws/parameterized_initializer'
10
+ autoload :CloudFormation, 'active_aws/cloud_formation'
11
+ autoload :EC2, 'active_aws/ec2'
12
+
13
+ end
@@ -0,0 +1,11 @@
1
+ module ActiveAws
2
+ module CloudFormation
3
+ autoload :Template, 'active_aws/cloud_formation/template'
4
+
5
+ class << self
6
+ def template(params = {}, &block)
7
+ Template.new(params, &block)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,111 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/json'
3
+
4
+ require 'active_aws/parameterized_initializer'
5
+ require 'active_aws/cloud_formation/template/properties'
6
+
7
+ module ActiveAws
8
+ module CloudFormation
9
+ class Template
10
+ include ActiveAws::ParameterizedInitializer
11
+
12
+ DEFAULT_AWS_TEMPLATE_FORMAT_VERSION = '2010-09-09'
13
+
14
+ autoload :DSLBlock, 'active_aws/cloud_formation/template/dsl_block'
15
+ autoload :Parameter, 'active_aws/cloud_formation/template/parameter'
16
+ autoload :Mappings, 'active_aws/cloud_formation/template/mappings'
17
+ autoload :Resource, 'active_aws/cloud_formation/template/resource'
18
+
19
+ module Referrer
20
+ # You can also just use Ref: 'Name' directly
21
+ def ref(to)
22
+ { Ref: to }
23
+ end
24
+ end
25
+
26
+ attr_reader :aws_template_format_version, :resources, :outputs
27
+ attr_accessor :description
28
+
29
+ def initialize(params = {}, &block)
30
+ super
31
+ DSL.new(self).instance_eval(&block) if block_given?
32
+ end
33
+
34
+ def parameters(&block)
35
+ @parameters ||= Parameter::Collection.new
36
+ @parameters.instance_eval(&block) if block_given?
37
+ @parameters
38
+ end
39
+
40
+ def mappings(&block)
41
+ @mappings ||= Mappings.new
42
+ @mappings.instance_eval(&block) if block_given?
43
+ @mappings
44
+ end
45
+
46
+ def resources(&block)
47
+ @resources ||= Resource::Collection.new
48
+ @resources.instance_eval(&block) if block_given?
49
+ @resources
50
+ end
51
+
52
+ require 'delegate'
53
+
54
+ # Where the magic inside a Template.new {} block happens
55
+ class DSL < DelegateClass(Template)
56
+ attr_accessor :template
57
+ def initialize(t)
58
+ super(template = t)
59
+ end
60
+ def description(description)
61
+ self.description = description
62
+ end
63
+ def parameter(name, type, options = {})
64
+ parameters.add(name, type, options)
65
+ end
66
+ def mapping(*args)
67
+ mappings.map(*args)
68
+ end
69
+ end
70
+
71
+ def to_h
72
+ {'AWSTemplateFormatVersion' => DEFAULT_AWS_TEMPLATE_FORMAT_VERSION}.tap {|h|
73
+ h['Description'] = @description unless @description.nil?
74
+ [:parameters, :mappings, :resources].each {|sym|
75
+ v = instance_variable_get("@#{sym}")
76
+ h[sym.to_s.capitalize] = v.to_h unless v.nil? || v.empty?
77
+ }
78
+ }
79
+ end
80
+
81
+ def to_json(*args)
82
+ pretty = args.include?(:pretty)
83
+ pretty ? JSON.pretty_generate(to_h) : to_h.to_json
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ # Here we get clever
90
+ module Fn
91
+ class << self
92
+ def Base64(s)
93
+ { 'Fn::Base64' => s }
94
+ end
95
+ def FindInMap(map_name, top_level_key, second_level_key)
96
+ { 'Fn::FindInMap' => [ map_name, top_level_key, second_level_key ] }
97
+ end
98
+ def GetAtt(resource_name, attr_name)
99
+ { 'Fn::GetAtt' => [ resource_name, attr_name ] }
100
+ end
101
+ def GetAZs(region)
102
+ { 'Fn::GetAZs' => region }
103
+ end
104
+ def Join(delim, *args)
105
+ { 'Fn::Join' => [ delim, args ] }
106
+ end
107
+ def Select(index, list)
108
+ { 'Fn::Select' => [ index, list ] }
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,48 @@
1
+ require 'ostruct'
2
+
3
+ module ActiveAws
4
+ module CloudFormation
5
+ class Template
6
+
7
+ # Returns a block that acts as a facade for the given target, allowing
8
+ # calls of the form
9
+ #
10
+ # attribute 'value'
11
+ #
12
+ # and transforms them into the equivalent
13
+ #
14
+ # @target.attribute = 'value'
15
+ class DSLBlock
16
+ include Referrer
17
+
18
+ attr_reader :target
19
+ def initialize(target)
20
+ @target = target
21
+ end
22
+
23
+ def method_missing(method_name, *args)
24
+ if args.size == 0 && (target.is_a?(OpenStruct) || @target.respond_to?(method_name))
25
+ return @target.send(method_name)
26
+ elsif args.size == 1
27
+ if @target.is_a?(Hash)
28
+ return @target.store(method_name, args.first)
29
+ else
30
+ setter = "#{method_name}=".to_sym
31
+ if @target.is_a?(OpenStruct) || @target.respond_to?(setter)
32
+ return @target.send(setter, args.first)
33
+ end
34
+ end
35
+ end
36
+ # fall back to super
37
+ super
38
+ end
39
+
40
+ class << self
41
+ def eval_using(target, block)
42
+ DSLBlock.new(target).instance_eval(&block)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,22 @@
1
+ module ActiveAws
2
+ module CloudFormation
3
+ class Template
4
+ class Mappings < DelegateClass(Hash)
5
+ def initialize
6
+ super(@mappings = {})
7
+ end
8
+
9
+ def map(name, mapping = {}, &block)
10
+ raise 'Mappings only accepts a Hash as values' unless mapping.is_a? Hash
11
+ @mappings[name] = mapping
12
+ yield mapping if block_given?
13
+ mapping
14
+ end
15
+
16
+ def to_h
17
+ {'Mappings' => @mappings}
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,50 @@
1
+ module ActiveAws
2
+ module CloudFormation
3
+ class Template
4
+ class Parameter
5
+ include ActiveAws::ParameterizedInitializer
6
+
7
+ TYPES = [:string, :number, :list]
8
+ TYPES_MAP = {string: 'String', number: 'Number', list: 'CommaDelimitedList'}
9
+
10
+ attr_accessor :name, :type
11
+
12
+ FIELDS_MAP = [:description, :default, :no_echo, :allowed_values, :allowed_pattern,
13
+ :min_length, :max_length, :min_value, :max_value, :constraint_description].each_with_object({}) {|s, h|
14
+ attr_accessor s
15
+ h[s] = s.to_s.camelize
16
+ }
17
+
18
+ def to_h
19
+ FIELDS_MAP.each_with_object('Type' => TYPES_MAP[type]) do |(method, key), h|
20
+ if v = self.send(method)
21
+ h[key] = v
22
+ end
23
+ end
24
+ end
25
+
26
+ class Collection < Array
27
+ def add(name, type, options = {})
28
+ Parameter.new(options.merge({name: name, type: type})).tap { |parameter| push parameter }
29
+ end
30
+
31
+ # define helper methods 'string()', 'number()', 'list()'
32
+ Parameter::TYPES.each {|type|
33
+ module_eval <<-EOF
34
+ def #{type}(name, options = {}, &block)
35
+ parameter = add(name, :#{type}, options)
36
+ DSLBlock.eval_using(parameter, block) if block_given?
37
+ parameter
38
+ end
39
+ EOF
40
+ }
41
+
42
+ def to_h
43
+ each_with_object({}) {|parameter, h| h[parameter.name] = parameter.to_h }
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,80 @@
1
+ require 'active_support/concern'
2
+
3
+ module ActiveAws
4
+ module CloudFormation
5
+ class Template
6
+
7
+ class Properties < Hash
8
+ def add(name, value)
9
+ store(name, value)
10
+ end
11
+ def method_missing(method_name, *args, &block)
12
+ if has_key?(method_name)
13
+ fetch(method_name)
14
+ else
15
+ super
16
+ end
17
+ end
18
+ end
19
+
20
+ # See http://ruby-doc.org/core-2.2.0/Struct.html
21
+ PropertyDefinition = Struct.new :name, :type
22
+ class PropertyDefinition
23
+ class Collection < Array
24
+ def add(name, type)
25
+ puts "PropertyDefinition::Collection.add(#{name},#{type})"
26
+ self.push(PropertyDefinition.new(name,type))
27
+ end
28
+ end
29
+ end
30
+
31
+ # The basis of almost all CloudFormation resources and embedded properties
32
+ module HasProperties
33
+ extend ActiveSupport::Concern
34
+
35
+ PRIMITIVE_PROPERTY_TYPES = %i(string boolean integer)
36
+
37
+ included do
38
+ end
39
+
40
+ class_methods do
41
+ def properties(*args, &block)
42
+ puts("properties(#{args})")
43
+ if args.empty?
44
+ @@properties ||= PropertyDefinition::Collection.new
45
+ puts("@@properties => #{@@properties || 'nil'}")
46
+ else
47
+ if args.first.is_a?(Hash)
48
+
49
+ end
50
+ end
51
+ if block_given?
52
+ DSL.new(self).instance_eval(&block)
53
+ end
54
+ @@properties
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ class DSL
61
+ def initialize(base_class)
62
+ puts("DSL.initialize(#{base_class})")
63
+ @base_class = base_class
64
+ end
65
+ def string(name)
66
+ puts("DSL.string(#{name})")
67
+ @base_class.properties.add name, :string
68
+ @base_class.send(:define_method, "#{name}=") { |value| instance_variable_set("@#{name}", value)}
69
+ @base_class.send(:define_method, name) { instance_variable_get("@#{name}")}
70
+ end
71
+ end
72
+ end
73
+
74
+ class OpenStructWithProperties < OpenStruct
75
+ include HasProperties
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,218 @@
1
+ require 'active_support/core_ext' # for Hash#except
2
+
3
+ require 'active_aws/cloud_formation/template/properties'
4
+
5
+ module ActiveAws
6
+ module CloudFormation
7
+ class Template
8
+ class Resource
9
+ include Referrer
10
+
11
+ # These helpers have to come before the autoload below, otherwise they won't be seen by subclasses
12
+ class << self
13
+
14
+ def type(type)
15
+ end
16
+
17
+ # Allows us to do:
18
+ #
19
+ # class LoadBalancer < Resource
20
+ # array_attr :availability_zones
21
+ #
22
+ # Then to go:
23
+ #
24
+ # load_balancer 'LoadBalancer' do
25
+ # availability_zones 'ap-southeast-1a', 'ap-southeast-1b'
26
+ #
27
+ # Or, alternatively
28
+ #
29
+ # load_balancer 'LoadBalancer' do
30
+ # availability_zone 'ap-southeast-1a'
31
+ # availability_zone 'ap-southeast-1b'
32
+ def array_attr(*names)
33
+ names.each {|name|
34
+ define_method(name) do |*args|
35
+ if properties.key?(name)
36
+ properties.store(name, properties.fetch(name).concat(args))
37
+ else
38
+ properties.store(name, args)
39
+ end
40
+ end
41
+ define_method(name.to_s.singularize) do |arg|
42
+ if properties.key?(name)
43
+ properties.fetch(name) << arg
44
+ else
45
+ properties.store(name, [arg])
46
+ end
47
+ end
48
+ }
49
+ end
50
+ end
51
+
52
+ # auto-autoload, duh
53
+ Dir[File.expand_path('resource/*.rb', File.dirname(__FILE__))].each {|f|
54
+ basename = File.basename(f).chomp('.rb')
55
+ autoload basename.classify.to_sym, "active_aws/cloud_formation/template/resource/#{basename}"
56
+ }
57
+
58
+ attr_accessor :name, :type
59
+
60
+ METHOD_TYPES_MAP = {
61
+ auto_scaling_group: ['AWS::AutoScaling::AutoScalingGroup', AutoScalingGroup],
62
+ launch_config: ['AWS::AutoScaling::LaunchConfiguration', LaunchConfig],
63
+ ec2_instance: ['AWS::EC2::Instance', Ec2Instance ],
64
+ ec2_security_group: 'AWS::EC2::SecurityGroup',
65
+ load_balancer: ['AWS::ElasticLoadBalancing::LoadBalancer', LoadBalancer],
66
+ route53_record_set: 'AWS::Route53::RecordSet',
67
+ sqs_queue: 'AWS::SQS::Queue'
68
+ }
69
+ TYPES = METHOD_TYPES_MAP.map {|k, v|
70
+ [v].flatten.first # short for v.is_a?(Array) ? v[0] : v
71
+ }
72
+
73
+ private_class_method :new
74
+
75
+ class << self
76
+
77
+ def build(params = {})
78
+ name = params[:name]
79
+ type = params[:type]
80
+ raise 'Resource name cannot be blank or empty' if name.blank?
81
+ raise "Resource name '#{name}' is non alphanumeric" unless name =~ /^[[:alnum:]]+$/
82
+ raise 'Resource type cannot be black or empty' if type.blank?
83
+ raise "Resource type '#{type}' unknown or not yet handled" unless TYPES.include?(type)
84
+ if arr = METHOD_TYPES_MAP.select {|k, v| v.is_a? Array }.find {|k, (type_name, class_ref)| type_name == type }
85
+ method_name, (type_name, class_ref) = arr
86
+ # At this point, LoadBalancer.new is also private
87
+ class_ref.send(:new, params)
88
+ else
89
+ # We can call new instead of Resource.send(:new, params) since we *are* Resource
90
+ new(params)
91
+ end
92
+ end
93
+
94
+ end
95
+
96
+ def initialize(params = {})
97
+ @name = params[:name] if params[:name]
98
+ @type = params[:type] if params[:type]
99
+ raise 'Resource name cannot be blank or empty' if @name.blank?
100
+ raise "Resource name '#{@name}' is non alphanumeric" unless @name =~ /^[[:alnum:]]+$/
101
+ raise 'Resource type cannot be black or empty' if @type.blank?
102
+ raise "Resource type '#{@type}' unknown or not yet handled" unless TYPES.include?(@type)
103
+ props = params.except(:name, :type)
104
+ unless props.empty?
105
+ @properties = Properties.new
106
+ @properties.merge!(props)
107
+ end
108
+ add_validations_based_on_type
109
+ end
110
+
111
+ def properties(&block)
112
+ @properties ||= Properties.new
113
+ @properties.instance_eval(&block) if block_given?
114
+ @properties
115
+ end
116
+
117
+ def to_h
118
+ h = { 'Type' => self.type }
119
+ h['Properties'] = camelize_keys(@properties) unless @properties.nil? || @properties.empty?
120
+ h
121
+ end
122
+
123
+ def camelize_keys(hash)
124
+ hash.each_with_object({}) {|(k, v), h|
125
+ key = k.is_a?(Symbol) ? k.to_s.camelize : k
126
+ h[key] = case
127
+ when v.is_a?(Hash)
128
+ camelize_keys(v)
129
+ when v.is_a?(Array)
130
+ v.map {|e| e.is_a?(Hash) ? camelize_keys(e) : e }
131
+ else
132
+ v
133
+ end
134
+ }
135
+ end
136
+
137
+ def method_missing(method_name, *args, &block)
138
+ if args.size == 1
139
+ properties.store(method_name, args.first)
140
+ elsif block_given?
141
+ DSLBlock.eval_using(properties, block) if block_given?
142
+ else
143
+ super
144
+ end
145
+
146
+ end
147
+
148
+ # A Resource::Collection provides some convenience methods over a standard Array
149
+ class Collection < Array
150
+ def add(name, type, props = {}, &block)
151
+ Resource.build(name: name, type: type).tap { |resource|
152
+ resource.properties.merge! props
153
+ DSLBlock.eval_using(resource.properties, block) if block_given?
154
+ push resource
155
+ }
156
+ end
157
+ alias_method :resource, :add
158
+
159
+ # Handle this DSL method specifically
160
+ def load_balancer(name, options = {}, &block)
161
+ resource = add(name, 'AWS::ElasticLoadBalancing::LoadBalancer', options)
162
+ resource.instance_eval(&block) if block_given?
163
+ resource
164
+ end
165
+
166
+ METHOD_TYPES_MAP.each {|method, v|
167
+ unless instance_methods.include?(method)
168
+ if v.is_a?(Array)
169
+ type = [v].flatten.first # short for v.is_a?(Array) ? v[0] : v
170
+ module_eval <<-EOF
171
+ def #{method}(name, options = {}, &block)
172
+ resource = add(name, '#{type}', options)
173
+ resource.instance_eval(&block) if block_given?
174
+ resource
175
+ end
176
+ EOF
177
+ else
178
+ type = v
179
+ # We can't use define_method because it doesn't support blocks
180
+ module_eval <<-EOF
181
+ def #{method}(name, options = {}, &block)
182
+ resource = add(name, '#{type}', options)
183
+ DSLBlock.eval_using(resource.properties, block) if block_given?
184
+ resource
185
+ end
186
+ EOF
187
+ end
188
+ end
189
+ }
190
+
191
+ def to_h
192
+ each_with_object({}) {|parameter, h| h[parameter.name] = parameter.to_h }
193
+ end
194
+ end # Collection
195
+
196
+ # Resource
197
+ private
198
+
199
+ def validations
200
+ @validations ||= Hash.new
201
+ end
202
+
203
+ def validate(property, &block)
204
+ validations[:property] = block
205
+ end
206
+
207
+ def add_validations_based_on_type
208
+ case @type
209
+ when 'AWS::ElasticLoadBalancing::LoadBalancer'
210
+ validate :listeners do
211
+ raise "Resource '#{@name}' (AWS::ElasticLoadBalancing::LoadBalancer) Listeners cannot be empty" if listeners.empty?
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end