Skip to content

Commit d3279aa

Browse files
committed
Merge branch 'main' of https://github.com/awslabs/aws-sdk-kotlin into jmespath-flatten
2 parents a79aa1e + eb24838 commit d3279aa

File tree

177 files changed

+195897
-26768
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

177 files changed

+195897
-26768
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ build/
1212
.idea/
1313
__pycache__/
1414
local.properties
15+
1516
# ignore generated files
1617
services/*/generated-src
1718
services/*/build.gradle.kts
1819
.kotest/
19-
*.klib
20+
*.klib
21+
tests/codegen/smoke-tests/services/*/generated-src
22+
tests/codegen/smoke-tests/services/*/build.gradle.kts

CHANGELOG.md

Lines changed: 439 additions & 48 deletions
Large diffs are not rendered by default.

CONTRIBUTING.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ AWS service clients are generated from [Smithy](https://awslabs.github.io/smithy
1616
As such the code in `aws-sdk-kotlin` is a layer on top of generic Smithy based code generation tooling.
1717

1818

19-
2. Smithy Kotlin Codegen repo ([awslabs/smithy-kotlin](https://github.com/awslabs/smithy-kotlin))
19+
2. Smithy Kotlin Codegen repo ([smithy-lang/smithy-kotlin](https://github.com/smithy-lang/smithy-kotlin))
2020

2121
The `smithy-kotlin` repository contains the generic Smithy code generation tools for Kotlin.
2222

23-
If you want to contribute by diving into the codegen machinery and helping develop the SDK please refer to the [contributing guide](https://github.com/awslabs/smithy-kotlin/blob/main/CONTRIBUTING.md) in that repo.
23+
If you want to contribute by diving into the codegen machinery and helping develop the SDK please refer to the [contributing guide](https://github.com/smithy-lang/smithy-kotlin/blob/main/CONTRIBUTING.md) in that repo.
2424

2525

2626
## Reporting Bugs/Feature Requests
@@ -78,7 +78,7 @@ following fields:
7878
| `id` | `string` | yes | | A unique identifier for this entry. We recommend you generate a UUID for this field. |
7979
| `type` | `string` | yes | `bugfix`, `feature`, `documentation`, `misc` | The type of change being made. |
8080
| `description` | `string` | yes | | A description of the change being made.<ul><li>Prefix with `**Breaking**:` if the change is breaking</li><li>Use the imperative present tense (e.g., "change" not "changed" nor "changes")</li><li>Capitalize first letter</li><li>No dot (.) at the end unless there are multiple sentences</li></ul> |
81-
| `issues` | `string[]` | no | | A list of references to any related issues in the relevant repositories. A reference can be specified in several ways:<ul><li>The issue number, if local to this repository (eg. `#12345`)</li><li>A fully-qualified issue ID (eg.`awslabs/smithy-kotlin#12345`)</li><li>A fully-qualified URL (eg. `https://issuetracker.com/12345`)</li></ul> |
81+
| `issues` | `string[]` | no | | A list of references to any related issues in the relevant repositories. A reference can be specified in several ways:<ul><li>The issue number, if local to this repository (eg. `#12345`)</li><li>A fully-qualified issue ID (eg.`smithy-lang/smithy-kotlin#12345`)</li><li>A fully-qualified URL (eg. `https://issuetracker.com/12345`)</li></ul> |
8282
| `module` | `string` | no | | The area of the code affected by your changes. If unsure, leave this value unset. |
8383
| `requiresMinorVersionBump` | `boolean` | no | | Indicates the change will require a new minor version. This is usually the case after a breaking change. Defaults to false if flag is not included. |
8484

aws-runtime/aws-http/api/aws-http.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ public final class aws/sdk/kotlin/runtime/http/interceptors/AwsBusinessMetric :
145145
public static final field S3_EXPRESS_BUCKET Laws/sdk/kotlin/runtime/http/interceptors/AwsBusinessMetric;
146146
public static fun getEntries ()Lkotlin/enums/EnumEntries;
147147
public fun getIdentifier ()Ljava/lang/String;
148+
public fun toString ()Ljava/lang/String;
148149
public static fun valueOf (Ljava/lang/String;)Laws/sdk/kotlin/runtime/http/interceptors/AwsBusinessMetric;
149150
public static fun values ()[Laws/sdk/kotlin/runtime/http/interceptors/AwsBusinessMetric;
150151
}

aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/BusinessMetricsInterceptor.kt

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,19 @@ import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext
1313
import aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor
1414
import aws.smithy.kotlin.runtime.http.request.HttpRequest
1515
import aws.smithy.kotlin.runtime.http.request.toBuilder
16+
import aws.smithy.kotlin.runtime.telemetry.logging.Logger
17+
import aws.smithy.kotlin.runtime.telemetry.logging.logger
18+
import kotlin.coroutines.coroutineContext
1619

1720
/**
1821
* Appends business metrics to the `User-Agent` header.
1922
*/
2023
public class BusinessMetricsInterceptor : HttpInterceptor {
2124
override suspend fun modifyBeforeTransmit(context: ProtocolRequestInterceptorContext<Any, HttpRequest>): HttpRequest {
25+
val logger = coroutineContext.logger<BusinessMetricsInterceptor>()
26+
2227
context.executionContext.getOrNull(BusinessMetrics)?.let { metrics ->
23-
val metricsString = formatMetrics(metrics)
28+
val metricsString = formatMetrics(metrics, logger)
2429
val currentUserAgentHeader = context.protocolRequest.headers[USER_AGENT]
2530
val modifiedRequest = context.protocolRequest.toBuilder()
2631

@@ -34,10 +39,22 @@ public class BusinessMetricsInterceptor : HttpInterceptor {
3439

3540
/**
3641
* Makes sure the metrics do not exceed the maximum size and truncates them if so.
42+
* Makes sure that metric identifiers are not > 2 chars in length. Skips them if so.
3743
*/
38-
private fun formatMetrics(metrics: MutableSet<BusinessMetric>): String {
39-
if (metrics.isEmpty()) return ""
40-
val metricsString = metrics.joinToString(",", "m/") { it.identifier }
44+
private fun formatMetrics(metrics: MutableSet<BusinessMetric>, logger: Logger): String {
45+
val allowedMetrics = metrics.filter {
46+
if (it.identifier.length > 2) {
47+
logger.warn {
48+
"Business metric '${it.identifier}' will be skipped due to length being > 2. " +
49+
"This is likely a bug. Please raise an issue at https://github.com/awslabs/aws-sdk-kotlin/issues/new/choose"
50+
}
51+
false
52+
} else {
53+
true
54+
}
55+
}
56+
if (allowedMetrics.isEmpty()) return ""
57+
val metricsString = allowedMetrics.joinToString(",", "m/") { it.identifier }
4158
val metricsByteArray = metricsString.encodeToByteArray()
4259

4360
if (metricsByteArray.size <= BUSINESS_METRICS_MAX_LENGTH) return metricsString
@@ -65,4 +82,7 @@ private fun formatMetrics(metrics: MutableSet<BusinessMetric>): String {
6582
public enum class AwsBusinessMetric(public override val identifier: String) : BusinessMetric {
6683
S3_EXPRESS_BUCKET("J"),
6784
DDB_MAPPER("d"),
85+
;
86+
87+
override fun toString(): String = identifier
6888
}

aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/middleware/UserAgent.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public class UserAgent(
4444

4545
// NOTE: Due to legacy issues with processing the user agent, the original content for
4646
// x-amz-user-agent and User-Agent is swapped here. See top note in the
47-
// sdk-user-agent-header SEP and https://github.com/awslabs/smithy-kotlin/issues/373
47+
// sdk-user-agent-header SEP and https://github.com/smithy-lang/smithy-kotlin/issues/373
4848
// for further details.
4949
req.subject.headers[USER_AGENT] = requestMetadata.xAmzUserAgent
5050
req.subject.headers[X_AMZ_USER_AGENT] = requestMetadata.userAgent

aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/interceptors/BusinessMetricsInterceptorTest.kt

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,12 @@ import aws.smithy.kotlin.runtime.businessmetrics.BusinessMetric
1010
import aws.smithy.kotlin.runtime.businessmetrics.SmithyBusinessMetric
1111
import aws.smithy.kotlin.runtime.businessmetrics.emitBusinessMetric
1212
import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext
13-
import aws.smithy.kotlin.runtime.collections.get
1413
import aws.smithy.kotlin.runtime.http.*
1514
import aws.smithy.kotlin.runtime.http.request.HttpRequest
1615
import aws.smithy.kotlin.runtime.net.url.Url
1716
import aws.smithy.kotlin.runtime.operation.ExecutionContext
1817
import kotlinx.coroutines.test.runTest
19-
import kotlin.test.Test
20-
import kotlin.test.assertFailsWith
21-
import kotlin.test.assertFalse
22-
import kotlin.test.assertTrue
18+
import kotlin.test.*
2319

2420
class BusinessMetricsInterceptorTest {
2521
@Test
@@ -32,6 +28,23 @@ class BusinessMetricsInterceptorTest {
3228
assertFalse(userAgentHeader.endsWith("m/"))
3329
}
3430

31+
@Test
32+
fun noValidBusinessMetrics() = runTest {
33+
val executionContext = ExecutionContext()
34+
35+
val invalidBusinessMetric = object : BusinessMetric {
36+
override val identifier: String = "All work and no play makes Jack a dull boy".repeat(1000)
37+
}
38+
39+
executionContext.emitBusinessMetric(invalidBusinessMetric)
40+
41+
val interceptor = BusinessMetricsInterceptor()
42+
val request = interceptor.modifyBeforeTransmit(interceptorContext(executionContext))
43+
val userAgentHeader = request.headers[USER_AGENT]!!
44+
45+
assertFalse(userAgentHeader.endsWith("m/"))
46+
}
47+
3548
@Test
3649
fun businessMetrics() = runTest {
3750
val executionContext = ExecutionContext()
@@ -66,49 +79,57 @@ class BusinessMetricsInterceptorTest {
6679
}
6780

6881
@Test
69-
fun truncateBusinessMetrics() = runTest {
82+
fun businessMetricsMaxLength() = runTest {
7083
val executionContext = ExecutionContext()
7184
executionContext.attributes[aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics] = mutableSetOf()
7285

73-
for (i in 0..1024) {
86+
for (i in 0..BUSINESS_METRICS_MAX_LENGTH) {
7487
executionContext.emitBusinessMetric(
7588
object : BusinessMetric {
7689
override val identifier: String = i.toString()
7790
},
7891
)
7992
}
8093

81-
val rawMetrics = executionContext[aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics]
82-
val rawMetricsString = rawMetrics.joinToString(",", "m/")
83-
val rawMetricsByteArray = rawMetricsString.encodeToByteArray()
84-
85-
assertTrue(rawMetricsByteArray.size >= BUSINESS_METRICS_MAX_LENGTH)
86-
8794
val interceptor = BusinessMetricsInterceptor()
8895
val request = interceptor.modifyBeforeTransmit(interceptorContext(executionContext))
8996
val userAgentHeader = request.headers[USER_AGENT]!!
90-
val truncatedMetrics = "m/" + userAgentHeader.substringAfter("m/")
97+
val metrics = "m/" + userAgentHeader.substringAfter("m/")
9198

92-
assertTrue(truncatedMetrics.encodeToByteArray().size <= BUSINESS_METRICS_MAX_LENGTH)
93-
assertFalse(truncatedMetrics.endsWith(","))
99+
assertTrue(metrics.encodeToByteArray().size <= BUSINESS_METRICS_MAX_LENGTH)
100+
assertFalse(metrics.endsWith(","))
94101
}
95102

96103
@Test
97-
fun malformedBusinessMetrics() = runTest {
104+
fun invalidBusinessMetric() = runTest {
98105
val executionContext = ExecutionContext()
99-
val reallyLongMetric = "All work and no play makes Jack a dull boy".repeat(1000)
100106

101-
executionContext.attributes.emitBusinessMetric(
102-
object : BusinessMetric {
103-
override val identifier: String = reallyLongMetric
104-
},
105-
)
107+
val validMetric = AwsBusinessMetric.S3_EXPRESS_BUCKET
108+
val invalidMetric = object : BusinessMetric {
109+
override val identifier: String = "All work and no play makes Jack a dull boy".repeat(1000)
110+
}
111+
112+
executionContext.attributes.emitBusinessMetric(validMetric)
113+
executionContext.attributes.emitBusinessMetric(invalidMetric)
106114

107115
val interceptor = BusinessMetricsInterceptor()
116+
val request = interceptor.modifyBeforeTransmit(interceptorContext(executionContext))
117+
val userAgentHeader = request.headers[USER_AGENT]!!
108118

109-
assertFailsWith<IllegalStateException>("Business metrics are incorrectly formatted:") {
110-
interceptor.modifyBeforeTransmit(interceptorContext(executionContext))
111-
}
119+
assertTrue(
120+
userAgentHeader.contains(validMetric.identifier),
121+
)
122+
assertFalse(
123+
userAgentHeader.contains(invalidMetric.identifier),
124+
)
125+
}
126+
127+
@Test
128+
fun businessMetricToString() {
129+
val businessMetricToString = AwsBusinessMetric.S3_EXPRESS_BUCKET.toString()
130+
val businessMetricIdentifier = AwsBusinessMetric.S3_EXPRESS_BUCKET.identifier
131+
132+
assertEquals(businessMetricIdentifier, businessMetricToString)
112133
}
113134
}
114135

bom/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ fun createBomConstraintsAndVersionCatalog() {
5454
}
5555
}
5656

57+
// Add the BOM itself to the version catalog
58+
catalogExt.versionCatalog {
59+
library("bom", "aws.sdk.kotlin:bom:$version")
60+
}
61+
5762
val ignoredSmithyKotlin = setOf(
5863
"smithy.kotlin.codegen",
5964
"smithy.kotlin.http.test",

codegen/aws-sdk-codegen/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies {
2929
api(libs.smithy.aws.cloudformation.traits)
3030
api(libs.smithy.protocol.test.traits)
3131
implementation(libs.smithy.aws.endpoints)
32+
implementation(libs.smithy.smoke.test.traits)
3233

3334
testImplementation(libs.junit.jupiter)
3435
testImplementation(libs.junit.jupiter.params)

codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/GradleGenerator.kt

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,16 @@
44
*/
55
package aws.sdk.kotlin.codegen
66

7+
import aws.sdk.kotlin.codegen.model.traits.testing.TestFailedResponseTrait
8+
import aws.sdk.kotlin.codegen.model.traits.testing.TestSuccessResponseTrait
79
import software.amazon.smithy.kotlin.codegen.core.*
810
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
11+
import software.amazon.smithy.kotlin.codegen.model.expectShape
12+
import software.amazon.smithy.kotlin.codegen.model.hasTrait
913
import software.amazon.smithy.kotlin.codegen.rendering.GradleWriter
14+
import software.amazon.smithy.kotlin.codegen.utils.topDownOperations
15+
import software.amazon.smithy.model.shapes.ServiceShape
16+
import software.amazon.smithy.smoketests.traits.SmokeTestsTrait
1017

1118
// TODO - would be nice to allow integrations to define custom settings in the plugin
1219
// e.g. we could then more consistently apply this integration if we could define a property like: `build.isAwsSdk: true`
@@ -64,9 +71,72 @@ class GradleGenerator : KotlinIntegration {
6471
}
6572
}
6673
}
74+
if (ctx.model.topDownOperations(ctx.settings.service).any { it.hasTrait<SmokeTestsTrait>() }) {
75+
write("")
76+
generateSmokeTestConfig(writer, ctx)
77+
}
6778
}
6879

6980
val contents = writer.toString()
7081
delegator.fileManifest.writeFile("build.gradle.kts", contents)
7182
}
83+
84+
private fun generateSmokeTestConfig(writer: GradleWriter, ctx: CodegenContext) {
85+
generateSmokeTestJarTask(writer, ctx)
86+
writer.write("")
87+
generateSmokeTestTask(writer, ctx)
88+
}
89+
90+
/**
91+
* Generates a gradle task to create smoke test runner JARs
92+
*/
93+
private fun generateSmokeTestJarTask(writer: GradleWriter, ctx: CodegenContext) {
94+
writer.withBlock("jvm {", "}") {
95+
withBlock("compilations {", "}") {
96+
write("val mainPath = getByName(#S).output.classesDirs", "main")
97+
write("val testPath = getByName(#S).output.classesDirs", "test")
98+
withBlock("tasks {", "}") {
99+
withBlock("register<Jar>(#S) {", "}", "smokeTestJar") {
100+
write("description = #S", "Creates smoke tests jar")
101+
write("group = #S", "application")
102+
write("dependsOn(build)")
103+
write("mustRunAfter(build)")
104+
withBlock("manifest {", "}") {
105+
write("attributes[#S] = #S", "Main-Class", "${ctx.settings.pkg.name}.smoketests.SmokeTestsKt")
106+
}
107+
write("val runtimePath = configurations.getByName(#S).map { if (it.isDirectory) it else zipTree(it) }", "jvmRuntimeClasspath")
108+
write("duplicatesStrategy = DuplicatesStrategy.EXCLUDE")
109+
write("from(runtimePath, mainPath, testPath)")
110+
write("archiveBaseName.set(#S)", "\${project.name}-smoketests")
111+
}
112+
}
113+
}
114+
}
115+
}
116+
117+
/**
118+
* Generates a gradle task to run smoke tests
119+
*/
120+
private fun generateSmokeTestTask(writer: GradleWriter, ctx: CodegenContext) {
121+
val hasSuccessResponseTrait = ctx.model.expectShape<ServiceShape>(ctx.settings.service).hasTrait(TestSuccessResponseTrait.ID)
122+
val hasFailedResponseTrait = ctx.model.expectShape<ServiceShape>(ctx.settings.service).hasTrait(TestFailedResponseTrait.ID)
123+
val inTestingEnvironment = hasFailedResponseTrait || hasSuccessResponseTrait
124+
125+
/**
126+
* E2E tests don't have sdkVersion in jar names. They're added later for publishing.
127+
* @see SmokeTestE2ETest
128+
*/
129+
val jarName = if (inTestingEnvironment) "\${project.name}-smoketests.jar" else "\${project.name}-smoketests-\$sdkVersion.jar"
130+
131+
writer.withBlock("tasks.register<JavaExec>(#S) {", "}", "smokeTest") {
132+
write("description = #S", "Runs smoke tests jar")
133+
write("group = #S", "verification")
134+
write("dependsOn(tasks.getByName(#S))", "smokeTestJar")
135+
write("mustRunAfter(tasks.getByName(#S))", "smokeTestJar")
136+
write("")
137+
write("val sdkVersion: String by project")
138+
write("val jarFile = file(#S)", "build/libs/$jarName")
139+
write("classpath = files(jarFile)")
140+
}
141+
}
72142
}

0 commit comments

Comments
 (0)