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,164 @@
1
+ require 'miasma'
2
+ require 'miasma/utils/smash'
3
+ require 'time'
4
+
5
+ module Miasma
6
+ module Contrib
7
+
8
+ # Rackspace API core helper
9
+ class RackspaceApiCore
10
+
11
+ module ApiCommon
12
+
13
+ # Set attributes into model
14
+ #
15
+ # @param klass [Class]
16
+ def self.included(klass)
17
+ klass.class_eval do
18
+ attribute :rackspace_api_key, String, :required => true
19
+ attribute :rackspace_username, String, :required => true
20
+ attribute :rackspace_region, String, :required => true
21
+ end
22
+ end
23
+
24
+ # @return [HTTP] with auth token provided
25
+ def connection
26
+ super.with_headers('X-Auth-Token' => token)
27
+ end
28
+
29
+ # @return [String] endpoint URL
30
+ def endpoint
31
+ rackspace_api.endpoint_for(
32
+ Utils.snake(self.class.to_s.split('::')[-2]).to_sym,
33
+ rackspace_region
34
+ )
35
+ end
36
+
37
+ # @return [String] valid API token
38
+ def token
39
+ rackspace_api.api_token
40
+ end
41
+
42
+ # @return [Miasma::Contrib::RackspaceApiCore]
43
+ def rackspace_api
44
+ key = "miasma_rackspace_api_#{attributes.checksum}".to_sym
45
+ memoize(key, :direct) do
46
+ Miasma::Contrib::RackspaceApiCore.new(attributes)
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ # @return [Smash] Authentication endpoints
53
+ AUTH_ENDPOINT = Smash.new(
54
+ :us => 'https://identity.api.rackspacecloud.com/v2.0',
55
+ :uk => 'https://lon.identity.api.rackspacecloud.com/v2.0'
56
+ )
57
+
58
+ # @return [Smash] Mapping to external service name
59
+ # @note ["cloudFilesCDN", "cloudFiles", "cloudBlockStorage",
60
+ # "cloudImages", "cloudQueues", "cloudBigData",
61
+ # "cloudOrchestration", "cloudServersOpenStack", "autoscale",
62
+ # "cloudDatabases", "cloudBackup", "cloudMetrics",
63
+ # "cloudLoadBalancers", "cloudNetworks", "cloudFeeds",
64
+ # "cloudMonitoring", "cloudDNS"]
65
+
66
+ API_MAP = Smash.new(
67
+ 'compute' => 'cloudServersOpenStack',
68
+ 'orchestration' => 'cloudOrchestration',
69
+ 'auto_scale' => 'autoscale',
70
+ 'load_balancer' => 'cloudLoadBalancers'
71
+ )
72
+
73
+ # @return [String] username
74
+ attr_reader :user
75
+ # @return [Smash] remote service catalog
76
+ attr_reader :service_catalog
77
+ # @return [Smash] token information
78
+ attr_reader :token
79
+ # @return [Smash] credentials in use
80
+ attr_reader :credentials
81
+
82
+ # Create a new api instance
83
+ #
84
+ # @param creds [Smash] credential hash
85
+ # @return [self]
86
+ def initialize(creds)
87
+ @credentials = creds
88
+ end
89
+
90
+ # Provide end point URL for service
91
+ #
92
+ # @param api_name [String] name of api
93
+ # @param region [String] region in use
94
+ # @return [String] public URL
95
+ def endpoint_for(api_name, region)
96
+ identify_and_load unless service_catalog
97
+ api = API_MAP[api_name]
98
+ srv = service_catalog.detect do |info|
99
+ info[:name] == api
100
+ end
101
+ unless(srv)
102
+ raise NotImplementedError.new("No API mapping found for `#{api_name}`")
103
+ end
104
+ region = region.to_s.upcase
105
+ point = srv[:endpoints].detect do |endpoint|
106
+ endpoint[:region] == region
107
+ end
108
+ if(point)
109
+ point[:publicURL]
110
+ end
111
+ end
112
+
113
+ # @return [String] API token
114
+ def api_token
115
+ if(token.nil? || Time.now > token[:expires])
116
+ identify_and_load
117
+ end
118
+ token[:id]
119
+ end
120
+
121
+ # @return [String] ID of account
122
+ def account_id
123
+ if(token.nil? || Time.now > token[:expires])
124
+ identify_and_load
125
+ end
126
+ token[:tenant][:id]
127
+ end
128
+
129
+ # Identify with authentication service and load
130
+ # token information and service catalog
131
+ #
132
+ # @return [TrueClass]
133
+ def identify_and_load
134
+ endpoint = credentials[:rackspace_region].to_s == 'lon' ? AUTH_ENDPOINT[:uk] : AUTH_ENDPOINT[:us]
135
+ result = HTTP.post(File.join(endpoint, 'tokens'),
136
+ :json => {
137
+ 'auth' => {
138
+ 'RAX-KSKEY:apiKeyCredentials' => {
139
+ 'username' => credentials[:rackspace_username],
140
+ 'apiKey' => credentials[:rackspace_api_key]
141
+ }
142
+ }
143
+ }
144
+ )
145
+ unless(result.status == 200)
146
+ raise Error::ApiError::AuthenticationError.new('Failed to authenticate', :response => result)
147
+ end
148
+ info = MultiJson.load(result.body.to_s).to_smash
149
+ info = info[:access]
150
+ @user = info[:user]
151
+ @service_catalog = info[:serviceCatalog]
152
+ @token = info[:token]
153
+ token[:expires] = Time.parse(token[:expires])
154
+ true
155
+ end
156
+
157
+ end
158
+ end
159
+
160
+ Models::Compute.autoload :Rackspace, 'miasma/contrib/rackspace/compute'
161
+ Models::Orchestration.autoload :Rackspace, 'miasma/contrib/rackspace/orchestration'
162
+ Models::AutoScale.autoload :Rackspace, 'miasma/contrib/rackspace/auto_scale'
163
+ Models::LoadBalancer.autoload :Rackspace, 'miasma/contrib/rackspace/load_balancer'
164
+ end
@@ -0,0 +1,84 @@
1
+ require 'miasma'
2
+
3
+ module Miasma
4
+ module Models
5
+ class AutoScale
6
+ class Rackspace < AutoScale
7
+
8
+ include Contrib::RackspaceApiCore::ApiCommon
9
+
10
+ # Save auto scale group
11
+ #
12
+ # @param group [Models::AutoScale::Group]
13
+ # @return [Models::AutoScale::Group]
14
+ def group_save(group)
15
+ raise NotImplementedError
16
+ end
17
+
18
+ # Reload the group data from the API
19
+ #
20
+ # @param group [Models::AutoScale::Group]
21
+ # @return [Models::AutoScale::Group]
22
+ def group_reload(group)
23
+ if(group.persisted?)
24
+ result = request(
25
+ :method => :get,
26
+ :path => "/groups/#{group.id}",
27
+ :expects => 200
28
+ )
29
+ grp = result.get(:body, :group)
30
+ group.load_data(
31
+ :name => grp.get('groupConfiguration', :name),
32
+ :minimum_size => grp.get('groupConfiguration', 'minEntities'),
33
+ :maximum_size => grp.get('groupConfiguration', 'maxEntities'),
34
+ :desired_size => grp.get(:state, 'desiredCapacity'),
35
+ :current_size => grp.get(:state, 'activeCapacity'),
36
+ :servers => grp.get(:state, :active).map{|s| AutoScale::Group::Server.new(self, :id => s[:id])}
37
+ ).valid_state
38
+ else
39
+ group
40
+ end
41
+ end
42
+
43
+ # Delete auto scale group
44
+ #
45
+ # @param group [Models::AutoScale::Group]
46
+ # @return [TrueClass, FalseClass]
47
+ def group_destroy(group)
48
+ if(group.persisted?)
49
+ request(
50
+ :path => "/groups/#{group.id}",
51
+ :method => :delete,
52
+ :expects => 204
53
+ )
54
+ true
55
+ else
56
+ false
57
+ end
58
+ end
59
+
60
+ # Return all auto scale groups
61
+ #
62
+ # @param options [Hash] filter
63
+ # @return [Array<Models::AutoScale::Group>]
64
+ def group_all(options={})
65
+ result = request(
66
+ :method => :get,
67
+ :path => '/groups',
68
+ :expects => 200
69
+ )
70
+ result.fetch(:body, 'groups', []).map do |lb|
71
+ Group.new(
72
+ self,
73
+ :id => lb[:id],
74
+ :name => lb.get(:state, :name),
75
+ :current_size => lb.get(:state, 'activeCapacity'),
76
+ :desired_size => lb.get(:state, 'desiredCapacity')
77
+ ).valid_state
78
+ end
79
+ end
80
+
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,104 @@
1
+ require 'miasma'
2
+
3
+ module Miasma
4
+ module Models
5
+ class Compute
6
+ class Rackspace < Compute
7
+
8
+ include Contrib::RackspaceApiCore::ApiCommon
9
+
10
+ # @return [Smash] map state to valid internal values
11
+ SERVER_STATE_MAP = Smash.new(
12
+ 'ACTIVE' => :running,
13
+ 'DELETED' => :terminated,
14
+ 'SUSPENDED' => :stopped,
15
+ 'PASSWORD' => :running
16
+ )
17
+
18
+ def server_save(server)
19
+ unless(server.persisted?)
20
+ server.load_data(server.attributes)
21
+ result = request(
22
+ :expects => 202,
23
+ :method => :post,
24
+ :path => '/servers',
25
+ :json => {
26
+ :server => {
27
+ :flavorRef => server.flavor_id,
28
+ :name => server.name,
29
+ :imageRef => server.image_id,
30
+ :metadata => server.metadata,
31
+ :personality => server.personality,
32
+ :key_pair => server.key_name
33
+ }
34
+ }
35
+ )
36
+ server.id = result.get(:body, :server, :id)
37
+ else
38
+ raise "WAT DO I DO!?"
39
+ end
40
+ end
41
+
42
+ def server_destroy(server)
43
+ if(server.persisted?)
44
+ result = request(
45
+ :expects => 204,
46
+ :method => :delete,
47
+ :path => "/servers/#{server.id}"
48
+ )
49
+ else
50
+ raise "this doesn't even exist"
51
+ end
52
+ end
53
+
54
+ def server_change_state(server, state)
55
+ end
56
+
57
+ def server_reload(server)
58
+ res = servers.reload.all
59
+ node = res.detect do |s|
60
+ s.id == server.id
61
+ end
62
+ if(node)
63
+ server.load_data(node.data.dup)
64
+ server.valid_state
65
+ else
66
+ server.data[:state] = :terminated
67
+ server.dirty.clear
68
+ server
69
+ end
70
+ end
71
+
72
+ def server_all
73
+ result = request(
74
+ :method => :get,
75
+ :path => '/servers/detail'
76
+ )
77
+ result[:body].fetch(:servers, []).map do |srv|
78
+ Server.new(
79
+ self,
80
+ :id => srv[:id],
81
+ :name => srv[:name],
82
+ :image_id => srv.get(:image, :id),
83
+ :flavor_id => srv.get(:flavor, :id),
84
+ :state => SERVER_STATE_MAP.fetch(srv[:status], :pending),
85
+ :addresses_private => srv.fetch(:addresses, :private, []).map{|a|
86
+ Server::Address.new(
87
+ :version => a[:version].to_i, :address => a[:addr]
88
+ )
89
+ },
90
+ :addresses_public => srv.fetch(:addresses, :public, []).map{|a|
91
+ Server::Address.new(
92
+ :version => a[:version].to_i, :address => a[:addr]
93
+ )
94
+ },
95
+ :status => srv[:status],
96
+ :key_name => srv[:key_name]
97
+ ).valid_state
98
+ end
99
+ end
100
+
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,117 @@
1
+ require 'miasma'
2
+
3
+ module Miasma
4
+ module Models
5
+ class LoadBalancer
6
+ class Rackspace < LoadBalancer
7
+
8
+ include Contrib::RackspaceApiCore::ApiCommon
9
+
10
+ # Save load balancer
11
+ #
12
+ # @param balancer [Models::LoadBalancer::Balancer]
13
+ # @return [Models::LoadBalancer::Balancer]
14
+ def balancer_save(balancer)
15
+ raise NotImplementedError
16
+ end
17
+
18
+ # Reload the balancer data from the API
19
+ #
20
+ # @param balancer [Models::LoadBalancer::Balancer]
21
+ # @return [Models::LoadBalancer::Balancer]
22
+ def balancer_reload(balancer)
23
+ if(balancer.persisted?)
24
+ result = request(
25
+ :path => "/loadbalancers/#{balancer.id}",
26
+ :method => :get,
27
+ :expects => 200
28
+ )
29
+ lb = result.get(:body, 'loadBalancer')
30
+ balancer.load_data(
31
+ :name => lb[:name],
32
+ :name => lb[:name],
33
+ :state => lb[:status] == 'ACTIVE' ? :active : :pending,
34
+ :status => lb[:status],
35
+ :created => lb.get(:created, :time),
36
+ :updated => lb.get(:updated, :time),
37
+ :public_addresses => lb['virtualIps'].map{|addr|
38
+ if(addr[:type] == 'PUBLIC')
39
+ Balancer::Address.new(
40
+ :address => addr[:address],
41
+ :version => addr['ipVersion'].sub('IPV', '').to_i
42
+ )
43
+ end
44
+ }.compact,
45
+ :private_addresses => lb['virtualIps'].map{|addr|
46
+ if(addr[:type] != 'PUBLIC')
47
+ Balancer::Address.new(
48
+ :address => addr[:address],
49
+ :version => addr['ipVersion'].sub('IPV', '').to_i
50
+ )
51
+ end
52
+ }.compact,
53
+ :servers => lb.fetch('nodes', []).map{|s|
54
+ srv = self.api_for(:compute).servers.all.detect do |csrv|
55
+ csrv.addresses.map(&:address).include?(s[:address])
56
+ end
57
+ if(srv)
58
+ Balancer::Server.new(self.api_for(:compute), :id => srv.id)
59
+ end
60
+ }.compact
61
+ ).valid_state
62
+ else
63
+ balancer
64
+ end
65
+ end
66
+
67
+ # Delete load balancer
68
+ #
69
+ # @param balancer [Models::LoadBalancer::Balancer]
70
+ # @return [TrueClass, FalseClass]
71
+ def balancer_destroy(balancer)
72
+ raise NotImplementedError
73
+ end
74
+
75
+ # Return all load balancers
76
+ #
77
+ # @param options [Hash] filter
78
+ # @return [Array<Models::LoadBalancer::Balancer>]
79
+ def balancer_all(options={})
80
+ result = request(
81
+ :path => '/loadbalancers',
82
+ :method => :get,
83
+ :expects => 200
84
+ )
85
+ result.fetch(:body, 'loadBalancers', []).map do |lb|
86
+ Balancer.new(
87
+ self,
88
+ :id => lb[:id],
89
+ :name => lb[:name],
90
+ :state => lb[:status] == 'ACTIVE' ? :active : :pending,
91
+ :status => lb[:status],
92
+ :created => lb.get(:created, :time),
93
+ :updated => lb.get(:updated, :time),
94
+ :public_addresses => lb['virtualIps'].map{|addr|
95
+ if(addr[:type] == 'PUBLIC')
96
+ Balancer::Address.new(
97
+ :address => addr[:address],
98
+ :version => addr['ipVersion'].sub('IPV', '').to_i
99
+ )
100
+ end
101
+ }.compact,
102
+ :private_addresses => lb['virtualIps'].map{|addr|
103
+ if(addr[:type] != 'PUBLIC')
104
+ Balancer::Address.new(
105
+ :address => addr[:address],
106
+ :version => addr['ipVersion'].sub('IPV', '').to_i
107
+ )
108
+ end
109
+ }.compact
110
+ ).valid_state
111
+ end
112
+ end
113
+
114
+ end
115
+ end
116
+ end
117
+ end