right_aws 1.8.0 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -174,7 +174,7 @@ Initial release.
174
174
 
175
175
  - Removed the 1.7.2 monkey-patch of the Ruby File class on Windows. This patch broke Rails 2.0.
176
176
  The patch is now included in the README for anyone to use at their own risk.
177
-
177
+
178
178
  == 1.8.0
179
179
 
180
180
  Release Notes:
@@ -184,4 +184,23 @@ Initial release.
184
184
  and delete EBS volumes, attach and detach them from instances, snapshot
185
185
  volumes, and list the available volumes and snapshots.
186
186
 
187
- Bug fixes include correction of RightAws::S3 copy's failure to url-encode the source key.
187
+ Bug fixes include correction of RightAws::S3 copy's failure to url-encode
188
+ the source key.
189
+
190
+ == 1.8.1
191
+
192
+ Release Notes:
193
+
194
+ RightScale::SdbInterface & ::ActiveSdb have several enhancements, including:
195
+ - RightAws::SdbInterface#last_query_expression added for debug puposes
196
+ - RightAws::ActiveSdb::Base#query :order and :auto_load options added to support query
197
+ result sorting and attributes auto loading
198
+ - RightAws::ActiveSdb::Base#find_all_by_ and find_by_ helpers improved to support
199
+ :order, :auto_load, :limit and :next_token options
200
+ - RightAws::SdbInterface#delete_attributes bug fixed
201
+ - SdbInterface allows specification of a string value to use for
202
+ representing Ruby nil in SDB.
203
+ - Sdb tests fixed and improved
204
+
205
+ The ::S3 interface now has support for S3's server access logging.
206
+ Amazon considers server access logging to be a beta or provisional feature.
@@ -24,6 +24,7 @@
24
24
  # Test
25
25
  module RightAws
26
26
  require 'md5'
27
+ require 'pp'
27
28
 
28
29
  class AwsUtils #:nodoc:
29
30
  @@digest = OpenSSL::Digest::Digest.new("sha1")
@@ -44,6 +45,23 @@ module RightAws
44
45
  e = URI.escape(raw)
45
46
  e.gsub(/\+/, "%2b")
46
47
  end
