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