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,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
@@ -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