48
+
49
+ def self.allow_only(allowed_keys, params)
50
+ bogus_args = []
51
+ params.keys.each {|p| bogus_args.push(p) unless allowed_keys.include?(p) }
52
+ raise AwsError.new("The following arguments were given but are not legal for the function call #{caller_method}: #{bogus_args.inspect}") if bogus_args.length > 0
53
+ end
54
+
55
+ def self.mandatory_arguments(required_args, params)
56
+ rargs = required_args.dup
57
+ params.keys.each {|p| rargs.delete(p)}
58
+ raise AwsError.new("The following mandatory arguments were not provided to #{caller_method}: #{rargs.inspect}") if rargs.length > 0
59
+ end
60
+
61
+ def self.caller_method
62
+ caller[1]=~/`(.*?)'/
63
+ $1
64
+ end
47
65
 
48
66
  end
49
67
 
@@ -863,7 +863,7 @@ module RightAws
863
863
  #-----------------------------------------------------------------
864
864
  # EBS: Volumes
865
865
  #-----------------------------------------------------------------
866
-
866
+
867
867
  # Describe all EBS volumes.
868
868
  #
869
869
  # ec2.describe_volumes #=>
@@ -891,7 +891,7 @@ module RightAws
891
891
  rescue Exception
892
892
  on_exception
893
893
  end
894
-
894
+
895
895
  # Create new EBS volume based on previously created snapshot.
896
896
  # +Size+ in Gigabytes.
897
897
  #
@@ -1380,7 +1380,7 @@ module RightAws
1380
1380
  #-----------------------------------------------------------------
1381
1381
  # PARSERS: EBS - Volumes
1382
1382
  #-----------------------------------------------------------------
1383
-
1383
+
1384
1384
  class QEc2CreateVolumeParser < RightAWSParser #:nodoc:
1385
1385
  def tagend(name)
1386
1386
  case name
@@ -1396,7 +1396,7 @@ module RightAws
1396
1396
  @result = {}
1397
1397
  end
1398
1398
  end
1399
-
1399
+
1400
1400
  class QEc2AttachAndDetachVolumeParser < RightAWSParser #:nodoc:
1401
1401
  def tagend(name)
1402
1402
  case name
@@ -1411,13 +1411,13 @@ module RightAws
1411
1411
  @result = {}
1412
1412
  end
1413
1413
  end
1414
-
1414
+
1415
1415
  class QEc2DescribeVolumesParser < RightAWSParser #:nodoc:
1416
1416
  def tagstart(name, attributes)
1417
1417
  case name
1418
1418
  when 'item'
1419
1419
  case @xmlpath
1420
- when 'DescribeVolumesResponse/volumeSet' then @volume = {}
1420
+ when 'DescribeVolumesResponse/volumeSet' then @volume = {}
1421
1421
  end
1422
1422
  end
1423
1423
  end
@@ -52,7 +52,7 @@ module RightAws #:nodoc:
52
52
  module VERSION #:nodoc:
53
53
  MAJOR = 1
54
54
  MINOR = 8
55
- TINY = 0
55
+ TINY = 1
56
56
 
57
57
  STRING = [MAJOR, MINOR, TINY].join('.')
58
58
  end
@@ -53,6 +53,14 @@ module RightAws
53
53
  # Create a new handle to an S3 account. All handles share the same per process or per thread
54
54
  # HTTP connection to Amazon S3. Each handle is for a specific account.
55
55
  # The +params+ are passed through as-is to RightAws::S3Interface.new
56
+ #
57
+ # Params is a hash:
58
+ #
59
+ # {:server => 's3.amazonaws.com' # Amazon service host: 's3.amazonaws.com'(default)
60
+ # :port => 443 # Amazon service port: 80 or 443(default)
61
+ # :protocol => 'https' # Amazon service protocol: 'http' or 'https'(default)
62
+ # :multi_thread => true|false # Multi-threaded (connection per each thread): true or false(default)
63
+ # :logger => Logger Object} # Logger instance: logs to STDOUT if omitted }
56
64
  def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
57
65
  @interface = S3Interface.new(aws_access_key_id, aws_secret_access_key, params)
58
66
  end
@@ -161,6 +169,36 @@ module RightAws
161
169
  def location
162
170
  @location ||= @s3.interface.bucket_location(@name)
163
171
  end
172
+
173
+ # Retrieves the logging configuration for a bucket.
174
+ # Returns a hash of {:enabled, :targetbucket, :targetprefix}
175
+ #
176
+ # bucket.logging_info()
177
+ # => {:enabled=>true, :targetbucket=>"mylogbucket", :targetprefix=>"loggylogs/"}
178
+ def logging_info
179
+ @s3.interface.get_logging_parse(:bucket => @name)
180
+ end
181
+
182
+ # Enables S3 server access logging on a bucket. The target bucket must have been properly configured to receive server
183
+ # access logs.
184
+ # Params:
185
+ # :targetbucket - either the target bucket object or the name of the target bucket
186
+ # :targetprefix - the prefix under which all logs should be stored
187
+ #
188
+ # bucket.enable_logging(:targetbucket=>"mylogbucket", :targetprefix=>"loggylogs/")
189
+ # => true
190
+ def enable_logging(params)
191
+ AwsUtils.mandatory_arguments([:targetbucket, :targetprefix], params)
192
+ AwsUtils.allow_only([:targetbucket, :targetprefix], params)
193
+ xmldoc = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><BucketLoggingStatus xmlns=\"http://doc.s3.amazonaws.com/2006-03-01\"><LoggingEnabled><TargetBucket>#{params[:targetbucket]}</TargetBucket><TargetPrefix>#{params[:targetprefix]}</TargetPrefix></LoggingEnabled></BucketLoggingStatus>"
194
+ @s3.interface.put_logging(:bucket => @name, :xmldoc => xmldoc)
195
+ end
196
+
197
+ # Disables S3 server access logging on a bucket. Takes no arguments.
198
+ def disable_logging
199
+ xmldoc = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><BucketLoggingStatus xmlns=\"http://doc.s3.amazonaws.com/2006-03-01\"></BucketLoggingStatus>"
200
+ @s3.interface.put_logging(:bucket => @name, :xmldoc => xmldoc)
201
+ end
164
202
 
165
203
  # Retrieve a group of keys from Amazon.
166
204
  # +options+ is a hash: { 'prefix'=>'', 'marker'=>'', 'max-keys'=>5, 'delimiter'=>'' }).
@@ -95,7 +95,7 @@ module RightAws
95
95
  out_string << '?acl' if path[/[&?]acl($|&|=)/]
96
96
  out_string << '?torrent' if path[/[&?]torrent($|&|=)/]
97
97
  out_string << '?location' if path[/[&?]location($|&|=)/]
98
- # out_string << '?logging' if path[/[&?]logging($|&|=)/] # this one is beta, no support for now
98
+ out_string << '?logging' if path[/[&?]logging($|&|=)/] # this one is beta, no support for now
99
99
  out_string
100
100
  end
101
101
 
@@ -211,6 +211,37 @@ module RightAws
211
211
  on_exception
212
212
  end
213
213
 
214
+ # Retrieves the logging configuration for a bucket.
215
+ # Returns a hash of {:enabled, :targetbucket, :targetprefix}
216
+ #
217
+ # s3.interface.get_logging_parse(:bucket => "asset_bucket")
218
+ # => {:enabled=>true, :targetbucket=>"mylogbucket", :targetprefix=>"loggylogs/"}
219
+ #
220
+ #
221
+ def get_logging_parse(params)
222
+ AwsUtils.mandatory_arguments([:bucket], params)
223
+ AwsUtils.allow_only([:bucket, :headers], params)
224
+ params[:headers] = {} unless params[:headers]
225
+ req_hash = generate_rest_request('GET', params[:headers].merge(:url=>"#{params[:bucket]}?logging"))
226
+ request_info(req_hash, S3LoggingParser.new)
227
+ rescue
228
+ on_exception
229
+ end
230
+
231
+ # Sets logging configuration for a bucket from the XML configuration document.
232
+ # params:
233
+ # :bucket
234
+ # :xmldoc
235
+ def put_logging(params)
236
+ AwsUtils.mandatory_arguments([:bucket,:xmldoc], params)
237
+ AwsUtils.allow_only([:bucket,:xmldoc, :headers], params)
238
+ params[:headers] = {} unless params[:headers]
239
+ req_hash = generate_rest_request('PUT', params[:headers].merge(:url=>"#{params[:bucket]}?logging", :data => params[:xmldoc]))
240
+ request_info(req_hash, S3TrueParser.new)
241
+ rescue
242
+ on_exception
243
+ end
244
+
214
245
  # Deletes new bucket. Bucket must be empty! Returns +true+ or an exception.
215
246
  #
216
247
  # s3.delete_bucket('my_awesome_bucket') #=> true
@@ -349,6 +380,7 @@ module RightAws
349
380
  # a text mode IO object is passed to PUT, it will be converted to binary
350
381
  # mode.
351
382
  #
383
+
352
384
  def put(bucket, key, data=nil, headers={})
353
385
  # On Windows, if someone opens a file in text mode, we must reset it so
354
386
  # to binary mode for streaming to work properly
@@ -364,6 +396,86 @@ module RightAws
364
396
  rescue
365
397
  on_exception
366
398
  end
399
+
400
+
401
+
402
+ # New experimental API for uploading objects, introduced in RightAws 1.8.1.
403
+ # store_object is similar in function to the older function put, but returns the full response metadata. It also allows for optional verification
404
+ # of object md5 checksums on upload. Parameters are passed as hash entries and are checked for completeness as well as for spurious arguments.
405
+ # The hash of the response headers contains useful information like the Amazon request ID and the object ETag (MD5 checksum).
406
+ #
407
+ # If the optional :md5 argument is provided, store_object verifies that the given md5 matches the md5 returned by S3. The :verified_md5 field in the response hash is
408
+ # set true or false depending on the outcome of this check. If no :md5 argument is given, :verified_md5 will be false in the response.
409
+ #
410
+ # The optional argument of :headers allows the caller to specify arbitrary request header values.
411
+ #
412
+ # s3.store_object(:bucket => "foobucket", :key => "foo", :md5 => "a507841b1bc8115094b00bbe8c1b2954", :data => "polemonium" )
413
+ # => {"x-amz-id-2"=>"SVsnS2nfDaR+ixyJUlRKM8GndRyEMS16+oZRieamuL61pPxPaTuWrWtlYaEhYrI/",
414
+ # "etag"=>"\"a507841b1bc8115094b00bbe8c1b2954\"",
415
+ # "date"=>"Mon, 29 Sep 2008 18:57:46 GMT",
416
+ # :verified_md5=>true,
417
+ # "x-amz-request-id"=>"63916465939995BA",
418
+ # "server"=>"AmazonS3",
419
+ # "content-length"=>"0"}
420
+ #
421
+ # s3.store_object(:bucket => "foobucket", :key => "foo", :data => "polemonium" )
422
+ # => {"x-amz-id-2"=>"MAt9PLjgLX9UYJ5tV2fI/5dBZdpFjlzRVpWgBDpvZpl+V+gJFcBMW2L+LBstYpbR",
423
+ # "etag"=>"\"a507841b1bc8115094b00bbe8c1b2954\"",
424
+ # "date"=>"Mon, 29 Sep 2008 18:58:56 GMT",
425
+ # :verified_md5=>false,
426
+ # "x-amz-request-id"=>"3B25A996BC2CDD3B",
427
+ # "server"=>"AmazonS3",
428
+ # "content-length"=>"0"}
429
+
430
+ def store_object(params)
431
+ AwsUtils.allow_only([:bucket, :key, :data, :headers, :md5], params)
432
+ AwsUtils.mandatory_arguments([:bucket, :key, :data], params)
433
+ params[:headers] = {} unless params[:headers]
434
+
435
+ params[:data].binmode if(params[:data].respond_to?(:binmode)) # On Windows, if someone opens a file in text mode, we must reset it to binary mode for streaming to work properly
436
+ if (params[:data].respond_to?(:lstat) && params[:data].lstat.size >= USE_100_CONTINUE_PUT_SIZE) ||
437
+ (params[:data].respond_to?(:size) && params[:data].size >= USE_100_CONTINUE_PUT_SIZE)
438
+ params[:headers]['expect'] = '100-continue'
439
+ end
440
+
441
+ req_hash = generate_rest_request('PUT', params[:headers].merge(:url=>"#{params[:bucket]}/#{CGI::escape params[:key]}", :data=>params[:data]))
442
+ resp = request_info(req_hash, S3HttpResponseHeadParser.new)
443
+ if(params[:md5])
444
+ resp[:verified_md5] = (resp['etag'].gsub(/\"/, '') == params[:md5]) ? true : false
445
+ else
446
+ resp[:verified_md5] = false
447
+ end
448
+ resp
449
+ rescue
450
+ on_exception
451
+ end
452
+
453
+ # Identical in function to store_object, but requires verification that the returned ETag is identical to the checksum passed in by the user as the 'md5' argument.
454
+ # If the check passes, returns the response metadata with the "verified_md5" field set true. Raises an exception if the checksums conflict.
455
+ # This call is implemented as a wrapper around put_object and the user may gain different semantics by creating a custom wrapper.
456
+ #
457
+ # s3.store_object_and_verify(:bucket => "foobucket", :key => "foo", :md5 => "a507841b1bc8115094b00bbe8c1b2954", :data => "polemonium" )
458
+ # => {"x-amz-id-2"=>"IZN3XsH4FlBU0+XYkFTfHwaiF1tNzrm6dIW2EM/cthKvl71nldfVC0oVQyydzWpb",
459
+ # "etag"=>"\"a507841b1bc8115094b00bbe8c1b2954\"",
460
+ # "date"=>"Mon, 29 Sep 2008 18:38:32 GMT",
461
+ # :verified_md5=>true,
462
+ # "x-amz-request-id"=>"E8D7EA4FE00F5DF7",
463
+ # "server"=>"AmazonS3",
464
+ # "content-length"=>"0"}
465
+ #
466
+ # s3.store_object_and_verify(:bucket => "foobucket", :key => "foo", :md5 => "a507841b1bc8115094b00bbe8c1b2953", :data => "polemonium" )
467
+ # RightAws::AwsError: Uploaded object failed MD5 checksum verification: {"x-amz-id-2"=>"HTxVtd2bf7UHHDn+WzEH43MkEjFZ26xuYvUzbstkV6nrWvECRWQWFSx91z/bl03n",
468
+ # "etag"=>"\"a507841b1bc8115094b00bbe8c1b2954\"",
469
+ # "date"=>"Mon, 29 Sep 2008 18:38:41 GMT",
470
+ # :verified_md5=>false,
471
+ # "x-amz-request-id"=>"0D7ADE09F42606F2",
472
+ # "server"=>"AmazonS3",
473
+ # "content-length"=>"0"}
474
+ def store_object_and_verify(params)
475
+ AwsUtils.mandatory_arguments([:md5], params)
476
+ r = put_object(params)
477
+ r[:verified_md5] ? (return r) : (raise AwsError.new("Uploaded object failed MD5 checksum verification: #{r.inspect}"))
478
+ end
367
479
 
368
480
  # Retrieves object data from Amazon. Returns a +hash+ or an exception.
369
481
  #
@@ -398,6 +510,68 @@ module RightAws
398
510
  rescue
399
511
  on_exception
400
512
  end
513
+
514
+ # New experimental API for retrieving objects, introduced in RightAws 1.8.1.
515
+ # retrieve_object is similar in function to the older function get. It allows for optional verification
516
+ # of object md5 checksums on retrieval. Parameters are passed as hash entries and are checked for completeness as well as for spurious arguments.
517
+ #
518
+ # If the optional :md5 argument is provided, retrieve_object verifies that the given md5 matches the md5 returned by S3. The :verified_md5 field in the response hash is
519
+ # set true or false depending on the outcome of this check. If no :md5 argument is given, :verified_md5 will be false in the response.
520
+ #
521
+ # The optional argument of :headers allows the caller to specify arbitrary request header values.
522
+ # Mandatory arguments:
523
+ # :bucket - the bucket in which the object is stored
524
+ # :key - the object address (or path) within the bucket
525
+ # Optional arguments:
526
+ # :headers - hash of additional HTTP headers to include with the request
527
+ # :md5 - MD5 checksum against which to verify the retrieved object
528
+ #
529
+ # s3.retrieve_object(:bucket => "foobucket", :key => "foo")
530
+ # => {:verified_md5=>false,
531
+ # :headers=>{"last-modified"=>"Mon, 29 Sep 2008 18:58:56 GMT",
532
+ # "x-amz-id-2"=>"2Aj3TDz6HP5109qly//18uHZ2a1TNHGLns9hyAtq2ved7wmzEXDOPGRHOYEa3Qnp",
533
+ # "content-type"=>"",
534
+ # "etag"=>"\"a507841b1bc8115094b00bbe8c1b2954\"",
535
+ # "date"=>"Tue, 30 Sep 2008 00:52:44 GMT",
536
+ # "x-amz-request-id"=>"EE4855DE27A2688C",
537
+ # "server"=>"AmazonS3",
538
+ # "content-length"=>"10"},
539
+ # :object=>"polemonium"}
540
+ #
541
+ # s3.retrieve_object(:bucket => "foobucket", :key => "foo", :md5=>'a507841b1bc8115094b00bbe8c1b2954')
542
+ # => {:verified_md5=>true,
543
+ # :headers=>{"last-modified"=>"Mon, 29 Sep 2008 18:58:56 GMT",
544
+ # "x-amz-id-2"=>"mLWQcI+VuKVIdpTaPXEo84g0cz+vzmRLbj79TS8eFPfw19cGFOPxuLy4uGYVCvdH",
545
+ # "content-type"=>"", "etag"=>"\"a507841b1bc8115094b00bbe8c1b2954\"",
546
+ # "date"=>"Tue, 30 Sep 2008 00:53:08 GMT",
547
+ # "x-amz-request-id"=>"6E7F317356580599",
548
+ # "server"=>"AmazonS3",
549
+ # "content-length"=>"10"},
550
+ # :object=>"polemonium"}
551
+ def retrieve_object(params, &block)
552
+ AwsUtils.mandatory_arguments([:bucket, :key], params)
553
+ AwsUtils.allow_only([:bucket, :key, :headers, :md5], params)
554
+ params[:headers] = {} unless params[:headers]
555
+ req_hash = generate_rest_request('GET', params[:headers].merge(:url=>"#{params[:bucket]}/#{CGI::escape params[:key]}"))
556
+ resp = request_info(req_hash, S3HttpResponseBodyParser.new, &block)
557
+ resp[:verified_md5] = false
558
+ if(params[:md5] && (resp[:headers]['etag'].gsub(/\"/,'') == params[:md5]))
559
+ resp[:verified_md5] = true
560
+ end
561
+ resp
562
+ rescue
563
+ on_exception
564
+ end
565
+
566
+ # Identical in function to retrieve_object, but requires verification that the returned ETag is identical to the checksum passed in by the user as the 'md5' argument.
567
+ # If the check passes, returns the response metadata with the "verified_md5" field set true. Raises an exception if the checksums conflict.
568
+ # This call is implemented as a wrapper around retrieve_object and the user may gain different semantics by creating a custom wrapper.
569
+ def retrieve_object_and_verify(params, &block)
570
+ AwsUtils.mandatory_arguments([:md5], params)
571
+ resp = get_object(params, block)
572
+ return resp if resp[:verified_md5]
573
+ raise AwsError.new("Retrieved object failed MD5 checksum verification: #{resp.inspect}")
574
+ end
401
575
 
402
576
  # Retrieves object metadata. Returns a +hash+ of http_response_headers.
403
577
  #
@@ -932,6 +1106,28 @@ module RightAws
932
1106
  end
933
1107
  end
934
1108
  end
1109
+
1110
+ class S3LoggingParser < RightAWSParser # :nodoc:
1111
+ def reset
1112
+ @result = {:enabled => false, :targetbucket => '', :targetprefix => ''}
1113
+ @current_grantee = {}
1114
+ end
1115
+ def tagend(name)
1116
+ case name
1117
+ # service info
1118
+ when 'TargetBucket'
1119
+ if @xmlpath == 'BucketLoggingStatus/LoggingEnabled'
1120
+ @result[:targetbucket] = @text
1121
+ @result[:enabled] = true
1122
+ end
1123
+ when 'TargetPrefix'
1124
+ if @xmlpath == 'BucketLoggingStatus/LoggingEnabled'
1125
+ @result[:targetprefix] = @text
1126
+ @result[:enabled] = true
1127
+ end
1128
+ end
1129
+ end
1130
+ end
935
1131
 
936
1132
  class S3CopyParser < RightAWSParser # :nodoc:
937
1133
  def reset
@@ -101,6 +101,15 @@ module RightAws
101
101
  # Create a new handle to an Sdb account. All handles share the same per process or per thread
102
102
  # HTTP connection to Amazon Sdb. Each handle is for a specific account.
103
103
  # The +params+ are passed through as-is to RightAws::SdbInterface.new
104
+ # Params:
105
+ # { :server => 'sdb.amazonaws.com' # Amazon service host: 'sdb.amazonaws.com'(default)
106
+ # :port => 443 # Amazon service port: 80 or 443(default)
107
+ # :protocol => 'https' # Amazon service protocol: 'http' or 'https'(default)
108
+ # :signature_version => '0' # The signature version : '0' or '1'(default)
109
+ # :multi_thread => true|false # Multi-threaded (connection per each thread): true or false(default)
110
+ # :logger => Logger Object # Logger instance: logs to STDOUT if omitted
111
+ # :nil_representation => 'mynil'} # interpret Ruby nil as this string value; i.e. use this string in SDB to represent Ruby nils (default is the string 'nil')
112
+
104
113
  def establish_connection(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
105
114
  @connection = RightAws::SdbInterface.new(aws_access_key_id, aws_secret_access_key, params)
106
115
  end
@@ -259,9 +268,10 @@ module RightAws
259
268
  # Client.find_by_name('Matias Rust')
260
269
  # Client.find_by_name_and_city('Putin','Moscow')
261
270
  # Client.find_by_name_and_city_and_post('Medvedev','Moscow','president')
262
- #
271
+ #
263
272
  # Client.find_all_by_author('G.Bush jr')
264
273
  # Client.find_all_by_age_and_gender_and_ethnicity('34','male','russian')
274
+ # Client.find_all_by_gender_and_country('male', 'Russia', :auto_load => true, :order => 'name desc')
265
275
  #
266
276
  # Returned records have to be +reloaded+ to access their attributes.
267
277
  #
@@ -276,37 +286,100 @@ module RightAws
276
286
  # Client.find(:all, :limit => 10, :next_token => Client.next_token)
277
287
  # end while Client.next_token
278
288
  #
289
+ # Sort oder:
290
+ # Client.find(:all, :order => 'gender')
291
+ # Client.find(:all, :order => 'name desc')
292
+ #
293
+ # Attributes auto load (be carefull - this may take lot of time for a huge bunch of records):
294
+ # Client.find(:first) #=> #<Client:0xb77d0d40 @attributes={"id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7"}, @new_record=false>
295
+ # Client.find(:first, :auto_load => true) #=> #<Client:0xb77d0d40 @attributes={"id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "name"=>["Cat"], "toys"=>["Jons socks", "clew", "mice"]}, @new_record=false>
296
+ #
279
297
  def find(*args)
280
298
  options = args.last.is_a?(Hash) ? args.pop : {}
281
299
  case args.first
282
- when :all : find_every options
283
- when :first : find_initial options
284
- else find_from_ids args, options
300
+ when :all then find_every options
301
+ when :first then find_initial options
302
+ else find_from_ids args, options
285
303
  end
286
304
  end
287
305
 
288
- protected
289
-
290
- def query(query_expression=nil, max_number_of_items = nil, next_token = nil) # :nodoc:
291
- @next_token = next_token
306
+ protected
307
+
308
+ # Returns an array of query attributes.
309
+ # Query_expression must be a well formated SDB query string:
310
+ # query_attributes("['title' starts-with 'O\\'Reily'] intersection ['year' = '2007']") #=> ["title", "year"]
311
+ def query_attributes(query_expression)
312
+ attrs = []
313
+ array = query_expression.scan(/['"](.*?[^\\])['"]/).flatten
314
+ until array.empty? do
315
+ attrs << array.shift # skip it's value
316
+ array.shift #
317
+ end
318
+ attrs
319
+ end
320
+
321
+ # Returns an array of [attribute_name, 'asc'|'desc']
322
+ def sort_options(sort_string)
323
+ sort_string[/['"]?(\w+)['"]? *(asc|desc)?/i]
324
+ [$1, ($2 || 'asc')]
325
+ end
326
+
327
+ # Perform a query request.
328
+ #
329
+ # Options
330
+ # :query_expression nil | string | array
331
+ # :max_number_of_items nil | integer
332
+ # :next_token nil | string
333
+ # :sort_option nil | string "name desc|asc"
334
+ #
335
+ def query(options) # :nodoc:
336
+ @next_token = options[:next_token]
337
+ query_expression = options[:query_expression]
338
+ query_expression = connection.query_expression_from_array(query_expression) if query_expression.is_a?(Array)
339
+ # add sort_options to the query_expression
340
+ if options[:sort_option]
341
+ sort_by, sort_order = sort_options(options[:sort_option])
342
+ sort_query_expression = "['#{sort_by}' starts-with '']"
343
+ sort_by_expression = " sort '#{sort_by}' #{sort_order}"
344
+ # make query_expression to be a string (it may be null)
345
+ query_expression = query_expression.to_s
346
+ # quote from Amazon:
347
+ # The sort attribute must be present in at least one of the predicates of the query expression.
348
+ if query_expression.blank?
349
+ query_expression = sort_query_expression
350
+ elsif !query_attributes(query_expression).include?(sort_by)
351
+ query_expression += " intersection #{sort_query_expression}"
352
+ end
353
+ query_expression += sort_by_expression
354
+ end
292
355
  # request items
293
- query_result = self.connection.query(domain, query_expression, max_number_of_items, @next_token)
356
+ query_result = self.connection.query(domain, query_expression, options[:max_number_of_items], @next_token)
294
357
  @next_token = query_result[:next_token]
295
358
  items = query_result[:items].map do |name|
296
359
  new_item = self.new('id' => name)
297
360
  new_item.mark_as_old
361
+ new_item.reload if options[:auto_load]
298
362
  new_item
299
363
  end
300
364
  items
301
365
  end
302
366
 
367
+ def reload_all_records(*list) # :nodoc:
368
+ list.flatten.each { |record| record.reload }
369
+ end
370
+
303
371
  def find_every(options) # :nodoc:
304
- query(options[:conditions], options[:limit], options[:next_token])
372
+ records = query( :query_expression => options[:conditions],
373
+ :max_number_of_items => options[:limit],
374
+ :next_token => options[:next_token],
375
+ :sort_option => options[:sort] || options[:order] )
376
+ options[:auto_load] ? reload_all_records(records) : records
305
377
  end
306
378
 
307
379
  def find_initial(options) # :nodoc:
308
380
  options[:limit] = 1
309
- find_every(options)[0]
381
+ record = find_every(options).first
382
+ options[:auto_load] ? reload_all_records(record).first : record
310
383
  end
311
384
 
312
385
  def find_from_ids(args, options) # :nodoc:
@@ -326,7 +399,7 @@ module RightAws
326
399
  result = find_every(options)
327
400
  # if one record was requested then return it
328
401
  unless bunch_of_records_requested
329
- result.first
402
+ options[:auto_load] ? reload_all_records(result.first).first : result.first
330
403
  else
331
404
  # if a bunch of records was requested then return check that we found all of them
332
405
  # and return as an array
@@ -334,35 +407,40 @@ module RightAws
334
407
  id_list = args.map{|i| "'#{i}'"}.join(',')
335
408
  raise ActiveSdbError.new("Couldn't find all #{name} with IDs (#{id_list}) (found #{result.size} results, but was looking for #{args.size})")
336
409
  else
337
- result
410
+ options[:auto_load] ? reload_all_records(result) : result
338
411
  end
339
412
  end
340
413
  end
341
414
 
342
415
  # find_by helpers
343
- def find_all_by_(format_str, args, limit=nil) # :nodoc:
416
+ def find_all_by_(format_str, args, options) # :nodoc:
344
417
  fields = format_str.to_s.sub(/^find_(all_)?by_/,'').split('_and_')
345
418
  conditions = fields.map { |field| "['#{field}'=?]" }.join(' intersection ')
346
- find(:all, :conditions => [conditions, *args], :limit => limit)
419
+ options[:conditions] = [conditions, *args]
420
+ find(:all, options)
347
421
  end
348
422
 
349
- def find_by_(format_str, args) # :nodoc:
350
- find_all_by_(format_str, args, 1)[0]
423
+ def find_by_(format_str, args, options) # :nodoc:
424
+ options[:limit] = 1
425
+ find_all_by_(format_str, args, options).first
351
426
  end
352
427
 
353
428
  def method_missing(method, *args) # :nodoc:
354
- if method.to_s[/^find_all_by_/] then return find_all_by_(method, args)
355
- elsif method.to_s[/^find_by_/] then return find_by_(method, args)
356
- else super(method, *args)
429
+ if method.to_s[/^(find_all_by_|find_by_)/]
430
+ options = args.last.is_a?(Hash) ? args.pop : {}
431
+ if method.to_s[/^find_all_by_/]
432
+ find_all_by_(method, args, options)
433
+ else
434
+ find_by_(method, args, options)
435
+ end
436
+ else
437
+ super(method, *args)
357
438
  end
358
439
  end
359
-
360
440
  end
361
441
 
362
442
  def self.generate_id # :nodoc:
363
- result = ''
364
- result = UUID.timestamp_create().to_s
365
- result
443
+ UUID.timestamp_create().to_s
366
444
  end
367
445
 
368
446
  public
@@ -33,11 +33,14 @@ module RightAws
33
33
  DEFAULT_PORT = 443
34
34
  DEFAULT_PROTOCOL = 'https'
35
35
  API_VERSION = '2007-11-07'
36
+ DEFAULT_NIL_REPRESENTATION = 'nil'
36
37
 
37
38
  @@bench = AwsBenchmarkingBlock.new
38
39
  def self.bench_xml; @@bench.xml; end
39
40
  def self.bench_sdb; @@bench.service; end
40
41
 
42
+ attr_reader :last_query_expression
43
+
41
44
  # Creates new RightSdb instance.
42
45
  #
43
46
  # Params:
@@ -46,7 +49,8 @@ module RightAws
46
49
  # :protocol => 'https' # Amazon service protocol: 'http' or 'https'(default)
47
50
  # :signature_version => '0' # The signature version : '0' or '1'(default)
48
51
  # :multi_thread => true|false # Multi-threaded (connection per each thread): true or false(default)
49
- # :logger => Logger Object} # Logger instance: logs to STDOUT if omitted }
52
+ # :logger => Logger Object # Logger instance: logs to STDOUT if omitted
53
+ # :nil_representation => 'mynil'} # interpret Ruby nil as this string value; i.e. use this string in SDB to represent Ruby nils (default is the string 'nil')
50
54
  #
51
55
  # Example:
52
56
  #
@@ -55,6 +59,8 @@ module RightAws
55
59
  # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/
56
60
  #
57
61
  def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
62
+ @nil_rep = params[:nil_representation] ? params[:nil_representation] : DEFAULT_NIL_REPRESENTATION
63
+ params.delete(:nil_representation)
58
64
  init({ :name => 'SDB',
59
65
  :default_host => ENV['SDB_URL'] ? URI.parse(ENV['SDB_URL']).host : DEFAULT_HOST,
60
66
  :default_port => ENV['SDB_URL'] ? URI.parse(ENV['SDB_URL']).port : DEFAULT_PORT,
@@ -69,8 +75,8 @@ module RightAws
69
75
  #-----------------------------------------------------------------
70
76
  def generate_request(action, params={}) #:nodoc:
71
77
  # remove empty params from request
72
- params.delete_if {|key,value| value.blank? }
73
- params_string = params.to_a.collect{|key,val| key + "=#{CGI::escape(val.to_s)}" }.join("&")
78
+ params.delete_if {|key,value| value.nil? }
79
+ #params_string = params.to_a.collect{|key,val| key + "=#{CGI::escape(val.to_s)}" }.join("&")
74
80
  # prepare service data
75
81
  service_hash = {"Action" => action,
76
82
  "AWSAccessKeyId" => @aws_access_key_id,
@@ -80,20 +86,20 @@ module RightAws
80
86
  service_hash.update(params)
81
87
  # prepare string to sight
82
88
  string_to_sign = case signature_version
83
- when '0' : service_hash["Action"] + service_hash["Timestamp"]
84
- when '1' : service_hash.sort{|a,b| (a[0].to_s.downcase)<=>(b[0].to_s.downcase)}.to_s
89
+ when '0' then service_hash["Action"] + service_hash["Timestamp"]
90
+ when '1' then service_hash.sort{|a,b| (a[0].to_s.downcase)<=>(b[0].to_s.downcase)}.to_s
85
91
  end
86
92
  service_hash.update('Signature' => AwsUtils::sign(@aws_secret_access_key, string_to_sign))
87
93
  service_string = service_hash.to_a.collect{|key,val| key + "=#{CGI::escape(val.to_s)}" }.join("&")
88
94
  #
89
95
  # use POST method if the length of the query string is too large
90
96
  # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/MakingRESTRequests.html
91
- if (service_string + params_string).size > 2000
92
- request = Net::HTTP::Post.new("/?#{service_string}")
93
- request.body = params_string
97
+ if service_string.size > 2000
98
+ request = Net::HTTP::Post.new("/")
99
+ request.body = service_string
94
100
  else
95
- params_string = "&#{params_string}" unless params_string.blank?
96
- request = Net::HTTP::Get.new("/?#{service_string}#{params_string}")
101
+ #params_string = "&#{params_string}" unless params_string.blank?
102
+ request = Net::HTTP::Get.new("/?#{service_string}")
97
103
  end
98
104
  # prepare output hash
99
105
  { :request => request,
@@ -116,18 +122,20 @@ module RightAws
116
122
  result = {}
117
123
  if attributes
118
124
  idx = 0
125
+ skip_values = attributes.is_a?(Array)
119
126
  attributes.each do |attribute, values|
120
127
  # set replacement attribute
121
128
  result["Attribute.#{idx}.Replace"] = 'true' if replace
122
129
  # pack Name/Value
123
- unless values.blank?
124
- values.to_a.each do |value|
130
+ unless values.nil?
131
+ Array(values).each do |value|
125
132
  result["Attribute.#{idx}.Name"] = attribute
126
- result["Attribute.#{idx}.Value"] = value
133
+ result["Attribute.#{idx}.Value"] = ruby_to_sdb(value) unless skip_values
127
134
  idx += 1
128
135
  end
129
136
  else
130
137
  result["Attribute.#{idx}.Name"] = attribute
138
+ result["Attribute.#{idx}.Value"] = ruby_to_sdb(nil) unless skip_values
131
139
  idx += 1
132
140
  end
133
141
  end
@@ -144,6 +152,18 @@ module RightAws
144
152
  %Q{'#{value.to_s.gsub(/(['\\])/){ "\\#{$1}" }}'} if value
145
153
  end
146
154
 
155
+ # Convert a Ruby language value to a SDB value by replacing Ruby nil with the user's chosen string representation of nil.
156
+ # Non-nil values are unaffected by this filter.
157
+ def ruby_to_sdb(value)
158
+ value.nil? ? @nil_rep : value
159
+ end
160
+
161
+ # Convert a SDB value to a Ruby language value by replacing the user's chosen string representation of nil with Ruby nil.
162
+ # Values are unaffected by this filter unless they match the nil representation exactly.
163
+ def sdb_to_ruby(value)
164
+ value.eql?(@nil_rep) ? nil : value
165
+ end
166
+
147
167
  # Create query expression from an array.
148
168
  # (similar to ActiveRecord::Base#find using :conditions => ['query', param1, .., paramN])
149
169
  #
@@ -324,7 +344,11 @@ module RightAws
324
344
  link = generate_request("GetAttributes", 'DomainName' => domain_name,
325
345
  'ItemName' => item_name,
326
346
  'AttributeName' => attribute_name )
327
- request_info(link, QSdbGetAttributesParser.new)
347
+ res = request_info(link, QSdbGetAttributesParser.new)
348
+ res[:attributes].each_value do |values|
349
+ values.collect! { |e| sdb_to_ruby(e) }
350
+ end
351
+ res
328
352
  rescue Exception
329
353
  on_exception
330
354
  end
@@ -385,10 +409,15 @@ module RightAws
385
409
  # query = [ "['cat'=?] union ['dog'=?]", "clew", "Jon's boot" ]
386
410
  # sdb.query('family', query)
387
411
  #
412
+ # query = [ "['cat'=?] union ['dog'=?] sort 'cat' desc", "clew", "Jon's boot" ]
413
+ # sdb.query('family', query)
414
+ #
388
415
  # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_Query.html
416
+ # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?SortingData.html
389
417
  #
390
418
  def query(domain_name, query_expression = nil, max_number_of_items = nil, next_token = nil)
391
419
  query_expression = query_expression_from_array(query_expression) if query_expression.is_a?(Array)
420
+ @last_query_expression = query_expression
392
421
  #
393
422
  request_params = { 'DomainName' => domain_name,
394
423
  'QueryExpression' => query_expression,
@@ -420,10 +449,10 @@ module RightAws
420
449
  end
421
450
  def tagend(name)
422
451
  case name
423
- when 'NextToken' : @result[:next_token] = @text
424
- when 'DomainName' : @result[:domains] << @text
425
- when 'BoxUsage' : @result[:box_usage] = @text
426
- when 'RequestId' : @result[:request_id] = @text
452
+ when 'NextToken' then @result[:next_token] = @text
453
+ when 'DomainName' then @result[:domains] << @text
454
+ when 'BoxUsage' then @result[:box_usage] = @text
455
+ when 'RequestId' then @result[:request_id] = @text
427
456
  end
428
457
  end
429
458
  end
@@ -434,8 +463,8 @@ module RightAws
434
463
  end
435
464
  def tagend(name)
436
465
  case name
437
- when 'BoxUsage' : @result[:box_usage] = @text
438
- when 'RequestId' : @result[:request_id] = @text
466
+ when 'BoxUsage' then @result[:box_usage] = @text
467
+ when 'RequestId' then @result[:request_id] = @text
439
468
  end
440
469
  end
441
470
  end
@@ -447,10 +476,10 @@ module RightAws
447
476
  end
448
477
  def tagend(name)
449
478
  case name
450
- when 'Name' : @last_attribute_name = @text
451
- when 'Value' : (@result[:attributes][@last_attribute_name] ||= []) << @text
452
- when 'BoxUsage' : @result[:box_usage] = @text
453
- when 'RequestId' : @result[:request_id] = @text
479
+ when 'Name' then @last_attribute_name = @text
480
+ when 'Value' then (@result[:attributes][@last_attribute_name] ||= []) << @text
481
+ when 'BoxUsage' then @result[:box_usage] = @text
482
+ when 'RequestId' then @result[:request_id] = @text
454
483
  end
455
484
  end
456
485
  end
@@ -461,10 +490,10 @@ module RightAws
461
490
  end
462
491
  def tagend(name)
463
492
  case name
464
- when 'ItemName' : @result[:items] << @text
465
- when 'BoxUsage' : @result[:box_usage] = @text
466
- when 'RequestId' : @result[:request_id] = @text
467
- when 'NextToken' : @result[:next_token] = @text
493
+ when 'ItemName' then @result[:items] << @text
494
+ when 'BoxUsage' then @result[:box_usage] = @text
495
+ when 'RequestId' then @result[:request_id] = @text
496
+ when 'NextToken' then @result[:next_token] = @text
468
497
  end
469
498
  end
470
499
  end
@@ -54,6 +54,14 @@ module RightAws
54
54
  # ...
55
55
  # grantee2 = queue.grantees('another_cool_guy@email.address')
56
56
  # grantee2.revoke('SENDMESSAGE')
57
+ #
58
+ # Params is a hash:
59
+ #
60
+ # {:server => 'queue.amazonaws.com' # Amazon service host: 'queue.amazonaws.com' (default)
61
+ # :port => 443 # Amazon service port: 80 or 443 (default)
62
+ # :multi_thread => true|false # Multi-threaded (connection per each thread): true or false (default)
63
+ # :signature_version => '0' # The signature version : '0' or '1'(default)
64
+ # :logger => Logger Object} # Logger instance: logs to STDOUT if omitted }
57
65
  #
58
66
  class Sqs
59
67
  attr_reader :interface
@@ -55,6 +55,14 @@ module RightAws
55
55
  # ...
56
56
  #
57
57
  # NB: Second-generation SQS has eliminated the entire access grant mechanism present in Gen 1.
58
+ #
59
+ # Params is a hash:
60
+ #
61
+ # {:server => 'queue.amazonaws.com' # Amazon service host: 'queue.amazonaws.com' (default)
62
+ # :port => 443 # Amazon service port: 80 or 443 (default)
63
+ # :multi_thread => true|false # Multi-threaded (connection per each thread): true or false (default)
64
+ # :signature_version => '0' # The signature version : '0' or '1'(default)
65
+ # :logger => Logger Object} # Logger instance: logs to STDOUT if omitted }
58
66
  class SqsGen2
59
67
  attr_reader :interface
60
68
 
@@ -7,6 +7,7 @@ class TestS3 < Test::Unit::TestCase
7
7
  def setup
8
8
  @s3 = Rightscale::S3Interface.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key)
9
9
  @bucket = 'right_s3_awesome_test_bucket_00001'
10
+ @bucket2 = 'right_s3_awesome_test_bucket_00002'
10
11
  @key1 = 'test/woohoo1/'
11
12
  @key2 = 'test1/key/woohoo2'
12
13
  @key3 = 'test2/A%B@C_D&E?F+G=H"I'
@@ -72,9 +73,9 @@ class TestS3 < Test::Unit::TestCase
72
73
  def test_08_keys
73
74
  keys = @s3.list_bucket(@bucket).map{|b| b[:key]}
74
75
  assert_equal keys.size, 3, "There should be 3 keys"
75
- assert(keys.include? @key1)
76
- assert(keys.include? @key2)
77
- assert(keys.include? @key3)
76
+ assert(keys.include?(@key1))
77
+ assert(keys.include?(@key2))
78
+ assert(keys.include?(@key3))
78
79
  end
79
80
 
80
81
  def test_09_copy_key
@@ -120,18 +121,26 @@ class TestS3 < Test::Unit::TestCase
120
121
  keys = @s3.list_bucket(@bucket).map{|b| b[:key]}
121
122
  assert(!keys.include?(@key2))
122
123
  end
123
-
124
- def test_12_delete_folder
124
+ def test_12_retrieve_object
125
+ assert_raise(Rightscale::AwsError) { @s3.retrieve_object(:bucket => @bucket, :key => 'undefined/key') }
126
+ data1 = @s3.retrieve_object(:bucket => @bucket, :key => @key1_new_name)
127
+ assert_equal RIGHT_OBJECT_TEXT, data1[:object], "Object text must be equal to '#{RIGHT_OBJECT_TEXT}'"
128
+ assert_equal 'Woohoo1!', data1[:headers]['x-amz-meta-family'], "x-amz-meta-family header must be equal to 'Woohoo1!'"
129
+ end
130
+ def test_13_delete_folder
125
131
  assert_equal 1, @s3.delete_folder(@bucket, 'test').size, "Only one key(#{@key1}) must be deleted!"
126
132
  end
127
-
128
- def test_13_delete_bucket
133
+
134
+ def test_14_delete_bucket
129
135
  assert_raise(Rightscale::AwsError) { @s3.delete_bucket(@bucket) }
130
136
  assert @s3.clear_bucket(@bucket), 'Clear_bucket fail'
131
137
  assert_equal 0, @s3.list_bucket(@bucket).size, 'Bucket must be empty'
132
138
  assert @s3.delete_bucket(@bucket)
133
139
  assert !@s3.list_all_my_buckets.map{|bucket| bucket[:name]}.include?(@bucket), "#{@bucket} must not exist"
134
140
  end
141
+
142
+
143
+
135
144
 
136
145
  #---------------------------
137
146
  # Rightscale::S3 classes
@@ -312,10 +321,10 @@ class TestS3 < Test::Unit::TestCase
312
321
  assert grantee.apply
313
322
  assert !grantee.exists?
314
323
  # Check multiple perms assignment
315
- assert grantee.grant 'FULL_CONTROL', 'READ', 'WRITE'
324
+ assert grantee.grant('FULL_CONTROL', 'READ', 'WRITE')
316
325
  assert_equal ['FULL_CONTROL','READ','WRITE'].sort, grantee.perms.sort
317
326
  # Check multiple perms removal
318
- assert grantee.revoke 'FULL_CONTROL', 'WRITE'
327
+ assert grantee.revoke('FULL_CONTROL', 'WRITE')
319
328
  assert_equal ['READ'], grantee.perms
320
329
  # check 'Drop' method
321
330
  assert grantee.drop
@@ -384,5 +393,27 @@ class TestS3 < Test::Unit::TestCase
384
393
  RightAws::S3Interface.amazon_problems= nil
385
394
  assert_nil(RightAws::S3Interface.amazon_problems)
386
395
  end
396
+
397
+ def test_37_access_logging
398
+ bucket = Rightscale::S3::Bucket.create(@s, @bucket, false)
399
+ targetbucket = Rightscale::S3::Bucket.create(@s, @bucket2, true)
400
+ # Take 'AllUsers' grantee
401
+ grantee = Rightscale::S3::Grantee.new(targetbucket,'http://acs.amazonaws.com/groups/s3/LogDelivery')
402
+
403
+ assert grantee.grant(['READ_ACP', 'WRITE'])
404
+
405
+ assert bucket.enable_logging(:targetbucket => targetbucket, :targetprefix => "loggylogs/")
406
+
407
+ assert_equal(bucket.logging_info, {:enabled => true, :targetbucket => @bucket2, :targetprefix => "loggylogs/"})
408
+
409
+ assert bucket.disable_logging
410
+
411
+ # check 'Drop' method
412
+ assert grantee.drop
413
+
414
+ # Delete bucket
415
+ bucket.delete(true)
416
+ targetbucket.delete(true)
417
+ end
387
418
 
388
419
  end
@@ -127,6 +127,11 @@ class TestSdb < Test::Unit::TestCase
127
127
  assert_equal 2, Client.find_all_by_post_and_country('president','Russia').size
128
128
  # find all women in USA that love flowers
129
129
  assert_equal 2, Client.find_all_by_gender_and_country_and_hobby('female','Russia','flowers').size
130
+ # order and auto_load:
131
+ clients = Client.find_all_by_post('president', :order => 'name', :auto_load => true)
132
+ assert_equal [['Bush'], ['Medvedev'], ['Putin']], clients.map{|c| c['name']}
133
+ clients = Client.find_all_by_post('president', :order => 'name desc', :auto_load => true)
134
+ assert_equal [['Putin'], ['Medvedev'], ['Bush']], clients.map{|c| c['name']}
130
135
  end
131
136
 
132
137
  def test_07_find_by_helpers
@@ -135,7 +140,9 @@ class TestSdb < Test::Unit::TestCase
135
140
  # find any russian president
136
141
  assert Client.find_by_post_and_country('president','Russia')
137
142
  # find Mary in Russia that loves flowers
138
- assert Client.find_by_gender_and_country_and_hobby('female','Russia','flowers')
143
+ # order and auto_load:
144
+ assert_equal ['Bush'], Client.find_by_post('president', :order => 'name', :auto_load => true)['name']
145
+ assert_equal ['Putin'], Client.find_by_post('president', :order => 'name desc', :auto_load => true)['name']
139
146
  end
140
147
 
141
148
  def test_08_reload
@@ -226,7 +233,7 @@ class TestSdb < Test::Unit::TestCase
226
233
  assert ['russian'], new_putin['language'].sort
227
234
  # --- delete_attributes
228
235
  putin.delete_attributes('language', 'hobby')
229
- wait SDB_DELAY, 'test 11: after put_attributes'
236
+ wait SDB_DELAY, 'test 11: after delete_attributes'
230
237
  # trash hoddy and langs
231
238
  new_putin = Client.find_by_name('Putin')
232
239
  new_putin.reload
@@ -1,2 +1,3 @@
1
1
  require 'test/unit'
2
2
  require File.dirname(__FILE__) + '/../../lib/right_aws'
3
+ require 'sdb/active_sdb'
@@ -97,15 +97,15 @@ class TestSdb < Test::Unit::TestCase
97
97
  end
98
98
 
99
99
  def test_07_delete_item
100
- @sdb.put_attributes @domain, @item, {'Jon' => ['girls','vodka']}
100
+ @sdb.put_attributes @domain, @item, {'Volodya' => ['girls','vodka']}
101
101
  wait SDB_DELAY, 'after adding attributes'
102
102
  # get attributes ('girls' and 'vodka' must be there)
103
- values = @sdb.get_attributes(@domain, @item)[:attributes]['Jon'].to_a.sort
103
+ values = @sdb.get_attributes(@domain, @item)[:attributes]['Volodya'].to_a.sort
104
104
  assert_equal values, ['girls', 'vodka']
105
105
  # delete an item
106
106
  @sdb.delete_attributes @domain, @item
107
107
  # get attributes (values must be empty)
108
- values = @sdb.get_attributes(@domain, @item)[:attributes]['Jon']
108
+ values = @sdb.get_attributes(@domain, @item)[:attributes]['Volodya']
109
109
  assert_equal values, nil
110
110
  end
111
111
 
@@ -121,6 +121,7 @@ class TestSdb < Test::Unit::TestCase
121
121
  def test_09_signature_version_0
122
122
  sdb = Rightscale::SdbInterface.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key, :signature_version => '0')
123
123
  item = 'toys'
124
+ # TODO: need to change the below test. I think Juergen's intention was to include some umlauts in the values
124
125
  # put attributes
125
126
  # mhhh... Not sure how to translate this: hölzchehn klötzchen grÃŒnspan buße... Lets assume this is:
126
127
  attributes = { 'Jurgen' => %w{kitten puppy chickabiddy piglet} }
@@ -134,8 +135,58 @@ class TestSdb < Test::Unit::TestCase
134
135
  assert sdb.last_request.path.include?('SignatureVersion=0')
135
136
  end
136
137
 
138
+ def test_10_array_of_attrs
139
+ item = 'multiples'
140
+ assert_nothing_thrown "Failed to put multiple attrs" do
141
+ @sdb.put_attributes(@domain, item, {:one=>1, :two=>2, :three=>3})
142
+ end
143
+ end
144
+
145
+ def test_11_zero_len_attrs
146
+ item = 'zeroes'
147
+ assert_nothing_thrown "Failed to put zero-length attributes" do
148
+ @sdb.put_attributes(@domain, item, {:one=>"", :two=>"", :three=>""})
149
+ end
150
+ end
151
+
152
+ def test_12_nil_attrs
153
+ item = 'nils'
154
+ res = nil
155
+ assert_nothing_thrown do
156
+ @sdb.put_attributes(@domain, item, {:one=>nil, :two=>nil, :three=>'chunder'})
157
+ end
158
+ assert_nothing_thrown do
159
+ res = @sdb.get_attributes(@domain, item)
160
+ end
161
+ assert_nil(res[:attributes]['one'][0])
162
+ assert_nil(res[:attributes]['two'][0])
163
+ assert_not_nil(res[:attributes]['three'][0])
164
+ end
165
+
166
+ def test_13_url_escape
167
+ item = 'urlescapes'
168
+ content = {:a=>"one & two & three",
169
+ :b=>"one ? two / three"}
170
+ @sdb.put_attributes(@domain, item, content)
171
+
172
+ res = @sdb.get_attributes(@domain, item)
173
+ assert_equal(content[:a], res[:attributes]['a'][0])
174
+ assert_equal(content[:b], res[:attributes]['b'][0])
175
+ end
176
+
177
+ def test_14_put_attrs_by_post
178
+ item = 'reqgirth'
179
+ i = 0
180
+ sa = ""
181
+ while(i < 64) do
182
+ sa += "aaaaaaaa"
183
+ i += 1
184
+ end
185
+ @sdb.put_attributes(@domain, item, {:a => sa, :b => sa, :c => sa, :d => sa, :e => sa})
186
+ end
187
+
137
188
  # Keep this test last, because it deletes the domain...
138
- def test_10_delete_domain
189
+ def test_20_delete_domain
139
190
  assert @sdb.delete_domain(@domain), 'delete_domain fail'
140
191
  wait SDB_DELAY, 'after domain deletion'
141
192
  # check that domain does not exist
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: right_aws
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 1.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - RightScale, Inc.
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-08-22 00:00:00 -07:00
12
+ date: 2008-10-07 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency