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

feat: Account ID-based endpoints #1841

wants to merge 24 commits into from

Conversation

jbelkins
Copy link
Contributor

@jbelkins jbelkins commented Dec 6, 2024

Description of changes

  • The AWS-specific EndpointResolverMiddleware is renamed to AWSEndpointResolverMiddleware to make clear that it is an AWS-specialized type
  • AWSEndpointResolverMiddleware is protected with Swift @_spi() to conceal it from public usage.
  • Endpoint params are now created by a block which can be passed into the middleware for inline evaluation, since account ID may not be known yet when endpoint params are currently constructed.
  • accountId is no longer exposed as a client config param since it is set only via AWS credential identity resolvers
  • Update to aws-crt-swift 0.41.0 with support for account ID in AWS credentials

Note: Bump minor version when this feature ships

Also note: This is a breaking change to the DynamoDB client because client config fields have been either removed or changed in type. Expected breakage on the customer's side is zero because those fields are currently exposed but non-functional. Only DynamoDB is affected, no other client has these config fields at this time.

New/existing dependencies impact assessment, if applicable

No new dependencies were added to this change.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@@ -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.

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.

@@ -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.

Package.swift Outdated
@@ -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.

@@ -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.

@_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.

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

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.

@@ -18,35 +18,37 @@ import enum ClientRuntime.EndpointsAuthScheme
import protocol ClientRuntime.EndpointsAuthSchemeResolver
import protocol ClientRuntime.EndpointsRequestContextProviding

public struct EndpointResolverMiddleware<OperationStackOutput, Params: EndpointsRequestContextProviding> {
@_spi(AWSEndpointResolverMiddleware)
public struct AWSEndpointResolverMiddleware<OperationStackOutput, Params: EndpointsRequestContextProviding> {
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 middleware is changed to take a endpoint params block, since account ID cannot be resolved until after auth scheme has been resolved & credentials are sourced (and account ID along with it.)

// Handle N
if let endpoint = config.endpoint, !endpoint.isEmpty {
context.businessMetrics = ["ENDPOINT_OVERRIDE": "N"]
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Below, several features are added to business metrics.

context: context
)

let userAgent = testUserAgent()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

creating userAgent for this test is extracted to a helper method below

import software.amazon.smithy.swift.codegen.integration.ServiceConfig
import software.amazon.smithy.swift.codegen.middleware.MiddlewareExecutionGenerator
import software.amazon.smithy.swift.codegen.middleware.OperationMiddleware

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 a custom subclass of the smithy-swift generic HTTP protocol client generator.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Its sole customization is to provide a custom middleware execution generator.

writer.write(
"return \"\\(\$L.clientName) - \\(region ?? \"\")\"",
serviceConfig.clientName.toUpperCamelCase(),
)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

cleanup

}
writer.write("")
}

private val authSchemeResolverDefaultProvider = DefaultProvider(
{ "Default${AuthSchemeResolverGenerator.getSdkId(ctx)}AuthSchemeResolver()" },
{ writer.format("Default\$LAuthSchemeResolver()", AuthSchemeResolverGenerator.getSdkId(ctx)) },
Copy link
Contributor Author

Choose a reason for hiding this comment

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

cleanup

false,
false
)

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 method below:

  • hides accountId from being exposed as a config property since it is only to be supplied through credential resolution.
  • replaces the string accountIdEndpointMode config property with a custom Swift enum that is more user-friendly.

): MiddlewareExecutionGenerator(
ctx, writer, httpBindingResolver, httpProtocolCustomizable, operationMiddleware, operationStackName
) {

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 only customization on this middleware generator is that it writes accountIdEndpointMode to context (for use in business metrics.)

// // In Linux, Foundation.URLRequest is moved to FoundationNetworking.
// writer.addImport(packageName = "FoundationNetworking", importOnlyIfCanImport = true)
// }
// }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Above, removed unused code

writer.write("return nil")
}

generator.render(serviceShape, op, PRESIGN_REQUEST)
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 trailing param to render has been removed

writer.write(
"context.set(key: \$N<EndpointParams>(name: \"EndpointParams\"), value: endpointParamsBlock(context))",
SmithyTypes.AttributeKey
)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

cleanup

