miasma 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -0
  3. data/README.md +179 -0
  4. data/lib/miasma.rb +52 -0
  5. data/lib/miasma/contrib/aws.rb +390 -0
  6. data/lib/miasma/contrib/aws/auto_scale.rb +85 -0
  7. data/lib/miasma/contrib/aws/compute.rb +112 -0
  8. data/lib/miasma/contrib/aws/load_balancer.rb +185 -0
  9. data/lib/miasma/contrib/aws/orchestration.rb +338 -0
  10. data/lib/miasma/contrib/rackspace.rb +164 -0
  11. data/lib/miasma/contrib/rackspace/auto_scale.rb +84 -0
  12. data/lib/miasma/contrib/rackspace/compute.rb +104 -0
  13. data/lib/miasma/contrib/rackspace/load_balancer.rb +117 -0
  14. data/lib/miasma/contrib/rackspace/orchestration.rb +255 -0
  15. data/lib/miasma/error.rb +89 -0
  16. data/lib/miasma/models.rb +14 -0
  17. data/lib/miasma/models/auto_scale.rb +55 -0
  18. data/lib/miasma/models/auto_scale/group.rb +64 -0
  19. data/lib/miasma/models/auto_scale/groups.rb +34 -0
  20. data/lib/miasma/models/block_storage.rb +0 -0
  21. data/lib/miasma/models/compute.rb +70 -0
  22. data/lib/miasma/models/compute/server.rb +71 -0
  23. data/lib/miasma/models/compute/servers.rb +35 -0
  24. data/lib/miasma/models/dns.rb +0 -0
  25. data/lib/miasma/models/load_balancer.rb +55 -0
  26. data/lib/miasma/models/load_balancer/balancer.rb +72 -0
  27. data/lib/miasma/models/load_balancer/balancers.rb +34 -0
  28. data/lib/miasma/models/monitoring.rb +0 -0
  29. data/lib/miasma/models/orchestration.rb +127 -0
  30. data/lib/miasma/models/orchestration/event.rb +38 -0
  31. data/lib/miasma/models/orchestration/events.rb +64 -0
  32. data/lib/miasma/models/orchestration/resource.rb +79 -0
  33. data/lib/miasma/models/orchestration/resources.rb +55 -0
  34. data/lib/miasma/models/orchestration/stack.rb +144 -0
  35. data/lib/miasma/models/orchestration/stacks.rb +46 -0
  36. data/lib/miasma/models/queues.rb +0 -0
  37. data/lib/miasma/models/storage.rb +60 -0
  38. data/lib/miasma/models/storage/bucket.rb +36 -0
  39. data/lib/miasma/models/storage/buckets.rb +41 -0
  40. data/lib/miasma/models/storage/file.rb +45 -0
  41. data/lib/miasma/models/storage/files.rb +52 -0
  42. data/lib/miasma/types.rb +13 -0
  43. data/lib/miasma/types/api.rb +145 -0
  44. data/lib/miasma/types/collection.rb +116 -0
  45. data/lib/miasma/types/data.rb +53 -0
  46. data/lib/miasma/types/model.rb +118 -0
  47. data/lib/miasma/types/thin_model.rb +76 -0
  48. data/lib/miasma/utils.rb +12 -0
  49. data/lib/miasma/utils/animal_strings.rb +29 -0
  50. data/lib/miasma/utils/immutable.rb +36 -0
  51. data/lib/miasma/utils/lazy.rb +231 -0
  52. data/lib/miasma/utils/memoization.rb +55 -0
  53. data/lib/miasma/utils/smash.rb +149 -0
  54. data/lib/miasma/version.rb +4 -0
  55. data/miasma.gemspec +18 -0
  56. metadata +57 -3
