Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(storage) Parity: AWS SDK S3 accelerate via useAccelerateEndpoint pluginOptions #3099

Merged
merged 1 commit into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@ extension AWSS3StoragePlugin {
if let pluginOptions = options.pluginOptions as? AWSStorageGetURLOptions, pluginOptions.validateObjectExistence {
try await storageService.validateObjectExistence(serviceKey: serviceKey)
}
let result = try await storageService.getPreSignedURL(serviceKey: serviceKey,
signingOperation: .getObject,
expires: options.expires)
let accelerate = try AWSS3PluginOptions.accelerateValue(
pluginOptions: options.pluginOptions)
let result = try await storageService.getPreSignedURL(
serviceKey: serviceKey,
signingOperation: .getObject,
accelerate: accelerate,
expires: options.expires)

let channel = HubChannel(from: categoryType)
let payload = HubPayload(eventName: HubPayload.EventName.Storage.getURL, context: options, data: result)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Amplify
import Foundation

/// - Tag: AWSS3PluginOptions
struct AWSS3PluginOptions {

/// - Tag: AWSS3PluginOptionsCodingKeys
enum CodingKeys: String, CodingKey {

/// See: https://docs.amplify.aws/lib/storage/transfer-acceleration/q/platform/js/
/// - Tag: AWSS3PluginOptionsCodingKeys.useAccelerateEndpoint
case useAccelerateEndpoint
}

/// Attempts to extract the boolean under the
/// [useAccelerateEndpoint](x-source-tag://AWSS3PluginOptionsCodingKeys.useAccelerateEndpoint)
/// contained in the given dictionary.
///
/// In other words, a non-nil boolean is returned if:
///
/// * The `pluginOptions` parameter is a dictionary ([String: Any])
/// * The `pluginOptions` dictionary contains a boolean key under the [useAccelerateEndpoint](x-source-tag://AWSS3PluginOptionsCodingKeys.useAccelerateEndpoint) key.
///
/// - Tag: AWSS3PluginOptions.accelerateValue
static func accelerateValue(pluginOptions: Any?) throws -> Bool? {
guard let pluginOptions = pluginOptions as? [String:Any] else {
return nil
}
guard let value = pluginOptions[CodingKeys.useAccelerateEndpoint.rawValue] else {
return nil
}
guard let boolValue = value as? Bool else {
throw StorageError.validation(CodingKeys.useAccelerateEndpoint.rawValue,
"Expecting boolean value for key \(CodingKeys.useAccelerateEndpoint.rawValue)",
"Ensure the value associated with \(CodingKeys.useAccelerateEndpoint.rawValue) is a boolean",
nil)
}
return boolValue
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ class AWSS3PreSignedURLBuilderAdapter: AWSS3PreSignedURLBuilderBehavior {
/// - Returns: Pre-Signed URL
func getPreSignedURL(key: String,
signingOperation: AWSS3SigningOperation,
accelerate: Bool? = nil,
expires: Int64? = nil) async throws -> URL {
let expiresDate = Date(timeIntervalSinceNow: Double(expires ?? defaultExpiration))
let expiration = expiresDate.timeIntervalSinceNow
let config = (accelerate == nil) ? self.config : S3ClientConfigurationProxy(
target: self.config,
accelerateOverride: accelerate)
let preSignedUrl: URL?
switch signingOperation {
case .getObject:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,36 @@ import AWSS3
import ClientRuntime
import AWSClientRuntime

/// - Tag: AWSS3PreSignedURLBuilderError
enum AWSS3PreSignedURLBuilderError: Error {

/// Returned by an implementation of a
/// [AWSS3PreSignedURLBuilderBehavior](x-source-tag://AWSS3PreSignedURLBuilderBehavior)
///
/// - Tag: AWSS3PreSignedURLBuilderError.failed
case failed(reason: String, error: Error?)
}

// Behavior that the implemenation class for AWSS3PreSignedURLBuilder will use.
/// Behavior that the implemenation class for AWSS3PreSignedURLBuilder will use.
///
/// - Tag: AWSS3PreSignedURLBuilderBehavior
protocol AWSS3PreSignedURLBuilderBehavior {

/// Gets a pre-signed URL.
/// Attempts to generate a pre-signed URL.
///
/// - Parameters:
/// - key: String represnting the key of an S3 object.
/// - signingOperation: [AWSS3SigningOperation](x-source-tag://AWSS3SigningOperation)
/// (get, put, upload part) for which the URL will be generated.
/// - accelerate: Optional boolean indicating wether or not to enable S3 bucket
/// [transfer acceleration](https://docs.amplify.aws/lib/storage/transfer-acceleration/q/platform/js/)
/// - expires: Int64 indicating the expiration as the number of milliseconds since the 1970 epoc.
/// - Returns: Pre-Signed URL
func getPreSignedURL(key: String, signingOperation: AWSS3SigningOperation, expires: Int64?) async throws -> URL
///
/// - Tag: AWSS3PreSignedURLBuilderBehavior.getPreSignedURL
func getPreSignedURL(key: String,
signingOperation: AWSS3SigningOperation,
accelerate: Bool?,
expires: Int64?) async throws -> URL

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0

import AWSS3
import AWSClientRuntime
import ClientRuntime
import Foundation

/// Convenience proxy class around a
/// [S3ClientConfigurationProtocol](x-source-tag://S3ClientConfigurationProtocol)
/// implementaitons that allows Amplify to change configuration values JIT.
///
/// - Tag: S3ClientConfigurationProxy
struct S3ClientConfigurationProxy {

/// - Tag: S3ClientConfigurationProxy.target
var target: S3ClientConfigurationProtocol

/// - Tag: S3ClientConfigurationProxy.accelerateOverride
var accelerateOverride: Bool?
}

extension S3ClientConfigurationProxy: S3ClientConfigurationProtocol {

var accelerate: Bool? {
if let accelerateOverride = accelerateOverride {
return accelerateOverride
}
return target.accelerate
}

var disableMultiRegionAccessPoints: Bool? {
return target.disableMultiRegionAccessPoints
}

var endpointResolver: EndpointResolver {
return target.endpointResolver
}

var forcePathStyle: Bool? {
return target.forcePathStyle
}

var useArnRegion: Bool? {
return target.useArnRegion
}

var useGlobalEndpoint: Bool? {
return target.useGlobalEndpoint
}

var credentialsProvider: AWSClientRuntime.CredentialsProvider {
get {
return target.credentialsProvider
}
set(newValue) {
target.credentialsProvider = newValue
}
}

var region: String? {
get {
return target.region
}
set(newValue) {
target.region = newValue
}
}

var signingRegion: String? {
get {
return target.signingRegion
}
set(newValue) {
target.signingRegion = newValue
}
}

var regionResolver: RegionResolver? {
get {
return target.regionResolver
}
set(newValue) {
target.regionResolver = newValue
}
}

var frameworkMetadata: FrameworkMetadata? {
get {
return target.frameworkMetadata
}
set(newValue) {
target.frameworkMetadata = newValue
}
}

var useFIPS: Bool? {
get {
return target.useFIPS
}
set(newValue) {
target.useFIPS = newValue
}
}

var useDualStack: Bool? {
get {
return target.useDualStack
}
set(newValue) {
target.useDualStack = newValue
}
}

var logger: LogAgent {
return target.logger
}

var retryer: ClientRuntime.SDKRetryer {
return target.retryer
}

var endpoint: String? {
get {
return target.endpoint
}
set(newValue) {
target.endpoint = newValue
}
}

var encoder: ClientRuntime.RequestEncoder? {
return target.encoder
}

var decoder: ClientRuntime.ResponseDecoder? {
return target.decoder
}

var httpClientEngine: ClientRuntime.HttpClientEngine {
return target.httpClientEngine
}

var httpClientConfiguration: ClientRuntime.HttpClientConfiguration {
return target.httpClientConfiguration
}

var idempotencyTokenGenerator: ClientRuntime.IdempotencyTokenGenerator {
return target.idempotencyTokenGenerator
}

var clientLogMode: ClientRuntime.ClientLogMode {
return target.clientLogMode
}

var partitionID: String? {
return target.partitionID
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ class AWSS3StorageDownloadDataOperation: AmplifyInProcessReportingOperation<
do {
let prefix = try await prefixResolver.resolvePrefix(for: request.options.accessLevel, targetIdentityId: request.options.targetIdentityId)
let serviceKey = prefix + request.key
storageService.download(serviceKey: serviceKey, fileURL: nil) { [weak self] event in
let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions)
storageService.download(serviceKey: serviceKey, fileURL: nil, accelerate: accelerate) { [weak self] event in
self?.onServiceEvent(event: event)
}
} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ class AWSS3StorageDownloadFileOperation: AmplifyInProcessReportingOperation<
do {
let prefix = try await prefixResolver.resolvePrefix(for: request.options.accessLevel, targetIdentityId: request.options.targetIdentityId)
let serviceKey = prefix + request.key
storageService.download(serviceKey: serviceKey, fileURL: self.request.local) { [weak self] event in
let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions)
storageService.download(serviceKey: serviceKey, fileURL: self.request.local, accelerate: accelerate) { [weak self] event in
self?.onServiceEvent(event: event)
}
} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,21 @@ class AWSS3StorageUploadDataOperation: AmplifyInProcessReportingOperation<
let prefix = try await prefixResolver.resolvePrefix(for: request.options.accessLevel, targetIdentityId: request.options.targetIdentityId)
let serviceKey = prefix + request.key
let serviceMetadata = StorageRequestUtils.getServiceMetadata(request.options.metadata)
let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions)
if request.data.count > StorageUploadDataRequest.Options.multiPartUploadSizeThreshold {
storageService.multiPartUpload(serviceKey: serviceKey,
uploadSource: .data(request.data),
contentType: request.options.contentType,
metadata: serviceMetadata) { [weak self] event in
metadata: serviceMetadata,
accelerate: accelerate) { [weak self] event in
self?.onServiceEvent(event: event)
}
} else {
storageService.upload(serviceKey: serviceKey,
uploadSource: .data(request.data),
contentType: request.options.contentType,
metadata: serviceMetadata) { [weak self] event in
metadata: serviceMetadata,
accelerate: accelerate) { [weak self] event in
self?.onServiceEvent(event: event)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,18 +116,21 @@ class AWSS3StorageUploadFileOperation: AmplifyInProcessReportingOperation<
let prefix = try await prefixResolver.resolvePrefix(for: request.options.accessLevel, targetIdentityId: request.options.targetIdentityId)
let serviceKey = prefix + request.key
let serviceMetadata = StorageRequestUtils.getServiceMetadata(request.options.metadata)
let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions)
if uploadSize > StorageUploadFileRequest.Options.multiPartUploadSizeThreshold {
storageService.multiPartUpload(serviceKey: serviceKey,
uploadSource: .local(request.local),
contentType: request.options.contentType,
metadata: serviceMetadata) { [weak self] event in
metadata: serviceMetadata,
accelerate: accelerate) { [weak self] event in
self?.onServiceEvent(event: event)
}
} else {
storageService.upload(serviceKey: serviceKey,
uploadSource: .local(request.local),
contentType: request.options.contentType,
metadata: serviceMetadata) { [weak self] event in
metadata: serviceMetadata,
accelerate: accelerate) { [weak self] event in
self?.onServiceEvent(event: event)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ extension AWSS3StorageService {

func download(serviceKey: String,
fileURL: URL?,
onEvent: @escaping StorageServiceDownloadEventHandler) {
accelerate: Bool?,
onEvent: @escaping StorageServiceDownloadEventHandler) {
let fail: (Error) -> Void = { error in
let storageError = StorageError(error: error)
onEvent(.failed(storageError))
Expand All @@ -27,7 +28,10 @@ extension AWSS3StorageService {

Task {
do {
let preSignedURL = try await preSignedURLBuilder.getPreSignedURL(key: serviceKey, signingOperation: .getObject, expires: nil)
let preSignedURL = try await preSignedURLBuilder.getPreSignedURL(key: serviceKey,
signingOperation: .getObject,
accelerate: accelerate,
expires: nil)
startDownload(preSignedURL: preSignedURL, transferTask: transferTask)
} catch {
onEvent(.failed(StorageError.unknown("Failed to get pre-signed URL", nil)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ extension AWSS3StorageService {

func getPreSignedURL(serviceKey: String,
signingOperation: AWSS3SigningOperation,
accelerate: Bool?,
expires: Int) async throws -> URL {
return try await preSignedURLBuilder.getPreSignedURL(key: serviceKey, signingOperation: signingOperation, expires: Int64(expires))
return try await preSignedURLBuilder.getPreSignedURL(
key: serviceKey,
signingOperation: signingOperation,
accelerate: accelerate,
expires: Int64(expires))
}

func validateObjectExistence(serviceKey: String) async throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ extension AWSS3StorageService {
uploadSource: UploadSource,
contentType: String?,
metadata: [String: String]?,
accelerate: Bool?,
onEvent: @escaping StorageServiceMultiPartUploadEventHandler) {
let fail: (Error) -> Void = { error in
let storageError = StorageError(error: error)
Expand Down
Loading