Skip to content

Commit 00a12d6

Browse files
authored
feat: add support for detecting custom metadata in sys props and env vars (#604)
1 parent 4227caf commit 00a12d6

File tree

10 files changed

+102
-18
lines changed

10 files changed

+102
-18
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "16c733de-c490-404e-a5f9-4e1ba917d020",
3+
"type": "feature",
4+
"description": "Add support for detecting custom metadata in system properties (starting with `aws.customMetadata.`) and environment variables (starting with `AWS_CUSTOM_METADATA_`)",
5+
"issues": [
6+
"awslabs/aws-sdk-kotlin#575"
7+
]
8+
}

aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/config/AwsSdkSettingTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ class AwsSdkSettingTest {
5353

5454
private fun mockPlatform(env: Map<String, String>, jvmProps: Map<String, String>): PlatformEnvironProvider {
5555
return object : PlatformEnvironProvider {
56+
override fun getAllEnvVars(): Map<String, String> = env
5657
override fun getenv(key: String): String? = env[key]
58+
override fun getAllProperties(): Map<String, String> = jvmProps
5759
override fun getProperty(key: String): String? = jvmProps[key]
5860
}
5961
}

aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/region/EnvironmentRegionProviderTest.kt

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

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

8+
import aws.smithy.kotlin.runtime.util.EnvironmentProvider
89
import kotlinx.coroutines.ExperimentalCoroutinesApi
910
import kotlinx.coroutines.test.runTest
1011
import kotlin.test.Test
@@ -13,11 +14,15 @@ import kotlin.test.assertNull
1314

1415
@OptIn(ExperimentalCoroutinesApi::class)
1516
class EnvironmentRegionProviderTest {
17+
fun Map<String, String>.asEnvironmentProvider() = object : EnvironmentProvider {
18+
override fun getAllEnvVars(): Map<String, String> = this@asEnvironmentProvider
19+
override fun getenv(key: String): String? = this@asEnvironmentProvider[key]
20+
}
1621

1722
@Test
1823
fun noRegion() = runTest {
1924
val environ = mapOf<String, String>()
20-
val provider = EnvironmentRegionProvider { environ[it] }
25+
val provider = EnvironmentRegionProvider(environ.asEnvironmentProvider())
2126
assertNull(provider.getRegion())
2227
}
2328

@@ -27,7 +32,7 @@ class EnvironmentRegionProviderTest {
2732
"AWS_REGION" to "us-east-1"
2833
)
2934

30-
val provider = EnvironmentRegionProvider { environ[it] }
35+
val provider = EnvironmentRegionProvider(environ.asEnvironmentProvider())
3136

3237
assertEquals("us-east-1", provider.getRegion())
3338
}

aws-runtime/aws-config/jvm/test/aws/sdk/kotlin/runtime/auth/credentials/DefaultChainCredentialsProviderTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ class DefaultChainCredentialsProviderTest {
4646
private val fs: Filesystem
4747
) : PlatformProvider, Filesystem by fs {
4848
override fun osInfo(): OperatingSystem = OperatingSystem(OsFamily.Linux, "test")
49+
override fun getAllProperties(): Map<String, String> = mapOf()
4950
override fun getProperty(key: String): String? = null
51+
override fun getAllEnvVars(): Map<String, String> = env
5052
override fun getenv(key: String): String? = env[key]
5153
}
5254

aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/AwsUserAgentMetadata.kt

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
package aws.sdk.kotlin.runtime.http
77

88
import aws.sdk.kotlin.runtime.InternalSdkApi
9-
import aws.sdk.kotlin.runtime.http.operation.ConfigMetadata
109
import aws.sdk.kotlin.runtime.http.operation.CustomUserAgentMetadata
11-
import aws.sdk.kotlin.runtime.http.operation.FeatureMetadata
1210
import aws.smithy.kotlin.runtime.util.*
1311
import kotlin.jvm.JvmInline
1412

@@ -69,20 +67,23 @@ public data class AwsUserAgentMetadata(
6967
ua.add("md/internal")
7068
}
7169

70+
// FIXME user agent strings are now too long so commenting out several metadata below.
71+
// Re-enable once user agent strings can be longer.
72+
7273
ua.add("$sdkMetadata")
73-
ua.add("$apiMetadata")
74+
// ua.add("$apiMetadata")
7475
ua.add("$osMetadata")
7576
ua.add("$languageMetadata")
76-
execEnvMetadata?.let { ua.add("$it") }
77+
// execEnvMetadata?.let { ua.add("$it") }
7778

78-
val features = customMetadata?.typedExtras?.filterIsInstance<FeatureMetadata>()
79-
features?.forEach { ua.add("$it") }
79+
// val features = customMetadata?.typedExtras?.filterIsInstance<FeatureMetadata>()
80+
// features?.forEach { ua.add("$it") }
8081

81-
val config = customMetadata?.typedExtras?.filterIsInstance<ConfigMetadata>()
82-
config?.forEach { ua.add("$it") }
82+
// val config = customMetadata?.typedExtras?.filterIsInstance<ConfigMetadata>()
83+
// config?.forEach { ua.add("$it") }
8384

8485
frameworkMetadata?.let { ua.add("$it") }
85-
appId?.let { ua.add("app/$it") }
86+
// appId?.let { ua.add("app/$it") }
8687

8788
customMetadata?.extras?.let {
8889
val wrapper = AdditionalMetadata(it)
@@ -107,6 +108,8 @@ internal fun loadAwsUserAgentMetadataFromEnvironment(platform: PlatformProvider,
107108
val appId = platform.getProperty(AWS_APP_ID_PROP) ?: platform.getenv(AWS_APP_ID_ENV)
108109

109110
val frameworkMetadata = FrameworkMetadata.fromEnvironment(platform)
111+
val customMetadata = CustomUserAgentMetadata.fromEnvironment(platform)
112+
110113
return AwsUserAgentMetadata(
111114
sdkMeta,
112115
apiMeta,
@@ -115,6 +118,7 @@ internal fun loadAwsUserAgentMetadataFromEnvironment(platform: PlatformProvider,
115118
detectExecEnv(platform),
116119
frameworkMetadata = frameworkMetadata,
117120
appId = appId,
121+
customMetadata = customMetadata,
118122
)
119123
}
120124

@@ -183,10 +187,12 @@ public data class LanguageMetadata(
183187
) {
184188
override fun toString(): String = buildString {
185189
append("lang/kotlin/$version")
190+
/* FIXME re-enable once user agent strings can be longer
186191
if (extras.isNotEmpty()) {
187192
val wrapper = AdditionalMetadata(extras)
188193
append(" $wrapper")
189194
}
195+
*/
190196
}
191197
}
192198

aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/operation/CustomUserAgentMetadata.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ package aws.sdk.kotlin.runtime.http.operation
88
import aws.sdk.kotlin.runtime.InternalSdkApi
99
import aws.smithy.kotlin.runtime.client.ExecutionContext
1010
import aws.smithy.kotlin.runtime.util.AttributeKey
11+
import aws.smithy.kotlin.runtime.util.PlatformEnvironProvider
12+
13+
private const val CUSTOM_METADATA_ENV_PREFIX = "AWS_CUSTOM_METADATA_"
14+
private const val CUSTOM_METADATA_PROP_PREFIX = "aws.customMetadata."
1115

1216
/**
1317
* Operation context element for adding additional metadata to the `User-Agent` header string.
@@ -20,6 +24,17 @@ public class CustomUserAgentMetadata {
2024

2125
internal companion object {
2226
public val ContextKey: AttributeKey<CustomUserAgentMetadata> = AttributeKey("CustomUserAgentMetadata")
27+
28+
internal fun fromEnvironment(provider: PlatformEnvironProvider): CustomUserAgentMetadata {
29+
fun Map<String, String>.findAndStripKeyPrefix(prefix: String) = this
30+
.filterKeys { it.startsWith(prefix) }
31+
.mapKeys { (key, _) -> key.substring(prefix.length) }
32+
33+
val envVarMap = provider.getAllEnvVars().findAndStripKeyPrefix(CUSTOM_METADATA_ENV_PREFIX)
34+
val propMap = provider.getAllProperties().findAndStripKeyPrefix(CUSTOM_METADATA_PROP_PREFIX)
35+
val allProps = envVarMap + propMap
36+
return CustomUserAgentMetadata().apply { allProps.forEach { (key, value) -> add(key, value) } }
37+
}
2338
}
2439

2540
/**

aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/AwsUserAgentMetadataTest.kt

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ package aws.sdk.kotlin.runtime.http
88
import aws.sdk.kotlin.runtime.http.operation.CustomUserAgentMetadata
99
import aws.sdk.kotlin.runtime.testing.TestPlatformProvider
1010
import aws.smithy.kotlin.runtime.util.OsFamily
11-
import io.kotest.matchers.string.shouldContain
11+
import io.kotest.matchers.string.shouldNotContain
1212
import kotlin.test.Test
1313
import kotlin.test.assertEquals
1414

@@ -31,7 +31,15 @@ class AwsUserAgentMetadataTest {
3131
add("foo", "bar")
3232
}
3333
val ua = AwsUserAgentMetadata(sdkMeta, apiMeta, osMetadata, langMeta, customMetadata = custom)
34-
val expected = "aws-sdk-kotlin/1.2.3 api/test-service/1.2.3 os/linux/ubuntu-20.04 lang/kotlin/1.4.31 md/jvmVersion/1.11 md/foo/bar"
34+
// FIXME re-enable once user agent strings can be longer
35+
val expected = listOf(
36+
"aws-sdk-kotlin/1.2.3",
37+
// "api/test-service/1.2.3",
38+
"os/linux/ubuntu-20.04",
39+
"lang/kotlin/1.4.31",
40+
// "md/jvmVersion/1.11",
41+
"md/foo/bar",
42+
).joinToString(separator = " ")
3543
assertEquals(expected, ua.xAmzUserAgent)
3644
}
3745

@@ -82,7 +90,10 @@ class AwsUserAgentMetadataTest {
8290
)
8391
testEnvironments.forEach { test ->
8492
val actual = loadAwsUserAgentMetadataFromEnvironment(test.provider, ApiMetadata("Test Service", "1.2.3"))
85-
actual.xAmzUserAgent.shouldContain(test.expected)
93+
94+
// FIXME re-enable once user agent strings can be longer
95+
// actual.xAmzUserAgent.shouldContain(test.expected)
96+
actual.xAmzUserAgent.shouldNotContain(test.expected)
8697
}
8798
}
8899
}

aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/middleware/UserAgentTest.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ class UserAgentTest {
6060
assertTrue(request.headers.contains(USER_AGENT))
6161
assertTrue(request.headers.contains(X_AMZ_USER_AGENT))
6262
assertEquals("aws-sdk-kotlin/1.2.3", request.headers[X_AMZ_USER_AGENT])
63-
assertTrue(request.headers[USER_AGENT]!!.startsWith("aws-sdk-kotlin/1.2.3 api/test-service/1.2.3"))
63+
// FIXME re-enable once user agent strings can be longer
64+
// assertTrue(request.headers[USER_AGENT]!!.startsWith("aws-sdk-kotlin/1.2.3 api/test-service/1.2.3"))
65+
assertTrue(request.headers[USER_AGENT]!!.startsWith("aws-sdk-kotlin/1.2.3"))
6466
}
6567

6668
@Test

aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/operation/CustomUserAgentMetadataTest.kt

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import aws.sdk.kotlin.runtime.http.loadAwsUserAgentMetadataFromEnvironment
1010
import aws.sdk.kotlin.runtime.testing.TestPlatformProvider
1111
import io.kotest.matchers.string.shouldContain
1212
import kotlin.test.Test
13+
import kotlin.test.assertEquals
1314

1415
class CustomUserAgentMetadataTest {
1516
@Test
@@ -30,15 +31,45 @@ class CustomUserAgentMetadataTest {
3031

3132
val actual = metadata.copy(customMetadata = customMetadata).xAmzUserAgent
3233

34+
// FIXME re-enable once user agent strings can be longer
3335
listOf(
3436
"md/foo/bar",
3537
"md/truthy",
3638
"md/falsey/false",
37-
"cfg/retry-mode/standard",
38-
"ft/s3-transfer/1.2.3",
39-
"ft/waiter"
39+
// "cfg/retry-mode/standard",
40+
// "ft/s3-transfer/1.2.3",
41+
// "ft/waiter",
4042
).forEach { partial ->
4143
actual.shouldContain(partial)
4244
}
4345
}
46+
47+
@Test
48+
fun testFromEnvironment() {
49+
val props = mapOf(
50+
"irrelevantProp" to "shouldBeIgnored",
51+
"aws.customMetadata" to "shouldBeIgnored",
52+
"aws.customMetadata.foo" to "bar",
53+
"aws.customMetadata.baz" to "qux",
54+
"aws.customMetadata.priority" to "props",
55+
)
56+
val envVars = mapOf(
57+
"IRRELEVANT_PROP" to "shouldBeIgnored",
58+
"AWS_CUSTOM_METADATA" to "shouldBeIgnored",
59+
"AWS_CUSTOM_METADATA_oof" to "rab",
60+
"AWS_CUSTOM_METADATA_zab" to "xuq",
61+
"AWS_CUSTOM_METADATA_priority" to "envVars",
62+
)
63+
val provider = TestPlatformProvider(env = envVars, props = props)
64+
val metadata = CustomUserAgentMetadata.fromEnvironment(provider)
65+
66+
val expected = mapOf(
67+
"foo" to "bar",
68+
"baz" to "qux",
69+
"oof" to "rab",
70+
"zab" to "xuq",
71+
"priority" to "props", // System properties take precedence over env vars
72+
)
73+
assertEquals(expected, metadata.extras)
74+
}
4475
}

aws-runtime/testing/common/src/aws/sdk/kotlin/runtime/testing/TestPlatformProvider.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public class TestPlatformProvider(
3535
}
3636

3737
override fun osInfo(): OperatingSystem = os
38+
override fun getAllProperties(): Map<String, String> = props
3839
override fun getProperty(key: String): String? = props[key]
40+
override fun getAllEnvVars(): Map<String, String> = env
3941
override fun getenv(key: String): String? = env[key]
4042
}

0 commit comments

Comments
 (0)