right_aws 1.9.0 → 1.10.0

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.
data/History.txt CHANGED
@@ -219,3 +219,28 @@ the source key.
219
219
  S3Interface::retrieve_object_and_verify (thanks to numerous user reports)
220
220
  - Updates to caching for Ec2::describe_images_by methods
221
221
  - Ec2 now has Ec2::last_request_id
222
+
223
+ === 1.10.0
224
+
225
+ Release Notes:
226
+
227
+ The big new features are SDB's SQL-like query and query_with_attributes
228
+ support as well as signature v2 support for all services. There are also
229
+ numerous bug fixes, many of them reported and patched by users and
230
+ customers.
231
+
232
+ - AwsBase: signature v2 support added
233
+ - AwsBase: fix the regex matching for xmlpath which didn't work in Ruby
234
+ 1.8.7 (thanks to a customer report)
235
+ - Ec2: describe_availability_zones improved to support regions
236
+ - Ec2: Disabled retries when EC2 returns "ServiceUnavailable: Request limit
237
+ exceeded"
238
+ - Ec2: Use POST for large queries; this avoids truncation of large user data
239
+ when launching instances (thanks to Bob for the report)
240
+ - CloudFront: docs fixes
241
+ - SDB: added: SQL-like query, select and query_with_attributes support
242
+ - SDB: fixed no method error when searching for id that doesn't exist
243
+ (thanks to multiple users for reporting this)
244
+ - S3: Fixed overzealous URL-encoding when generating URLs for S3 keys
245
+ (thanks to a bug report on the RubyForge forum)
246
+
data/README.txt CHANGED
@@ -38,7 +38,7 @@ The RightScale AWS gems comprise:
38
38
  AWS accounts.
39
39
  - Support for both first- and second-generation SQS (API versions 2007-05-01
40
40
  and 2008-01-01). These versions of SQS are not compatible.
