cloudservers 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ pkg/
2
+ doc/
data/COPYING ADDED
@@ -0,0 +1,10 @@
1
+ Unless otherwise noted, all files are released under the MIT license, exceptions contain licensing information in them.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
+
9
+ Except as contained in this notice, the name of Rackspace US, Inc. shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from Rackspace US, Inc.
10
+
data/README.rdoc ADDED
@@ -0,0 +1,87 @@
1
+ = Rackspace Cloud Servers
2
+
3
+ == Description
4
+
5
+ This is a Ruby interface into the Rackspace[http://rackspacecloud.com/] {Cloud Servers}[http://www.rackspacecloud.com/cloud_hosting_products/servers] service. Cloud Servers from The Rackspace Cloud put you in complete control of your hosting infrastructure. Each Cloud Server is a fully-customizable, pay by the hour, virtualized Windows or Linux server instance that you launch, maintain, and control with full root access.
6
+
7
+ *Note that in version 0.2.0 the connection style changed, from positional arguments to a hash of options*
8
+
9
+ == Installation
10
+
11
+ This source is available on Github[http://github.com/rackspace/ruby-cloudservers/] and the gem is available on Gemcutter[http://gemcutter.org/]. To install it, do
12
+
13
+ gem sources -a http://gemcutter.org/ (Newer Ruby Gems have this already)
14
+
15
+ sudo gem install cloudservers
16
+
17
+ To use it in a Rails application, add the following information to your config/environment.rb
18
+
19
+ config.gem "cloudservers"
20
+
21
+
22
+ == Examples
23
+
24
+ See the class definitions for documentation on specific methods and operations.
25
+
26
+ require 'rubygems'
27
+ require 'cloudservers'
28
+
29
+ # Log into the Cloud Servers system
30
+ cs = CloudServers::Connection.new(:username => USERNAME, :api_key => API_KEY)
31
+
32
+ # Get a listing of all current servers
33
+ >> cs.servers
34
+ => [{:name=>"RenamedRubyTest", :id=>110917}]
35
+
36
+ # Access a specific server
37
+ >> server = cs.server(110917)
38
+ >> server.name
39
+ => "RenamedRubyTest"
40
+
41
+ # or...
42
+ server_manager.find(110917)
43
+
44
+
45
+ # See what type of server this is
46
+ >> server.flavor.name
47
+ => "256 server"
48
+ >> server.image.name
49
+ => "Ubuntu 8.04.2 LTS (hardy)"
50
+
51
+ # Soft-reboot the server
52
+ >> server.reboot
53
+ => true
54
+
55
+ # Create a new 512MB CentOS 5.2 server. The root password is returned in the adminPass method.
56
+ >> image = cs.get_image(8)
57
+ => #<CloudServers::Image:0x1014a8060 ...>, status"ACTIVE"
58
+ >> image.name
59
+ => "CentOS 5.2"
60
+ >> flavor = cs.get_flavor(2)
61
+ => #<CloudServers::Flavor:0x101469130 @disk=20, @name="512 server", @id=2, @ram=512>
62
+ >> flavor.name
63
+ => "512 server"
64
+ >> newserver = cs.create_server(:name => "New Server", :imageId => image.id, :flavorId => flavor.id)
65
+ => #<CloudServers::Server:0x101433f08 ....
66
+ >> newserver.status
67
+ => "BUILD"
68
+ >> newserver.progress
69
+ => 0
70
+ >> newserver.adminPass
71
+ => "NewServerMbhzUnO"
72
+ >> newserver.refresh
73
+ => true
74
+ >> newserver.progress
75
+ => 12
76
+
77
+ # Delete the new server
78
+ >> newserver.delete!
79
+ => true
80
+
81
+ == Authors
82
+
83
+ By H. Wade Minter <wade.minter@rackspace.com> and Mike Mayo <mike.mayo@rackspace.com>
84
+
85
+ == License
86
+
87
+ See COPYING for license information.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require './lib/cloudservers.rb'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gemspec|
7
+ gemspec.name = "cloudservers"
8
+ gemspec.summary = "Rackspace Cloud Servers Ruby API"
9
+ gemspec.description = "A Ruby API to version 1.0 of the Rackspace Cloud Servers product."
10
+ gemspec.email = "wade.minter@rackspace.com"
11
+ gemspec.homepage = "http://github.com/rackspace/cloudservers"
12
+ gemspec.authors = ["H. Wade Minter","Mike Mayo"]
13
+ gemspec.add_dependency 'json'
14
+ end
15
+ rescue LoadError
16
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
17
+ end
data/TODO ADDED
@@ -0,0 +1,13 @@
1
+ * There are caching bugs in the API, so that accurate information is not always returned (ie. if you add a server and
2
+ then list available servers, the new one will not show up.). That needs to be corrected on the API end before
3
+ data will be accurate.
4
+
5
+ * Add pagination in object listing.
6
+
7
+ * Allow some sort of flag to get the stack trace when an exception is thrown.
8
+
9
+ * Shared IP group modification code.
10
+
11
+ * Support the changes-since parameter.
12
+
13
+ * Tests. Accusations of heresy and/or apostasy are expected. :-)
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,62 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{cloudservers}
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["H. Wade Minter", "Mike Mayo"]
12
+ s.date = %q{2010-05-06}
13
+ s.description = %q{A Ruby API to version 1.0 of the Rackspace Cloud Servers product.}
14
+ s.email = %q{wade.minter@rackspace.com}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc",
17
+ "TODO"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "COPYING",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "TODO",
25
+ "VERSION",
26
+ "cloudservers.gemspec",
27
+ "lib/cloudservers.rb",
28
+ "lib/cloudservers/authentication.rb",
29
+ "lib/cloudservers/connection.rb",
30
+ "lib/cloudservers/entity_manager.rb",
31
+ "lib/cloudservers/exception.rb",
32
+ "lib/cloudservers/flavor.rb",
33
+ "lib/cloudservers/image.rb",
34
+ "lib/cloudservers/server.rb",
35
+ "lib/cloudservers/shared_ip_group.rb",
36
+ "test/cloudservers_authentication_test.rb",
37
+ "test/test_helper.rb"
38
+ ]
39
+ s.homepage = %q{http://github.com/rackspace/cloudservers}
40
+ s.rdoc_options = ["--charset=UTF-8"]
41
+ s.require_paths = ["lib"]
42
+ s.rubygems_version = %q{1.3.6}
43
+ s.summary = %q{Rackspace Cloud Servers Ruby API}
44
+ s.test_files = [
45
+ "test/cloudservers_authentication_test.rb",
46
+ "test/test_helper.rb"
47
+ ]
48
+
49
+ if s.respond_to? :specification_version then
50
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
51
+ s.specification_version = 3
52
+
53
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
54
+ s.add_runtime_dependency(%q<json>, [">= 0"])
55
+ else
56
+ s.add_dependency(%q<json>, [">= 0"])
57
+ end
58
+ else
59
+ s.add_dependency(%q<json>, [">= 0"])
60
+ end
61
+ end
62
+
@@ -0,0 +1,38 @@
1
+ module CloudServers
2
+ class Authentication
3
+
4
+ # Performs an authentication to the Rackspace Cloud authorization servers. Opens a new HTTP connection to the API server,
5
+ # sends the credentials, and looks for a successful authentication. If it succeeds, it sets the svrmgmthost,
6
+ # svrmgtpath, svrmgmtport, svrmgmtscheme, authtoken, and authok variables on the connection. If it fails, it raises
7
+ # an exception.
8
+ #
9
+ # Should probably never be called directly.
10
+ def initialize(connection)
11
+ path = '/v1.0'
12
+ hdrhash = { "X-Auth-User" => connection.authuser, "X-Auth-Key" => connection.authkey }
13
+ begin
14
+ server = Net::HTTP::Proxy(connection.proxy_host, connection.proxy_port).new('auth.api.rackspacecloud.com',443)
15
+ server.use_ssl = true
16
+ server.verify_mode = OpenSSL::SSL::VERIFY_NONE
17
+ server.start
18
+ rescue
19
+ raise CloudServers::Exception::Connection, "Unable to connect to #{server}"
20
+ end
21
+ response = server.get(path,hdrhash)
22
+ if (response.code == "204")
23
+ connection.authtoken = response["x-auth-token"]
24
+ connection.svrmgmthost = URI.parse(response["x-server-management-url"]).host
25
+ connection.svrmgmtpath = URI.parse(response["x-server-management-url"]).path
26
+ # Force the path into the v1.0 URL space
27
+ connection.svrmgmtpath.sub!(/\/.*?\//, '/v1.0/')
28
+ connection.svrmgmtport = URI.parse(response["x-server-management-url"]).port
29
+ connection.svrmgmtscheme = URI.parse(response["x-server-management-url"]).scheme
30
+ connection.authok = true
31
+ else
32
+ connection.authtoken = false
33
+ raise CloudServers::Exception::Authentication, "Authentication failed with response code #{response.code}"
34
+ end
35
+ server.finish
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,306 @@
1
+ module CloudServers
2
+ class Connection
3
+
4
+ attr_reader :authuser
5
+ attr_reader :authkey
6
+ attr_accessor :authtoken
7
+ attr_accessor :authok
8
+ attr_accessor :svrmgmthost
9
+ attr_accessor :svrmgmtpath
10
+ attr_accessor :svrmgmtport
11
+ attr_accessor :svrmgmtscheme
12
+ attr_reader :proxy_host
13
+ attr_reader :proxy_port
14
+
15
+ # Creates a new CloudServers::Connection object. Uses CloudServers::Authentication to perform the login for the connection.
16
+ #
17
+ # Setting the retry_auth option to false will cause an exception to be thrown if your authorization token expires.
18
+ # Otherwise, it will attempt to reauthenticate.
19
+ #
20
+ # This is useful if you are using the library on a Rackspace-hosted system, as it provides faster speeds, keeps traffic off of
21
+ # the public network, and the bandwidth is not billed.
22
+ #
23
+ # This will likely be the base class for most operations.
24
+ #
25
+ # The constructor takes a hash of options, including:
26
+ #
27
+ # :username - Your Rackspace Cloud username *required*
28
+ # :api_key - Your Rackspace Cloud API key *required*
29
+ # :retry_auth - Whether to retry if your auth token expires (defaults to true)
30
+ # :proxy_host - If you need to connect through a proxy, supply the hostname here
31
+ # :proxy_port - If you need to connect through a proxy, supply the port here
32
+ #
33
+ # cf = CloudServers::Connection.new(:username => 'YOUR_USERNAME', :api_key => 'YOUR_API_KEY')
34
+ def initialize(options = {:retry_auth => true})
35
+ @authuser = options[:username] || (raise Authentication, "Must supply a :username")
36
+ @authkey = options[:api_key] || (raise Authentication, "Must supply an :api_key")
37
+ @retry_auth = options[:retry_auth]
38
+ @proxy_host = options[:proxy_host]
39
+ @proxy_port = options[:proxy_port]
40
+ @authok = false
41
+ @http = {}
42
+ CloudServers::Authentication.new(self)
43
+ end
44
+
45
+ # Returns true if the authentication was successful and returns false otherwise.
46
+ #
47
+ # cs.authok?
48
+ # => true
49
+ def authok?
50
+ @authok
51
+ end
52
+
53
+ # This method actually makes the HTTP REST calls out to the server
54
+ def csreq(method,server,path,port,scheme,headers = {},data = nil,attempts = 0) # :nodoc:
55
+ start = Time.now
56
+ hdrhash = headerprep(headers)
57
+ start_http(server,path,port,scheme,hdrhash)
58
+ request = Net::HTTP.const_get(method.to_s.capitalize).new(path,hdrhash)
59
+ request.body = data
60
+ response = @http[server].request(request)
61
+ raise CloudServers::Exception::ExpiredAuthToken if response.code == "401"
62
+ response
63
+ rescue Errno::EPIPE, Timeout::Error, Errno::EINVAL, EOFError
64
+ # Server closed the connection, retry
65
+ raise CloudServers::Exception::Connection, "Unable to reconnect to #{server} after #{count} attempts" if attempts >= 5
66
+ attempts += 1
67
+ @http[server].finish
68
+ start_http(server,path,port,scheme,headers)
69
+ retry
70
+ rescue CloudServers::Exception::ExpiredAuthToken
71
+ raise CloudServers::Exception::Connection, "Authentication token expired and you have requested not to retry" if @retry_auth == false
72
+ CloudFiles::Authentication.new(self)
73
+ retry
74
+ end
75
+
76
+ # Returns the CloudServers::Server object identified by the given id.
77
+ #
78
+ # >> server = cs.get_server(110917)
79
+ # => #<CloudServers::Server:0x101407ae8 ...>
80
+ # >> server.name
81
+ # => "MyServer"
82
+ def get_server(id)
83
+ CloudServers::Server.new(self,id)
84
+ end
85
+ alias :server :get_server
86
+
87
+ # Returns an array of hashes, one for each server that exists under this account. The hash keys are :name and :id.
88
+ #
89
+ # >> cs.list_servers
90
+ # => [{:name=>"MyServer", :id=>110917}]
91
+ def list_servers(options = {})
92
+ url_params = "?limit=#{URI.escape(options[:limit].to_s)}&offset=#{URI.escape(options[:offset].to_s)}" if options[:limit] && options[:offset]
93
+ response = csreq("GET",svrmgmthost,"#{svrmgmtpath}/servers",svrmgmtport,svrmgmtscheme)
94
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
95
+ CloudServers.symbolize_keys(JSON.parse(response.body)["servers"])
96
+ end
97
+ alias :servers :list_servers
98
+
99
+ # Returns an array of hashes with more details about each server that exists under this account. Additional information
100
+ # includes public and private IP addresses, status, hostID, and more. All hash keys are symbols except for the metadata
101
+ # hash, which are verbatim strings.
102
+ #
103
+ # >> cs.list_servers_detail
104
+ # => [{:name=>"MyServer", :addresses=>{:public=>["67.23.42.37"], :private=>["10.176.241.237"]}, :metadata=>{"MyData" => "Valid"}, :imageId=>10, :progress=>100, :hostId=>"36143b12e9e48998c2aef79b50e144d2", :flavorId=>1, :id=>110917, :status=>"ACTIVE"}]
105
+ def list_servers_detail
106
+ response = csreq("GET",svrmgmthost,"#{svrmgmtpath}/servers/detail",svrmgmtport,svrmgmtscheme)
107
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
108
+ CloudServers.symbolize_keys(JSON.parse(response.body)["servers"])
109
+ end
110
+ alias :servers_detail :list_servers_detail
111
+
112
+ # Creates a new server instance on Cloud Servers
113
+ #
114
+ # The argument is a hash of options. The keys :name, :flavorId, and :imageId are required, :metadata and :personality are optional.
115
+ #
116
+ # :flavorId and :imageId are numbers identifying a particular server flavor and image to use when building the server. The :imageId can either
117
+ # be a stock Cloud Servers image, or one of your own created with the server.create_image method.
118
+ #
119
+ # The :metadata argument will take a hash of up to five key/value pairs. This metadata will be applied to the server at the Cloud Servers
120
+ # API level.
121
+ #
122
+ # The "Personality" option allows you to include up to five files, of 10Kb or less in size, that will be placed on the created server.
123
+ # For :personality, pass a hash of the form {'local_path' => 'server_path'}. The file located at local_path will be base64-encoded
124
+ # and placed at the location identified by server_path on the new server.
125
+ #
126
+ # Returns a CloudServers::Server object. The root password is available in the adminPass instance method.
127
+ #
128
+ # >> server = cs.create_server(:name => "New Server", :imageId => 2, :flavorId => 2, :metadata => {'Racker' => 'Fanatical'}, :personality => {'/Users/me/Pictures/wedding.jpg' => '/root/me.jpg'})
129
+ # => #<CloudServers::Server:0x101229eb0 ...>
130
+ # >> server.name
131
+ # => "NewServer"
132
+ # >> server.status
133
+ # => "BUILD"
134
+ # >> server.adminPass
135
+ # => "NewServerSHMGpvI"
136
+ def create_server(options)
137
+ raise CloudServers::Exception::MissingArgument, "Server name, flavor ID, and image ID must be supplied" unless (options[:name] && options[:flavorId] && options[:imageId])
138
+ options[:personality] = get_personality(options[:personality])
139
+ raise TooManyMetadataItems, "Metadata is limited to a total of #{MAX_PERSONALITY_METADATA_ITEMS} key/value pairs" if options[:metadata].is_a?(Hash) && options[:metadata].keys.size > MAX_PERSONALITY_METADATA_ITEMS
140
+ data = JSON.generate(:server => options)
141
+ response = csreq("POST",svrmgmthost,"#{svrmgmtpath}/servers",svrmgmtport,svrmgmtscheme,{'content-type' => 'application/json'},data)
142
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
143
+ server_info = JSON.parse(response.body)['server']
144
+ server = CloudServers::Server.new(self,server_info['id'])
145
+ server.adminPass = server_info['adminPass']
146
+ return server
147
+ end
148
+
149
+ # Returns an array of hashes listing available server images that you have access too, including stock Cloud Servers images and
150
+ # any that you have created. The "id" key in the hash can be used where imageId is required.
151
+ #
152
+ # >> cs.list_images
153
+ # => [{:name=>"CentOS 5.2", :id=>2, :updated=>"2009-07-20T09:16:57-05:00", :status=>"ACTIVE", :created=>"2009-07-20T09:16:57-05:00"},
154
+ # {:name=>"Gentoo 2008.0", :id=>3, :updated=>"2009-07-20T09:16:57-05:00", :status=>"ACTIVE", :created=>"2009-07-20T09:16:57-05:00"},...
155
+ def list_images
156
+ response = csreq("GET",svrmgmthost,"#{svrmgmtpath}/images/detail",svrmgmtport,svrmgmtscheme)
157
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
158
+ CloudServers.symbolize_keys(JSON.parse(response.body)['images'])
159
+ end
160
+ alias :images :list_images
161
+
162
+ # Returns a CloudServers::Image object for the image identified by the provided id.
163
+ #
164
+ # >> image = cs.get_image(8)
165
+ # => #<CloudServers::Image:0x101659698 ...>
166
+ def get_image(id)
167
+ CloudServers::Image.new(self,id)
168
+ end
169
+ alias :image :get_image
170
+
171
+ # Returns an array of hashes listing all available server flavors. The :id key in the hash can be used when flavorId is required.
172
+ #
173
+ # >> cs.list_flavors
174
+ # => [{:name=>"256 server", :id=>1, :ram=>256, :disk=>10},
175
+ # {:name=>"512 server", :id=>2, :ram=>512, :disk=>20},...
176
+ def list_flavors
177
+ response = csreq("GET",svrmgmthost,"#{svrmgmtpath}/flavors/detail",svrmgmtport,svrmgmtscheme)
178
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
179
+ CloudServers.symbolize_keys(JSON.parse(response.body)['flavors'])
180
+ end
181
+ alias :flavors :list_flavors
182
+
183
+ # Returns a CloudServers::Flavor object for the flavor identified by the provided ID.
184
+ #
185
+ # >> flavor = cs.flavor(1)
186
+ # => #<CloudServers::Flavor:0x10156dcc0 @name="256 server", @disk=10, @id=1, @ram=256>
187
+ def get_flavor(id)
188
+ CloudServers::Flavor.new(self,id)
189
+ end
190
+ alias :flavor :get_flavor
191
+
192
+ # Returns an array of hashes for all Shared IP Groups that are available. The :id key can be used to find that specific object.
193
+ #
194
+ # >> cs.list_shared_ip_groups
195
+ # => [{:name=>"New Group", :id=>127}]
196
+ def list_shared_ip_groups
197
+ response = csreq("GET",svrmgmthost,"#{svrmgmtpath}/shared_ip_groups/detail",svrmgmtport,svrmgmtscheme)
198
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
199
+ CloudServers.symbolize_keys(JSON.parse(response.body)['sharedIpGroups'])
200
+ end
201
+ alias :shared_ip_groups :list_shared_ip_groups
202
+
203
+ # Returns a CloudServers::SharedIPGroup object for the IP group identified by the provided ID.
204
+ #
205
+ # >> sig = cs.get_shared_ip_group(127)
206
+ # => #<CloudServers::SharedIPGroup:0x10153ca30 ...>
207
+ def get_shared_ip_group(id)
208
+ CloudServers::SharedIPGroup.new(self,id)
209
+ end
210
+ alias :shared_ip_group :get_shared_ip_group
211
+
212
+ # Creates a new Shared IP group. Takes a hash as an argument.
213
+ #
214
+ # Valid hash keys are :name (required) and :server (optional), which indicates the one server to place into this group by default.
215
+ #
216
+ # >> sig = cs.create_shared_ip_group(:name => "Production Web", :server => 110917)
217
+ # => #<CloudServers::SharedIPGroup:0x101501d18 ...>
218
+ # >> sig.name
219
+ # => "Production Web"
220
+ # >> sig.servers
221
+ # => [110917]
222
+ def create_shared_ip_group(options)
223
+ data = JSON.generate(:sharedIpGroup => options)
224
+ response = csreq("POST",svrmgmthost,"#{svrmgmtpath}/shared_ip_groups",svrmgmtport,svrmgmtscheme,{'content-type' => 'application/json'},data)
225
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
226
+ ip_group = JSON.parse(response.body)['sharedIpGroup']
227
+ CloudServers::SharedIPGroup.new(self,ip_group['id'])
228
+ end
229
+
230
+ # Returns the current state of the programatic API limits. Each account has certain limits on the number of resources
231
+ # allowed in the account, and a rate of API operations.
232
+ #
233
+ # The operation returns a hash. The :absolute hash key reveals the account resource limits, including the maxmimum
234
+ # amount of total RAM that can be allocated (combined among all servers), the maximum members of an IP group, and the
235
+ # maximum number of IP groups that can be created.
236
+ #
237
+ # The :rate hash key returns an array of hashes indicating the limits on the number of operations that can be performed in a
238
+ # given amount of time. An entry in this array looks like:
239
+ #
240
+ # {:regex=>"^/servers", :value=>50, :verb=>"POST", :remaining=>50, :unit=>"DAY", :resetTime=>1272399820, :URI=>"/servers*"}
241
+ #
242
+ # This indicates that you can only run 50 POST operations against URLs in the /servers URI space per day, we have not run
243
+ # any operations today (50 remaining), and gives the Unix time that the limits reset.
244
+ #
245
+ # Another example is:
246
+ #
247
+ # {:regex=>".*", :value=>10, :verb=>"PUT", :remaining=>10, :unit=>"MINUTE", :resetTime=>1272399820, :URI=>"*"}
248
+ #
249
+ # This says that you can run 10 PUT operations on all possible URLs per minute, and also gives the number remaining and the
250
+ # time that the limit resets.
251
+ #
252
+ # Use this information as you're building your applications to put in relevant pauses if you approach your API limitations.
253
+ def limits
254
+ response = csreq("GET",svrmgmthost,"#{svrmgmtpath}/limits",svrmgmtport,svrmgmtscheme)
255
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
256
+ CloudServers.symbolize_keys(JSON.parse(response.body)['limits'])
257
+ end
258
+
259
+ private
260
+
261
+ # Sets up standard HTTP headers
262
+ def headerprep(headers = {}) # :nodoc:
263
+ default_headers = {}
264
+ default_headers["X-Auth-Token"] = @authtoken if (authok? && @account.nil?)
265
+ default_headers["X-Storage-Token"] = @authtoken if (authok? && !@account.nil?)
266
+ default_headers["Connection"] = "Keep-Alive"
267
+ default_headers["User-Agent"] = "CloudServers Ruby API #{VERSION}"
268
+ default_headers["Accept"] = "application/json"
269
+ default_headers.merge(headers)
270
+ end
271
+
272
+ # Starts (or restarts) the HTTP connection
273
+ def start_http(server,path,port,scheme,headers) # :nodoc:
274
+ if (@http[server].nil?)
275
+ begin
276
+ @http[server] = Net::HTTP::Proxy(self.proxy_host, self.proxy_port).new(server,port)
277
+ if scheme == "https"
278
+ @http[server].use_ssl = true
279
+ @http[server].verify_mode = OpenSSL::SSL::VERIFY_NONE
280
+ end
281
+ @http[server].start
282
+ rescue
283
+ raise ConnectionException, "Unable to connect to #{server}"
284
+ end
285
+ end
286
+ end
287
+
288
+ # Handles parsing the Personality hash to load it up with Base64-encoded data.
289
+ def get_personality(options)
290
+ return if options.nil?
291
+ require 'base64'
292
+ data = []
293
+ itemcount = 0
294
+ options.each do |localpath,svrpath|
295
+ raise CloudServers::Exception::TooManyPersonalityItems, "Personality files are limited to a total of #{MAX_PERSONALITY_ITEMS} items" if itemcount >= MAX_PERSONALITY_ITEMS
296
+ raise CloudServers::Exception::PersonalityFilePathTooLong, "Server-side path of #{svrpath} exceeds the maximum length of #{MAX_SERVER_PATH_LENGTH} characters" if svrpath.size > MAX_SERVER_PATH_LENGTH
297
+ raise CloudServers::Exception::PersonalityFileTooLarge, "Local file #{localpath} exceeds the maximum size of #{MAX_PERSONALITY_FILE_SIZE} bytes" if File.size(localpath) > MAX_PERSONALITY_FILE_SIZE
298
+ b64 = Base64.encode64(IO.read(localpath))
299
+ data.push({:path => svrpath, :contents => b64})
300
+ itemcount += 1
301
+ end
302
+ return data
303
+ end
304
+
305
+ end
306
+ end
@@ -0,0 +1,4 @@
1
+ module CloudServers
2
+ class EntityManager
3
+ end
4
+ end
@@ -0,0 +1,66 @@
1
+ module CloudServers
2
+ class Exception
3
+
4
+ class CloudServersFault < StandardError # :nodoc:
5
+ end
6
+ class ServiceUnavailable < StandardError # :nodoc:
7
+ end
8
+ class Unauthorized < StandardError # :nodoc:
9
+ end
10
+ class BadRequest < StandardError # :nodoc:
11
+ end
12
+ class OverLimit < StandardError # :nodoc:
13
+ end
14
+ class BadMediaType < StandardError # :nodoc:
15
+ end
16
+ class BadMethod < StandardError # :nodoc:
17
+ end
18
+ class ItemNotFound < StandardError # :nodoc:
19
+ end
20
+ class BuildInProgress < StandardError # :nodoc:
21
+ end
22
+ class ServerCapacityUnavailable < StandardError # :nodoc:
23
+ end
24
+ class BackupOrResizeInProgress < StandardError # :nodoc:
25
+ end
26
+ class ResizeNotAllowed < StandardError # :nodoc:
27
+ end
28
+ class NotImplemented < StandardError # :nodoc:
29
+ end
30
+
31
+ # Plus some others that we define here
32
+
33
+ class Other < StandardError # :nodoc:
34
+ end
35
+ class ExpiredAuthToken < StandardError # :nodoc:
36
+ end
37
+ class MissingArgument < StandardError # :nodoc:
38
+ end
39
+ class TooManyPersonalityItems < StandardError # :nodoc:
40
+ end
41
+ class PersonalityFilePathTooLong < StandardError # :nodoc:
42
+ end
43
+ class PersonalityFileTooLarge < StandardError # :nodoc:
44
+ end
45
+ class Authentication < StandardError # :nodoc:
46
+ end
47
+ class Connection < StandardError # :nodoc:
48
+ end
49
+
50
+ # In the event of a non-200 HTTP status code, this method takes the HTTP response, parses
51
+ # the JSON from the body to get more information about the exception, then raises the
52
+ # proper error. Note that all exceptions are scoped in the CloudServers::Exception namespace.
53
+ def self.raise_exception(response)
54
+ return if response.code =~ /^20.$/
55
+ fault,info = JSON.parse(response.body).first
56
+ begin
57
+ exception_class = self.const_get(fault[0,1].capitalize+fault[1,fault.length])
58
+ raise exception_class, info["message"]
59
+ rescue NameError
60
+ raise CloudServers::Exception::Other, "The server returned status #{response.code}"
61
+ end
62
+ end
63
+
64
+ end
65
+ end
66
+
@@ -0,0 +1,31 @@
1
+ module CloudServers
2
+ class Flavor
3
+
4
+ attr_reader :id
5
+ attr_reader :name
6
+ attr_reader :ram
7
+ attr_reader :disk
8
+
9
+ # This class provides an object for the "Flavor" of a server. The Flavor can generally be taken as the server specification,
10
+ # providing information on things like memory and disk space.
11
+ #
12
+ # The disk attribute is an integer representing the disk space in GB. The memory attribute is an integer representing the RAM in MB.
13
+ #
14
+ # This is called from the get_flavor method on a CloudServers::Connection object, returns a CloudServers::Flavor object, and will likely not be called directly.
15
+ #
16
+ # >> flavor = cs.get_flavor(1)
17
+ # => #<CloudServers::Flavor:0x1014f8bc8 @name="256 server", @disk=10, @id=1, @ram=256>
18
+ # >> flavor.name
19
+ # => "256 server"
20
+ def initialize(connection,id)
21
+ response = connection.csreq("GET",connection.svrmgmthost,"#{connection.svrmgmtpath}/flavors/#{URI.escape(id.to_s)}",connection.svrmgmtport,connection.svrmgmtscheme)
22
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
23
+ data = JSON.parse(response.body)['flavor']
24
+ @id = data['id']
25
+ @name = data['name']
26
+ @ram = data['ram']
27
+ @disk = data['disk']
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,60 @@
1
+ module CloudServers
2
+ class Image
3
+
4
+ attr_reader :id
5
+ attr_reader :name
6
+ attr_reader :serverId
7
+ attr_reader :updated
8
+ attr_reader :created
9
+ attr_reader :status
10
+ attr_reader :progress
11
+
12
+ # This class provides an object for the "Image" of a server. The Image refers to the Operating System type and version.
13
+ #
14
+ # Returns the Image object identifed by the supplied ID number. Called from the get_image instance method of CloudServers::Connection,
15
+ # it will likely not be called directly from user code.
16
+ #
17
+ # >> cs = CloudServers::Connection.new(USERNAME,API_KEY)
18
+ # >> image = cs.get_image(2)
19
+ # => #<CloudServers::Image:0x1015371c0 ...>
20
+ # >> image.name
21
+ # => "CentOS 5.2"
22
+ def initialize(connection,id)
23
+ @id = id
24
+ @connection = connection
25
+ populate
26
+ end
27
+
28
+ # Makes the HTTP call to load information about the provided image. Can also be called directly on the Image object to refresh data.
29
+ # Returns true if the refresh call succeeds.
30
+ #
31
+ # >> image.populate
32
+ # => true
33
+ def populate
34
+ response = @connection.csreq("GET",@connection.svrmgmthost,"#{@connection.svrmgmtpath}/images/#{URI.escape(self.id.to_s)}",@connection.svrmgmtport,@connection.svrmgmtscheme)
35
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
36
+ data = JSON.parse(response.body)['image']
37
+ @id = data['id']
38
+ @name = data['name']
39
+ @serverId = data['serverId']
40
+ @updated = DateTime.parse(data['updated'])
41
+ @created = DateTime.parse(data['created'])
42
+ @status = data['status']
43
+ @progress = data['progress']
44
+ return true
45
+ end
46
+ alias :refresh :populate
47
+
48
+ # Delete an image. This should be returning invalid permissions when attempting to delete system images, but it's not.
49
+ # Returns true if the deletion succeeds.
50
+ #
51
+ # >> image.delete!
52
+ # => true
53
+ def delete!
54
+ response = @connection.csreq("DELETE",@connection.svrmgmthost,"#{@connection.svrmgmtpath}/images/#{URI.escape(self.id.to_s)}",@connection.svrmgmtport,@connection.svrmgmtscheme)
55
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
56
+ true
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,254 @@
1
+ module CloudServers
2
+ class Server
3
+
4
+ attr_reader :id
5
+ attr_reader :name
6
+ attr_reader :status
7
+ attr_reader :progress
8
+ attr_reader :addresses
9
+ attr_reader :metadata
10
+ attr_reader :hostId
11
+ attr_reader :imageId
12
+ attr_reader :flavorId
13
+ attr_reader :metadata
14
+ attr_accessor :adminPass
15
+
16
+ # This class is the representation of a single Cloud Server object. The constructor finds the server identified by the specified
17
+ # ID number, accesses the API via the populate method to get information about that server, and returns the object.
18
+ #
19
+ # Will be called via the get_server or create_server methods on the CloudServers::Connection object, and will likely not be called directly.
20
+ #
21
+ # >> server = cs.get_server(110917)
22
+ # => #<CloudServers::Server:0x1014e5438 ....>
23
+ # >> server.name
24
+ # => "RenamedRubyTest"
25
+ def initialize(connection,id)
26
+ @connection = connection
27
+ @id = id
28
+ @svrmgmthost = connection.svrmgmthost
29
+ @svrmgmtpath = connection.svrmgmtpath
30
+ @svrmgmtport = connection.svrmgmtport
31
+ @svrmgmtscheme = connection.svrmgmtscheme
32
+ populate
33
+ return self
34
+ end
35
+
36
+ # Makes the actual API call to get information about the given server object. If you are attempting to track the status or project of
37
+ # a server object (for example, when rebuilding, creating, or resizing a server), you will likely call this method within a loop until
38
+ # the status becomes "ACTIVE" or other conditions are met.
39
+ #
40
+ # Returns true if the API call succeeds.
41
+ #
42
+ # >> server.refresh
43
+ # => true
44
+ def populate
45
+ response = @connection.csreq("GET",@svrmgmthost,"#{@svrmgmtpath}/servers/#{URI.encode(@id.to_s)}",@svrmgmtport,@svrmgmtscheme)
46
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
47
+ data = JSON.parse(response.body)["server"]
48
+ @id = data["id"]
49
+ @name = data["name"]
50
+ @status = data["status"]
51
+ @progress = data["progress"]
52
+ @addresses = CloudServers.symbolize_keys(data["addresses"])
53
+ @metadata = data["metadata"]
54
+ @hostId = data["hostId"]
55
+ @imageId = data["imageId"]
56
+ @flavorId = data["flavorId"]
57
+ @metadata = data["metadata"]
58
+ true
59
+ end
60
+ alias :refresh :populate
61
+
62
+ # Returns a new CloudServers::Flavor object for the flavor assigned to this server.
63
+ #
64
+ # >> flavor = server.flavor
65
+ # => #<CloudServers::Flavor:0x1014aac20 @name="256 server", @disk=10, @id=1, @ram=256>
66
+ # >> flavor.name
67
+ # => "256 server"
68
+ def flavor
69
+ CloudServers::Flavor.new(@connection,self.flavorId)
70
+ end
71
+
72
+ # Returns a new CloudServers::Image object for the image assigned to this server.
73
+ #
74
+ # >> image = server.image
75
+ # => #<CloudServers::Image:0x10149a960 ...>
76
+ # >> image.name
77
+ # => "Ubuntu 8.04.2 LTS (hardy)"
78
+ def image
79
+ CloudServers::Image.new(@connection,self.imageId)
80
+ end
81
+
82
+ # Sends an API request to reboot this server. Takes an optional argument for the type of reboot, which can be "SOFT" (graceful shutdown)
83
+ # or "HARD" (power cycle). The hard reboot is also triggered by server.reboot!, so that may be a better way to call it.
84
+ #
85
+ # Returns true if the API call succeeds.
86
+ #
87
+ # >> server.reboot
88
+ # => true
89
+ def reboot(type="SOFT")
90
+ data = JSON.generate(:reboot => {:type => type})
91
+ response = @connection.csreq("POST",@svrmgmthost,"#{@svrmgmtpath}/servers/#{URI.encode(self.id.to_s)}/action",@svrmgmtport,@svrmgmtscheme,{'content-type' => 'application/json'},data)
92
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
93
+ true
94
+ end
95
+
96
+ # Sends an API request to hard-reboot (power cycle) the server. See the reboot method for more information.
97
+ #
98
+ # Returns true if the API call succeeds.
99
+ #
100
+ # >> server.reboot!
101
+ # => true
102
+ def reboot!
103
+ self.reboot("HARD")
104
+ end
105
+
106
+ # Updates various parameters about the server. Currently, the only operations supported are changing the server name (not the actual hostname
107
+ # on the server, but simply the label in the Cloud Servers API) and the administrator password (note: changing the admin password will trigger
108
+ # a reboot of the server). Other options are ignored. One or both key/value pairs may be provided. Keys are case-sensitive.
109
+ #
110
+ # Input hash key values are :name and :adminPass. Returns true if the API call succeeds.
111
+ #
112
+ # >> server.update(:name => "MyServer", :adminPass => "12345")
113
+ # => true
114
+ # >> server.name
115
+ # => "MyServer"
116
+ def update(options)
117
+ data = JSON.generate(:server => options)
118
+ response = @connection.csreq("PUT",@svrmgmthost,"#{@svrmgmtpath}/servers/#{URI.encode(self.id.to_s)}",@svrmgmtport,@svrmgmtscheme,{'content-type' => 'application/json'},data)
119
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
120
+ # If we rename the instance, repopulate the object
121
+ self.populate if options[:name]
122
+ true
123
+ end
124
+
125
+ # Deletes the server from Cloud Servers. The server will be shut down, data deleted, and billing stopped.
126
+ #
127
+ # Returns true if the API call succeeds.
128
+ #
129
+ # >> server.delete!
130
+ # => true
131
+ def delete!
132
+ response = @connection.csreq("DELETE",@svrmgmthost,"#{@svrmgmtpath}/servers/#{URI.encode(self.id.to_s)}",@svrmgmtport,@svrmgmtscheme)
133
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
134
+ true
135
+ end
136
+
137
+ # Takes the existing server and rebuilds it with the image identified by the imageId argument. If no imageId is provided, the current image
138
+ # will be used.
139
+ #
140
+ # This will wipe and rebuild the server, but keep the server ID number, name, and IP addresses the same.
141
+ #
142
+ # Returns true if the API call succeeds.
143
+ #
144
+ # >> server.rebuild!
145
+ # => true
146
+ def rebuild!(imageId = self.imageId)
147
+ data = JSON.generate(:rebuild => {:imageId => imageId})
148
+ response = @connection.csreq("POST",@svrmgmthost,"#{@svrmgmtpath}/servers/#{URI.encode(self.id.to_s)}/action",@svrmgmtport,@svrmgmtscheme,{'content-type' => 'application/json'},data)
149
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
150
+ self.populate
151
+ true
152
+ end
153
+
154
+ # Takes a snapshot of the server and creates a server image from it. That image can then be used to build new servers. The
155
+ # snapshot is saved asynchronously. Check the image status to make sure that it is ACTIVE before attempting to perform operations
156
+ # on it.
157
+ #
158
+ # A name string for the saved image must be provided. A new CloudServers::Image object for the saved image is returned.
159
+ #
160
+ # The image is saved as a backup, of which there are only three available slots. If there are no backup slots available,
161
+ # A CloudServers::Exception::CloudServersFault will be raised.
162
+ #
163
+ # >> image = server.create_image("My Rails Server")
164
+ # =>
165
+ def create_image(name)
166
+ data = JSON.generate(:image => {:serverId => self.id, :name => name})
167
+ response = @connection.csreq("POST",@svrmgmthost,"#{@svrmgmtpath}/images",@svrmgmtport,@svrmgmtscheme,{'content-type' => 'application/json'},data)
168
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
169
+ CloudServers::Image.new(@connection,JSON.parse(response.body)['image']['id'])
170
+ end
171
+
172
+ # Resizes the server to the size contained in the server flavor found at ID flavorId. The server name, ID number, and IP addresses
173
+ # will remain the same. After the resize is done, the server.status will be set to "VERIFY_RESIZE" until the resize is confirmed or reverted.
174
+ #
175
+ # Refreshes the CloudServers::Server object, and returns true if the API call succeeds.
176
+ #
177
+ # >> server.resize!(1)
178
+ # => true
179
+ def resize!(flavorId)
180
+ data = JSON.generate(:resize => {:flavorId => flavorId})
181
+ response = @connection.csreq("POST",@svrmgmthost,"#{@svrmgmtpath}/servers/#{URI.encode(self.id.to_s)}/action",@svrmgmtport,@svrmgmtscheme,{'content-type' => 'application/json'},data)
182
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
183
+ self.populate
184
+ true
185
+ end
186
+
187
+ # After a server resize is complete, calling this method will confirm the resize with the Cloud Servers API, and discard the fallback/original image.
188
+ #
189
+ # Returns true if the API call succeeds.
190
+ #
191
+ # >> server.confirm_resize!
192
+ # => true
193
+ def confirm_resize!
194
+ # If the resize bug gets figured out, should put a check here to make sure that it's in the proper state for this.
195
+ data = JSON.generate(:confirmResize => nil)
196
+ response = @connection.csreq("POST",@svrmgmthost,"#{@svrmgmtpath}/servers/#{URI.encode(self.id.to_s)}/action",@svrmgmtport,@svrmgmtscheme,{'content-type' => 'application/json'},data)
197
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
198
+ self.populate
199
+ true
200
+ end
201
+
202
+ # After a server resize is complete, calling this method will reject the resized server with the Cloud Servers API, destroying
203
+ # the new image and replacing it with the pre-resize fallback image.
204
+ #
205
+ # Returns true if the API call succeeds.
206
+ #
207
+ # >> server.confirm_resize!
208
+ # => true
209
+ def revert_resize!
210
+ # If the resize bug gets figured out, should put a check here to make sure that it's in the proper state for this.
211
+ data = JSON.generate(:revertResize => nil)
212
+ response = @connection.csreq("POST",@svrmgmthost,"#{@svrmgmtpath}/servers/#{URI.encode(self.id.to_s)}/action",@svrmgmtport,@svrmgmtscheme,{'content-type' => 'application/json'},data)
213
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
214
+ self.populate
215
+ true
216
+ end
217
+
218
+ # Provides information about the backup schedule for this server. Returns a hash of the form
219
+ # {"weekly" => state, "daily" => state, "enabled" => boolean}
220
+ #
221
+ # >> server.backup_schedule
222
+ # => {"weekly"=>"THURSDAY", "daily"=>"H_0400_0600", "enabled"=>true}
223
+ def backup_schedule
224
+ response = @connection.csreq("GET",@svrmgmthost,"#{@svrmgmtpath}/servers/#{URI.encode(@id.to_s)}/backup_schedule",@svrmgmtport,@svrmgmtscheme)
225
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
226
+ JSON.parse(response.body)['backupSchedule']
227
+ end
228
+
229
+ # Updates the backup schedule for the server. Takes a hash of the form: {:weekly => state, :daily => state, :enabled => boolean} as an argument.
230
+ # All three keys (:weekly, :daily, :enabled) must be provided or an exception will get raised.
231
+ #
232
+ # >> server.backup_schedule=({:weekly=>"THURSDAY", :daily=>"H_0400_0600", :enabled=>true})
233
+ # => {:weekly=>"THURSDAY", :daily=>"H_0400_0600", :enabled=>true}
234
+ def backup_schedule=(options)
235
+ data = JSON.generate('backupSchedule' => options)
236
+ response = @connection.csreq("POST",@svrmgmthost,"#{@svrmgmtpath}/servers/#{URI.encode(self.id.to_s)}/backup_schedule",@svrmgmtport,@svrmgmtscheme,{'content-type' => 'application/json'},data)
237
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
238
+ true
239
+ end
240
+
241
+ # Removes the existing backup schedule for the server, setting the backups to disabled.
242
+ #
243
+ # Returns true if the API call succeeds.
244
+ #
245
+ # >> server.disable_backup_schedule!
246
+ # => true
247
+ def disable_backup_schedule!
248
+ response = @connection.csreq("DELETE",@svrmgmthost,"#{@svrmgmtpath}/servers/#{URI.encode(self.id.to_s)}/backup_schedule",@svrmgmtport,@svrmgmtscheme)
249
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
250
+ true
251
+ end
252
+
253
+ end
254
+ end
@@ -0,0 +1,52 @@
1
+ module CloudServers
2
+ class SharedIPGroup
3
+
4
+ attr_reader :id
5
+ attr_reader :name
6
+ attr_reader :servers
7
+
8
+ # Creates a new Shared IP Group object, with information on the group identified by the ID number. Will most likely be called
9
+ # by the get_shared_ip_group method on a CloudServers::Connection object.
10
+ #
11
+ # >> sig = cs.get_shared_ip_group(127)
12
+ # => #<CloudServers::SharedIPGroup:0x101513798 ...>
13
+ # >> sig.name
14
+ # => "New Group"
15
+ def initialize(connection,id)
16
+ @connection = connection
17
+ @id = id
18
+ populate
19
+ end
20
+
21
+ # Makes the API call that populates the CloudServers::SharedIPGroup object with information on the group. Can also be called directly on
22
+ # an existing object to update its information.
23
+ #
24
+ # Returns true if the API call succeeds.
25
+ #
26
+ # >> sig.populate
27
+ # => true
28
+ def populate
29
+ response = @connection.csreq("GET",@connection.svrmgmthost,"#{@connection.svrmgmtpath}/shared_ip_groups/#{URI.escape(self.id.to_s)}",@connection.svrmgmtport,@connection.svrmgmtscheme)
30
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
31
+ data = JSON.parse(response.body)['sharedIpGroup']
32
+ @id = data['id']
33
+ @name = data['name']
34
+ @servers = data['servers']
35
+ true
36
+ end
37
+ alias :refresh :populate
38
+
39
+ # Deletes the Shared IP Group identified by the current object.
40
+ #
41
+ # Returns true if the API call succeeds.
42
+ #
43
+ # >> sig.delete!
44
+ # => true
45
+ def delete!
46
+ response = @connection.csreq("DELETE",@connection.svrmgmthost,"#{@connection.svrmgmtpath}/shared_ip_groups/#{URI.escape(self.id.to_s)}",@connection.svrmgmtport,@connection.svrmgmtscheme)
47
+ CloudServers::Exception.raise_exception(response) unless response.code.match(/^20.$/)
48
+ true
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # == Cloud Servers API
4
+ # ==== Connects Ruby Applications to Rackspace's {Cloud Servers service}[http://www.rackspacecloud.com/cloud_hosting_products/servers]
5
+ # By H. Wade Minter <wade.minter@rackspace.com> and Mike Mayo <mike.mayo@rackspace.com>
6
+ #
7
+ # See COPYING for license information.
8
+ # Copyright (c) 2009, Rackspace US, Inc.
9
+ # ----
10
+ #
11
+ # === Documentation & Examples
12
+ # To begin reviewing the available methods and examples, peruse the README.rodc file, or begin by looking at documentation for the
13
+ # CloudServers::Connection class.
14
+ #
15
+ # The CloudServers class is the base class. Not much of note aside from housekeeping happens here.
16
+ # To create a new CloudServers connection, use the CloudServers::Connection.new('user_name', 'api_key') method.
17
+
18
+ module CloudServers
19
+
20
+ VERSION = IO.read(File.dirname(__FILE__) + '/../VERSION')
21
+ require 'net/http'
22
+ require 'net/https'
23
+ require 'uri'
24
+ require 'rubygems'
25
+ require 'json'
26
+
27
+ unless "".respond_to? :each_char
28
+ require "jcode"
29
+ $KCODE = 'u'
30
+ end
31
+
32
+ $:.unshift(File.dirname(__FILE__))
33
+ require 'cloudservers/authentication'
34
+ require 'cloudservers/connection'
35
+ require 'cloudservers/server'
36
+ require 'cloudservers/image'
37
+ require 'cloudservers/flavor'
38
+ require 'cloudservers/shared_ip_group'
39
+ require 'cloudservers/exception'
40
+
41
+ # Constants that set limits on server creation
42
+ MAX_PERSONALITY_ITEMS = 5
43
+ MAX_PERSONALITY_FILE_SIZE = 10240
44
+ MAX_SERVER_PATH_LENGTH = 255
45
+ MAX_PERSONALITY_METADATA_ITEMS = 5
46
+
47
+ # Helper method to recursively symbolize hash keys.
48
+ def self.symbolize_keys(obj)
49
+ case obj
50
+ when Array
51
+ obj.inject([]){|res, val|
52
+ res << case val
53
+ when Hash, Array
54
+ symbolize_keys(val)
55
+ else
56
+ val
57
+ end
58
+ res
59
+ }
60
+ when Hash
61
+ obj.inject({}){|res, (key, val)|
62
+ nkey = case key
63
+ when String
64
+ key.to_sym
65
+ else
66
+ key
67
+ end
68
+ nval = case val
69
+ when Hash, Array
70
+ symbolize_keys(val)
71
+ else
72
+ val
73
+ end
74
+ res[nkey] = nval
75
+ res
76
+ }
77
+ else
78
+ obj
79
+ end
80
+ end
81
+
82
+
83
+ end
@@ -0,0 +1,37 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class CloudserversAuthenticationTest < Test::Unit::TestCase
4
+
5
+
6
+ def test_good_authentication
7
+ response = {'x-cdn-management-url' => 'http://cdn.example.com/path', 'x-storage-url' => 'http://cdn.example.com/storage', 'authtoken' => 'dummy_token'}
8
+ response.stubs(:code).returns('204')
9
+ server = mock(:use_ssl= => true, :verify_mode= => true, :start => true, :finish => true)
10
+ server.stubs(:get).returns(response)
11
+ Net::HTTP.stubs(:new).returns(server)
12
+ @connection = stub(:authuser => 'dummy_user', :authkey => 'dummy_key', :cdnmgmthost= => true, :cdnmgmtpath= => true, :cdnmgmtport= => true, :cdnmgmtscheme= => true, :storagehost= => true, :storagepath= => true, :storageport= => true, :storagescheme= => true, :authtoken= => true, :authok= => true)
13
+ result = CloudServers::Authentication.new(@connection)
14
+ assert_equal result.class, CloudServers::Authentication
15
+ end
16
+
17
+ def test_bad_authentication
18
+ response = mock()
19
+ response.stubs(:code).returns('499')
20
+ server = mock(:use_ssl= => true, :verify_mode= => true, :start => true)
21
+ server.stubs(:get).returns(response)
22
+ Net::HTTP.stubs(:new).returns(server)
23
+ @connection = stub(:authuser => 'bad_user', :authkey => 'bad_key', :authok= => true, :authtoken= => true)
24
+ assert_raises(AuthenticationException) do
25
+ result = CloudServers::Authentication.new(@connection)
26
+ end
27
+ end
28
+
29
+ def test_bad_hostname
30
+ Net::HTTP.stubs(:new).raises(ConnectionException)
31
+ @connection = stub(:authuser => 'bad_user', :authkey => 'bad_key', :authok= => true, :authtoken= => true)
32
+ assert_raises(ConnectionException) do
33
+ result = CloudServers::Authentication.new(@connection)
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,5 @@
1
+ require 'test/unit'
2
+ $:.unshift File.dirname(__FILE__) + '/../lib'
3
+ require 'cloudservers'
4
+ require 'rubygems'
5
+ require 'mocha'
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cloudservers
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
10
+ platform: ruby
11
+ authors:
12
+ - H. Wade Minter
13
+ - Mike Mayo
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-05-06 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: json
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ description: A Ruby API to version 1.0 of the Rackspace Cloud Servers product.
34
+ email: wade.minter@rackspace.com
35
+ executables: []
36
+
37
+ extensions: []
38
+
39
+ extra_rdoc_files:
40
+ - README.rdoc
41
+ - TODO
42
+ files:
43
+ - .gitignore
44
+ - COPYING
45
+ - README.rdoc
46
+ - Rakefile
47
+ - TODO
48
+ - VERSION
49
+ - cloudservers.gemspec
50
+ - lib/cloudservers.rb
51
+ - lib/cloudservers/authentication.rb
52
+ - lib/cloudservers/connection.rb
53
+ - lib/cloudservers/entity_manager.rb
54
+ - lib/cloudservers/exception.rb
55
+ - lib/cloudservers/flavor.rb
56
+ - lib/cloudservers/image.rb
57
+ - lib/cloudservers/server.rb
58
+ - lib/cloudservers/shared_ip_group.rb
59
+ - test/cloudservers_authentication_test.rb
60
+ - test/test_helper.rb
61
+ has_rdoc: true
62
+ homepage: http://github.com/rackspace/cloudservers
63
+ licenses: []
64
+
65
+ post_install_message:
66
+ rdoc_options:
67
+ - --charset=UTF-8
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ segments:
75
+ - 0
76
+ version: "0"
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ segments:
82
+ - 0
83
+ version: "0"
84
+ requirements: []
85
+
86
+ rubyforge_project:
87
+ rubygems_version: 1.3.6
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: Rackspace Cloud Servers Ruby API
91
+ test_files:
92
+ - test/cloudservers_authentication_test.rb
93
+ - test/test_helper.rb