@@ -0,0 +1,85 @@
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 = request(
53
+ :path => '/',
54
+ :params => params
55
+ )
56
+ [result.get(:body, 'DescribeAutoScalingGroupsResponse', 'DescribeAutoScalingGroupsResult', 'AutoScalingGroups', 'member')].flatten(1).compact.map do |grp|
57
+ (group || Group.new(self)).load_data(
58
+ :id => grp['AutoScalingGroupName'],
59
+ :name => grp['AutoScalingGroupName'],
60
+ :servers => [grp.get('Instances', 'member')].flatten(1).compact.map{|i|
61
+ Group::Server.new(self, :id => i['InstanceId'])
62
+ },
63
+ :minimum_size => grp['MinSize'],
64
+ :maximum_size => grp['MaxSize'],
65
+ :status => grp['Status'],
66
+ :load_balancers => [grp.get('LoadBalancerNames', 'member')].flatten(1).compact.map{|i|
67
+ Group::Balancer.new(self, :id => i, :name => i)
68
+ }
69
+ ).valid_state
70
+ end
71
+ end
72
+
73
+
74
+ # Return all auto scale groups
75
+ #
76
+ # @param options [Hash] filter
77
+ # @return [Array<Models::AutoScale::Group>]
78
+ def group_all(options={})
79
+ load_group_data
80
+ end
81
+
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,112 @@
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 = srv[:instancesSet][:item]
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
107
+
108
+ end
109
+ end
110
+ end
111
+
112
+ end
@@ -0,0 +1,185 @@
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 = request(
109
+ :path => '/',
110
+ :params => params
111
+ )
112
+ [result.get(:body, 'DescribeLoadBalancersResponse', 'DescribeLoadBalancersResult', 'LoadBalancerDescriptions', 'member')].flatten.compact.map do |blr|
113
+ (balancer || Balancer.new(self)).load_data(
114
+ :id => blr['LoadBalancerName'],
115
+ :name => blr['LoadBalancerName'],
116
+ :state => :active,
117
+ :status => 'ACTIVE',
118
+ :created => blr['CreatedTime'],
119
+ :updated => blr['CreatedTime'],
120
+ :public_addresses => [
121
+ Balancer::Address.new(
122
+ :address => blr['DNSName'],
123
+ :version => 4
124
+ )
125
+ ],
126
+ :servers => [blr.get('Instances', 'member')].flatten(1).compact.map{|i|
127
+ Balancer::Server.new(self.api_for(:compute), :id => i['InstanceId'])
128
+ }
129
+ ).valid_state
130
+ end
131
+ end
132
+
133
+ # Delete load balancer
134
+ #
135
+ # @param balancer [Models::LoadBalancer::Balancer]
136
+ # @return [TrueClass, FalseClass]
137
+ def balancer_destroy(balancer)
138
+ if(balancer.persisted?)
139
+ request(
140
+ :path => '/',
141
+ :params => Smash.new(
142
+ 'Action' => 'DeleteLoadBalancer',
143
+ 'LoadBalancerName' => balancer.name
144
+ )
145
+ )
146
+ balancer.state = :pending
147
+ balancer.status = 'DELETE_IN_PROGRESS'
148
+ balancer.valid_state
149
+ true
150
+ else
151
+ false
152
+ end
153
+ end
154
+
155
+ # Return all load balancers
156
+ #
157
+ # @param options [Hash] filter
158
+ # @return [Array<Models::LoadBalancer::Balancer>]
159
+ def balancer_all(options={})
160
+ load_balancer_data
161
+ end
162
+
163
+ protected
164
+
165
+ # @return [Array<String>] availability zones
166
+ def availability_zones
167
+ memoize(:availability_zones) do
168
+ res = api_for(:compute).request(
169
+ :path => '/',
170
+ :params => Smash.new(
171
+ 'Action' => 'DescribeAvailabilityZones'
172
+ )
173
+ ).fetch(:body, 'DescribeAvailabilityZonesResponse', 'availabilityZoneInfo', 'item', [])
174
+ [res].flatten.compact.map do |item|
175
+ if(item['zoneState'] == 'available')
176
+ item['zoneName']
177
+ end
178
+ end.compact
179
+ end
180
+ end
181
+
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,338 @@
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
+ end
54
+ descriptions = [
55
+ request(:path => '/', :params => d_params).get(
56
+ :body, 'DescribeStacksResponse', 'DescribeStacksResult', 'Stacks', 'member'
57
+ )
58
+ ].flatten(1).compact
59
+ lists = request(:path => '/', :params => l_params)
60
+ [
61
+ lists.get(
62
+ :body, 'ListStacksResponse', 'ListStacksResult',
63
+ 'StackSummaries', 'member'
64
+ )
65
+ ].flatten(1).compact.map do |stk|
66
+ desc = descriptions.detect do |d_stk|
67
+ d_stk['StackId'] == stk['StackId']
68
+ end || Smash.new
69
+ stk.merge!(desc)
70
+ next if stack && stack.id != stk['StackId']
71
+ new_stack = stack || Stack.new(self)
72
+ new_stack.load_data(
73
+ :id => stk['StackId'],
74
+ :name => stk['StackName'],
75
+ :capabilities => [stk.get('Capabilities', 'member')].flatten(1).compact,
76
+ :description => stk['Description'],
77
+ :creation_time => stk['CreationTime'],
78
+ :updated_time => stk['LastUpdatedTime'],
79
+ :notification_topics => [stk.get('NotificationARNs', 'member')].flatten(1).compact,
80
+ :timeout_in_minutes => stk['TimeoutInMinutes'],
81
+ :status => stk['StackStatus'],
82
+ :status_reason => stk['StackStatusReason'],
83
+ :state => stk['StackStatus'].downcase.to_sym,
84
+ :template_description => stk['TemplateDescription'],
85
+ :disable_rollback => !!stk['DisableRollback'],
86
+ :outputs => [stk.get('Outputs', 'member')].flatten(1).compact.map{|o|
87
+ Smash.new(
88
+ :key => o['OutputKey'],
89
+ :value => o['OutputValue'],
90
+ :description => o['Description']
91
+ )
92
+ },
93
+ :parameters => Smash[
94
+ [stk.fetch('Parameters', 'member', [])].flatten(1).map{|param|
95
+ [param['ParameterKey'], param['ParameterValue']]
96
+ }
97
+ ]
98
+ ).valid_state
99
+ end
100
+ end
101
+
102
+ # Save the stack
103
+ #
104
+ # @param stack [Models::Orchestration::Stack]
105
+ # @return [Models::Orchestration::Stack]
106
+ def stack_save(stack)
107
+ params = Smash.new('StackName' => stack.name)
108
+ (stack.parameters || {}).each_with_index do |pair, idx|
109
+ params["Parameters.member.#{idx + 1}.ParameterKey"] = pair.first
110
+ params["Parameters.member.#{idx + 1}.ParameterValue"] = pair.last
111
+ end
112
+ (stack.capabilities || []).each_with_index do |cap, idx|
113
+ params["Capabilities.member.#{idx + 1}"] = cap
114
+ end
115
+ (stack.notification_topics || []).each_with_index do |topic, idx|
116
+ params["NotificationARNs.member.#{idx + 1}"] = topic
117
+ end
118
+ if(stack.template.empty?)
119
+ params['UsePreviousTemplate'] = true
120
+ else
121
+ params['TemplateBody'] = MultiJson.dump(stack.template)
122
+ end
123
+ if(stack.persisted?)
124
+ result = request(
125
+ :path => '/',
126
+ :method => :post,
127
+ :params => Smash.new(
128
+ 'Action' => 'UpdateStack'
129
+ ).merge(params)
130
+ )
131
+ stack
132
+ else
133
+ if(stack.timeout_in_minutes)
134
+ params['TimeoutInMinutes'] = stack.timeout_in_minutes
135
+ end
136
+ result = request(
137
+ :path => '/',
138
+ :method => :post,
139
+ :params => Smash.new(
140
+ 'Action' => 'CreateStack',
141
+ 'DisableRollback' => !!stack.disable_rollback
142
+ ).merge(params)
143
+ )
144
+ stack.id = result.get(:body, 'CreateStackResponse', 'CreateStackResult', 'StackId')
145
+ stack.valid_state
146
+ end
147
+ end
148
+
149
+ # Reload the stack data from the API
150
+ #
151
+ # @param stack [Models::Orchestration::Stack]
152
+ # @return [Models::Orchestration::Stack]
153
+ def stack_reload(stack)
154
+ if(stack.persisted?)
155
+ ustack = Stack.new(self)
156
+ ustack.id = stack.id
157
+ load_stack_data(ustack)
158
+ if(ustack.data[:name])
159
+ stack.load_data(ustack.attributes).valid_state
160
+ else
161
+ stack.status = 'DELETE_COMPLETE'
162
+ stack.state = :delete_complete
163
+ stack.valid_state
164
+ end
165
+ end
166
+ stack
167
+ end
168
+
169
+ # Delete the stack
170
+ #
171
+ # @param stack [Models::Orchestration::Stack]
172
+ # @return [TrueClass, FalseClass]
173
+ def stack_destroy(stack)
174
+ if(stack.persisted?)
175
+ request(
176
+ :path => '/',
177
+ :params => Smash.new(
178
+ 'Action' => 'DeleteStack',
179
+ 'StackName' => stack.id
180
+ )
181
+ )
182
+ true
183
+ else
184
+ false
185
+ end
186
+ end
187
+
188
+ # Fetch stack template
189
+ #
190
+ # @param stack [Stack]
191
+ # @return [Smash] stack template
192
+ def stack_template_load(stack)
193
+ if(stack.persisted?)
194
+ result = request(
195
+ :path => '/',
196
+ :params => Smash.new(
197
+ 'Action' => 'GetTemplate',
198
+ 'StackName' => stack.id
199
+ )
200
+ )
201
+ MultiJson.load(
202
+ result.get(:body, 'GetTemplateResponse', 'GetTemplateResult', 'TemplateBody')
203
+ ).to_smash
204
+ else
205
+ Smash.new
206
+ end
207
+ end
208
+
209
+ # Validate stack template
210
+ #
211
+ # @param stack [Stack]
212
+ # @return [NilClass, String] nil if valid, string error message if invalid
213
+ def stack_template_validate(stack)
214
+ begin
215
+ result = request(
216
+ :method => :post,
217
+ :path => '/',
218
+ :params => Smash.new(
219
+ 'Action' => 'ValidateTemplate',
220
+ 'TemplateBody' => MultiJson.dump(stack.template)
221
+ )
222
+ )
223
+ nil
224
+ rescue Error::ApiError::RequestError => e
225
+ MultiXml.parse(e.response.body.to_s).to_smash.get(
226
+ 'ErrorResponse', 'Error', 'Message'
227
+ )
228
+ end
229
+ end
230
+
231
+ # Return all stacks
232
+ #
233
+ # @param options [Hash] filter
234
+ # @return [Array<Models::Orchestration::Stack>]
235
+ # @todo check if we need any mappings on state set
236
+ def stack_all
237
+ load_stack_data
238
+ end
239
+
240
+ # Return all resources for stack
241
+ #
242
+ # @param stack [Models::Orchestration::Stack]
243
+ # @return [Array<Models::Orchestration::Stack::Resource>]
244
+ def resource_all(stack)
245
+ result = request(
246
+ :path => '/',
247
+ :params => Smash.new(
248
+ 'Action' => 'DescribeStackResources',
249
+ 'StackName' => stack.id
250
+ )
251
+ )
252
+ [
253
+ result.fetch(
254
+ :body, 'DescribeStackResourcesResponse',
255
+ 'DescribeStackResourcesResult',
256
+ 'StackResources', 'member', []
257
+ )
258
+ ].flatten(1).compact.map do |res|
259
+ Stack::Resource.new(
260
+ stack,
261
+ :id => res['PhysicalResourceId'],
262
+ :name => res['LogicalResourceId'],
263
+ :logical_id => res['LogicalResourceId'],
264
+ :type => res['ResourceType'],
265
+ :state => res['ResourceStatus'].downcase.to_sym,
266
+ :status => res['ResourceStatus'],
267
+ :status_reason => res['ResourceStatusReason'],
268
+ :updated_time => res['Timestamp']
269
+ ).valid_state
270
+ end
271
+ end
272
+
273
+ # Reload the stack resource data from the API
274
+ #
275
+ # @param resource [Models::Orchestration::Stack::Resource]
276
+ # @return [Models::Orchestration::Resource]
277
+ def resource_reload(resource)
278
+ resource.stack.resources.reload
279
+ resource.stack.resources.get(resource.id)
280
+ end
281
+
282
+ # Return all events for stack
283
+ #
284
+ # @param stack [Models::Orchestration::Stack]
285
+ # @return [Array<Models::Orchestration::Stack::Event>]
286
+ def event_all(stack, evt_id=nil)
287
+ results = all_result_pages(nil, :body, 'DescribeStackEventsResponse', 'DescribeStackEventsResult', 'StackEvents', 'member') do |options|
288
+ request(
289
+ :path => '/',
290
+ :params => options.merge(
291
+ 'Action' => 'DescribeStackEvents',
292
+ 'StackName' => stack.id
293
+ )
294
+ )
295
+ end
296
+ events = results.map do |event|
297
+ Stack::Event.new(
298
+ stack,
299
+ :id => event['EventId'],
300
+ :resource_id => event['PhysicalResourceId'],
301
+ :resource_name => event['LogicalResourceId'],
302
+ :resource_logical_id => event['LogicalResourceId'],
303
+ :resource_state => event['ResourceStatus'].downcase.to_sym,
304
+ :resource_status => event['ResourceStatus'],
305
+ :resource_status_reason => event['ResourceStatusReason'],
306
+ :time => Time.parse(event['Timestamp'])
307
+ ).valid_state
308
+ end
309
+ if(evt_id)
310
+ idx = events.index{|d| e.id == evt_id}
311
+ idx = idx ? idx + 1 : 0
312
+ events.slice(idx, events.size)
313
+ else
314
+ events
315
+ end
316
+ end
317
+
318
+ # Return all new events for event collection
319
+ #
320
+ # @param events [Models::Orchestration::Stack::Events]
321
+ # @return [Array<Models::Orchestration::Stack::Event>]
322
+ def event_all_new(events)
323
+ event_all(events.stack, events.all.first.id)
324
+ end
325
+
326
+ # Reload the stack event data from the API
327
+ #
328
+ # @param resource [Models::Orchestration::Stack::Event]
329
+ # @return [Models::Orchestration::Event]
330
+ def event_reload(event)
331
+ event.stack.events.reload
332
+ event.stack.events.get(event.id)
333
+ end
334
+
335
+ end
336
+ end
337
+ end
338
+ end