41
- - Support for signature versions 0 and 1 on SQS, SDB, and EC2.
41
+ - Support for signature versions 0, 1 and 2 on all services.
42
42
  - Interoperability with any cloud running Eucalyptus (http://eucalyptus.cs.ucsb.edu)
43
43
  - Test suite (requires AWS account to do "live" testing).
44
44
 
@@ -119,7 +119,9 @@ multithreaded mode.
119
119
  'incompatible Net::HTTP monkey-patch'
120
120
 
121
121
  This is due to a conflict between the right_http_connection gem and another
122
- gem required by attachment_fu.
122
+ gem required by attachment_fu. It may be possible to require right_aws (and
123
+ thus right_http_connection) in the .after_initialize method of the config object in
124
+ environment.rb (check the docs for Rails::Configuration.after_initialize).
123
125
 
124
126
  - 8/07: Amazon has changed the semantics of the SQS service. A
125
127
  new queue may not be created within 60 seconds of the destruction of any
@@ -142,7 +144,7 @@ sudo gem install right_aws
142
144
 
143
145
  == LICENSE:
144
146
 
145
- Copyright (c) 2007-2008 RightScale, Inc.
147
+ Copyright (c) 2007-2009 RightScale, Inc.
146
148
 
147
149
  Permission is hereby granted, free of charge, to any person obtaining
148
150
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -10,27 +10,36 @@ require 'lib/right_aws.rb'
10
10
  testglobs = ["test/ts_right_aws.rb"]
11
11
 
12
12
 
13
- # Suppress Hoe's self-inclusion as a dependency for our Gem. This also keeps
14
- # Rake & rubyforge out of the dependency list. Users must manually install
13
+ # Suppress Hoe's self-inclusion as a dependency for our Gem. This also keeps
14
+ # Rake & rubyforge out of the dependency list. Users must manually install
15
15
  # these gems to run tests, etc.
16
+ # TRB 2/24/09: also do this for the extra_dev_deps array present in newer hoes.
17
+ # Older versions of RubyGems will try to install developer-dependencies as
18
+ # required runtime dependencies. It would be great to take advantage of the extra dev deps, but not
19
+ # at the cost of requiring many additional dependencies if the user is running an older RubyGems.
16
20
  class Hoe
17
21
  def extra_deps
18
22
  @extra_deps.reject do |x|
19
23
  Array(x).first == 'hoe'
20
24
  end
21
25
  end
26
+ def extra_dev_deps
27
+ @extra_dev_deps.reject do |x|
28
+ Array(x).first == 'hoe'
29
+ end
30
+ end
22
31
  end
23
32
 
24
33
  Hoe.new('right_aws', RightAws::VERSION::STRING) do |p|
25
- p.rubyforge_name = 'rightaws'
34
+ p.rubyforge_name = 'rightscale'
26
35
  p.author = 'RightScale, Inc.'
27
- p.email = 'support@rightscale.com'
28
- p.summary = 'Interface classes for the Amazon EC2, SQS, and S3 Web Services'
36
+ p.email = 'rubygems@rightscale.com'
37
+ p.summary = 'Interface classes for the Amazon EC2/EBS, SQS, S3, SDB, and ACF Web Services'
29
38
  p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
30
39
  p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
31
40
  p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
32
41
  p.remote_rdoc_dir = "/right_aws_gem_doc"
33
- p.extra_deps = [['right_http_connection','>= 1.2.1']]
42
+ p.extra_deps = [['right_http_connection','>= 1.2.4']]
34
43
  p.test_globs = testglobs
35
44
  end
36
45
 
@@ -198,7 +198,7 @@ module RightAws
198
198
  # Create a new distribution.
199
199
  # Returns the just created distribution or RightAws::AwsError exception.
200
200
  #
201
- # acf.create_distribution('bucket-for-k-dzreyev.s3.amazonaws.com', 'Woo-Hoo!', ['web1.my-awesome-site.net'] ) #=>
201
+ # acf.create_distribution('bucket-for-k-dzreyev.s3.amazonaws.com', 'Woo-Hoo!', true, ['web1.my-awesome-site.net'] ) #=>
202
202
  # {:comment => "Woo-Hoo!",
203
203
  # :enabled => true,
204
204
  # :location => "https://cloudfront.amazonaws.com/2008-06-30/distribution/E2REJM3VUN5RSI",
@@ -27,9 +27,69 @@ module RightAws
27
27
  require 'pp'
28
28
 
29
29
  class AwsUtils #:nodoc:
30
- @@digest = OpenSSL::Digest::Digest.new("sha1")
30
+ @@digest1 = OpenSSL::Digest::Digest.new("sha1")
31
+ @@digest256 = nil
32
+ if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000
33
+ @@digest256 = OpenSSL::Digest::Digest.new("sha256") rescue nil # Some installation may not support sha256
34
+ end
35
+
31
36
  def self.sign(aws_secret_access_key, auth_string)
32
- Base64.encode64(OpenSSL::HMAC.digest(@@digest, aws_secret_access_key, auth_string)).strip
37
+ Base64.encode64(OpenSSL::HMAC.digest(@@digest1, aws_secret_access_key, auth_string)).strip
38
+ end
39
+
40
+ # Escape a string accordingly Amazon rulles
41
+ # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
42
+ def self.amz_escape(param)
43
+ param.to_s.gsub(/([^a-zA-Z0-9._~-]+)/n) do
44
+ '%' + $1.unpack('H2' * $1.size).join('%').upcase
45
+ end
46
+ end
47
+
48
+ # Set a timestamp and a signature version
49
+ def self.fix_service_params(service_hash, signature)
50
+ service_hash["Timestamp"] ||= Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.000Z") unless service_hash["Expires"]
51
+ service_hash["SignatureVersion"] = signature
52
+ service_hash
53
+ end
54
+
55
+ # Signature Version 0
56
+ # A deprecated guy (should work till septemper 2009)
57
+ def self.sign_request_v0(aws_secret_access_key, service_hash)
58
+ fix_service_params(service_hash, '0')
59
+ string_to_sign = "#{service_hash['Action']}#{service_hash['Timestamp'] || service_hash['Expires']}"
60
+ service_hash['Signature'] = AwsUtils::sign(aws_secret_access_key, string_to_sign)
61
+ service_hash.to_a.collect{|key,val| "#{amz_escape(key)}=#{amz_escape(val.to_s)}" }.join("&")
62
+ end
63
+
64
+ # Signature Version 1
65
+ # Another deprecated guy (should work till septemper 2009)
66
+ def self.sign_request_v1(aws_secret_access_key, service_hash)
67
+ fix_service_params(service_hash, '1')
68
+ string_to_sign = service_hash.sort{|a,b| (a[0].to_s.downcase)<=>(b[0].to_s.downcase)}.to_s
69
+ service_hash['Signature'] = AwsUtils::sign(aws_secret_access_key, string_to_sign)
70
+ service_hash.to_a.collect{|key,val| "#{amz_escape(key)}=#{amz_escape(val.to_s)}" }.join("&")
71
+ end
72
+
73
+ # Signature Version 2
74
+ # EC2, SQS and SDB requests must be signed by this guy.
75
+ # See: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
76
+ # http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1928
77
+ def self.sign_request_v2(aws_secret_access_key, service_hash, http_verb, host, uri)
78
+ fix_service_params(service_hash, '2')
79
+ # select a signing method (make an old openssl working with sha1)
80
+ # make 'HmacSHA256' to be a default one
81
+ service_hash['SignatureMethod'] = 'HmacSHA256' unless ['HmacSHA256', 'HmacSHA1'].include?(service_hash['SignatureMethod'])
82
+ service_hash['SignatureMethod'] = 'HmacSHA1' unless @@digest256
83
+ # select a digest
84
+ digest = (service_hash['SignatureMethod'] == 'HmacSHA256' ? @@digest256 : @@digest1)
85
+ # form string to sign
86
+ canonical_string = service_hash.keys.sort.map do |key|
87
+ "#{amz_escape(key)}=#{amz_escape(service_hash[key])}"
88
+ end.join('&')
89
+ string_to_sign = "#{http_verb.to_s.upcase}\n#{host.downcase}\n#{uri}\n#{canonical_string}"
90
+ # sign the string
91
+ signature = amz_escape(Base64.encode64(OpenSSL::HMAC.digest(digest, aws_secret_access_key, string_to_sign)).strip)
92
+ "#{canonical_string}&Signature=#{signature}"
33
93
  end
34
94
 
35
95
  # From Amazon's SQS Dev Guide, a brief description of how to escape:
@@ -111,7 +171,7 @@ module RightAws
111
171
  end
112
172
 
113
173
  module RightAwsBaseInterface
114
- DEFAULT_SIGNATURE_VERSION = '1'
174
+ DEFAULT_SIGNATURE_VERSION = '2'
115
175
 
116
176
  @@caching = false
117
177
  def self.caching
@@ -148,10 +208,20 @@ module RightAws
148
208
  if aws_access_key_id.blank? || aws_secret_access_key.blank?
149
209
  @aws_access_key_id = aws_access_key_id
150
210
  @aws_secret_access_key = aws_secret_access_key
151
- @params[:server] ||= service_info[:default_host]
152
- @params[:port] ||= service_info[:default_port]
153
- @params[:service] ||= service_info[:default_service]
154
- @params[:protocol] ||= service_info[:default_protocol]
211
+ # if the endpoint was explicitly defined - then use it
212
+ if @params[:endpoint_url]
213
+ @params[:server] = URI.parse(@params[:endpoint_url]).host
214
+ @params[:port] = URI.parse(@params[:endpoint_url]).port
215
+ @params[:service] = URI.parse(@params[:endpoint_url]).path
216
+ @params[:protocol] = URI.parse(@params[:endpoint_url]).scheme
217
+ @params[:region] = nil
218
+ else
219
+ @params[:server] ||= service_info[:default_host]
220
+ @params[:server] = "#{@params[:region]}.#{@params[:server]}" if @params[:region]
221
+ @params[:port] ||= service_info[:default_port]
222
+ @params[:service] ||= service_info[:default_service]
223
+ @params[:protocol] ||= service_info[:default_protocol]
224
+ end
155
225
  @params[:multi_thread] ||= defined?(AWS_DAEMON)
156
226
  @logger = @params[:logger]
157
227
  @logger = RAILS_DEFAULT_LOGGER if !@logger && defined?(RAILS_DEFAULT_LOGGER)
@@ -162,6 +232,15 @@ module RightAws
162
232
  @signature_version = (params[:signature_version] || DEFAULT_SIGNATURE_VERSION).to_s
163
233
  end
164
234
 
235
+ def signed_service_params(aws_secret_access_key, service_hash, http_verb=nil, host=nil, service=nil )
236
+ case signature_version.to_s
237
+ when '0' then AwsUtils::sign_request_v0(aws_secret_access_key, service_hash)
238
+ when '1' then AwsUtils::sign_request_v1(aws_secret_access_key, service_hash)
239
+ when '2' then AwsUtils::sign_request_v2(aws_secret_access_key, service_hash, http_verb, host, service)
240
+ else raise AwsError.new("Unknown signature version (#{signature_version.to_s}) requested")
241
+ end
242
+ end
243
+
165
244
  # Returns +true+ if the describe_xxx responses are being cached
166
245
  def caching?
167
246
  @params.key?(:cache) ? @params[:cache] : @@caching
@@ -592,8 +671,9 @@ module RightAws
592
671
  @xmlpath += @xmlpath.empty? ? name : "/#{name}"
593
672
  end
594
673
  def tag_end(name)
595
- @xmlpath[/^(.*?)\/?#{name}$/]
596
- @xmlpath = $1
674
+ if @xmlpath =~ /^(.*?)\/?#{name}$/
675
+ @xmlpath = $1
676
+ end
597
677
  tagend(name)
598
678
  end
599
679
  def text(text)
data/lib/ec2/right_ec2.rb CHANGED
@@ -68,7 +68,7 @@ module RightAws
68
68
  include RightAwsBaseInterface
69
69
 
70
70
  # Amazon EC2 API version being used
71
- API_VERSION = "2008-08-08"
71
+ API_VERSION = "2008-12-01"
72
72
  DEFAULT_HOST = "ec2.amazonaws.com"
73
73
  DEFAULT_PATH = '/'
74
74
  DEFAULT_PROTOCOL = 'https'
@@ -100,7 +100,9 @@ module RightAws
100
100
  # Create a new handle to an EC2 account. All handles share the same per process or per thread
101
101
  # HTTP connection to Amazon EC2. Each handle is for a specific account. The params have the
102
102
  # following options:
103
+ # * <tt>:endpoint_url</tt> a fully qualified url to Amazon API endpoint (this overwrites: :server, :port, :service, :protocol and :region). Example: 'https://eu-west-1.ec2.amazonaws.com/'
103
104
  # * <tt>:server</tt>: EC2 service host, default: DEFAULT_HOST
105
+ # * <tt>:region</tt>: EC2 region (North America by default)
104
106
  # * <tt>:port</tt>: EC2 service port, default: DEFAULT_PORT
105
107
  # * <tt>:protocol</tt>: 'http' or 'https', default: DEFAULT_PROTOCOL
106
108
  # * <tt>:multi_thread</tt>: true=HTTP connection per thread, false=per process
@@ -120,24 +122,34 @@ module RightAws
120
122
  aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'] ,
121
123
  aws_secret_access_key|| ENV['AWS_SECRET_ACCESS_KEY'],
122
124
  params)
125
+ # EC2 doesn't really define any transient errors to retry, and in fact,
126
+ # when they return a 503 it is usually for 'request limit exceeded' which
127
+ # we most certainly should not retry. So let's pare down the list of
128
+ # retryable errors to InternalError only (see RightAwsBase for the default
129
+ # list)
130
+ amazon_problems = ['InternalError']
123
131
  end
124
132
 
125
133
 
126
134
  def generate_request(action, params={}) #:nodoc:
127
- service_hash = {"Action" => action,
128
- "AWSAccessKeyId" => @aws_access_key_id,
129
- "Version" => @@api,
130
- "Timestamp" => Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.000Z"),
131
- "SignatureVersion" => signature_version }
135
+ service_hash = {"Action" => action,
136
+ "AWSAccessKeyId" => @aws_access_key_id,
137
+ "Version" => @@api }
132
138
  service_hash.update(params)
133
- # prepare string to sign
134
- string_to_sign = case signature_version
135
- when '0' then service_hash["Action"] + service_hash["Timestamp"]
136
- when '1' then service_hash.sort{|a,b| (a[0].to_s.downcase)<=>(b[0].to_s.downcase)}.to_s
137
- end
138
- service_hash.update('Signature' => AwsUtils::sign(@aws_secret_access_key, string_to_sign))
139
- request_params = service_hash.to_a.collect{|key,val| key + "=" + CGI::escape(val) }.join("&")
140
- request = Net::HTTP::Get.new("#{@params[:service]}?#{request_params}")
139
+ service_params = signed_service_params(@aws_secret_access_key, service_hash, :get, @params[:server], @params[:service])
140
+
141
+ # use POST method if the length of the query string is too large
142
+ if service_params.size > 2000
143
+ if signature_version == '2'
144
+ # resign the request because HTTP verb is included into signature
145
+ service_params = signed_service_params(@aws_secret_access_key, service_hash, :post, @params[:server], @params[:service])
146
+ end
147
+ request = Net::HTTP::Post.new(service)
148
+ request.body = service_params
149
+ request['Content-Type'] = 'application/x-www-form-urlencoded'
150
+ else
151
+ request = Net::HTTP::Get.new("#{@params[:service]}?#{service_params}")
152
+ end
141
153
  # prepare output hash
142
154
  { :request => request,
143
155
  :server => @params[:server],
@@ -521,7 +533,7 @@ module RightAws
521
533
  # Amazon 169.254.169.254 does not like escaped symbols!
522
534
  # And it doesn't like "\n" inside of encoded string! Grrr....
523
535
  # Otherwise, some of UserData symbols will be lost...
524
- params['UserData'] = Base64.encode64(lparams[:user_data]).delete("\n") unless lparams[:user_data].blank?
536
+ params['UserData'] = Base64.encode64(lparams[:user_data]).delete("\n").strip unless lparams[:user_data].blank?
525
537
  end
526
538
  link = generate_request("RunInstances", params)
527
539
  #debugger
@@ -969,11 +981,13 @@ module RightAws
969
981
  # Describes availability zones that are currently available to the account and their states.
970
982
  # Returns an array of 2 keys (:zone_name and :zone_state) hashes:
971
983
  #
972
- # ec2.describe_availability_zones #=> [{:zone_state=>"available", :zone_name=>"us-east-1a"},
973
- # {:zone_state=>"available", :zone_name=>"us-east-1b"},
974
- # {:zone_state=>"available", :zone_name=>"us-east-1c"}]
984
+ # ec2.describe_availability_zones #=> [{:region_name=>"us-east-1",
985
+ # :zone_name=>"us-east-1a",
986
+ # :zone_state=>"available"}, ... ]
975
987
  #
976
- # ec2.describe_availability_zones('us-east-1c') #=> [{:zone_state=>"available", :zone_name=>"us-east-1c"}]
988
+ # ec2.describe_availability_zones('us-east-1c') #=> [{:region_name=>"us-east-1",
989
+ # :zone_state=>"available",
990
+ # :zone_name=>"us-east-1c"}]
977
991
  #
978
992
  def describe_availability_zones(list=[])
979
993
  link = generate_request("DescribeAvailabilityZones",
@@ -983,6 +997,23 @@ module RightAws
983
997
  on_exception
984
998
  end
985
999
 
1000
+ #-----------------------------------------------------------------
1001
+ # Regions
1002
+ #-----------------------------------------------------------------
1003
+
1004
+ # Describe regions.
1005
+ #
1006
+ # ec2.describe_regions #=> ["eu-west-1", "us-east-1"]
1007
+ #
1008
+ def describe_regions(list=[])
1009
+ link = generate_request("DescribeRegions",
1010
+ hash_params('RegionName',list.to_a))
1011
+ request_cache_or_info :describe_regions, link, QEc2DescribeRegionsParser, @@bench, list.blank?
1012
+ rescue Exception
1013
+ on_exception
1014
+ end
1015
+
1016
+
986
1017
  #-----------------------------------------------------------------
987
1018
  # EBS: Volumes
988
1019
  #-----------------------------------------------------------------
@@ -1133,6 +1164,45 @@ module RightAws
1133
1164
  rescue Exception
1134
1165
  on_exception
1135
1166
  end
1167
+
1168
+ # Create a snapshot of specified volume, but with the normal retry algorithms disabled.
1169
+ # This method will return immediately upon error. The user can specify connect and read timeouts (in s)
1170
+ # for the connection to AWS. If the user does not specify timeouts, try_create_snapshot uses the default values
1171
+ # in Rightscale::HttpConnection.
1172
+ #
1173
+ # ec2.try_create_snapshot('vol-898a6fe0') #=>
1174
+ # {:aws_volume_id => "vol-fd9f7a94",
1175
+ # :aws_started_at => Tue Jun 24 18:40:40 UTC 2008,
1176
+ # :aws_progress => "",
1177
+ # :aws_status => "pending",
1178
+ # :aws_id => "snap-d56783bc"}
1179
+ #
1180
+ def try_create_snapshot(volume_id, connect_timeout = nil, read_timeout = nil)
1181
+ # For safety in the ensure block...we don't want to restore values
1182
+ # if we never read them in the first place
1183
+ orig_reiteration_time = nil
1184
+ orig_http_params = nil
1185
+
1186
+ orig_reiteration_time = RightAws::AWSErrorHandler::reiteration_time
1187
+ RightAws::AWSErrorHandler::reiteration_time = 0
1188
+
1189
+ orig_http_params = Rightscale::HttpConnection::params()
1190
+ new_http_params = orig_http_params.dup
1191
+ new_http_params[:http_connection_retry_count] = 0
1192
+ new_http_params[:http_connection_open_timeout] = connect_timeout if !connect_timeout.nil?
1193
+ new_http_params[:http_connection_read_timeout] = read_timeout if !read_timeout.nil?
1194
+ Rightscale::HttpConnection::params = new_http_params
1195
+
1196
+ link = generate_request("CreateSnapshot",
1197
+ "VolumeId" => volume_id.to_s)
1198
+ request_info(link, QEc2CreateSnapshotParser.new(:logger => @logger))
1199
+
1200
+ rescue Exception
1201
+ on_exception
1202
+ ensure
1203
+ RightAws::AWSErrorHandler::reiteration_time = orig_reiteration_time if orig_reiteration_time
1204
+ Rightscale::HttpConnection::params = orig_http_params if orig_http_params
1205
+ end
1136
1206
 
1137
1207
  # Delete the specified snapshot.
1138
1208
  #
@@ -1527,8 +1597,9 @@ module RightAws
1527
1597
  end
1528
1598
  def tagend(name)
1529
1599
  case name
1530
- when 'zoneName' then @zone[:zone_name] = @text
1531
- when 'zoneState' then @zone[:zone_state] = @text
1600
+ when 'regionName' then @zone[:region_name] = @text
1601
+ when 'zoneName' then @zone[:zone_name] = @text
1602
+ when 'zoneState' then @zone[:zone_state] = @text
1532
1603
  when 'item' then @result << @zone
1533
1604
  end
1534
1605
  end
@@ -1537,6 +1608,19 @@ module RightAws
1537
1608
  end
1538
1609
  end
1539
1610
 
1611
+ #-----------------------------------------------------------------
1612
+ # PARSERS: Regions
1613
+ #-----------------------------------------------------------------
1614
+
1615
+ class QEc2DescribeRegionsParser < RightAWSParser #:nodoc:
1616
+ def tagend(name)
1617
+ @result << @text if name == 'regionName'
1618
+ end
1619
+ def reset
1620
+ @result = []
1621
+ end
1622
+ end
1623
+
1540
1624
  #-----------------------------------------------------------------
1541
1625
  # PARSERS: EBS - Volumes
1542
1626
  #-----------------------------------------------------------------
data/lib/right_aws.rb CHANGED
@@ -52,7 +52,7 @@ require 'acf/right_acf_interface'
52
52
  module RightAws #:nodoc:
53
53
  module VERSION #:nodoc:
54
54
  MAJOR = 1
55
- MINOR = 9
55
+ MINOR = 10
56
56
  TINY = 0
57
57
 
58
58
  STRING = [MAJOR, MINOR, TINY].join('.')
@@ -32,6 +32,7 @@ module RightAws
32
32
  DEFAULT_HOST = 's3.amazonaws.com'
33
33
  DEFAULT_PORT = 443
34
34
  DEFAULT_PROTOCOL = 'https'
35
+ DEFAULT_SERVICE = '/'
35
36
  REQUEST_TTL = 30
36
37
  DEFAULT_EXPIRES_AFTER = 1 * 24 * 60 * 60 # One day's worth of seconds
37
38
  ONE_YEAR_IN_SECONDS = 365 * 24 * 60 * 60
@@ -62,7 +63,8 @@ module RightAws
62
63
  def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
63
64
  init({ :name => 'S3',
64
65
  :default_host => ENV['S3_URL'] ? URI.parse(ENV['S3_URL']).host : DEFAULT_HOST,
65
- :default_port => ENV['S3_URL'] ? URI.parse(ENV['S3_URL']).port : DEFAULT_PORT,
66
+ :default_port => ENV['S3_URL'] ? URI.parse(ENV['S3_URL']).port : DEFAULT_PORT,
67
+ :default_service => ENV['S3_URL'] ? URI.parse(ENV['S3_URL']).path : DEFAULT_SERVICE,
66
68
  :default_protocol => ENV['S3_URL'] ? URI.parse(ENV['S3_URL']).scheme : DEFAULT_PROTOCOL },
67
69
  aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'],
68
70
  aws_secret_access_key || ENV['AWS_SECRET_ACCESS_KEY'],
@@ -108,29 +110,34 @@ module RightAws
108
110
  end
109
111
  true
110
112
  end
111
-
112
- # Generates request hash for REST API.
113
- # Assumes that headers[:url] is URL encoded (use CGI::escape)
114
- def generate_rest_request(method, headers) # :nodoc:
113
+
114
+ def fetch_request_params(headers) #:nodoc:
115
115
  # default server to use
116
- server = @params[:server]
117
- # fix path
118
- path_to_sign = headers[:url]
119
- path_to_sign = "/#{path_to_sign}" unless path_to_sign[/^\//]
116
+ server = @params[:server]
117
+ service = @params[:service].to_s
118
+ service.chop! if service[%r{/$}] # remove trailing '/' from service
120
119
  # extract bucket name and check it's dns compartibility
121
- path_to_sign[%r{^/([a-z0-9._-]*)(/[^?]*)?(\?.+)?}i]
120
+ headers[:url].to_s[%r{^([a-z0-9._-]*)(/[^?]*)?(\?.+)?}i]
122
121
  bucket_name, key_path, params_list = $1, $2, $3
123
122
  # select request model
124
123
  if is_dns_bucket?(bucket_name)
125
- # add backet to a server name
124
+ # fix a path
126
125
  server = "#{bucket_name}.#{server}"
127
- # remove bucket from the path
128
- path = "#{key_path || '/'}#{params_list}"
129
- # refactor the path (add '/' before params_list if the key is empty)
130
- path_to_sign = "/#{bucket_name}#{path}"
126
+ key_path ||= '/'
127
+ path = "#{service}#{key_path}#{params_list}"
131
128
  else
132
- path = path_to_sign
129
+ path = "#{service}/#{bucket_name}#{key_path}#{params_list}"
133
130
  end
131
+ path_to_sign = "#{service}/#{bucket_name}#{key_path}#{params_list}"
132
+ # path_to_sign = "/#{bucket_name}#{key_path}#{params_list}"
133
+ [ server, path, path_to_sign ]
134
+ end
135
+
136
+ # Generates request hash for REST API.
137
+ # Assumes that headers[:url] is URL encoded (use CGI::escape)
138
+ def generate_rest_request(method, headers) # :nodoc:
139
+ # calculate request data
140
+ server, path, path_to_sign = fetch_request_params(headers)
134
141
  data = headers[:data]
135
142
  # remove unset(==optional) and symbolyc keys
136
143
  headers.each{ |key, value| headers.delete(key) if (value.nil? || key.is_a?(Symbol)) }
@@ -808,26 +815,9 @@ module RightAws
808
815
 
809
816
  # Generates link for QUERY API
810
817
  def generate_link(method, headers={}, expires=nil) #:nodoc:
811
- # default server to use
812
- server = @params[:server]
813
- # fix path
814
- path_to_sign = headers[:url]
815
- path_to_sign = "/#{path_to_sign}" unless path_to_sign[/^\//]
816
- # extract bucket name and check it's dns compartibility
817
- path_to_sign[%r{^/([a-z0-9._-]*)(/[^?]*)?(\?.+)?}i]
818
- bucket_name, key_path, params_list = $1, $2, $3
819
- # select request model
820
- if is_dns_bucket?(bucket_name)
821
- # add backet to a server name
822
- server = "#{bucket_name}.#{server}"
823
- # remove bucket from the path
824
- path = "#{key_path || '/'}#{params_list}"
825
- # refactor the path (add '/' before params_list if the key is empty)
826
- path_to_sign = "/#{bucket_name}#{path}"
827
- else
828
- path = path_to_sign
829
- end
830
- # expiration time
818
+ # calculate request data
819
+ server, path, path_to_sign = fetch_request_params(headers)
820
+ # expiration time
831
821
  expires ||= DEFAULT_EXPIRES_AFTER
832
822
  expires = Time.now.utc + expires if expires.is_a?(Fixnum) && (expires < ONE_YEAR_IN_SECONDS)
833
823
  expires = expires.to_i
@@ -890,7 +880,7 @@ module RightAws
890
880
  # s3.put_link('my_awesome_bucket',key, object) #=> url string
891
881
  #
892
882
  def put_link(bucket, key, data=nil, expires=nil, headers={})
893
- generate_link('PUT', headers.merge(:url=>"#{bucket}/#{CGI::escape key}", :data=>data), expires)
883
+ generate_link('PUT', headers.merge(:url=>"#{bucket}/#{AwsUtils::URLencode key}", :data=>data), expires)
894
884
  rescue
895
885
  on_exception
896
886
  end
@@ -908,7 +898,7 @@ module RightAws
908
898
  #
909
899
  # see http://docs.amazonwebservices.com/AmazonS3/2006-03-01/VirtualHosting.html
910
900
  def get_link(bucket, key, expires=nil, headers={})
911
- generate_link('GET', headers.merge(:url=>"#{bucket}/#{CGI::escape key}"), expires)
901
+ generate_link('GET', headers.merge(:url=>"#{bucket}/#{AwsUtils::URLencode key}"), expires)
912
902
  rescue
913
903
  on_exception
914
904
  end
@@ -918,7 +908,7 @@ module RightAws
918
908
  # s3.head_link('my_awesome_bucket',key) #=> url string
919
909
  #
920
910
  def head_link(bucket, key, expires=nil, headers={})
921
- generate_link('HEAD', headers.merge(:url=>"#{bucket}/#{CGI::escape key}"), expires)
911
+ generate_link('HEAD', headers.merge(:url=>"#{bucket}/#{AwsUtils::URLencode key}"), expires)
922
912
  rescue
923
913
  on_exception
924
914
  end
@@ -928,7 +918,7 @@ module RightAws
928
918
  # s3.delete_link('my_awesome_bucket',key) #=> url string
929
919
  #
930
920
  def delete_link(bucket, key, expires=nil, headers={})
931
- generate_link('DELETE', headers.merge(:url=>"#{bucket}/#{CGI::escape key}"), expires)
921
+ generate_link('DELETE', headers.merge(:url=>"#{bucket}/#{AwsUtils::URLencode key}"), expires)
932
922
  rescue
933
923
  on_exception
934
924
  end
@@ -939,7 +929,7 @@ module RightAws
939
929
  # s3.get_acl_link('my_awesome_bucket',key) #=> url string
940
930
  #
941
931
  def get_acl_link(bucket, key='', headers={})
942
- return generate_link('GET', headers.merge(:url=>"#{bucket}/#{CGI::escape key}?acl"))
932
+ return generate_link('GET', headers.merge(:url=>"#{bucket}/#{AwsUtils::URLencode key}?acl"))
943
933
  rescue
944
934
  on_exception
945
935
  end
@@ -949,7 +939,7 @@ module RightAws
949
939
  # s3.put_acl_link('my_awesome_bucket',key) #=> url string
950
940
  #
951
941
  def put_acl_link(bucket, key='', headers={})
952
- return generate_link('PUT', headers.merge(:url=>"#{bucket}/#{CGI::escape key}?acl"))
942
+ return generate_link('PUT', headers.merge(:url=>"#{bucket}/#{AwsUtils::URLencode key}?acl"))
953
943
  rescue
954
944
  on_exception
955
945
  end