-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: endpoints2: aws middleware and integration changes #751
Conversation
*/ | ||
@InternalSdkApi | ||
public fun Endpoint.authScheme(): AuthScheme.SigV4? = | ||
attributes.getOrNull(AuthSchemesAttributeKey)?.find { it is AuthScheme.SigV4 } as AuthScheme.SigV4? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
correctness: as? AuthScheme.SigV4
/** | ||
* Sugar extension to pull an auth scheme out of the attribute set. | ||
* | ||
* FUTURE: Right now we only support sigv4. The auth scheme property can include multiple schemes, for now we only pull |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this return a list of auth schemes then?
Endpoint.authSchemes(): List<AuthScheme> =
attributes.getOrNull(AuthSchemesAttributeKey) ?: emptyList()
Then have a separate extensions like Endpoint.hasSigV4
or Endpoint.supportsSigv4
?
e.g.
@InternalSdkApi
public fun Endpoint.supportsSigv4(): Boolean =
authSchemes().find { it is AuthScheme.SigV4} != null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, I don't think it's necessary in the current context of us only supporting sigv4. When we have sigv4a as a first-class citizen the logic to retrieve this (it's basically used for presigning and for setting request context values for request signing) would have to evolve and at that point it would need extensions like this.
""".trimIndent(), | ||
) | ||
|
||
val EndpointUrlProp = ClientConfigProperty { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question/concern: Wouldn't this just be done by overriding the endpoint provider? This seems like two different ways to do the same thing to me but maybe I'm missing something
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is part of the endpoints SEP, it basically allows you to sideload an endpoint that you want to use in the ruleset and have it validated / transformed if necessary.
That said there's nothing preventing someone from writing their own endpoint provider that just blindly resolves to a static endpoint.
@@ -51,6 +48,31 @@ class AwsServiceConfigIntegration : KotlinIntegration { | |||
AwsRuntimeTypes.Config.Credentials.DefaultChainCredentialsProvider, | |||
) | |||
} | |||
|
|||
val UseFipsProp: ClientConfigProperty = ClientConfigProperty.Boolean( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: It is correct to add fips/dual-stack to every generated AWS client? Or should this be predicated on their use as builtins in rules?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is part of every aws client, yes.
@@ -382,12 +403,9 @@ class PresignerGenerator : KotlinIntegration { | |||
symbol = RuntimeTypes.Auth.Signing.AwsSigningCommon.SigningEndpointProvider | |||
name = "endpointProvider" | |||
documentation = | |||
"Provides the endpoint (hostname) and signing context to make requests to. When not provided a default resolver is configured automatically. This is an advanced client option." | |||
"Provides the endpoint (hostname) and signing context to make requests to. You MUST pass a provider will resolve to an endpoint for the desired request." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix: doc comment You MUST pass a provider that will resolve an endpoint ...
) { | ||
builtinParams.forEach { | ||
when (it.builtIn.get()) { | ||
"AWS::Region" -> renderBasicConfigBinding(writer, it, "region") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Can we use the config property names here rather than hard coding the strings? Or use shared constants?
writer.write("#L = config.endpointUrl?.toString()", EndpointParametersGenerator.getParamKotlinName(it)) | ||
|
||
// as a newer SDK we do NOT support these values, they are always false | ||
"AWS::S3::UseGlobalEndpoint", "AWS::STS::UseGlobalEndpoint" -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question/Correctness: Should we even render this for the Kotlin SDK? This seems like a legacy concern that we could filter or make internal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to force them false here if we definitely don't want to support this route in the SDK, since the ruleset could default them to true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I meant was should we even be surfacing this at the config level? I guess it's fine, it's just legacy cruft that doesn't really apply to us though.
@@ -59,4 +57,10 @@ internal fun <T : HttpMessageTestCase> HttpProtocolUnitTestGenerator<T>.renderCo | |||
|
|||
// specify a default region | |||
writer.write("region = \"us-east-1\"") | |||
writer.write( | |||
"endpointResolver = #T { #T(#S) }", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix: Isn't this the wrong config property name? (FWIW I still sort of think we should name the generated type/field EndpointResolver
but I believe it's endpointProvider
in generated config right now)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was incorrectly different based on whether or not the service had a ruleset, that's getting fixed in this next iteration (it'll all be the modern endpointProvider
).
*/ | ||
fun renderBindAwsBuiltins(ctx: ProtocolGenerator.GenerationContext, writer: KotlinWriter, builtinParams: List<Parameter>) { | ||
writer.withBlock( | ||
"public fun #T.Builder.bindAwsBuiltins(config: #T.Config) {", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix: internal
import software.amazon.smithy.model.shapes.OperationShape | ||
import software.amazon.smithy.rulesengine.language.EndpointRuleSet | ||
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait | ||
|
||
/** | ||
* HTTP client interceptor that resolves service endpoints for a single service | ||
*/ | ||
class ResolveAwsEndpointMiddleware(private val ctx: ProtocolGenerator.GenerationContext) : ProtocolMiddleware { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment: It would be really nice if we didn't even need this custom middleware anymore and could just use the one from smithy-kotlin
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact I don't think we do need this.
We seem to have punted on any kind of SPI or hook to customize bindings (which is fine) but we never really needed one anyway.
We have extremely fine grained ways to customize how parts of the code are generated via SectionWriter which integrations can register.
An example of this in current use is the service client declares a section (in this case with default code being generated in others it's empty and serves as a hook to append, example). Example of that section being overridden in AWS SDK.
We don't have to go this route but looking at EndpointParameterBindingGenerator
it actually seems like a reasonable use case to declare sections for rendering the various binding types (which will guarantee the order is correct). Integrations can wholesale override or append. Food for thought.
"smithy.api#documentation": "<p>The end time of the range for the request, expressed as the number of milliseconds\n after Jan 1, 1970 00:00:00 UTC. Events with a timestamp later than this time are not\n exported.</p>", | ||
"smithy.api#documentation": "<p>The end time of the range for the request, expressed as the number of milliseconds\n after Jan 1, 1970 00:00:00 UTC. Events with a timestamp later than this time are not\n exported.</p>\n <p>You must specify a time that is not earlier than when this log group was created.</p>", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Why are there service model changes in this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was just an incidental model update to make sure I wasn't ever breaking anything provider-wise with the newest version of the rules I could get. We can respect the in-main versions when we merge, or take the update, I don't have a real preference.
@@ -382,12 +403,9 @@ class PresignerGenerator : KotlinIntegration { | |||
symbol = RuntimeTypes.Auth.Signing.AwsSigningCommon.SigningEndpointProvider | |||
name = "endpointProvider" | |||
documentation = | |||
"Provides the endpoint (hostname) and signing context to make requests to. When not provided a default resolver is configured automatically. This is an advanced client option." | |||
"Provides the endpoint (hostname) and signing context to make requests to. You MUST pass a provider will resolve to an endpoint for the desired request." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: "You MUST pass a provider that will resolve..."?
Also, we don't usually scream "MUST" in our KDocs. That's appropriate for specifications but documentation would generally say something like "this is a required parameter".
override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { | ||
val resolverFeatureSymbol = buildSymbol { | ||
name = "ResolveAwsEndpoint" | ||
namespace(AwsKotlinDependency.AWS_HTTP, subpackage = "middleware") | ||
if (!ctx.service.hasTrait<EndpointRuleSetTrait>()) { | ||
writeDefault(writer) | ||
return | ||
} | ||
|
||
val rules = EndpointRuleSet.fromNode(ctx.service.expectTrait<EndpointRuleSetTrait>().ruleSet) | ||
val middlewareSymbol = ResolveEndpointMiddlewareGenerator.getSymbol(ctx.settings) | ||
val (inputSymbol, outputSymbol) = OperationIndex.of(ctx.model).getOperationInputOutputSymbols(op, ctx.symbolProvider) | ||
|
||
writer.withBlock( | ||
"op.install(#T<#T, #T>(config.endpointProvider) { input ->", | ||
"})", | ||
middlewareSymbol, | ||
inputSymbol, | ||
outputSymbol, | ||
) { | ||
write("#T(config)", bindAwsBuiltinsSymbol(ctx.settings)) | ||
EndpointParameterBindingGenerator(ctx.model, ctx.service, writer, op, rules, "input.").render() | ||
} | ||
writer.addImport(resolverFeatureSymbol) | ||
writer.write("op.install(#T(ServiceId, config.endpointResolver))", resolverFeatureSymbol) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Why should this be AWS-specific going forward? The Smithy spec now includes endpoint rules and in theory any custom service could use them, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It isn't, there's some minor code duplication to generate the default provider / tests in both this and the base ProtocolGenerator
- that's a duplication we chose to take on in the provider PR based on how we expose the hook to generate endpoints code on ProtocolGenerator
.
I think I misread your question. This isn't AWS-specific in the sense that there IS a base smithy version (in the other PR), this is just the AWS version that adds the builtin bindings.
insert, fix protocol tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple questions (and a few unanswered from previous review). Overall looks really good, think we're close
aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/imds/ImdsEndpointProvider.kt
Outdated
Show resolved
Hide resolved
aws-runtime/aws-endpoint/common/src/aws/sdk/kotlin/runtime/endpoint/AwsEndpoint.kt
Show resolved
Hide resolved
...n/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/endpoints/EndpointProviderGeneratorExt.kt
Outdated
Show resolved
Hide resolved
...gen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/core/AwsHttpBindingProtocolGenerator.kt
Show resolved
Hide resolved
.../src/main/kotlin/aws/sdk/kotlin/codegen/protocols/middleware/ResolveAwsEndpointMiddleware.kt
Outdated
Show resolved
Hide resolved
public val Endpoint.authScheme: AuthScheme.SigV4? get() = | ||
attributes.getOrNull(AuthSchemesAttributeKey)?.find { it is AuthScheme.SigV4 } as? AuthScheme.SigV4 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style: I suggest moving the get() =
to the next line with the getter body (ref: Kotlin coding conventions).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surprised that ktlintFormat didn't catch this one tbh
import software.amazon.smithy.model.shapes.OperationShape | ||
import software.amazon.smithy.rulesengine.language.EndpointRuleSet | ||
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait | ||
|
||
/** | ||
* HTTP client interceptor that resolves service endpoints for a single service | ||
*/ | ||
class ResolveAwsEndpointMiddleware(private val ctx: ProtocolGenerator.GenerationContext) : ProtocolMiddleware { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact I don't think we do need this.
We seem to have punted on any kind of SPI or hook to customize bindings (which is fine) but we never really needed one anyway.
We have extremely fine grained ways to customize how parts of the code are generated via SectionWriter which integrations can register.
An example of this in current use is the service client declares a section (in this case with default code being generated in others it's empty and serves as a hook to append, example). Example of that section being overridden in AWS SDK.
We don't have to go this route but looking at EndpointParameterBindingGenerator
it actually seems like a reasonable use case to declare sections for rendering the various binding types (which will guarantee the order is correct). Integrations can wholesale override or append. Food for thought.
Kudos, SonarCloud Quality Gate passed! 0 Bugs No Coverage information |
Description of changes
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.