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