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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2 -0
- data/README.md +179 -0
- data/lib/miasma.rb +52 -0
- data/lib/miasma/contrib/aws.rb +390 -0
- data/lib/miasma/contrib/aws/auto_scale.rb +85 -0
- data/lib/miasma/contrib/aws/compute.rb +112 -0
- data/lib/miasma/contrib/aws/load_balancer.rb +185 -0
- data/lib/miasma/contrib/aws/orchestration.rb +338 -0
- data/lib/miasma/contrib/rackspace.rb +164 -0
- data/lib/miasma/contrib/rackspace/auto_scale.rb +84 -0
- data/lib/miasma/contrib/rackspace/compute.rb +104 -0
- data/lib/miasma/contrib/rackspace/load_balancer.rb +117 -0
- data/lib/miasma/contrib/rackspace/orchestration.rb +255 -0
- data/lib/miasma/error.rb +89 -0
- data/lib/miasma/models.rb +14 -0
- data/lib/miasma/models/auto_scale.rb +55 -0
- data/lib/miasma/models/auto_scale/group.rb +64 -0
- data/lib/miasma/models/auto_scale/groups.rb +34 -0
- data/lib/miasma/models/block_storage.rb +0 -0
- data/lib/miasma/models/compute.rb +70 -0
- data/lib/miasma/models/compute/server.rb +71 -0
- data/lib/miasma/models/compute/servers.rb +35 -0
- data/lib/miasma/models/dns.rb +0 -0
- data/lib/miasma/models/load_balancer.rb +55 -0
- data/lib/miasma/models/load_balancer/balancer.rb +72 -0
- data/lib/miasma/models/load_balancer/balancers.rb +34 -0
- data/lib/miasma/models/monitoring.rb +0 -0
- data/lib/miasma/models/orchestration.rb +127 -0
- data/lib/miasma/models/orchestration/event.rb +38 -0
- data/lib/miasma/models/orchestration/events.rb +64 -0
- data/lib/miasma/models/orchestration/resource.rb +79 -0
- data/lib/miasma/models/orchestration/resources.rb +55 -0
- data/lib/miasma/models/orchestration/stack.rb +144 -0
- data/lib/miasma/models/orchestration/stacks.rb +46 -0
- data/lib/miasma/models/queues.rb +0 -0
- data/lib/miasma/models/storage.rb +60 -0
- data/lib/miasma/models/storage/bucket.rb +36 -0
- data/lib/miasma/models/storage/buckets.rb +41 -0
- data/lib/miasma/models/storage/file.rb +45 -0
- data/lib/miasma/models/storage/files.rb +52 -0
- data/lib/miasma/types.rb +13 -0
- data/lib/miasma/types/api.rb +145 -0
- data/lib/miasma/types/collection.rb +116 -0
- data/lib/miasma/types/data.rb +53 -0
- data/lib/miasma/types/model.rb +118 -0
- data/lib/miasma/types/thin_model.rb +76 -0
- data/lib/miasma/utils.rb +12 -0
- data/lib/miasma/utils/animal_strings.rb +29 -0
- data/lib/miasma/utils/immutable.rb +36 -0
- data/lib/miasma/utils/lazy.rb +231 -0
- data/lib/miasma/utils/memoization.rb +55 -0
- data/lib/miasma/utils/smash.rb +149 -0
- data/lib/miasma/version.rb +4 -0
- data/miasma.gemspec +18 -0
- metadata +57 -3
@@ -0,0 +1,255 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Models
|
5
|
+
class Orchestration
|
6
|
+
class Rackspace < Orchestration
|
7
|
+
|
8
|
+
include Contrib::RackspaceApiCore::ApiCommon
|
9
|
+
|
10
|
+
# @return [Smash] external to internal resource mapping
|
11
|
+
RESOURCE_MAPPING = Smash.new(
|
12
|
+
'Rackspace::Cloud::Server' => Smash.new(
|
13
|
+
:api => :compute,
|
14
|
+
:collection => :servers
|
15
|
+
),
|
16
|
+
'Rackspace::AutoScale::Group' => 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
|
+
:creation_time => 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_time => 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_time => 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 => 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
|
data/lib/miasma/error.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
# Generic Error class
|
5
|
+
class Error < StandardError
|
6
|
+
|
7
|
+
# Create new error instance
|
8
|
+
#
|
9
|
+
# @param msg [String] error message
|
10
|
+
# @param args [Hash] optional arguments
|
11
|
+
# @return [self]
|
12
|
+
def initialize(msg, args={})
|
13
|
+
super msg
|
14
|
+
end
|
15
|
+
|
16
|
+
# Api related errors
|
17
|
+
class ApiError < Error
|
18
|
+
|
19
|
+
# @return [HTTP::Response] result of bad request
|
20
|
+
attr_reader :response
|
21
|
+
# @return [String] response error message
|
22
|
+
attr_reader :response_error_msg
|
23
|
+
|
24
|
+
# Create new API error instance
|
25
|
+
#
|
26
|
+
# @param msg [String] error message
|
27
|
+
# @param args [Hash] optional arguments
|
28
|
+
# @option args [HTTP::Response] :response response from request
|
29
|
+
def initialize(msg, args={})
|
30
|
+
super
|
31
|
+
@response = args.to_smash[:response]
|
32
|
+
extract_error_message(@response)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [String] provides response error suffix
|
36
|
+
def message
|
37
|
+
[@message, @response_error_msg].compact.join(' - ')
|
38
|
+
end
|
39
|
+
|
40
|
+
# Attempt to extract error message from response
|
41
|
+
#
|
42
|
+
# @param response [HTTP::Response]
|
43
|
+
# @return [String, NilClass]
|
44
|
+
def extract_error_message(response)
|
45
|
+
begin
|
46
|
+
begin
|
47
|
+
content = MultiJson.load(response.body.to_s).to_smash
|
48
|
+
msgs = content.values.map do |arg|
|
49
|
+
arg[:message]
|
50
|
+
end.compact
|
51
|
+
unless(msgs.empty?)
|
52
|
+
@response_error_msg = msgs.join(' - ')
|
53
|
+
end
|
54
|
+
rescue MultiJson::ParseError
|
55
|
+
begin
|
56
|
+
content = MultiXml.parse(response.body.to_s).to_smash
|
57
|
+
if(content.get('ErrorResponse', 'Error'))
|
58
|
+
@response_error_msg = "#{content.get('ErrorResponse', 'Error', 'Code')}: #{content.get('ErrorResponse', 'Error', 'Message')}"
|
59
|
+
end
|
60
|
+
rescue MultiXml::ParseError
|
61
|
+
content = Smash.new
|
62
|
+
end
|
63
|
+
rescue
|
64
|
+
# do nothing
|
65
|
+
end
|
66
|
+
end
|
67
|
+
@response_error_msg
|
68
|
+
end
|
69
|
+
|
70
|
+
# Api request error
|
71
|
+
class RequestError < ApiError; end
|
72
|
+
|
73
|
+
# Api authentication error
|
74
|
+
class AuthenticationError < ApiError; end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
# Orchestration error
|
79
|
+
class OrchestrationError < Error
|
80
|
+
# Template failed to validate
|
81
|
+
class InvalidTemplate < OrchestrationError
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Invalid modification request
|
86
|
+
class ImmutableError < Error; end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Models
|
5
|
+
autoload :AutoScale, 'miasma/models/auto_scale'
|
6
|
+
autoload :BlockStorage, 'miasma/models/block_storage'
|
7
|
+
autoload :Compute, 'miasma/models/compute'
|
8
|
+
autoload :Dns, 'miasma/models/dns'
|
9
|
+
autoload :LoadBalancer, 'miasma/models/load_balancer'
|
10
|
+
autoload :Orchestration, 'miasma/models/orchestration'
|
11
|
+
autoload :Queues, 'miasma/models/queues'
|
12
|
+
autoload :Storage, 'miasma/models/storage'
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Models
|
5
|
+
# Abstract auto scale API
|
6
|
+
class AutoScale < Types::Api
|
7
|
+
|
8
|
+
autoload :Group, 'miasma/models/auto_scale/group'
|
9
|
+
autoload :Groups, 'miasma/models/auto_scale/groups'
|
10
|
+
|
11
|
+
# Auto scale groups
|
12
|
+
#
|
13
|
+
# @param filter [Hash] filtering options
|
14
|
+
# @return [Types::Collection<Models::AutoScale::Groups>] auto scale groups
|
15
|
+
def groups(filter={})
|
16
|
+
memoize(:groups) do
|
17
|
+
Groups.new(self)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Save auto scale group
|
22
|
+
#
|
23
|
+
# @param group [Models::AutoScale::Group]
|
24
|
+
# @return [Models::AutoScale::Group]
|
25
|
+
def group_save(group)
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
# Reload the group data from the API
|
30
|
+
#
|
31
|
+
# @param group [Models::AutoScale::Group]
|
32
|
+
# @return [Models::AutoScale::Group]
|
33
|
+
def group_reload(group)
|
34
|
+
raise NotImplementedError
|
35
|
+
end
|
36
|
+
|
37
|
+
# Delete auto scale group
|
38
|
+
#
|
39
|
+
# @param group [Models::AutoScale::Group]
|
40
|
+
# @return [TrueClass, FalseClass]
|
41
|
+
def group_destroy(group)
|
42
|
+
raise NotImplementedError
|
43
|
+
end
|
44
|
+
|
45
|
+
# Return all auto scale groups
|
46
|
+
#
|
47
|
+
# @param options [Hash] filter
|
48
|
+
# @return [Array<Models::AutoScale::Group>]
|
49
|
+
def group_all(options={})
|
50
|
+
raise NotImplementedError
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Models
|
5
|
+
class AutoScale
|
6
|
+
# Abstract group
|
7
|
+
class Group < Types::Model
|
8
|
+
|
9
|
+
class Server < Types::ThinModel
|
10
|
+
|
11
|
+
model Miasma::Models::Compute::Server
|
12
|
+
attribute :name, String
|
13
|
+
|
14
|
+
# @return [Miasma::Models::Compute::Server]
|
15
|
+
def expand
|
16
|
+
api.api_for(:compute).servers.get(self.id || self.name)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Balancer < Types::ThinModel
|
21
|
+
|
22
|
+
model Miasma::Models::LoadBalancer::Balancer
|
23
|
+
attribute :name, String
|
24
|
+
|
25
|
+
# @return [Miasma::Models::LoadBalancer::Balancer]
|
26
|
+
def expand
|
27
|
+
api.api_for(:load_balancer).balancers.get(self.id || self.name)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
attribute :name, String, :required => true
|
33
|
+
attribute :created, Time, :coerce => lambda{|v| Time.parse(v.to_s)}
|
34
|
+
attribute :load_balancers, Balancer, :multiple => true, :coerce => lambda{|v,obj| Balancer.new(obj.api, v)}
|
35
|
+
attribute :minimum_size, Integer, :coerce => lambda{|v| v.to_i}
|
36
|
+
attribute :maximum_size, Integer, :coerce => lambda{|v| v.to_i}
|
37
|
+
attribute :desired_size, Integer, :coerce => lambda{|v| v.to_i}
|
38
|
+
attribute :current_size, Integer, :coerce => lambda{|v| v.to_i}
|
39
|
+
attribute :state, Symbol, :allowed_values => []
|
40
|
+
attribute :servers, Server, :multiple => true, :coerce => lambda{|v,obj| Server.new(obj.api, v)}
|
41
|
+
|
42
|
+
on_missing :reload
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
# Proxy save action up to the API
|
47
|
+
def perform_save
|
48
|
+
api.group_save(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Proxy reload action up to the API
|
52
|
+
def perform_reload
|
53
|
+
api.group_reload(self)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Proxy destroy action up to the API
|
57
|
+
def perform_destroy
|
58
|
+
api.group_destroy(self)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|