miasma 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,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
@@ -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