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: Account ID-based endpoints #1841

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c8288c0
Update codegen
jbelkins Dec 3, 2024
09f8729
Merge branch 'main' into jbe/account_id_config
jbelkins Dec 4, 2024
3ee4347
Merge branch 'main' into jbe/account_id_config
jbelkins Dec 5, 2024
c1315df
feat: Account ID-based endpoints
jbelkins Dec 6, 2024
6aa9a90
Fix tests
jbelkins Dec 6, 2024
cada066
Wire up account ID
jbelkins Dec 6, 2024
f85862e
Add account ID endpoint mode resolution
jbelkins Dec 9, 2024
93e0634
fix tests
jbelkins Dec 9, 2024
adea19b
Merge branch 'main' into jbe/account_id_config
jbelkins Dec 9, 2024
34769a4
Merge branch 'main' into jbe/account_id_config
jbelkins Dec 10, 2024
c8aec20
Integrate CRT support for account ID, add tests
jbelkins Dec 12, 2024
b3c0987
Merge branch 'main' into jbe/account_id_config
jbelkins Dec 12, 2024
b632492
Generate dynamodb for integration tests
jbelkins Dec 12, 2024
c1543cd
Set region in endpoint mode test
jbelkins Dec 12, 2024
8c5c3e5
Add business metrics
jbelkins Dec 13, 2024
82da91b
Several lint and config fixes
jbelkins Dec 17, 2024
dd08cd2
Merge branch 'main' into jbe/account_id_config
jbelkins Dec 17, 2024
095dd5b
Merge branch 'main' into jbe/account_id_config
jbelkins Dec 17, 2024
d1908b3
Merge branch 'main' into jbe/account_id_config
jbelkins Dec 19, 2024
6e4ef4d
Merge branch 'jbe/account_id_config' of github.com:awslabs/aws-sdk-sw…
jbelkins Dec 19, 2024
1952b95
Merge branch 'main' into jbe/account_id_config
jbelkins Dec 27, 2024
3f2fb2a
Merge branch 'main' into jbe/account_id_config
jbelkins Jan 6, 2025
73a21dc
Merge branch 'main' into jbe/account_id_config
jbelkins Jan 7, 2025
49202bc
Merge branch 'main' into jbe/account_id_config
jbelkins Jan 7, 2025
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
1 change: 1 addition & 0 deletions IntegrationTests/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ func addIntegrationTestTarget(_ name: String) {

let servicesWithIntegrationTests: [String] = [
"AWSCloudFrontKeyValueStore",
"AWSDynamoDB",
Copy link
Contributor Author

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.

"AWSEC2",
"AWSECS",
"AWSEventBridge",
Expand Down
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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)
}
}
Empty file.
55 changes: 31 additions & 24 deletions IntegrationTests/XCTestPlans/AWSIntegrationTestsOnCI.xctestplan
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,43 @@
{
"target" : {
"containerPath" : "container:",
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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"
}
},
{
Expand All @@ -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"
}
}
],
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,19 @@ public class AWSClientConfigDefaultsProvider {
rateLimitingMode: resolvedRateLimitingMode
)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 {

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 .preferred mode.

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
//

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Utilities to get/set accountIDEndpointMode on the context.

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")
Loading