Skip to content

Commit 12c4386

Browse files
authored
Added a new dualstackEnabled property to every client builder. (#2818)
This option can be used to make calls be invoked against AWS endpoints which return IPv6 records. This can also be enabled via the `AWS_USE_DUALSTACK_ENDPOINT` environment variable, `aws.useDualstackEndpoint` system property, or the `use_dualstack_endpoint` profile file property: 1. This PR builds on top of the 'endpoint variants' #2808, by specifying the DUALSTACK endpoint variant whenever the customer requests dualstack to be enabled. 2. The bulk of the files in this PR are addressing the fact that we already have service configuration for enabling dualstack for S3 and S3 control. These existing options have been deprecated in favor of the new option that works for all services.
1 parent 3f28c17 commit 12c4386

File tree

48 files changed

+922
-61
lines changed

Some content is hidden

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

48 files changed

+922
-61
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "Amazon S3",
3+
"contributor": "",
4+
"type": "deprecation",
5+
"description": "Deprecated `S3Configuration.Builder`'s `dualstackEnabled` in favor of the new service-standard `S3ClientBuilder.dualstackEnabled`."
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "Amazon S3 Control",
3+
"contributor": "",
4+
"type": "deprecation",
5+
"description": "Deprecated `S3ControlConfiguration.Builder`'s `dualstackEnabled` in favor of the new service-standard `S3ControlClientBuilder.dualstackEnabled`."
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "AWS SDK for Java",
3+
"contributor": "",
4+
"type": "feature",
5+
"description": "Added a new `dualstackEnabled` property to every client builder, which can be used to make calls be invoked against AWS endpoints which return IPv6 records. This can also be enabled via the `AWS_USE_DUALSTACK_ENDPOINT` environment variable, `aws.useDualstackEndpoint` system property, or the `use_dualstack_endpoint` profile file property."
6+
}

codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ public class CustomizationConfig {
4242
* does not have advanced configuration.
4343
*/
4444
private String serviceSpecificClientConfigClass;
45+
46+
/**
47+
* Whether a service has a dualstack configuration in its {@link #serviceSpecificClientConfigClass}.
48+
*/
49+
private boolean serviceConfigHasDualstackConfig = false;
50+
4551
/**
4652
* Specify shapes to be renamed.
4753
*/
@@ -197,6 +203,8 @@ public class CustomizationConfig {
197203

198204
private RetryMode defaultRetryMode;
199205

206+
207+
200208
private CustomizationConfig() {
201209
}
202210

@@ -244,6 +252,14 @@ public void setServiceSpecificClientConfigClass(String serviceSpecificClientConf
244252
this.serviceSpecificClientConfigClass = serviceSpecificClientConfig;
245253
}
246254

255+
public boolean getServiceConfigHasDualstackConfig() {
256+
return serviceConfigHasDualstackConfig;
257+
}
258+
259+
public void setServiceConfigHasDualstackConfig(boolean serviceConfigHasDualstackConfig) {
260+
this.serviceConfigHasDualstackConfig = serviceConfigHasDualstackConfig;
261+
}
262+
247263
public List<ConvenienceTypeOverload> getConvenienceTypeOverloads() {
248264
return this.convenienceTypeOverloads;
249265
}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import software.amazon.awssdk.annotations.SdkInternalApi;
3535
import software.amazon.awssdk.auth.signer.Aws4Signer;
3636
import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder;
37+
import software.amazon.awssdk.awscore.client.config.AwsClientOption;
3738
import software.amazon.awssdk.codegen.internal.Utils;
3839
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
3940
import software.amazon.awssdk.codegen.model.intermediate.OperationModel;
@@ -54,6 +55,7 @@
5455
import software.amazon.awssdk.utils.AttributeMap;
5556
import software.amazon.awssdk.utils.CollectionUtils;
5657
import software.amazon.awssdk.utils.StringUtils;
58+
import software.amazon.awssdk.utils.Validate;
5759

5860
public class BaseClientBuilderClass implements ClassSpec {
5961
private final IntermediateModel model;
@@ -241,9 +243,20 @@ private MethodSpec finalizeServiceConfigurationMethod() {
241243
if (StringUtils.isNotBlank(clientConfigClassName)) {
242244
ClassName clientConfigClass = ClassName.bestGuess(clientConfigClassName);
243245
builder.addCode("$1T.Builder c = (($1T) config.option($2T.SERVICE_CONFIGURATION)).toBuilder();" +
244-
"c.profileFile(c.profileFile() != null ? c.profileFile() : config.option($2T.PROFILE_FILE))" +
245-
" .profileName(c.profileName() != null ? c.profileName() : config.option($2T.PROFILE_NAME));",
246+
"c.profileFile(c.profileFile() != null ? c.profileFile() : config.option($2T.PROFILE_FILE));" +
247+
"c.profileName(c.profileName() != null ? c.profileName() : config.option($2T.PROFILE_NAME));",
246248
clientConfigClass, SdkClientOption.class);
249+
250+
if (model.getCustomizationConfig().getServiceConfigHasDualstackConfig()) {
251+
builder.addCode("if (c.dualstackEnabled() != null) {")
252+
.addCode(" $T.validState(config.option($T.DUALSTACK_ENDPOINT_ENABLED) == null, \"Dualstack has been "
253+
+ "configured on both $L and the client/global level. Please limit dualstack configuration to "
254+
+ "one location.\");",
255+
Validate.class, AwsClientOption.class, clientConfigClassName)
256+
.addCode("} else {")
257+
.addCode(" c.dualstackEnabled(config.option($T.DUALSTACK_ENDPOINT_ENABLED));", AwsClientOption.class)
258+
.addCode("}");
259+
}
247260
}
248261

249262
// Update configuration

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import software.amazon.awssdk.annotations.SdkInternalApi;
88
import software.amazon.awssdk.auth.signer.Aws4Signer;
99
import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder;
10+
import software.amazon.awssdk.awscore.client.config.AwsClientOption;
1011
import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
1112
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
1213
import software.amazon.awssdk.core.client.config.SdkClientOption;
@@ -15,6 +16,7 @@
1516
import software.amazon.awssdk.core.signer.Signer;
1617
import software.amazon.awssdk.utils.AttributeMap;
1718
import software.amazon.awssdk.utils.CollectionUtils;
19+
import software.amazon.awssdk.utils.Validate;
1820

1921
/**
2022
* Internal base class for {@link DefaultJsonClientBuilder} and {@link DefaultJsonAsyncClientBuilder}.
@@ -47,8 +49,15 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon
4749
interceptors = CollectionUtils.mergeLists(interceptors, config.option(SdkClientOption.EXECUTION_INTERCEPTORS));
4850
ServiceConfiguration.Builder c = ((ServiceConfiguration) config.option(SdkClientOption.SERVICE_CONFIGURATION))
4951
.toBuilder();
50-
c.profileFile(c.profileFile() != null ? c.profileFile() : config.option(SdkClientOption.PROFILE_FILE)).profileName(
51-
c.profileName() != null ? c.profileName() : config.option(SdkClientOption.PROFILE_NAME));
52+
c.profileFile(c.profileFile() != null ? c.profileFile() : config.option(SdkClientOption.PROFILE_FILE));
53+
c.profileName(c.profileName() != null ? c.profileName() : config.option(SdkClientOption.PROFILE_NAME));
54+
if (c.dualstackEnabled() != null) {
55+
Validate.validState(
56+
config.option(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED) == null,
57+
"Dualstack has been configured on both ServiceConfiguration and the client/global level. Please limit dualstack configuration to one location.");
58+
} else {
59+
c.dualstackEnabled(config.option(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED));
60+
}
5261
return config.toBuilder().option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors)
5362
.option(SdkClientOption.RETRY_POLICY, MyServiceRetryPolicy.resolveRetryPolicy(config))
5463
.option(SdkClientOption.SERVICE_CONFIGURATION, c.build()).build();

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/json/customization.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"presignersFqcn": "software.amazon.awssdk.services.acm.presign.AcmClientPresigners",
66
"serviceSpecificHttpConfig": "software.amazon.MyServiceHttpConfig",
77
"serviceSpecificClientConfigClass": "ServiceConfiguration",
8+
"serviceConfigHasDualstackConfig": true,
89
"customRetryPolicy": "software.amazon.MyServiceRetryPolicy",
910
"verifiedSimpleMethods" : ["paginatedOperationWithResultKey"],
1011
"blacklistedSimpleMethods" : [

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/rest-json/customization.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"presignersFqcn": "software.amazon.awssdk.services.acm.presign.AcmClientPresigners",
66
"serviceSpecificHttpConfig": "software.amazon.MyServiceHttpConfig",
77
"serviceSpecificClientConfigClass": "ServiceConfiguration",
8+
"serviceConfigHasDualstackConfig": true,
89
"customRetryPolicy": "software.amazon.MyServiceRetryPolicy",
910
"verifiedSimpleMethods" : ["paginatedOperationWithResultKey"],
1011
"blacklistedSimpleMethods" : [

core/aws-core/src/main/java/software/amazon/awssdk/awscore/AwsExecutionAttribute.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ public final class AwsExecutionAttribute extends SdkExecutionAttribute {
3939
*/
4040
public static final ExecutionAttribute<String> ENDPOINT_PREFIX = new ExecutionAttribute<>("AwsEndpointPrefix");
4141

42+
public static final ExecutionAttribute<Boolean> DUALSTACK_ENDPOINT_ENABLED =
43+
new ExecutionAttribute<>("DualstackEndpointsEnabled");
44+
4245
private AwsExecutionAttribute() {
4346
}
4447
}

core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsClientBuilder.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ public interface AwsClientBuilder<BuilderT extends AwsClientBuilder<BuilderT, Cl
5151
*/
5252
BuilderT credentialsProvider(AwsCredentialsProvider credentialsProvider);
5353

54-
5554
/**
5655
* Configure the region with which the SDK should communicate.
5756
*
@@ -62,6 +61,24 @@ public interface AwsClientBuilder<BuilderT extends AwsClientBuilder<BuilderT, Cl
6261
* <li>Check the {user.home}/.aws/credentials and {user.home}/.aws/config files for the region.</li>
6362
* <li>If running in EC2, check the EC2 metadata service for the region.</li>
6463
* </ol>
64+
*
65+
* <p>If the region is not found in any of the locations above, an exception will be thrown at {@link #build()} time.
6566
*/
6667
BuilderT region(Region region);
68+
69+
/**
70+
* Configure whether the SDK should use the AWS dualstack endpoint.
71+
*
72+
* <p>If this is not specified, the SDK will attempt to determine whether the dualstack endpoint should be used
73+
* automatically using the following logic:
74+
* <ol>
75+
* <li>Check the 'aws.useDualstackEndpoint' system property for 'true' or 'false'.</li>
76+
* <li>Check the 'AWS_USE_DUALSTACK_ENDPOINT' environment variable for 'true' or 'false'.</li>
77+
* <li>Check the {user.home}/.aws/credentials and {user.home}/.aws/config files for the 'use_dualstack_endpoint'
78+
* property set to 'true' or 'false'.</li>
79+
* </ol>
80+
*
81+
* <p>If the setting is not found in any of the locations above, 'false' will be used.
82+
*/
83+
BuilderT dualstackEnabled(Boolean dualstackEndpointEnabled);
6784
}

core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import software.amazon.awssdk.awscore.client.config.AwsAdvancedClientOption;
2727
import software.amazon.awssdk.awscore.client.config.AwsClientOption;
2828
import software.amazon.awssdk.awscore.endpoint.DefaultServiceEndpointBuilder;
29+
import software.amazon.awssdk.awscore.endpoint.DualstackEnabledProvider;
2930
import software.amazon.awssdk.awscore.eventstream.EventStreamInitialRequestInterceptor;
3031
import software.amazon.awssdk.awscore.interceptor.HelpfulUnknownHostExceptionInterceptor;
3132
import software.amazon.awssdk.awscore.retry.AwsRetryPolicy;
@@ -141,6 +142,8 @@ protected final SdkClientConfiguration finalizeChildConfiguration(SdkClientConfi
141142

142143
configuration = configuration.toBuilder()
143144
.option(AwsClientOption.AWS_REGION, resolveRegion(configuration))
145+
.option(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED,
146+
resolveDualstackEndpointEnabled(configuration))
144147
.build();
145148

146149
return configuration.toBuilder()
@@ -180,6 +183,7 @@ private URI endpointFromConfig(SdkClientConfiguration config) {
180183
.withRegion(config.option(AwsClientOption.AWS_REGION))
181184
.withProfileFile(config.option(SdkClientOption.PROFILE_FILE))
182185
.withProfileName(config.option(SdkClientOption.PROFILE_NAME))
186+
.withDualstackEnabled(config.option(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED))
183187
.getServiceEndpoint();
184188
}
185189

@@ -210,6 +214,29 @@ private Region regionFromDefaultProvider(SdkClientConfiguration config) {
210214
.getRegion();
211215
}
212216

217+
/**
218+
* Resolve whether a dualstack endpoint should be used for this client.
219+
*/
220+
private Boolean resolveDualstackEndpointEnabled(SdkClientConfiguration config) {
221+
return config.option(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED) != null
222+
? config.option(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)
223+
: dualstackEndpointFromDefaultProvider(config);
224+
}
225+
226+
/**
227+
* Load the dualstack endpoint setting from the default provider logic.
228+
*/
229+
private Boolean dualstackEndpointFromDefaultProvider(SdkClientConfiguration config) {
230+
ProfileFile profileFile = config.option(SdkClientOption.PROFILE_FILE);
231+
String profileName = config.option(SdkClientOption.PROFILE_NAME);
232+
return DualstackEnabledProvider.builder()
233+
.profileFile(() -> profileFile)
234+
.profileName(profileName)
235+
.build()
236+
.isDualstackEnabled()
237+
.orElse(null);
238+
}
239+
213240
/**
214241
* Resolve the credentials that should be used based on the customer's configuration.
215242
*/
@@ -251,6 +278,16 @@ public final void setRegion(Region region) {
251278
region(region);
252279
}
253280

281+
@Override
282+
public BuilderT dualstackEnabled(Boolean dualstackEndpointEnabled) {
283+
clientConfiguration.option(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED, dualstackEndpointEnabled);
284+
return thisBuilder();
285+
}
286+
287+
public final void setDualstackEnabled(Boolean dualstackEndpointEnabled) {
288+
dualstackEnabled(dualstackEndpointEnabled);
289+
}
290+
254291
@Override
255292
public final BuilderT credentialsProvider(AwsCredentialsProvider credentialsProvider) {
256293
clientConfiguration.option(AwsClientOption.CREDENTIALS_PROVIDER, credentialsProvider);

core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/config/AwsClientOption.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ public final class AwsClientOption<T> extends ClientOption<T> {
4040
*/
4141
public static final AwsClientOption<Region> SIGNING_REGION = new AwsClientOption<>(Region.class);
4242

43+
/**
44+
* Whether the SDK should resolve dualstack endpoints instead of default endpoints. See
45+
* {@link AwsClientBuilder#dualstackEnabled(Boolean)}.
46+
*/
47+
public static final AwsClientOption<Boolean> DUALSTACK_ENDPOINT_ENABLED = new AwsClientOption<>(Boolean.class);
48+
4349
/**
4450
* Scope name to use during signing of a request.
4551
*/

core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoint/DefaultServiceEndpointBuilder.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@
1717

1818
import java.net.URI;
1919
import java.net.URISyntaxException;
20+
import java.util.ArrayList;
2021
import java.util.List;
2122
import software.amazon.awssdk.annotations.NotThreadSafe;
2223
import software.amazon.awssdk.annotations.SdkProtectedApi;
2324
import software.amazon.awssdk.core.exception.SdkClientException;
2425
import software.amazon.awssdk.profiles.ProfileFile;
26+
import software.amazon.awssdk.profiles.ProfileFileSystemSetting;
27+
import software.amazon.awssdk.regions.EndpointTag;
2528
import software.amazon.awssdk.regions.Region;
29+
import software.amazon.awssdk.regions.ServiceEndpointKey;
2630
import software.amazon.awssdk.regions.ServiceMetadata;
2731
import software.amazon.awssdk.utils.Validate;
2832

@@ -40,6 +44,7 @@ public final class DefaultServiceEndpointBuilder {
4044
private Region region;
4145
private ProfileFile profileFile;
4246
private String profileName;
47+
private Boolean dualstackEnabled;
4348

4449
public DefaultServiceEndpointBuilder(String serviceName, String protocol) {
4550
this.serviceName = Validate.paramNotNull(serviceName, "serviceName");
@@ -64,18 +69,49 @@ public DefaultServiceEndpointBuilder withProfileName(String profileName) {
6469
return this;
6570
}
6671

72+
public DefaultServiceEndpointBuilder withDualstackEnabled(Boolean dualstackEnabled) {
73+
this.dualstackEnabled = dualstackEnabled;
74+
return this;
75+
}
76+
6777
public URI getServiceEndpoint() {
78+
if (profileFile == null) {
79+
profileFile = ProfileFile.defaultProfileFile();
80+
}
81+
82+
if (profileName == null) {
83+
profileName = ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow();
84+
}
85+
86+
if (dualstackEnabled == null) {
87+
dualstackEnabled = DualstackEnabledProvider.builder()
88+
.profileFile(() -> profileFile)
89+
.profileName(profileName)
90+
.build()
91+
.isDualstackEnabled()
92+
.orElse(false);
93+
}
94+
95+
List<EndpointTag> endpointTags = new ArrayList<>();
96+
if (dualstackEnabled) {
97+
endpointTags.add(EndpointTag.DUALSTACK);
98+
}
99+
68100
ServiceMetadata serviceMetadata = ServiceMetadata.of(serviceName)
69101
.reconfigure(c -> c.profileFile(() -> profileFile)
70102
.profileName(profileName));
71-
URI endpoint = addProtocolToServiceEndpoint(serviceMetadata.endpointFor(region));
103+
URI endpoint = addProtocolToServiceEndpoint(serviceMetadata.endpointFor(ServiceEndpointKey.builder()
104+
.region(region)
105+
.tags(endpointTags)
106+
.build()));
72107

73108
if (endpoint.getHost() == null) {
74-
String error = "Configured region (" + region + ") resulted in an invalid URI: " + endpoint;
109+
String error = "Configured region (" + region + ") and tags (" + endpointTags + ") resulted in an invalid URI: "
110+
+ endpoint + ". This is usually caused by an invalid region configuration.";
75111

76112
List<Region> exampleRegions = serviceMetadata.regions();
77113
if (!exampleRegions.isEmpty()) {
78-
error += " Valid region examples: " + exampleRegions;
114+
error += " Valid regions: " + exampleRegions;
79115
}
80116

81117
throw SdkClientException.create(error);

0 commit comments

Comments
 (0)