-
Notifications
You must be signed in to change notification settings - Fork 80
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: Account ID-based endpoints #1841
base: main
Are you sure you want to change the base?
Changes from 15 commits
c8288c0
09f8729
3ee4347
c1315df
6aa9a90
cada066
f85862e
93e0634
adea19b
34769a4
c8aec20
b3c0987
b632492
c1543cd
8c5c3e5
82da91b
dd08cd2
095dd5b
d1908b3
6e4ef4d
1952b95
3f2fb2a
73a21dc
49202bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
// | ||
// Copyright Amazon.com Inc. or its affiliates. | ||
// All Rights Reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
import XCTest | ||
import AWSDynamoDB | ||
import SmithyTestUtil | ||
import SmithyIdentity | ||
import enum AWSClientRuntime.AccountIDEndpointMode | ||
import enum ClientRuntime.EndpointError | ||
|
||
final class AccountIDEndpointModeTests: XCTestCase { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an integration test (although it doesn't actually hit the service.) Account ID & endpoint mode are passed into the service client, while a dummy HTTP client (the same one used for protocol tests) is used to inspect the resulting HTTP request. |
||
private let accountID = "0123456789" | ||
|
||
// MARK: - Tests | ||
|
||
// MARK: nil | ||
|
||
func test_nilMode_prefersByDefault() async throws { | ||
let subject = try await subject(accountIDEndpointMode: nil, setAccountID: true) | ||
|
||
do { | ||
_ = try await subject.getItem(input: GetItemInput()) | ||
XCTFail("Request should have thrown") | ||
} catch TestCheckError.actual(let request) { | ||
XCTAssert(request.endpoint.host.contains(accountID)) | ||
} catch { | ||
XCTFail("Request should have succeeded, threw error: \(error)") | ||
} | ||
} | ||
|
||
func test_nilMode_createsEndpointWithoutAccountID() async throws { | ||
let subject = try await subject(accountIDEndpointMode: nil, setAccountID: false) | ||
|
||
do { | ||
_ = try await subject.getItem(input: GetItemInput()) | ||
XCTFail("Request should have thrown") | ||
} catch TestCheckError.actual(let request) { | ||
XCTAssertFalse(request.endpoint.host.contains(accountID)) | ||
} catch { | ||
XCTFail("Request failed on unexpected error") | ||
} | ||
} | ||
|
||
// MARK: preferred | ||
|
||
func test_preferredMode_prefersByDefault() async throws { | ||
let subject = try await subject(accountIDEndpointMode: .preferred, setAccountID: true) | ||
|
||
do { | ||
_ = try await subject.getItem(input: GetItemInput()) | ||
XCTFail("Request should have thrown") | ||
} catch TestCheckError.actual(let request) { | ||
XCTAssert(request.endpoint.host.contains(accountID)) | ||
} catch { | ||
XCTFail("Request should have succeeded, threw error: \(error)") | ||
} | ||
} | ||
|
||
func test_preferredMode_createsEndpointWithoutAccountID() async throws { | ||
let subject = try await subject(accountIDEndpointMode: .preferred, setAccountID: false) | ||
|
||
do { | ||
_ = try await subject.getItem(input: GetItemInput()) | ||
XCTFail("Request should have thrown") | ||
} catch TestCheckError.actual(let request) { | ||
XCTAssertFalse(request.endpoint.host.contains(accountID)) | ||
} catch { | ||
XCTFail("Request failed on unexpected error") | ||
} | ||
} | ||
|
||
// MARK: required | ||
|
||
func test_requiredMode_createsEndpointWithAccountID() async throws { | ||
let subject = try await subject(accountIDEndpointMode: .required, setAccountID: true) | ||
|
||
do { | ||
_ = try await subject.getItem(input: GetItemInput()) | ||
XCTFail("Request should have thrown") | ||
} catch TestCheckError.actual(let request) { | ||
XCTAssert(request.endpoint.host.contains(accountID)) | ||
} catch { | ||
XCTFail("Request should have succeeded, threw error: \(error)") | ||
} | ||
} | ||
|
||
func test_requiredMode_failsWhenRequiredButNotPresent() async throws { | ||
let subject = try await subject(accountIDEndpointMode: .required, setAccountID: false) | ||
|
||
do { | ||
_ = try await subject.getItem(input: GetItemInput()) | ||
XCTFail("Request should have thrown") | ||
} catch EndpointError.unresolved { | ||
// No action, test succeeded | ||
} catch { | ||
XCTFail("Request failed on unexpected error") | ||
} | ||
} | ||
|
||
// MARK: disabled | ||
|
||
func test_disabledMode_createsEndpointWithoutAccountIDWhenNil() async throws { | ||
let subject = try await subject(accountIDEndpointMode: .disabled, setAccountID: false) | ||
|
||
do { | ||
_ = try await subject.getItem(input: GetItemInput()) | ||
XCTFail("Request should have thrown") | ||
} catch TestCheckError.actual(let request) { | ||
XCTAssertFalse(request.endpoint.host.contains(accountID)) | ||
} catch { | ||
XCTFail("Request failed on unexpected error") | ||
} | ||
} | ||
|
||
func test_disabledMode_createsEndpointWithoutAccountIDWhenSupplied() async throws { | ||
let subject = try await subject(accountIDEndpointMode: .disabled, setAccountID: true) | ||
|
||
do { | ||
_ = try await subject.getItem(input: GetItemInput()) | ||
XCTFail("Request should have thrown") | ||
} catch TestCheckError.actual(let request) { | ||
XCTAssertFalse(request.endpoint.host.contains(accountID)) | ||
} catch { | ||
XCTFail("Request failed on unexpected error") | ||
} | ||
} | ||
|
||
// MARK: - Private methods | ||
|
||
private func subject( | ||
accountIDEndpointMode: AccountIDEndpointMode?, | ||
setAccountID: Bool | ||
) async throws -> DynamoDBClient { | ||
let accountID = setAccountID ? self.accountID : nil | ||
let credentials = AWSCredentialIdentity(accessKey: "abc", secret: "def", accountID: accountID) | ||
let resolver = try StaticAWSCredentialIdentityResolver(credentials) | ||
let config = try await DynamoDBClient.Config( | ||
awsCredentialIdentityResolver: resolver, | ||
region: "us-east-1", | ||
accountIdEndpointMode: accountIDEndpointMode, | ||
httpClientEngine: ProtocolTestClient() | ||
) | ||
return DynamoDBClient(config: config) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,43 +17,43 @@ | |
{ | ||
"target" : { | ||
"containerPath" : "container:", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A lot of churn in this file, but the only material change is that the DynamoDB test bundle is added to the test plan. |
||
"identifier" : "AWSCognitoIdentityIntegrationTests", | ||
"name" : "AWSCognitoIdentityIntegrationTests" | ||
"identifier" : "AWSSQSIntegrationTests", | ||
"name" : "AWSSQSIntegrationTests" | ||
} | ||
}, | ||
{ | ||
"target" : { | ||
"containerPath" : "container:", | ||
"identifier" : "AWSSTSIntegrationTests", | ||
"name" : "AWSSTSIntegrationTests" | ||
"identifier" : "AWSKinesisIntegrationTests", | ||
"name" : "AWSKinesisIntegrationTests" | ||
} | ||
}, | ||
{ | ||
"target" : { | ||
"containerPath" : "container:", | ||
"identifier" : "AWSTranscribeStreamingIntegrationTests", | ||
"name" : "AWSTranscribeStreamingIntegrationTests" | ||
"identifier" : "AWSEC2IntegrationTests", | ||
"name" : "AWSEC2IntegrationTests" | ||
} | ||
}, | ||
{ | ||
"target" : { | ||
"containerPath" : "container:", | ||
"identifier" : "AWSECSIntegrationTests", | ||
"name" : "AWSECSIntegrationTests" | ||
"identifier" : "AWSSTSIntegrationTests", | ||
"name" : "AWSSTSIntegrationTests" | ||
} | ||
}, | ||
{ | ||
"target" : { | ||
"containerPath" : "container:", | ||
"identifier" : "AWSGlacierIntegrationTests", | ||
"name" : "AWSGlacierIntegrationTests" | ||
"identifier" : "AWSDynamoDBIntegrationTests", | ||
"name" : "AWSDynamoDBIntegrationTests" | ||
} | ||
}, | ||
{ | ||
"target" : { | ||
"containerPath" : "container:", | ||
"identifier" : "AWSMediaConvertIntegrationTests", | ||
"name" : "AWSMediaConvertIntegrationTests" | ||
"identifier" : "AWSECSIntegrationTests", | ||
"name" : "AWSECSIntegrationTests" | ||
} | ||
}, | ||
{ | ||
|
@@ -66,43 +66,50 @@ | |
{ | ||
"target" : { | ||
"containerPath" : "container:", | ||
"identifier" : "AWSSQSIntegrationTests", | ||
"name" : "AWSSQSIntegrationTests" | ||
"identifier" : "AWSS3IntegrationTests", | ||
"name" : "AWSS3IntegrationTests" | ||
} | ||
}, | ||
{ | ||
"target" : { | ||
"containerPath" : "container:", | ||
"identifier" : "AWSEventBridgeIntegrationTests", | ||
"name" : "AWSEventBridgeIntegrationTests" | ||
"identifier" : "AWSCognitoIdentityIntegrationTests", | ||
"name" : "AWSCognitoIdentityIntegrationTests" | ||
} | ||
}, | ||
{ | ||
"target" : { | ||
"containerPath" : "container:", | ||
"identifier" : "AWSEC2IntegrationTests", | ||
"name" : "AWSEC2IntegrationTests" | ||
"identifier" : "AWSTranscribeStreamingIntegrationTests", | ||
"name" : "AWSTranscribeStreamingIntegrationTests" | ||
} | ||
}, | ||
{ | ||
"target" : { | ||
"containerPath" : "container:", | ||
"identifier" : "AWSKinesisIntegrationTests", | ||
"name" : "AWSKinesisIntegrationTests" | ||
"identifier" : "AWSRoute53IntegrationTests", | ||
"name" : "AWSRoute53IntegrationTests" | ||
} | ||
}, | ||
{ | ||
"target" : { | ||
"containerPath" : "container:", | ||
"identifier" : "AWSRoute53IntegrationTests", | ||
"name" : "AWSRoute53IntegrationTests" | ||
"identifier" : "AWSGlacierIntegrationTests", | ||
"name" : "AWSGlacierIntegrationTests" | ||
} | ||
}, | ||
{ | ||
"target" : { | ||
"containerPath" : "container:", | ||
"identifier" : "AWSS3IntegrationTests", | ||
"name" : "AWSS3IntegrationTests" | ||
"identifier" : "AWSMediaConvertIntegrationTests", | ||
"name" : "AWSMediaConvertIntegrationTests" | ||
} | ||
}, | ||
{ | ||
"target" : { | ||
"containerPath" : "container:", | ||
"identifier" : "AWSEventBridgeIntegrationTests", | ||
"name" : "AWSEventBridgeIntegrationTests" | ||
} | ||
} | ||
], | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,7 @@ import PackageDescription | |
// MARK: - Dynamic Content | ||
|
||
let clientRuntimeVersion: Version = "0.101.0" | ||
let crtVersion: Version = "0.40.0" | ||
let crtVersion: Version = "0.41.0" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New CRT (with support for Account ID in credentials) is added. |
||
|
||
let excludeRuntimeUnitTests = false | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -135,4 +135,19 @@ public class AWSClientConfigDefaultsProvider { | |
rateLimitingMode: resolvedRateLimitingMode | ||
) | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Function below is used to resolve a default value for account ID endpoint mode, if needed. |
||
public static func accountIDEndpointMode( | ||
_ accountIDEndpointMode: AccountIDEndpointMode? = nil | ||
) throws -> AccountIDEndpointMode { | ||
let fileBasedConfig = try CRTFileBasedConfiguration.make() | ||
if let accountIDEndpointMode { | ||
return accountIDEndpointMode | ||
} else { | ||
return AWSEndpointConfig.accountIDEndpointMode( | ||
configValue: accountIDEndpointMode, | ||
profileName: nil, | ||
fileBasedConfig: fileBasedConfig | ||
) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// | ||
// Copyright Amazon.com Inc. or its affiliates. | ||
// All Rights Reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
@_spi(FileBasedConfig) import AWSSDKCommon | ||
|
||
public enum AWSEndpointConfig { | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Function below attempts to resolve account ID endpoint mode from env var & config file, and if no luck defaults to |
||
static func accountIDEndpointMode( | ||
configValue: AccountIDEndpointMode?, | ||
profileName: String?, | ||
fileBasedConfig: FileBasedConfiguration | ||
) -> AccountIDEndpointMode { | ||
FieldResolver( | ||
configValue: configValue, | ||
envVarName: "AWS_ACCOUNT_ID_ENDPOINT_MODE", | ||
configFieldName: "account_id_endpoint_mode", | ||
fileBasedConfig: fileBasedConfig, | ||
profileName: profileName, | ||
converter: { AccountIDEndpointMode(rawValue: $0) } | ||
).value ?? .preferred | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// | ||
// Copyright Amazon.com Inc. or its affiliates. | ||
// All Rights Reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The enum type below is exposed on the service client config as the "accountIDEndpointMode" config option instead of a raw string. |
||
/// Determines how & whether an account ID-based endpoint will be used for requests. | ||
public enum AccountIDEndpointMode: String, Equatable { | ||
// Note : these case names match string values for the accountIdEndpointMode endpoint param. | ||
// Do not rename them | ||
|
||
/// An account ID-based endpoint will be used if an account ID is available. | ||
/// | ||
/// This is the default mode. | ||
case preferred // the default case | ||
|
||
/// An account ID-based endpoint will never be used. | ||
/// | ||
/// The request will fail if a non-account ID-based endpoint is not available. | ||
case disabled | ||
|
||
/// An account ID-based endpoint will always be used. | ||
/// | ||
/// The request will fail if an account ID-based endpoint cannot be constructed. | ||
case required | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// | ||
// Copyright Amazon.com Inc. or its affiliates. | ||
// All Rights Reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
import struct Smithy.Attributes | ||
import struct Smithy.AttributeKey | ||
import class Smithy.Context | ||
import class Smithy.ContextBuilder | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Utilities to get/set |
||
public extension Context { | ||
|
||
var accountIDEndpointMode: AccountIDEndpointMode? { | ||
get { get(key: accountIDEndpointModeKey) } | ||
set { set(key: accountIDEndpointModeKey, value: newValue) } | ||
} | ||
} | ||
|
||
public extension ContextBuilder { | ||
|
||
@discardableResult | ||
func withAccountIDEndpointMode(value: AccountIDEndpointMode?) -> Self { | ||
attributes.set(key: accountIDEndpointModeKey, value: value) | ||
return self | ||
} | ||
} | ||
|
||
private let accountIDEndpointModeKey = AttributeKey<AccountIDEndpointMode>(name: "AccountIDEndpointMode") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DynamoDB is used to verify that the client handles Account ID and endpoint mode correctly.