clouddb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ # IDE files to ignore
2
+ *.iml
3
+ .idea/*
data/COPYING ADDED
@@ -0,0 +1,12 @@
1
+ Unless otherwise noted, all files are released under the MIT license, exceptions contain licensing information in them.
2
+
3
+ Copyright (C) 2012 Rackspace US, Inc.
4
+
5
+ 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:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ 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.
10
+
11
+ 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.
12
+
@@ -0,0 +1,45 @@
1
+ = Rackspace Cloud Databases
2
+
3
+ == Description
4
+
5
+ This is a Ruby interface into the Rackspace[http://rackspace.com/] {Cloud Databases}[http://www.rackspace.com/blog/announcing-the-rackspace-mysql-cloud-database-private-beta/] service.
6
+
7
+ == Installation
8
+
9
+ This source is available on Github[http://github.com/rackspace/ruby-clouddb/] and the gem is available on RubyGems[http://rubygems.org/] (coming soon..). To install it, do
10
+
11
+ sudo gem install clouddb
12
+
13
+ To use it in Bundler, add the following statement to your Gemfile
14
+
15
+ gem "clouddb"
16
+
17
+ == RDOC Documentation
18
+
19
+ Find the latest RDoc documentation for this library at http://rdoc.info/github/rackspace/ruby-clouddb/master/frames
20
+
21
+ == API Documentation
22
+
23
+ This binding attempts to conform to the latest API specifications. For current API documentation, visit http://docs.rackspacecloud.com/api/
24
+
25
+ == Examples
26
+
27
+ See the class definitions for documentation on specific methods and operations.
28
+
29
+ require 'rubygems'
30
+ require 'clouddb'
31
+
32
+ # Authenticate to the Rackspace Cloud, and choose to manage databases in the Dallas/Ft. Worth datacenter
33
+ dbaas = CloudDB::Connection.new(:username => "MY_USERNAME", :api_key => "MY_API_KEY", :region => :dfw)
34
+
35
+ == Authors
36
+
37
+ {Jorge Miramontes}[https://github.com/jorgem1106/] <jorge.miramontes@rackspace.com>
38
+ {H. Wade Minter}[https://github.com/minter/] <minter@lunenburg.org>
39
+
40
+ == License
41
+
42
+ See COPYING for license information.
43
+ Copyright (c) 2012, Rackspace US, Inc.
44
+
45
+
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib/', __FILE__)
3
+ $:.unshift lib unless $:.include?(lib)
4
+
5
+ require File.expand_path('../lib/clouddb/version.rb', __FILE__)
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "clouddb"
9
+ s.version = CloudDB::VERSION
10
+ s.platform = Gem::Platform::RUBY
11
+ s.authors = %w("Jorge Miramontes", "H. Wade Minter")
12
+ s.email = %w(jorge.miramontes@rackspace.com minter@lunenburg.org)
13
+ s.homepage = "http://github.com/rackspace/ruby-clouddb"
14
+ s.summary = "Ruby API into the Rackspace Cloud Databases product"
15
+ s.description = "A Ruby API to manage the Rackspace Cloud Databases product"
16
+
17
+ s.required_rubygems_version = ">= 1.3.6"
18
+
19
+ s.add_runtime_dependency "typhoeus"
20
+ s.add_runtime_dependency "json"
21
+
22
+ s.files = [
23
+ "COPYING",
24
+ ".gitignore",
25
+ "README.rdoc",
26
+ "clouddb.gemspec",
27
+ "lib/clouddb.rb",
28
+ "lib/clouddb/authentication.rb",
29
+ "lib/clouddb/flavor.rb",
30
+ "lib/clouddb/instance.rb",
31
+ "lib/clouddb/database.rb",
32
+ "lib/clouddb/user.rb",
33
+ "lib/clouddb/connection.rb",
34
+ "lib/clouddb/exception.rb",
35
+ "lib/clouddb/version.rb"
36
+ ]
37
+ s.require_path = 'lib'
38
+ end
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # == Cloud Databases API
4
+ # ==== Connects Ruby Applications to Rackspace's {Cloud Databases service}[http://www.rackspace.com/cloud/cloud_hosting_products/databases/]
5
+ # By Jorge Miramontes <jorge.miramontes@rackspace.com> and H. Wade Minter <minter@lunenburg.org>
6
+ #
7
+ # See COPYING for license information.
8
+ # Copyright (c) 2012, 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
+ # CloudDB::Connection class.
14
+ #
15
+ # The CloudDB class is the base class. Not much of note aside from housekeeping happens here.
16
+ # To create a new CloudDB connection, use the CloudDB::Connection.new method.
17
+
18
+ module CloudDB
19
+
20
+ AUTH_USA = "https://auth.api.rackspacecloud.com/v1.0"
21
+ AUTH_UK = "https://lon.auth.api.rackspacecloud.com/v1.0"
22
+
23
+ require 'uri'
24
+ require 'rubygems'
25
+ require 'json'
26
+ require 'date'
27
+ require 'typhoeus'
28
+
29
+ unless "".respond_to? :each_char
30
+ require "jcode"
31
+ $KCODE = 'u'
32
+ end
33
+
34
+ $:.unshift(File.dirname(__FILE__))
35
+ require 'clouddb/version'
36
+ require 'clouddb/exception'
37
+ require 'clouddb/authentication'
38
+ require 'clouddb/connection'
39
+ require 'clouddb/flavor'
40
+ require 'clouddb/instance'
41
+ require 'clouddb/database'
42
+ require 'clouddb/user'
43
+
44
+ # Helper method to recursively symbolize hash keys.
45
+ def self.symbolize_keys(obj)
46
+ case obj
47
+ when Array
48
+ obj.inject([]){|res, val|
49
+ res << case val
50
+ when Hash, Array
51
+ symbolize_keys(val)
52
+ else
53
+ val
54
+ end
55
+ res
56
+ }
57
+ when Hash
58
+ obj.inject({}){|res, (key, val)|
59
+ nkey = case key
60
+ when String
61
+ key.to_sym
62
+ else
63
+ key
64
+ end
65
+ nval = case val
66
+ when Hash, Array
67
+ symbolize_keys(val)
68
+ else
69
+ val
70
+ end
71
+ res[nkey] = nval
72
+ res
73
+ }
74
+ else
75
+ obj
76
+ end
77
+ end
78
+
79
+ def self.hydra
80
+ @@hydra ||= Typhoeus::Hydra.new
81
+ end
82
+
83
+ # CGI.escape, but without special treatment on spaces
84
+ def self.escape(str,extra_exclude_chars = '')
85
+ str.gsub(/([^a-zA-Z0-9_.-#{extra_exclude_chars}]+)/) do
86
+ '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
87
+ end
88
+ end
89
+
90
+ def self.paginate(options = {})
91
+ path_args = []
92
+ path_args.push(URI.encode("limit=#{options[:limit]}")) if options[:limit]
93
+ path_args.push(URI.encode("offset=#{options[:offset]}")) if options[:offset]
94
+ path_args.join("&")
95
+ end
96
+
97
+
98
+ end
@@ -0,0 +1,36 @@
1
+ module CloudDB
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
+ request = Typhoeus::Request.new(connection.auth_url,
12
+ :method => :get,
13
+ :headers => { "X-Auth-User" => connection.authuser, "X-Auth-Key" => connection.authkey, "User-Agent" => "Cloud Databases Ruby API #{VERSION}" },
14
+ :verbose => ENV['DATABASES_VERBOSE'] ? true : false)
15
+ CloudDB.hydra.queue(request)
16
+ CloudDB.hydra.run
17
+ response = request.response
18
+ headers = response.headers_hash
19
+ if (response.code.to_s == "204")
20
+ connection.authtoken = headers["x-auth-token"]
21
+ user_id = headers["x-server-management-url"].match(/.*\/(\d+)$/)[1]
22
+ headers["x-server-management-url"] = "https://#{connection.region}.databases.api.rackspacecloud.com/v1.0/#{user_id}"
23
+ connection.dbmgmthost = URI.parse(headers["x-server-management-url"]).host
24
+ connection.dbmgmtpath = URI.parse(headers["x-server-management-url"]).path.chomp
25
+ # Force the path into the v1.0 URL space
26
+ connection.dbmgmtpath.sub!(/\/.*?\//, '/v1.0/')
27
+ connection.dbmgmtport = URI.parse(headers["x-server-management-url"]).port
28
+ connection.dbmgmtscheme = URI.parse(headers["x-server-management-url"]).scheme
29
+ connection.authok = true
30
+ else
31
+ connection.authtoken = false
32
+ raise CloudDB::Exception::Authentication, "Authentication failed with response code #{response.code}"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,224 @@
1
+ module CloudDB
2
+ class Connection
3
+
4
+ attr_reader :authuser
5
+ attr_reader :authkey
6
+ attr_accessor :authtoken
7
+ attr_accessor :authok
8
+ attr_accessor :dbmgmthost
9
+ attr_accessor :dbmgmtpath
10
+ attr_accessor :dbmgmtport
11
+ attr_accessor :dbmgmtscheme
12
+ attr_reader :auth_url
13
+ attr_reader :region
14
+
15
+ # Creates a new CloudDB::Connection object. Uses CloudDB::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 re-authenticate.
19
+ #
20
+ # This will likely be the base class for most operations.
21
+ #
22
+ # The constructor takes a hash of options, including:
23
+ # :username - Your Rackspace Cloud username *required*
24
+ # :api_key - Your Rackspace Cloud API key *required*
25
+ # :region - The region in which to manage database instances. Current options are :dfw (Rackspace Dallas/Ft. Worth
26
+ # Datacenter), :ord (Rackspace Chicago Datacenter) and :lon (Rackspace London Datacenter). *required*
27
+ # :auth_url - The URL to use for authentication. (defaults to Rackspace USA).
28
+ # :retry_auth - Whether to retry if your auth token expires (defaults to true)
29
+ #
30
+ # Example:
31
+ # dbaas = CloudDB::Connection.new(:username => 'YOUR_USERNAME', :api_key => 'YOUR_API_KEY', :region => :dfw)
32
+ def initialize(options = {:retry_auth => true})
33
+ @authuser = options[:username] || (raise CloudDB::Exception::Authentication, "Must supply a :username")
34
+ @authkey = options[:api_key] || (raise CloudDB::Exception::Authentication, "Must supply an :api_key")
35
+ @region = options[:region] || (raise CloudDB::Exception::Authentication, "Must supply a :region")
36
+ @retry_auth = options[:retry_auth]
37
+ @auth_url = options[:auth_url] || CloudDB::AUTH_USA
38
+ @snet = ENV['RACKSPACE_SERVICENET'] || options[:snet]
39
+ @authok = false
40
+ @http = {}
41
+ CloudDB::Authentication.new(self)
42
+ end
43
+
44
+ # Returns true if the authentication was successful and returns false otherwise.
45
+ #
46
+ # Example:
47
+ # dbaas.authok?
48
+ # => true
49
+ def authok?
50
+ @authok
51
+ end
52
+
53
+ # Returns the list of available database instances.
54
+ #
55
+ # Information returned includes:
56
+ # :id - The numeric id of the instance.
57
+ # :name - The name of the instance.
58
+ # :status - The current state of the instance (BUILD, ACTIVE, BLOCKED, RESIZE, SHUTDOWN, FAILED).
59
+ def list_instances()
60
+ response = dbreq("GET", dbmgmthost, "#{dbmgmtpath}/instances", dbmgmtport, dbmgmtscheme)
61
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
62
+ instances = CloudDB.symbolize_keys(JSON.parse(response.body)["instances"])
63
+ return instances
64
+ end
65
+ alias :instances :list_instances
66
+
67
+ # Returns the list of available database instances with detail.
68
+ #
69
+ # Information returned includes:
70
+ # :id - The numeric ID of the instance.
71
+ # :name - The name of the instance.
72
+ # :status - The current state of the instance (BUILD, ACTIVE, BLOCKED, RESIZE, SHUTDOWN, FAILED).
73
+ # :hostname - A DNS-resolvable hostname associated with the database instance.
74
+ # :flavor - The flavor of the instance.
75
+ # :volume - The volume size of the instance.
76
+ # :created - The time when the instance was created.
77
+ # :updated - The time when the instance was last updated.
78
+ def list_instances_detail()
79
+ response = dbreq("GET", dbmgmthost, "#{dbmgmtpath}/instances/detail", dbmgmtport, dbmgmtscheme)
80
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
81
+ instances = CloudDB.symbolize_keys(JSON.parse(response.body)["instances"])
82
+ return instances
83
+ end
84
+ alias :instances_detail :list_instances_detail
85
+
86
+ # Returns a CloudDB::Instance object for the given instance ID number.
87
+ #
88
+ # Example:
89
+ # dbaas.get_instance(692d8418-7a8f-47f1-8060-59846c6e024f)
90
+ def get_instance(id)
91
+ CloudDB::Instance.new(self,id)
92
+ end
93
+ alias :instance :get_instance
94
+
95
+ # Creates a brand new database instance under your account.
96
+ #
97
+ # Options:
98
+ # :flavor_ref - reference to a flavor as specified in the response from the List Flavors API call. *required*
99
+ # :name - the name of the database instance. Limited to 128 characters or less. *required*
100
+ # :size - specifies the volume size in gigabytes (GB). The value specified must be between 1 and 10. *required*
101
+ # :databases - the databases to be created for the instance.
102
+ # :users - the users to be created for the instance.
103
+ #
104
+ # Example:
105
+ # i = dbaas.create_instance(:flavor_ref => "https://ord.databases.api.rackspacecloud.com/v1.0/1234/flavors/1",
106
+ # :name => "test_instance",
107
+ # :volume => {:size => "1"},
108
+ # :databases => [{:name => "testdb"}],
109
+ # :users => [{:name => "test",
110
+ # :password => "test",
111
+ # :databases => [{:name => "testdb"}]}
112
+ # ]
113
+ # )
114
+ def create_instance(options = {})
115
+ body = Hash.new
116
+ body[:instance] = Hash.new
117
+
118
+ body[:instance][:flavorRef] = options[:flavor_ref] or raise CloudDB::Exception::MissingArgument, "Must provide a flavor to create an instance"
119
+ body[:instance][:name] = options[:name] or raise CloudDB::Exception::MissingArgument, "Must provide a name to create an instance"
120
+ body[:instance][:volume] = options[:volume] or raise CloudDB::Exception::MissingArgument, "Must provide a size to create an instance"
121
+ body[:instance][:databases] = options[:databases] if options[:databases]
122
+ body[:instance][:users] = options[:users] if options[:users]
123
+ (raise CloudDB::Exception::Syntax, "Instance name must be 128 characters or less") if options[:name].size > 128
124
+
125
+ response = dbreq("POST", dbmgmthost, "#{dbmgmtpath}/instances", dbmgmtport, dbmgmtscheme, {}, body.to_json)
126
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
127
+ body = JSON.parse(response.body)['instance']
128
+ return get_instance(body["id"])
129
+ end
130
+
131
+ # Returns the list of available database flavors.
132
+ #
133
+ # Information returned includes:
134
+ # :id - The numeric id of this flavor
135
+ # :name - The name of the flavor
136
+ # :links - Useful information regarding the flavor
137
+ def list_flavors()
138
+ response = dbreq("GET", dbmgmthost, "#{dbmgmtpath}/flavors", dbmgmtport, dbmgmtscheme)
139
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
140
+ flavors = CloudDB.symbolize_keys(JSON.parse(response.body)["flavors"])
141
+ return flavors
142
+ end
143
+ alias :flavors :list_flavors
144
+
145
+ # Returns the list of available database flavors in detail.
146
+ #
147
+ # Information returned includes:
148
+ # :id - The numeric id of this flavor
149
+ # :name - The name of the flavor
150
+ # :vcpus - The amount of virtual cpu power
151
+ # :ram - The available memory in MB
152
+ # :links - Useful information regarding the flavor
153
+ def list_flavors_detail()
154
+ response = dbreq("GET", dbmgmthost, "#{dbmgmtpath}/flavors/detail", dbmgmtport, dbmgmtscheme)
155
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
156
+ flavors = CloudDB.symbolize_keys(JSON.parse(response.body)["flavors"])
157
+ return flavors
158
+ end
159
+ alias :flavors_detail :list_flavors_detail
160
+
161
+ # Returns a CloudDB::Flavor object for the given flavor id number.
162
+ #
163
+ # Example:
164
+ # dbaas.get_flavor(3)
165
+ def get_flavor(id)
166
+ CloudDB::Flavor.new(self,id)
167
+ end
168
+ alias :flavor :get_flavor
169
+
170
+ # This method actually makes the HTTP REST calls out to the server. Relies on the thread-safe typhoeus
171
+ # gem to do the heavy lifting. Never called directly.
172
+ def dbreq(method, server, path, port, scheme, headers = {}, data = nil, attempts = 0) # :nodoc:
173
+ if data
174
+ unless data.is_a?(IO)
175
+ headers['Content-Length'] = data.respond_to?(:lstat) ? data.stat.size : data.size
176
+ end
177
+ else
178
+ headers['Content-Length'] = 0
179
+ end
180
+ hdrhash = headerprep(headers)
181
+ url = "#{scheme}://#{server}#{path}"
182
+ print "DEBUG: Data is #{data}\n" if (data && ENV['DATABASES_VERBOSE'])
183
+ request = Typhoeus::Request.new(url,
184
+ :body => data,
185
+ :method => method.downcase.to_sym,
186
+ :headers => hdrhash,
187
+ :verbose => ENV['DATABASES_VERBOSE'] ? true : false)
188
+ CloudDB.hydra.queue(request)
189
+ CloudDB.hydra.run
190
+
191
+ response = request.response
192
+ print "DEBUG: Body is #{response.body}\n" if ENV['DATABASES_VERBOSE']
193
+ raise CloudDB::Exception::ExpiredAuthToken if response.code.to_s == "401"
194
+ response
195
+ rescue Errno::EPIPE, Errno::EINVAL, EOFError
196
+ # Server closed the connection, retry
197
+ raise CloudDB::Exception::Connection, "Unable to reconnect to #{server} after #{attempts} attempts" if attempts >= 5
198
+ attempts += 1
199
+ @http[server].finish if @http[server].started?
200
+ start_http(server,path,port,scheme,headers)
201
+ retry
202
+ rescue CloudDB::Exception::ExpiredAuthToken
203
+ raise CloudDB::Exception::Connection, "Authentication token expired and you have requested not to retry" if @retry_auth == false
204
+ CloudDB::Authentication.new(self)
205
+ retry
206
+ end
207
+
208
+
209
+ private
210
+
211
+ # Sets up standard HTTP headers
212
+ def headerprep(headers = {}) # :nodoc:
213
+ default_headers = {}
214
+ default_headers["X-Auth-Token"] = @authtoken if (authok? && @account.nil?)
215
+ default_headers["X-Storage-Token"] = @authtoken if (authok? && !@account.nil?)
216
+ default_headers["Connection"] = "Keep-Alive"
217
+ default_headers["Accept"] = "application/json"
218
+ default_headers["Content-Type"] = "application/json"
219
+ default_headers["User-Agent"] = "Cloud Databases Ruby API #{CloudDB::VERSION}"
220
+ default_headers.merge(headers)
221
+ end
222
+
223
+ end
224
+ end
@@ -0,0 +1,27 @@
1
+ module CloudDB
2
+ class Database
3
+
4
+ attr_reader :name
5
+
6
+ # Creates a new CloudDB::Database object representing a database.
7
+ def initialize(instance, name)
8
+ @connection = instance.connection
9
+ @instance = instance
10
+ @name = name
11
+ @dbmgmthost = @connection.dbmgmthost
12
+ @dbmgmtpath = @connection.dbmgmtpath
13
+ @dbmgmtport = @connection.dbmgmtport
14
+ @dbmgmtscheme = @connection.dbmgmtscheme
15
+ self
16
+ end
17
+
18
+ # Deletes the current Database object and removes it from the instance. Returns true if successful, raises an
19
+ # exception if not.
20
+ def destroy!
21
+ response = @connection.dbreq("DELETE",@dbmgmthost,"#{@dbmgmtpath}/instances/#{CloudDB.escape(@instance.id.to_s)}/databases/#{CloudDB.escape(@name.to_s)}",@dbmgmtport,@dbmgmtscheme)
22
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
23
+ true
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,73 @@
1
+ module CloudDB
2
+ class Exception
3
+
4
+ class CloudDBError < StandardError
5
+
6
+ attr_reader :response_body
7
+ attr_reader :response_code
8
+
9
+ def initialize(message, code, response_body)
10
+ @response_code=code
11
+ @response_body=response_body
12
+ super(message)
13
+ end
14
+
15
+ end
16
+
17
+ class ServiceFault < CloudDBError # :nodoc:
18
+ end
19
+ class InstanceFault < CloudDBError # :nodoc:
20
+ end
21
+ class ServiceUnavailable < CloudDBError # :nodoc:
22
+ end
23
+ class Unauthorized < CloudDBError # :nodoc:
24
+ end
25
+ class BadRequest < CloudDBError # :nodoc:
26
+ end
27
+ class ItemNotFound < CloudDBError # :nodoc:
28
+ end
29
+ class OverLimit < CloudDBError # :nodoc:
30
+ end
31
+ class ImmutableEntity < CloudDBError # :nodoc:
32
+ end
33
+ class UnprocessableEntity < CloudDBError # :nodoc:
34
+ end
35
+ class Other < CloudDBError # :nodoc:
36
+ end
37
+
38
+ # Plus some others that we define here
39
+
40
+ class ExpiredAuthToken < StandardError # :nodoc:
41
+ end
42
+ class MissingArgument < StandardError # :nodoc:
43
+ end
44
+ class Authentication < StandardError # :nodoc:
45
+ end
46
+ class Connection < StandardError # :nodoc:
47
+ end
48
+ class Syntax < StandardError # :nodoc:
49
+ end
50
+
51
+
52
+ # In the event of a non-200 HTTP status code, this method takes the HTTP response, parses
53
+ # the JSON from the body to get more information about the exception, then raises the
54
+ # proper error. Note that all exceptions are scoped in the CloudDB::Exception namespace.
55
+ def self.raise_exception(response)
56
+ return if response.code =~ /^20.$/
57
+ begin
58
+ fault = nil
59
+ info = nil
60
+ JSON.parse(response.body).each_pair do |key, val|
61
+ fault=key
62
+ info=val
63
+ end
64
+ exception_class = self.const_get(fault[0,1].capitalize+fault[1,fault.length])
65
+ raise exception_class.new(info["message"], response.code, response.body)
66
+ rescue NameError, JSON::ParserError
67
+ raise CloudDB::Exception::Other.new("The server returned status #{response.code} with body #{response.body}", response.code, response.body)
68
+ end
69
+ end
70
+
71
+ end
72
+ end
73
+
@@ -0,0 +1,36 @@
1
+ module CloudDB
2
+ class Flavor
3
+
4
+ attr_reader :id
5
+ attr_reader :name
6
+ attr_reader :ram
7
+ attr_reader :vcpus
8
+
9
+ # Creates a new CloudDB::Flavor object representing a database flavor.
10
+ def initialize(connection,id)
11
+ @connection = connection
12
+ @id = id
13
+ @dbmgmthost = connection.dbmgmthost
14
+ @dbmgmtpath = connection.dbmgmtpath
15
+ @dbmgmtport = connection.dbmgmtport
16
+ @dbmgmtscheme = connection.dbmgmtscheme
17
+ populate
18
+ return self
19
+ end
20
+
21
+ # Updates the information about the current Flavor object by making an API call.
22
+ def populate
23
+ response = @connection.dbreq("GET",@dbmgmthost,"#{@dbmgmtpath}/flavors/#{CloudDB.escape(@id.to_s)}",@dbmgmtport,@dbmgmtscheme)
24
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
25
+ data = JSON.parse(response.body)['flavor']
26
+ @id = data["id"]
27
+ @name = data["name"]
28
+ @ram = data["ram"]
29
+ @vcpus = data["vcpus"]
30
+ @links = data["links"]
31
+ true
32
+ end
33
+ alias :refresh :populate
34
+
35
+ end
36
+ end
@@ -0,0 +1,244 @@
1
+ module CloudDB
2
+ class Instance
3
+
4
+ attr_reader :connection
5
+
6
+ attr_reader :id
7
+ attr_reader :name
8
+ attr_reader :hostname
9
+ attr_reader :flavor_id
10
+ attr_reader :root_enabled
11
+ attr_reader :volume_used
12
+ attr_reader :volume_size
13
+ attr_reader :status
14
+ attr_reader :created
15
+ attr_reader :updated
16
+ attr_reader :links
17
+
18
+ # Creates a new CloudDB::Instance object representing a database instance.
19
+ def initialize(connection,id)
20
+ @connection = connection
21
+ @id = id
22
+ @dbmgmthost = connection.dbmgmthost
23
+ @dbmgmtpath = connection.dbmgmtpath
24
+ @dbmgmtport = connection.dbmgmtport
25
+ @dbmgmtscheme = connection.dbmgmtscheme
26
+ populate
27
+ self
28
+ end
29
+
30
+ # Updates the information about the current instance object by making an API call.
31
+ def populate
32
+ response = @connection.dbreq("GET", @dbmgmthost, "#{@dbmgmtpath}/instances/#{CloudDB.escape(@id.to_s)}", @dbmgmtport, @dbmgmtscheme)
33
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
34
+ data = JSON.parse(response.body)['instance']
35
+ @id = data["id"]
36
+ @name = data["name"]
37
+ @hostname = data["hostname"]
38
+ @flavor_id = data["flavor"]["id"] if data["flavor"]
39
+ @root_enabled = data["rootEnabled"]
40
+ @volume_used = data["volume"]["used"] if data["volume"]
41
+ @volume_size = data["volume"]["size"] if data["volume"]
42
+ @status = data["status"]
43
+ @created = data["created"]
44
+ @updated = data["updated"]
45
+ @links = data["links"]
46
+ true
47
+ end
48
+ alias :refresh :populate
49
+
50
+ # Lists the databases associated with this instance
51
+ #
52
+ # Example:
53
+ # i.list_databases
54
+ def list_databases
55
+ response = @connection.dbreq("GET", @dbmgmthost, "#{@dbmgmtpath}/instances/#{CloudDB.escape(@id.to_s)}/databases", @dbmgmtport, @dbmgmtscheme)
56
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
57
+ CloudDB.symbolize_keys(JSON.parse(response.body)["databases"])
58
+ end
59
+ alias :databases :list_databases
60
+
61
+ # Returns a CloudDB::Database object for the given database name.
62
+ def get_database(name)
63
+ CloudDB::Database.new(self, name)
64
+ end
65
+ alias :database :get_database
66
+
67
+ # Creates brand new databases and associates them with the current instance. Returns true if successful.
68
+ #
69
+ # Options for each database in the array:
70
+ # :name - Specifies the database name for creating the database. *required*
71
+ # :character_set - Set of symbols and encodings. The default character set is utf8.
72
+ # :collate - Set of rules for comparing characters in a character set. The default value for collate is
73
+ # utf8_general_ci.
74
+ def create_databases(databases)
75
+ (raise CloudDB::Exception::Syntax, "Must provide at least one database in the array") if (!databases.is_a?(Array) || databases.size < 1)
76
+
77
+ body = Hash.new
78
+ body[:databases] = Array.new
79
+
80
+ for database in databases
81
+ new_database = Hash.new
82
+ new_database[:name] = database[:name] or raise CloudDB::Exception::MissingArgument, "Must provide a name for each database"
83
+ new_database[:character_set] = database[:character_set] || 'utf8'
84
+ new_database[:collate] = database[:collate] || 'utf8_general_ci'
85
+ (raise CloudDB::Exception::Syntax, "Database names must be 64 characters or less") if database[:name].size > 64
86
+
87
+ body[:databases] << new_database
88
+ end
89
+
90
+ response = @connection.dbreq("POST", @dbmgmthost, "#{@dbmgmtpath}/instances/#{CloudDB.escape(@id.to_s)}/databases", @dbmgmtport, @dbmgmtscheme, {}, body.to_json)
91
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
92
+ true
93
+ end
94
+
95
+ # Creates a brand new database and associates it with the current instance. Returns true if successful.
96
+ #
97
+ # Options:
98
+ # :name - Specifies the database name for creating the database. *required*
99
+ # :character_set - Set of symbols and encodings. The default character set is utf8.
100
+ # :collate - Set of rules for comparing characters in a character set. The default value for collate is
101
+ # utf8_general_ci.
102
+ def create_database(options={})
103
+ new_databases = Array.new
104
+ new_databases << options
105
+ create_databases new_databases
106
+ end
107
+
108
+ # Lists the users associated with the current Instance
109
+ #
110
+ # Example:
111
+ # i.list_users
112
+ def list_users
113
+ response = @connection.dbreq("GET", @dbmgmthost, "#{@dbmgmtpath}/instances/#{CloudDB.escape(@id.to_s)}/users", @dbmgmtport, @dbmgmtscheme)
114
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
115
+ CloudDB.symbolize_keys(JSON.parse(response.body)["users"])
116
+ end
117
+ alias :users :list_users
118
+
119
+ # Returns a CloudDB::User object for the given user name.
120
+ def get_user(name)
121
+ CloudDB::User.new(self, name)
122
+ end
123
+ alias :user :get_user
124
+
125
+ # Creates brand new users and associates them with the current instance. Returns true if successful.
126
+ #
127
+ # Options for each user in the array:
128
+ # :name - Name of the user for the database(s). *required*
129
+ # :password - User password for database access. *required*
130
+ # :databases - An array of databases with at least one database. *required*
131
+ def create_users(users)
132
+ (raise CloudDB::Exception::Syntax, "Must provide at least one user in the array") if (!users.is_a?(Array) || users.size < 1)
133
+
134
+ body = Hash.new
135
+ body[:users] = Array.new
136
+
137
+ for user in users
138
+ new_user = Hash.new
139
+ new_user[:name] = user[:name] or raise CloudDB::Exception::MissingArgument, "Must provide a name for each user"
140
+ new_user[:password] = user[:password] or raise CloudDB::Exception::MissingArgument, "Must provide a password for each user"
141
+ new_user[:databases] = user[:databases]
142
+ (raise CloudDB::Exception::Syntax, "User names must be 16 characters or less") if user[:name].size > 16
143
+ (raise CloudDB::Exception::Syntax, "Must provide at least one database in each user :databases array") if (!user[:databases].is_a?(Array) || user[:databases].size < 1)
144
+
145
+ body[:users] << new_user
146
+ end
147
+
148
+ response = @connection.dbreq("POST", @dbmgmthost, "#{@dbmgmtpath}/instances/#{CloudDB.escape(@id.to_s)}/users", @dbmgmtport, @dbmgmtscheme, {}, body.to_json)
149
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
150
+ true
151
+ end
152
+
153
+ # Creates a brand new user and associates it with the current instance. Returns true if successful.
154
+ #
155
+ # Options:
156
+ # :name - Name of the user for the database(s). *required*
157
+ # :password - User password for database access. *required*
158
+ # :databases - An array of databases with at least one database. *required*
159
+ def create_user(options={})
160
+ new_users = Array.new
161
+ new_users << options
162
+ create_users new_users
163
+ end
164
+
165
+ # Enables the root user for the specified database instance and returns the root password.
166
+ #
167
+ # Example:
168
+ # i.enable_root
169
+ # => true
170
+ def enable_root()
171
+ response = @connection.dbreq("POST", @dbmgmthost, "#{@dbmgmtpath}/instances/#{CloudDB.escape(@id.to_s)}/root", @dbmgmtport, @dbmgmtscheme, {})
172
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
173
+ @root_enabled = true
174
+ body = JSON.parse(response.body)['user']
175
+ return body
176
+ end
177
+
178
+ # Returns true if root user is enabled for the specified database instance or false otherwise.
179
+ #
180
+ # Example:
181
+ # i.root_enabled?
182
+ # => true
183
+ def root_enabled?()
184
+ response = @connection.dbreq("GET", @dbmgmthost, "#{@dbmgmtpath}/instances/#{CloudDB.escape(@id.to_s)}/root", @dbmgmtport, @dbmgmtscheme, {})
185
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
186
+ @root_enabled = JSON.parse(response.body)['rootEnabled']
187
+ return @root_enabled
188
+ end
189
+
190
+ # This operation changes the memory size of the instance, assuming a valid flavorRef is provided. Restarts MySQL in
191
+ # the process.
192
+ #
193
+ # Options:
194
+ # :flavor_ref - reference to a flavor as specified in the response from the List Flavors API call. *required*
195
+ def resize(options={})
196
+ body = Hash.new
197
+ body[:resize] = Hash.new
198
+
199
+ body[:resize][:flavorRef] = options[:flavor_ref] or raise CloudDB::Exception::MissingArgument, "Must provide a flavor to create an instance"
200
+
201
+ response = @connection.dbreq("POST", @dbmgmthost, "#{@dbmgmtpath}/instances/#{CloudDB.escape(@id.to_s)}/action", @dbmgmtport, @dbmgmtscheme, {}, body.to_json)
202
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
203
+ true
204
+ end
205
+
206
+ # This operation supports resizing the attached volume for an instance. It supports only increasing the volume size
207
+ # and does not support decreasing the size. The volume size is in gigabytes (GB) and must be an integer.
208
+ #
209
+ # Options:
210
+ # :size - specifies the volume size in gigabytes (GB). The value specified must be between 1 and 10. *required*
211
+ def resize_volume(options={})
212
+ body = Hash.new
213
+ body[:resize] = Hash.new
214
+ volume = Hash.new
215
+ body[:resize][:volume] = volume
216
+
217
+ volume[:size] = options[:size] or raise CloudDB::Exception::MissingArgument, "Must provide a volume size"
218
+ (raise CloudDB::Exception::Syntax, "Volume size must be a value between 1 and 10") if !options[:size].is_a?(Integer) || options[:size] < 1 || options[:size] > 10
219
+
220
+ response = @connection.dbreq("POST", @dbmgmthost, "#{@dbmgmtpath}/instances/#{CloudDB.escape(@id.to_s)}/action", @dbmgmtport, @dbmgmtscheme, {}, body.to_json)
221
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
222
+ true
223
+ end
224
+
225
+ # The restart operation will restart only the MySQL Instance. Restarting MySQL will erase any dynamic configuration
226
+ # settings that you have made within MySQL.
227
+ def restart()
228
+ body = Hash.new
229
+ body[:restart] = Hash.new
230
+
231
+ response = @connection.dbreq("POST", @dbmgmthost, "#{@dbmgmtpath}/instances/#{CloudDB.escape(@id.to_s)}/action", @dbmgmtport, @dbmgmtscheme, {}, body.to_json)
232
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
233
+ true
234
+ end
235
+
236
+ # Deletes the current instance object. Returns true if successful, raises an exception otherwise.
237
+ def destroy!
238
+ response = @connection.dbreq("DELETE", @dbmgmthost, "#{@dbmgmtpath}/instances/#{CloudDB.escape(@id.to_s)}", @dbmgmtport, @dbmgmtscheme)
239
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^202$/)
240
+ true
241
+ end
242
+
243
+ end
244
+ end
@@ -0,0 +1,27 @@
1
+ module CloudDB
2
+ class User
3
+
4
+ attr_reader :name
5
+
6
+ # Creates a new CloudDB::User object representing a database.
7
+ def initialize(instance, name)
8
+ @connection = instance.connection
9
+ @instance = instance
10
+ @name = name
11
+ @dbmgmthost = @connection.dbmgmthost
12
+ @dbmgmtpath = @connection.dbmgmtpath
13
+ @dbmgmtport = @connection.dbmgmtport
14
+ @dbmgmtscheme = @connection.dbmgmtscheme
15
+ self
16
+ end
17
+
18
+ # Deletes the current User object and removes it from the instance. Returns true if successful, raises an
19
+ # exception if not.
20
+ def destroy!
21
+ response = @connection.dbreq("DELETE",@dbmgmthost,"#{@dbmgmtpath}/instances/#{CloudDB.escape(@instance.id.to_s)}/users/#{CloudDB.escape(@name.to_s)}",@dbmgmtport,@dbmgmtscheme)
22
+ CloudDB::Exception.raise_exception(response) unless response.code.to_s.match(/^20.$/)
23
+ true
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ module CloudDB
2
+ VERSION = '0.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: clouddb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - ! '"Jorge'
9
+ - Miramontes",
10
+ - ! '"H.'
11
+ - Wade
12
+ - Minter"
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+ date: 2012-04-30 00:00:00.000000000 Z
17
+ dependencies:
18
+ - !ruby/object:Gem::Dependency
19
+ name: typhoeus
20
+ requirement: !ruby/object:Gem::Requirement
21
+ none: false
22
+ requirements:
23
+ - - ! '>='
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ type: :runtime
27
+ prerelease: false
28
+ version_requirements: !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ - !ruby/object:Gem::Dependency
35
+ name: json
36
+ requirement: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ description: A Ruby API to manage the Rackspace Cloud Databases product
51
+ email:
52
+ - jorge.miramontes@rackspace.com
53
+ - minter@lunenburg.org
54
+ executables: []
55
+ extensions: []
56
+ extra_rdoc_files: []
57
+ files:
58
+ - COPYING
59
+ - .gitignore
60
+ - README.rdoc
61
+ - clouddb.gemspec
62
+ - lib/clouddb.rb
63
+ - lib/clouddb/authentication.rb
64
+ - lib/clouddb/flavor.rb
65
+ - lib/clouddb/instance.rb
66
+ - lib/clouddb/database.rb
67
+ - lib/clouddb/user.rb
68
+ - lib/clouddb/connection.rb
69
+ - lib/clouddb/exception.rb
70
+ - lib/clouddb/version.rb
71
+ homepage: http://github.com/rackspace/ruby-clouddb
72
+ licenses: []
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: 1.3.6
89
+ requirements: []
90
+ rubyforge_project:
91
+ rubygems_version: 1.8.19
92
+ signing_key:
93
+ specification_version: 3
94
+ summary: Ruby API into the Rackspace Cloud Databases product
95
+ test_files: []