@@ -65,14 +68,13 @@ class OperationEndpointResolverMiddleware(
) {
val output = MiddlewareShapeUtils.outputSymbol(ctx.symbolProvider, ctx.model, op)
writer.write(
"\$N<\$N, EndpointParams>(endpointResolverBlock: { [config] in try config.endpointResolver.resolve(params: \$\$0) }, endpointParams: endpointParams)",
"\$N<\$N, EndpointParams>(paramsBlock: endpointParamsBlock, resolverBlock: { [config] in try config.endpointResolver.resolve(params: \$\$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.

Updated for new EndpointResolverMiddleware signature

@@ -96,15 +98,16 @@ class OperationEndpointResolverMiddleware(
operationContextParams[param.name.toString()],
clientContextParams[param.name.toString()],
writer,
outputError
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unused param is removed

writer.write("let endpointParams = EndpointParams(${params.joinToString(separator = ", ")})")
writer.openBlock("let endpointParamsBlock = { [config] (context: \$N) in", "}", SmithyTypes.Context) {
writer.write("EndpointParams(\$L)", params.joinToString(", "))
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Endpoint params are broken onto individual lines for clarity

}
param.name.toString() == "AccountIdEndpointMode" -> {
"config.accountIdEndpointMode?.rawValue"
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Handle account ID & endpoint mode specially

@jbelkins jbelkins marked this pull request as ready for review December 17, 2024 19:45
Copy link
Contributor

@sichanyoo sichanyoo left a comment

Choose a reason for hiding this comment

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

Initial review w/ comments about re-using extension points and removing business feature metric for ACCOUNT_ID_ENDPOINT

Comment on lines +1 to +21
//
// 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 struct SmithyHTTPAPI.Endpoint

public extension Context {

var resolvedEndpoint: Endpoint? {
get { get(key: resolvedEndpointKey) }
set { set(key: resolvedEndpointKey, value: newValue) }
}
}

private let resolvedEndpointKey = AttributeKey<Endpoint>(name: "ResolvedEndpoint")
Copy link
Contributor

Choose a reason for hiding this comment

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

Apparently the ACCOUNT_ID_ENDPOINT feature ID shouldn't be tracked atm (saving and parsing resolved endpoint) and shouldn't be implemented until future update according to the feature ID table SEP page --/FEATURES.md.

Comment on lines +50 to +51
// Put endpoint into context for use in business metrics
attributes.resolvedEndpoint = endpoint
Copy link
Contributor

Choose a reason for hiding this comment

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

As per comment on Context+ResolvedEndpoint.swift, should be removed

Comment on lines +104 to +107
// Handle O
if let endpoint = context.resolvedEndpoint, let accountID = context.resolvedAWSAccountID, endpoint.host.contains(accountID) {
context.businessMetrics = ["ACCOUNT_ID_ENDPOINT": "O"]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

As per comment on Context+ResolvedEndpoint.swift, should be removed

Comment on lines +162 to +180
override fun customizedClientConfigProperty(property: ConfigProperty): ConfigProperty? {
return when (property.name) {
"accountId" -> null // do not expose accountId as a client config property
"accountIdEndpointMode" -> { // expose accountIdEndpointMode as a Swift string-backed enum
ConfigProperty(
"accountIdEndpointMode",
AWSClientRuntimeTypes.Core.AccountIDEndpointMode.toOptional(),
{ writer ->
writer.format(
"\$N.accountIDEndpointMode()",
AWSClientRuntimeTypes.Core.AWSClientConfigDefaultsProvider,
)
},
true
)
}
else -> property
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Wrote a comment over in smithy-swift PR that has the base default implementation of this as well, but this extension should already be doable using overrideConfigProperties. Let's remove duplicate purpose extension point and merge this to existing one. See here.

Comment on lines +1 to +29
package software.amazon.smithy.aws.swift.codegen

import software.amazon.smithy.swift.codegen.SwiftWriter
import software.amazon.smithy.swift.codegen.config.ConfigProperty
import software.amazon.smithy.swift.codegen.integration.HTTPProtocolCustomizable
import software.amazon.smithy.swift.codegen.integration.HttpBindingResolver
import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator
import software.amazon.smithy.swift.codegen.middleware.MiddlewareExecutionGenerator
import software.amazon.smithy.swift.codegen.middleware.OperationMiddleware

class AWSMiddlewareExecutionGenerator(
ctx: ProtocolGenerator.GenerationContext,
private val writer: SwiftWriter,
httpBindingResolver: HttpBindingResolver,
httpProtocolCustomizable: HTTPProtocolCustomizable,
operationMiddleware: OperationMiddleware,
operationStackName: String
) : MiddlewareExecutionGenerator(
ctx, writer, httpBindingResolver, httpProtocolCustomizable, operationMiddleware, operationStackName
) {

override fun renderConfigPropertyToContext(configProperty: ConfigProperty) {
when (configProperty.name) {
"accountIdEndpointMode" -> { // Used for business metrics
writer.write(" .withAccountIDEndpointMode(value: config.accountIdEndpointMode)")
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Wrote comment in smithy-swift PR as well; this is redundant; we can use existing extension point for printing custom AWS specific context attributes. See here. The extension point used by previous link is here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants