Skip to content

Commit b340f2c

Browse files
authored
feat: allow setting endpoint url through env and shared config (#1039)
1 parent 031a25e commit b340f2c

File tree

17 files changed

+2660
-29
lines changed

17 files changed

+2660
-29
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "bac6a11b-f4bd-4aae-ac8c-dc27b753c3c8",
3+
"type": "feature",
4+
"description": "Allow endpoint URL configuration via env and shared config."
5+
}

aws-runtime/aws-config/api/aws-config.api

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ public final class aws/sdk/kotlin/runtime/auth/credentials/internal/sso/SsoClien
188188
public final class aws/sdk/kotlin/runtime/auth/credentials/internal/sso/SsoClient$Companion : aws/sdk/kotlin/runtime/config/AbstractAwsSdkClientFactory {
189189
public fun builder ()Laws/sdk/kotlin/runtime/auth/credentials/internal/sso/SsoClient$Builder;
190190
public synthetic fun builder ()Laws/smithy/kotlin/runtime/client/SdkClient$Builder;
191+
public synthetic fun finalizeConfig (Laws/smithy/kotlin/runtime/client/SdkClient$Builder;Laws/smithy/kotlin/runtime/util/LazyAsyncValue;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
191192
}
192193

193194
public final class aws/sdk/kotlin/runtime/auth/credentials/internal/sso/SsoClient$Config : aws/sdk/kotlin/runtime/client/AwsSdkClientConfig, aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProviderConfig, aws/smithy/kotlin/runtime/client/RetryClientConfig, aws/smithy/kotlin/runtime/client/RetryStrategyClientConfig, aws/smithy/kotlin/runtime/client/SdkClientConfig, aws/smithy/kotlin/runtime/http/auth/HttpAuthConfig, aws/smithy/kotlin/runtime/http/config/HttpClientConfig, aws/smithy/kotlin/runtime/http/config/HttpEngineConfig, aws/smithy/kotlin/runtime/telemetry/TelemetryConfig {
@@ -521,6 +522,7 @@ public final class aws/sdk/kotlin/runtime/auth/credentials/internal/ssooidc/SsoO
521522
public final class aws/sdk/kotlin/runtime/auth/credentials/internal/ssooidc/SsoOidcClient$Companion : aws/sdk/kotlin/runtime/config/AbstractAwsSdkClientFactory {
522523
public fun builder ()Laws/sdk/kotlin/runtime/auth/credentials/internal/ssooidc/SsoOidcClient$Builder;
523524
public synthetic fun builder ()Laws/smithy/kotlin/runtime/client/SdkClient$Builder;
525+
public synthetic fun finalizeConfig (Laws/smithy/kotlin/runtime/client/SdkClient$Builder;Laws/smithy/kotlin/runtime/util/LazyAsyncValue;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
524526
}
525527

526528
public final class aws/sdk/kotlin/runtime/auth/credentials/internal/ssooidc/SsoOidcClient$Config : aws/sdk/kotlin/runtime/client/AwsSdkClientConfig, aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProviderConfig, aws/smithy/kotlin/runtime/client/RetryClientConfig, aws/smithy/kotlin/runtime/client/RetryStrategyClientConfig, aws/smithy/kotlin/runtime/client/SdkClientConfig, aws/smithy/kotlin/runtime/http/auth/HttpAuthConfig, aws/smithy/kotlin/runtime/http/config/HttpClientConfig, aws/smithy/kotlin/runtime/http/config/HttpEngineConfig, aws/smithy/kotlin/runtime/telemetry/TelemetryConfig {
@@ -1043,6 +1045,7 @@ public final class aws/sdk/kotlin/runtime/auth/credentials/internal/sts/StsClien
10431045
public final class aws/sdk/kotlin/runtime/auth/credentials/internal/sts/StsClient$Companion : aws/sdk/kotlin/runtime/config/AbstractAwsSdkClientFactory {
10441046
public fun builder ()Laws/sdk/kotlin/runtime/auth/credentials/internal/sts/StsClient$Builder;
10451047
public synthetic fun builder ()Laws/smithy/kotlin/runtime/client/SdkClient$Builder;
1048+
public synthetic fun finalizeConfig (Laws/smithy/kotlin/runtime/client/SdkClient$Builder;Laws/smithy/kotlin/runtime/util/LazyAsyncValue;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
10461049
}
10471050

10481051
public final class aws/sdk/kotlin/runtime/auth/credentials/internal/sts/StsClient$Config : aws/sdk/kotlin/runtime/client/AwsSdkClientConfig, aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProviderConfig, aws/smithy/kotlin/runtime/client/RetryClientConfig, aws/smithy/kotlin/runtime/client/RetryStrategyClientConfig, aws/smithy/kotlin/runtime/client/SdkClientConfig, aws/smithy/kotlin/runtime/http/auth/HttpAuthConfig, aws/smithy/kotlin/runtime/http/config/HttpClientConfig, aws/smithy/kotlin/runtime/http/config/HttpEngineConfig, aws/smithy/kotlin/runtime/telemetry/TelemetryConfig {
@@ -1659,9 +1662,15 @@ public abstract class aws/sdk/kotlin/runtime/config/AbstractAwsSdkClientFactory
16591662
public fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/client/SdkClient;
16601663
}
16611664

1665+
public final class aws/sdk/kotlin/runtime/config/AwsSdkSettingKt {
1666+
}
1667+
16621668
public final class aws/sdk/kotlin/runtime/config/endpoints/EndpointsKt {
16631669
}
16641670

1671+
public final class aws/sdk/kotlin/runtime/config/endpoints/ResolveEndpointUrlKt {
1672+
}
1673+
16651674
public final class aws/sdk/kotlin/runtime/config/imds/EC2MetadataError : aws/sdk/kotlin/runtime/AwsServiceException {
16661675
public fun <init> (ILjava/lang/String;)V
16671676
public final fun getStatusCode ()I
@@ -1746,6 +1755,10 @@ public final class aws/sdk/kotlin/runtime/config/profile/AwsConfigParseException
17461755
public final class aws/sdk/kotlin/runtime/config/profile/AwsProfileKt {
17471756
}
17481757

1758+
public final class aws/sdk/kotlin/runtime/config/profile/AwsSharedConfigKt {
1759+
public static final fun resolveEndpointUrl (Laws/sdk/kotlin/runtime/config/profile/AwsSharedConfig;Ljava/lang/String;)Laws/smithy/kotlin/runtime/net/Url;
1760+
}
1761+
17491762
public final class aws/sdk/kotlin/runtime/config/retries/ResolveRetryStrategyKt {
17501763
}
17511764

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/AbstractAwsSdkClientFactory.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ package aws.sdk.kotlin.runtime.config
88
import aws.sdk.kotlin.runtime.client.AwsSdkClientConfig
99
import aws.sdk.kotlin.runtime.config.endpoints.resolveUseDualStack
1010
import aws.sdk.kotlin.runtime.config.endpoints.resolveUseFips
11-
import aws.sdk.kotlin.runtime.config.profile.AwsProfile
11+
import aws.sdk.kotlin.runtime.config.profile.AwsSharedConfig
1212
import aws.sdk.kotlin.runtime.config.profile.loadAwsSharedConfig
1313
import aws.sdk.kotlin.runtime.config.retries.resolveRetryStrategy
1414
import aws.sdk.kotlin.runtime.region.resolveRegion
@@ -57,27 +57,28 @@ public abstract class AbstractAwsSdkClientFactory<
5757
val tracer = telemetryProvider.tracerProvider.getOrCreateTracer("AwsSdkClientFactory")
5858

5959
tracer.withSpan("fromEnvironment") {
60-
val profile = asyncLazy { loadAwsSharedConfig(PlatformProvider.System).activeProfile }
60+
val sharedConfig = asyncLazy { loadAwsSharedConfig(PlatformProvider.System) }
61+
val profile = asyncLazy { sharedConfig.get().activeProfile }
6162

6263
// As a DslBuilderProperty, the value of retryStrategy cannot be checked for nullability because it may have
6364
// been set using a DSL. Thus, set the resolved strategy _first_ to ensure it's used as the fallback.
6465
if (config is RetryStrategyClientConfig.Builder) {
6566
config.retryStrategy = resolveRetryStrategy(profile = profile)
6667
}
6768

68-
if (block != null) config.apply(block)
69+
block?.let(config::apply)
6970

7071
config.logMode = config.logMode ?: ClientSettings.LogMode.resolve(platform = PlatformProvider.System)
7172
config.region = config.region ?: resolveRegion(profile = profile)
7273
config.useFips = config.useFips ?: resolveUseFips(profile = profile)
7374
config.useDualStack = config.useDualStack ?: resolveUseDualStack(profile = profile)
74-
finalizeConfig(builder, profile)
75+
finalizeConfig(builder, sharedConfig)
7576
}
7677
return builder.build()
7778
}
7879

7980
/**
8081
* Inject any client-specific config.
8182
*/
82-
protected open suspend fun finalizeConfig(builder: TClientBuilder, profile: LazyAsyncValue<AwsProfile>) { }
83+
protected open suspend fun finalizeConfig(builder: TClientBuilder, sharedConfig: LazyAsyncValue<AwsSharedConfig>) { }
8384
}

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/AwsSdkSetting.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ package aws.sdk.kotlin.runtime.config
88
import aws.sdk.kotlin.runtime.InternalSdkApi
99
import aws.smithy.kotlin.runtime.client.config.RetryMode
1010
import aws.smithy.kotlin.runtime.config.*
11+
import aws.smithy.kotlin.runtime.net.Url
12+
import aws.smithy.kotlin.runtime.util.PlatformProvider
1113

1214
// NOTE: The JVM property names MUST match the ones defined in the Java SDK for any setting added.
1315
// see: https://github.com/aws/aws-sdk-java-v2/blob/master/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkSystemSetting.java
@@ -152,4 +154,29 @@ public object AwsSdkSetting {
152154
*/
153155
public val AwsUseDualStackEndpoint: EnvironmentSetting<Boolean> =
154156
boolEnvSetting("aws.useDualstackEndpoint", "AWS_USE_DUALSTACK_ENDPOINT")
157+
158+
/**
159+
* The globally-configured endpoint URL that applies to all services.
160+
*/
161+
public val AwsEndpointUrl: EnvironmentSetting<Url> =
162+
EnvironmentSetting(Url::parse)("aws.endpointUrl", "AWS_ENDPOINT_URL")
163+
164+
/**
165+
* Whether to ignore configured endpoint URLs.
166+
*/
167+
public val AwsIgnoreEndpointUrls: EnvironmentSetting<Boolean> =
168+
boolEnvSetting("aws.ignoreConfiguredEndpointUrls", "AWS_IGNORE_CONFIGURED_ENDPOINT_URLS")
169+
}
170+
171+
/**
172+
* Resolves an endpoint url for a given service.
173+
*/
174+
@InternalSdkApi
175+
public fun AwsSdkSetting.resolveEndpointUrl(
176+
provider: PlatformProvider,
177+
sysPropSuffix: String,
178+
envSuffix: String,
179+
): Url? {
180+
val serviceSetting = EnvironmentSetting(Url::parse)("aws.endpointUrl$sysPropSuffix", "AWS_ENDPOINT_URL_$envSuffix")
181+
return serviceSetting.resolve(provider) ?: AwsEndpointUrl.resolve(provider)
155182
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.sdk.kotlin.runtime.config.endpoints
6+
7+
import aws.sdk.kotlin.runtime.InternalSdkApi
8+
import aws.sdk.kotlin.runtime.config.AwsSdkSetting
9+
import aws.sdk.kotlin.runtime.config.profile.*
10+
import aws.sdk.kotlin.runtime.config.resolveEndpointUrl
11+
import aws.smithy.kotlin.runtime.config.resolve
12+
import aws.smithy.kotlin.runtime.net.Url
13+
import aws.smithy.kotlin.runtime.util.LazyAsyncValue
14+
import aws.smithy.kotlin.runtime.util.PlatformProvider
15+
16+
/**
17+
* Attempts to find the configured endpoint URL for a specific service.
18+
*
19+
* We look at the following sources in-order:
20+
*
21+
* 1. The value provided by a service-specific environment setting:
22+
* 1. The environment variable `AWS_ENDPOINT_URL_${SNAKE_CASE_SERVICE_ID}`.
23+
* 2. The JVM system property `aws.endpointUrl${JavaSDKClientPrefix}`.
24+
* 2. The value provided by the global endpoint environment setting:
25+
* 1. The environment variable `AWS_ENDPOINT_URL`.
26+
* 2. The JVM system property `aws.endpointUrl`.
27+
* 3. The value provided by a service-specific parameter from a services definition section in the shared configuration
28+
* file (`${snake_case_service_id}.endpoint_url`).
29+
* 4. The value provided by the global parameter from a profile in the shared configuration file (`endpoint_url`).
30+
*
31+
* Endpoint URL settings can be disabled globally through either the environment or shared config, in which case the
32+
* above list of sources is ignored.
33+
*/
34+
@InternalSdkApi
35+
public suspend fun resolveEndpointUrl(
36+
sharedConfig: LazyAsyncValue<AwsSharedConfig>,
37+
sysPropSuffix: String,
38+
envSuffix: String,
39+
sharedConfigKey: String,
40+
provider: PlatformProvider = PlatformProvider.System,
41+
): Url? {
42+
if (resolveIgnoreEndpointUrls(provider, sharedConfig)) {
43+
return null // the "disable" directive overrides ALL sources, regardless of where it comes from itself
44+
}
45+
46+
return AwsSdkSetting.resolveEndpointUrl(provider, sysPropSuffix, envSuffix)
47+
?: sharedConfig.get().resolveEndpointUrl(sharedConfigKey)
48+
}
49+
50+
private suspend fun resolveIgnoreEndpointUrls(
51+
provider: PlatformProvider,
52+
sharedConfig: LazyAsyncValue<AwsSharedConfig>,
53+
): Boolean =
54+
AwsSdkSetting.AwsIgnoreEndpointUrls.resolve(provider)
55+
?: sharedConfig.get().activeProfile.ignoreEndpointUrls
56+
?: false

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/profile/AwsProfile.kt

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package aws.sdk.kotlin.runtime.config.profile
88
import aws.sdk.kotlin.runtime.ConfigurationException
99
import aws.sdk.kotlin.runtime.InternalSdkApi
1010
import aws.smithy.kotlin.runtime.client.config.RetryMode
11+
import aws.smithy.kotlin.runtime.net.Url
1112

1213
/**
1314
* Represents an AWS config profile.
@@ -100,17 +101,46 @@ public val AwsProfile.useFips: Boolean?
100101
public val AwsProfile.useDualStack: Boolean?
101102
get() = getBooleanOrNull("use_dualstack_endpoint")
102103

104+
/**
105+
* The default endpoint URL that applies to all services.
106+
*/
107+
@InternalSdkApi
108+
public val AwsProfile.endpointUrl: Url?
109+
get() = getUrlOrNull("endpoint_url")
110+
111+
/**
112+
* Whether to ignore configured endpoint URLs.
113+
*/
114+
@InternalSdkApi
115+
public val AwsProfile.ignoreEndpointUrls: Boolean?
116+
get() = getBooleanOrNull("ignore_configured_endpoint_urls")
117+
118+
/**
119+
* The name of the services config section used by this profile.
120+
*/
121+
@InternalSdkApi
122+
public val AwsProfile.servicesSection: String?
123+
get() = getOrNull("services")
124+
103125
/**
104126
* Parse a config value as a boolean, ignoring case.
105127
*/
106128
@InternalSdkApi
107129
public fun AwsProfile.getBooleanOrNull(key: String, subKey: String? = null): Boolean? =
108130
getOrNull(key, subKey)?.let {
109131
it.lowercase().toBooleanStrictOrNull() ?: throw ConfigurationException(
110-
buildString {
111-
append("Failed to parse config property $key")
112-
subKey?.let { append(".$it") }
113-
append(" as a boolean")
114-
},
132+
"Failed to parse config property ${buildKeyString(key, subKey)} as a boolean",
115133
)
116134
}
135+
136+
internal fun AwsProfile.getUrlOrNull(key: String, subKey: String? = null): Url? =
137+
getOrNull(key, subKey)?.let {
138+
try {
139+
Url.parse(it)
140+
} catch (e: Exception) {
141+
throw ConfigurationException("Failed to parse config property ${buildKeyString(key, subKey)} as a URL", e)
142+
}
143+
}
144+
145+
private fun buildKeyString(key: String, subKey: String? = null): String =
146+
listOfNotNull(key, subKey).joinToString(".")

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/profile/AwsSharedConfig.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
package aws.sdk.kotlin.runtime.config.profile
77

8+
import aws.sdk.kotlin.runtime.ConfigurationException
89
import aws.sdk.kotlin.runtime.InternalSdkApi
10+
import aws.smithy.kotlin.runtime.net.Url
911

1012
/**
1113
* Represents shared configuration (profiles, SSO sessions, credentials, etc)
@@ -28,9 +30,30 @@ public class AwsSharedConfig internal constructor(
2830
public val ssoSessions: Map<String, SsoSessionConfig>
2931
get() = sections[ConfigSectionType.SSO_SESSION] ?: emptyMap()
3032

33+
/**
34+
* Map of section name to ServiceConfig
35+
*/
36+
public val services: Map<String, ServicesConfig>
37+
get() = sections[ConfigSectionType.SERVICES] ?: emptyMap()
38+
3139
/**
3240
* Resolve the active profile or the default profile if none is defined
3341
*/
3442
public val activeProfile: AwsProfile
3543
get() = profiles[source.profile] ?: AwsProfile(source.profile, emptyMap())
3644
}
45+
46+
/**
47+
* Get the configured endpoint URL for a specific service, falling back to the global default.
48+
* @param serviceKey The config key for the service, generally this is sdkId in snake_case form.
49+
*/
50+
public fun AwsSharedConfig.resolveEndpointUrl(serviceKey: String): Url? =
51+
resolveServiceEndpointUrl(serviceKey) ?: activeProfile.endpointUrl
52+
53+
private fun AwsSharedConfig.resolveServiceEndpointUrl(serviceKey: String): Url? =
54+
activeProfile.servicesSection?.let { sectionName ->
55+
val section = services[sectionName]
56+
?: throw ConfigurationException("shared config points to nonexistent services section '$sectionName'")
57+
58+
section.getUrlOrNull(serviceKey, "endpoint_url")
59+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package aws.sdk.kotlin.runtime.config.profile
7+
8+
import aws.sdk.kotlin.runtime.InternalSdkApi
9+
10+
@InternalSdkApi
11+
public typealias ServicesConfig = ConfigSection

0 commit comments

Comments
 (0)