Skip to content

Commit d68bb03

Browse files
authored
feat(storage): update s3 storage service to support multiple s3 client for multi-bucket support (#5493)
1 parent 0d4e492 commit d68bb03

File tree

3 files changed

+99
-19
lines changed

3 files changed

+99
-19
lines changed

packages/amplify_core/lib/src/types/storage/storage_bucket_from_outputs.dart

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,32 @@ class StorageBucketFromOutputs implements StorageBucket {
1717
assert(
1818
storageOutputs != null,
1919
const InvalidStorageBucketException(
20-
'Amplify Storage is not configured.',
20+
'Amplify Outputs file does not have storage configuration.',
2121
recoverySuggestion:
22-
'Make sure storage exists in the Amplify Outputs file.',
22+
'Make sure Amplify Storage is configured and the Amplify Outputs '
23+
'file has storage configuration.',
24+
),
25+
);
26+
final buckets = storageOutputs!.buckets;
27+
if (buckets == null) {
28+
throw const InvalidStorageBucketException(
29+
'Amplify Outputs storage configuration does not have buckets specified.',
30+
recoverySuggestion:
31+
'Make sure Amplify Outputs file has storage configuration with '
32+
'buckets specified.',
33+
);
34+
}
35+
final bucket = buckets.singleWhere(
36+
(e) => e.name == _name,
37+
orElse: () => throw const InvalidStorageBucketException(
38+
'Unable to lookup bucket from provided name in Amplify Outputs file.',
39+
recoverySuggestion: 'Make sure Amplify Outputs file has the specified '
40+
'bucket configuration.',
2341
),
2442
);
25-
// TODO(nikahsn): fix after adding buckets to StorageOutputs.
2643
return BucketInfo(
27-
bucketName: _name,
28-
region: storageOutputs!.awsRegion,
44+
bucketName: bucket.bucketName,
45+
region: bucket.awsRegion,
2946
);
3047
}
3148
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import 'package:amplify_storage_s3_dart/src/sdk/src/s3/s3_client.dart';
2+
import 'package:meta/meta.dart';
3+
import 'package:smithy_aws/smithy_aws.dart';
4+
5+
/// It holds Amazon S3 client information.
6+
@internal
7+
class S3ClientInfo {
8+
const S3ClientInfo({required this.client, required this.config});
9+
10+
final S3Client client;
11+
final S3ClientConfig config;
12+
}

packages/storage/amplify_storage_s3_dart/lib/src/storage_s3_service/service/storage_s3_service_impl.dart

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'package:amplify_storage_s3_dart/src/path_resolver/s3_path_resolver.dart'
1414
import 'package:amplify_storage_s3_dart/src/sdk/s3.dart' as s3;
1515
import 'package:amplify_storage_s3_dart/src/sdk/src/s3/common/endpoint_resolver.dart'
1616
as endpoint_resolver;
17+
import 'package:amplify_storage_s3_dart/src/storage_s3_service/service/s3_client_info.dart';
1718
import 'package:amplify_storage_s3_dart/src/storage_s3_service/storage_s3_service.dart';
1819
import 'package:amplify_storage_s3_dart/src/storage_s3_service/transfer/transfer.dart'
1920
as transfer;
@@ -84,10 +85,8 @@ class StorageS3Service {
8485
..supportedProtocols = SupportedProtocols.http1,
8586
),
8687
_pathResolver = pathResolver,
88+
_credentialsProvider = credentialsProvider,
8789
_logger = logger,
88-
// dependencyManager.get() => sigv4.AWSSigV4Signer is used for unit tests
89-
_awsSigV4Signer = dependencyManager.get() ??
90-
sigv4.AWSSigV4Signer(credentialsProvider: credentialsProvider),
9190
_dependencyManager = dependencyManager,
9291
_serviceStartingTime = DateTime.now();
9392

@@ -101,14 +100,10 @@ class StorageS3Service {
101100
final s3.S3Client _defaultS3Client;
102101
final S3PathResolver _pathResolver;
103102
final AWSLogger _logger;
104-
final sigv4.AWSSigV4Signer _awsSigV4Signer;
105103
final DependencyManager _dependencyManager;
106104
final DateTime _serviceStartingTime;
107-
108-
sigv4.AWSCredentialScope get _signerScope => sigv4.AWSCredentialScope(
109-
region: _storageOutputs.awsRegion,
110-
service: AWSService.s3,
111-
);
105+
final AWSIamAmplifyAuthProvider _credentialsProvider;
106+
final Map<String, S3ClientInfo> _s3ClientsInfo = {};
112107

113108
transfer.TransferDatabase get _transferDatabase =>
114109
_dependencyManager.getOrCreate();
@@ -261,10 +256,20 @@ class StorageS3Service {
261256
path: '/$resolvedPath',
262257
);
263258

259+
// dependencyManager.get<sigv4.AWSSigV4Signer>() is used for unit tests
260+
final awsSigV4Signer = _dependencyManager.get<sigv4.AWSSigV4Signer>() ??
261+
sigv4.AWSSigV4Signer(
262+
credentialsProvider: _credentialsProvider,
263+
);
264+
final signerScope = sigv4.AWSCredentialScope(
265+
region: _storageOutputs.awsRegion,
266+
service: AWSService.s3,
267+
);
268+
264269
return S3GetUrlResult(
265-
url: await _awsSigV4Signer.presign(
270+
url: await awsSigV4Signer.presign(
266271
urlRequest,
267-
credentialScope: _signerScope,
272+
credentialScope: signerScope,
268273
expiresIn: s3PluginOptions.expiresIn,
269274
serviceConfiguration: _defaultS3SignerConfiguration,
270275
),
@@ -323,12 +328,17 @@ class StorageS3Service {
323328
void Function(S3TransferProgress)? onProgress,
324329
FutureOr<void> Function()? onDone,
325330
FutureOr<void> Function()? onError,
331+
StorageBucket? bucket,
326332
}) {
333+
// ignore: invalid_use_of_internal_member
334+
final bucketName = bucket?.resolveBucketInfo(_storageOutputs).bucketName ??
335+
_storageOutputs.bucketName;
336+
final s3ClientInfo = _getS3ClientInfo(bucket);
327337
final uploadDataTask = S3UploadTask.fromDataPayload(
328338
dataPayload,
329-
s3Client: _defaultS3Client,
330-
defaultS3ClientConfig: _defaultS3ClientConfig,
331-
bucket: _storageOutputs.bucketName,
339+
s3Client: s3ClientInfo.client,
340+
defaultS3ClientConfig: s3ClientInfo.config,
341+
bucket: bucketName,
332342
path: path,
333343
options: options,
334344
pathResolver: _pathResolver,
@@ -611,4 +621,45 @@ class StorageS3Service {
611621
}
612622
}
613623
}
624+
625+
S3ClientInfo _getS3ClientInfo(StorageBucket? storageBucket) {
626+
if (storageBucket == null) {
627+
return S3ClientInfo(
628+
client: _defaultS3Client,
629+
config: _defaultS3ClientConfig,
630+
);
631+
}
632+
// ignore: invalid_use_of_internal_member
633+
final bucketInfo = storageBucket.resolveBucketInfo(_storageOutputs);
634+
if (_s3ClientsInfo[bucketInfo.bucketName] != null) {
635+
return _s3ClientsInfo[bucketInfo.bucketName]!;
636+
}
637+
638+
final usePathStyle = bucketInfo.bucketName.contains('.');
639+
if (usePathStyle) {
640+
_logger.warn(
641+
'Since your bucket name contains dots (`"."`), the StorageS3 plugin'
642+
' will use path style URLs to communicate with the S3 service. S3'
643+
' Transfer acceleration is not supported for path style URLs. For more'
644+
' information, refer to:'
645+
' https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html');
646+
}
647+
final s3ClientConfig = smithy_aws.S3ClientConfig(
648+
signerConfiguration: _defaultS3SignerConfiguration,
649+
usePathStyle: usePathStyle,
650+
);
651+
final s3Client = s3.S3Client(
652+
region: bucketInfo.region,
653+
credentialsProvider: _credentialsProvider,
654+
s3ClientConfig: s3ClientConfig,
655+
client: AmplifyHttpClient(_dependencyManager)
656+
..supportedProtocols = SupportedProtocols.http1,
657+
);
658+
final s3ClientInfo = S3ClientInfo(
659+
client: s3Client,
660+
config: s3ClientConfig,
661+
);
662+
_s3ClientsInfo[bucketInfo.bucketName] = s3ClientInfo;
663+
return s3ClientInfo;
664+
}
614665
}

0 commit comments

Comments
 (0)