diff --git a/.changes/next-release/feature-AWSSKDforJavav2-bdf010f.json b/.changes/next-release/feature-AWSSKDforJavav2-bdf010f.json
new file mode 100644
index 000000000000..467081baa914
--- /dev/null
+++ b/.changes/next-release/feature-AWSSKDforJavav2-bdf010f.json
@@ -0,0 +1,6 @@
+{
+ "type": "feature",
+ "category": "AWS SDK for Java v2",
+ "contributor": "",
+ "description": "Added functionality to be able to configure an endpoint override through the [services] section in the aws config file for specific services. \nhttps://docs.aws.amazon.com/sdkref/latest/guide/feature-ss-endpoints.html"
+}
diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoint/AwsClientEndpointProvider.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoint/AwsClientEndpointProvider.java
index 2257666f3324..fbee6e0ec47f 100644
--- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoint/AwsClientEndpointProvider.java
+++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoint/AwsClientEndpointProvider.java
@@ -50,6 +50,7 @@
*
The service-agnostic endpoint override system property (i.e. 'aws.endpointUrl')
* The service-specific endpoint override environment variable (e.g. 'AWS_ENDPOINT_URL_S3')
* The service-agnostic endpoint override environment variable (i.e. 'AWS_ENDPOINT_URL')
+ * The service-specific endpoint override from services section (e.g. '[services dev] s3.endpoint_url')
* The service-specific endpoint override profile property (e.g. 's3.endpoint_url')
* The service-agnostic endpoint override profile property (i.e. 'endpoint_url')
* The {@link ServiceMetadata} for the service
@@ -129,6 +130,15 @@ private Optional clientEndpointFromEnvironment(Builder builder)
() -> systemProperty(GLOBAL_ENDPOINT_OVERRIDE_SYSTEM_PROPERTY),
() -> environmentVariable(builder.serviceEndpointOverrideEnvironmentVariable),
() -> environmentVariable(GLOBAL_ENDPOINT_OVERRIDE_ENVIRONMENT_VARIABLE),
+ () -> servicesProperty(builder),
+
+ /*
+ * This is a deviation from the cross-SDK standard.
+ * There should not have been support for service-specific
+ * endpoint override under the [profile] section.
+ * It is in this order to maintain backwards compatibility, and to reflect that
+ * service-specific endpoint overrides from the [services] section should be preferred.
+ */
() -> profileProperty(builder,
builder.serviceProfileProperty + "."
+ ProfileProperty.ENDPOINT_URL),
@@ -156,6 +166,20 @@ private Optional profileProperty(Builder builder, String profileProperty) {
.flatMap(p -> p.property(profileProperty)));
}
+ private Optional servicesProperty(Builder builder) {
+ Optional profileFile = Optional.ofNullable(builder.profileFile.get());
+ Optional servicesSectionName = profileFile
+ .flatMap(pf -> pf.profile(builder.profileName))
+ .flatMap(p -> p.property("services"));
+
+ Optional serviceEndpoint = servicesSectionName
+ .flatMap(name -> profileFile.flatMap(pf -> pf.getSection("services", name)))
+ .flatMap(p -> p.property(builder.serviceProfileProperty
+ + "." + ProfileProperty.ENDPOINT_URL));
+
+ return createUri("services section property", serviceEndpoint);
+ }
+
private Optional clientEndpointFromServiceMetadata(Builder builder) {
// This value is generally overridden after endpoints 2.0. It seems to exist for backwards-compatibility
// with older client versions or interceptors.
diff --git a/core/profiles/src/main/java/software/amazon/awssdk/profiles/internal/ProfileSection.java b/core/profiles/src/main/java/software/amazon/awssdk/profiles/internal/ProfileSection.java
index cfa47e02e75e..24cbe1d9fa81 100644
--- a/core/profiles/src/main/java/software/amazon/awssdk/profiles/internal/ProfileSection.java
+++ b/core/profiles/src/main/java/software/amazon/awssdk/profiles/internal/ProfileSection.java
@@ -29,7 +29,13 @@ public enum ProfileSection {
* are part of a named collection of attributes.
* This `sso-session` section is referenced by the user when configuring a profile to derive an SSO token.
*/
- SSO_SESSION("sso-session", "sso_session");
+ SSO_SESSION("sso-session", "sso_session"),
+
+ /**
+ * A `services` section declares a group of service-specific configurations that can be referenced by profiles.
+ * Service sub-sections use standardized names derived from the AWS service identifiers.
+ */
+ SERVICES("services", "services");
private final String sectionTitle;
private final String propertyKeyName;
diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/EndpointSharedConfigTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/EndpointSharedConfigTest.java
index 546810cf1c50..78662bb0dc59 100644
--- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/EndpointSharedConfigTest.java
+++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/EndpointSharedConfigTest.java
@@ -76,6 +76,13 @@ public void resolvesCorrectEndpoint() {
.append(" endpoint_url = ").append(testCase.serviceProfileSetting).append("\n");
}
+ if (testCase.serviceSectionProfileSetting != null) {
+ profileFileContent.append("services = dev\n\n");
+ profileFileContent.append("[services dev] \n")
+ .append("amazonprotocolrestjson =\n")
+ .append(" endpoint_url = ").append(testCase.serviceSectionProfileSetting).append("\n");
+ }
+
ProfileFile profileFile =
ProfileFile.builder()
.type(ProfileFile.Type.CONFIGURATION)
@@ -119,6 +126,7 @@ public static Iterable testCases() {
"Global system property",
"Service environment variable",
"Global environment variable",
+ "Services Section profile file",
"Service profile file",
"Global profile file");
@@ -209,6 +217,7 @@ public static class TestCase {
"https://global-system-property-endpoint.com",
"https://service-env-var-endpoint.com",
"https://global-env-var-endpoint.com",
+ "https://service-section-endpoint.com",
"https://service-profile-endpoint.com",
"https://global-profile-endpoint.com");
@@ -219,12 +228,13 @@ public static class TestCase {
private final String globalEnvVarSetting;
private final String serviceProfileSetting;
private final String globalProfileSetting;
+ private final String serviceSectionProfileSetting;
private final String caseName;
private final String expectedEndpoint;
public TestCase(boolean[] settings, Integer expectedEndpointIndex, String caseName) {
this(endpoint(settings, 0), endpoint(settings, 1), endpoint(settings, 2), endpoint(settings, 3),
- endpoint(settings, 4), endpoint(settings, 5), endpoint(settings, 6),
+ endpoint(settings, 4), endpoint(settings, 5), endpoint(settings, 6), endpoint(settings, 7),
endpointForIndex(expectedEndpointIndex), caseName);
}
@@ -244,6 +254,7 @@ private TestCase(String clientSetting,
String globalSystemPropSetting,
String serviceEnvVarSetting,
String globalEnvVarSetting,
+ String serviceSectionProfileSetting,
String serviceProfileSetting,
String globalProfileSetting,
String expectedEndpoint,
@@ -255,6 +266,7 @@ private TestCase(String clientSetting,
this.globalEnvVarSetting = globalEnvVarSetting;
this.serviceProfileSetting = serviceProfileSetting;
this.globalProfileSetting = globalProfileSetting;
+ this.serviceSectionProfileSetting = serviceSectionProfileSetting;
this.expectedEndpoint = expectedEndpoint;
this.caseName = caseName;
}
diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/ProfileFileServicesTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/ProfileFileServicesTest.java
new file mode 100644
index 000000000000..ae8314a8b0d4
--- /dev/null
+++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/ProfileFileServicesTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.services;
+
+import java.util.Optional;
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
+import software.amazon.awssdk.awscore.endpoint.AwsClientEndpointProvider;
+import software.amazon.awssdk.profiles.Profile;
+import software.amazon.awssdk.profiles.ProfileFile;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClient;
+import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClientBuilder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ProfileFileServicesTest {
+
+ @Test
+ public void servicesSection_shouldRecognizeServicesSection() {
+ String profileContent =
+ "[services dev]\n" +
+ "s3 = \n" +
+ " endpoint_url = https://foo.bar:9000\n";
+
+ ProfileFile profileFile = ProfileFile.builder()
+ .content(profileContent)
+ .type(ProfileFile.Type.CONFIGURATION)
+ .build();
+
+ Optional servicesSection = profileFile.getSection("services", "dev");
+ assertThat(servicesSection).isPresent();
+ }
+
+ @Test
+ public void servicesSection_canParseServicesSectionProperties() {
+ String profileContent =
+ "[services dev]\n" +
+ "s3 = \n" +
+ " endpoint_url = https://foo.bar:9000\n" +
+ " foo = bar\n" +
+ "\n" +
+ "[profile test-profile]\n" +
+ "services = dev";
+
+ ProfileFile profileFile = ProfileFile.builder()
+ .content(profileContent)
+ .type(ProfileFile.Type.CONFIGURATION)
+ .build();
+
+ Optional servicesSection = profileFile.getSection("services", "dev");
+ assertThat(servicesSection).isPresent();
+
+ Profile services = servicesSection.get();
+ assertThat(services.properties())
+ .containsEntry("s3.endpoint_url", "https://foo.bar:9000");
+ }
+
+
+ @Test
+ public void servicesSection_canParseMultipleServicesInSection() {
+ String profileContent =
+ "[services testing-s3-and-eb]\n" +
+ "s3 = \n" +
+ " endpoint_url = http://localhost:4567\n" +
+ "elastic_beanstalk = \n" +
+ " endpoint_url = http://localhost:8000\n" +
+ "\n" +
+ "[profile dev]\n" +
+ "services = testing-s3-and-eb";
+
+ ProfileFile profileFile = ProfileFile.builder()
+ .content(profileContent)
+ .type(ProfileFile.Type.CONFIGURATION)
+ .build();
+
+ Optional servicesSection = profileFile.getSection("services", "testing-s3-and-eb");
+ assertThat(servicesSection).isPresent();
+
+ Profile services = servicesSection.get();
+ assertThat(services.properties())
+ .containsEntry("s3.endpoint_url", "http://localhost:4567")
+ .containsEntry("elastic_beanstalk.endpoint_url", "http://localhost:8000");
+ }
+
+ @org.junit.Test(expected = EndpointCapturingInterceptor.CaptureCompletedException.class)
+ public void invalidNestedBlockFormat_shouldThrowCaptureCompletedException() {
+ StringBuilder profileFileContent = new StringBuilder();
+ profileFileContent.append("[default] \n")
+ .append("services = dev \n")
+ .append("\n")
+ .append("[services dev] \n")
+ .append("amazonprotocolrestjson =\n")
+ .append("endpoint_url =");
+
+ ProfileFile profileFile = ProfileFile.builder()
+ .type(ProfileFile.Type.CONFIGURATION)
+ .content(profileFileContent.toString())
+ .build();
+
+ ProtocolRestJsonClientBuilder builder = ProtocolRestJsonClient.builder()
+ .region(Region.US_WEST_2)
+ .credentialsProvider(AnonymousCredentialsProvider.create())
+ .overrideConfiguration(c -> c.defaultProfileFile(profileFile)
+ .defaultProfileName("default"));
+
+ EndpointCapturingInterceptor interceptor = new EndpointCapturingInterceptor();
+ builder.overrideConfiguration(b -> b.addExecutionInterceptor(interceptor));
+
+ ProtocolRestJsonClient client = builder.build();
+
+ client.allTypes();
+ }
+}
\ No newline at end of file