Skip to content

Commit cfc4a3e

Browse files
millemsdavidh44
andauthored
Add support for service-specific endpoint overrides without code changes. (#5562)
* Made protected API changes in support of service-specific endpoint overrides. Class-Level Changes: * DefaultServiceEndpointBuilder - Deprecated in favor of the new ClientEndpointProvider and AwsClientEndpointProvider * ClientEndpointProvider - An interface for resolving the client-level endpoint. This endpoint may be overridden by the request-level EndpointProvider. This joins the existing fleet of endpoint-related providers, like region providers, dualstack providers, and FIPS providers. * AwsClientEndpointProvider - An implementation of ClientEndpointProvider that checks the client configuration, environment and service metadata to determine the client endpoint. Field Changes: * Deprecated SdkClientOption.ENDPOINT and SdkClientOption.ENDPOINT_OVERRIDDEN in favor of a new SdkClientOption.CLIENT_ENDPOINT_PROVIDER. The new property allows updating both values in a single field. It was a necessary improvement so that these properties can stay in sync, reliably. * Deprecated SdkExecutionAttribute.CLIENT_ENDPOINT and SdkClientOption.ENDPOINT_OVERRIDDEN in favor of a new SdkInternalExecutionAttribute.CLIENT_ENDPOINT_PROVIDER. The new property exists for much the same reasons as the new SdkClientOption, but also provides an opportunity to hide these should-be-internal-to-the-SDK attributes. * Updates to the way SDK clients are built, to support the new client endpoint providers. 1. Make clients set the SdkClientOption.CLIENT_ENDPOINT_PROVIDER when they are created. 2. Update the default AWS and SDK client builders to default the SdkClientOption.CLIENT_ENDPOINT_PROVIDERs so that old clients continue to work with the new core. Old clients versions won't support setting the endpoint with the new environment variable/system property/profile settings. This commit also includes EndpointSharedConfigTest which tests to make sure that the new ways of overriding the endpoint work for clients. * Moved non-client users of DefaultServiceEndpointBuilder over to AwsClientEndpointProvider. These are the users that remain after the last commit: 1. RdsPresignInterceptors in RDS, Neptune and DocDB. It's unfortunate that these are pretty much copy+pasted classes, but that seems like a future problem to figure out. 2. S3Utilities 3. S3Presigner 4. PollyPresigner * Miscellaneous changes required to move the SDK over from the old SdkClientOptions and SdkExecutionAttributes to the new SdkClientOptions and SdkInternalExecutionAttribute. * Codegen test file changes. * Changelog entry. * Fix checkstyle issue. * Update .changes/next-release/feature-AWSSDKforJavav2-eea40a4.json Co-authored-by: David Ho <[email protected]> * WIP * Updated environment variables and profile properties to match the other AWS SDKs. * Addressed comments. * Updated test to use new profile property and environment variables. --------- Co-authored-by: David Ho <[email protected]>
1 parent a495ee8 commit cfc4a3e

File tree

70 files changed

+2363
-640
lines changed

Some content is hidden

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

70 files changed

+2363
-640
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "Add support for specifying endpoint overrides using environment variables, system properties or profile files. More information about this feature is available here: https://docs.aws.amazon.com/sdkref/latest/guide/feature-ss-endpoints.html"
6+
}

codegen/src/main/java/software/amazon/awssdk/codegen/internal/Utils.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package software.amazon.awssdk.codegen.internal;
1717

1818
import static java.util.stream.Collectors.toList;
19+
import static software.amazon.awssdk.utils.StringUtils.lowerCase;
1920

