active_aws 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +32 -0
- data/Gemfile.lock +153 -0
- data/LICENSE.txt +20 -0
- data/README.md +19 -0
- data/Rakefile +44 -0
- data/VERSION +1 -0
- data/active_aws.gemspec +120 -0
- data/config/cucumber.yml +2 -0
- data/features/cloud_formation/cloud_formation.feature +160 -0
- data/features/cloud_formation/dsl/parameterized_initializer.feature +15 -0
- data/features/cloud_formation/dsl/properties.feature +2 -0
- data/features/cloud_formation/dsl/resource_definition.feature +0 -0
- data/features/cloud_formation/templates/ec2_instances.feature +79 -0
- data/features/ec2/client/ec2_client.feature +19 -0
- data/features/step_definitions/common_steps.rb +79 -0
- data/features/step_definitions/ec2_client_steps.rb +7 -0
- data/features/support/env.rb +15 -0
- data/features/support/vcr.rb +11 -0
- data/fixtures/vcr_cassettes/ActiveAws_EC2_client_feature/It_has_availability_zones_.yml +68 -0
- data/fixtures/vcr_cassettes/ActiveAws_EC2_client_feature/It_has_instances_.yml +143 -0
- data/fixtures/vcr_cassettes/ActiveAws_EC2_client_feature/It_has_regions_.yml +92 -0
- data/lib/active_aws.rb +13 -0
- data/lib/active_aws/cloud_formation.rb +11 -0
- data/lib/active_aws/cloud_formation/template.rb +111 -0
- data/lib/active_aws/cloud_formation/template/dsl_block.rb +48 -0
- data/lib/active_aws/cloud_formation/template/mappings.rb +22 -0
- data/lib/active_aws/cloud_formation/template/parameter.rb +50 -0
- data/lib/active_aws/cloud_formation/template/properties.rb +80 -0
- data/lib/active_aws/cloud_formation/template/resource.rb +218 -0
- data/lib/active_aws/cloud_formation/template/resource/auto_scaling_group.rb +11 -0
- data/lib/active_aws/cloud_formation/template/resource/ec2_instance.rb +21 -0
- data/lib/active_aws/cloud_formation/template/resource/launch_config.rb +11 -0
- data/lib/active_aws/cloud_formation/template/resource/load_balancer.rb +40 -0
- data/lib/active_aws/cloud_formation/template/resource/metadata.rb +11 -0
- data/lib/active_aws/ec2.rb +27 -0
- data/lib/active_aws/parameterized_initializer.rb +15 -0
- 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,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
|