diff --git a/.changes/873fb1ab-bfbb-4110-89cd-bf7ac352bc86.json b/.changes/873fb1ab-bfbb-4110-89cd-bf7ac352bc86.json new file mode 100644 index 00000000000..4ffb0fcf432 --- /dev/null +++ b/.changes/873fb1ab-bfbb-4110-89cd-bf7ac352bc86.json @@ -0,0 +1,6 @@ +{ + "id": "873fb1ab-bfbb-4110-89cd-bf7ac352bc86", + "type": "feature", + "description": "⚠️ **IMPORTANT**: Enable account ID based endpoint routing for services that support it", + "requiresMinorVersionBump": true +} \ No newline at end of file diff --git a/.changes/98b36000-c7c2-43e1-8bfe-ec7e93317f3a.json b/.changes/98b36000-c7c2-43e1-8bfe-ec7e93317f3a.json new file mode 100644 index 00000000000..8d8502afcfc --- /dev/null +++ b/.changes/98b36000-c7c2-43e1-8bfe-ec7e93317f3a.json @@ -0,0 +1,5 @@ +{ + "id": "98b36000-c7c2-43e1-8bfe-ec7e93317f3a", + "type": "misc", + "description": "Upgrade dependencies to their latest versions, notably Kotlin 1.9.20" +} \ No newline at end of file diff --git a/aws-runtime/aws-config/api/aws-config.api b/aws-runtime/aws-config/api/aws-config.api index af5ffd8fcbe..0921c5f2c96 100644 --- a/aws-runtime/aws-config/api/aws-config.api +++ b/aws-runtime/aws-config/api/aws-config.api @@ -151,9 +151,11 @@ public final class aws/sdk/kotlin/runtime/auth/credentials/StaticCredentialsProv public fun ()V public final fun build ()Laws/sdk/kotlin/runtime/auth/credentials/StaticCredentialsProvider; public final fun getAccessKeyId ()Ljava/lang/String; + public final fun getAccountId ()Ljava/lang/String; public final fun getSecretAccessKey ()Ljava/lang/String; public final fun getSessionToken ()Ljava/lang/String; public final fun setAccessKeyId (Ljava/lang/String;)V + public final fun setAccountId (Ljava/lang/String;)V public final fun setSecretAccessKey (Ljava/lang/String;)V public final fun setSessionToken (Ljava/lang/String;)V } @@ -209,7 +211,7 @@ public final class aws/sdk/kotlin/runtime/auth/credentials/internal/ManagedCrede public abstract class aws/sdk/kotlin/runtime/config/AbstractAwsSdkClientFactory : aws/smithy/kotlin/runtime/client/SdkClientFactory { public fun ()V - protected fun finalizeConfig (Laws/smithy/kotlin/runtime/client/SdkClient$Builder;Laws/smithy/kotlin/runtime/util/LazyAsyncValue;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected fun finalizeConfig (Laws/smithy/kotlin/runtime/client/SdkClient$Builder;Laws/smithy/kotlin/runtime/util/LazyAsyncValue;Laws/smithy/kotlin/runtime/util/LazyAsyncValue;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun fromEnvironment (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun fromEnvironment$default (Laws/sdk/kotlin/runtime/config/AbstractAwsSdkClientFactory;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/client/SdkClient; @@ -218,10 +220,16 @@ public abstract class aws/sdk/kotlin/runtime/config/AbstractAwsSdkClientFactory public final class aws/sdk/kotlin/runtime/config/AwsSdkSettingKt { } -public final class aws/sdk/kotlin/runtime/config/endpoints/EndpointsKt { +public final class aws/sdk/kotlin/runtime/config/endpoints/AccountIdEndpointMode : java/lang/Enum { + public static final field DISABLED Laws/sdk/kotlin/runtime/config/endpoints/AccountIdEndpointMode; + public static final field PREFERRED Laws/sdk/kotlin/runtime/config/endpoints/AccountIdEndpointMode; + public static final field REQUIRED Laws/sdk/kotlin/runtime/config/endpoints/AccountIdEndpointMode; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Laws/sdk/kotlin/runtime/config/endpoints/AccountIdEndpointMode; + public static fun values ()[Laws/sdk/kotlin/runtime/config/endpoints/AccountIdEndpointMode; } -public final class aws/sdk/kotlin/runtime/config/endpoints/ResolveEndpointUrlKt { +public final class aws/sdk/kotlin/runtime/config/endpoints/ResolversKt { } public final class aws/sdk/kotlin/runtime/config/imds/EC2MetadataError : aws/sdk/kotlin/runtime/AwsServiceException { diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/arns/Arn.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/arns/Arn.kt new file mode 100644 index 00000000000..fdfeafe1b71 --- /dev/null +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/arns/Arn.kt @@ -0,0 +1,114 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.runtime.arns + +private const val ARN_COMPONENT_COUNT = 6 + +/** + * Represents an [Amazon Resource Name (ARN)](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html). + * + * The following Arn formats are supported: + * * `arn:partition:service:region:account-id:resource-id` + * * `arn:partition:service:region:account-id:resource-type/resource-id` + * * `arn:partition:service:region:account-id:resource-type:resource-id` + * * `arn:partition:service:region:account-id:resource-type:resource-id:qualifier` + * * `arn:partition:service:region:account-id:resource-type:resource-id/qualifier` + * + * The exact format of an ARN depends on the service and resource type. Some resource ARNs can include a path or + * wildcard. To look up the ARN format for a specific AWS resource, open the + * [Service Authorization Reference](https://docs.aws.amazon.com/service-authorization/latest/reference/), + * open the page for the service, and navigate to the resource types table. + */ +internal class Arn( + public val partition: String, + public val service: String, + public val region: String?, + public val accountId: String?, + public val resource: String, +) { + public companion object { + + public inline operator fun invoke(block: Builder.() -> Unit): Arn = Builder().apply(block).build() + + /** + * Parse a string into an [Arn] + */ + public fun parse(arn: String): Arn { + val parts = arn.split(':', limit = ARN_COMPONENT_COUNT) + require(parts.size == ARN_COMPONENT_COUNT) { "Malformed ARN ($arn) does not have the expected number of components" } + require(parts[0] == "arn") { "Malformed ARN - does not start with `arn:`" } + require(parts[1].isNotBlank()) { "Malformed ARN - no AWS partition specified" } + require(parts[2].isNotBlank()) { "Malformed ARN - no AWS service specified" } + + return Arn { + partition = parts[1] + service = parts[2] + region = parts[3].takeIf(String::isNotBlank) + accountId = parts[4].takeIf(String::isNotBlank) + resource = parts[5] + } + } + } + + internal constructor(builder: Builder) : this( + builder.partition!!, + builder.service!!, + builder.region, + builder.accountId, + builder.resource!!, + ) + + init { + require(region == null || region.isNotBlank()) { "ARN region must not be blank" } + require(accountId == null || accountId.isNotBlank()) { "ARN accountId must not be blank" } + } + + override fun toString(): String = buildString { + append("arn:$partition:$service:") + if (region != null) { + append(region) + } + append(":") + if (accountId != null) { + append(accountId) + } + append(":$resource") + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Arn) return false + if (partition != other.partition) return false + if (service != other.service) return false + if (region != other.region) return false + if (accountId != other.accountId) return false + return resource == other.resource + } + + override fun hashCode(): Int { + var result = partition.hashCode() + result = 31 * result + service.hashCode() + result = 31 * result + (region?.hashCode() ?: 0) + result = 31 * result + (accountId?.hashCode() ?: 0) + result = 31 * result + resource.hashCode() + return result + } + + public class Builder { + public var partition: String? = null + public var service: String? = null + public var region: String? = null + public var accountId: String? = null + public var resource: String? = null + + @PublishedApi + internal fun build(): Arn { + require(!partition.isNullOrBlank()) { "ARN partition must not be null or blank" } + require(!service.isNullOrBlank()) { "ARN service must not be null or blank" } + requireNotNull(resource) { "ARN resource must not be null" } + return Arn(this) + } + } +} diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/EcsCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/EcsCredentialsProvider.kt index 51a0ad82b38..a170bd61332 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/EcsCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/EcsCredentialsProvider.kt @@ -5,6 +5,7 @@ package aws.sdk.kotlin.runtime.auth.credentials +import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.sdk.kotlin.runtime.config.AwsSdkSetting import aws.sdk.kotlin.runtime.config.AwsSdkSetting.AwsContainerCredentialsRelativeUri import aws.smithy.kotlin.runtime.ErrorMetadata @@ -188,12 +189,13 @@ private class EcsCredentialsDeserializer : HttpDeserialize { throw CredentialsProviderException("HTTP credentials response was not of expected format") } - return Credentials( + return credentials( resp.accessKeyId, resp.secretAccessKey, resp.sessionToken, resp.expiration, PROVIDER_NAME, + resp.accountId, ) } } diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/EnvironmentCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/EnvironmentCredentialsProvider.kt index 3dd31fdeb5e..104c398fb50 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/EnvironmentCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/EnvironmentCredentialsProvider.kt @@ -5,6 +5,7 @@ package aws.sdk.kotlin.runtime.auth.credentials +import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.sdk.kotlin.runtime.config.AwsSdkSetting import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider @@ -18,6 +19,7 @@ private const val PROVIDER_NAME = "Environment" private val ACCESS_KEY_ID = AwsSdkSetting.AwsAccessKeyId.envVar private val SECRET_ACCESS_KEY = AwsSdkSetting.AwsSecretAccessKey.envVar private val SESSION_TOKEN = AwsSdkSetting.AwsSessionToken.envVar +private val ACCOUNT_ID = AwsSdkSetting.AwsAccountId.envVar /** * A [CredentialsProvider] which reads from `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN`. @@ -33,11 +35,12 @@ public class EnvironmentCredentialsProvider( coroutineContext.trace { "Attempting to load credentials from env vars $ACCESS_KEY_ID/$SECRET_ACCESS_KEY/$SESSION_TOKEN" } - return Credentials( + return credentials( accessKeyId = requireEnv(ACCESS_KEY_ID), secretAccessKey = requireEnv(SECRET_ACCESS_KEY), sessionToken = getEnv(SESSION_TOKEN), providerName = PROVIDER_NAME, + accountId = getEnv(ACCOUNT_ID), ) } } diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/JsonCredentialsDeserializer.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/JsonCredentialsDeserializer.kt index 3392f249dc7..8c4c7e4144f 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/JsonCredentialsDeserializer.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/JsonCredentialsDeserializer.kt @@ -28,6 +28,7 @@ internal sealed class JsonCredentialsResponse { val secretAccessKey: String, val sessionToken: String, val expiration: Instant?, + val accountId: String? = null, ) : JsonCredentialsResponse() // TODO - add support for static credentials @@ -78,6 +79,7 @@ internal suspend fun deserializeJsonCredentials(deserializer: Deserializer): Jso val SECRET_ACCESS_KEY_ID_DESCRIPTOR = SdkFieldDescriptor(SerialKind.String, JsonSerialName("SecretAccessKey")) val SESSION_TOKEN_DESCRIPTOR = SdkFieldDescriptor(SerialKind.String, JsonSerialName("Token")) val EXPIRATION_DESCRIPTOR = SdkFieldDescriptor(SerialKind.Timestamp, JsonSerialName("Expiration")) + val ACCOUNT_ID_DESCRIPTOR = SdkFieldDescriptor(SerialKind.String, JsonSerialName("AccountId")) val MESSAGE_DESCRIPTOR = SdkFieldDescriptor(SerialKind.String, JsonSerialName("Message")) val OBJ_DESCRIPTOR = SdkObjectDescriptor.build { @@ -86,6 +88,7 @@ internal suspend fun deserializeJsonCredentials(deserializer: Deserializer): Jso field(SECRET_ACCESS_KEY_ID_DESCRIPTOR) field(SESSION_TOKEN_DESCRIPTOR) field(EXPIRATION_DESCRIPTOR) + field(ACCOUNT_ID_DESCRIPTOR) field(MESSAGE_DESCRIPTOR) } @@ -95,6 +98,7 @@ internal suspend fun deserializeJsonCredentials(deserializer: Deserializer): Jso var sessionToken: String? = null var expiration: Instant? = null var message: String? = null + var accountId: String? = null try { deserializer.deserializeStruct(OBJ_DESCRIPTOR) { @@ -105,6 +109,7 @@ internal suspend fun deserializeJsonCredentials(deserializer: Deserializer): Jso SECRET_ACCESS_KEY_ID_DESCRIPTOR.index -> secretAccessKey = deserializeString() SESSION_TOKEN_DESCRIPTOR.index -> sessionToken = deserializeString() EXPIRATION_DESCRIPTOR.index -> expiration = Instant.fromIso8601(deserializeString()) + ACCOUNT_ID_DESCRIPTOR.index -> accountId = deserializeString() // error responses MESSAGE_DESCRIPTOR.index -> message = deserializeString() @@ -124,7 +129,7 @@ internal suspend fun deserializeJsonCredentials(deserializer: Deserializer): Jso if (secretAccessKey == null) throw InvalidJsonCredentialsException("missing field `SecretAccessKey`") if (sessionToken == null) throw InvalidJsonCredentialsException("missing field `Token`") if (expiration == null) throw InvalidJsonCredentialsException("missing field `Expiration`") - JsonCredentialsResponse.SessionCredentials(accessKeyId!!, secretAccessKey!!, sessionToken!!, expiration!!) + JsonCredentialsResponse.SessionCredentials(accessKeyId!!, secretAccessKey!!, sessionToken!!, expiration!!, accountId) } else -> JsonCredentialsResponse.Error(code, message) } @@ -141,6 +146,7 @@ internal fun deserializeJsonProcessCredentials(deserializer: Deserializer): Json val SESSION_TOKEN_DESCRIPTOR = SdkFieldDescriptor(SerialKind.String, JsonSerialName("SessionToken")) val EXPIRATION_DESCRIPTOR = SdkFieldDescriptor(SerialKind.Timestamp, JsonSerialName("Expiration")) val VERSION_DESCRIPTOR = SdkFieldDescriptor(SerialKind.String, JsonSerialName("Version")) + val ACCOUNT_ID_DESCRIPTOR = SdkFieldDescriptor(SerialKind.String, JsonSerialName("AccountId")) val OBJ_DESCRIPTOR = SdkObjectDescriptor.build { field(ACCESS_KEY_ID_DESCRIPTOR) @@ -148,6 +154,7 @@ internal fun deserializeJsonProcessCredentials(deserializer: Deserializer): Json field(SESSION_TOKEN_DESCRIPTOR) field(EXPIRATION_DESCRIPTOR) field(VERSION_DESCRIPTOR) + field(ACCOUNT_ID_DESCRIPTOR) } var accessKeyId: String? = null @@ -155,6 +162,7 @@ internal fun deserializeJsonProcessCredentials(deserializer: Deserializer): Json var sessionToken: String? = null var expiration: Instant? = null var version: Int? = null + var accountId: String? = null try { deserializer.deserializeStruct(OBJ_DESCRIPTOR) { @@ -165,7 +173,7 @@ internal fun deserializeJsonProcessCredentials(deserializer: Deserializer): Json SESSION_TOKEN_DESCRIPTOR.index -> sessionToken = deserializeString() EXPIRATION_DESCRIPTOR.index -> expiration = Instant.fromIso8601(deserializeString()) VERSION_DESCRIPTOR.index -> version = deserializeInt() - + ACCOUNT_ID_DESCRIPTOR.index -> accountId = deserializeString() null -> break@loop else -> skipValue() } @@ -180,5 +188,5 @@ internal fun deserializeJsonProcessCredentials(deserializer: Deserializer): Json if (sessionToken == null) throw InvalidJsonCredentialsException("missing field `${SESSION_TOKEN_DESCRIPTOR.serialName}`") if (version == null) throw InvalidJsonCredentialsException("missing field `${VERSION_DESCRIPTOR.serialName}`") if (version != 1) throw InvalidJsonCredentialsException("version $version is not supported") - return JsonCredentialsResponse.SessionCredentials(accessKeyId!!, secretAccessKey!!, sessionToken!!, expiration) + return JsonCredentialsResponse.SessionCredentials(accessKeyId!!, secretAccessKey!!, sessionToken!!, expiration, accountId) } diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ProcessCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ProcessCredentialsProvider.kt index 46a0722c3d8..99d6500e701 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ProcessCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ProcessCredentialsProvider.kt @@ -4,6 +4,7 @@ */ package aws.sdk.kotlin.runtime.auth.credentials +import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProviderException @@ -66,12 +67,13 @@ public class ProcessCredentialsProvider( val deserializer = JsonDeserializer(payload) return when (val resp = deserializeJsonProcessCredentials(deserializer)) { - is JsonCredentialsResponse.SessionCredentials -> Credentials( + is JsonCredentialsResponse.SessionCredentials -> credentials( resp.accessKeyId, resp.secretAccessKey, resp.sessionToken, resp.expiration ?: Instant.MAX_VALUE, PROVIDER_NAME, + resp.accountId, ) else -> throw CredentialsProviderException("Credentials response was not of expected format") } diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/SsoCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/SsoCredentialsProvider.kt index 140ee86bee9..c7cf97fa952 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/SsoCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/SsoCredentialsProvider.kt @@ -5,6 +5,7 @@ package aws.sdk.kotlin.runtime.auth.credentials +import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.sdk.kotlin.runtime.auth.credentials.internal.sso.SsoClient import aws.sdk.kotlin.runtime.auth.credentials.internal.sso.getRoleCredentials import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials @@ -116,12 +117,13 @@ public class SsoCredentialsProvider public constructor( val roleCredentials = resp.roleCredentials ?: throw CredentialsProviderException("Expected SSO roleCredentials to not be null") - return Credentials( + return credentials( accessKeyId = checkNotNull(roleCredentials.accessKeyId) { "Expected accessKeyId in SSO roleCredentials response" }, secretAccessKey = checkNotNull(roleCredentials.secretAccessKey) { "Expected secretAccessKey in SSO roleCredentials response" }, sessionToken = roleCredentials.sessionToken, expiration = Instant.fromEpochMilliseconds(roleCredentials.expiration), PROVIDER_NAME, + accountId = accountId, ) } diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StaticCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StaticCredentialsProvider.kt index bc2de3347c1..7ca99261cec 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StaticCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StaticCredentialsProvider.kt @@ -5,10 +5,13 @@ package aws.sdk.kotlin.runtime.auth.credentials +import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider import aws.smithy.kotlin.runtime.util.Attributes +private const val PROVIDER_NAME = "Static" + /** * A credentials provider for a fixed set of credentials * @@ -16,7 +19,15 @@ import aws.smithy.kotlin.runtime.util.Attributes */ public class StaticCredentialsProvider(public val credentials: Credentials) : CredentialsProvider { - private constructor(builder: Builder) : this(Credentials(builder.accessKeyId!!, builder.secretAccessKey!!, builder.sessionToken)) + private constructor(builder: Builder) : this( + credentials( + builder.accessKeyId!!, + builder.secretAccessKey!!, + builder.sessionToken, + providerName = PROVIDER_NAME, + accountId = builder.accountId, + ), + ) override suspend fun resolve(attributes: Attributes): Credentials = credentials @@ -31,6 +42,7 @@ public class StaticCredentialsProvider(public val credentials: Credentials) : Cr public var accessKeyId: String? = null public var secretAccessKey: String? = null public var sessionToken: String? = null + public var accountId: String? = null public fun build(): StaticCredentialsProvider { if (accessKeyId == null || secretAccessKey == null) { diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StsAssumeRoleCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StsAssumeRoleCredentialsProvider.kt index 72ccf8c479d..4374edc83ed 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StsAssumeRoleCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StsAssumeRoleCredentialsProvider.kt @@ -5,6 +5,8 @@ package aws.sdk.kotlin.runtime.auth.credentials +import aws.sdk.kotlin.runtime.arns.Arn +import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.sdk.kotlin.runtime.auth.credentials.internal.sts.StsClient import aws.sdk.kotlin.runtime.auth.credentials.internal.sts.assumeRole import aws.sdk.kotlin.runtime.auth.credentials.internal.sts.model.PolicyDescriptorType @@ -135,14 +137,16 @@ public class StsAssumeRoleCredentialsProvider( } val roleCredentials = resp.credentials ?: throw CredentialsProviderException("STS credentials must not be null") - logger.debug { "obtained assumed credentials; expiration=${roleCredentials.expiration?.format(TimestampFormat.ISO_8601)}" } + val accountId = resp.assumedRoleUser?.arn?.let { Arn.parse(it).accountId } + logger.debug { "obtained assumed credentials; expiration=${roleCredentials.expiration.format(TimestampFormat.ISO_8601)}" } - return Credentials( - accessKeyId = checkNotNull(roleCredentials.accessKeyId) { "Expected accessKeyId in STS assumeRole response" }, - secretAccessKey = checkNotNull(roleCredentials.secretAccessKey) { "Expected secretAccessKey in STS assumeRole response" }, + return credentials( + accessKeyId = roleCredentials.accessKeyId, + secretAccessKey = roleCredentials.secretAccessKey, sessionToken = roleCredentials.sessionToken, expiration = roleCredentials.expiration, providerName = PROVIDER_NAME, + accountId = accountId, ) } } diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProvider.kt index 951e2180966..692e7471947 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProvider.kt @@ -5,6 +5,8 @@ package aws.sdk.kotlin.runtime.auth.credentials +import aws.sdk.kotlin.runtime.arns.Arn +import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.sdk.kotlin.runtime.auth.credentials.internal.sts.StsClient import aws.sdk.kotlin.runtime.auth.credentials.internal.sts.assumeRoleWithWebIdentity import aws.sdk.kotlin.runtime.auth.credentials.internal.sts.model.PolicyDescriptorType @@ -138,14 +140,16 @@ public class StsWebIdentityCredentialsProvider( } val roleCredentials = resp.credentials ?: throw CredentialsProviderException("STS credentials must not be null") - logger.debug { "obtained assumed credentials via web identity; expiration=${roleCredentials.expiration?.format(TimestampFormat.ISO_8601)}" } + logger.debug { "obtained assumed credentials via web identity; expiration=${roleCredentials.expiration.format(TimestampFormat.ISO_8601)}" } + val accountId = resp.assumedRoleUser?.arn?.let { Arn.parse(it) }?.accountId - return Credentials( - accessKeyId = checkNotNull(roleCredentials.accessKeyId) { "Expected accessKeyId in STS assumeRoleWithWebIdentity response" }, - secretAccessKey = checkNotNull(roleCredentials.secretAccessKey) { "Expected secretAccessKey in STS assumeRoleWithWebIdentity response" }, + return credentials( + accessKeyId = roleCredentials.accessKeyId, + secretAccessKey = roleCredentials.secretAccessKey, sessionToken = roleCredentials.sessionToken, expiration = roleCredentials.expiration, providerName = PROVIDER_NAME, + accountId = accountId, ) } } diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/SystemPropertyCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/SystemPropertyCredentialsProvider.kt index 2a274ab029c..e2f1b971e59 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/SystemPropertyCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/SystemPropertyCredentialsProvider.kt @@ -5,6 +5,7 @@ package aws.sdk.kotlin.runtime.auth.credentials +import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.sdk.kotlin.runtime.config.AwsSdkSetting import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider @@ -18,6 +19,7 @@ private const val PROVIDER_NAME = "SystemProperties" private val ACCESS_KEY_ID = AwsSdkSetting.AwsAccessKeyId.sysProp private val SECRET_ACCESS_KEY = AwsSdkSetting.AwsSecretAccessKey.sysProp private val SESSION_TOKEN = AwsSdkSetting.AwsSessionToken.sysProp +private val ACCOUNT_ID = AwsSdkSetting.AwsAccountId.sysProp /** * A [CredentialsProvider] which reads `aws.accessKeyId`, `aws.secretAccessKey`, and `aws.sessionToken` from system properties. @@ -33,11 +35,12 @@ public class SystemPropertyCredentialsProvider( coroutineContext.trace { "Attempting to load credentials from system properties $ACCESS_KEY_ID/$SECRET_ACCESS_KEY/$SESSION_TOKEN" } - return Credentials( + return credentials( accessKeyId = requireProperty(ACCESS_KEY_ID), secretAccessKey = requireProperty(SECRET_ACCESS_KEY), sessionToken = getProperty(SESSION_TOKEN), providerName = PROVIDER_NAME, + accountId = getProperty(ACCOUNT_ID), ) } } diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/internal/CredentialsExt.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/internal/CredentialsExt.kt new file mode 100644 index 00000000000..5c8b0947635 --- /dev/null +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/internal/CredentialsExt.kt @@ -0,0 +1,31 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.runtime.auth.credentials.internal + +import aws.sdk.kotlin.runtime.client.AwsClientOption +import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials +import aws.smithy.kotlin.runtime.identity.IdentityAttributes +import aws.smithy.kotlin.runtime.time.Instant +import aws.smithy.kotlin.runtime.util.emptyAttributes +import aws.smithy.kotlin.runtime.util.mutableAttributes +import aws.smithy.kotlin.runtime.util.setIfValueNotNull + +internal fun credentials( + accessKeyId: String, + secretAccessKey: String, + sessionToken: String? = null, + expiration: Instant? = null, + providerName: String? = null, + accountId: String? = null, +): Credentials { + val attributes = when { + providerName != null || accountId != null -> mutableAttributes().apply { + setIfValueNotNull(IdentityAttributes.ProviderName, providerName) + setIfValueNotNull(AwsClientOption.AccountId, accountId) + } + else -> emptyAttributes() + } + return Credentials(accessKeyId, secretAccessKey, sessionToken, expiration, attributes = attributes) +} diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/profile/ProfileChain.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/profile/ProfileChain.kt index c154279e675..41f92abf6d1 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/profile/ProfileChain.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/profile/ProfileChain.kt @@ -6,10 +6,10 @@ package aws.sdk.kotlin.runtime.auth.credentials.profile import aws.sdk.kotlin.runtime.auth.credentials.ProviderConfigurationException +import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.sdk.kotlin.runtime.auth.credentials.profile.LeafProviderResult.Err import aws.sdk.kotlin.runtime.config.profile.AwsProfile import aws.sdk.kotlin.runtime.config.profile.AwsSharedConfig -import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials /** * A chain of profile providers @@ -121,6 +121,7 @@ internal const val WEB_IDENTITY_TOKEN_FILE = "web_identity_token_file" internal const val AWS_ACCESS_KEY_ID = "aws_access_key_id" internal const val AWS_SECRET_ACCESS_KEY = "aws_secret_access_key" internal const val AWS_SESSION_TOKEN = "aws_session_token" +internal const val AWS_ACCOUNT_ID = "aws_account_id" internal const val SSO_START_URL = "sso_start_url" internal const val SSO_REGION = "sso_region" @@ -254,13 +255,14 @@ private fun AwsProfile.processCreds(): LeafProviderResult? { private fun AwsProfile.staticCreds(): LeafProviderResult { val accessKeyId = getOrNull(AWS_ACCESS_KEY_ID) val secretKey = getOrNull(AWS_SECRET_ACCESS_KEY) + val accountId = getOrNull(AWS_ACCOUNT_ID) return when { accessKeyId == null && secretKey == null -> LeafProviderResult.Err("profile ($name) did not contain credential information") accessKeyId == null -> LeafProviderResult.Err("profile ($name) missing `aws_access_key_id`") secretKey == null -> LeafProviderResult.Err("profile ($name) missing `aws_secret_access_key`") else -> { val sessionToken = getOrNull(AWS_SESSION_TOKEN) - val provider = LeafProvider.AccessKey(Credentials(accessKeyId, secretKey, sessionToken)) + val provider = LeafProvider.AccessKey(credentials(accessKeyId, secretKey, sessionToken, accountId = accountId)) LeafProviderResult.Ok(provider) } } diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/AbstractAwsSdkClientFactory.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/AbstractAwsSdkClientFactory.kt index f5986ac8408..a1eed7bad4a 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/AbstractAwsSdkClientFactory.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/AbstractAwsSdkClientFactory.kt @@ -8,6 +8,7 @@ package aws.sdk.kotlin.runtime.config import aws.sdk.kotlin.runtime.client.AwsSdkClientConfig import aws.sdk.kotlin.runtime.config.endpoints.resolveUseDualStack import aws.sdk.kotlin.runtime.config.endpoints.resolveUseFips +import aws.sdk.kotlin.runtime.config.profile.AwsProfile import aws.sdk.kotlin.runtime.config.profile.AwsSharedConfig import aws.sdk.kotlin.runtime.config.profile.loadAwsSharedConfig import aws.sdk.kotlin.runtime.config.retries.resolveRetryStrategy @@ -76,7 +77,7 @@ public abstract class AbstractAwsSdkClientFactory< config.useDualStack = config.useDualStack ?: resolveUseDualStack(profile = profile) config.applicationId = config.applicationId ?: resolveUserAgentAppId(platform, profile) - finalizeConfig(builder, sharedConfig) + finalizeConfig(builder, sharedConfig, profile) } return builder.build() } @@ -84,5 +85,10 @@ public abstract class AbstractAwsSdkClientFactory< /** * Inject any client-specific config. */ - protected open suspend fun finalizeConfig(builder: TClientBuilder, sharedConfig: LazyAsyncValue) { } + protected open suspend fun finalizeConfig( + builder: TClientBuilder, + sharedConfig: LazyAsyncValue, + activeProfile: LazyAsyncValue, + ) { + } } diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/AwsSdkSetting.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/AwsSdkSetting.kt index 7e6e0fa4588..bf76994eef0 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/AwsSdkSetting.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/AwsSdkSetting.kt @@ -6,6 +6,7 @@ package aws.sdk.kotlin.runtime.config import aws.sdk.kotlin.runtime.InternalSdkApi +import aws.sdk.kotlin.runtime.config.endpoints.AccountIdEndpointMode import aws.sdk.kotlin.runtime.http.AWS_APP_ID_ENV import aws.sdk.kotlin.runtime.http.AWS_APP_ID_PROP import aws.smithy.kotlin.runtime.client.config.RetryMode @@ -47,6 +48,11 @@ public object AwsSdkSetting { */ public val AwsRegion: EnvironmentSetting = strEnvSetting("aws.region", "AWS_REGION") + /** + * Configure the AWS account ID + */ + public val AwsAccountId: EnvironmentSetting = strEnvSetting("aws.accountId", "AWS_ACCOUNT_ID") + /** * Configure the user agent app ID */ @@ -179,6 +185,11 @@ public object AwsSdkSetting { */ public val AwsIgnoreEndpointUrls: EnvironmentSetting = boolEnvSetting("aws.ignoreConfiguredEndpointUrls", "AWS_IGNORE_CONFIGURED_ENDPOINT_URLS") + + /** + * The mode to use when resolving endpoints that make use of the AWS account ID. + */ + public val AwsAccountIdEndpointMode: EnvironmentSetting = enumEnvSetting("aws.accountIdEndpointMode", "AWS_ACCOUNT_ID_ENDPOINT_MODE") } /** diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/endpoints/AccountIdEndpointMode.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/endpoints/AccountIdEndpointMode.kt new file mode 100644 index 00000000000..87660783414 --- /dev/null +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/endpoints/AccountIdEndpointMode.kt @@ -0,0 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.runtime.config.endpoints + +/** + * Controls how the account ID endpoint parameter is bound for services that support routing + * endpoints based on it. + */ +public enum class AccountIdEndpointMode { + /** + * Endpoint parameters are populated on a best effort basis. + */ + PREFERRED, + + /** + * Endpoint parameters are never populated even when they are available. + */ + DISABLED, + + /** + * Endpoint parameters are always populated and an error is raised if the AWS account ID + * is not available. + */ + REQUIRED, +} diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/endpoints/Endpoints.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/endpoints/Endpoints.kt deleted file mode 100644 index 28ef4a3a0ce..00000000000 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/endpoints/Endpoints.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.sdk.kotlin.runtime.config.endpoints - -import aws.sdk.kotlin.runtime.InternalSdkApi -import aws.sdk.kotlin.runtime.config.AwsSdkSetting -import aws.sdk.kotlin.runtime.config.profile.* -import aws.smithy.kotlin.runtime.config.resolve -import aws.smithy.kotlin.runtime.util.LazyAsyncValue -import aws.smithy.kotlin.runtime.util.PlatformProvider -import aws.smithy.kotlin.runtime.util.asyncLazy - -/** - * Attempts to resolve the enabled state of FIPS endpoints from the environment. - */ -@InternalSdkApi -public suspend fun resolveUseFips( - provider: PlatformProvider = PlatformProvider.System, - profile: LazyAsyncValue = asyncLazy { loadAwsSharedConfig(provider).activeProfile }, -): Boolean? = - AwsSdkSetting.AwsUseFipsEndpoint.resolve(provider) - ?: profile.get().useFips - -/** - * Attempts to resolve the enabled state of dual-stack endpoints from the environment. - */ -@InternalSdkApi -public suspend fun resolveUseDualStack( - provider: PlatformProvider = PlatformProvider.System, - profile: LazyAsyncValue = asyncLazy { loadAwsSharedConfig(provider).activeProfile }, -): Boolean? = - AwsSdkSetting.AwsUseDualStackEndpoint.resolve(provider) - ?: profile.get().useDualStack diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/endpoints/ResolveEndpointUrl.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/endpoints/Resolvers.kt similarity index 53% rename from aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/endpoints/ResolveEndpointUrl.kt rename to aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/endpoints/Resolvers.kt index 07035ee896d..7c10ee79bf4 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/endpoints/ResolveEndpointUrl.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/endpoints/Resolvers.kt @@ -4,14 +4,40 @@ */ package aws.sdk.kotlin.runtime.config.endpoints +import aws.sdk.kotlin.runtime.ConfigurationException import aws.sdk.kotlin.runtime.InternalSdkApi +import aws.sdk.kotlin.runtime.client.AwsClientOption import aws.sdk.kotlin.runtime.config.AwsSdkSetting import aws.sdk.kotlin.runtime.config.profile.* import aws.sdk.kotlin.runtime.config.resolveEndpointUrl import aws.smithy.kotlin.runtime.config.resolve import aws.smithy.kotlin.runtime.net.Url +import aws.smithy.kotlin.runtime.util.Attributes import aws.smithy.kotlin.runtime.util.LazyAsyncValue import aws.smithy.kotlin.runtime.util.PlatformProvider +import aws.smithy.kotlin.runtime.util.asyncLazy + +/** + * Attempts to resolve the enabled state of FIPS endpoints from the environment. + */ +@InternalSdkApi +public suspend fun resolveUseFips( + provider: PlatformProvider = PlatformProvider.System, + profile: LazyAsyncValue = asyncLazy { loadAwsSharedConfig(provider).activeProfile }, +): Boolean? = + AwsSdkSetting.AwsUseFipsEndpoint.resolve(provider) + ?: profile.get().useFips + +/** + * Attempts to resolve the enabled state of dual-stack endpoints from the environment. + */ +@InternalSdkApi +public suspend fun resolveUseDualStack( + provider: PlatformProvider = PlatformProvider.System, + profile: LazyAsyncValue = asyncLazy { loadAwsSharedConfig(provider).activeProfile }, +): Boolean? = + AwsSdkSetting.AwsUseDualStackEndpoint.resolve(provider) + ?: profile.get().useDualStack /** * Attempts to find the configured endpoint URL for a specific service. @@ -54,3 +80,24 @@ private suspend fun resolveIgnoreEndpointUrls( AwsSdkSetting.AwsIgnoreEndpointUrls.resolve(provider) ?: sharedConfig.get().activeProfile.ignoreEndpointUrls ?: false + +/** + * Resolve the [AccountIdEndpointMode] from the environment. + */ +@InternalSdkApi +public suspend fun resolveAccountIdEndpointMode( + provider: PlatformProvider = PlatformProvider.System, + profile: LazyAsyncValue = asyncLazy { loadAwsSharedConfig(provider).activeProfile }, +): AccountIdEndpointMode = + AwsSdkSetting.AwsAccountIdEndpointMode.resolve(provider) + ?: profile.get().accountIdEndpointMode ?: AccountIdEndpointMode.PREFERRED + +/** + * Resolve the account ID from the given [attributes] while respecting the given [AccountIdEndpointMode] + */ +@InternalSdkApi +public fun resolveAccountId(endpointMode: AccountIdEndpointMode, attributes: Attributes): String? = when (endpointMode) { + AccountIdEndpointMode.PREFERRED -> attributes.getOrNull(AwsClientOption.AccountId) + AccountIdEndpointMode.DISABLED -> null + AccountIdEndpointMode.REQUIRED -> attributes.getOrNull(AwsClientOption.AccountId) ?: throw ConfigurationException("AccountIdEndpointMode is set to required but no AWS account ID found") +} diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/profile/AwsProfile.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/profile/AwsProfile.kt index 33a3753c0ef..2631cee0691 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/profile/AwsProfile.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/profile/AwsProfile.kt @@ -7,6 +7,7 @@ package aws.sdk.kotlin.runtime.config.profile import aws.sdk.kotlin.runtime.ConfigurationException import aws.sdk.kotlin.runtime.InternalSdkApi +import aws.sdk.kotlin.runtime.config.endpoints.AccountIdEndpointMode import aws.smithy.kotlin.runtime.client.config.RetryMode import aws.smithy.kotlin.runtime.net.Url @@ -67,7 +68,7 @@ public val AwsProfile.sourceProfile: String? @InternalSdkApi public val AwsProfile.maxAttempts: Int? get() = getOrNull("max_attempts")?.run { - toIntOrNull() ?: throw ConfigurationException("Failed to parse maxAttempts $this as an integer") + toIntOrNull() ?: throw ConfigurationException("Failed to parse max_attempts $this as an integer") } /** @@ -84,7 +85,11 @@ public val AwsProfile.credentialProcess: String? public val AwsProfile.retryMode: RetryMode? get() = getOrNull("retry_mode")?.run { RetryMode.values().firstOrNull { it.name.equals(this, ignoreCase = true) } - ?: throw ConfigurationException("Retry mode $this is not supported, should be one of: ${RetryMode.values().joinToString(", ")}") + ?: throw ConfigurationException( + "retry_mode $this is not supported, should be one of: ${ + RetryMode.values().joinToString(", ") { it.name.lowercase() } + }", + ) } /** @@ -129,6 +134,20 @@ public val AwsProfile.servicesSection: String? public val AwsProfile.sdkUserAgentAppId: String? get() = getOrNull("sdk_ua_app_id") +/** + * Whether service clients should make requests to the dual-stack endpoint variant. + */ +@InternalSdkApi +public val AwsProfile.accountIdEndpointMode: AccountIdEndpointMode? + get() = getOrNull("account_id_endpoint_mode")?.run { + AccountIdEndpointMode.values().firstOrNull { it.name.equals(this, ignoreCase = true) } + ?: throw ConfigurationException( + "account_id_endpoint_mode $this is not supported, should be one of: ${ + AccountIdEndpointMode.values().joinToString(", ") { it.name.lowercase() } + }", + ) + } + /** * Parse a config value as a boolean, ignoring case. */ diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/ecs_assume_role/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/ecs_assume_role/test-case.json index 190bf1b8205..34730c3ab04 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/ecs_assume_role/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/ecs_assume_role/test-case.json @@ -6,7 +6,8 @@ "access_key_id": "ASIARCORRECT", "secret_access_key": "secretkeycorrect", "session_token": "tokencorrect", - "expiry": 1632249686 + "expiry": 1632249686, + "accountId": "130633740322" } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_assume_role/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_assume_role/test-case.json index a5e16e56bd3..8f59ddf5c61 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_assume_role/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_assume_role/test-case.json @@ -6,7 +6,8 @@ "access_key_id": "ASIARCORRECT", "secret_access_key": "secretkeycorrect", "session_token": "tokencorrect", - "expiry": 1632249686 + "expiry": 1632249686, + "accountId": "130633740322" } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/legacy_sso_role/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/legacy_sso_role/test-case.json index 844ecff01c4..2406302d49c 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/legacy_sso_role/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/legacy_sso_role/test-case.json @@ -6,7 +6,8 @@ "access_key_id": "ASIARCORRECT", "secret_access_key": "secretkeycorrect", "session_token": "tokencorrect", - "expiry": 1641240833 + "expiry": 1641240833, + "accountId": "123456789" } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/retry_on_error/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/retry_on_error/test-case.json index da1f2e553a5..0eccf448608 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/retry_on_error/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/retry_on_error/test-case.json @@ -6,7 +6,8 @@ "access_key_id": "ASIARTESTID", "secret_access_key": "TESTSECRETKEY", "session_token": "TESTSESSIONTOKEN", - "expiry": 1628193482 + "expiry": 1628193482, + "accountId": "123456789012" } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/sso_session/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/sso_session/test-case.json index 410884f7ecc..fdb622b1ff8 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/sso_session/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/sso_session/test-case.json @@ -6,7 +6,8 @@ "access_key_id": "ASIARCORRECT", "secret_access_key": "secretkeycorrect", "session_token": "tokencorrect", - "expiry": 1641240833 + "expiry": 1641240833, + "accountId": "123456789" } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_env/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_env/test-case.json index 156d088ee08..511e780ad17 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_env/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_env/test-case.json @@ -6,7 +6,8 @@ "access_key_id": "AKIDTEST", "secret_access_key": "SECRETKEYTEST", "session_token": "SESSIONTOKEN_TEST", - "expiry": 1629147173 + "expiry": 1629147173, + "accountId": "123456789012" } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_profile/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_profile/test-case.json index ac5fad29878..3d18b175471 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_profile/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_profile/test-case.json @@ -6,7 +6,8 @@ "access_key_id": "ASIARABCDEFGHIJKLMNOP", "secret_access_key": "TESTSECRET", "session_token": "TESTSESSIONTOKEN", - "expiry": 1629233704 + "expiry": 1629233704, + "accountId": "123456789012" } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_source_profile/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_source_profile/test-case.json index cafbfdf3c50..b1b0ea1e38b 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_source_profile/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_source_profile/test-case.json @@ -6,7 +6,8 @@ "access_key_id": "ASIARTESTID", "secret_access_key": "TESTSECRETKEY", "session_token": "TESTSESSIONTOKEN", - "expiry": 1628193482 + "expiry": 1628193482, + "accountId": "123456789012" } } } diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/arns/ArnTest.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/arns/ArnTest.kt new file mode 100644 index 00000000000..8ab6eff1f99 --- /dev/null +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/arns/ArnTest.kt @@ -0,0 +1,71 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.runtime.arns + +import kotlin.test.Test +import kotlin.test.assertEquals + +class ArnTest { + + @Test + fun testParse() { + val tests = listOf( + "arn:aws:iam::123456789012:user/johndoe" to Arn { + partition = "aws" + service = "iam" + accountId = "123456789012" + resource = "user/johndoe" + }, + "arn:aws:sns:us-east-1:123456789012:example-sns-topic-name" to Arn { + partition = "aws" + service = "sns" + region = "us-east-1" + accountId = "123456789012" + resource = "example-sns-topic-name" + }, + "arn:aws:ec2:us-east-1:123456789012:vpc/vpc-0e9801d129EXAMPLE" to Arn { + partition = "aws" + service = "ec2" + region = "us-east-1" + accountId = "123456789012" + resource = "vpc/vpc-0e9801d129EXAMPLE" + }, + "arn:aws:s3:::bucket/key" to Arn { + partition = "aws" + service = "s3" + resource = "bucket/key" + }, + "arn:aws:lambda:us-east-2:12345:function" to Arn { + partition = "aws" + service = "lambda" + region = "us-east-2" + accountId = "12345" + resource = "function" + }, + "arn:aws:lambda:us-east-2:12345:function:version" to Arn { + partition = "aws" + service = "lambda" + region = "us-east-2" + accountId = "12345" + resource = "function:version" + }, + ) + + tests.forEach { (arnString, arnExpected) -> + val parsed = Arn.parse(arnString) + assertEquals(arnExpected, parsed) + // test round trip + assertEquals(arnString, parsed.toString()) + } + } + + @Test + fun testEquivalence() { + val arn = "arn:aws:s3:us-east-1:12345678910:myresource:foobar" + val arn1 = Arn.parse(arn) + val arn2 = Arn.parse(arn) + assertEquals(arn1, arn2) + } +} diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/EcsCredentialsProviderTest.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/EcsCredentialsProviderTest.kt index d55a3f6873d..8e3d7ff3e2c 100644 --- a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/EcsCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/EcsCredentialsProviderTest.kt @@ -5,6 +5,7 @@ package aws.sdk.kotlin.runtime.auth.credentials +import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.sdk.kotlin.runtime.config.AwsSdkSetting import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProviderException @@ -26,6 +27,8 @@ import aws.smithy.kotlin.runtime.time.TimestampFormat import aws.smithy.kotlin.runtime.util.TestPlatformProvider import io.kotest.matchers.string.shouldContain import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -42,18 +45,20 @@ class EcsCredentialsProviderTest { "EcsContainer", ) - private fun ecsResponse(): HttpResponse { - val payload = """ - { - "Code" : "Success", - "LastUpdated" : "2021-09-17T20:57:08Z", - "Type" : "AWS-HMAC", - "AccessKeyId" : "AKID", - "SecretAccessKey" : "test-secret", - "Token" : "test-token", - "Expiration" : "${expectedExpiration.format(TimestampFormat.ISO_8601)}" - } - """.encodeToByteArray() + private fun ecsResponse(accountId: String? = null): HttpResponse { + val payload = buildJsonObject { + put("Code", "Success") + put("LastUpdated", "2021-09-17T20:57:08Z") + put("Type", "AWS-HMAC") + put("AccessKeyId", "AKID") + put("SecretAccessKey", "test-secret") + put("Token", "test-token") + put("Expiration", expectedExpiration.format(TimestampFormat.ISO_8601)) + if (accountId != null) { + put("AccountId", accountId) + } + }.toString().encodeToByteArray() + return HttpResponse(HttpStatusCode.OK, Headers.Empty, HttpBody.fromBytes(payload)) } @@ -495,4 +500,31 @@ class EcsCredentialsProviderTest { engine.assertRequests() } + + @Test + fun testAccountIdResolves() = runTest { + val engine = buildTestConnection { + expect( + ecsRequest("http://169.254.170.2/relative?foo=bar"), + ecsResponse("12345"), + ) + } + + val testPlatform = TestPlatformProvider( + env = mapOf(AwsSdkSetting.AwsContainerCredentialsRelativeUri.envVar to "/relative?foo=bar"), + ) + + val provider = EcsCredentialsProvider(testPlatform, engine) + val actual = provider.resolve() + val expected = credentials( + "AKID", + "test-secret", + "test-token", + expectedExpiration, + "EcsContainer", + "12345", + ) + assertEquals(expected, actual) + engine.assertRequests() + } } diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/EnvironmentCredentialsProviderTest.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/EnvironmentCredentialsProviderTest.kt index 666b3ee0dab..7c8c139ad00 100644 --- a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/EnvironmentCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/EnvironmentCredentialsProviderTest.kt @@ -5,8 +5,10 @@ package aws.sdk.kotlin.runtime.auth.credentials +import aws.sdk.kotlin.runtime.client.AwsClientOption import aws.sdk.kotlin.runtime.config.AwsSdkSetting import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials +import aws.smithy.kotlin.runtime.util.attributesOf import io.kotest.matchers.string.shouldContain import kotlinx.coroutines.test.runTest import kotlin.test.Test @@ -68,4 +70,22 @@ class EnvironmentCredentialsProviderTest { ).resolve() }.message.shouldContain("Missing value for environment variable `AWS_SECRET_ACCESS_KEY`") } + + @Test + fun testAccountIdIsResolved() = runTest { + val provider = provider( + AwsSdkSetting.AwsAccessKeyId.envVar to "abc", + AwsSdkSetting.AwsSecretAccessKey.envVar to "def", + AwsSdkSetting.AwsAccountId.envVar to "12345", + ) + + val actual = provider.resolve() + val expected = Credentials( + "abc", + "def", + providerName = "Environment", + attributes = attributesOf { AwsClientOption.AccountId to "12345" }, + ) + assertEquals(expected, actual) + } } diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/ProfileCredentialsProviderTest.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/ProfileCredentialsProviderTest.kt index 85889708e10..b3b517bba03 100644 --- a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/ProfileCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/ProfileCredentialsProviderTest.kt @@ -5,6 +5,7 @@ package aws.sdk.kotlin.runtime.auth.credentials +import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.sdk.kotlin.runtime.client.AwsClientOption import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.httptest.TestConnection @@ -102,7 +103,7 @@ class ProfileCredentialsProviderTest { fun testBasicAssumeRole() = runTest { // smoke test for assume role, more involved scenarios are tested through the default chain - val testArn = "arn:aws:iam:1234567/test-role" + val testArn = "arn:aws:iam::1234567:role/test-role" val testProvider = TestPlatformProvider( env = mapOf( "AWS_CONFIG_FILE" to "config", @@ -140,7 +141,7 @@ class ProfileCredentialsProviderTest { @Test fun testExplicitRegion() = runTest { - val testArn = "arn:aws:iam:1234567/test-role" + val testArn = "arn:aws:iam::1234567:role/test-role" val testProvider = TestPlatformProvider( env = mapOf( "AWS_CONFIG_FILE" to "config", @@ -181,7 +182,7 @@ class ProfileCredentialsProviderTest { @Test fun testProfileRegion() = runTest { - val testArn = "arn:aws:iam:1234567/test-role" + val testArn = "arn:aws:iam::1234567:role/test-role" val testProvider = TestPlatformProvider( env = mapOf( "AWS_CONFIG_FILE" to "config", @@ -222,7 +223,7 @@ class ProfileCredentialsProviderTest { @Test fun testAttributeRegion() = runTest { - val testArn = "arn:aws:iam:1234567/test-role" + val testArn = "arn:aws:iam::1234567:role/test-role" val testProvider = TestPlatformProvider( env = mapOf( "AWS_CONFIG_FILE" to "config", @@ -261,7 +262,7 @@ class ProfileCredentialsProviderTest { @Test fun testPlatformRegion() = runTest { - val testArn = "arn:aws:iam:1234567/test-role" + val testArn = "arn:aws:iam::1234567:role/test-role" val testProvider = TestPlatformProvider( env = mapOf( "AWS_CONFIG_FILE" to "config", @@ -293,4 +294,28 @@ class ProfileCredentialsProviderTest { val requests = testEngine.requests().first() assertEquals(Host.Domain("sts.us-west-2.amazonaws.com"), requests.actual.url.host) } + + @Test + fun testAccountId() = runTest { + val testProvider = TestPlatformProvider( + env = mapOf("AWS_CONFIG_FILE" to "config"), + fs = mapOf( + "config" to """ + [default] + aws_access_key_id = AKID-Default + aws_secret_access_key = Default-Secret + aws_account_id = 12345 + """.trimIndent(), + ), + ) + val testEngine = TestConnection() + + val provider = ProfileCredentialsProvider( + platformProvider = testProvider, + httpClient = testEngine, + ) + val actual = provider.resolve() + val expected = credentials("AKID-Default", "Default-Secret", accountId = "12345") + assertEquals(expected, actual) + } } diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/SsoCredentialsProviderTest.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/SsoCredentialsProviderTest.kt index 78e6503f4ab..37f2852ea7a 100644 --- a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/SsoCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/SsoCredentialsProviderTest.kt @@ -5,7 +5,7 @@ package aws.sdk.kotlin.runtime.auth.credentials -import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials +import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.smithy.kotlin.runtime.http.Headers import aws.smithy.kotlin.runtime.http.HttpBody import aws.smithy.kotlin.runtime.http.HttpStatusCode @@ -201,7 +201,7 @@ class SsoCredentialsProviderTest { ) val actual = provider.resolve() - val expected = Credentials("AKID", "secret", "session-token", expectedExpiration, "SSO") + val expected = credentials("AKID", "secret", "session-token", expectedExpiration, "SSO", "123456789") assertEquals(expected, actual) } } diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StaticCredentialsProviderTest.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StaticCredentialsProviderTest.kt index 43a33e1776f..8aa7b34e846 100644 --- a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StaticCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StaticCredentialsProviderTest.kt @@ -6,6 +6,7 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials +import aws.smithy.kotlin.runtime.auth.awscredentials.copy import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals @@ -22,6 +23,7 @@ class StaticCredentialsProviderTest { secretAccessKey = expected.secretAccessKey sessionToken = expected.sessionToken } - assertEquals(expected, provider2.resolve()) + val actual = provider2.resolve().copy(providerName = null) + assertEquals(expected, actual) } } diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StsTestUtils.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StsTestUtils.kt index b162306469a..e08868cfeec 100644 --- a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StsTestUtils.kt +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StsTestUtils.kt @@ -5,7 +5,7 @@ package aws.sdk.kotlin.runtime.auth.credentials -import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials +import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.smithy.kotlin.runtime.http.Headers import aws.smithy.kotlin.runtime.http.HttpBody import aws.smithy.kotlin.runtime.http.HttpMethod @@ -20,7 +20,7 @@ import aws.smithy.kotlin.runtime.util.text.urlEncodeComponent import kotlin.time.Duration.Companion.minutes object StsTestUtils { - const val ARN = "arn:aws:iam:1234567/test-role" + const val ARN = "arn:aws:iam::1234567:role/test-role" const val POLICY = "foo!" const val REGION = "us-east-2" const val SESSION_NAME = "aws-sdk-kotlin-1234567890" @@ -28,12 +28,13 @@ object StsTestUtils { val POLICY_ARNS = listOf("apple", "banana", "cherry") val TAGS = mapOf("foo" to "bar", "baz" to "qux") val EPOCH = Instant.fromIso8601("2020-10-16T03:56:00Z") - val CREDENTIALS = Credentials( + val CREDENTIALS = credentials( "AKIDTest", "test-secret", "test-token", EPOCH + 15.minutes, "AssumeRoleProvider", + "1234567", ) fun stsRequest(bodyParameters: Map) = HttpRequestBuilder().apply { diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProviderTest.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProviderTest.kt index 0c6bc2b0728..2194df2593a 100644 --- a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProviderTest.kt @@ -5,7 +5,7 @@ package aws.sdk.kotlin.runtime.auth.credentials -import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials +import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProviderException import aws.smithy.kotlin.runtime.http.Headers import aws.smithy.kotlin.runtime.http.HttpBody @@ -27,12 +27,13 @@ import kotlin.time.Duration.Companion.minutes private const val TOKEN_PATH = "token-path" private const val TOKEN_VALUE = "jwt-token" -private val CREDENTIALS = Credentials( +private val CREDENTIALS = credentials( "AKIDTest", "test-secret", "test-token", StsTestUtils.EPOCH + 15.minutes, "WebIdentityToken", + "1234567", ) class StsWebIdentityCredentialsProviderTest { diff --git a/aws-runtime/aws-config/jvm/test/aws/sdk/kotlin/runtime/auth/credentials/DefaultChainCredentialsProviderTest.kt b/aws-runtime/aws-config/jvm/test/aws/sdk/kotlin/runtime/auth/credentials/DefaultChainCredentialsProviderTest.kt index 91af881fb36..af2ab69bd44 100644 --- a/aws-runtime/aws-config/jvm/test/aws/sdk/kotlin/runtime/auth/credentials/DefaultChainCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/jvm/test/aws/sdk/kotlin/runtime/auth/credentials/DefaultChainCredentialsProviderTest.kt @@ -5,7 +5,9 @@ package aws.sdk.kotlin.runtime.auth.credentials +import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials +import aws.smithy.kotlin.runtime.auth.awscredentials.copy import aws.smithy.kotlin.runtime.httptest.TestConnection import aws.smithy.kotlin.runtime.time.Instant import aws.smithy.kotlin.runtime.util.Filesystem @@ -89,11 +91,12 @@ class DefaultChainCredentialsProviderTest { return when { "Ok" in result -> { val o = checkNotNull(result["Ok"]).jsonObject - val creds = Credentials( + val creds = credentials( checkNotNull(o["access_key_id"]).jsonPrimitive.content, checkNotNull(o["secret_access_key"]).jsonPrimitive.content, o["session_token"]?.jsonPrimitive?.content, o["expiry"]?.jsonPrimitive?.longOrNull?.let { Instant.fromEpochSeconds(it) }, + accountId = o["accountId"]?.jsonPrimitive?.content, ) Ok(name, docs, creds) } diff --git a/aws-runtime/aws-config/jvm/test/aws/sdk/kotlin/runtime/auth/credentials/ProcessCredentialsProviderTest.kt b/aws-runtime/aws-config/jvm/test/aws/sdk/kotlin/runtime/auth/credentials/ProcessCredentialsProviderTest.kt index 1be45fe083b..6752529dd13 100644 --- a/aws-runtime/aws-config/jvm/test/aws/sdk/kotlin/runtime/auth/credentials/ProcessCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/jvm/test/aws/sdk/kotlin/runtime/auth/credentials/ProcessCredentialsProviderTest.kt @@ -4,6 +4,7 @@ */ package aws.sdk.kotlin.runtime.auth.credentials +import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProviderException import aws.smithy.kotlin.runtime.time.Instant @@ -136,4 +137,37 @@ class ProcessCredentialsProviderTest { val ex = assertFailsWith { processCredentialsProvider.resolve() } assertContains(ex.message!!, stderr) // the exception message should contain the program's stderr } + + @Test + fun testSuccessWithAccountId() = runTest { + mockkStatic(::executeCommand) + coEvery { executeCommand(any(), any(), any(), any(), any()) }.returns( + Pair( + 0, + """ + { + "Version": 1, + "AccessKeyId": "AccessKeyId", + "SecretAccessKey": "SecretAccessKey", + "AccountId": "12345", + "SessionToken": "SessionToken", + "Expiration": "2022-10-14T00:00:00Z" + } + """.trimIndent(), + ), + ) + + val expectedCredentials = credentials( + accessKeyId = "AccessKeyId", + secretAccessKey = "SecretAccessKey", + sessionToken = "SessionToken", + expiration = Instant.fromEpochSeconds(1665705600), + providerName = "Process", + accountId = "12345", + ) + + val processCredentialsProvider = ProcessCredentialsProvider("anyString") + val actualCredentials = processCredentialsProvider.resolve() + assertEquals(expectedCredentials, actualCredentials) + } } diff --git a/aws-runtime/aws-core/api/aws-core.api b/aws-runtime/aws-core/api/aws-core.api index e4b891b34ee..9bf831d3c1e 100644 --- a/aws-runtime/aws-core/api/aws-core.api +++ b/aws-runtime/aws-core/api/aws-core.api @@ -34,6 +34,7 @@ public abstract interface annotation class aws/sdk/kotlin/runtime/InternalSdkApi public final class aws/sdk/kotlin/runtime/client/AwsClientOption { public static final field INSTANCE Laws/sdk/kotlin/runtime/client/AwsClientOption; + public final fun getAccountId ()Laws/smithy/kotlin/runtime/util/AttributeKey; public final fun getRegion ()Laws/smithy/kotlin/runtime/util/AttributeKey; } diff --git a/aws-runtime/aws-core/common/src/aws/sdk/kotlin/runtime/client/AwsClientOption.kt b/aws-runtime/aws-core/common/src/aws/sdk/kotlin/runtime/client/AwsClientOption.kt index 29876eacc57..085c08f09bb 100644 --- a/aws-runtime/aws-core/common/src/aws/sdk/kotlin/runtime/client/AwsClientOption.kt +++ b/aws-runtime/aws-core/common/src/aws/sdk/kotlin/runtime/client/AwsClientOption.kt @@ -15,5 +15,10 @@ public object AwsClientOption { * The AWS region the client should use. Note this is not always the same as [AwsSigningAttributes.SigningRegion] in * the case of global services like IAM */ - public val Region: AttributeKey = AttributeKey("AwsRegion") + public val Region: AttributeKey = AttributeKey("aws.sdk.kotlin#AwsRegion") + + /** + * The ID of the AWS account requests are routed to. + */ + public val AccountId: AttributeKey = AttributeKey("aws.sdk.kotlin#AccountId") } diff --git a/aws-runtime/aws-core/common/test/FlowUtilTest.kt b/aws-runtime/aws-core/common/test/aws/sdk/kotlin/runtime/FlowUtilTest.kt similarity index 94% rename from aws-runtime/aws-core/common/test/FlowUtilTest.kt rename to aws-runtime/aws-core/common/test/aws/sdk/kotlin/runtime/FlowUtilTest.kt index fa1197d5160..47fa6d4487e 100644 --- a/aws-runtime/aws-core/common/test/FlowUtilTest.kt +++ b/aws-runtime/aws-core/common/test/aws/sdk/kotlin/runtime/FlowUtilTest.kt @@ -2,8 +2,8 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -import aws.sdk.kotlin.runtime.InternalSdkApi -import aws.sdk.kotlin.runtime.mergeSequential +package aws.sdk.kotlin.runtime + import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList diff --git a/codegen/protocol-tests/build.gradle.kts b/codegen/protocol-tests/build.gradle.kts index 94df3375796..19f61dc30ac 100644 --- a/codegen/protocol-tests/build.gradle.kts +++ b/codegen/protocol-tests/build.gradle.kts @@ -66,6 +66,9 @@ codegen { "aws.sdk.kotlin.runtime.InternalSdkApi", ) } + apiSettings { + defaultValueSerializationMode = "always" + } } } } diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt index 8ed77606042..e6b138ff481 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt @@ -38,7 +38,10 @@ object AwsRuntimeTypes { val AbstractAwsSdkClientFactory = symbol("AbstractAwsSdkClientFactory", "config") object Endpoints : RuntimeTypePackage(AwsKotlinDependency.AWS_CONFIG, "config.endpoints") { + val AccountIdEndpointMode = symbol("AccountIdEndpointMode") val resolveEndpointUrl = symbol("resolveEndpointUrl") + val resolveAccountId = symbol("resolveAccountId") + val resolveAccountIdEndpointMode = symbol("resolveAccountIdEndpointMode") } object Profile : RuntimeTypePackage(AwsKotlinDependency.AWS_CONFIG, "config.profile") { diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/ServiceClientCompanionObjectWriter.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/ServiceClientCompanionObjectWriter.kt index 65f18d85c35..918d0bb5c05 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/ServiceClientCompanionObjectWriter.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/ServiceClientCompanionObjectWriter.kt @@ -15,9 +15,7 @@ import software.amazon.smithy.kotlin.codegen.utils.toPascalCase * * Includes the ability to extend the config finalizer, which by default handles resolution of endpoint url config. */ -class ServiceClientCompanionObjectWriter( - private val extendFinalizeConfig: (KotlinWriter.() -> Unit)? = null, -) : SectionWriter { +class ServiceClientCompanionObjectWriter : SectionWriter { override fun write(writer: KotlinWriter, previousValue: String?) { val serviceSymbol = writer.getContextValue(ServiceClientGenerator.Sections.CompanionObject.ServiceSymbol) @@ -41,17 +39,14 @@ class ServiceClientCompanionObjectWriter( private fun KotlinWriter.writeFinalizeConfig() { withBlock( - "override suspend fun finalizeConfig(builder: Builder, sharedConfig: #T<#T>) {", + "override suspend fun finalizeConfig(builder: Builder, sharedConfig: #1T<#2T>, activeProfile: #1T<#3T>) {", "}", RuntimeTypes.Core.Utils.LazyAsyncValue, AwsRuntimeTypes.Config.Profile.AwsSharedConfig, + AwsRuntimeTypes.Config.Profile.AwsProfile, ) { - declareSection(ServiceClientGenerator.Sections.FinalizeConfig) writeResolveEndpointUrl() - extendFinalizeConfig?.let { - write("") - it() - } + declareSection(ServiceClientGenerator.Sections.FinalizeConfig) } } diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/AccountIdEndpointBuiltinCustomization.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/AccountIdEndpointBuiltinCustomization.kt new file mode 100644 index 00000000000..00ead3262a7 --- /dev/null +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/AccountIdEndpointBuiltinCustomization.kt @@ -0,0 +1,57 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.codegen.customization + +import aws.sdk.kotlin.codegen.AwsRuntimeTypes +import aws.sdk.kotlin.codegen.protocols.endpoints.AwsBuiltins +import software.amazon.smithy.kotlin.codegen.KotlinSettings +import software.amazon.smithy.kotlin.codegen.core.CodegenContext +import software.amazon.smithy.kotlin.codegen.integration.AppendingSectionWriter +import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration +import software.amazon.smithy.kotlin.codegen.integration.SectionWriterBinding +import software.amazon.smithy.kotlin.codegen.model.expectShape +import software.amazon.smithy.kotlin.codegen.model.getEndpointRules +import software.amazon.smithy.kotlin.codegen.rendering.ServiceClientGenerator +import software.amazon.smithy.kotlin.codegen.rendering.util.ConfigProperty +import software.amazon.smithy.kotlin.codegen.rendering.util.ConfigPropertyType +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.shapes.ServiceShape + +/** + * Registers support for the `AWS::Auth::AccountId` endpoint builtin + */ +class AccountIdEndpointBuiltinCustomization : KotlinIntegration { + companion object { + + val AccountIdEndpointModeProp = ConfigProperty { + name = "accountIdEndpointMode" + symbol = AwsRuntimeTypes.Config.Endpoints.AccountIdEndpointMode + documentation = """ + Control the way account ID is bound to the endpoint resolver parameters. + Defaults to [AccountIdEndpointMode.PREFERRED]. + """.trimIndent() + propertyType = ConfigPropertyType.RequiredWithDefault("AccountIdEndpointMode.PREFERRED") + } + } + + override fun enabledForService(model: Model, settings: KotlinSettings): Boolean { + val rules = model.expectShape(settings.service).getEndpointRules() + return rules?.parameters?.find { it.isBuiltIn && it.builtIn.get() == AwsBuiltins.ACCOUNT_ID } != null + } + + override val sectionWriters: List + get() = listOf(SectionWriterBinding(ServiceClientGenerator.Sections.FinalizeConfig, resolveAccountIdEndpointModeSectionWriter)) + + private val resolveAccountIdEndpointModeSectionWriter = AppendingSectionWriter { writer -> + writer.write( + "builder.config.#L = #T(profile = activeProfile)", + AccountIdEndpointModeProp.propertyName, + AwsRuntimeTypes.Config.Endpoints.resolveAccountIdEndpointMode, + ) + } + + override fun additionalServiceConfigProps(ctx: CodegenContext): List = + listOf(AccountIdEndpointModeProp) +} diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/ClockSkew.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/ClockSkew.kt index 3a8372fbb70..863ee5e75d5 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/ClockSkew.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/ClockSkew.kt @@ -5,8 +5,8 @@ package aws.sdk.kotlin.codegen.customization import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes +import software.amazon.smithy.kotlin.codegen.integration.AppendingSectionWriter import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration -import software.amazon.smithy.kotlin.codegen.integration.SectionWriter import software.amazon.smithy.kotlin.codegen.integration.SectionWriterBinding import software.amazon.smithy.kotlin.codegen.rendering.ServiceClientGenerator @@ -17,7 +17,7 @@ class ClockSkew : KotlinIntegration { override val sectionWriters: List get() = listOf(SectionWriterBinding(ServiceClientGenerator.Sections.FinalizeConfig, clockSkewSectionWriter)) - private val clockSkewSectionWriter = SectionWriter { writer, _ -> + private val clockSkewSectionWriter = AppendingSectionWriter { writer -> val interceptorSymbol = RuntimeTypes.AwsProtocolCore.ClockSkewInterceptor writer.write("builder.config.interceptors.add(0, #T())", interceptorSymbol) } diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/ClientConfigIntegration.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/ClientConfigIntegration.kt index b81781e24de..5926c47e98e 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/ClientConfigIntegration.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/ClientConfigIntegration.kt @@ -4,9 +4,9 @@ */ package aws.sdk.kotlin.codegen.customization.s3 -import aws.sdk.kotlin.codegen.ServiceClientCompanionObjectWriter import software.amazon.smithy.kotlin.codegen.KotlinSettings import software.amazon.smithy.kotlin.codegen.core.CodegenContext +import software.amazon.smithy.kotlin.codegen.integration.AppendingSectionWriter import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration import software.amazon.smithy.kotlin.codegen.integration.SectionWriterBinding import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes @@ -90,16 +90,14 @@ class ClientConfigIntegration : KotlinIntegration { ) override val sectionWriters: List - get() = listOf( - SectionWriterBinding(ServiceClientGenerator.Sections.CompanionObject, serviceClientCompanionObjectWriter), - ) + get() = listOf(SectionWriterBinding(ServiceClientGenerator.Sections.FinalizeConfig, finalizeS3ConfigWriter)) // add S3-specific config finalization - private val serviceClientCompanionObjectWriter = ServiceClientCompanionObjectWriter { + private val finalizeS3ConfigWriter = AppendingSectionWriter { writer -> val finalizeS3Config = buildSymbol { name = "finalizeS3Config" namespace = "aws.sdk.kotlin.services.s3.internal" } - write("#T(builder, sharedConfig)", finalizeS3Config) + writer.write("#T(builder, sharedConfig)", finalizeS3Config) } } diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/endpoints/BindAwsEndpointBuiltins.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/endpoints/BindAwsEndpointBuiltins.kt index 4ef6c0ba76b..e8f1b2e3563 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/endpoints/BindAwsEndpointBuiltins.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/endpoints/BindAwsEndpointBuiltins.kt @@ -4,9 +4,12 @@ */ package aws.sdk.kotlin.codegen.protocols.endpoints +import aws.sdk.kotlin.codegen.AwsRuntimeTypes import aws.sdk.kotlin.codegen.AwsServiceConfigIntegration +import aws.sdk.kotlin.codegen.customization.AccountIdEndpointBuiltinCustomization import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.kotlin.codegen.core.KotlinWriter +import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes import software.amazon.smithy.kotlin.codegen.core.withBlock import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration import software.amazon.smithy.kotlin.codegen.model.buildSymbol @@ -29,7 +32,7 @@ class BindAwsEndpointBuiltins : KotlinIntegration { writer: KotlinWriter, ) { val builtins = rules.parameters?.toList()?.filter(Parameter::isBuiltIn) ?: return - writer.write("#T(config)", bindAwsBuiltinsSymbol(ctx, builtins)) + writer.write("#T(config, request)", bindAwsBuiltinsSymbol(ctx, builtins)) } } @@ -39,29 +42,38 @@ class BindAwsEndpointBuiltins : KotlinIntegration { */ fun renderBindAwsBuiltins(ctx: ProtocolGenerator.GenerationContext, writer: KotlinWriter, builtinParams: List) { writer.withBlock( - "private fun #T.Builder.bindAwsBuiltins(config: #T.Config) {", + "private fun #T.Builder.bindAwsBuiltins(config: #T.Config, request: #T) {", "}", EndpointParametersGenerator.getSymbol(ctx.settings), ctx.symbolProvider.toSymbol(ctx.service), + RuntimeTypes.HttpClient.Operation.ResolveEndpointRequest, ) { builtinParams.forEach { when (it.builtIn.get()) { - "AWS::Region" -> renderBasicConfigBinding(writer, it, AwsServiceConfigIntegration.RegionProp.propertyName) - "AWS::UseFIPS" -> renderBasicConfigBinding(writer, it, AwsServiceConfigIntegration.UseFipsProp.propertyName) - "AWS::UseDualStack" -> renderBasicConfigBinding(writer, it, AwsServiceConfigIntegration.UseDualStackProp.propertyName) + AwsBuiltins.REGION -> renderBasicConfigBinding(writer, it, AwsServiceConfigIntegration.RegionProp.propertyName) + AwsBuiltins.USE_FIPS -> renderBasicConfigBinding(writer, it, AwsServiceConfigIntegration.UseFipsProp.propertyName) + AwsBuiltins.USE_DUAL_STACK -> renderBasicConfigBinding(writer, it, AwsServiceConfigIntegration.UseDualStackProp.propertyName) - "AWS::S3::Accelerate" -> renderBasicConfigBinding(writer, it, S3ClientConfigIntegration.EnableAccelerateProp.propertyName) - "AWS::S3::ForcePathStyle" -> renderBasicConfigBinding(writer, it, S3ClientConfigIntegration.ForcePathStyleProp.propertyName) - "AWS::S3::DisableMultiRegionAccessPoints" -> renderBasicConfigBinding(writer, it, S3ClientConfigIntegration.DisableMrapProp.propertyName) - "AWS::S3::UseArnRegion" -> renderBasicConfigBinding(writer, it, S3ClientConfigIntegration.UseArnRegionProp.propertyName) - "AWS::S3Control::UseArnRegion" -> renderBasicConfigBinding(writer, it, S3ControlClientConfigIntegration.UseArnRegionProp.propertyName) + AwsBuiltins.S3_ACCELERATE -> renderBasicConfigBinding(writer, it, S3ClientConfigIntegration.EnableAccelerateProp.propertyName) + AwsBuiltins.S3_FORCE_PATH_STYLE -> renderBasicConfigBinding(writer, it, S3ClientConfigIntegration.ForcePathStyleProp.propertyName) + AwsBuiltins.S3_DISABLE_MRAP -> renderBasicConfigBinding(writer, it, S3ClientConfigIntegration.DisableMrapProp.propertyName) + AwsBuiltins.S3_USE_ARN_REGION -> renderBasicConfigBinding(writer, it, S3ClientConfigIntegration.UseArnRegionProp.propertyName) + AwsBuiltins.S3_CONTROL_USE_ARN_REGION -> renderBasicConfigBinding(writer, it, S3ControlClientConfigIntegration.UseArnRegionProp.propertyName) - "SDK::Endpoint" -> + AwsBuiltins.SDK_ENDPOINT -> writer.write("#L = config.#L?.toString()", it.defaultName(), AwsServiceConfigIntegration.EndpointUrlProp.propertyName) // as a newer SDK we do NOT support these values, they are always false - "AWS::S3::UseGlobalEndpoint", "AWS::STS::UseGlobalEndpoint" -> + AwsBuiltins.S3_USE_GLOBAL_ENDPOINT, AwsBuiltins.STS_USE_GLOBAL_ENDPOINT -> writer.write("#L = false", it.defaultName()) + + AwsBuiltins.ACCOUNT_ID -> + writer.write( + "#L = #T(config.#L, request.identity.attributes)", + it.defaultName(), + AwsRuntimeTypes.Config.Endpoints.resolveAccountId, + AccountIdEndpointBuiltinCustomization.AccountIdEndpointModeProp.propertyName, + ) } } } diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/endpoints/Builtins.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/endpoints/Builtins.kt new file mode 100644 index 00000000000..4eb068a5ebe --- /dev/null +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/endpoints/Builtins.kt @@ -0,0 +1,20 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.codegen.protocols.endpoints + +object AwsBuiltins { + const val ACCOUNT_ID = "AWS::Auth::AccountId" + const val REGION = "AWS::Region" + const val USE_FIPS = "AWS::UseFIPS" + const val USE_DUAL_STACK = "AWS::UseDualStack" + const val SDK_ENDPOINT = "SDK::Endpoint" + const val S3_ACCELERATE = "AWS::S3::Accelerate" + const val S3_FORCE_PATH_STYLE = "AWS::S3::ForcePathStyle" + const val S3_DISABLE_MRAP = "AWS::S3::DisableMultiRegionAccessPoints" + const val S3_USE_ARN_REGION = "AWS::S3::UseArnRegion" + const val S3_CONTROL_USE_ARN_REGION = "AWS::S3Control::UseArnRegion" + const val S3_USE_GLOBAL_ENDPOINT = "AWS::S3::UseGlobalEndpoint" + const val STS_USE_GLOBAL_ENDPOINT = "AWS::STS::UseGlobalEndpoint" +} diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration b/codegen/smithy-aws-kotlin-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration index 1199832f456..d21433dc632 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration +++ b/codegen/smithy-aws-kotlin-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration @@ -1,33 +1,34 @@ aws.sdk.kotlin.codegen.SdkProtocolGeneratorSupplier aws.sdk.kotlin.codegen.AddUserAgentMetadataIntegration -aws.sdk.kotlin.codegen.AwsRetryHeaderIntegration +aws.sdk.kotlin.codegen.AwsServiceConfigIntegration aws.sdk.kotlin.codegen.customization.s3.S3GeneratorSupplier aws.sdk.kotlin.codegen.GradleGenerator -aws.sdk.kotlin.codegen.AwsServiceConfigIntegration -aws.sdk.kotlin.codegen.customization.s3.S3SigningConfig -aws.sdk.kotlin.codegen.customization.s3.S3ErrorMetadataIntegration +aws.sdk.kotlin.codegen.AwsRetryHeaderIntegration +aws.sdk.kotlin.codegen.customization.ClockSkew +aws.sdk.kotlin.codegen.customization.AccountIdEndpointBuiltinCustomization aws.sdk.kotlin.codegen.customization.PresignableModelIntegration -aws.sdk.kotlin.codegen.PresignerGenerator -aws.sdk.kotlin.codegen.customization.apigateway.ApiGatewayAddAcceptHeader -aws.sdk.kotlin.codegen.customization.glacier.GlacierAddVersionHeader -aws.sdk.kotlin.codegen.customization.glacier.GlacierAccountIdDefault -aws.sdk.kotlin.codegen.customization.polly.PollyPresigner -aws.sdk.kotlin.codegen.customization.glacier.GlacierBodyChecksum -aws.sdk.kotlin.codegen.customization.machinelearning.MachineLearningEndpointCustomization aws.sdk.kotlin.codegen.customization.BackfillOptionalAuth +aws.sdk.kotlin.codegen.protocols.endpoints.BindAwsEndpointBuiltins +aws.sdk.kotlin.codegen.PresignerGenerator aws.sdk.kotlin.codegen.customization.flexiblechecksums.FlexibleChecksumsRequest aws.sdk.kotlin.codegen.customization.flexiblechecksums.FlexibleChecksumsResponse -aws.sdk.kotlin.codegen.customization.route53.TrimResourcePrefix -aws.sdk.kotlin.codegen.customization.route53.ChangeResourceRecordSetsUnmarshallingIntegration +aws.sdk.kotlin.codegen.customization.s3.S3SigningConfig +aws.sdk.kotlin.codegen.customization.s3.S3ErrorMetadataIntegration aws.sdk.kotlin.codegen.customization.s3.ClientConfigIntegration aws.sdk.kotlin.codegen.customization.s3.ContinueIntegration aws.sdk.kotlin.codegen.customization.s3.GetObjectResponseLengthValidationIntegration aws.sdk.kotlin.codegen.customization.s3.HttpPathFilter aws.sdk.kotlin.codegen.customization.s3.TruncatablePaginationIntegration -aws.sdk.kotlin.codegen.customization.s3control.HostPrefixFilter -aws.sdk.kotlin.codegen.customization.s3control.ClientConfigIntegration -aws.sdk.kotlin.codegen.protocols.endpoints.BindAwsEndpointBuiltins aws.sdk.kotlin.codegen.customization.s3.HostPrefixRequestRouteFilter aws.sdk.kotlin.codegen.customization.s3.UnwrappedXmlOutputIntegration -aws.sdk.kotlin.codegen.customization.ClockSkew +aws.sdk.kotlin.codegen.customization.s3control.HostPrefixFilter +aws.sdk.kotlin.codegen.customization.s3control.ClientConfigIntegration +aws.sdk.kotlin.codegen.customization.apigateway.ApiGatewayAddAcceptHeader +aws.sdk.kotlin.codegen.customization.glacier.GlacierAddVersionHeader +aws.sdk.kotlin.codegen.customization.glacier.GlacierAccountIdDefault +aws.sdk.kotlin.codegen.customization.glacier.GlacierBodyChecksum +aws.sdk.kotlin.codegen.customization.polly.PollyPresigner +aws.sdk.kotlin.codegen.customization.machinelearning.MachineLearningEndpointCustomization +aws.sdk.kotlin.codegen.customization.route53.TrimResourcePrefix +aws.sdk.kotlin.codegen.customization.route53.ChangeResourceRecordSetsUnmarshallingIntegration aws.sdk.kotlin.codegen.customization.ec2.EC2MakePrimitivesOptional diff --git a/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/aws/sdk/kotlin/codegen/ServiceClientCompanionObjectWriterTest.kt b/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/aws/sdk/kotlin/codegen/ServiceClientCompanionObjectWriterTest.kt index 69b1d3eea40..9a5d4c0fe59 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/aws/sdk/kotlin/codegen/ServiceClientCompanionObjectWriterTest.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/test/kotlin/aws/sdk/kotlin/codegen/ServiceClientCompanionObjectWriterTest.kt @@ -27,7 +27,7 @@ class ServiceClientCompanionObjectWriterTest { @JvmStatic override fun builder(): Builder = Builder() - override suspend fun finalizeConfig(builder: Builder, sharedConfig: LazyAsyncValue) { + override suspend fun finalizeConfig(builder: Builder, sharedConfig: LazyAsyncValue, activeProfile: LazyAsyncValue) { builder.config.endpointUrl = builder.config.endpointUrl ?: resolveEndpointUrl( sharedConfig, "TestGenerator", @@ -40,38 +40,4 @@ class ServiceClientCompanionObjectWriterTest { writer.toString().shouldContainOnlyOnceWithDiff(expected) } - - @Test - fun testExtendFinalizeConfig() { - val writer = KotlinWriter(TestModelDefault.NAMESPACE) - writer.putContext( - mapOf( - "ServiceSymbol" to buildSymbol { name = "TestGeneratorClient" }, - "SdkId" to "Test Generator", - ), - ) - ServiceClientCompanionObjectWriter { - write("// extended") - }.write(writer, null) - - val expected = """ - public companion object : AbstractAwsSdkClientFactory() { - @JvmStatic - override fun builder(): Builder = Builder() - - override suspend fun finalizeConfig(builder: Builder, sharedConfig: LazyAsyncValue) { - builder.config.endpointUrl = builder.config.endpointUrl ?: resolveEndpointUrl( - sharedConfig, - "TestGenerator", - "TEST_GENERATOR", - "test_generator", - ) - - // extended - } - } - """.trimIndent() - - writer.toString().shouldContainOnlyOnceWithDiff(expected) - } } diff --git a/gradle.properties b/gradle.properties index 2abf04adf5e..d2a793c8f15 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,10 +9,10 @@ org.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=2G sdkVersion=0.34.10-SNAPSHOT # kotlin -kotlinVersion=1.9.10 +kotlinVersion=1.9.20 # dokka config (values specified at build-time as needed) smithyKotlinDocBaseUrl=https://sdk.amazonaws.com/kotlin/api/smithy-kotlin/api/$smithyKotlinRuntimeVersion/ # atomicfu -kotlinx.atomicfu.enableJvmIrTransformation=true \ No newline at end of file +kotlinx.atomicfu.enableJvmIrTransformation=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 087f2001b6c..6a1cc45d1b1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,26 +1,26 @@ [versions] -kotlin-version = "1.9.10" -dokka-version = "1.9.0" +kotlin-version = "1.9.20" +dokka-version = "1.9.10" # libs coroutines-version = "1.7.3" atomicfu-version = "0.22.0" # smithy-kotlin codegen and runtime are versioned separately -smithy-kotlin-runtime-version = "0.28.2" -smithy-kotlin-codegen-version = "0.28.2" +smithy-kotlin-runtime-version = "0.29.0" +smithy-kotlin-codegen-version = "0.29.0" # codegen -smithy-version = "1.39.0" -smithy-gradle-version = "0.6.0" +smithy-version = "1.41.1" +smithy-gradle-version = "0.7.0" # testing -junit-version = "5.9.2" -kotest-version = "5.7.2" +junit-version = "5.10.1" +kotest-version = "5.8.0" kotlinx-benchmark-version = "0.4.9" kotlinx-serialization-version = "1.6.0" -mockk-version = "1.13.3" -slf4j-version = "2.0.6" +mockk-version = "1.13.7" +slf4j-version = "2.0.9" [libraries] diff --git a/tests/benchmarks/service-benchmarks/jvm/src/aws/sdk/kotlin/benchmarks/service/BenchmarkHarness.kt b/tests/benchmarks/service-benchmarks/jvm/src/aws/sdk/kotlin/benchmarks/service/BenchmarkHarness.kt index fe16cb71166..4f523a93f2d 100644 --- a/tests/benchmarks/service-benchmarks/jvm/src/aws/sdk/kotlin/benchmarks/service/BenchmarkHarness.kt +++ b/tests/benchmarks/service-benchmarks/jvm/src/aws/sdk/kotlin/benchmarks/service/BenchmarkHarness.kt @@ -9,7 +9,6 @@ import aws.sdk.kotlin.benchmarks.service.telemetry.MetricSummary import aws.smithy.kotlin.runtime.client.SdkClient import aws.smithy.kotlin.runtime.io.use import kotlin.time.Duration.Companion.seconds -import kotlin.time.ExperimentalTime import kotlin.time.TimeSource val DEFAULT_WARMUP_TIME = 5.seconds @@ -91,7 +90,6 @@ class BenchmarkHarness { } } -@OptIn(ExperimentalTime::class) private inline fun forAtLeast(runMode: RunMode, block: () -> Unit) { val start = TimeSource.Monotonic.markNow()