miasma 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +6 -0
- data/LICENSE +13 -0
- data/README.md +18 -3
- data/lib/miasma/contrib/aws.rb +18 -1
- data/lib/miasma/contrib/aws/orchestration.rb +3 -3
- data/lib/miasma/contrib/aws/storage.rb +330 -0
- data/lib/miasma/contrib/open_stack.rb +334 -0
- data/lib/miasma/contrib/open_stack/compute.rb +105 -0
- data/lib/miasma/contrib/open_stack/orchestration.rb +255 -0
- data/lib/miasma/contrib/rackspace.rb +34 -94
- data/lib/miasma/contrib/rackspace/compute.rb +1 -92
- data/lib/miasma/contrib/rackspace/orchestration.rb +1 -231
- data/lib/miasma/error.rb +7 -5
- data/lib/miasma/models/orchestration/resource.rb +1 -1
- data/lib/miasma/models/orchestration/stack.rb +2 -2
- data/lib/miasma/models/storage.rb +36 -8
- data/lib/miasma/models/storage/bucket.rb +23 -10
- data/lib/miasma/models/storage/file.rb +44 -4
- data/lib/miasma/models/storage/files.rb +6 -1
- data/lib/miasma/types/api.rb +10 -4
- data/lib/miasma/types/model.rb +4 -1
- data/lib/miasma/utils/lazy.rb +7 -2
- data/lib/miasma/version.rb +1 -1
- data/miasma.gemspec +2 -1
- metadata +21 -2
@@ -0,0 +1,334 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
require 'miasma/utils/smash'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Miasma
|
6
|
+
module Contrib
|
7
|
+
|
8
|
+
# OpenStack API core helper
|
9
|
+
class OpenStackApiCore
|
10
|
+
|
11
|
+
# Authentication helper class
|
12
|
+
class Authenticate
|
13
|
+
|
14
|
+
# @return [Smash] token info
|
15
|
+
attr_reader :token
|
16
|
+
# @return [Smash] credentials in use
|
17
|
+
attr_reader :credentials
|
18
|
+
|
19
|
+
# Create new instance
|
20
|
+
#
|
21
|
+
# @return [self]
|
22
|
+
def initialize(credentials)
|
23
|
+
@credentials = credentials.to_smash
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [String] username
|
27
|
+
def user
|
28
|
+
load!
|
29
|
+
@user
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Smash] remote service catalog
|
33
|
+
def service_catalog
|
34
|
+
load!
|
35
|
+
@service_catalog
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [String] current API token
|
39
|
+
def api_token
|
40
|
+
if(token.nil? || Time.now > token[:expires])
|
41
|
+
identify_and_load
|
42
|
+
end
|
43
|
+
token[:id]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Identify with authentication endpoint
|
47
|
+
# and load the service catalog
|
48
|
+
#
|
49
|
+
# @return [self]
|
50
|
+
def identity_and_load
|
51
|
+
raise NotImplementedError
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Smash] authentication request body
|
55
|
+
def authentication_request
|
56
|
+
raise NotImplementedError
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
# @return [TrueClass] load authenticator
|
62
|
+
def load!
|
63
|
+
!!api_token
|
64
|
+
end
|
65
|
+
|
66
|
+
# Authentication implementation compatible for v2
|
67
|
+
class Version2 < Authenticate
|
68
|
+
|
69
|
+
# @return [Smash] authentication request body
|
70
|
+
def authentication_request
|
71
|
+
if(credentials[:open_stack_token])
|
72
|
+
auth = Smash.new(
|
73
|
+
:token => Smash.new(
|
74
|
+
:id => credentials[:open_stack_token]
|
75
|
+
)
|
76
|
+
)
|
77
|
+
else
|
78
|
+
auth = Smash.new(
|
79
|
+
'passwordCredentials' => Smash.new(
|
80
|
+
'username' => credentials[:open_stack_username],
|
81
|
+
'password' => credentials[:open_stack_password]
|
82
|
+
)
|
83
|
+
)
|
84
|
+
end
|
85
|
+
if(credentials[:open_stack_tenant_name])
|
86
|
+
auth['tenantName'] = credentials[:open_stack_tenant_name]
|
87
|
+
end
|
88
|
+
auth
|
89
|
+
end
|
90
|
+
|
91
|
+
# Identify with authentication service and load
|
92
|
+
# token information and service catalog
|
93
|
+
#
|
94
|
+
# @return [TrueClass]
|
95
|
+
def identify_and_load
|
96
|
+
result = HTTP.post(
|
97
|
+
File.join(
|
98
|
+
credentials[:open_stack_identity_url],
|
99
|
+
'tokens'
|
100
|
+
),
|
101
|
+
:json => Smash.new(
|
102
|
+
:auth => authentication_request
|
103
|
+
)
|
104
|
+
)
|
105
|
+
unless(result.status == 200)
|
106
|
+
raise Error::ApiError::AuthenticationError.new('Failed to authenticate', :response => result)
|
107
|
+
end
|
108
|
+
info = MultiJson.load(result.body.to_s).to_smash
|
109
|
+
info = info[:access]
|
110
|
+
@user = info[:user]
|
111
|
+
@service_catalog = info[:serviceCatalog]
|
112
|
+
@token = info[:token]
|
113
|
+
token[:expires] = Time.parse(token[:expires])
|
114
|
+
true
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
# Authentication implementation compatible for v2
|
120
|
+
class Version3 < Authenticate
|
121
|
+
|
122
|
+
# @return [Smash] authentication request body
|
123
|
+
def authentication_request
|
124
|
+
ident = Smash.new(:methods => [])
|
125
|
+
if(credentials[:open_stack_password])
|
126
|
+
ident[:methods] << 'password'
|
127
|
+
ident[:password] = Smash.new(
|
128
|
+
:user => Smash.new(
|
129
|
+
:password => credentials[:open_stack_password]
|
130
|
+
)
|
131
|
+
)
|
132
|
+
if(credentials[:open_stack_user_id])
|
133
|
+
ident[:password][:user][:id] = credentials[:open_stack_user_id]
|
134
|
+
else
|
135
|
+
ident[:password][:user][:name] = credentials[:open_stack_username]
|
136
|
+
end
|
137
|
+
if(credentials[:open_stack_domain])
|
138
|
+
ident[:password][:user][:domain] = Smash.new(
|
139
|
+
:name => credentials[:open_stack_domain]
|
140
|
+
)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
if(credentials[:open_stack_token])
|
144
|
+
ident[:methods] << 'token'
|
145
|
+
ident[:token] = Smash.new(
|
146
|
+
:token => Smash.new(
|
147
|
+
:id => credentials[:open_stack_token]
|
148
|
+
)
|
149
|
+
)
|
150
|
+
end
|
151
|
+
if(credentials[:open_stack_project_id])
|
152
|
+
scope = Smash.new(
|
153
|
+
:project => Smash.new(
|
154
|
+
:id => credentials[:open_stack_project_id]
|
155
|
+
)
|
156
|
+
)
|
157
|
+
else
|
158
|
+
if(credentials[:open_stack_domain])
|
159
|
+
scope = Smash.new(
|
160
|
+
:domain => Smash.new(
|
161
|
+
:name => credentials[:open_stack_domain]
|
162
|
+
)
|
163
|
+
)
|
164
|
+
if(credentials[:open_stack_project])
|
165
|
+
scope[:project] = Smash.new(
|
166
|
+
:name => credentials[:open_stack_project]
|
167
|
+
)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
auth = Smash.new(:identity => ident)
|
172
|
+
if(scope)
|
173
|
+
auth[:scope] = scope
|
174
|
+
end
|
175
|
+
auth
|
176
|
+
end
|
177
|
+
|
178
|
+
# Identify with authentication service and load
|
179
|
+
# token information and service catalog
|
180
|
+
#
|
181
|
+
# @return [TrueClass]
|
182
|
+
def identify_and_load
|
183
|
+
result = HTTP.post(
|
184
|
+
File.join(credentials[:open_stack_identity_url], 'tokens'),
|
185
|
+
:json => Smash.new(
|
186
|
+
:auth => authentication_request
|
187
|
+
)
|
188
|
+
)
|
189
|
+
unless(result.status == 200)
|
190
|
+
raise Error::ApiError::AuthenticationError.new('Failed to authenticate!', result)
|
191
|
+
end
|
192
|
+
info = MultiJson.load(result.body.to_s).to_smash[:token]
|
193
|
+
@service_catalog = info.delete(:catalog)
|
194
|
+
@token = Smash.new(
|
195
|
+
:expires => Time.parse(info[:expires_at]),
|
196
|
+
:id => result.headers['X-Subject-Token']
|
197
|
+
)
|
198
|
+
@user = info[:user][:name]
|
199
|
+
true
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
# Common API methods
|
207
|
+
module ApiCommon
|
208
|
+
|
209
|
+
# Set attributes into model
|
210
|
+
#
|
211
|
+
# @param klass [Class]
|
212
|
+
def self.included(klass)
|
213
|
+
klass.class_eval do
|
214
|
+
attribute :open_stack_identity_url, String, :required => true
|
215
|
+
attribute :open_stack_username, String
|
216
|
+
attribute :open_stack_user_id, String
|
217
|
+
attribute :open_stack_password, String
|
218
|
+
attribute :open_stack_token, String
|
219
|
+
attribute :open_stack_region, String
|
220
|
+
attribute :open_stack_tenant_name, String
|
221
|
+
attribute :open_stack_domain, String
|
222
|
+
attribute :open_stack_project, String
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# @return [HTTP] with auth token provided
|
227
|
+
def connection
|
228
|
+
super.with_headers('X-Auth-Token' => token)
|
229
|
+
end
|
230
|
+
|
231
|
+
# @return [String] endpoint URL
|
232
|
+
def endpoint
|
233
|
+
open_stack_api.endpoint_for(
|
234
|
+
Utils.snake(self.class.to_s.split('::')[-2]).to_sym,
|
235
|
+
open_stack_region
|
236
|
+
)
|
237
|
+
end
|
238
|
+
|
239
|
+
# @return [String] valid API token
|
240
|
+
def token
|
241
|
+
open_stack_api.api_token
|
242
|
+
end
|
243
|
+
|
244
|
+
# @return [Miasma::Contrib::OpenStackApiCore]
|
245
|
+
def open_stack_api
|
246
|
+
key = "miasma_open_stack_api_#{attributes.checksum}".to_sym
|
247
|
+
memoize(key, :direct) do
|
248
|
+
Miasma::Contrib::OpenStackApiCore.new(attributes)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
|
254
|
+
# @return [Smash] Mapping to external service name
|
255
|
+
API_MAP = Smash.new(
|
256
|
+
'compute' => 'nova',
|
257
|
+
'orchestration' => 'heat',
|
258
|
+
'network' => 'neutron',
|
259
|
+
'identity' => 'keystone'
|
260
|
+
)
|
261
|
+
|
262
|
+
# @return [Miasma::Contrib::OpenStackApiCore::Authenticate]
|
263
|
+
attr_reader :identity
|
264
|
+
|
265
|
+
# Create a new api instance
|
266
|
+
#
|
267
|
+
# @param creds [Smash] credential hash
|
268
|
+
# @return [self]
|
269
|
+
def initialize(creds)
|
270
|
+
@credentials = creds
|
271
|
+
if(creds[:open_stack_identity_url].include?('v3'))
|
272
|
+
@identity = identity_class('Authenticate::Version3').new(creds)
|
273
|
+
elsif(creds[:open_stack_identity_url].include?('v2'))
|
274
|
+
@identity = identity_class('Authenticate::Version2').new(creds)
|
275
|
+
else
|
276
|
+
# @todo allow attribute to override?
|
277
|
+
raise ArgumentError.new('Failed to determine Identity service version')
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# @return [Class] class from instance class, falls back to parent
|
282
|
+
def identity_class(i_name)
|
283
|
+
[self.class, Miasma::Contrib::OpenStackApiCore].map do |klass|
|
284
|
+
i_name.split('::').inject(klass) do |memo, key|
|
285
|
+
if(memo.const_defined?(key))
|
286
|
+
memo.const_get(key)
|
287
|
+
else
|
288
|
+
break
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end.compact.first
|
292
|
+
end
|
293
|
+
|
294
|
+
# Provide end point URL for service
|
295
|
+
#
|
296
|
+
# @param api_name [String] name of api
|
297
|
+
# @param region [String] region in use
|
298
|
+
# @return [String] public URL
|
299
|
+
def endpoint_for(api_name, region)
|
300
|
+
api = self.class.const_get(:API_MAP)[api_name]
|
301
|
+
srv = identity.service_catalog.detect do |info|
|
302
|
+
info[:name] == api
|
303
|
+
end
|
304
|
+
unless(srv)
|
305
|
+
raise NotImplementedError.new("No API mapping found for `#{api_name}`")
|
306
|
+
end
|
307
|
+
if(region)
|
308
|
+
point = srv[:endpoints].detect do |endpoint|
|
309
|
+
endpoint[:region].to_s.downcase == region.to_s.downcase
|
310
|
+
end
|
311
|
+
else
|
312
|
+
point = srv[:endpoints].first
|
313
|
+
end
|
314
|
+
if(point)
|
315
|
+
point.fetch(
|
316
|
+
:publicURL,
|
317
|
+
point[:url]
|
318
|
+
)
|
319
|
+
else
|
320
|
+
raise KeyError.new("Lookup failed for `#{api_name}` within region `#{region}`")
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# @return [String] API token
|
325
|
+
def api_token
|
326
|
+
identity.api_token
|
327
|
+
end
|
328
|
+
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
Models::Compute.autoload :OpenStack, 'miasma/contrib/open_stack/compute'
|
333
|
+
Models::Orchestration.autoload :OpenStack, 'miasma/contrib/open_stack/orchestration'
|
334
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Models
|
5
|
+
class Compute
|
6
|
+
class OpenStack < Compute
|
7
|
+
|
8
|
+
include Contrib::OpenStackApiCore::ApiCommon
|
9
|
+
|
10
|
+
# @return [Smash] map state to valid internal values
|
11
|
+
SERVER_STATE_MAP = Smash.new(
|
12
|
+
'ACTIVE' => :running,
|
13
|
+
'DELETED' => :terminated,
|
14
|
+
'SUSPENDED' => :stopped,
|
15
|
+
'PASSWORD' => :running
|
16
|
+
)
|
17
|
+
|
18
|
+
def server_save(server)
|
19
|
+
unless(server.persisted?)
|
20
|
+
server.load_data(server.attributes)
|
21
|
+
result = request(
|
22
|
+
:expects => 202,
|
23
|
+
:method => :post,
|
24
|
+
:path => '/servers',
|
25
|
+
:json => {
|
26
|
+
:server => {
|
27
|
+
:flavorRef => server.flavor_id,
|
28
|
+
:name => server.name,
|
29
|
+
:imageRef => server.image_id,
|
30
|
+
:metadata => server.metadata,
|
31
|
+
:personality => server.personality,
|
32
|
+
:key_pair => server.key_name
|
33
|
+
}
|
34
|
+
}
|
35
|
+
)
|
36
|
+
server.id = result.get(:body, :server, :id)
|
37
|
+
else
|
38
|
+
raise "WAT DO I DO!?"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def server_destroy(server)
|
43
|
+
if(server.persisted?)
|
44
|
+
result = request(
|
45
|
+
:expects => 204,
|
46
|
+
:method => :delete,
|
47
|
+
:path => "/servers/#{server.id}"
|
48
|
+
)
|
49
|
+
true
|
50
|
+
else
|
51
|
+
false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def server_change_state(server, state)
|
56
|
+
end
|
57
|
+
|
58
|
+
def server_reload(server)
|
59
|
+
res = servers.reload.all
|
60
|
+
node = res.detect do |s|
|
61
|
+
s.id == server.id
|
62
|
+
end
|
63
|
+
if(node)
|
64
|
+
server.load_data(node.data.dup)
|
65
|
+
server.valid_state
|
66
|
+
else
|
67
|
+
server.data[:state] = :terminated
|
68
|
+
server.dirty.clear
|
69
|
+
server
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def server_all
|
74
|
+
result = request(
|
75
|
+
:method => :get,
|
76
|
+
:path => '/servers/detail'
|
77
|
+
)
|
78
|
+
result[:body].fetch(:servers, []).map do |srv|
|
79
|
+
Server.new(
|
80
|
+
self,
|
81
|
+
:id => srv[:id],
|
82
|
+
:name => srv[:name],
|
83
|
+
:image_id => srv.get(:image, :id),
|
84
|
+
:flavor_id => srv.get(:flavor, :id),
|
85
|
+
:state => SERVER_STATE_MAP.fetch(srv[:status], :pending),
|
86
|
+
:addresses_private => srv.fetch(:addresses, :private, []).map{|a|
|
87
|
+
Server::Address.new(
|
88
|
+
:version => a[:version].to_i, :address => a[:addr]
|
89
|
+
)
|
90
|
+
},
|
91
|
+
:addresses_public => srv.fetch(:addresses, :public, []).map{|a|
|
92
|
+
Server::Address.new(
|
93
|
+
:version => a[:version].to_i, :address => a[:addr]
|
94
|
+
)
|
95
|
+
},
|
96
|
+
:status => srv[:status],
|
97
|
+
:key_name => srv[:key_name]
|
98
|
+
).valid_state
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,255 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Models
|
5
|
+
class Orchestration
|
6
|
+
class OpenStack < Orchestration
|
7
|
+
|
8
|
+
include Contrib::OpenStackApiCore::ApiCommon
|
9
|
+
|
10
|
+
# @return [Smash] external to internal resource mapping
|
11
|
+
RESOURCE_MAPPING = Smash.new(
|
12
|
+
'OS::Nova::Server' => Smash.new(
|
13
|
+
:api => :compute,
|
14
|
+
:collection => :servers
|
15
|
+
)
|
16
|
+
# 'OS::Heat::AutoScalingGroup' => Smash.new(
|
17
|
+
# :api => :auto_scale,
|
18
|
+
# :collection => :groups
|
19
|
+
# )
|
20
|
+
)
|
21
|
+
|
22
|
+
# Save the stack
|
23
|
+
#
|
24
|
+
# @param stack [Models::Orchestration::Stack]
|
25
|
+
# @return [Models::Orchestration::Stack]
|
26
|
+
def stack_save(stack)
|
27
|
+
if(stack.persisted?)
|
28
|
+
stack.load_data(stack.attributes)
|
29
|
+
result = request(
|
30
|
+
:expects => 202,
|
31
|
+
:method => :put,
|
32
|
+
:path => "/stacks/#{stack.name}/#{stack.id}",
|
33
|
+
:json => {
|
34
|
+
:stack_name => stack.name,
|
35
|
+
:template => MultiJson.dump(stack.template),
|
36
|
+
:parameters => stack.parameters || {}
|
37
|
+
}
|
38
|
+
)
|
39
|
+
stack.valid_state
|
40
|
+
else
|
41
|
+
stack.load_data(stack.attributes)
|
42
|
+
result = request(
|
43
|
+
:expects => 201,
|
44
|
+
:method => :post,
|
45
|
+
:path => '/stacks',
|
46
|
+
:json => {
|
47
|
+
:stack_name => stack.name,
|
48
|
+
:template => MultiJson.dump(stack.template),
|
49
|
+
:parameters => stack.parameters || {},
|
50
|
+
:disable_rollback => (!!stack.disable_rollback).to_s
|
51
|
+
}
|
52
|
+
)
|
53
|
+
stack.id = result.get(:body, :stack, :id)
|
54
|
+
stack.valid_state
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Reload the stack data from the API
|
59
|
+
#
|
60
|
+
# @param stack [Models::Orchestration::Stack]
|
61
|
+
# @return [Models::Orchestration::Stack]
|
62
|
+
def stack_reload(stack)
|
63
|
+
if(stack.persisted?)
|
64
|
+
result = request(
|
65
|
+
:method => :get,
|
66
|
+
:path => "/stacks/#{stack.name}/#{stack.id}",
|
67
|
+
:expects => 200
|
68
|
+
)
|
69
|
+
stk = result.get(:body, :stack)
|
70
|
+
stack.load_data(
|
71
|
+
:id => stk[:id],
|
72
|
+
:capabilities => stk[:capabilities],
|
73
|
+
:created => Time.parse(stk[:creation_time]),
|
74
|
+
:description => stk[:description],
|
75
|
+
:disable_rollback => stk[:disable_rollback].to_s.downcase == 'true',
|
76
|
+
:notification_topics => stk[:notification_topics],
|
77
|
+
:name => stk[:stack_name],
|
78
|
+
:state => stk[:stack_status].downcase.to_sym,
|
79
|
+
:status => stk[:stack_status],
|
80
|
+
:status_reason => stk[:stack_status_reason],
|
81
|
+
:template_description => stk[:template_description],
|
82
|
+
:timeout_in_minutes => stk[:timeout_mins].to_s.empty? ? nil : stk[:timeout_mins].to_i,
|
83
|
+
:updated => stk[:updated_time].to_s.empty? ? nil : Time.parse(stk[:updated_time]),
|
84
|
+
:parameters => stk.fetch(:parameters, Smash.new),
|
85
|
+
:outputs => stk.fetch(:outputs, []).map{ |output|
|
86
|
+
Smash.new(
|
87
|
+
:key => output[:output_key],
|
88
|
+
:value => output[:output_value],
|
89
|
+
:description => output[:description]
|
90
|
+
)
|
91
|
+
}
|
92
|
+
).valid_state
|
93
|
+
end
|
94
|
+
stack
|
95
|
+
end
|
96
|
+
|
97
|
+
# Delete the stack
|
98
|
+
#
|
99
|
+
# @param stack [Models::Orchestration::Stack]
|
100
|
+
# @return [TrueClass, FalseClass]
|
101
|
+
def stack_destroy(stack)
|
102
|
+
if(stack.persisted?)
|
103
|
+
request(
|
104
|
+
:method => :delete,
|
105
|
+
:path => "/stacks/#{stack.name}/#{stack.id}",
|
106
|
+
:expects => 204
|
107
|
+
)
|
108
|
+
true
|
109
|
+
else
|
110
|
+
false
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Fetch stack template
|
115
|
+
#
|
116
|
+
# @param stack [Stack]
|
117
|
+
# @return [Smash] stack template
|
118
|
+
def stack_template_load(stack)
|
119
|
+
if(stack.persisted?)
|
120
|
+
result = request(
|
121
|
+
:method => :get,
|
122
|
+
:path => "/stacks/#{stack.name}/#{stack.id}/template"
|
123
|
+
)
|
124
|
+
result.fetch(:body, Smash.new)
|
125
|
+
else
|
126
|
+
Smash.new
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Validate stack template
|
131
|
+
#
|
132
|
+
# @param stack [Stack]
|
133
|
+
# @return [NilClass, String] nil if valid, string error message if invalid
|
134
|
+
def stack_template_validate(stack)
|
135
|
+
begin
|
136
|
+
result = request(
|
137
|
+
:method => :post,
|
138
|
+
:path => '/validate',
|
139
|
+
:json => Smash.new(
|
140
|
+
:template => stack.template
|
141
|
+
)
|
142
|
+
)
|
143
|
+
nil
|
144
|
+
rescue Error::ApiError::RequestError => e
|
145
|
+
MultiJson.load(e.response.body.to_s).to_smash.get(:error, :message)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Return all stacks
|
150
|
+
#
|
151
|
+
# @param options [Hash] filter
|
152
|
+
# @return [Array<Models::Orchestration::Stack>]
|
153
|
+
# @todo check if we need any mappings on state set
|
154
|
+
def stack_all(options={})
|
155
|
+
result = request(
|
156
|
+
:method => :get,
|
157
|
+
:path => '/stacks'
|
158
|
+
)
|
159
|
+
result.fetch(:body, :stacks, []).map do |s|
|
160
|
+
Stack.new(
|
161
|
+
self,
|
162
|
+
:id => s[:id],
|
163
|
+
:creation_time => Time.parse(s[:creation_time]),
|
164
|
+
:description => s[:description],
|
165
|
+
:name => s[:stack_name],
|
166
|
+
:state => s[:stack_status].downcase.to_sym,
|
167
|
+
:status => s[:stack_status],
|
168
|
+
:status_reason => s[:stack_status_reason],
|
169
|
+
:updated => s[:updated_time].to_s.empty? ? nil : Time.parse(s[:updated_time])
|
170
|
+
).valid_state
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Return all resources for stack
|
175
|
+
#
|
176
|
+
# @param stack [Models::Orchestration::Stack]
|
177
|
+
# @return [Array<Models::Orchestration::Stack::Resource>]
|
178
|
+
def resource_all(stack)
|
179
|
+
result = request(
|
180
|
+
:method => :get,
|
181
|
+
:path => "/stacks/#{stack.name}/#{stack.id}/resources",
|
182
|
+
:expects => 200
|
183
|
+
)
|
184
|
+
result.fetch(:body, :resources, []).map do |resource|
|
185
|
+
Stack::Resource.new(
|
186
|
+
stack,
|
187
|
+
:id => resource[:physical_resource_id],
|
188
|
+
:name => resource[:resource_name],
|
189
|
+
:type => resource[:resource_type],
|
190
|
+
:logical_id => resource[:logical_resource_id],
|
191
|
+
:state => resource[:resource_status].downcase.to_sym,
|
192
|
+
:status => resource[:resource_status],
|
193
|
+
:status_reason => resource[:resource_status_reason],
|
194
|
+
:updated => Time.parse(resource[:updated_time])
|
195
|
+
).valid_state
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Reload the stack resource data from the API
|
200
|
+
#
|
201
|
+
# @param resource [Models::Orchestration::Stack::Resource]
|
202
|
+
# @return [Models::Orchestration::Resource]
|
203
|
+
def resource_reload(resource)
|
204
|
+
resource.stack.resources.reload
|
205
|
+
resource.stack.resources.get(resource.id)
|
206
|
+
end
|
207
|
+
|
208
|
+
# Return all events for stack
|
209
|
+
#
|
210
|
+
# @param stack [Models::Orchestration::Stack]
|
211
|
+
# @return [Array<Models::Orchestration::Stack::Event>]
|
212
|
+
def event_all(stack, marker = nil)
|
213
|
+
params = marker ? {:marker => marker} : {}
|
214
|
+
result = request(
|
215
|
+
:path => "/stacks/#{stack.name}/#{stack.id}/events",
|
216
|
+
:method => :get,
|
217
|
+
:expects => 200,
|
218
|
+
:params => params
|
219
|
+
)
|
220
|
+
result.fetch(:body, :events, []).map do |event|
|
221
|
+
Stack::Event.new(
|
222
|
+
stack,
|
223
|
+
:id => event[:id],
|
224
|
+
:resource_id => event[:physical_resource_id],
|
225
|
+
:resource_name => event[:resource_name],
|
226
|
+
:resource_logical_id => event[:logical_resource_id],
|
227
|
+
:resource_state => event[:resource_status].downcase.to_sym,
|
228
|
+
:resource_status => event[:resource_status],
|
229
|
+
:resource_status_reason => event[:resource_status_reason],
|
230
|
+
:time => Time.parse(event[:event_time])
|
231
|
+
).valid_state
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Return all new events for event collection
|
236
|
+
#
|
237
|
+
# @param events [Models::Orchestration::Stack::Events]
|
238
|
+
# @return [Array<Models::Orchestration::Stack::Event>]
|
239
|
+
def event_all_new(events)
|
240
|
+
event_all(events.stack, events.all.first.id)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Reload the stack event data from the API
|
244
|
+
#
|
245
|
+
# @param resource [Models::Orchestration::Stack::Event]
|
246
|
+
# @return [Models::Orchestration::Event]
|
247
|
+
def event_reload(event)
|
248
|
+
event.stack.events.reload
|
249
|
+
event.stack.events.get(event.id)
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|