Skip to content

Commit 3743d98

Browse files
authored
feat: make region optional (#970)
1 parent 2cbe2bb commit 3743d98

File tree

10 files changed

+42
-34
lines changed

10 files changed

+42
-34
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "137f993a-88e6-4158-b55d-9ae0076d8501",
3+
"type": "feature",
4+
"description": "Make `region` an optional client config parameter to support multi-region use cases",
5+
"issues": [
6+
"awslabs/aws-sdk-kotlin#969"
7+
]
8+
}

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ProfileCredentialsProvider.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public class ProfileCredentialsProvider(
124124
}
125125
}
126126

127-
private suspend fun LeafProvider.toCredentialsProvider(region: LazyAsyncValue<String>): CredentialsProvider =
127+
private suspend fun LeafProvider.toCredentialsProvider(region: LazyAsyncValue<String?>): CredentialsProvider =
128128
when (this) {
129129
is LeafProvider.NamedSource -> namedProviders[name]
130130
?: throw ProviderConfigurationException("unknown credentials source: $name")
@@ -164,7 +164,7 @@ public class ProfileCredentialsProvider(
164164

165165
private suspend fun RoleArn.toCredentialsProvider(
166166
creds: Credentials,
167-
region: LazyAsyncValue<String>,
167+
region: LazyAsyncValue<String?>,
168168
): CredentialsProvider = StsAssumeRoleCredentialsProvider(
169169
credentialsProvider = StaticCredentialsProvider(creds),
170170
roleArn = roleArn,

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProvider.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ private const val PROVIDER_NAME = "WebIdentityToken"
4343
public class StsWebIdentityCredentialsProvider(
4444
private val roleArn: String,
4545
private val webIdentityTokenFilePath: String,
46-
private val region: String,
46+
private val region: String?,
4747
private val roleSessionName: String? = null,
4848
private val duration: Duration = DEFAULT_CREDENTIALS_REFRESH_SECONDS.seconds,
4949
private val platformProvider: PlatformProvider = PlatformProvider.System,
@@ -67,7 +67,7 @@ public class StsWebIdentityCredentialsProvider(
6767
): StsWebIdentityCredentialsProvider {
6868
val resolvedRoleArn = platformProvider.resolve(roleArn, AwsSdkSetting.AwsRoleArn, "roleArn")
6969
val resolvedTokenFilePath = platformProvider.resolve(webIdentityTokenFilePath, AwsSdkSetting.AwsWebIdentityTokenFile, "webIdentityTokenFilePath")
70-
val resolvedRegion = platformProvider.resolve(region, AwsSdkSetting.AwsRegion, "region")
70+
val resolvedRegion = region ?: AwsSdkSetting.AwsRegion.resolve(platformProvider)
7171
return StsWebIdentityCredentialsProvider(resolvedRoleArn, resolvedTokenFilePath, resolvedRegion, roleSessionName, duration, platformProvider, httpClient)
7272
}
7373
}

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/region/ResolveRegion.kt

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
package aws.sdk.kotlin.runtime.region
77

8-
import aws.sdk.kotlin.runtime.ConfigurationException
98
import aws.sdk.kotlin.runtime.InternalSdkApi
109
import aws.sdk.kotlin.runtime.config.profile.AwsProfile
1110
import aws.sdk.kotlin.runtime.config.profile.loadAwsSharedConfig
@@ -15,14 +14,10 @@ import aws.smithy.kotlin.runtime.util.PlatformProvider
1514
import aws.smithy.kotlin.runtime.util.asyncLazy
1615

1716
/**
18-
* Attempt to resolve the region to make requests to, throws [ConfigurationException] if region could not be
19-
* resolved.
17+
* Attempt to resolve the AWS region to which requests should be made. Returns null if none was detected.
2018
*/
2119
@InternalSdkApi
2220
public suspend fun resolveRegion(
2321
platformProvider: PlatformProvider = PlatformProvider.System,
2422
profile: LazyAsyncValue<AwsProfile> = asyncLazy { loadAwsSharedConfig(platformProvider).activeProfile },
25-
): String =
26-
DefaultRegionProviderChain(platformProvider, profile = profile).use { providerChain ->
27-
providerChain.getRegion() ?: throw ConfigurationException("unable to auto detect AWS region, tried: $providerChain")
28-
}
23+
): String? = DefaultRegionProviderChain(platformProvider, profile = profile).use { it.getRegion() }

aws-runtime/aws-core/common/src/aws/sdk/kotlin/runtime/client/AwsSdkClientConfig.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public interface AwsSdkClientConfig : SdkClientConfig {
1717
* [global infrastructure](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/) for more
1818
* information
1919
*/
20-
public val region: String
20+
public val region: String?
2121

2222
/**
2323
* Flag to toggle whether to use [FIPS](https://aws.amazon.com/compliance/fips/) endpoints when making requests.

codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsServiceConfigIntegration.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ class AwsServiceConfigIntegration : KotlinIntegration {
3030
[global infrastructure](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/) for more
3131
information
3232
""".trimIndent()
33-
propertyType = ConfigPropertyType.Required()
3433
order = -100
3534
}
3635

codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/core/AwsHttpProtocolClientGenerator.kt

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@
55

66
package aws.sdk.kotlin.codegen.protocols.core
77

8-
import aws.sdk.kotlin.codegen.AwsRuntimeTypes
8+
import aws.sdk.kotlin.codegen.AwsRuntimeTypes.Core.Client.AwsClientOption
99
import aws.sdk.kotlin.codegen.sdkId
10+
import software.amazon.smithy.codegen.core.Symbol
1011
import software.amazon.smithy.kotlin.codegen.core.*
11-
import software.amazon.smithy.kotlin.codegen.model.buildSymbol
12+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes.Auth.Signing.AwsSigningCommon.AwsSigningAttributes
13+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes.SmithyClient.SdkClientOption
1214
import software.amazon.smithy.kotlin.codegen.model.hasIdempotentTokenMember
1315
import software.amazon.smithy.kotlin.codegen.model.knowledge.AwsSignatureVersion4
14-
import software.amazon.smithy.kotlin.codegen.model.namespace
1516
import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpBindingResolver
1617
import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpProtocolClientGenerator
1718
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
1819
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware
20+
import software.amazon.smithy.kotlin.codegen.utils.dq
1921
import software.amazon.smithy.model.knowledge.OperationIndex
2022
import software.amazon.smithy.model.knowledge.ServiceIndex
2123
import software.amazon.smithy.model.shapes.OperationShape
@@ -76,30 +78,38 @@ open class AwsHttpProtocolClientGenerator(
7678
private fun renderMergeServiceDefaults(writer: KotlinWriter) {
7779
// FIXME - we likely need a way to let customizations modify/override this
7880
// FIXME - we also need a way to tie in config properties added via integrations that need to influence the context
79-
val putIfAbsentSym = buildSymbol { name = "putIfAbsent"; namespace(KotlinDependency.CORE, "util") }
80-
val sdkClientOptionSym = buildSymbol { name = "SdkClientOption"; namespace(KotlinDependency.CORE, "client") }
81-
8281
writer.dokka("merge the defaults configured for the service into the execution context before firing off a request")
8382
writer.withBlock(
8483
"private suspend fun mergeServiceDefaults(ctx: #T) {",
8584
"}",
8685
RuntimeTypes.Core.ExecutionContext,
8786
) {
88-
write("ctx.#T(#T.Region, config.region)", putIfAbsentSym, AwsRuntimeTypes.Core.Client.AwsClientOption)
89-
write("ctx.#T(#T.ClientName, config.clientName)", putIfAbsentSym, sdkClientOptionSym)
90-
write("ctx.#T(#T.LogMode, config.logMode)", putIfAbsentSym, sdkClientOptionSym)
87+
putIfAbsent(AwsClientOption, "Region", nullable = true)
88+
putIfAbsent(SdkClientOption, "ClientName")
89+
putIfAbsent(SdkClientOption, "LogMode")
9190
// fill in auth/signing attributes
9291
if (AwsSignatureVersion4.isSupportedAuthentication(ctx.model, ctx.service)) {
9392
// default signing context (most of this has been moved to auth schemes but some things like event streams still depend on this)
9493
val signingServiceName = AwsSignatureVersion4.signingServiceName(ctx.service)
95-
write("ctx.#T(#T.SigningService, #S)", putIfAbsentSym, RuntimeTypes.Auth.Signing.AwsSigningCommon.AwsSigningAttributes, signingServiceName)
96-
write("ctx.#T(#T.SigningRegion, config.region)", putIfAbsentSym, RuntimeTypes.Auth.Signing.AwsSigningCommon.AwsSigningAttributes)
97-
write("ctx.#T(#T.CredentialsProvider, config.credentialsProvider)", putIfAbsentSym, RuntimeTypes.Auth.Signing.AwsSigningCommon.AwsSigningAttributes)
94+
putIfAbsent(AwsSigningAttributes, "SigningService", signingServiceName.dq())
95+
putIfAbsent(AwsSigningAttributes, "SigningRegion", "config.region", nullable = true)
96+
putIfAbsent(AwsSigningAttributes, "CredentialsProvider")
9897
}
9998

10099
if (ctx.service.hasIdempotentTokenMember(ctx.model)) {
101-
write("config.idempotencyTokenProvider?.let { ctx[#T.IdempotencyTokenProvider] = it }", sdkClientOptionSym)
100+
putIfAbsent(SdkClientOption, "IdempotencyTokenProvider", nullable = true)
102101
}
103102
}
104103
}
105104
}
105+
106+
private fun KotlinWriter.putIfAbsent(
107+
attributesSymbol: Symbol,
108+
name: String,
109+
literalValue: String? = null,
110+
nullable: Boolean = false,
111+
) {
112+
val putSymbol = if (nullable) RuntimeTypes.Core.Utils.putIfAbsentNotNull else RuntimeTypes.Core.Utils.putIfAbsent
113+
val actualValue = literalValue ?: "config.${name.replaceFirstChar(Char::lowercaseChar)}"
114+
write("ctx.#T(#T.#L, #L)", putSymbol, attributesSymbol, name, actualValue)
115+
}

codegen/smithy-aws-kotlin-codegen/src/test/kotlin/aws/sdk/kotlin/codegen/AwsServiceConfigIntegrationTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class AwsServiceConfigIntegrationTest {
4545
val contents = writer.toString()
4646

4747
val expectedProps = """
48-
override val region: String = requireNotNull(builder.region) { "region is a required configuration property" }
48+
override val region: String? = builder.region
4949
override val credentialsProvider: CredentialsProvider = builder.credentialsProvider ?: DefaultChainCredentialsProvider(httpClient = httpClient, region = region).manage()
5050
"""
5151
contents.shouldContainOnlyOnceWithDiff(expectedProps)

services/s3/common/test/aws/sdk/kotlin/services/s3/CreateClientTest.kt

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@
55

66
package aws.sdk.kotlin.services.s3
77

8-
import io.kotest.matchers.string.shouldContain
98
import kotlinx.coroutines.ExperimentalCoroutinesApi
109
import kotlinx.coroutines.test.runTest
1110
import kotlin.test.Test
12-
import kotlin.test.assertFailsWith
1311

1412
/**
1513
* Validate the way service clients can be constructed.
@@ -18,12 +16,10 @@ import kotlin.test.assertFailsWith
1816
*/
1917
@OptIn(ExperimentalCoroutinesApi::class)
2018
class CreateClientTest {
21-
2219
@Test
2320
fun testMissingRegion() {
24-
assertFailsWith<IllegalArgumentException> {
25-
S3Client { }
26-
}.message.shouldContain("region is a required configuration property")
21+
// Should _not_ throw an exception since region is optional
22+
S3Client { }
2723
}
2824

2925
@Test

services/s3/e2eTest/src/S3TestUtils.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ object S3TestUtils {
4545
client.createBucket {
4646
bucket = testBucket
4747
createBucketConfiguration {
48-
locationConstraint = BucketLocationConstraint.fromValue(client.config.region)
48+
locationConstraint = BucketLocationConstraint.fromValue(client.config.region!!)
4949
}
5050
}
5151

0 commit comments

Comments
 (0)