miasma-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/CHANGELOG.md +2 -0
- data/LICENSE +13 -0
- data/README.md +20 -0
- data/lib/miasma-aws.rb +2 -0
- data/lib/miasma-aws/version.rb +4 -0
- data/lib/miasma/contrib/aws.rb +444 -0
- data/lib/miasma/contrib/aws/auto_scale.rb +86 -0
- data/lib/miasma/contrib/aws/compute.rb +113 -0
- data/lib/miasma/contrib/aws/load_balancer.rb +187 -0
- data/lib/miasma/contrib/aws/orchestration.rb +350 -0
- data/lib/miasma/contrib/aws/storage.rb +405 -0
- data/miasma-aws.gemspec +15 -0
- metadata +70 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Models
|
5
|
+
class AutoScale
|
6
|
+
class Aws < AutoScale
|
7
|
+
|
8
|
+
# Service name of the API
|
9
|
+
API_SERVICE = 'autoscaling'
|
10
|
+
# Supported version of the AutoScaling API
|
11
|
+
API_VERSION = '2011-01-01'
|
12
|
+
|
13
|
+
include Contrib::AwsApiCore::ApiCommon
|
14
|
+
include Contrib::AwsApiCore::RequestUtils
|
15
|
+
|
16
|
+
# Save auto scale group
|
17
|
+
#
|
18
|
+
# @param group [Models::AutoScale::Group]
|
19
|
+
# @return [Models::AutoScale::Group]
|
20
|
+
def group_save(group)
|
21
|
+
raise NotImplementedError
|
22
|
+
end
|
23
|
+
|
24
|
+
# Reload the group data from the API
|
25
|
+
#
|
26
|
+
# @param group [Models::AutoScale::Group]
|
27
|
+
# @return [Models::AutoScale::Group]
|
28
|
+
def group_reload(group)
|
29
|
+
if(group.id || group.name)
|
30
|
+
load_group_data(group)
|
31
|
+
end
|
32
|
+
group
|
33
|
+
end
|
34
|
+
|
35
|
+
# Delete auto scale group
|
36
|
+
#
|
37
|
+
# @param group [Models::AutoScale::Group]
|
38
|
+
# @return [TrueClass, FalseClass]
|
39
|
+
def group_destroy(group)
|
40
|
+
raise NotImplemented
|
41
|
+
end
|
42
|
+
|
43
|
+
# Fetch groups or update provided group data
|
44
|
+
#
|
45
|
+
# @param group [Models::AutoScale::Group]
|
46
|
+
# @return [Array<Models::AutoScale::Group>]
|
47
|
+
def load_group_data(group=nil)
|
48
|
+
params = Smash.new('Action' => 'DescribeAutoScalingGroups')
|
49
|
+
if(group)
|
50
|
+
params.merge('AutoScalingGroupNames.member.1' => group.id || group.name)
|
51
|
+
end
|
52
|
+
result = all_result_pages(nil, :body, 'DescribeAutoScalingGroupsResponse', 'DescribeAutoScalingGroupsResult', 'AutoScalingGroups', 'member') do |options|
|
53
|
+
request(
|
54
|
+
:path => '/',
|
55
|
+
:params => options.merge(params)
|
56
|
+
)
|
57
|
+
end.map do |grp|
|
58
|
+
(group || Group.new(self)).load_data(
|
59
|
+
:id => grp['AutoScalingGroupName'],
|
60
|
+
:name => grp['AutoScalingGroupName'],
|
61
|
+
:servers => [grp.get('Instances', 'member')].flatten(1).compact.map{|i|
|
62
|
+
Group::Server.new(self, :id => i['InstanceId'])
|
63
|
+
},
|
64
|
+
:minimum_size => grp['MinSize'],
|
65
|
+
:maximum_size => grp['MaxSize'],
|
66
|
+
:status => grp['Status'],
|
67
|
+
:load_balancers => [grp.get('LoadBalancerNames', 'member')].flatten(1).compact.map{|i|
|
68
|
+
Group::Balancer.new(self, :id => i, :name => i)
|
69
|
+
}
|
70
|
+
).valid_state
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
# Return all auto scale groups
|
76
|
+
#
|
77
|
+
# @param options [Hash] filter
|
78
|
+
# @return [Array<Models::AutoScale::Group>]
|
79
|
+
def group_all(options={})
|
80
|
+
load_group_data
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
|
5
|
+
module Models
|
6
|
+
class Compute
|
7
|
+
# Compute interface for AWS
|
8
|
+
class Aws < Compute
|
9
|
+
|
10
|
+
# Service name of the API
|
11
|
+
API_SERVICE = 'ec2'
|
12
|
+
# Supported version of the EC2 API
|
13
|
+
API_VERSION = '2014-06-15'
|
14
|
+
|
15
|
+
include Contrib::AwsApiCore::ApiCommon
|
16
|
+
include Contrib::AwsApiCore::RequestUtils
|
17
|
+
|
18
|
+
# @return [Smash] map state to valid internal values
|
19
|
+
SERVER_STATE_MAP = Smash.new(
|
20
|
+
'running' => :running,
|
21
|
+
'pending' => :pending,
|
22
|
+
'shutting-down' => :pending,
|
23
|
+
'terminated' => :terminated,
|
24
|
+
'stopping' => :pending,
|
25
|
+
'stopped' => :stopped
|
26
|
+
)
|
27
|
+
|
28
|
+
# @todo catch bad lookup and clear model
|
29
|
+
def server_reload(server)
|
30
|
+
result = request(
|
31
|
+
:path => '/',
|
32
|
+
:params => {
|
33
|
+
'Action' => 'DescribeInstances',
|
34
|
+
'InstanceId.1' => server.id
|
35
|
+
}
|
36
|
+
)
|
37
|
+
srv = result.get(:body, 'DescribeInstancesResponse', 'reservationSet', 'item', 'instancesSet', 'item')
|
38
|
+
server.load_data(
|
39
|
+
:id => srv[:instanceId],
|
40
|
+
:name => srv.fetch(:tagSet, :item, []).map{|tag| tag[:value] if tag.is_a?(Hash) && tag[:key] == 'Name'}.compact.first,
|
41
|
+
:image_id => srv[:imageId],
|
42
|
+
:flavor_id => srv[:instanceType],
|
43
|
+
:state => SERVER_STATE_MAP.fetch(srv.get(:instanceState, :name), :pending),
|
44
|
+
:addresses_private => [Server::Address.new(:version => 4, :address => srv[:privateIpAddress])],
|
45
|
+
:addresses_public => [Server::Address.new(:version => 4, :address => srv[:ipAddress])],
|
46
|
+
:status => srv.get(:instanceState, :name),
|
47
|
+
:key_name => srv[:keyName]
|
48
|
+
)
|
49
|
+
server.valid_state
|
50
|
+
end
|
51
|
+
|
52
|
+
def server_destroy(server)
|
53
|
+
if(server.persisted?)
|
54
|
+
result = request(
|
55
|
+
:path => '/',
|
56
|
+
:params => {
|
57
|
+
'Action' => 'TerminateInstances',
|
58
|
+
'InstanceId.1' => server.id
|
59
|
+
}
|
60
|
+
)
|
61
|
+
else
|
62
|
+
raise "this doesn't even exist"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def server_save(server)
|
67
|
+
unless(server.persisted?)
|
68
|
+
server.load_data(server.attributes)
|
69
|
+
result = request(
|
70
|
+
:path => '/',
|
71
|
+
:params => {
|
72
|
+
'Action' => 'RunInstances',
|
73
|
+
'ImageId' => server.image_id,
|
74
|
+
'InstanceType' => server.flavor_id,
|
75
|
+
'KeyName' => server.key_name,
|
76
|
+
'MinCount' => 1,
|
77
|
+
'MaxCount' => 1
|
78
|
+
}
|
79
|
+
)
|
80
|
+
server.id = result.get(:body, 'RunInstancesResponse', 'instancesSet', 'item', 'instanceId')
|
81
|
+
else
|
82
|
+
raise 'WAT DO I DO!?'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# @todo need to add auto pagination helper (as common util)
|
87
|
+
def server_all
|
88
|
+
results = all_result_pages(nil, :body, 'DescribeInstancesResponse', 'reservationSet', 'item') do |options|
|
89
|
+
request(:path => '/', :params => options.merge('Action' => 'DescribeInstances'))
|
90
|
+
end
|
91
|
+
results.map do |srv|
|
92
|
+
[srv[:instancesSet][:item]].flatten.compact.map do |srv|
|
93
|
+
Server.new(
|
94
|
+
self,
|
95
|
+
:id => srv[:instanceId],
|
96
|
+
:name => srv.fetch(:tagSet, :item, []).map{|tag| tag[:value] if tag.is_a?(Hash) && tag[:key] == 'Name'}.compact.first,
|
97
|
+
:image_id => srv[:imageId],
|
98
|
+
:flavor_id => srv[:instanceType],
|
99
|
+
:state => SERVER_STATE_MAP.fetch(srv.get(:instanceState, :name), :pending),
|
100
|
+
:addresses_private => [Server::Address.new(:version => 4, :address => srv[:privateIpAddress])],
|
101
|
+
:addresses_public => [Server::Address.new(:version => 4, :address => srv[:ipAddress])],
|
102
|
+
:status => srv.get(:instanceState, :name),
|
103
|
+
:key_name => srv[:keyName]
|
104
|
+
).valid_state
|
105
|
+
end
|
106
|
+
end.flatten
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Models
|
5
|
+
class LoadBalancer
|
6
|
+
class Aws < LoadBalancer
|
7
|
+
|
8
|
+
include Contrib::AwsApiCore::ApiCommon
|
9
|
+
include Contrib::AwsApiCore::RequestUtils
|
10
|
+
|
11
|
+
# Service name of API
|
12
|
+
API_SERVICE = 'elasticloadbalancing'
|
13
|
+
# Supported version of the ELB API
|
14
|
+
API_VERSION = '2012-06-01'
|
15
|
+
|
16
|
+
# Save load balancer
|
17
|
+
#
|
18
|
+
# @param balancer [Models::LoadBalancer::Balancer]
|
19
|
+
# @return [Models::LoadBalancer::Balancer]
|
20
|
+
def balancer_save(balancer)
|
21
|
+
unless(balancer.persisted?)
|
22
|
+
params = Smash.new(
|
23
|
+
'LoadBalancerName' => balancer.name
|
24
|
+
)
|
25
|
+
availability_zones.each_with_index do |az, i|
|
26
|
+
params["AvailabilityZones.member.#{i+1}"] = az
|
27
|
+
end
|
28
|
+
if(balancer.listeners)
|
29
|
+
balancer.listeners.each_with_index do |listener, i|
|
30
|
+
key = "Listeners.member.#{i + 1}"
|
31
|
+
params["#{key}.Protocol"] = listener.protocol
|
32
|
+
params["#{key}.InstanceProtocol"] = listener.instance_protocol
|
33
|
+
params["#{key}.LoadBalancerPort"] = listener.load_balancer_port
|
34
|
+
params["#{key}.InstancePort"] = listener.instance_port
|
35
|
+
if(listener.ssl_certificate_id)
|
36
|
+
params["#{key}.SSLCertificateId"] = listener.ssl_certificate_id
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
result = request(
|
41
|
+
:path => '/',
|
42
|
+
:params => params.merge(
|
43
|
+
Smash.new(
|
44
|
+
'Action' => 'CreateLoadBalancer'
|
45
|
+
)
|
46
|
+
)
|
47
|
+
)
|
48
|
+
balancer.public_addresses = [
|
49
|
+
:address => result.get(:body, 'CreateLoadBalancerResponse', 'CreateLoadBalancerResult', 'DNSName')
|
50
|
+
]
|
51
|
+
balancer.load_data(:id => balancer.name).valid_state
|
52
|
+
if(balancer.health_check)
|
53
|
+
balancer_health_check(balancer)
|
54
|
+
end
|
55
|
+
if(balancer.servers && !balancer.servers.empty?)
|
56
|
+
balancer_set_instances(balancer)
|
57
|
+
end
|
58
|
+
else
|
59
|
+
if(balancer.dirty?)
|
60
|
+
if(balancer.dirty?(:health_check))
|
61
|
+
balancer_health_check(balancer)
|
62
|
+
end
|
63
|
+
if(balancer.dirty?(:servers))
|
64
|
+
balancer_set_instances(balancer)
|
65
|
+
end
|
66
|
+
balancer.reload
|
67
|
+
end
|
68
|
+
balancer
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Save the load balancer health check
|
73
|
+
#
|
74
|
+
# @param balancer [Models::LoadBalancer::Balancer]
|
75
|
+
# @return [Models::LoadBalancer::Balancer]
|
76
|
+
def balancer_health_check(balancer)
|
77
|
+
balancer
|
78
|
+
end
|
79
|
+
|
80
|
+
# Save the load balancer attached servers
|
81
|
+
#
|
82
|
+
# @param balancer [Models::LoadBalancer::Balancer]
|
83
|
+
# @return [Models::LoadBalancer::Balancer]
|
84
|
+
def balancer_set_instances(balancer)
|
85
|
+
balancer
|
86
|
+
end
|
87
|
+
|
88
|
+
# Reload the balancer data from the API
|
89
|
+
#
|
90
|
+
# @param balancer [Models::LoadBalancer::Balancer]
|
91
|
+
# @return [Models::LoadBalancer::Balancer]
|
92
|
+
def balancer_reload(balancer)
|
93
|
+
if(balancer.persisted?)
|
94
|
+
load_balancer_data(balancer)
|
95
|
+
end
|
96
|
+
balancer
|
97
|
+
end
|
98
|
+
|
99
|
+
# Fetch balancers or update provided balancer data
|
100
|
+
#
|
101
|
+
# @param balancer [Models::LoadBalancer::Balancer]
|
102
|
+
# @return [Array<Models::LoadBalancer::Balancer>]
|
103
|
+
def load_balancer_data(balancer=nil)
|
104
|
+
params = Smash.new('Action' => 'DescribeLoadBalancers')
|
105
|
+
if(balancer)
|
106
|
+
params.merge('LoadBalancerNames.member.1' => balancer.id || balancer.name)
|
107
|
+
end
|
108
|
+
result = all_result_pages(nil, :body, 'DescribeLoadBalancersResponse', 'DescribeLoadBalancersResult', 'LoadBalancerDescriptions', 'member') do |options|
|
109
|
+
request(
|
110
|
+
:path => '/',
|
111
|
+
:params => options.merge(params)
|
112
|
+
)
|
113
|
+
end
|
114
|
+
result.map do |blr|
|
115
|
+
(balancer || Balancer.new(self)).load_data(
|
116
|
+
:id => blr['LoadBalancerName'],
|
117
|
+
:name => blr['LoadBalancerName'],
|
118
|
+
:state => :active,
|
119
|
+
:status => 'ACTIVE',
|
120
|
+
:created => blr['CreatedTime'],
|
121
|
+
:updated => blr['CreatedTime'],
|
122
|
+
:public_addresses => [
|
123
|
+
Balancer::Address.new(
|
124
|
+
:address => blr['DNSName'],
|
125
|
+
:version => 4
|
126
|
+
)
|
127
|
+
],
|
128
|
+
:servers => [blr.get('Instances', 'member')].flatten(1).compact.map{|i|
|
129
|
+
Balancer::Server.new(self.api_for(:compute), :id => i['InstanceId'])
|
130
|
+
}
|
131
|
+
).valid_state
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Delete load balancer
|
136
|
+
#
|
137
|
+
# @param balancer [Models::LoadBalancer::Balancer]
|
138
|
+
# @return [TrueClass, FalseClass]
|
139
|
+
def balancer_destroy(balancer)
|
140
|
+
if(balancer.persisted?)
|
141
|
+
request(
|
142
|
+
:path => '/',
|
143
|
+
:params => Smash.new(
|
144
|
+
'Action' => 'DeleteLoadBalancer',
|
145
|
+
'LoadBalancerName' => balancer.name
|
146
|
+
)
|
147
|
+
)
|
148
|
+
balancer.state = :pending
|
149
|
+
balancer.status = 'DELETE_IN_PROGRESS'
|
150
|
+
balancer.valid_state
|
151
|
+
true
|
152
|
+
else
|
153
|
+
false
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Return all load balancers
|
158
|
+
#
|
159
|
+
# @param options [Hash] filter
|
160
|
+
# @return [Array<Models::LoadBalancer::Balancer>]
|
161
|
+
def balancer_all(options={})
|
162
|
+
load_balancer_data
|
163
|
+
end
|
164
|
+
|
165
|
+
protected
|
166
|
+
|
167
|
+
# @return [Array<String>] availability zones
|
168
|
+
def availability_zones
|
169
|
+
memoize(:availability_zones) do
|
170
|
+
res = api_for(:compute).request(
|
171
|
+
:path => '/',
|
172
|
+
:params => Smash.new(
|
173
|
+
'Action' => 'DescribeAvailabilityZones'
|
174
|
+
)
|
175
|
+
).fetch(:body, 'DescribeAvailabilityZonesResponse', 'availabilityZoneInfo', 'item', [])
|
176
|
+
[res].flatten.compact.map do |item|
|
177
|
+
if(item['zoneState'] == 'available')
|
178
|
+
item['zoneName']
|
179
|
+
end
|
180
|
+
end.compact
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,350 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Models
|
5
|
+
class Orchestration
|
6
|
+
class Aws < Orchestration
|
7
|
+
|
8
|
+
# Service name of the API
|
9
|
+
API_SERVICE = 'cloudformation'
|
10
|
+
# Supported version of the AutoScaling API
|
11
|
+
API_VERSION = '2010-05-15'
|
12
|
+
|
13
|
+
# Valid stack lookup states
|
14
|
+
STACK_STATES = [
|
15
|
+
"CREATE_COMPLETE", "CREATE_FAILED", "CREATE_IN_PROGRESS", "DELETE_FAILED",
|
16
|
+
"DELETE_IN_PROGRESS", "ROLLBACK_COMPLETE", "ROLLBACK_FAILED", "ROLLBACK_IN_PROGRESS",
|
17
|
+
"UPDATE_COMPLETE", "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", "UPDATE_IN_PROGRESS",
|
18
|
+
"UPDATE_ROLLBACK_COMPLETE", "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS", "UPDATE_ROLLBACK_FAILED",
|
19
|
+
"UPDATE_ROLLBACK_IN_PROGRESS"
|
20
|
+
]
|
21
|
+
|
22
|
+
include Contrib::AwsApiCore::ApiCommon
|
23
|
+
include Contrib::AwsApiCore::RequestUtils
|
24
|
+
|
25
|
+
# @return [Smash] external to internal resource mapping
|
26
|
+
RESOURCE_MAPPING = Smash.new(
|
27
|
+
'AWS::EC2::Instance' => Smash.new(
|
28
|
+
:api => :compute,
|
29
|
+
:collection => :servers
|
30
|
+
),
|
31
|
+
'AWS::ElasticLoadBalancing::LoadBalancer' => Smash.new(
|
32
|
+
:api => :load_balancer,
|
33
|
+
:collection => :balancers
|
34
|
+
),
|
35
|
+
'AWS::AutoScaling::AutoScalingGroup' => Smash.new(
|
36
|
+
:api => :auto_scale,
|
37
|
+
:collection => :groups
|
38
|
+
)
|
39
|
+
)
|
40
|
+
|
41
|
+
# Fetch stacks or update provided stack data
|
42
|
+
#
|
43
|
+
# @param stack [Models::Orchestration::Stack]
|
44
|
+
# @return [Array<Models::Orchestration::Stack>]
|
45
|
+
def load_stack_data(stack=nil)
|
46
|
+
d_params = Smash.new('Action' => 'DescribeStacks')
|
47
|
+
l_params = Smash.new('Action' => 'ListStacks')
|
48
|
+
STACK_STATES.each_with_index do |state, idx|
|
49
|
+
l_params["StackStatusFilter.member.#{idx + 1}"] = state.to_s.upcase
|
50
|
+
end
|
51
|
+
if(stack)
|
52
|
+
d_params['StackName'] = stack.id
|
53
|
+
descriptions = all_result_pages(nil, :body, 'DescribeStacksResponse', 'DescribeStacksResult', 'Stacks', 'member') do |options|
|
54
|
+
request(
|
55
|
+
:path => '/',
|
56
|
+
:params => options.merge(d_params)
|
57
|
+
)
|
58
|
+
end
|
59
|
+
else
|
60
|
+
descriptions = []
|
61
|
+
end
|
62
|
+
lists = all_result_pages(nil, :body, 'ListStacksResponse', 'ListStacksResult', 'StackSummaries', 'member') do |options|
|
63
|
+
request(
|
64
|
+
:path => '/',
|
65
|
+
:params => options.merge(l_params)
|
66
|
+
)
|
67
|
+
end.map do |stk|
|
68
|
+
desc = descriptions.detect do |d_stk|
|
69
|
+
d_stk['StackId'] == stk['StackId']
|
70
|
+
end || Smash.new
|
71
|
+
stk.merge!(desc)
|
72
|
+
if(stack)
|
73
|
+
next if stack.id != stk['StackId'] && stk['StackId'].split('/')[1] != stack.id
|
74
|
+
end
|
75
|
+
new_stack = stack || Stack.new(self)
|
76
|
+
new_stack.load_data(
|
77
|
+
:id => stk['StackId'],
|
78
|
+
:name => stk['StackName'],
|
79
|
+
:capabilities => [stk.get('Capabilities', 'member')].flatten(1).compact,
|
80
|
+
:description => stk['Description'],
|
81
|
+
:created => stk['CreationTime'],
|
82
|
+
:updated => stk['LastUpdatedTime'],
|
83
|
+
:notification_topics => [stk.get('NotificationARNs', 'member')].flatten(1).compact,
|
84
|
+
:timeout_in_minutes => stk['TimeoutInMinutes'] ? stk['TimeoutInMinutes'].to_i : nil,
|
85
|
+
:status => stk['StackStatus'],
|
86
|
+
:status_reason => stk['StackStatusReason'],
|
87
|
+
:state => stk['StackStatus'].downcase.to_sym,
|
88
|
+
:template_description => stk['TemplateDescription'],
|
89
|
+
:disable_rollback => !!stk['DisableRollback'],
|
90
|
+
:outputs => [stk.get('Outputs', 'member')].flatten(1).compact.map{|o|
|
91
|
+
Smash.new(
|
92
|
+
:key => o['OutputKey'],
|
93
|
+
:value => o['OutputValue'],
|
94
|
+
:description => o['Description']
|
95
|
+
)
|
96
|
+
},
|
97
|
+
:parameters => Smash[
|
98
|
+
[stk.fetch('Parameters', 'member', [])].flatten(1).map{|param|
|
99
|
+
[param['ParameterKey'], param['ParameterValue']]
|
100
|
+
}
|
101
|
+
]
|
102
|
+
).valid_state
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Save the stack
|
107
|
+
#
|
108
|
+
# @param stack [Models::Orchestration::Stack]
|
109
|
+
# @return [Models::Orchestration::Stack]
|
110
|
+
def stack_save(stack)
|
111
|
+
params = Smash.new('StackName' => stack.name)
|
112
|
+
(stack.parameters || {}).each_with_index do |pair, idx|
|
113
|
+
params["Parameters.member.#{idx + 1}.ParameterKey"] = pair.first
|
114
|
+
params["Parameters.member.#{idx + 1}.ParameterValue"] = pair.last
|
115
|
+
end
|
116
|
+
(stack.capabilities || []).each_with_index do |cap, idx|
|
117
|
+
params["Capabilities.member.#{idx + 1}"] = cap
|
118
|
+
end
|
119
|
+
(stack.notification_topics || []).each_with_index do |topic, idx|
|
120
|
+
params["NotificationARNs.member.#{idx + 1}"] = topic
|
121
|
+
end
|
122
|
+
if(stack.template.empty?)
|
123
|
+
params['UsePreviousTemplate'] = true
|
124
|
+
else
|
125
|
+
params['TemplateBody'] = MultiJson.dump(stack.template)
|
126
|
+
end
|
127
|
+
if(stack.persisted?)
|
128
|
+
result = request(
|
129
|
+
:path => '/',
|
130
|
+
:method => :post,
|
131
|
+
:params => Smash.new(
|
132
|
+
'Action' => 'UpdateStack'
|
133
|
+
).merge(params)
|
134
|
+
)
|
135
|
+
stack
|
136
|
+
else
|
137
|
+
if(stack.timeout_in_minutes)
|
138
|
+
params['TimeoutInMinutes'] = stack.timeout_in_minutes
|
139
|
+
end
|
140
|
+
result = request(
|
141
|
+
:path => '/',
|
142
|
+
:method => :post,
|
143
|
+
:params => Smash.new(
|
144
|
+
'Action' => 'CreateStack',
|
145
|
+
'DisableRollback' => !!stack.disable_rollback
|
146
|
+
).merge(params)
|
147
|
+
)
|
148
|
+
stack.id = result.get(:body, 'CreateStackResponse', 'CreateStackResult', 'StackId')
|
149
|
+
stack.valid_state
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Reload the stack data from the API
|
154
|
+
#
|
155
|
+
# @param stack [Models::Orchestration::Stack]
|
156
|
+
# @return [Models::Orchestration::Stack]
|
157
|
+
def stack_reload(stack)
|
158
|
+
if(stack.persisted?)
|
159
|
+
ustack = Stack.new(self)
|
160
|
+
ustack.id = stack.id
|
161
|
+
load_stack_data(ustack)
|
162
|
+
if(ustack.data[:name])
|
163
|
+
stack.load_data(ustack.attributes).valid_state
|
164
|
+
else
|
165
|
+
stack.status = 'DELETE_COMPLETE'
|
166
|
+
stack.state = :delete_complete
|
167
|
+
stack.valid_state
|
168
|
+
end
|
169
|
+
end
|
170
|
+
stack
|
171
|
+
end
|
172
|
+
|
173
|
+
# Delete the stack
|
174
|
+
#
|
175
|
+
# @param stack [Models::Orchestration::Stack]
|
176
|
+
# @return [TrueClass, FalseClass]
|
177
|
+
def stack_destroy(stack)
|
178
|
+
if(stack.persisted?)
|
179
|
+
request(
|
180
|
+
:path => '/',
|
181
|
+
:params => Smash.new(
|
182
|
+
'Action' => 'DeleteStack',
|
183
|
+
'StackName' => stack.id
|
184
|
+
)
|
185
|
+
)
|
186
|
+
true
|
187
|
+
else
|
188
|
+
false
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Fetch stack template
|
193
|
+
#
|
194
|
+
# @param stack [Stack]
|
195
|
+
# @return [Smash] stack template
|
196
|
+
def stack_template_load(stack)
|
197
|
+
if(stack.persisted?)
|
198
|
+
result = request(
|
199
|
+
:path => '/',
|
200
|
+
:params => Smash.new(
|
201
|
+
'Action' => 'GetTemplate',
|
202
|
+
'StackName' => stack.id
|
203
|
+
)
|
204
|
+
)
|
205
|
+
MultiJson.load(
|
206
|
+
result.get(:body, 'GetTemplateResponse', 'GetTemplateResult', 'TemplateBody')
|
207
|
+
).to_smash
|
208
|
+
else
|
209
|
+
Smash.new
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Validate stack template
|
214
|
+
#
|
215
|
+
# @param stack [Stack]
|
216
|
+
# @return [NilClass, String] nil if valid, string error message if invalid
|
217
|
+
def stack_template_validate(stack)
|
218
|
+
begin
|
219
|
+
result = request(
|
220
|
+
:method => :post,
|
221
|
+
:path => '/',
|
222
|
+
:params => Smash.new(
|
223
|
+
'Action' => 'ValidateTemplate',
|
224
|
+
'TemplateBody' => MultiJson.dump(stack.template)
|
225
|
+
)
|
226
|
+
)
|
227
|
+
nil
|
228
|
+
rescue Error::ApiError::RequestError => e
|
229
|
+
MultiXml.parse(e.response.body.to_s).to_smash.get(
|
230
|
+
'ErrorResponse', 'Error', 'Message'
|
231
|
+
)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Return single stack
|
236
|
+
#
|
237
|
+
# @param ident [String] name or ID
|
238
|
+
# @return [Stack]
|
239
|
+
def stack_get(ident)
|
240
|
+
i = Stack.new(self)
|
241
|
+
i.id = ident
|
242
|
+
i.reload
|
243
|
+
i.name ? i : nil
|
244
|
+
end
|
245
|
+
|
246
|
+
# Return all stacks
|
247
|
+
#
|
248
|
+
# @param options [Hash] filter
|
249
|
+
# @return [Array<Models::Orchestration::Stack>]
|
250
|
+
# @todo check if we need any mappings on state set
|
251
|
+
def stack_all
|
252
|
+
load_stack_data
|
253
|
+
end
|
254
|
+
|
255
|
+
# Return all resources for stack
|
256
|
+
#
|
257
|
+
# @param stack [Models::Orchestration::Stack]
|
258
|
+
# @return [Array<Models::Orchestration::Stack::Resource>]
|
259
|
+
def resource_all(stack)
|
260
|
+
results = all_result_pages(nil, :body, 'DescribeStackResourcesResponse', 'DescribeStackResourcesResult', 'StackResources', 'member') do |options|
|
261
|
+
request(
|
262
|
+
:path => '/',
|
263
|
+
:params => options.merge(
|
264
|
+
Smash.new(
|
265
|
+
'Action' => 'DescribeStackResources',
|
266
|
+
'StackName' => stack.id
|
267
|
+
)
|
268
|
+
)
|
269
|
+
)
|
270
|
+
end.map do |res|
|
271
|
+
Stack::Resource.new(
|
272
|
+
stack,
|
273
|
+
:id => res['PhysicalResourceId'],
|
274
|
+
:name => res['LogicalResourceId'],
|
275
|
+
:logical_id => res['LogicalResourceId'],
|
276
|
+
:type => res['ResourceType'],
|
277
|
+
:state => res['ResourceStatus'].downcase.to_sym,
|
278
|
+
:status => res['ResourceStatus'],
|
279
|
+
:status_reason => res['ResourceStatusReason'],
|
280
|
+
:updated => res['Timestamp']
|
281
|
+
).valid_state
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# Reload the stack resource data from the API
|
286
|
+
#
|
287
|
+
# @param resource [Models::Orchestration::Stack::Resource]
|
288
|
+
# @return [Models::Orchestration::Resource]
|
289
|
+
def resource_reload(resource)
|
290
|
+
resource.stack.resources.reload
|
291
|
+
resource.stack.resources.get(resource.id)
|
292
|
+
end
|
293
|
+
|
294
|
+
# Return all events for stack
|
295
|
+
#
|
296
|
+
# @param stack [Models::Orchestration::Stack]
|
297
|
+
# @return [Array<Models::Orchestration::Stack::Event>]
|
298
|
+
def event_all(stack, evt_id=nil)
|
299
|
+
results = all_result_pages(nil, :body, 'DescribeStackEventsResponse', 'DescribeStackEventsResult', 'StackEvents', 'member') do |options|
|
300
|
+
request(
|
301
|
+
:path => '/',
|
302
|
+
:params => options.merge(
|
303
|
+
'Action' => 'DescribeStackEvents',
|
304
|
+
'StackName' => stack.id
|
305
|
+
)
|
306
|
+
)
|
307
|
+
end
|
308
|
+
events = results.map do |event|
|
309
|
+
Stack::Event.new(
|
310
|
+
stack,
|
311
|
+
:id => event['EventId'],
|
312
|
+
:resource_id => event['PhysicalResourceId'],
|
313
|
+
:resource_name => event['LogicalResourceId'],
|
314
|
+
:resource_logical_id => event['LogicalResourceId'],
|
315
|
+
:resource_state => event['ResourceStatus'].downcase.to_sym,
|
316
|
+
:resource_status => event['ResourceStatus'],
|
317
|
+
:resource_status_reason => event['ResourceStatusReason'],
|
318
|
+
:time => Time.parse(event['Timestamp'])
|
319
|
+
).valid_state
|
320
|
+
end
|
321
|
+
if(evt_id)
|
322
|
+
idx = events.index{|d| e.id == evt_id}
|
323
|
+
idx = idx ? idx + 1 : 0
|
324
|
+
events.slice(idx, events.size)
|
325
|
+
else
|
326
|
+
events
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
# Return all new events for event collection
|
331
|
+
#
|
332
|
+
# @param events [Models::Orchestration::Stack::Events]
|
333
|
+
# @return [Array<Models::Orchestration::Stack::Event>]
|
334
|
+
def event_all_new(events)
|
335
|
+
event_all(events.stack, events.all.first.id)
|
336
|
+
end
|
337
|
+
|
338
|
+
# Reload the stack event data from the API
|
339
|
+
#
|
340
|
+
# @param resource [Models::Orchestration::Stack::Event]
|
341
|
+
# @return [Models::Orchestration::Event]
|
342
|
+
def event_reload(event)
|
343
|
+
event.stack.events.reload
|
344
|
+
event.stack.events.get(event.id)
|
345
|
+
end
|
346
|
+
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|