miasma 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,36 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Models
|
5
|
+
class Storage
|
6
|
+
# Abstract bucket
|
7
|
+
class Bucket < Types::Model
|
8
|
+
|
9
|
+
attribute :metadata, Hash, :coerce => lambda{|o| o.to_smash}
|
10
|
+
|
11
|
+
# Filter buckets
|
12
|
+
#
|
13
|
+
# @param filter [Hash]
|
14
|
+
# @return [Array<Bucket>]
|
15
|
+
def filter(filter={})
|
16
|
+
end
|
17
|
+
|
18
|
+
# Rename bucket
|
19
|
+
#
|
20
|
+
# @param new_name [String]
|
21
|
+
# @return [self]
|
22
|
+
def rename(new_name)
|
23
|
+
perform_rename(new_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def perform_rename(new_name)
|
29
|
+
api.bucket_rename(self, new_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Models
|
5
|
+
# Abstract storage API
|
6
|
+
class Storage
|
7
|
+
|
8
|
+
# Abstract bucket collection
|
9
|
+
class Buckets < Types::Collection
|
10
|
+
|
11
|
+
# Return buckets matching given filter
|
12
|
+
#
|
13
|
+
# @param options [Hash] filter options
|
14
|
+
# @return [Array<Bucket>]
|
15
|
+
# @option options [String] :prefix key prefix
|
16
|
+
def filter(options={})
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Bucket] new unsaved instance
|
21
|
+
def build(args={})
|
22
|
+
Bucket.new(api, args.to_smash)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Bucket] collection item class
|
26
|
+
def model
|
27
|
+
Bucket
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
# @return [Array<Bucket>]
|
33
|
+
def perform_population
|
34
|
+
api.bucket_all
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
module Miasma
|
5
|
+
module Models
|
6
|
+
class Storage
|
7
|
+
|
8
|
+
# Abstract file
|
9
|
+
class File < Types::Collection
|
10
|
+
|
11
|
+
attribute :body, IO, :coerce => lambda{|v| StringIO.new(v.to_s) }
|
12
|
+
attribute :content_type, String
|
13
|
+
attribute :content_disposition, String
|
14
|
+
attribute :etag, String
|
15
|
+
attribute :last_modified, DateTime
|
16
|
+
attribute :size, Integer
|
17
|
+
attribute :metadata, Hash, :coerce => lambda{|o| o.to_smash}
|
18
|
+
|
19
|
+
# @return [Bucket] parent bucket
|
20
|
+
attr_reader :bucket
|
21
|
+
|
22
|
+
# Create a new instance
|
23
|
+
#
|
24
|
+
# @param bucket [Bucket]
|
25
|
+
# @param args [Hash]
|
26
|
+
# @return [self]
|
27
|
+
def initialize(bucket, args={})
|
28
|
+
@bucket = bucket
|
29
|
+
super bucket.api, args
|
30
|
+
end
|
31
|
+
|
32
|
+
# Return files matching given filter
|
33
|
+
#
|
34
|
+
# @param options [Hash] filter options
|
35
|
+
# @return [Array<File>]
|
36
|
+
# @option options [String] :prefix key prefix
|
37
|
+
def filter(options={})
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Models
|
5
|
+
class Storage
|
6
|
+
|
7
|
+
# Abstract file collection
|
8
|
+
class Files < Types::Collection
|
9
|
+
|
10
|
+
# @return [Bucket] parent bucket
|
11
|
+
attr_reader :bucket
|
12
|
+
|
13
|
+
# Create new instance
|
14
|
+
#
|
15
|
+
# @param bucket [Bucket] parent bucket
|
16
|
+
# @return [self]
|
17
|
+
def initialize(bucket)
|
18
|
+
@bucket = bucket
|
19
|
+
super bucket.api
|
20
|
+
end
|
21
|
+
|
22
|
+
# Return files matching given filter
|
23
|
+
#
|
24
|
+
# @param options [Hash] filter options
|
25
|
+
# @return [Array<File>]
|
26
|
+
# @option options [String] :prefix key prefix
|
27
|
+
def filter(options={})
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [File] new unsaved instance
|
32
|
+
def build(args={})
|
33
|
+
File.new(api, args.to_smash)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [File] collection item class
|
37
|
+
def model
|
38
|
+
File
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
# @return [Array<File>]
|
44
|
+
def perform_population
|
45
|
+
api.file_all(bucket)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/miasma/types.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Types
|
5
|
+
|
6
|
+
autoload :Api, 'miasma/types/api'
|
7
|
+
autoload :Collection, 'miasma/types/collection'
|
8
|
+
autoload :Model, 'miasma/types/model'
|
9
|
+
autoload :ThinModel, 'miasma/types/thin_model'
|
10
|
+
autoload :Data, 'miasma/types/data'
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Types
|
5
|
+
|
6
|
+
# Remote API connection
|
7
|
+
class Api
|
8
|
+
|
9
|
+
include Miasma::Utils::Lazy
|
10
|
+
include Miasma::Utils::Memoization
|
11
|
+
|
12
|
+
# Create new API connection
|
13
|
+
#
|
14
|
+
# @param creds [Smash] credentials
|
15
|
+
# @return [self]
|
16
|
+
def initialize(creds)
|
17
|
+
if(creds.is_a?(Hash))
|
18
|
+
load_data(creds)
|
19
|
+
else
|
20
|
+
raise TypeError.new "Expecting `credentials` to be of type `Hash`. Received: `#{creds.class}`"
|
21
|
+
end
|
22
|
+
connect
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Symbol] name of provider
|
26
|
+
def provider
|
27
|
+
Utils.snake(self.class.to_s.split('::').last).to_sym
|
28
|
+
end
|
29
|
+
|
30
|
+
# Connect to the remote API
|
31
|
+
#
|
32
|
+
# @return [self]
|
33
|
+
def connect
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# Build new API for specified type using current provider / creds
|
38
|
+
#
|
39
|
+
# @param type [Symbol] api type
|
40
|
+
# @return [Api]
|
41
|
+
def api_for(type)
|
42
|
+
memoize(type) do
|
43
|
+
Miasma.api(
|
44
|
+
Smash.new(
|
45
|
+
:type => type,
|
46
|
+
:provider => provider,
|
47
|
+
:credentials => attributes
|
48
|
+
)
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [HTTP]
|
54
|
+
def connection
|
55
|
+
HTTP.with_headers('User-Agent' => "miasma/v#{Miasma::VERSION}")
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [String] url endpoint
|
59
|
+
def endpoint
|
60
|
+
'http://api.example.com'
|
61
|
+
end
|
62
|
+
|
63
|
+
# Perform request to remote API
|
64
|
+
#
|
65
|
+
# @param args [Hash] options
|
66
|
+
# @option args [String, Symbol] :method HTTP request method
|
67
|
+
# @option args [String] :path request path
|
68
|
+
# @option args [Integer] :expects expected response status code
|
69
|
+
# @return [Smash] {:result => HTTP::Response, :headers => Smash, :body => Object}
|
70
|
+
# @raises [Error::ApiError::RequestError]
|
71
|
+
def request(args)
|
72
|
+
args = args.to_smash
|
73
|
+
http_method = args.fetch(:method, 'get').to_s.downcase.to_sym
|
74
|
+
unless(HTTP::Request::METHODS.include?(http_method))
|
75
|
+
raise ArgumentError.new 'Invalid request method provided!'
|
76
|
+
end
|
77
|
+
request_args = [].tap do |ary|
|
78
|
+
ary.push(
|
79
|
+
File.join(endpoint, args[:path].to_s)
|
80
|
+
)
|
81
|
+
options = {}.tap do |opts|
|
82
|
+
[:form, :params, :json, :body].each do |key|
|
83
|
+
opts[key] = args[key] if args[key]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
ary.push(options) unless options.empty?
|
87
|
+
end
|
88
|
+
if(args[:headers])
|
89
|
+
_connection = connection.with_headers(args[:headers])
|
90
|
+
args.delete(:headers)
|
91
|
+
else
|
92
|
+
_connection = connection
|
93
|
+
end
|
94
|
+
result = make_request(_connection, http_method, request_args)
|
95
|
+
unless(result.code == args.fetch(:expects, 200).to_i)
|
96
|
+
raise Error::ApiError::RequestError.new(result.reason, :response => result)
|
97
|
+
end
|
98
|
+
format_response(result)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Perform request
|
102
|
+
#
|
103
|
+
# @param connection [HTTP]
|
104
|
+
# @param http_method [Symbol]
|
105
|
+
# @param request_args [Array]
|
106
|
+
# @return [HTTP::Response]
|
107
|
+
# @note this is mainly here for concrete APIs to
|
108
|
+
# override if things need to be done prior to
|
109
|
+
# the actual request (like signature generation)
|
110
|
+
def make_request(connection, http_method, request_args)
|
111
|
+
connection.send(http_method, *request_args)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Makes best attempt at formatting response
|
115
|
+
#
|
116
|
+
# @param result [HTTP::Response]
|
117
|
+
# @return [Smash]
|
118
|
+
def format_response(result)
|
119
|
+
extracted_headers = Smash[result.headers.map{|k,v| [Utils.snake(k), v]}]
|
120
|
+
if(extracted_headers[:content_type].include?('application/json'))
|
121
|
+
begin
|
122
|
+
extracted_body = MultiJson.load(result.body.to_s).to_smash
|
123
|
+
rescue MultiJson::ParseError
|
124
|
+
extracted_body = result.body.to_s
|
125
|
+
end
|
126
|
+
elsif(extracted_headers[:content_type].include?('text/xml'))
|
127
|
+
begin
|
128
|
+
extracted_body = MultiXml.parse(result.body.to_s).to_smash
|
129
|
+
rescue MultiXml::ParseError
|
130
|
+
extracted_body = result.body.to_s
|
131
|
+
end
|
132
|
+
else
|
133
|
+
extracted_body = result.body.to_s
|
134
|
+
end
|
135
|
+
Smash.new(
|
136
|
+
:response => result,
|
137
|
+
:headers => extracted_headers,
|
138
|
+
:body => extracted_body
|
139
|
+
)
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'miasma'
|
2
|
+
|
3
|
+
module Miasma
|
4
|
+
module Types
|
5
|
+
|
6
|
+
# Base collection
|
7
|
+
class Collection
|
8
|
+
|
9
|
+
include Utils::Memoization
|
10
|
+
|
11
|
+
# @return [Miasma::Api] underlying service API
|
12
|
+
attr_reader :api
|
13
|
+
|
14
|
+
def initialize(api)
|
15
|
+
@api = api
|
16
|
+
@collection = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Array<Model>]
|
20
|
+
def all
|
21
|
+
memoize(:collection) do
|
22
|
+
perform_population
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Reload the collection
|
27
|
+
#
|
28
|
+
# @return [self]
|
29
|
+
def reload
|
30
|
+
clear_memoizations!
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
# Return model with given name or ID
|
35
|
+
#
|
36
|
+
# @param ident [String, Symbol] model identifier
|
37
|
+
# @return [Model, NilClass]
|
38
|
+
def get(ident)
|
39
|
+
memoize(ident) do
|
40
|
+
perform_get(ident)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Return models matching given filter
|
45
|
+
#
|
46
|
+
# @param args [Hash] filter options
|
47
|
+
# @return [Array<Model>]
|
48
|
+
# @todo need to add helper to deep sort args, convert to string
|
49
|
+
# and hash to use as memoization key
|
50
|
+
def filter(args={})
|
51
|
+
memoize(args.to_s)
|
52
|
+
raise NotImplementedError
|
53
|
+
end
|
54
|
+
|
55
|
+
# Build a new model
|
56
|
+
#
|
57
|
+
# @param args [Hash] creation options
|
58
|
+
# @return [Model]
|
59
|
+
def build(args={})
|
60
|
+
instance = self.model.new(self.api)
|
61
|
+
args.each do |m_name, m_value|
|
62
|
+
m_name = "#{m_name}="
|
63
|
+
instance.send(m_name, m_value)
|
64
|
+
end
|
65
|
+
instance
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [String] collection of models
|
69
|
+
def to_json(*_)
|
70
|
+
self.all.to_json
|
71
|
+
end
|
72
|
+
|
73
|
+
# Load collection via JSON
|
74
|
+
#
|
75
|
+
# @param json [String]
|
76
|
+
# @return [self]
|
77
|
+
def from_json(json)
|
78
|
+
loaded = MultiJson.load(json)
|
79
|
+
unless(loaded.is_a?(Array))
|
80
|
+
raise TypeError.new "Expecting type `Array` but received `#{loaded.class}`"
|
81
|
+
end
|
82
|
+
unmemoize(:collection)
|
83
|
+
memoize(:collection) do
|
84
|
+
loaded.map do |item|
|
85
|
+
model.from_json(self.api, MultiJson.dump(item))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [Miasma::Types::Model] model class within collection
|
92
|
+
def model
|
93
|
+
raise NotImplementedError
|
94
|
+
end
|
95
|
+
|
96
|
+
protected
|
97
|
+
|
98
|
+
# Return model with given name or ID
|
99
|
+
#
|
100
|
+
# @param ident [String, Symbol] model identifier
|
101
|
+
# @return [Model, NilClass]
|
102
|
+
def perform_get(ident)
|
103
|
+
all.detect do |obj|
|
104
|
+
obj.id == ident ||
|
105
|
+
(obj.respond_to?(:name) && obj.name == ident)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# @return [Array<Model>]
|
110
|
+
def perform_population
|
111
|
+
raise NotImplementedError
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|