convection 0.0.1 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +26 -8
- data/.rubocop_todo.yml +77 -0
- data/.ruby-version +1 -0
- data/.travis.yml +10 -0
- data/Gemfile +9 -0
- data/README.md +27 -2
- data/Rakefile +11 -1
- data/bin/convection +49 -0
- data/convection.gemspec +5 -7
- data/example/.ruby-version +1 -0
- data/example/Cloudfile +13 -0
- data/example/deprecated/elb.rb +27 -0
- data/example/deprecated/iam_access_key.rb +18 -0
- data/example/deprecated/iam_group.rb +31 -0
- data/example/{iam_role.rb → deprecated/iam_role.rb} +21 -32
- data/example/deprecated/iam_user.rb +31 -0
- data/example/deprecated/rds.rb +70 -0
- data/example/{s3.rb → deprecated/s3.rb} +0 -0
- data/example/deprecated/sqs.rb +32 -0
- data/example/deprecated/vpc.rb +85 -0
- data/example/foobar.rb +22 -0
- data/example/output/vpc.json +335 -0
- data/example/security-groups.rb +40 -0
- data/example/trust_cloudtrail.rb +24 -0
- data/example/vpc.rb +63 -81
- data/ext/resource_generator.sh +21 -0
- data/lib/convection.rb +5 -4
- data/lib/convection/control/cloud.rb +59 -0
- data/lib/convection/control/stack.rb +261 -60
- data/lib/convection/dsl/helpers.rb +63 -5
- data/lib/convection/model/attributes.rb +60 -0
- data/lib/convection/model/cloudfile.rb +58 -0
- data/lib/convection/model/diff.rb +39 -0
- data/lib/convection/model/event.rb +62 -0
- data/lib/convection/model/exceptions.rb +18 -0
- data/lib/convection/model/mixin/cidr_block.rb +4 -4
- data/lib/convection/model/mixin/colorize.rb +20 -0
- data/lib/convection/model/mixin/conditional.rb +1 -3
- data/lib/convection/model/mixin/policy.rb +89 -0
- data/lib/convection/model/mixin/protocol.rb +29 -0
- data/lib/convection/model/mixin/taggable.rb +2 -2
- data/lib/convection/model/template.rb +248 -21
- data/lib/convection/model/template/condition.rb +56 -0
- data/lib/convection/model/template/mapping.rb +4 -3
- data/lib/convection/model/template/output.rb +9 -7
- data/lib/convection/model/template/parameter.rb +19 -4
- data/lib/convection/model/template/resource.rb +317 -23
- data/lib/convection/model/template/resource/aws_auto_scaling_auto_scaling_group.rb +39 -0
- data/lib/convection/model/template/resource/aws_auto_scaling_launch_configuration.rb +30 -0
- data/lib/convection/model/template/resource/aws_auto_scaling_scaling_policy.rb +20 -0
- data/lib/convection/model/template/resource/aws_cloud_watch_alarm.rb +31 -0
- data/lib/convection/model/template/resource/aws_ec2_instance.rb +10 -46
- data/lib/convection/model/template/resource/aws_ec2_internet_gateway.rb +3 -14
- data/lib/convection/model/template/resource/aws_ec2_network_acl.rb +45 -0
- data/lib/convection/model/template/resource/aws_ec2_network_acl_entry.rb +27 -0
- data/lib/convection/model/template/resource/aws_ec2_route.rb +7 -40
- data/lib/convection/model/template/resource/aws_ec2_route_table.rb +2 -17
- data/lib/convection/model/template/resource/aws_ec2_security_group.rb +24 -30
- data/lib/convection/model/template/resource/aws_ec2_security_group_ingres.rb +25 -0
- data/lib/convection/model/template/resource/aws_ec2_subnet.rb +21 -28
- data/lib/convection/model/template/resource/aws_ec2_subnet_network_acl_association.rb +18 -0
- data/lib/convection/model/template/resource/aws_ec2_subnet_route_table_association.rb +3 -24
- data/lib/convection/model/template/resource/aws_ec2_vpc.rb +20 -22
- data/lib/convection/model/template/resource/aws_ec2_vpc_gateway_attachment.rb +4 -28
- data/lib/convection/model/template/resource/aws_elasticache_cluster.rb +24 -0
- data/lib/convection/model/template/resource/aws_elasticache_parameter_group.rb +19 -0
- data/lib/convection/model/template/resource/aws_elasticache_security_group.rb +17 -0
- data/lib/convection/model/template/resource/aws_elasticache_security_group_ingress.rb +19 -0
- data/lib/convection/model/template/resource/aws_elb.rb +39 -0
- data/lib/convection/model/template/resource/aws_iam_access_key.rb +19 -0
- data/lib/convection/model/template/resource/aws_iam_group.rb +18 -0
- data/lib/convection/model/template/resource/aws_iam_instance_profile.rb +21 -0
- data/lib/convection/model/template/resource/aws_iam_policy.rb +28 -24
- data/lib/convection/model/template/resource/aws_iam_role.rb +88 -19
- data/lib/convection/model/template/resource/aws_iam_user.rb +53 -0
- data/lib/convection/model/template/resource/aws_logs_loggroup.rb +33 -0
- data/lib/convection/model/template/resource/aws_rds_db_instance.rb +59 -0
- data/lib/convection/model/template/resource/aws_rds_db_parameter_group.rb +27 -0
- data/lib/convection/model/template/resource/aws_rds_db_security_group.rb +40 -0
- data/lib/convection/model/template/resource/aws_rds_db_subnet_group.rb +26 -0
- data/lib/convection/model/template/resource/aws_route53_health_check.rb +17 -0
- data/lib/convection/model/template/resource/aws_route53_recordset.rb +30 -0
- data/lib/convection/model/template/resource/aws_s3_bucket.rb +8 -44
- data/lib/convection/model/template/resource/aws_s3_bucket_policy.rb +14 -19
- data/lib/convection/model/template/resource/aws_sns_topic.rb +19 -0
- data/lib/convection/model/template/resource/aws_sqs_queue.rb +31 -0
- data/lib/convection/model/template/resource/aws_sqs_queue_policy.rb +18 -0
- data/test/convection/model/test_conditions.rb +121 -0
- data/test/convection/model/test_elasticache.rb +97 -0
- data/test/convection/model/test_loggroups.rb +25 -0
- data/test/convection/model/test_rds.rb +76 -0
- data/test/convection/model/test_template.rb +64 -0
- data/test/convection/model/test_validation.rb +216 -0
- data/test/test_helper.rb +17 -0
- metadata +131 -50
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative '../lib/convection'
|
2
|
+
|
3
|
+
module Convection
|
4
|
+
module Demo
|
5
|
+
SECURITY_GROUPS = Convection.template do
|
6
|
+
description 'Demo Security Groups'
|
7
|
+
|
8
|
+
ec2_security_group 'FoobarELB' do
|
9
|
+
vpc stack.get('vpc', 'id')
|
10
|
+
description 'Foobar ELB Ingress'
|
11
|
+
|
12
|
+
ingress_rule(:tcp, 80, '0.0.0.0/0')
|
13
|
+
ingress_rule(:tcp, 443, '0.0.0.0/0')
|
14
|
+
|
15
|
+
tag 'Name', "sg-foobar-elb-#{ stack.cloud }"
|
16
|
+
tag 'Service', 'foobar'
|
17
|
+
tag 'Resource', 'ELB'
|
18
|
+
tag 'Scope', 'public'
|
19
|
+
tag 'Stack', stack.cloud
|
20
|
+
|
21
|
+
with_output
|
22
|
+
end
|
23
|
+
|
24
|
+
ec2_security_group 'Foobar' do
|
25
|
+
vpc stack.get('vpc', 'id')
|
26
|
+
description 'Foobar Ingress'
|
27
|
+
|
28
|
+
ingress_rule(:tcp, 8080) { source_group fn_ref('FoobarELB') }
|
29
|
+
|
30
|
+
tag 'Name', "sg-foobar-#{ stack.cloud }"
|
31
|
+
tag 'Service', 'foobar'
|
32
|
+
tag 'Resource', 'EC2'
|
33
|
+
tag 'Scope', 'private'
|
34
|
+
tag 'Stack', stack.cloud
|
35
|
+
|
36
|
+
with_output
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'convection'
|
2
|
+
|
3
|
+
module CLOUDTRAIL
|
4
|
+
#IAM role to create a log stream & put events
|
5
|
+
iam_role 'role' do
|
6
|
+
path "/"
|
7
|
+
#defines trust relationship
|
8
|
+
trust_cloudtrail
|
9
|
+
|
10
|
+
policy 'CreateStreamPolicy' do
|
11
|
+
allow do
|
12
|
+
resource 'arn:aws:logs:*:*:*'
|
13
|
+
action 'logs:CreateLogStream'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
policy 'PutEventsPolicy' do
|
18
|
+
allow do
|
19
|
+
resource 'arn:aws:logs:*:*:*'
|
20
|
+
action 'logs:PutLogEvents'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/example/vpc.rb
CHANGED
@@ -1,85 +1,67 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
1
|
+
require_relative '../lib/convection'
|
2
|
+
|
3
|
+
module Convection
|
4
|
+
module Demo
|
5
|
+
VPC = Convection.template do
|
6
|
+
description 'Demo VPC'
|
7
|
+
|
8
|
+
## Define the VPC
|
9
|
+
ec2_vpc 'TargetVPC' do
|
10
|
+
network stack['subnet']
|
11
|
+
subnet_length 24
|
12
|
+
enable_dns true
|
13
|
+
|
14
|
+
tag 'Name', stack.cloud
|
15
|
+
tag 'Stack', stack.cloud
|
16
|
+
with_output 'id'
|
17
|
+
|
18
|
+
## Add an InternetGateway
|
19
|
+
add_internet_gateway
|
20
|
+
|
21
|
+
public_acl = add_network_acl 'Public' do
|
22
|
+
entry 'AllowAllIngress' do
|
23
|
+
action 'allow'
|
24
|
+
number 100
|
25
|
+
network '0.0.0.0/0'
|
26
|
+
protocol :any
|
27
|
+
range :From => 0,
|
28
|
+
:To => 65_535
|
29
|
+
end
|
30
|
+
|
31
|
+
entry 'AllowAllEgress' do
|
32
|
+
action 'allow'
|
33
|
+
number 100
|
34
|
+
egress true
|
35
|
+
network '0.0.0.0/0'
|
36
|
+
protocol :any
|
37
|
+
range :From => 0,
|
38
|
+
:To => 65_535
|
39
|
+
end
|
40
|
+
|
41
|
+
tag 'Name', "acl-public-#{ stack.cloud }"
|
42
|
+
tag 'Stack', stack.cloud
|
43
|
+
end
|
44
|
+
|
45
|
+
public_table = add_route_table 'Public', :gateway_route => true do
|
46
|
+
tag 'Name', "routes-public-#{ stack.cloud }"
|
47
|
+
tag 'Stack', stack.cloud
|
48
|
+
end
|
49
|
+
|
50
|
+
stack.availability_zones do |zone, i|
|
51
|
+
add_subnet "Public#{ i }" do
|
52
|
+
availability_zone zone
|
53
|
+
acl public_acl
|
54
|
+
route_table public_table
|
55
|
+
|
56
|
+
with_output
|
57
|
+
|
58
|
+
immutable_metadata "public-#{ stack.cloud }"
|
59
|
+
tag 'Name', "subnet-public-#{ stack.cloud }-#{ zone }"
|
60
|
+
tag 'Stack', stack.cloud
|
61
|
+
tag 'Service', 'Public'
|
62
|
+
end
|
63
|
+
end
|
47
64
|
end
|
48
65
|
end
|
49
|
-
|
50
|
-
tag 'Name', join('-', 'cf-test-vpc', fn_ref('AWS::StackName'))
|
51
|
-
end
|
52
|
-
|
53
|
-
ec2_security_group 'BetterSecurityGroup' do
|
54
|
-
ingress_rule do
|
55
|
-
cidr_ip '0.0.0.0/0'
|
56
|
-
from 22
|
57
|
-
to 22
|
58
|
-
protocol 'TCP'
|
59
|
-
end
|
60
|
-
egress_rule do
|
61
|
-
cidr_ip '0.0.0.0/0'
|
62
|
-
from 0
|
63
|
-
to 65_535
|
64
|
-
protocol(-1)
|
65
|
-
end
|
66
|
-
|
67
|
-
description 'Allow SSH traffic from all of the places'
|
68
|
-
vpc_id fn_ref('TargetVPC')
|
69
|
-
|
70
|
-
tag 'Name', join('-', fn_ref('AWS::StackName'), 'BetterSecurityGroup')
|
71
66
|
end
|
72
67
|
end
|
73
|
-
|
74
|
-
# puts test_template.render
|
75
|
-
# puts test_template.to_json
|
76
|
-
|
77
|
-
# stack_e1 = Convection.stack('TestStackE1B1', test_template, :region => 'us-east-1')
|
78
|
-
stack_w1 = Convection.stack('TestStackW1B2', test_template, :region => 'us-west-1')
|
79
|
-
|
80
|
-
# puts stack_e1.status
|
81
|
-
# puts stack_e1.apply
|
82
|
-
puts stack_w1.to_json
|
83
|
-
|
84
|
-
puts "Status #{ stack_w1.status }"
|
85
|
-
# puts stack_w1.apply
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
#
|
3
|
+
# Given content like the following passed as the first argument
|
4
|
+
# this should build the methods needed to write most of the convection
|
5
|
+
# code for you. You will have to do some massaging of the method names,
|
6
|
+
# but this takes most of the grunt work out.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
# % . ./resource_generator.sh
|
10
|
+
# or add it to your .profile
|
11
|
+
#
|
12
|
+
# % resource_generator ""Count" : String,
|
13
|
+
#"Handle" : String,
|
14
|
+
#"Timeout" : String"
|
15
|
+
#echo $1 | sed "s/\"//g" | cut -d ':' -f 1
|
16
|
+
#echo $1 | sed "s/\"//g" | cut -d ':' -f 1 | tr -d ' ' | xargs -I {} printf "def {}\(value\)\n property\(\'{}\', value\)\nend\n\n"
|
17
|
+
|
18
|
+
|
19
|
+
resource_generator() {
|
20
|
+
echo $1 | sed "s/\"//g" | cut -d ':' -f 1 | tr -d ' ' | xargs -I {} printf "def {}\(value\)\n property\(\'{}\', value\)\nend\n\n"
|
21
|
+
}
|
data/lib/convection.rb
CHANGED
@@ -3,16 +3,17 @@
|
|
3
3
|
##
|
4
4
|
module Convection
|
5
5
|
class << self
|
6
|
-
def template(&block)
|
7
|
-
Model::Template.new(&block)
|
6
|
+
def template(*args, &block)
|
7
|
+
Model::Template.new(*args, &block)
|
8
8
|
end
|
9
9
|
|
10
|
-
def stack(
|
11
|
-
Control::Stack.new(
|
10
|
+
def stack(*args)
|
11
|
+
Control::Stack.new(*args)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
16
|
require_relative 'convection/version'
|
17
|
+
require_relative 'convection/model/attributes'
|
17
18
|
require_relative 'convection/model/template'
|
18
19
|
require_relative 'convection/control/stack'
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require_relative '../model/cloudfile'
|
2
|
+
require_relative '../model/event'
|
3
|
+
|
4
|
+
module Convection
|
5
|
+
module Control
|
6
|
+
##
|
7
|
+
# Control tour Clouds
|
8
|
+
##
|
9
|
+
class Cloud
|
10
|
+
def configure(cloudfile)
|
11
|
+
@cloudfile = Model::Cloudfile.new(cloudfile)
|
12
|
+
end
|
13
|
+
|
14
|
+
def stacks
|
15
|
+
@cloudfile.stacks
|
16
|
+
end
|
17
|
+
|
18
|
+
def deck
|
19
|
+
@cloudfile.deck
|
20
|
+
end
|
21
|
+
|
22
|
+
def converge(to_stack, &block)
|
23
|
+
unless to_stack.nil? || stacks.include?(to_stack)
|
24
|
+
block.call(Model::Event.new(:error, "Stack #{ to_stack } is not defined", :error)) if block
|
25
|
+
return
|
26
|
+
end
|
27
|
+
|
28
|
+
deck.each do |stack|
|
29
|
+
block.call(Model::Event.new(:converge, "Stack #{ stack.name }", :info)) if block
|
30
|
+
stack.apply(&block)
|
31
|
+
|
32
|
+
if stack.error?
|
33
|
+
block.call(Model::Event.new(:error, "Error converging stack #{ stack.name }", :error), stack.errors) if block
|
34
|
+
break
|
35
|
+
end
|
36
|
+
|
37
|
+
## Stop on converge error
|
38
|
+
break unless stack.success?
|
39
|
+
|
40
|
+
## Stop here
|
41
|
+
break if !to_stack.nil? && stack.name == to_stack
|
42
|
+
sleep rand @cloudfile.splay || 2
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def diff(&block)
|
47
|
+
@cloudfile.deck.each do |stack|
|
48
|
+
block.call(Model::Event.new(:compare, "Compare local state of stack #{ stack.name } (#{ stack.cloud_name }) with remote template", :info))
|
49
|
+
sleep rand @cloudfile.splay || 2
|
50
|
+
|
51
|
+
difference = stack.diff
|
52
|
+
next block.call(Model::Event.new(:unchanged, "Stack #{ stack.cloud_name } Has no changes", :info)) if difference.empty?
|
53
|
+
|
54
|
+
difference.each { |diff| block.call(diff) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -7,139 +7,338 @@ module Convection
|
|
7
7
|
# Instantiation of a template in an account/region
|
8
8
|
##
|
9
9
|
class Stack
|
10
|
+
attr_reader :id
|
10
11
|
attr_reader :name
|
11
12
|
attr_accessor :template
|
13
|
+
attr_reader :exist
|
14
|
+
attr_reader :status
|
15
|
+
alias_method :exist?, :exist
|
12
16
|
|
17
|
+
attr_reader :attributes
|
18
|
+
attr_reader :errors
|
19
|
+
attr_reader :options
|
20
|
+
attr_reader :resources
|
21
|
+
attr_reader :attribute_mapping_values
|
22
|
+
attr_reader :outputs
|
23
|
+
|
24
|
+
## AWS-SDK
|
13
25
|
attr_accessor :region
|
26
|
+
attr_accessor :cloud
|
27
|
+
attr_reader :capabilities
|
14
28
|
attr_accessor :credentials
|
15
29
|
attr_reader :parameters
|
16
30
|
attr_reader :tags
|
17
31
|
attr_accessor :on_failure
|
18
|
-
attr_reader :capabilities
|
19
|
-
attr_reader :options
|
20
32
|
|
21
33
|
## Valid Stack Statuses
|
22
|
-
CREATE_IN_PROGRESS = 'CREATE_IN_PROGRESS'
|
23
|
-
CREATE_FAILED = 'CREATE_FAILED'
|
24
34
|
CREATE_COMPLETE = 'CREATE_COMPLETE'
|
25
|
-
|
26
|
-
|
27
|
-
ROLLBACK_COMPLETE = 'ROLLBACK_COMPLETE'
|
28
|
-
DELETE_IN_PROGRESS = 'DELETE_IN_PROGRESS'
|
29
|
-
DELETE_FAILED = 'DELETE_FAILED'
|
35
|
+
CREATE_FAILED = 'CREATE_FAILED'
|
36
|
+
CREATE_IN_PROGRESS = 'CREATE_IN_PROGRESS'
|
30
37
|
DELETE_COMPLETE = 'DELETE_COMPLETE'
|
31
|
-
|
32
|
-
|
38
|
+
DELETE_FAILED = 'DELETE_FAILED'
|
39
|
+
DELETE_IN_PROGRESS = 'DELETE_IN_PROGRESS'
|
40
|
+
ROLLBACK_COMPLETE = 'ROLLBACK_COMPLETE'
|
41
|
+
ROLLBACK_FAILED = 'ROLLBACK_FAILED'
|
42
|
+
ROLLBACK_IN_PROGRESS = 'ROLLBACK_IN_PROGRESS'
|
33
43
|
UPDATE_COMPLETE = 'UPDATE_COMPLETE'
|
34
|
-
|
35
|
-
|
36
|
-
|
44
|
+
UPDATE_COMPLETE_CLEANUP_IN_PROGRESS = 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS'
|
45
|
+
UPDATE_FAILED = 'UPDATE_FAILED'
|
46
|
+
UPDATE_IN_PROGRESS = 'UPDATE_IN_PROGRESS'
|
37
47
|
UPDATE_ROLLBACK_COMPLETE = 'UPDATE_ROLLBACK_COMPLETE'
|
48
|
+
UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS = 'UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS'
|
49
|
+
UPDATE_ROLLBACK_FAILED = 'UPDATE_ROLLBACK_FAILED'
|
50
|
+
UPDATE_ROLLBACK_IN_PROGRESS = 'UPDATE_ROLLBACK_IN_PROGRESS'
|
38
51
|
|
39
52
|
## Internal status
|
40
53
|
NOT_CREATED = 'NOT_CREATED'
|
41
54
|
|
42
55
|
def initialize(name, template, options = {})
|
43
56
|
@name = name
|
44
|
-
@template = template
|
57
|
+
@template = template.clone(self)
|
58
|
+
@errors = []
|
45
59
|
|
60
|
+
@cloud = options.delete(:cloud)
|
61
|
+
@cloud_name = options.delete(:cloud_name)
|
46
62
|
@region = options.delete(:region) { |_| 'us-east-1' }
|
47
63
|
@credentials = options.delete(:credentials)
|
48
64
|
@parameters = options.delete(:parameters) { |_| {} } # Default empty hash
|
49
65
|
@tags = options.delete(:tags) { |_| {} } # Default empty hash
|
50
|
-
|
51
|
-
## There can be only one...
|
66
|
+
options.delete(:disable_rollback) # There can be only one...
|
52
67
|
@on_failure = options.delete(:on_failure) { |_| 'DELETE' }
|
53
|
-
options.delete(:disable_rollback)
|
54
|
-
|
55
68
|
@capabilities = options.delete(:capabilities) { |_| ['CAPABILITY_IAM'] }
|
69
|
+
|
70
|
+
@attributes = options.delete(:attributes) { |_| Model::Attributes.new }
|
56
71
|
@options = options
|
57
72
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
73
|
+
client_options = {}.tap do |opt|
|
74
|
+
opt[:region] = @region
|
75
|
+
opt[:credentials] = @credentials unless @credentials.nil?
|
76
|
+
end
|
77
|
+
@ec2_client = Aws::EC2::Client.new(client_options)
|
78
|
+
@cf_client = Aws::CloudFormation::Client.new(client_options)
|
79
|
+
|
80
|
+
## Remote state
|
81
|
+
@exist = false
|
82
|
+
@status = NOT_CREATED
|
83
|
+
@id = nil
|
84
|
+
@outputs = {}
|
85
|
+
@resources = {}
|
86
|
+
@current_template = {}
|
87
|
+
@last_event_seen = nil
|
88
|
+
|
89
|
+
## Get initial state
|
90
|
+
get_status(cloud_name)
|
91
|
+
return unless exist?
|
92
|
+
|
93
|
+
get_resources
|
94
|
+
get_template
|
95
|
+
resource_attributes
|
96
|
+
get_events(1) # Get the latest page of events (Set @last_event_seen before starting)
|
97
|
+
rescue Aws::Errors::ServiceError => e
|
98
|
+
@errors << e
|
62
99
|
end
|
63
100
|
|
64
|
-
def
|
65
|
-
@
|
101
|
+
def cloud_name
|
102
|
+
return @cloud_name unless @cloud_name.nil?
|
103
|
+
return name if cloud.nil?
|
104
|
+
"#{ cloud }-#{ name }"
|
66
105
|
end
|
67
106
|
|
68
|
-
|
69
|
-
|
107
|
+
##
|
108
|
+
# Attribute Accessors
|
109
|
+
##
|
110
|
+
def include?(stack, key = nil)
|
111
|
+
return @attributes.include?(name, stack) if key.nil?
|
112
|
+
@attributes.include?(stack, key)
|
70
113
|
end
|
71
114
|
|
72
|
-
def
|
73
|
-
|
115
|
+
def [](key)
|
116
|
+
@attributes.get(name, key)
|
74
117
|
end
|
75
118
|
|
76
|
-
def
|
77
|
-
|
119
|
+
def []=(key, value)
|
120
|
+
@attributes.set(name, key, value)
|
78
121
|
end
|
79
122
|
|
80
|
-
def
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
123
|
+
def get(*args)
|
124
|
+
@attributes.get(*args)
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Stack State
|
129
|
+
##
|
130
|
+
def in_progress?
|
131
|
+
[CREATE_IN_PROGRESS, ROLLBACK_IN_PROGRESS, DELETE_IN_PROGRESS,
|
132
|
+
UPDATE_IN_PROGRESS, UPDATE_COMPLETE_CLEANUP_IN_PROGRESS,
|
133
|
+
UPDATE_ROLLBACK_IN_PROGRESS,
|
134
|
+
UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS
|
135
|
+
].include?(status)
|
136
|
+
end
|
137
|
+
|
138
|
+
def complete?
|
139
|
+
[CREATE_COMPLETE, ROLLBACK_COMPLETE, UPDATE_COMPLETE, UPDATE_ROLLBACK_COMPLETE].include?(status)
|
85
140
|
end
|
86
141
|
|
87
142
|
def fail?
|
88
143
|
[CREATE_FAILED, ROLLBACK_FAILED, DELETE_FAILED, UPDATE_ROLLBACK_FAILED].include?(status)
|
89
144
|
end
|
90
145
|
|
146
|
+
def error?
|
147
|
+
!errors.empty?
|
148
|
+
end
|
149
|
+
|
150
|
+
def success?
|
151
|
+
!error? && complete?
|
152
|
+
end
|
153
|
+
|
154
|
+
##
|
155
|
+
# Rendderers
|
156
|
+
##
|
91
157
|
def render
|
92
|
-
template.render
|
158
|
+
@template.render
|
159
|
+
end
|
160
|
+
|
161
|
+
def to_json(pretty = false)
|
162
|
+
@template.to_json(nil, pretty)
|
93
163
|
end
|
94
164
|
|
95
|
-
def
|
96
|
-
template.
|
165
|
+
def diff
|
166
|
+
@template.diff(@current_template)
|
97
167
|
end
|
98
168
|
|
99
|
-
|
100
|
-
|
169
|
+
##
|
170
|
+
# Controllers
|
171
|
+
##
|
172
|
+
def apply(&block)
|
101
173
|
request_options = @options.clone.tap do |o|
|
102
|
-
o[:stack_name] = name
|
103
174
|
o[:template_body] = to_json
|
104
175
|
o[:parameters] = cf_parameters
|
105
176
|
o[:capabilities] = capabilities
|
106
177
|
end
|
107
178
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
179
|
+
if exist?
|
180
|
+
if diff.empty? ## No Changes. Just get resources and move on
|
181
|
+
block.call(Model::Event.new(:complete, "Stack #{ name } has no changes", :info)) if block
|
182
|
+
get_status
|
183
|
+
return
|
184
|
+
end
|
185
|
+
|
186
|
+
## Update
|
187
|
+
@cf_client.update_stack(request_options.tap do |o|
|
188
|
+
o[:stack_name] = id
|
189
|
+
end)
|
190
|
+
else
|
191
|
+
## Create
|
192
|
+
@cf_client.create_stack(request_options.tap do |o|
|
193
|
+
o[:stack_name] = cloud_name
|
194
|
+
|
195
|
+
o[:tags] = cf_tags
|
196
|
+
o[:on_failure] = on_failure
|
197
|
+
end)
|
198
|
+
|
199
|
+
get_status(cloud_name) # Get ID of new stack
|
200
|
+
end
|
201
|
+
|
202
|
+
watch(&block) if block # Block execution on stack status
|
203
|
+
rescue Aws::Errors::ServiceError => e
|
204
|
+
@errors << e
|
117
205
|
end
|
118
206
|
|
119
|
-
def delete
|
207
|
+
def delete(&block)
|
120
208
|
@cf_client.delete_stack(
|
121
|
-
:stack_name =>
|
209
|
+
:stack_name => id
|
122
210
|
)
|
211
|
+
|
212
|
+
## Block execution on stack status
|
213
|
+
watch(&block) if block
|
214
|
+
|
215
|
+
get_status
|
216
|
+
rescue Aws::Errors::ServiceError => e
|
217
|
+
@errors << e
|
218
|
+
end
|
219
|
+
|
220
|
+
def watch(poll = 2, &block)
|
221
|
+
get_status
|
222
|
+
|
223
|
+
loop do
|
224
|
+
get_events.reverse_each do |event|
|
225
|
+
block.call(Model::Event.from_cf(event))
|
226
|
+
end if block
|
227
|
+
|
228
|
+
break unless in_progress?
|
229
|
+
|
230
|
+
sleep poll
|
231
|
+
get_status
|
232
|
+
end
|
233
|
+
rescue Aws::Errors::ServiceError => e
|
234
|
+
@errors << e
|
123
235
|
end
|
124
236
|
|
125
237
|
def availability_zones(&block)
|
126
238
|
@availability_zones ||=
|
127
|
-
@ec2_client.describe_availability_zones.
|
239
|
+
@ec2_client.describe_availability_zones.availability_zones.map(&:zone_name).sort
|
128
240
|
|
129
241
|
@availability_zones.each_with_index(&block) if block
|
130
242
|
@availability_zones
|
131
243
|
end
|
132
244
|
|
245
|
+
def validate
|
246
|
+
result = @cf_client.validate_template(:template_body => template.to_json)
|
247
|
+
fail result.context.http_response.inspect unless result.successful?
|
248
|
+
puts "\nTemplate validated successfully"
|
249
|
+
end
|
250
|
+
|
133
251
|
private
|
134
252
|
|
135
|
-
def
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
253
|
+
def get_status(stack_name = id)
|
254
|
+
cf_stack = @cf_client.describe_stacks(:stack_name => stack_name).stacks.first
|
255
|
+
|
256
|
+
@id = cf_stack.stack_id
|
257
|
+
@status = cf_stack.stack_status
|
258
|
+
@exist = true
|
259
|
+
|
260
|
+
## Parse outputs
|
261
|
+
@outputs = {}.tap do |collection|
|
262
|
+
cf_stack.outputs.each do |output|
|
263
|
+
collection[output[:output_key].to_s] = (JSON.parse(output[:output_value]) rescue output[:output_value])
|
141
264
|
end
|
142
265
|
end
|
266
|
+
|
267
|
+
## Add outputs to attribute set
|
268
|
+
@attributes.load_outputs(self)
|
269
|
+
rescue Aws::CloudFormation::Errors::ValidationError # Stack does not exist
|
270
|
+
@exist = false
|
271
|
+
@status = NOT_CREATED
|
272
|
+
@id = nil
|
273
|
+
@outputs = {}
|
274
|
+
end
|
275
|
+
|
276
|
+
## Fetch current resources
|
277
|
+
def get_resources
|
278
|
+
@resources = {}.tap do |collection|
|
279
|
+
@cf_client.list_stack_resources(:stack_name => @id).each do |page|
|
280
|
+
page.stack_resource_summaries.each do |resource|
|
281
|
+
collection[resource[:logical_resource_id]] = resource
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
rescue Aws::CloudFormation::Errors::ValidationError # Stack does not exist
|
286
|
+
@resources = {}
|
287
|
+
end
|
288
|
+
|
289
|
+
def get_template
|
290
|
+
@current_template = JSON.parse(@cf_client.get_template(:stack_name => id).template_body)
|
291
|
+
rescue Aws::CloudFormation::Errors::ValidationError # Stack does not exist
|
292
|
+
@current_template = {}
|
293
|
+
end
|
294
|
+
|
295
|
+
## Fetch new stack events
|
296
|
+
def get_events(pages = nil, stack_name = id)
|
297
|
+
return [] unless exist?
|
298
|
+
|
299
|
+
[].tap do |collection|
|
300
|
+
@cf_client.describe_stack_events(:stack_name => stack_name).each do |page|
|
301
|
+
pages -= 1 unless pages.nil?
|
302
|
+
|
303
|
+
page.stack_events.each do |event|
|
304
|
+
if @last_event_seen == event.event_id
|
305
|
+
pages = 0 # Break page loop
|
306
|
+
break
|
307
|
+
end
|
308
|
+
|
309
|
+
collection << event
|
310
|
+
end
|
311
|
+
|
312
|
+
break if pages == 0
|
313
|
+
end
|
314
|
+
|
315
|
+
@last_event_seen = collection.first.event_id unless collection.empty?
|
316
|
+
end
|
317
|
+
rescue Aws::CloudFormation::Errors::ValidationError # Stack does not exist
|
318
|
+
end
|
319
|
+
|
320
|
+
## TODO No. This will become unnecessary as current_state is fleshed out
|
321
|
+
def resource_attributes
|
322
|
+
@attribute_mapping_values = {}
|
323
|
+
@template.execute ## Populate mappings fro the template
|
324
|
+
|
325
|
+
@resources.each do |logical, resource|
|
326
|
+
next unless @template.attribute_mappings.include?(logical)
|
327
|
+
|
328
|
+
attribute_map = @template.attribute_mappings[logical]
|
329
|
+
case attribute_map[:type].to_sym
|
330
|
+
when :string
|
331
|
+
@attribute_mapping_values[attribute_map[:name]] = resource[:physical_resource_id]
|
332
|
+
when :array
|
333
|
+
@attribute_mapping_values[attribute_map[:name]] = [] unless @attribute_mapping_values[attribute_map[:name]].is_a?(Array)
|
334
|
+
@attribute_mapping_values[attribute_map[:name]].push(resource[:physical_resource_id])
|
335
|
+
else
|
336
|
+
fail TypeError, "Attribute Mapping must be defined with type `string` or `array`, not #{ type }"
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
## Add mapped resource IDs to attributes
|
341
|
+
@attributes.load_resources(self)
|
143
342
|
end
|
144
343
|
|
145
344
|
def cf_parameters
|
@@ -163,3 +362,5 @@ module Convection
|
|
163
362
|
end
|
164
363
|
end
|
165
364
|
end
|
365
|
+
|
366
|
+
require_relative '../model/event'
|