rackspace-cloud 0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Manifest.txt +22 -0
- data/PostInstall.txt +4 -0
- data/README.rdoc +72 -0
- data/Rakefile +17 -0
- data/lib/rackspace-cloud.rb +1 -0
- data/lib/rackspace.rb +21 -0
- data/lib/rackspace/cloud_servers/base.rb +133 -0
- data/lib/rackspace/cloud_servers/flavor.rb +13 -0
- data/lib/rackspace/cloud_servers/image.rb +8 -0
- data/lib/rackspace/cloud_servers/server.rb +8 -0
- data/lib/rackspace/connection.rb +115 -0
- data/lib/rackspace/exceptions.rb +7 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test/test_base.rb +149 -0
- data/test/test_connection.rb +219 -0
- data/test/test_flavor.rb +138 -0
- data/test/test_helper.rb +64 -0
- data/test/test_image.rb +177 -0
- data/test/test_server.rb +259 -0
- metadata +114 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
PostInstall.txt
|
4
|
+
README.rdoc
|
5
|
+
Rakefile
|
6
|
+
lib/rackspace.rb
|
7
|
+
lib/rackspace-cloud.rb
|
8
|
+
lib/rackspace/connection.rb
|
9
|
+
lib/rackspace/exceptions.rb
|
10
|
+
lib/rackspace/cloud_servers/base.rb
|
11
|
+
lib/rackspace/cloud_servers/server.rb
|
12
|
+
lib/rackspace/cloud_servers/flavor.rb
|
13
|
+
lib/rackspace/cloud_servers/image.rb
|
14
|
+
script/console
|
15
|
+
script/destroy
|
16
|
+
script/generate
|
17
|
+
test/test_base.rb
|
18
|
+
test/test_connection.rb
|
19
|
+
test/test_flavor.rb
|
20
|
+
test/test_helper.rb
|
21
|
+
test/test_image.rb
|
22
|
+
test/test_server.rb
|
data/PostInstall.txt
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
= rackspace-cloud
|
2
|
+
|
3
|
+
* http://github.com/edraper/rackspace-cloud
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
rackspace-cloud is a Ruby gem that provides easy, simple access to the Rackspace Cloud APIs, and specifically the Cloud Servers API.
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
Currently just has support for Cloud Servers, and not Cloud Files.
|
12
|
+
|
13
|
+
Server addresses, IP groups, backup schedules, and reboots/rebuilds/resizes not yet implemented for Cloud Servers.
|
14
|
+
|
15
|
+
== SYNOPSIS:
|
16
|
+
|
17
|
+
To initialize:
|
18
|
+
|
19
|
+
Rackspace::Connection.init "user", "key"
|
20
|
+
|
21
|
+
Optionally, you can specify a version of the API to use too (defaults to "v1.0"):
|
22
|
+
|
23
|
+
Rackspace::Connection.init "user", "key", "v1.1"
|
24
|
+
|
25
|
+
To authenticate:
|
26
|
+
|
27
|
+
Rackspace::Connection.authenticate
|
28
|
+
|
29
|
+
For Cloud Servers:
|
30
|
+
|
31
|
+
Rackspace::CloudServers::Server.all
|
32
|
+
|
33
|
+
Rackspace::CloudServers::Server.create(:name => "api-test", :imageId => 8, :flavorId => 1)
|
34
|
+
|
35
|
+
Rackspace::CloudServers::Image.all
|
36
|
+
|
37
|
+
Rackspace::CloudServers::Flavor.all
|
38
|
+
|
39
|
+
== REQUIREMENTS:
|
40
|
+
|
41
|
+
rest-client (1.0.3+)
|
42
|
+
|
43
|
+
activesupport (2.3.4+)
|
44
|
+
|
45
|
+
== INSTALL:
|
46
|
+
|
47
|
+
gem install rackspace-cloud
|
48
|
+
|
49
|
+
== LICENSE:
|
50
|
+
|
51
|
+
(The MIT License)
|
52
|
+
|
53
|
+
Copyright (c) 2009 Elliott Draper <el@ejdraper.com>
|
54
|
+
|
55
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
56
|
+
a copy of this software and associated documentation files (the
|
57
|
+
'Software'), to deal in the Software without restriction, including
|
58
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
59
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
60
|
+
permit persons to whom the Software is furnished to do so, subject to
|
61
|
+
the following conditions:
|
62
|
+
|
63
|
+
The above copyright notice and this permission notice shall be
|
64
|
+
included in all copies or substantial portions of the Software.
|
65
|
+
|
66
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
67
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
68
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
69
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
70
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
71
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
72
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'hoe', '>= 2.1.0'
|
3
|
+
require 'hoe'
|
4
|
+
require 'fileutils'
|
5
|
+
require File.join(File.dirname(__FILE__), "lib", "rackspace-cloud")
|
6
|
+
|
7
|
+
Hoe.plugin :newgem
|
8
|
+
|
9
|
+
$hoe = Hoe.spec 'rackspace-cloud' do
|
10
|
+
self.developer 'Elliott Draper', 'el@ejdraper.com'
|
11
|
+
self.post_install_message = 'PostInstall.txt'
|
12
|
+
self.rubyforge_name = self.name
|
13
|
+
self.extra_deps = [['rest-client','>= 1.0.3'], ['activesupport','>= 2.3.4']]
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'newgem/tasks'
|
17
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "rackspace")
|
data/lib/rackspace.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
gem "activesupport"
|
5
|
+
require "active_support"
|
6
|
+
gem "rest-client"
|
7
|
+
require "rest_client"
|
8
|
+
|
9
|
+
module Rackspace
|
10
|
+
VERSION = '0.5'
|
11
|
+
|
12
|
+
module CloudServers
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
require File.join(File.dirname(__FILE__), "rackspace", "exceptions")
|
17
|
+
require File.join(File.dirname(__FILE__), "rackspace", "connection")
|
18
|
+
require File.join(File.dirname(__FILE__), "rackspace", "cloud_servers", "base")
|
19
|
+
require File.join(File.dirname(__FILE__), "rackspace", "cloud_servers", "server")
|
20
|
+
require File.join(File.dirname(__FILE__), "rackspace", "cloud_servers", "flavor")
|
21
|
+
require File.join(File.dirname(__FILE__), "rackspace", "cloud_servers", "image")
|
@@ -0,0 +1,133 @@
|
|
1
|
+
class Rackspace::CloudServers::Base
|
2
|
+
attr_accessor :id
|
3
|
+
|
4
|
+
# The resource can be established with a hash of parameters
|
5
|
+
def initialize(attribs = {})
|
6
|
+
set_attributes(attribs)
|
7
|
+
end
|
8
|
+
|
9
|
+
# This sets the relevant accessors using the specified attributes
|
10
|
+
def set_attributes(attribs)
|
11
|
+
attribs.each_pair do |key, value|
|
12
|
+
self.send("#{key}=", value) if self.respond_to?("#{key}=")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# This returns true if the record hasn't yet been persisted, false otherwise
|
17
|
+
def new_record?
|
18
|
+
self.id.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
# This creates the record if it is new, otherwise it attempts to update the record
|
22
|
+
def save
|
23
|
+
self.new_record? ? create : update
|
24
|
+
end
|
25
|
+
|
26
|
+
# This creates the new record using the API
|
27
|
+
def create
|
28
|
+
set_attributes(JSON.parse(Rackspace::Connection.post(self.class.resource_url, {self.class.resource.singularize => JSON.parse(self.to_json)}))[self.class.resource.singularize])
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
# This updates the existing record using the API
|
33
|
+
def update
|
34
|
+
attribs = JSON.parse(self.to_json)
|
35
|
+
unless self.attributes_for_update.nil?
|
36
|
+
attribs.keys.each do |key|
|
37
|
+
attribs.delete(key) unless self.attributes_for_update.include?(key.to_sym)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
Rackspace::Connection.put(self.class.resource_url(self.id), {self.class.resource.singularize => attribs})
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
# This deletes the record using the API
|
45
|
+
def destroy
|
46
|
+
Rackspace::Connection.delete(self.class.resource_url(self.id))
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
# These are the attributes used for the update operations
|
51
|
+
# Empty array means no properties will be updated, but this can be overridden with
|
52
|
+
# nil (all properties are updated), or an explicit array of properties that can be updated
|
53
|
+
def attributes_for_update
|
54
|
+
[]
|
55
|
+
end
|
56
|
+
|
57
|
+
# This reloads the current object with the latest persisted data
|
58
|
+
def reload
|
59
|
+
return false if self.new_record?
|
60
|
+
result = Rackspace::Connection.get(self.class.resource_url(self.id))
|
61
|
+
return nil if result.to_s.blank?
|
62
|
+
json = JSON.parse(result.to_s)
|
63
|
+
self.set_attributes(json[self.class.resource.singularize])
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
class << self
|
68
|
+
# This returns the name of the resource, used for the API URLs
|
69
|
+
def resource
|
70
|
+
self.name.split("::").last.tableize
|
71
|
+
end
|
72
|
+
|
73
|
+
# This returns the resource URL
|
74
|
+
def resource_url(id = nil)
|
75
|
+
root = "#{Rackspace::Connection.server_management_url}/#{self.resource}"
|
76
|
+
id.nil? ? root : "#{root}/#{id}"
|
77
|
+
end
|
78
|
+
|
79
|
+
# This returns all records for the resource
|
80
|
+
def all
|
81
|
+
self.find
|
82
|
+
end
|
83
|
+
|
84
|
+
# This returns the first record for the resource
|
85
|
+
def first
|
86
|
+
self.find(:first)
|
87
|
+
end
|
88
|
+
|
89
|
+
# This returns the last record for the resource
|
90
|
+
def last
|
91
|
+
self.find(:last)
|
92
|
+
end
|
93
|
+
|
94
|
+
# This finds all records for the resource, or a specific resource by ID
|
95
|
+
def find(action = :all)
|
96
|
+
result = case action
|
97
|
+
when :all
|
98
|
+
Rackspace::Connection.get "#{self.resource_url}/detail"
|
99
|
+
when :first
|
100
|
+
Rackspace::Connection.get "#{self.resource_url}/detail"
|
101
|
+
when :last
|
102
|
+
Rackspace::Connection.get "#{self.resource_url}/detail"
|
103
|
+
else
|
104
|
+
Rackspace::Connection.get self.resource_url(action)
|
105
|
+
end
|
106
|
+
return nil if result.to_s.blank?
|
107
|
+
json = JSON.parse(result.to_s)
|
108
|
+
case action
|
109
|
+
when :all
|
110
|
+
json[self.resource].collect { |h| self.new(h) }
|
111
|
+
when :first
|
112
|
+
json[self.resource].collect { |h| self.new(h) }.first
|
113
|
+
when :last
|
114
|
+
json[self.resource].collect { |h| self.new(h) }.last
|
115
|
+
else
|
116
|
+
self.new(json[self.resource.singularize])
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# This returns the amount of records for this resource
|
121
|
+
def count
|
122
|
+
records = self.all
|
123
|
+
records.nil? ? 0 : records.length
|
124
|
+
end
|
125
|
+
|
126
|
+
# This creates and saves a resource with the specified attributes in one call
|
127
|
+
def create(attribs = {})
|
128
|
+
o = self.new(attribs)
|
129
|
+
o.save
|
130
|
+
o
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Rackspace::CloudServers::Flavor < Rackspace::CloudServers::Base
|
2
|
+
attr_accessor :name, :ram, :disk
|
3
|
+
|
4
|
+
# Saving (create/update) isn't allowed for flavors
|
5
|
+
def save
|
6
|
+
false
|
7
|
+
end
|
8
|
+
|
9
|
+
# Deletion isn't allowed for flavors
|
10
|
+
def destroy
|
11
|
+
false
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
class Rackspace::CloudServers::Server < Rackspace::CloudServers::Base
|
2
|
+
attr_accessor :name, :imageId, :flavorId, :hostId, :status, :progress, :addresses, :metadata, :personality, :adminPass
|
3
|
+
|
4
|
+
# Overriding the update attributes so that just name and adminPass are persisted on an update
|
5
|
+
def attributes_for_update
|
6
|
+
[:name, :adminPass]
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
class Rackspace::Connection
|
2
|
+
AUTH_URL = "https://auth.api.rackspacecloud.com"
|
3
|
+
VERSION_URL = "https://servers.api.rackspacecloud.com"
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# This initializes the Rackspace environment with the data necessary to make API calls
|
7
|
+
def init(user, key, version = "v1.0")
|
8
|
+
@user = user
|
9
|
+
@key = key
|
10
|
+
@version = version
|
11
|
+
raise Rackspace::InvalidVersion unless self.versions.include?(version)
|
12
|
+
@initialized = true
|
13
|
+
end
|
14
|
+
|
15
|
+
# This returns the API user being used for calls
|
16
|
+
def api_user
|
17
|
+
@user
|
18
|
+
end
|
19
|
+
|
20
|
+
# This returns the API key being used for calls
|
21
|
+
def api_key
|
22
|
+
@key
|
23
|
+
end
|
24
|
+
|
25
|
+
# This returns the API version being used for calls
|
26
|
+
def api_version
|
27
|
+
@version
|
28
|
+
end
|
29
|
+
|
30
|
+
# This returns whether or not we've been initialized yet
|
31
|
+
def initialized?
|
32
|
+
@initialized || false
|
33
|
+
end
|
34
|
+
|
35
|
+
# This authenticates with Rackspace and returns the information necessary to make subsequent authenticated calls to the API
|
36
|
+
def authenticate
|
37
|
+
raise Rackspace::NotInitialized unless self.initialized?
|
38
|
+
headers = RestClient::Request.execute(:method => :get, :url => "#{AUTH_URL}/#{self.api_version}", :headers => {"X-Auth-User" => self.api_user, "X-Auth-Key" => self.api_key}, :raw_response => true).headers
|
39
|
+
{:auth_token => headers[:x_auth_token], :storage_url => headers[:x_storage_url], :server_management_url => headers[:x_server_management_url], :cdn_management_url => headers[:x_cdn_management_url]}
|
40
|
+
end
|
41
|
+
|
42
|
+
# These are default headers we need to use on all requests
|
43
|
+
def default_headers
|
44
|
+
{:accept => "application/json", :content_type => "application/json"}
|
45
|
+
end
|
46
|
+
|
47
|
+
# This returns the available versions of the API
|
48
|
+
def versions
|
49
|
+
JSON.parse(RestClient.get("#{VERSION_URL}/.json", self.default_headers))["versions"].collect { |v| v["id"] }.uniq
|
50
|
+
end
|
51
|
+
|
52
|
+
# This caches the authentication response for subsequent usage
|
53
|
+
def auth_response
|
54
|
+
@auth_response ||= self.authenticate
|
55
|
+
end
|
56
|
+
|
57
|
+
# This is the auth token provided by Rackspace after a successful authentication
|
58
|
+
def auth_token
|
59
|
+
self.auth_response[:auth_token]
|
60
|
+
end
|
61
|
+
|
62
|
+
# This returns the root URL for Cloud Files API queries (not yet implemented)
|
63
|
+
def storage_url
|
64
|
+
self.auth_response[:storage_url]
|
65
|
+
end
|
66
|
+
|
67
|
+
# This returns the root URL for Cloud Servers API queries
|
68
|
+
def server_management_url
|
69
|
+
self.auth_response[:server_management_url]
|
70
|
+
end
|
71
|
+
|
72
|
+
# This returns the root URL for CDN Cloud Files API queries (not yet implemented)
|
73
|
+
def cdn_management_url
|
74
|
+
self.auth_response[:cdn_management_url]
|
75
|
+
end
|
76
|
+
|
77
|
+
# This performs a basic GET request using the supplied URL and headers
|
78
|
+
def get(url, headers = {})
|
79
|
+
http :get, "#{url}.json", headers
|
80
|
+
end
|
81
|
+
|
82
|
+
# This performs a basic POST request using the supplied URL, payload and headers
|
83
|
+
def post(url, payload = {}, headers = {})
|
84
|
+
http :post, "#{url}.json", payload.to_json, headers
|
85
|
+
end
|
86
|
+
|
87
|
+
# This performs a basic PUT request using the supplied URL, payload and headers
|
88
|
+
def put(url, payload = {}, headers = {})
|
89
|
+
http :put, "#{url}.json", payload.to_json, headers
|
90
|
+
end
|
91
|
+
|
92
|
+
# This performs a basic DELETE request using the supplied URL and headers
|
93
|
+
def delete(url, headers = {})
|
94
|
+
http :delete, "#{url}.json", headers
|
95
|
+
end
|
96
|
+
|
97
|
+
# This will perform an HTTP call with the specified method, and arguments
|
98
|
+
# It will also pick up if the response is that the request was unauthorized, and will attempt
|
99
|
+
# the same request again after re-authenticating (in case the auth token has expired)
|
100
|
+
def http(method, *args)
|
101
|
+
args.last.merge!(self.default_headers).merge!("X-Auth-Token" => self.auth_token)
|
102
|
+
response = RestClient.send(method, *args)
|
103
|
+
@retried = false
|
104
|
+
response
|
105
|
+
rescue RestClient::Unauthorized
|
106
|
+
@auth_response = nil
|
107
|
+
if @retried
|
108
|
+
raise
|
109
|
+
else
|
110
|
+
@retried = true
|
111
|
+
retry
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|