fog-aws 3.6.6 → 3.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.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +10 -0
- data/.github/workflows/ruby.yml +36 -0
- data/.github/workflows/stale.yml +23 -0
- data/CHANGELOG.md +169 -18
- data/Gemfile +1 -1
- data/README.md +21 -1
- data/fog-aws.gemspec +2 -2
- data/lib/fog/aws/credential_fetcher.rb +34 -3
- data/lib/fog/aws/models/compute/flavors.rb +460 -0
- data/lib/fog/aws/models/storage/file.rb +125 -3
- data/lib/fog/aws/models/storage/files.rb +32 -2
- data/lib/fog/aws/parsers/storage/upload_part_copy_object.rb +18 -0
- data/lib/fog/aws/requests/compute/request_spot_instances.rb +1 -1
- data/lib/fog/aws/requests/storage/delete_multiple_objects.rb +18 -8
- data/lib/fog/aws/requests/storage/shared_mock_methods.rb +1 -0
- data/lib/fog/aws/requests/storage/upload_part_copy.rb +119 -0
- data/lib/fog/aws/storage.rb +6 -3
- data/lib/fog/aws/version.rb +1 -1
- data/tests/credentials_tests.rb +42 -0
- data/tests/helpers/succeeds_helper.rb +2 -2
- data/tests/models/storage/directory_tests.rb +113 -2
- data/tests/models/storage/files_tests.rb +32 -0
- data/tests/requests/storage/bucket_tests.rb +1 -1
- data/tests/requests/storage/multipart_copy_tests.rb +93 -0
- data/tests/requests/storage/object_tests.rb +7 -0
- data/tests/requests/storage/versioning_tests.rb +38 -0
- metadata +18 -15
- data/.travis.yml +0 -53
- data/gemfiles/Gemfile-ruby-2.0 +0 -7
- data/stale.yml +0 -17
@@ -4,6 +4,10 @@ module Fog
|
|
4
4
|
module AWS
|
5
5
|
class Storage
|
6
6
|
class File < Fog::Model
|
7
|
+
MIN_MULTIPART_CHUNK_SIZE = 5242880
|
8
|
+
MAX_SINGLE_PUT_SIZE = 5368709120
|
9
|
+
MULTIPART_COPY_THRESHOLD = 15728640
|
10
|
+
|
7
11
|
# @see AWS Object docs http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectOps.html
|
8
12
|
|
9
13
|
identity :key, :aliases => 'Key'
|
@@ -27,14 +31,54 @@ module Fog
|
|
27
31
|
attribute :kms_key_id, :aliases => 'x-amz-server-side-encryption-aws-kms-key-id'
|
28
32
|
attribute :tags, :aliases => 'x-amz-tagging'
|
29
33
|
|
34
|
+
UploadPartData = Struct.new(:part_number, :upload_options, :etag)
|
35
|
+
|
36
|
+
class PartList
|
37
|
+
def initialize(parts = [])
|
38
|
+
@parts = parts
|
39
|
+
@mutex = Mutex.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def push(part)
|
43
|
+
@mutex.synchronize { @parts.push(part) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def shift
|
47
|
+
@mutex.synchronize { @parts.shift }
|
48
|
+
end
|
49
|
+
|
50
|
+
def clear!
|
51
|
+
@mutex.synchronize { @parts.clear }
|
52
|
+
end
|
53
|
+
|
54
|
+
def size
|
55
|
+
@mutex.synchronize { @parts.size }
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_a
|
59
|
+
@mutex.synchronize { @parts.dup }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
30
63
|
# @note Chunk size to use for multipart uploads.
|
31
64
|
# Use small chunk sizes to minimize memory. E.g. 5242880 = 5mb
|
32
65
|
attr_reader :multipart_chunk_size
|
33
66
|
def multipart_chunk_size=(mp_chunk_size)
|
34
|
-
raise ArgumentError.new("minimum multipart_chunk_size is
|
67
|
+
raise ArgumentError.new("minimum multipart_chunk_size is #{MIN_MULTIPART_CHUNK_SIZE}") if mp_chunk_size < MIN_MULTIPART_CHUNK_SIZE
|
35
68
|
@multipart_chunk_size = mp_chunk_size
|
36
69
|
end
|
37
70
|
|
71
|
+
# @note Number of threads used to copy files.
|
72
|
+
def concurrency=(concurrency)
|
73
|
+
raise ArgumentError.new('minimum concurrency is 1') if concurrency.to_i < 1
|
74
|
+
|
75
|
+
@concurrency = concurrency.to_i
|
76
|
+
end
|
77
|
+
|
78
|
+
def concurrency
|
79
|
+
@concurrency || 1
|
80
|
+
end
|
81
|
+
|
38
82
|
def acl
|
39
83
|
requires :directory, :key
|
40
84
|
service.get_object_acl(directory.key, key).body['AccessControlList']
|
@@ -99,7 +143,18 @@ module Fog
|
|
99
143
|
#
|
100
144
|
def copy(target_directory_key, target_file_key, options = {})
|
101
145
|
requires :directory, :key
|
102
|
-
|
146
|
+
|
147
|
+
# With a single PUT operation you can upload objects up to 5 GB in size. Automatically set MP for larger objects.
|
148
|
+
self.multipart_chunk_size = MIN_MULTIPART_CHUNK_SIZE * 2 if !multipart_chunk_size && self.content_length.to_i > MAX_SINGLE_PUT_SIZE
|
149
|
+
|
150
|
+
if multipart_chunk_size && self.content_length.to_i >= multipart_chunk_size
|
151
|
+
upload_part_options = options.select { |key, _| ALLOWED_UPLOAD_PART_OPTIONS.include?(key.to_sym) }
|
152
|
+
upload_part_options = upload_part_options.merge({ 'x-amz-copy-source' => "#{directory.key}/#{key}" })
|
153
|
+
multipart_copy(options, upload_part_options, target_directory_key, target_file_key)
|
154
|
+
else
|
155
|
+
service.copy_object(directory.key, key, target_directory_key, target_file_key, options)
|
156
|
+
end
|
157
|
+
|
103
158
|
target_directory = service.directories.new(:key => target_directory_key)
|
104
159
|
target_directory.files.head(target_file_key)
|
105
160
|
end
|
@@ -214,7 +269,7 @@ module Fog
|
|
214
269
|
options.merge!(encryption_headers)
|
215
270
|
|
216
271
|
# With a single PUT operation you can upload objects up to 5 GB in size. Automatically set MP for larger objects.
|
217
|
-
self.multipart_chunk_size =
|
272
|
+
self.multipart_chunk_size = MIN_MULTIPART_CHUNK_SIZE if !multipart_chunk_size && Fog::Storage.get_body_size(body) > MAX_SINGLE_PUT_SIZE
|
218
273
|
|
219
274
|
if multipart_chunk_size && Fog::Storage.get_body_size(body) >= multipart_chunk_size && body.respond_to?(:read)
|
220
275
|
data = multipart_save(options)
|
@@ -294,6 +349,30 @@ module Fog
|
|
294
349
|
service.complete_multipart_upload(directory.key, key, upload_id, part_tags)
|
295
350
|
end
|
296
351
|
|
352
|
+
def multipart_copy(options, upload_part_options, target_directory_key, target_file_key)
|
353
|
+
# Initiate the upload
|
354
|
+
res = service.initiate_multipart_upload(target_directory_key, target_file_key, options)
|
355
|
+
upload_id = res.body["UploadId"]
|
356
|
+
|
357
|
+
# Store ETags of upload parts
|
358
|
+
part_tags = []
|
359
|
+
pending = PartList.new(create_part_list(upload_part_options))
|
360
|
+
thread_count = self.concurrency
|
361
|
+
completed = PartList.new
|
362
|
+
errors = upload_in_threads(target_directory_key, target_file_key, upload_id, pending, completed, thread_count)
|
363
|
+
|
364
|
+
raise errors.first if errors.any?
|
365
|
+
|
366
|
+
part_tags = completed.to_a.sort_by { |part| part.part_number }.map(&:etag)
|
367
|
+
rescue => e
|
368
|
+
# Abort the upload & reraise
|
369
|
+
service.abort_multipart_upload(target_directory_key, target_file_key, upload_id) if upload_id
|
370
|
+
raise
|
371
|
+
else
|
372
|
+
# Complete the upload
|
373
|
+
service.complete_multipart_upload(target_directory_key, target_file_key, upload_id, part_tags)
|
374
|
+
end
|
375
|
+
|
297
376
|
def encryption_headers
|
298
377
|
if encryption && encryption_key
|
299
378
|
encryption_customer_key_headers
|
@@ -318,6 +397,49 @@ module Fog
|
|
318
397
|
'x-amz-server-side-encryption-customer-key-md5' => Base64.encode64(OpenSSL::Digest::MD5.digest(encryption_key.to_s)).chomp!
|
319
398
|
}
|
320
399
|
end
|
400
|
+
|
401
|
+
def create_part_list(upload_part_options)
|
402
|
+
current_pos = 0
|
403
|
+
count = 0
|
404
|
+
pending = []
|
405
|
+
|
406
|
+
while current_pos < self.content_length do
|
407
|
+
start_pos = current_pos
|
408
|
+
end_pos = [current_pos + self.multipart_chunk_size, self.content_length - 1].min
|
409
|
+
range = "bytes=#{start_pos}-#{end_pos}"
|
410
|
+
part_options = upload_part_options.dup
|
411
|
+
part_options['x-amz-copy-source-range'] = range
|
412
|
+
pending << UploadPartData.new(count + 1, part_options, nil)
|
413
|
+
count += 1
|
414
|
+
current_pos = end_pos + 1
|
415
|
+
end
|
416
|
+
|
417
|
+
pending
|
418
|
+
end
|
419
|
+
|
420
|
+
def upload_in_threads(target_directory_key, target_file_key, upload_id, pending, completed, thread_count)
|
421
|
+
threads = []
|
422
|
+
|
423
|
+
thread_count.times do
|
424
|
+
thread = Thread.new do
|
425
|
+
begin
|
426
|
+
while part = pending.shift
|
427
|
+
part_upload = service.upload_part_copy(target_directory_key, target_file_key, upload_id, part.part_number, part.upload_options)
|
428
|
+
part.etag = part_upload.body['ETag']
|
429
|
+
completed.push(part)
|
430
|
+
end
|
431
|
+
rescue => error
|
432
|
+
pending.clear!
|
433
|
+
error
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
thread.abort_on_exception = true
|
438
|
+
threads << thread
|
439
|
+
end
|
440
|
+
|
441
|
+
threads.map(&:value).compact
|
442
|
+
end
|
321
443
|
end
|
322
444
|
end
|
323
445
|
end
|
@@ -17,6 +17,15 @@ module Fog
|
|
17
17
|
|
18
18
|
model Fog::AWS::Storage::File
|
19
19
|
|
20
|
+
DASHED_HEADERS = %w(
|
21
|
+
Cache-Control
|
22
|
+
Content-Disposition
|
23
|
+
Content-Encoding
|
24
|
+
Content-Length
|
25
|
+
Content-MD5
|
26
|
+
Content-Type
|
27
|
+
).freeze
|
28
|
+
|
20
29
|
def all(options = {})
|
21
30
|
requires :directory
|
22
31
|
options = {
|
@@ -114,8 +123,29 @@ module Fog
|
|
114
123
|
end
|
115
124
|
|
116
125
|
def normalize_headers(data)
|
117
|
-
data.headers['Last-Modified'] = Time.parse(data
|
118
|
-
|
126
|
+
data.headers['Last-Modified'] = Time.parse(fetch_and_delete_header(data, 'Last-Modified'))
|
127
|
+
|
128
|
+
etag = fetch_and_delete_header(data, 'ETag').gsub('"','')
|
129
|
+
data.headers['ETag'] = etag
|
130
|
+
|
131
|
+
DASHED_HEADERS.each do |header|
|
132
|
+
value = fetch_and_delete_header(data, header)
|
133
|
+
data.headers[header] = value if value
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def fetch_and_delete_header(response, header)
|
140
|
+
value = response.get_header(header)
|
141
|
+
|
142
|
+
return unless value
|
143
|
+
|
144
|
+
response.headers.keys.each do |key|
|
145
|
+
response.headers.delete(key) if key.downcase == header.downcase
|
146
|
+
end
|
147
|
+
|
148
|
+
value
|
119
149
|
end
|
120
150
|
end
|
121
151
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Fog
|
2
|
+
module Parsers
|
3
|
+
module AWS
|
4
|
+
module Storage
|
5
|
+
class UploadPartCopyObject < Fog::Parsers::Base
|
6
|
+
def end_element(name)
|
7
|
+
case name
|
8
|
+
when 'ETag'
|
9
|
+
@response[name] = value.gsub('"', '')
|
10
|
+
when 'LastModified'
|
11
|
+
@response[name] = Time.parse(value)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -113,7 +113,7 @@ module Fog
|
|
113
113
|
raise Fog::AWS::Compute::Error.new(message)
|
114
114
|
end
|
115
115
|
|
116
|
-
if !image_id.match(/^ami-[a-f0-9]{8}$/)
|
116
|
+
if !image_id.match(/^ami-[a-f0-9]{8,17}$/)
|
117
117
|
message = "The image id '[#{image_id}]' does not exist"
|
118
118
|
raise Fog::AWS::Compute::NotFound.new(message)
|
119
119
|
end
|
@@ -36,13 +36,20 @@ module Fog
|
|
36
36
|
data << "<Quiet>true</Quiet>" if headers.delete(:quiet)
|
37
37
|
version_ids = headers.delete('versionId')
|
38
38
|
object_names.each do |object_name|
|
39
|
-
data << "<Object>"
|
40
|
-
data << "<Key>#{CGI.escapeHTML(object_name)}</Key>"
|
41
39
|
object_version = version_ids.nil? ? nil : version_ids[object_name]
|
42
40
|
if object_version
|
43
|
-
|
41
|
+
object_version = object_version.is_a?(String) ? [object_version] : object_version
|
42
|
+
object_version.each do |version_id|
|
43
|
+
data << "<Object>"
|
44
|
+
data << "<Key>#{CGI.escapeHTML(object_name)}</Key>"
|
45
|
+
data << "<VersionId>#{CGI.escapeHTML(version_id)}</VersionId>"
|
46
|
+
data << "</Object>"
|
47
|
+
end
|
48
|
+
else
|
49
|
+
data << "<Object>"
|
50
|
+
data << "<Key>#{CGI.escapeHTML(object_name)}</Key>"
|
51
|
+
data << "</Object>"
|
44
52
|
end
|
45
|
-
data << "</Object>"
|
46
53
|
end
|
47
54
|
data << "</Delete>"
|
48
55
|
|
@@ -72,10 +79,13 @@ module Fog
|
|
72
79
|
response.body = { 'DeleteResult' => [] }
|
73
80
|
version_ids = headers.delete('versionId')
|
74
81
|
object_names.each do |object_name|
|
75
|
-
object_version = version_ids.nil? ? nil : version_ids[object_name]
|
76
|
-
|
77
|
-
|
78
|
-
|
82
|
+
object_version = version_ids.nil? ? [nil] : version_ids[object_name]
|
83
|
+
object_version = object_version.is_a?(String) ? [object_version] : object_version
|
84
|
+
object_version.each do |version_id|
|
85
|
+
response.body['DeleteResult'] << delete_object_helper(bucket,
|
86
|
+
object_name,
|
87
|
+
version_id)
|
88
|
+
end
|
79
89
|
end
|
80
90
|
else
|
81
91
|
response.status = 404
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Fog
|
2
|
+
module AWS
|
3
|
+
class Storage
|
4
|
+
# From https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPartCopy.html
|
5
|
+
ALLOWED_UPLOAD_PART_OPTIONS = %i(
|
6
|
+
x-amz-copy-source
|
7
|
+
x-amz-copy-source-if-match
|
8
|
+
x-amz-copy-source-if-modified-since
|
9
|
+
x-amz-copy-source-if-none-match
|
10
|
+
x-amz-copy-source-if-unmodified-since
|
11
|
+
x-amz-copy-source-range
|
12
|
+
x-amz-copy-source-server-side-encryption-customer-algorithm
|
13
|
+
x-amz-copy-source-server-side-encryption-customer-key
|
14
|
+
x-amz-copy-source-server-side-encryption-customer-key-MD5
|
15
|
+
x-amz-expected-bucket-owner
|
16
|
+
x-amz-request-payer
|
17
|
+
x-amz-server-side-encryption-customer-algorithm
|
18
|
+
x-amz-server-side-encryption-customer-key
|
19
|
+
x-amz-server-side-encryption-customer-key-MD5
|
20
|
+
x-amz-source-expected-bucket-owner
|
21
|
+
).freeze
|
22
|
+
|
23
|
+
class Real
|
24
|
+
require 'fog/aws/parsers/storage/upload_part_copy_object'
|
25
|
+
|
26
|
+
# Upload a part for a multipart copy
|
27
|
+
#
|
28
|
+
# @param target_bucket_name [String] Name of bucket to create copy in
|
29
|
+
# @param target_object_name [String] Name for new copy of object
|
30
|
+
# @param upload_id [String] Id of upload to add part to
|
31
|
+
# @param part_number [String] Index of part in upload
|
32
|
+
# @param options [Hash]:
|
33
|
+
# @option options [String] x-amz-metadata-directive Specifies whether to copy metadata from source or replace with data in request. Must be in ['COPY', 'REPLACE']
|
34
|
+
# @option options [String] x-amz-copy_source-if-match Copies object if its etag matches this value
|
35
|
+
# @option options [Time] x-amz-copy_source-if-modified_since Copies object it it has been modified since this time
|
36
|
+
# @option options [String] x-amz-copy_source-if-none-match Copies object if its etag does not match this value
|
37
|
+
# @option options [Time] x-amz-copy_source-if-unmodified-since Copies object it it has not been modified since this time
|
38
|
+
# @option options [Time] x-amz-copy-source-range Specifes the range of bytes to copy from the source object
|
39
|
+
#
|
40
|
+
# @return [Excon::Response]
|
41
|
+
# * body [Hash]:
|
42
|
+
# * ETag [String] - etag of new object
|
43
|
+
# * LastModified [Time] - date object was last modified
|
44
|
+
#
|
45
|
+
# @see https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPartCopy.html
|
46
|
+
#
|
47
|
+
def upload_part_copy(target_bucket_name, target_object_name, upload_id, part_number, options = {})
|
48
|
+
headers = options
|
49
|
+
request({
|
50
|
+
:expects => 200,
|
51
|
+
:idempotent => true,
|
52
|
+
:headers => headers,
|
53
|
+
:bucket_name => target_bucket_name,
|
54
|
+
:object_name => target_object_name,
|
55
|
+
:method => 'PUT',
|
56
|
+
:query => {'uploadId' => upload_id, 'partNumber' => part_number},
|
57
|
+
:parser => Fog::Parsers::AWS::Storage::UploadPartCopyObject.new,
|
58
|
+
})
|
59
|
+
end
|
60
|
+
end # Real
|
61
|
+
|
62
|
+
class Mock # :nodoc:all
|
63
|
+
require 'fog/aws/requests/storage/shared_mock_methods'
|
64
|
+
include Fog::AWS::Storage::SharedMockMethods
|
65
|
+
|
66
|
+
def upload_part_copy(target_bucket_name, target_object_name, upload_id, part_number, options = {})
|
67
|
+
validate_options!(options)
|
68
|
+
|
69
|
+
copy_source = options['x-amz-copy-source']
|
70
|
+
copy_range = options['x-amz-copy-source-range']
|
71
|
+
|
72
|
+
raise 'No x-amz-copy-source header provided' unless copy_source
|
73
|
+
raise 'No x-amz-copy-source-range header provided' unless copy_range
|
74
|
+
|
75
|
+
source_bucket_name, source_object_name = copy_source.split('/', 2)
|
76
|
+
verify_mock_bucket_exists(source_bucket_name)
|
77
|
+
|
78
|
+
source_bucket = self.data[:buckets][source_bucket_name]
|
79
|
+
source_object = source_bucket && source_bucket[:objects][source_object_name] && source_bucket[:objects][source_object_name].first
|
80
|
+
upload_info = get_upload_info(target_bucket_name, upload_id)
|
81
|
+
|
82
|
+
response = Excon::Response.new
|
83
|
+
|
84
|
+
if source_object
|
85
|
+
start_pos, end_pos = byte_range(copy_range, source_object[:body].length)
|
86
|
+
upload_info[:parts][part_number] = source_object[:body][start_pos..end_pos]
|
87
|
+
|
88
|
+
response.status = 200
|
89
|
+
response.body = {
|
90
|
+
# just use the part number as the ETag, for simplicity
|
91
|
+
'ETag' => part_number.to_i,
|
92
|
+
'LastModified' => Time.parse(source_object['Last-Modified'])
|
93
|
+
}
|
94
|
+
response
|
95
|
+
else
|
96
|
+
response.status = 404
|
97
|
+
raise(Excon::Errors.status_error({:expects => 200}, response))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def byte_range(range, size)
|
102
|
+
matches = range.match(/bytes=(\d*)-(\d*)/)
|
103
|
+
|
104
|
+
return nil unless matches
|
105
|
+
|
106
|
+
end_pos = [matches[2].to_i, size].min
|
107
|
+
|
108
|
+
[matches[1].to_i, end_pos]
|
109
|
+
end
|
110
|
+
|
111
|
+
def validate_options!(options)
|
112
|
+
options.keys.each do |key|
|
113
|
+
raise "Invalid UploadPart option: #{key}" unless ::Fog::AWS::Storage::ALLOWED_UPLOAD_PART_OPTIONS.include?(key.to_sym)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end # Mock
|
117
|
+
end # Storage
|
118
|
+
end # AWS
|
119
|
+
end # Fog
|
data/lib/fog/aws/storage.rb
CHANGED
@@ -112,6 +112,7 @@ module Fog
|
|
112
112
|
request :put_request_payment
|
113
113
|
request :sync_clock
|
114
114
|
request :upload_part
|
115
|
+
request :upload_part_copy
|
115
116
|
|
116
117
|
module Utils
|
117
118
|
attr_accessor :region
|
@@ -227,7 +228,7 @@ module Fog
|
|
227
228
|
when %r{\Acn-.*}
|
228
229
|
"s3.#{region}.amazonaws.com.cn"
|
229
230
|
else
|
230
|
-
"s3
|
231
|
+
"s3.#{region}.amazonaws.com"
|
231
232
|
end
|
232
233
|
end
|
233
234
|
|
@@ -283,10 +284,10 @@ module Fog
|
|
283
284
|
path_style = params.fetch(:path_style, @path_style)
|
284
285
|
if !path_style
|
285
286
|
if COMPLIANT_BUCKET_NAMES !~ bucket_name
|
286
|
-
Fog::Logger.warning("fog: the specified s3 bucket name(#{bucket_name}) is not a valid dns name, which will negatively impact performance. For details see:
|
287
|
+
Fog::Logger.warning("fog: the specified s3 bucket name(#{bucket_name}) is not a valid dns name, which will negatively impact performance. For details see: https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html")
|
287
288
|
path_style = true
|
288
289
|
elsif scheme == 'https' && !path_style && bucket_name =~ /\./
|
289
|
-
Fog::Logger.warning("fog: the specified s3 bucket name(#{bucket_name}) contains a '.' so is not accessible over https as a virtual hosted bucket, which will negatively impact performance. For details see:
|
290
|
+
Fog::Logger.warning("fog: the specified s3 bucket name(#{bucket_name}) contains a '.' so is not accessible over https as a virtual hosted bucket, which will negatively impact performance. For details see: https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html")
|
290
291
|
path_style = true
|
291
292
|
end
|
292
293
|
end
|
@@ -297,6 +298,8 @@ module Fog
|
|
297
298
|
host = params.fetch(:cname, bucket_name)
|
298
299
|
elsif path_style
|
299
300
|
path = bucket_to_path bucket_name, path
|
301
|
+
elsif host.start_with?("#{bucket_name}.")
|
302
|
+
# no-op
|
300
303
|
else
|
301
304
|
host = [bucket_name, host].join('.')
|
302
305
|
end
|