active_aws 0.1.0

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.
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