mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(s3): support virtual-hosted-style URLs for S3 storages
Add a "Use path-style URLs" setting to S3 storages. It is detected from the endpoint (Alibaba OSS, Tencent COS, Volcengine/BytePlus TOS), and the connection test switches to virtual-hosted style when the provider rejects path-style requests. Backups pass `--path off` to mc. Closes #10347
This commit is contained in:
@@ -62,6 +62,7 @@ test('S3Storage model fillable attributes are configured correctly', function ()
|
||||
'secret',
|
||||
'bucket',
|
||||
'endpoint',
|
||||
'use_path_style_endpoint',
|
||||
'is_usable',
|
||||
'unusable_email_sent',
|
||||
]);
|
||||
@@ -97,6 +98,88 @@ test('S3Storage connection validation uses short s3 client timeouts', function (
|
||||
expect($s3Storage->is_usable)->toBeTrue();
|
||||
});
|
||||
|
||||
test('S3Storage uses path-style URLs unless the storage turns them off', function (?bool $setting, bool $expected, string $mcOption) {
|
||||
$disk = Mockery::mock(FilesystemAdapter::class);
|
||||
Storage::expects('build')
|
||||
->once()
|
||||
->with(Mockery::on(fn (array $config) => $config['use_path_style_endpoint'] === $expected))
|
||||
->andReturn($disk);
|
||||
|
||||
$s3Storage = new S3Storage;
|
||||
$s3Storage->setRawAttributes([
|
||||
'region' => 'oss-ap-southeast-1',
|
||||
'bucket' => 'backups',
|
||||
'endpoint' => 'https://s3.oss-ap-southeast-1.aliyuncs.com',
|
||||
'use_path_style_endpoint' => $setting,
|
||||
]);
|
||||
|
||||
expect($s3Storage->filesystem())->toBe($disk)
|
||||
->and($s3Storage->minioClientPathOption())->toBe($mcOption);
|
||||
})->with([
|
||||
'not saved yet' => [null, true, ''],
|
||||
'path-style' => [true, true, ''],
|
||||
'virtual-hosted-style' => [false, false, ' --path off'],
|
||||
]);
|
||||
|
||||
test('S3Storage detects the URL style from the endpoint host', function (?string $endpoint, bool $usesPathStyle) {
|
||||
expect(S3Storage::detectPathStyleEndpoint($endpoint))->toBe($usesPathStyle);
|
||||
})->with([
|
||||
'Alibaba Cloud OSS' => ['https://s3.oss-ap-southeast-1-internal.aliyuncs.com', false],
|
||||
'Tencent Cloud COS' => ['https://cos.ap-guangzhou.myqcloud.com', false],
|
||||
'Volcengine TOS' => ['https://tos-s3-cn-beijing.volces.com', false],
|
||||
'BytePlus TOS' => ['https://tos-s3-ap-southeast-1.bytepluses.com', false],
|
||||
'AWS' => ['https://s3.us-east-1.amazonaws.com', true],
|
||||
'look-alike host' => ['https://notaliyuncs.com', true],
|
||||
'MinIO by IP' => ['http://192.168.1.50:9000', true],
|
||||
'empty' => [null, true],
|
||||
]);
|
||||
|
||||
test('S3Storage connection validation switches to virtual-hosted-style URLs when the provider rejects path-style URLs', function () {
|
||||
$pathStyleDisk = Mockery::mock(FilesystemAdapter::class);
|
||||
$pathStyleDisk->expects('files')->once()->andThrow(new RuntimeException(
|
||||
'Unable to list contents',
|
||||
previous: new RuntimeException('AWS HTTP error: SecondLevelDomainForbidden (client): Please use virtual hosted style to access.'),
|
||||
));
|
||||
$virtualHostedDisk = Mockery::mock(FilesystemAdapter::class);
|
||||
$virtualHostedDisk->expects('files')->once()->andReturn([]);
|
||||
Storage::expects('build')->with(Mockery::on(fn (array $config) => $config['use_path_style_endpoint'] === true))->once()->andReturn($pathStyleDisk);
|
||||
Storage::expects('build')->with(Mockery::on(fn (array $config) => $config['use_path_style_endpoint'] === false))->once()->andReturn($virtualHostedDisk);
|
||||
|
||||
$s3Storage = new S3Storage;
|
||||
$s3Storage->setRawAttributes(['region' => 'us-east-1', 'bucket' => 'backups', 'endpoint' => 'https://s3.amazonaws.com']);
|
||||
|
||||
$s3Storage->testConnection();
|
||||
|
||||
expect($s3Storage->is_usable)->toBeTrue()
|
||||
->and($s3Storage->usesPathStyleEndpoint())->toBeFalse();
|
||||
});
|
||||
|
||||
test('S3Storage connection validation keeps path-style URLs when the virtual-hosted-style test also fails', function () {
|
||||
$pathStyleError = new RuntimeException('AWS HTTP error: PathStyleDomainForbidden (client): use a virtual-styled domain');
|
||||
$disk = Mockery::mock(FilesystemAdapter::class);
|
||||
$disk->expects('files')->twice()->andThrow($pathStyleError, new RuntimeException('AccessDenied'));
|
||||
Storage::expects('build')->twice()->andReturn($disk);
|
||||
|
||||
$s3Storage = new S3Storage;
|
||||
$s3Storage->setRawAttributes(['region' => 'us-east-1', 'bucket' => 'backups', 'endpoint' => 'https://s3.amazonaws.com']);
|
||||
|
||||
expect(fn () => $s3Storage->testConnection())->toThrow(RuntimeException::class, 'PathStyleDomainForbidden')
|
||||
->and($s3Storage->usesPathStyleEndpoint())->toBeTrue()
|
||||
->and($s3Storage->is_usable)->toBeFalse();
|
||||
});
|
||||
|
||||
test('S3Storage connection validation does not retry other errors', function () {
|
||||
$disk = Mockery::mock(FilesystemAdapter::class);
|
||||
$disk->expects('files')->once()->andThrow(new RuntimeException('AccessDenied'));
|
||||
Storage::expects('build')->once()->andReturn($disk);
|
||||
|
||||
$s3Storage = new S3Storage;
|
||||
$s3Storage->setRawAttributes(['region' => 'us-east-1', 'bucket' => 'backups', 'endpoint' => 'https://s3.amazonaws.com']);
|
||||
|
||||
expect(fn () => $s3Storage->testConnection())->toThrow(RuntimeException::class, 'AccessDenied')
|
||||
->and($s3Storage->usesPathStyleEndpoint())->toBeTrue();
|
||||
});
|
||||
|
||||
test('S3Storage connection validation returns friendly timeout error', function () {
|
||||
$disk = Mockery::mock(FilesystemAdapter::class);
|
||||
$disk->expects('files')
|
||||
|
||||
Reference in New Issue
Block a user