Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/next-release/feature-AWSSKDforJavav2-bdf010f.json
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
* <li>The service-agnostic endpoint override system property (i.e. 'aws.endpointUrl')</li>
* <li>The service-specific endpoint override environment variable (e.g. 'AWS_ENDPOINT_URL_S3')</li>
* <li>The service-agnostic endpoint override environment variable (i.e. 'AWS_ENDPOINT_URL')</li>
* <li>The service-specific endpoint override from services section (e.g. '[services dev] s3.endpoint_url')</li>
* <li>The service-specific endpoint override profile property (e.g. 's3.endpoint_url')</li>
* <li>The service-agnostic endpoint override profile property (i.e. 'endpoint_url')</li>
* <li>The {@link ServiceMetadata} for the service</li>
Expand Down Expand Up @@ -129,6 +130,15 @@ private Optional<ClientEndpoint> 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),
Expand Down Expand Up @@ -156,6 +166,20 @@ private Optional<URI> profileProperty(Builder builder, String profileProperty) {
.flatMap(p -> p.property(profileProperty)));
}

private Optional<URI> servicesProperty(Builder builder) {
Optional<ProfileFile> profileFile = Optional.ofNullable(builder.profileFile.get());
Optional<String> servicesSectionName = profileFile
.flatMap(pf -> pf.profile(builder.profileName))
.flatMap(p -> p.property("services"));

Optional<String> 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<ClientEndpoint> 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -119,6 +126,7 @@ public static Iterable<TestCase> testCases() {
"Global system property",
"Service environment variable",
"Global environment variable",
"Services Section profile file",
"Service profile file",
"Global profile file");

Expand Down Expand Up @@ -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");

Expand All @@ -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);
}

Expand All @@ -244,6 +254,7 @@ private TestCase(String clientSetting,
String globalSystemPropSetting,
String serviceEnvVarSetting,
String globalEnvVarSetting,
String serviceSectionProfileSetting,
String serviceProfileSetting,
String globalProfileSetting,
String expectedEndpoint,
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Profile> 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<Profile> 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<Profile> 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();
}
}
Loading