right_rackspace 0.0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,528 @@
1
+ #
2
+ # Copyright (c) 2009 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+ #
24
+ module Rightscale
25
+ module Rackspace
26
+
27
+ class BenchmarkingBlock #:nodoc:
28
+ attr_accessor :parser, :service
29
+ def initialize
30
+ # Benchmark::Tms instance for service access benchmarking.
31
+ @service = Benchmark::Tms.new()
32
+ # Benchmark::Tms instance for parsing benchmarking.
33
+ @parser = Benchmark::Tms.new()
34
+ end
35
+ end
36
+
37
+ class Interface
38
+ DEFAULT_AUTH_ENDPOINT = "https://auth.api.rackspacecloud.com/v1.0"
39
+ DEFAULT_LIMIT = 1000
40
+
41
+ @@rackspace_problems = []
42
+ # A Regexps of errors say the Rackspace has issues and
43
+ # a request is likely to be repeated
44
+ def self.rackspace_problems
45
+ @@rackspace_problems
46
+ end
47
+
48
+ @@bench = Rightscale::Rackspace::BenchmarkingBlock.new
49
+ def self.bench
50
+ @@bench
51
+ end
52
+
53
+ @@params = {}
54
+ def self.params
55
+ @@params
56
+ end
57
+
58
+ def params
59
+ @params
60
+ end
61
+
62
+ def merged_params #:nodoc:
63
+ @@params.merge(@params)
64
+ end
65
+
66
+ @@caching = false
67
+
68
+ attr_accessor :username
69
+ attr_accessor :auth_key
70
+ attr_reader :logged_in
71
+ attr_reader :auth_headers
72
+ attr_reader :auth_token
73
+ attr_accessor :auth_endpoint
74
+ attr_accessor :service_endpoint
75
+ attr_accessor :last_request
76
+ attr_accessor :last_response
77
+ attr_accessor :last_error
78
+ attr_reader :logger
79
+ attr_reader :cache
80
+
81
+ # Parses an endpoint and returns a hash of data
82
+ def endpoint_to_host_data(endpoint)# :nodoc:
83
+ service = URI.parse(endpoint).path
84
+ service.chop! if service[/\/$/] # remove a trailing '/'
85
+ { :server => URI.parse(endpoint).host,
86
+ :service => service,
87
+ :protocol => URI.parse(endpoint).scheme,
88
+ :port => URI.parse(endpoint).port }
89
+ end
90
+
91
+ # Create new Rackspace interface handle.
92
+ #
93
+ # Params:
94
+ # :logger - a logger object
95
+ # :caching - enabled/disables RightRackspace caching on level (only for list_xxx calls)
96
+ # :verbose_errors - verbose error messages
97
+ # :auth_endpoint - an auth endpoint URL ()
98
+ # :service_endpoint - a service endpoint URL
99
+ # callbacks:
100
+ # :on_request(self, request_hash) - every time before the request
101
+ # :on_response(self) - every time the response comes
102
+ # :on_error(self, error_message) - every time the response is not 2xx | 304
103
+ # :on_success(self) - once after the successfull response
104
+ # :on_login(self) - before login
105
+ # :on_login_success(self) - when login is successfull
106
+ # :on_login_failure(self) - when login fails
107
+ # :on_failure(self) - once after the unsuccessfull response
108
+ #
109
+ # Handle creation:
110
+ #
111
+ # # Just pass your username and youe key
112
+ # rackspace = Rightscale::Rackspace::Interface::new('uw1...cct', '99b0...047d')
113
+ #
114
+ # # The username and the key are in ENV vars: RACKSPACE_USERNAME & RACKSPACE_AUTH_KEY
115
+ # rackspace = Rightscale::Rackspace::Interface::new
116
+ #
117
+ # # Specify the auth endpoint and the service endpoint explicitly. Make the error
118
+ # # messages as verbose as possible.
119
+ # rackspace = Rightscale::Rackspace::Interface::new('uw1...cct', '99b0...047d',
120
+ # :auth_endpoint => 'https://api.mosso.com/auth',
121
+ # :service_point => 'https://servers.api.rackspacecloud.com/v1.0/413609',
122
+ # :verbose_errors => true )
123
+ #
124
+ # # Fix params after the handle creation:
125
+ # rackspace = Rightscale::Rackspace::Interface::new('uw1...cct', '99b0...047d')
126
+ # rackspace.params[:verbose_errors] = true
127
+ #
128
+ # Calbacks handling:
129
+ #
130
+ # # On response calback
131
+ # on_response = Proc.new do |handle|
132
+ # puts ">> response headers: #{handle.last_response.to_hash.inspect}"
133
+ # end
134
+ #
135
+ # # On error calback
136
+ # on_error = Proc.new do |handle, error_message|
137
+ # puts ">> Error: #{error_message}"
138
+ # end
139
+ #
140
+ # # Create a handle
141
+ # rackspace = Rightscale::Rackspace::Interface::new('uw1...cct', '99b0...047d',
142
+ # :on_response => on_response,
143
+ # :on_error => on_error)
144
+ #
145
+ def initialize(username=nil, auth_key=nil, params={})
146
+ @params = params
147
+ # Auth data
148
+ @username = username || ENV['RACKSPACE_USERNAME']
149
+ @auth_key = auth_key || ENV['RACKSPACE_AUTH_KEY']
150
+ @logged_in = false
151
+ # Auth host
152
+ @auth_headers = {} # a set of headers is returned on authentification coplete
153
+ @auth_endpoint = ENV['RACKSPACE_AUTH_ENDPOINT'] || params[:auth_endpoint] || DEFAULT_AUTH_ENDPOINT
154
+ @auth_endpoint_data = endpoint_to_host_data(@auth_endpoint)
155
+ # Logger
156
+ @logger = @params[:logger] || (defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER) || Logger.new(STDOUT)
157
+ # Request and response
158
+ @last_request = nil
159
+ @last_response = nil
160
+ # cache
161
+ @cache = {}
162
+ end
163
+
164
+ # Generate a request.
165
+ #
166
+ # opts:
167
+ # :body - String
168
+ # :endpoint_data - Hash
169
+ # :no_service_path - bool
170
+ # :headers - a hash or an array of HTTP headers
171
+ # :vars - a hash or an array of URL variables
172
+ #
173
+ def generate_request(verb, path='', opts={}) #:nodoc:
174
+ # Form a valid http verb: 'Get', 'Post', 'Put', 'Delete'
175
+ verb = verb.to_s.capitalize
176
+ raise "Unsupported HTTP verb #{verb.inspect}!" unless verb[/^(Get|Post|Put|Delete)$/]
177
+ # Select an endpoint
178
+ endpoint_data = (opts[:endpoint_data] || @service_endpoint_data).dup
179
+ # Fix a path
180
+ path = "/#{path}" if !path.blank? && !path[/^\//]
181
+ # Request variables
182
+ request_params = opts[:vars].to_a.map do |key, value|
183
+ key = key.to_s.downcase
184
+ # Make sure we do not pass a Time object instead of integer for 'changes-since'
185
+ value = value.to_i if key == 'changes-since'
186
+ "#{URI.escape(key)}=#{URI.escape(value.to_s)}"
187
+ end.join('&')
188
+ # Build a request final path
189
+ service = opts[:no_service_path] ? '' : endpoint_data[:service]
190
+ request_path = "#{service}#{path}"
191
+ request_path = '/' if request_path.blank?
192
+ request_path += "?#{request_params}" unless request_params.blank?
193
+ # Create a request
194
+ request = eval("Net::HTTP::#{verb}").new(request_path)
195
+ request.body = opts[:body] if opts[:body]
196
+ # Set headers
197
+ opts[:headers].to_a.each do |key, value|
198
+ key = key.to_s.downcase
199
+ # make sure 'if-modified-since' is always httpdated
200
+ if key == 'if-modified-since'
201
+ value = Time.at(value) if value.is_a?(Fixnum)
202
+ value = value.utc.httpdate if value.is_a?(Time)
203
+ end
204
+ request[key] = value.to_s
205
+ end
206
+ request['content-type'] ||= 'application/json'
207
+ request['accept'] = 'application/json'
208
+ # prepare output hash
209
+ endpoint_data.merge(:request => request)
210
+ end
211
+
212
+ # Just requests a remote end
213
+ def internal_request_info(request_hash) #:nodoc:
214
+ on_event(:on_request, request_hash)
215
+ @connection ||= Rightscale::HttpConnection.new(:exception => Error, :logger => @logger)
216
+ @last_request = request_hash[:request]
217
+ @@bench.service.add!{ @last_response = @connection.request(request_hash) }
218
+ on_event(:on_response)
219
+ end
220
+
221
+ # Request a remote end and process any errors is found
222
+ def request_info(request_hash) #:nodoc:
223
+ internal_request_info(request_hash)
224
+ result = nil
225
+ # check response for success...
226
+ case @last_response.code
227
+ when /^2..|304/ # SUCCESS
228
+ @error_handler = nil
229
+ on_event(:on_success)
230
+
231
+ # Cache hit: Cached
232
+ case @last_response.code
233
+ when '304' # 'changes-since' param
234
+ raise NoChange.new("NotModified: '#{simple_path(@last_request.path)}' has not changed since the requested time.")
235
+ when '203' # 'if-modified-since' header
236
+ # TODO: Mhhh... It seems Rackspace updates 'last-modified' header every 60 seconds or something even if nothing has changed
237
+ if @last_response.body.blank?
238
+ cached_path = cached_path(@last_request.path)
239
+ last_modified = @last_response['last-modified'].first
240
+ message_header = merged_params[:caching] &&
241
+ @cache[cached_path] &&
242
+ @cache[cached_path][:last_modified_at] == last_modified ? 'Cached' : 'NotModified'
243
+ # 203 + an empty response body means we asked whether the value did not change and it hit
244
+ raise NoChange.new("#{message_header}: '#{cached_path}' has not changed since #{last_modified}.")
245
+ end
246
+ end
247
+
248
+ # Parse a response body. If the body is empty the return +true+
249
+ @@bench.parser.add! do
250
+ result = if @last_response.body.blank? then true
251
+ else
252
+ case @last_response['content-type'].first
253
+ when 'application/json' then JSON::parse(@last_response.body)
254
+ else @last_response.body
255
+ end
256
+ end
257
+ end
258
+ else # ERROR
259
+ @last_error = HttpErrorHandler::extract_error_description(@last_response, merged_params[:verbose_errors])
260
+ on_event(:on_error, @last_error)
261
+ @error_handler ||= HttpErrorHandler.new(self, :errors_list => self.class.rackspace_problems)
262
+ result = @error_handler.check(request_hash)
263
+ @error_handler = nil
264
+ if result.nil?
265
+ on_event(:on_failure)
266
+ raise Error.new(@last_error)
267
+ end
268
+ end
269
+ result
270
+ rescue
271
+ @error_handler = nil
272
+ raise
273
+ end
274
+
275
+ # simple_path('/v1.0/123456/images/detail?var1=var2') #=> '/images/detail?var1=var2'
276
+ def simple_path(path) # :nodoc:
277
+ (path[/^#{@service_endpoint_data[:service]}(.*)/] && $1) || path
278
+ end
279
+
280
+ # simple_path('/v1.0/123456/images/detail?var1=var2') #=> '/images/detail'
281
+ def cached_path(path) # :nodoc:
282
+ simple_path(path)[/([^?]*)/] && $1
283
+ end
284
+
285
+ # detailed_path('/images', true) #=> '/images/detail'
286
+ def detailed_path(path, options) # :nodoc:
287
+ "#{path}#{options[:detail] ? '/detail' : ''}"
288
+ end
289
+
290
+ # Authenticate a user.
291
+ # Params: +soft+ is used for auto-authentication when auth_token expires. Soft auth
292
+ # do not overrides @last_request and @last_response attributes (are needed for a proper
293
+ # error handling) on success.
294
+ def authenticate(opts={}) # :nodoc:
295
+ @logged_in = false
296
+ @auth_headers = {}
297
+ opts = opts.dup
298
+ opts[:endpoint_data] = @auth_endpoint_data
299
+ (opts[:headers] ||= {}).merge!({ 'x-auth-user' => @username, 'x-auth-key' => @auth_key })
300
+ request_data = generate_request( :get, '', opts )
301
+ on_event(:on_login)
302
+ internal_request_info(request_data)
303
+ unless @last_response.is_a?(Net::HTTPSuccess)
304
+ @error_handler = nil
305
+ @last_error = HttpErrorHandler::extract_error_description(@last_response, merged_params[:verbose_errors])
306
+ on_event(:on_error, @last_error)
307
+ on_event(:on_login_failure)
308
+ on_event(:on_failure)
309
+ raise Error.new(@last_error)
310
+ end
311
+ # Store all auth response headers
312
+ @auth_headers = @last_response.to_hash
313
+ @auth_token = @auth_headers['x-auth-token'].first
314
+ # Service endpoint
315
+ @service_endpoint = merged_params[:service_endpoint] || @auth_headers['x-server-management-url'].first
316
+ @service_endpoint_data = endpoint_to_host_data(@service_endpoint)
317
+ @logged_in = true
318
+ on_event(:on_login_success)
319
+ true
320
+ end
321
+
322
+ # Incrementally lists something.
323
+ def incrementally_list_resources(verb, path, offset=nil, limit=nil, opts={}, &block) # :nodoc:
324
+ opts = opts.dup
325
+ opts[:vars] ||= {}
326
+ opts[:vars]['offset'] = offset || 0
327
+ opts[:vars]['limit'] = limit || DEFAULT_LIMIT
328
+ # Get a resource name by path:
329
+ # '/images' -> 'images'
330
+ # '/shared_ip_groups/detail' -> 'sharedIpGroups'
331
+ resource_name = ''
332
+ (path[%r{^/([^/]*)}] && $1).split('_').each_with_index do |w, i|
333
+ resource_name += (i==0 ? w.downcase : w.capitalize)
334
+ end
335
+ result = { resource_name => []}
336
+ loop do
337
+ # begin
338
+ response = api(verb, path, opts)
339
+ result[resource_name] += response[resource_name]
340
+ # rescue Rightscale::Rackspace::Error => e
341
+ # raise e unless e.message[/itemNotFound/]
342
+ # response = nil
343
+ # end
344
+ break if response.blank? ||
345
+ (response[resource_name].blank?) ||
346
+ (block && !block.call(response)) ||
347
+ (response[resource_name].size < opts[:vars]['limit'])
348
+ opts[:vars]['offset'] += opts[:vars]['limit']
349
+ end
350
+ result
351
+ end
352
+
353
+ # Call Rackspace. Caching is not used.
354
+ def api(verb, path='', options={}) # :nodoc:
355
+ login unless @logged_in
356
+ options[:headers] ||= {}
357
+ options[:headers]['x-auth-token'] = @auth_token
358
+ request_info(generate_request(verb, path, options))
359
+ end
360
+
361
+ # Call Rackspace. Use cache if possible
362
+ # opts:
363
+ # :incrementally - use incrementally list to get the whole list of items
364
+ # otherwise it will get max DEFAULT_LIMIT items (PS API call must support pagination)
365
+ #
366
+ def api_or_cache(verb, path, options={}) # :nodoc:
367
+ use_caching = merged_params[:caching] && options[:vars].blank?
368
+ cache_record = use_caching && @cache[path]
369
+ # Create a proc object to avoid a code duplication
370
+ proc = Proc.new do
371
+ if options[:incrementally]
372
+ incrementally_list_resources(verb, path, nil, nil, options)
373
+ else api(verb, path, options)
374
+ end
375
+ end
376
+ # The cache is not used or record is not found
377
+ unless cache_record
378
+ response = proc.call
379
+ if use_caching
380
+ last_modified_at = @last_response.to_hash['last-modified'].first
381
+ update_cache(path, last_modified_at, response)
382
+ end
383
+ response
384
+ else
385
+ # Record found - ask Rackspace whether it changed or not since last update
386
+ options = options.dup
387
+ options[:headers] ||= {}
388
+ options[:headers]['if-modified-since'] = cache_record[:last_modified_at]
389
+ proc.call
390
+ end
391
+ end
392
+
393
+ def update_cache(path, last_modified_at, data) #:nodoc:
394
+ @cache[path] ||= {}
395
+ @cache[path][:last_modified_at] = last_modified_at
396
+ @cache[path][:data] = data
397
+ end
398
+
399
+ # Events (callbacks) for logging and debugging features.
400
+ # These callbacks do not catch low level connection errors that are handled by RightHttpConnection but
401
+ # only HTTP errors.
402
+ def on_event(event, *params) #:nodoc:
403
+ self.merged_params[event].call(self, *params) if self.merged_params[event].kind_of?(Proc)
404
+ end
405
+
406
+ end
407
+
408
+ #------------------------------------------------------------
409
+ # Error handling
410
+ #------------------------------------------------------------
411
+
412
+ class NoChange < RuntimeError
413
+ end
414
+
415
+ class Error < RuntimeError
416
+ end
417
+
418
+ class HttpErrorHandler # :nodoc:
419
+
420
+ # Receiving these codes we have to reauthenticate at Rackspace
421
+ REAUTHENTICATE_ON = ['401']
422
+ # Some error are too ennoing to be logged: '404' comes very often when one calls
423
+ # incrementally_list_something
424
+ # SKIP_LOGGING_ON = ['404']
425
+ SKIP_LOGGING_ON = []
426
+
427
+ @@reiteration_start_delay = 0.2
428
+ def self.reiteration_start_delay
429
+ @@reiteration_start_delay
430
+ end
431
+ def self.reiteration_start_delay=(reiteration_start_delay)
432
+ @@reiteration_start_delay = reiteration_start_delay
433
+ end
434
+ @@reiteration_time = 5
435
+ def self.reiteration_time
436
+ @@reiteration_time
437
+ end
438
+ def self.reiteration_time=(reiteration_time)
439
+ @@reiteration_time = reiteration_time
440
+ end
441
+
442
+ # Format a response error message.
443
+ def self.extract_error_description(response, verbose=false) #:nodoc:
444
+ message = nil
445
+ Interface::bench.parser.add! do
446
+ message = begin
447
+ if response.body[/^<!DOCTYPE HTML PUBLIC/] then response.message
448
+ else
449
+ JSON::parse(response.body).to_a.map do |k,v|
450
+ "#{k}: #{v['message']}" + (verbose ? "\n#{v['details']}" : "")
451
+ end.join("\n")
452
+ end
453
+ rescue
454
+ response.message
455
+ end
456
+ end
457
+ "#{response.code}: #{message}"
458
+ end
459
+
460
+ # params:
461
+ # :reiteration_time
462
+ # :errors_list
463
+ def initialize(handle, params={}) #:nodoc:
464
+ @handle = handle # Link to RightEc2 | RightSqs | RightS3 instance
465
+ @started_at = Time.now
466
+ @stop_at = @started_at + (params[:reiteration_time] || @@reiteration_time)
467
+ @errors_list = params[:errors_list] || []
468
+ @reiteration_delay = @@reiteration_start_delay
469
+ @retries = 0
470
+ end
471
+
472
+ # Process errored response
473
+ def check(request_hash) #:nodoc:
474
+ result = nil
475
+ error_found = false
476
+ response = @handle.last_response
477
+ error_message = @handle.last_error
478
+ # Log the error
479
+ logger = @handle.logger
480
+ unless SKIP_LOGGING_ON.include?(response.code)
481
+ logger.warn("##### #{@handle.class.name} returned an error: #{error_message} #####")
482
+ logger.warn("##### #{@handle.class.name} request: #{request_hash[:server]}:#{request_hash[:port]}#{request_hash[:request].path} ####")
483
+ end
484
+ # now - check the error
485
+ @errors_list.each do |error_to_find|
486
+ if error_message[/#{error_to_find}/i]
487
+ error_found = error_to_find
488
+ logger.warn("##### Retry is needed, error pattern match: #{error_to_find} #####")
489
+ break
490
+ end
491
+ end
492
+ # yep, we know this error and have to do a retry when it comes
493
+ if error_found || REAUTHENTICATE_ON.include?(@handle.last_response.code)
494
+ # check the time has gone from the first error come
495
+ # Close the connection to the server and recreate a new one.
496
+ # It may have a chance that one server is a semi-down and reconnection
497
+ # will help us to connect to the other server
498
+ if (Time.now < @stop_at)
499
+ @retries += 1
500
+ @handle.logger.warn("##### Retry ##{@retries} is being performed. Sleeping for #{@reiteration_delay} sec. Whole time: #{Time.now-@started_at} sec ####")
501
+ sleep @reiteration_delay
502
+ @reiteration_delay *= 2
503
+ # Always make sure that the fp is set to point to the beginning(?)
504
+ # of the File/IO. TODO: it assumes that offset is 0, which is bad.
505
+ if request_hash[:request].body_stream && request_hash[:request].body_stream.respond_to?(:pos)
506
+ begin
507
+ request_hash[:request].body_stream.pos = 0
508
+ rescue Exception => e
509
+ logger.warn("Retry may fail due to unable to reset the file pointer -- #{self.class.name} : #{e.inspect}")
510
+ end
511
+ end
512
+ # Oops it seems we have been asked about reauthentication..
513
+ if REAUTHENTICATE_ON.include?(@handle.last_response.code)
514
+ @handle.authenticate
515
+ @handle.request_info(request_hash)
516
+ end
517
+ # Make another try
518
+ result = @handle.request_info(request_hash)
519
+ else
520
+ logger.warn("##### Ooops, time is over... ####")
521
+ end
522
+ end
523
+ result
524
+ end
525
+
526
+ end
527
+ end
528
+ end
@@ -0,0 +1,46 @@
1
+ #
2
+ # Copyright (c) 2009 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+
24
+ require 'benchmark'
25
+ require 'net/https'
26
+ require 'uri'
27
+ require "base64"
28
+ require 'rubygems'
29
+ require 'json'
30
+ require 'right_http_connection'
31
+
32
+ $:.unshift(File.dirname(__FILE__))
33
+ require 'benchmark_fix'
34
+ require 'support'
35
+ require 'rackspace_base'
36
+ require 'rackspace'
37
+
38
+ module RightRackspace #:nodoc:
39
+ module VERSION #:nodoc:
40
+ MAJOR = 0
41
+ MINOR = 0
42
+ TINY = 0
43
+
44
+ STRING = [MAJOR, MINOR, TINY].join('.')
45
+ end
46
+ end
data/lib/support.rb ADDED
@@ -0,0 +1,124 @@
1
+ # If ActiveSupport is loaded, then great - use it. But we don't
2
+ # want a dependency on it, so if it's not present, define the few
3
+ # extensions that we want to use...
4
+ unless defined? ActiveSupport::CoreExtensions
5
+ # These are ActiveSupport-;like extensions to do a few handy things in the gems
6
+ # Derived from ActiveSupport, so the AS copyright notice applies:
7
+ #
8
+ #
9
+ #
10
+ # Copyright (c) 2005 David Heinemeier Hansson
11
+ #
12
+ # Permission is hereby granted, free of charge, to any person obtaining
13
+ # a copy of this software and associated documentation files (the
14
+ # "Software"), to deal in the Software without restriction, including
15
+ # without limitation the rights to use, copy, modify, merge, publish,
16
+ # distribute, sublicense, and/or sell copies of the Software, and to
17
+ # permit persons to whom the Software is furnished to do so, subject to
18
+ # the following conditions:
19
+ #
20
+ # The above copyright notice and this permission notice shall be
21
+ # included in all copies or substantial portions of the Software.
22
+ #
23
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30
+ #++
31
+ #
32
+ #
33
+ class String #:nodoc:
34
+
35
+ # Constantize tries to find a declared constant with the name specified
36
+ # in the string. It raises a NameError when the name is not in CamelCase
37
+ # or is not initialized.
38
+ #
39
+ # Examples
40
+ # "Module".constantize #=> Module
41
+ # "Class".constantize #=> Class
42
+ def constantize()
43
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self
44
+ raise NameError, "#{self.inspect} is not a valid constant name!"
45
+ end
46
+
47
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
48
+ end
49
+
50
+ def camelize()
51
+ self.dup.split(/_/).map{ |word| word.capitalize }.join('')
52
+ end
53
+
54
+ end
55
+
56
+
57
+ class Object #:nodoc:
58
+ # "", " ", nil, [], and {} are blank
59
+ def blank?
60
+ if respond_to?(:empty?) && respond_to?(:strip)
61
+ empty? or strip.empty?
62
+ elsif respond_to?(:empty?)
63
+ empty?
64
+ else
65
+ !self
66
+ end
67
+ end
68
+ end
69
+
70
+ class NilClass #:nodoc:
71
+ def blank?
72
+ true
73
+ end
74
+ end
75
+
76
+ class FalseClass #:nodoc:
77
+ def blank?
78
+ true
79
+ end
80
+ end
81
+
82
+ class TrueClass #:nodoc:
83
+ def blank?
84
+ false
85
+ end
86
+ end
87
+
88
+ class Array #:nodoc:
89
+ alias_method :blank?, :empty?
90
+ end
91
+
92
+ class Hash #:nodoc:
93
+ alias_method :blank?, :empty?
94
+
95
+ # Return a new hash with all keys converted to symbols.
96
+ def symbolize_keys
97
+ inject({}) do |options, (key, value)|
98
+ options[key.to_sym] = value
99
+ options
100
+ end
101
+ end
102
+ end
103
+
104
+ class String #:nodoc:
105
+ def blank?
106
+ empty? || strip.empty?
107
+ end
108
+ end
109
+
110
+ class Numeric #:nodoc:
111
+ def blank?
112
+ false
113
+ end
114
+ end
115
+ end
116
+
117
+ # We also use String#first from 1.8.7 that doesn't exist in 1.8.6
118
+ unless String.instance_methods.include? "first"
119
+ class String
120
+ def first
121
+ self
122
+ end
123
+ end
124
+ end