2021
import java.io.Closeable;
2122
import java.io.File;
@@ -133,7 +134,7 @@ public static String removeLeading(String str, String toRemove) {
133134
if (str == null) {
134135
return null;
135136
}
136-
if (str.startsWith(toRemove)) {
137+
if (lowerCase(str).startsWith(lowerCase(toRemove))) {
137138
return str.substring(toRemove.length());
138139
}
139140
return str;
@@ -143,7 +144,7 @@ public static String removeTrailing(String str, String toRemove) {
143144
if (str == null) {
144145
return null;
145146
}
146-
if (str.endsWith(toRemove)) {
147+
if (lowerCase(str).endsWith(lowerCase(toRemove))) {
147148
return str.substring(0, str.length() - toRemove.length());
148149
}
149150
return str;

codegen/src/main/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategy.java

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,19 +114,42 @@ private static boolean isJavaKeyword(String word) {
114114

115115
@Override
116116
public String getServiceName() {
117-
String baseName = Stream.of(serviceModel.getMetadata().getServiceId())
118-
.filter(Objects::nonNull)
119-
.filter(s -> !s.trim().isEmpty())
120-
.findFirst()
121-
.orElseThrow(() -> new IllegalStateException("ServiceId is missing in the c2j model."));
122-
117+
String baseName = serviceId();
123118
baseName = pascalCase(baseName);
119+
baseName = removeRedundantPrefixesAndSuffixes(baseName);
120+
return baseName;
121+
}
124122

125-
// Special cases
126-
baseName = Utils.removeLeading(baseName, "Amazon");
127-
baseName = Utils.removeLeading(baseName, "Aws");
128-
baseName = Utils.removeTrailing(baseName, "Service");
123+
@Override
124+
public String getServiceNameForEnvironmentVariables() {
125+
String baseName = serviceId();
126+
baseName = baseName.replace(' ', '_');
127+
baseName = StringUtils.upperCase(baseName);
128+
return baseName;
129+
}
130+
131+
@Override
132+
public String getServiceNameForSystemProperties() {
133+
return getServiceName();
134+
}
135+
136+
@Override
137+
public String getServiceNameForProfileFile() {
138+
return StringUtils.lowerCase(getServiceNameForEnvironmentVariables());
139+
}
140+
141+
private String serviceId() {
142+
return Stream.of(serviceModel.getMetadata().getServiceId())
143+
.filter(Objects::nonNull)
144+
.filter(s -> !s.trim().isEmpty())
145+
.findFirst()
146+
.orElseThrow(() -> new IllegalStateException("ServiceId is missing in the c2j model."));
147+
}
129148

149+
private static String removeRedundantPrefixesAndSuffixes(String baseName) {
150+
baseName = Utils.removeLeading(baseName, "amazon");
151+
baseName = Utils.removeLeading(baseName, "aws");
152+
baseName = Utils.removeTrailing(baseName, "service");
130153
return baseName;
131154
}
132155

codegen/src/main/java/software/amazon/awssdk/codegen/naming/NamingStrategy.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,21 @@ public interface NamingStrategy {
2929
*/
3030
String getServiceName();
3131

32+
/**
33+
* Retrieve the service name that should be used for environment variables.
34+
*/
35+
String getServiceNameForEnvironmentVariables();
36+
37+
/**
38+
* Retrieve the service name that should be used for system properties.
39+
*/
40+
String getServiceNameForSystemProperties();
41+
42+
/**
43+
* Retrieve the service name that should be used for profile properties.
44+
*/
45+
String getServiceNameForProfileFile();
46+
3247
/**
3348
* Retrieve the client package name that should be used based on the service name.
3449
*/

codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import software.amazon.awssdk.auth.token.signer.aws.BearerTokenSigner;
4444
import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder;
4545
import software.amazon.awssdk.awscore.client.config.AwsClientOption;
46+
import software.amazon.awssdk.awscore.endpoint.AwsClientEndpointProvider;
4647
import software.amazon.awssdk.codegen.internal.Utils;
4748
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
4849
import software.amazon.awssdk.codegen.model.intermediate.OperationModel;
@@ -72,6 +73,7 @@
7273
import software.amazon.awssdk.identity.spi.IdentityProvider;
7374
import software.amazon.awssdk.identity.spi.IdentityProviders;
7475
import software.amazon.awssdk.identity.spi.TokenIdentity;
76+
import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption;
7577
import software.amazon.awssdk.utils.AttributeMap;
7678
import software.amazon.awssdk.utils.CollectionUtils;
7779
import software.amazon.awssdk.utils.StringUtils;
@@ -456,6 +458,27 @@ private MethodSpec finalizeServiceConfigurationMethod() {
456458
builder.addStatement("builder.option($T.$L, resolveAccountIdEndpointMode(config))",
457459
AwsClientOption.class, model.getNamingStrategy().getEnumValueName("accountIdEndpointMode"));
458460
}
461+
462+
String serviceNameForEnvVar = model.getNamingStrategy().getServiceNameForEnvironmentVariables();
463+
String serviceNameForSystemProperty = model.getNamingStrategy().getServiceNameForSystemProperties();
464+
String serviceNameForProfileFile = model.getNamingStrategy().getServiceNameForProfileFile();
465+
466+
builder.addCode("builder.lazyOptionIfAbsent($T.CLIENT_ENDPOINT_PROVIDER, c ->", SdkClientOption.class)
467+
.addCode(" $T.builder()", AwsClientEndpointProvider.class)
468+
.addCode(" .serviceEndpointOverrideEnvironmentVariable($S)", "AWS_ENDPOINT_URL_" + serviceNameForEnvVar)
469+
.addCode(" .serviceEndpointOverrideSystemProperty($S)", "aws.endpointUrl" + serviceNameForSystemProperty)
470+
.addCode(" .serviceProfileProperty($S)", serviceNameForProfileFile)
471+
.addCode(" .serviceEndpointPrefix(serviceEndpointPrefix())")
472+
.addCode(" .defaultProtocol($S)", "https")
473+
.addCode(" .region(c.get($T.AWS_REGION))", AwsClientOption.class)
474+
.addCode(" .profileFile(c.get($T.PROFILE_FILE_SUPPLIER))", SdkClientOption.class)
475+
.addCode(" .profileName(c.get($T.PROFILE_NAME))", SdkClientOption.class)
476+
.addCode(" .putAdvancedOption($T.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT,", ServiceMetadataAdvancedOption.class)
477+
.addCode(" c.get($T.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT))", ServiceMetadataAdvancedOption.class)
478+
.addCode(" .dualstackEnabled(c.get($T.DUALSTACK_ENDPOINT_ENABLED))", AwsClientOption.class)
479+
.addCode(" .fipsEnabled(c.get($T.FIPS_ENDPOINT_ENABLED))", AwsClientOption.class)
480+
.addCode(" .build());");
481+
459482
builder.addStatement("return builder.build()");
460483
return builder.build();
461484
}

codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClass.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,8 @@ private MethodSpec constructor(TypeSpec.Builder classBuilder) {
254254
"AsyncEndpointDiscoveryCacheLoader"));
255255

256256
if (model.getCustomizationConfig().allowEndpointOverrideForEndpointDiscoveryRequiredOperations()) {
257-
builder.beginControlFlow("if (clientConfiguration.option(SdkClientOption.ENDPOINT_OVERRIDDEN) == "
258-
+ "Boolean.TRUE)");
257+
builder.beginControlFlow("if (clientConfiguration.option(SdkClientOption.CLIENT_ENDPOINT_PROVIDER)"
258+
+ ".isEndpointOverridden())");
259259
builder.addStatement("log.warn($S)",
260260
"Endpoint discovery is enabled for this client, and an endpoint override was also "
261261
+ "specified. This will disable endpoint discovery for methods that require it, instead "
@@ -401,7 +401,8 @@ protected MethodSpec.Builder operationBody(MethodSpec.Builder builder, Operation
401401
builder.addStatement("boolean endpointDiscoveryEnabled = "
402402
+ "clientConfiguration.option(SdkClientOption.ENDPOINT_DISCOVERY_ENABLED)");
403403
builder.addStatement("boolean endpointOverridden = "
404-
+ "clientConfiguration.option(SdkClientOption.ENDPOINT_OVERRIDDEN) == Boolean.TRUE");
404+
+ "clientConfiguration.option(SdkClientOption.CLIENT_ENDPOINT_PROVIDER)"
405+
+ ".isEndpointOverridden()");
405406

406407
if (opModel.getEndpointDiscovery().isRequired()) {
407408
if (!model.getCustomizationConfig().allowEndpointOverrideForEndpointDiscoveryRequiredOperations()) {
@@ -443,7 +444,8 @@ protected MethodSpec.Builder operationBody(MethodSpec.Builder builder, Operation
443444
builder.addCode("endpointFuture = identityFuture.thenCompose(credentials -> {")
444445
.addCode(" $1T endpointDiscoveryRequest = $1T.builder()", EndpointDiscoveryRequest.class)
445446
.addCode(" .required($L)", opModel.getInputShape().getEndpointDiscovery().isRequired())
446-
.addCode(" .defaultEndpoint(clientConfiguration.option($T.ENDPOINT))", SdkClientOption.class)
447+
.addCode(" .defaultEndpoint(clientConfiguration.option($T.CLIENT_ENDPOINT_PROVIDER).clientEndpoint())",
448+
SdkClientOption.class)
447449
.addCode(" .overrideConfiguration($N.overrideConfiguration().orElse(null))",
448450
opModel.getInput().getVariableName())
449451
.addCode(" .build();")

codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/SyncClientClass.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,8 @@ private MethodSpec constructor() {
219219
"EndpointDiscoveryCacheLoader"));
220220

221221
if (model.getCustomizationConfig().allowEndpointOverrideForEndpointDiscoveryRequiredOperations()) {
222-
builder.beginControlFlow("if (clientConfiguration.option(SdkClientOption.ENDPOINT_OVERRIDDEN) == "
223-
+ "Boolean.TRUE)");
222+
builder.beginControlFlow("if (clientConfiguration.option(SdkClientOption.CLIENT_ENDPOINT_PROVIDER)"
223+
+ ".isEndpointOverridden())");
224224
builder.addStatement("log.warn(() -> $S)",
225225
"Endpoint discovery is enabled for this client, and an endpoint override was also "
226226
+ "specified. This will disable endpoint discovery for methods that require it, instead "
@@ -265,7 +265,8 @@ private MethodSpec traditionalMethod(OperationModel opModel) {
265265
method.addStatement("boolean endpointDiscoveryEnabled = "
266266
+ "clientConfiguration.option(SdkClientOption.ENDPOINT_DISCOVERY_ENABLED)");
267267
method.addStatement("boolean endpointOverridden = "
268-
+ "clientConfiguration.option(SdkClientOption.ENDPOINT_OVERRIDDEN) == Boolean.TRUE");
268+
+ "clientConfiguration.option(SdkClientOption.CLIENT_ENDPOINT_PROVIDER)"
269+
+ ".isEndpointOverridden()");
269270

270271
if (opModel.getEndpointDiscovery().isRequired()) {
271272
if (!model.getCustomizationConfig().allowEndpointOverrideForEndpointDiscoveryRequiredOperations()) {
@@ -309,7 +310,8 @@ private MethodSpec traditionalMethod(OperationModel opModel) {
309310

310311
method.addCode("$1T endpointDiscoveryRequest = $1T.builder()", EndpointDiscoveryRequest.class)
311312
.addCode(" .required($L)", opModel.getInputShape().getEndpointDiscovery().isRequired())
312-
.addCode(" .defaultEndpoint(clientConfiguration.option($T.ENDPOINT))", SdkClientOption.class)
313+
.addCode(" .defaultEndpoint(clientConfiguration.option($T.CLIENT_ENDPOINT_PROVIDER).clientEndpoint())",
314+
SdkClientOption.class)
313315
.addCode(" .overrideConfiguration($N.overrideConfiguration().orElse(null))",
314316
opModel.getInput().getVariableName())
315317
.addCode(" .build();");

codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ServiceClientConfigurationUtils.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
3434
import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeSpecUtils;
3535
import software.amazon.awssdk.codegen.poet.rules.EndpointRulesSpecUtils;
36+
import software.amazon.awssdk.core.ClientEndpointProvider;
3637
import software.amazon.awssdk.core.client.config.ClientOption;
3738
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
3839
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
@@ -161,21 +162,21 @@ private Field endpointOverrideField() {
161162
private CodeBlock endpointOverrideConfigSetter() {
162163
return CodeBlock.builder()
163164
.beginControlFlow("if (endpointOverride != null)")
164-
.addStatement("config.option($T.ENDPOINT, endpointOverride)", SdkClientOption.class)
165-
.addStatement("config.option($T.ENDPOINT_OVERRIDDEN, true)", SdkClientOption.class)
165+
.addStatement("config.option($T.CLIENT_ENDPOINT_PROVIDER, $T.forEndpointOverride(endpointOverride))",
166+
SdkClientOption.class, ClientEndpointProvider.class)
166167
.nextControlFlow("else")
167-
.addStatement("config.option($T.ENDPOINT, null)", SdkClientOption.class)
168-
.addStatement("config.option($T.ENDPOINT_OVERRIDDEN, false)", SdkClientOption.class)
168+
.addStatement("config.option($T.CLIENT_ENDPOINT_PROVIDER, null)", SdkClientOption.class)
169169
.endControlFlow()
170170
.addStatement("return this")
171171
.build();
172172
}
173173

174174
private CodeBlock endpointOverrideConfigGetter() {
175175
return CodeBlock.builder()
176-
.beginControlFlow("if (Boolean.TRUE.equals(config.option($T.ENDPOINT_OVERRIDDEN)))",
177-
SdkClientOption.class)
178-
.addStatement("return config.option($T.ENDPOINT)", SdkClientOption.class)
176+
.addStatement("$T clientEndpoint = config.option($T.CLIENT_ENDPOINT_PROVIDER)",
177+
ClientEndpointProvider.class, SdkClientOption.class)
178+
.beginControlFlow("if (clientEndpoint != null && clientEndpoint.isEndpointOverridden())")
179+
.addStatement("return clientEndpoint.clientEndpoint()")
179180
.endControlFlow()
180181
.addStatement("return null")
181182
.build();

codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/RequestEndpointInterceptorSpec.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import software.amazon.awssdk.core.interceptor.Context;
2727
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
2828
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
29-
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
3029
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
3130
import software.amazon.awssdk.endpoints.Endpoint;
3231
import software.amazon.awssdk.http.SdkHttpRequest;
@@ -71,14 +70,14 @@ private MethodSpec modifyHttpRequestMethod() {
7170
.addStatement("return context.httpRequest()")
7271
.endControlFlow().build();
7372

74-
b.addStatement("$1T endpoint = ($1T) executionAttributes.getAttribute($2T.RESOLVED_ENDPOINT)",
73+
b.addStatement("$T endpoint = executionAttributes.getAttribute($T.RESOLVED_ENDPOINT)",
7574
Endpoint.class,
7675
SdkInternalExecutionAttribute.class);
7776
b.addStatement("return $T.setUri(context.httpRequest(),"
78-
+ "executionAttributes.getAttribute($T.CLIENT_ENDPOINT),"
77+
+ "executionAttributes.getAttribute($T.CLIENT_ENDPOINT_PROVIDER).clientEndpoint(),"
7978
+ "endpoint.url())",
8079
endpointRulesSpecUtils.rulesRuntimeClassName("AwsEndpointProviderUtils"),
81-
SdkExecutionAttribute.class);
80+
SdkInternalExecutionAttribute.class);
8281
return b.build();
8382
}
8483

codegen/src/main/resources/software/amazon/awssdk/codegen/rules/AwsEndpointProviderUtils.java.resource

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ public final class AwsEndpointProviderUtils {
4444
public static String endpointBuiltIn(ExecutionAttributes executionAttributes) {
4545
if (endpointIsOverridden(executionAttributes)) {
4646
return invokeSafely(() -> {
47-
URI endpointOverride = executionAttributes.getAttribute(SdkExecutionAttribute.CLIENT_ENDPOINT);
47+
URI endpointOverride = executionAttributes.getAttribute(SdkInternalExecutionAttribute.CLIENT_ENDPOINT_PROVIDER)
48+
.clientEndpoint();
4849
return new URI(endpointOverride.getScheme(), null, endpointOverride.getHost(), endpointOverride.getPort(),
4950
endpointOverride.getPath(), null, endpointOverride.getFragment()).toString();
5051
});
@@ -53,11 +54,10 @@ public final class AwsEndpointProviderUtils {
5354
}
5455

5556
/**
56-
* True if the the {@link SdkExecutionAttribute#ENDPOINT_OVERRIDDEN} attribute is present and its value is
57-
* {@code true}, {@code false} otherwise.
57+
* Read {@link SdkExecutionAttribute#CLIENT_ENDPOINT_PROVIDER}'s isEndpointOverridden attribute.
5858
*/
5959
public static boolean endpointIsOverridden(ExecutionAttributes attrs) {
60-
return attrs.getOptionalAttribute(SdkExecutionAttribute.ENDPOINT_OVERRIDDEN).orElse(false);
60+
return attrs.getAttribute(SdkInternalExecutionAttribute.CLIENT_ENDPOINT_PROVIDER).isEndpointOverridden();
6161
}
6262

6363
/**

0 commit comments

Comments
 (0)