diff --git a/.changes/next-release/feature-AWSSDKforJavav2-ea2197e.json b/.changes/next-release/feature-AWSSDKforJavav2-ea2197e.json new file mode 100644 index 000000000000..01cb2d56932f --- /dev/null +++ b/.changes/next-release/feature-AWSSDKforJavav2-ea2197e.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "Added support for the AWS_IGNORE_CONFIGURED_ENDPOINT_URLS setting to skip endpoint URLs from environment variables and config files." +} 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 0bb5119b369e..cf740ac3f102 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 @@ -115,6 +115,12 @@ private Optional clientEndpointFromClientOverride(Builder builde } private Optional clientEndpointFromEnvironment(Builder builder) { + initializeProfileFileDefaults(builder); + if (shouldIgnoreConfiguredEndpointUrls(builder)) { + log.debug(() -> "Configured endpoint URLs are being ignored because ignore_configured_endpoint_urls is true."); + return Optional.empty(); + } + if (builder.serviceEndpointOverrideEnvironmentVariable == null || builder.serviceEndpointOverrideSystemProperty == null || builder.serviceProfileProperty == null) { @@ -181,6 +187,15 @@ private Optional servicesProperty(Builder builder) { return createUri("services section property", serviceEndpoint); } + private boolean shouldIgnoreConfiguredEndpointUrls(Builder builder) { + return IgnoreConfiguredEndpointUrlsProvider.builder() + .profileFile(builder.profileFile) + .profileName(builder.profileName) + .build() + .ignoreConfiguredEndpointUrls() + .orElse(false); + } + 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. @@ -479,6 +494,7 @@ public Builder putAdvancedOption(ServiceMetadataAdvancedOption option, T return this; } + public AwsClientEndpointProvider build() { return new AwsClientEndpointProvider(this); } diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoint/IgnoreConfiguredEndpointUrlsProvider.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoint/IgnoreConfiguredEndpointUrlsProvider.java new file mode 100644 index 000000000000..c980094e5772 --- /dev/null +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoint/IgnoreConfiguredEndpointUrlsProvider.java @@ -0,0 +1,89 @@ +/* + * 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.awscore.endpoint; + +import java.util.Optional; +import java.util.function.Supplier; +import software.amazon.awssdk.annotations.SdkProtectedApi; +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.profiles.ProfileFileSystemSetting; +import software.amazon.awssdk.profiles.ProfileProperty; +import software.amazon.awssdk.utils.Validate; + +/** + * Resolves whether configured endpoint URLs should be ignored. This checks the system property, environment variable, + * and profile file for the {@code ignore_configured_endpoint_urls} setting. + * + *

When this returns {@code true}, the SDK will not read endpoint URLs from environment variables, system properties, + * or the shared configuration file. Programmatic endpoint overrides on the client builder are not affected. + */ +@SdkProtectedApi +public class IgnoreConfiguredEndpointUrlsProvider { + private final Supplier profileFile; + private final String profileName; + + private IgnoreConfiguredEndpointUrlsProvider(Builder builder) { + this.profileFile = Validate.paramNotNull(builder.profileFile, "profileFile"); + this.profileName = builder.profileName; + } + + public static Builder builder() { + return new Builder(); + } + + /** + * Returns {@code true} when configured endpoint URLs should be ignored, {@code false} otherwise. + * Resolution order: system property, then environment variable, then profile file. If none are set, returns + * empty. + */ + public Optional ignoreConfiguredEndpointUrls() { + Optional setting = SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS.getBooleanValue(); + if (setting.isPresent()) { + return setting; + } + + return profileFile.get() + .profile(profileName()) + .flatMap(p -> p.booleanProperty(ProfileProperty.IGNORE_CONFIGURED_ENDPOINT_URLS)); + } + + private String profileName() { + return profileName != null ? profileName : ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow(); + } + + public static final class Builder { + private Supplier profileFile = ProfileFile::defaultProfileFile; + private String profileName; + + private Builder() { + } + + public Builder profileFile(Supplier profileFile) { + this.profileFile = profileFile; + return this; + } + + public Builder profileName(String profileName) { + this.profileName = profileName; + return this; + } + + public IgnoreConfiguredEndpointUrlsProvider build() { + return new IgnoreConfiguredEndpointUrlsProvider(this); + } + } +} diff --git a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/endpoint/IgnoreConfiguredEndpointUrlsProviderTest.java b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/endpoint/IgnoreConfiguredEndpointUrlsProviderTest.java new file mode 100644 index 000000000000..0bc6fdd316a5 --- /dev/null +++ b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/endpoint/IgnoreConfiguredEndpointUrlsProviderTest.java @@ -0,0 +1,102 @@ +/* + * 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.awscore.endpoint; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Optional; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.testutils.EnvironmentVariableHelper; +import software.amazon.awssdk.utils.StringInputStream; + +class IgnoreConfiguredEndpointUrlsProviderTest { + private static final EnvironmentVariableHelper ENVIRONMENT_VARIABLE_HELPER = new EnvironmentVariableHelper(); + private static final String PROFILE = "test"; + + @BeforeEach + void setup() { + ENVIRONMENT_VARIABLE_HELPER.reset(); + System.clearProperty(SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS.property()); + } + + @AfterEach + void teardown() { + ENVIRONMENT_VARIABLE_HELPER.reset(); + System.clearProperty(SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS.property()); + } + + @ParameterizedTest(name = "{index} - {0}") + @MethodSource("testCases") + void resolvesCorrectly(String description, String systemProperty, String envVar, String profileValue, + Optional expected) { + if (systemProperty != null) { + System.setProperty(SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS.property(), systemProperty); + } + if (envVar != null) { + ENVIRONMENT_VARIABLE_HELPER.set(SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS, envVar); + } + + ProfileFile profileFile = profileFile(profileValue); + + IgnoreConfiguredEndpointUrlsProvider provider = + IgnoreConfiguredEndpointUrlsProvider.builder() + .profileFile(() -> profileFile) + .profileName(PROFILE) + .build(); + + assertThat(provider.ignoreConfiguredEndpointUrls()).isEqualTo(expected); + } + + private static Stream testCases() { + return Stream.of( + Arguments.of("nothing set returns empty", null, null, null, Optional.empty()), + Arguments.of("system property true", "true", null, null, Optional.of(true)), + Arguments.of("system property false", "false", null, null, Optional.of(false)), + Arguments.of("system property case insensitive True", "True", null, null, Optional.of(true)), + Arguments.of("system property case insensitive TRUE", "TRUE", null, null, Optional.of(true)), + Arguments.of("env var true", null, "true", null, Optional.of(true)), + Arguments.of("env var false", null, "false", null, Optional.of(false)), + Arguments.of("profile true", null, null, "true", Optional.of(true)), + Arguments.of("profile false", null, null, "false", Optional.of(false)), + Arguments.of("system property wins over env var", "true", "false", null, Optional.of(true)), + Arguments.of("system property false wins over env var true", "false", "true", null, Optional.of(false)), + Arguments.of("system property wins over profile", "true", null, "false", Optional.of(true)), + Arguments.of("env var wins over profile", null, "true", "false", Optional.of(true)), + Arguments.of("env var false wins over profile true", null, "false", "true", Optional.of(false)), + Arguments.of("system property wins over both", "true", "false", "false", Optional.of(true)), + Arguments.of("system property false wins over both", "false", "true", "true", Optional.of(false)) + ); + } + + private static ProfileFile profileFile(String ignoreConfiguredEndpointUrlsValue) { + StringBuilder content = new StringBuilder(); + content.append("[profile test]\n"); + if (ignoreConfiguredEndpointUrlsValue != null) { + content.append("ignore_configured_endpoint_urls = ").append(ignoreConfiguredEndpointUrlsValue).append("\n"); + } + return ProfileFile.builder() + .type(ProfileFile.Type.CONFIGURATION) + .content(new StringInputStream(content.toString())) + .build(); + } +} diff --git a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileProperty.java b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileProperty.java index 069359c37567..b3243ba6c29b 100644 --- a/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileProperty.java +++ b/core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileProperty.java @@ -161,6 +161,8 @@ public final class ProfileProperty { public static final String USE_FIPS_ENDPOINT = "use_fips_endpoint"; + public static final String IGNORE_CONFIGURED_ENDPOINT_URLS = "ignore_configured_endpoint_urls"; + public static final String EC2_METADATA_SERVICE_ENDPOINT_MODE = "ec2_metadata_service_endpoint_mode"; public static final String EC2_METADATA_SERVICE_ENDPOINT = "ec2_metadata_service_endpoint"; diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkSystemSetting.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkSystemSetting.java index db0ee4d67f8f..e941ed2fd905 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkSystemSetting.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkSystemSetting.java @@ -218,6 +218,12 @@ public enum SdkSystemSetting implements SystemSetting { */ AWS_USE_FIPS_ENDPOINT("aws.useFipsEndpoint", null), + /** + * Defines whether endpoint URLs from environment variables, system properties, and the shared configuration file + * should be ignored. Endpoint URLs set programmatically via the client builder are not affected. + */ + AWS_IGNORE_CONFIGURED_ENDPOINT_URLS("aws.ignoreConfiguredEndpointUrls", null), + /** * Whether request compression is disabled for operations marked with the RequestCompression trait. The default value is * false, i.e., request compression is enabled. 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 78662bb0dc59..cc969843b877 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 @@ -12,6 +12,7 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; +import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClient; @@ -28,6 +29,8 @@ public class EndpointSharedConfigTest { private static final String GLOBAL_SYS_PROP = "aws.endpointUrl"; private static final String SERVICE_ENV_VAR = "AWS_ENDPOINT_URL_AMAZONPROTOCOLRESTJSON"; private static final String SERVICE_SYS_PROP = "aws.endpointUrlProtocolRestJson"; + private static final String IGNORE_ENDPOINT_URLS_SYS_PROP = + SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS.property(); @Parameterized.Parameter public TestCase testCase; @@ -37,6 +40,7 @@ public void resolvesCorrectEndpoint() { Map systemPropertiesBeforeTest = new HashMap<>(); systemPropertiesBeforeTest.put(GLOBAL_SYS_PROP, System.getProperty(GLOBAL_SYS_PROP)); systemPropertiesBeforeTest.put(SERVICE_SYS_PROP, System.getProperty(SERVICE_SYS_PROP)); + systemPropertiesBeforeTest.put(IGNORE_ENDPOINT_URLS_SYS_PROP, System.getProperty(IGNORE_ENDPOINT_URLS_SYS_PROP)); EnvironmentVariableHelper helper = new EnvironmentVariableHelper(); @@ -66,6 +70,10 @@ public void resolvesCorrectEndpoint() { System.setProperty(SERVICE_SYS_PROP, testCase.serviceSystemPropSetting); } + if (testCase.ignoreConfiguredEndpointUrls) { + System.setProperty(IGNORE_ENDPOINT_URLS_SYS_PROP, "true"); + } + StringBuilder profileFileContent = new StringBuilder(); profileFileContent.append("[default]\n"); if (testCase.globalProfileSetting != null) { @@ -128,7 +136,8 @@ public static Iterable testCases() { "Global environment variable", "Services Section profile file", "Service profile file", - "Global profile file"); + "Global profile file", + "Ignore configured endpoint URLs"); boolean[][] settingCombinations = getSettingCombinations(settingNames.size()); @@ -161,6 +170,9 @@ private static TestCase createCase(List settingNames, } } + boolean ignoreConfiguredEndpointUrls = settings[8]; + expectedEndpointIndex = applyIgnoreConfiguredEndpointUrls(expectedEndpointIndex, ignoreConfiguredEndpointUrls); + // Create case name String caseName; if (firstTrueSetting == null) { @@ -176,7 +188,17 @@ private static TestCase createCase(List settingNames, caseName += "."; } - return new TestCase(settings, expectedEndpointIndex, caseName); + return new TestCase(settings, expectedEndpointIndex, ignoreConfiguredEndpointUrls, caseName); + } + + /** + * When ignore_configured_endpoint_urls is true, all endpoint sources are suppressed except client override (index 0). + */ + private static Integer applyIgnoreConfiguredEndpointUrls(Integer expectedEndpointIndex, boolean ignore) { + if (!ignore || Integer.valueOf(0).equals(expectedEndpointIndex)) { + return expectedEndpointIndex; + } + return null; } public static void printArrayOfArrays(boolean[][] arrays) { @@ -229,13 +251,15 @@ public static class TestCase { private final String serviceProfileSetting; private final String globalProfileSetting; private final String serviceSectionProfileSetting; + private final boolean ignoreConfiguredEndpointUrls; private final String caseName; private final String expectedEndpoint; - public TestCase(boolean[] settings, Integer expectedEndpointIndex, String caseName) { + public TestCase(boolean[] settings, Integer expectedEndpointIndex, boolean ignoreConfiguredEndpointUrls, + 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, 7), - endpointForIndex(expectedEndpointIndex), caseName); + ignoreConfiguredEndpointUrls, endpointForIndex(expectedEndpointIndex), caseName); } private static String endpoint(boolean[] settings, int i) { @@ -257,6 +281,7 @@ private TestCase(String clientSetting, String serviceSectionProfileSetting, String serviceProfileSetting, String globalProfileSetting, + boolean ignoreConfiguredEndpointUrls, String expectedEndpoint, String caseName) { this.clientSetting = clientSetting; @@ -267,6 +292,7 @@ private TestCase(String clientSetting, this.serviceProfileSetting = serviceProfileSetting; this.globalProfileSetting = globalProfileSetting; this.serviceSectionProfileSetting = serviceSectionProfileSetting; + this.ignoreConfiguredEndpointUrls = ignoreConfiguredEndpointUrls; this.expectedEndpoint = expectedEndpoint; this.caseName = caseName; } diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/IgnoreConfiguredEndpointUrlsTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/IgnoreConfiguredEndpointUrlsTest.java new file mode 100644 index 000000000000..1ef0323fcd9b --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/IgnoreConfiguredEndpointUrlsTest.java @@ -0,0 +1,284 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; +import software.amazon.awssdk.core.SdkSystemSetting; +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 software.amazon.awssdk.testutils.EnvironmentVariableHelper; + +/** + * Tests that the {@code ignore_configured_endpoint_urls} setting correctly suppresses endpoint URL resolution from + * environment variables, system properties, and the shared configuration file, while preserving programmatic + * endpoint overrides set on the client builder. + */ +class IgnoreConfiguredEndpointUrlsTest { + private static final String SERVICE_ENV_VAR = "AWS_ENDPOINT_URL_AMAZONPROTOCOLRESTJSON"; + private static final String GLOBAL_ENV_VAR = "AWS_ENDPOINT_URL"; + private static final String SERVICE_SYS_PROP = "aws.endpointUrlProtocolRestJson"; + private static final String GLOBAL_SYS_PROP = "aws.endpointUrl"; + private static final String DEFAULT_ENDPOINT = "https://customresponsemetadata.us-west-2.amazonaws.com"; + private static final String ENV_ENDPOINT = "https://env-endpoint.example.com"; + private static final String PROFILE_ENDPOINT = "https://profile-endpoint.example.com"; + private static final String CLIENT_OVERRIDE_ENDPOINT = "https://client-override.example.com"; + + private final EnvironmentVariableHelper helper = new EnvironmentVariableHelper(); + + @BeforeEach + void setup() { + helper.reset(); + System.clearProperty(GLOBAL_SYS_PROP); + System.clearProperty(SERVICE_SYS_PROP); + System.clearProperty(SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS.property()); + } + + @AfterEach + void teardown() { + helper.reset(); + System.clearProperty(GLOBAL_SYS_PROP); + System.clearProperty(SERVICE_SYS_PROP); + System.clearProperty(SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS.property()); + } + + @Test + void defaultBehavior_endpointFromEnvVarIsUsed() { + helper.set(SERVICE_ENV_VAR, ENV_ENDPOINT); + + String resolved = resolveEndpoint(null, null); + + assertThat(resolved).startsWith(ENV_ENDPOINT); + } + + @Test + void ignoreViaSystemProperty_endpointFromEnvVarIsIgnored() { + helper.set(SERVICE_ENV_VAR, ENV_ENDPOINT); + System.setProperty(SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS.property(), "true"); + + String resolved = resolveEndpoint(null, null); + + assertThat(resolved).startsWith(DEFAULT_ENDPOINT); + } + + @Test + void ignoreViaEnvVar_endpointFromEnvVarIsIgnored() { + helper.set(SERVICE_ENV_VAR, ENV_ENDPOINT); + helper.set(SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS, "true"); + + String resolved = resolveEndpoint(null, null); + + assertThat(resolved).startsWith(DEFAULT_ENDPOINT); + } + + @Test + void ignoreViaProfile_endpointFromEnvVarIsIgnored() { + helper.set(SERVICE_ENV_VAR, ENV_ENDPOINT); + + ProfileFile profileFile = profileWithIgnore("true", null); + String resolved = resolveEndpoint(null, profileFile); + + assertThat(resolved).startsWith(DEFAULT_ENDPOINT); + } + + @Test + void ignoreViaProfile_profileEndpointInSameProfileIsAlsoIgnored() { + ProfileFile profileFile = profileWithIgnore("true", PROFILE_ENDPOINT); + String resolved = resolveEndpoint(null, profileFile); + + assertThat(resolved).startsWith(DEFAULT_ENDPOINT); + } + + @Test + void ignoreTrue_globalEnvVarIsIgnored() { + helper.set(GLOBAL_ENV_VAR, ENV_ENDPOINT); + System.setProperty(SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS.property(), "true"); + + String resolved = resolveEndpoint(null, null); + + assertThat(resolved).startsWith(DEFAULT_ENDPOINT); + } + + @Test + void ignoreTrue_serviceSysPropIsIgnored() { + System.setProperty(SERVICE_SYS_PROP, ENV_ENDPOINT); + System.setProperty(SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS.property(), "true"); + + String resolved = resolveEndpoint(null, null); + + assertThat(resolved).startsWith(DEFAULT_ENDPOINT); + } + + @Test + void ignoreTrue_globalSysPropIsIgnored() { + System.setProperty(GLOBAL_SYS_PROP, ENV_ENDPOINT); + System.setProperty(SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS.property(), "true"); + + String resolved = resolveEndpoint(null, null); + + assertThat(resolved).startsWith(DEFAULT_ENDPOINT); + } + + @Test + void ignoreTrue_profileEndpointIsIgnored() { + System.setProperty(SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS.property(), "true"); + + ProfileFile profileFile = profileWithEndpoint(PROFILE_ENDPOINT); + String resolved = resolveEndpoint(null, profileFile); + + assertThat(resolved).startsWith(DEFAULT_ENDPOINT); + } + + @Test + void ignoreTrue_servicesSectionEndpointIsIgnored() { + System.setProperty(SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS.property(), "true"); + + ProfileFile profileFile = profileWithServicesSection(PROFILE_ENDPOINT); + String resolved = resolveEndpoint(null, profileFile); + + assertThat(resolved).startsWith(DEFAULT_ENDPOINT); + } + + @Test + void ignoreTrue_programmaticEndpointOverrideStillWorks() { + helper.set(SERVICE_ENV_VAR, ENV_ENDPOINT); + System.setProperty(SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS.property(), "true"); + + String resolved = resolveEndpoint(URI.create(CLIENT_OVERRIDE_ENDPOINT), null); + + assertThat(resolved).startsWith(CLIENT_OVERRIDE_ENDPOINT); + } + + @Test + void ignoreTrue_programmaticEndpointOverrideWorksEvenWithAllSourcesConfigured() { + helper.set(SERVICE_ENV_VAR, ENV_ENDPOINT); + helper.set(GLOBAL_ENV_VAR, ENV_ENDPOINT); + System.setProperty(SERVICE_SYS_PROP, ENV_ENDPOINT); + System.setProperty(GLOBAL_SYS_PROP, ENV_ENDPOINT); + System.setProperty(SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS.property(), "true"); + + ProfileFile profileFile = profileWithEndpoint(PROFILE_ENDPOINT); + String resolved = resolveEndpoint(URI.create(CLIENT_OVERRIDE_ENDPOINT), profileFile); + + assertThat(resolved).startsWith(CLIENT_OVERRIDE_ENDPOINT); + } + + @Test + void ignoreFalse_endpointFromEnvVarIsUsed() { + helper.set(SERVICE_ENV_VAR, ENV_ENDPOINT); + System.setProperty(SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS.property(), "false"); + + String resolved = resolveEndpoint(null, null); + + assertThat(resolved).startsWith(ENV_ENDPOINT); + } + + @Test + void ignoreTrue_isEndpointOverriddenReturnsFalseWhenFallingToDefault() { + System.setProperty(SERVICE_SYS_PROP, ENV_ENDPOINT); + System.setProperty(SdkSystemSetting.AWS_IGNORE_CONFIGURED_ENDPOINT_URLS.property(), "true"); + + EndpointCapturingInterceptor interceptor = new EndpointCapturingInterceptor(); + ProtocolRestJsonClient client = baseBuilder(null, null) + .overrideConfiguration(c -> c.addExecutionInterceptor(interceptor)) + .build(); + + try { + client.allTypes(); + } catch (EndpointCapturingInterceptor.CaptureCompletedException e) { + // expected + } + + assertThat(interceptor.endpoints()).singleElement().asString().startsWith(DEFAULT_ENDPOINT); + } + + private String resolveEndpoint(URI endpointOverride, ProfileFile profileFile) { + EndpointCapturingInterceptor interceptor = new EndpointCapturingInterceptor(); + + ProtocolRestJsonClientBuilder builder = baseBuilder(endpointOverride, profileFile); + builder.overrideConfiguration(c -> { + if (profileFile != null) { + c.defaultProfileFile(profileFile).defaultProfileName("default"); + } + c.addExecutionInterceptor(interceptor); + }); + + ProtocolRestJsonClient client = builder.build(); + + try { + client.allTypes(); + } catch (EndpointCapturingInterceptor.CaptureCompletedException e) { + // expected + } + + assertThat(interceptor.endpoints()).hasSize(1); + return interceptor.endpoints().get(0); + } + + private ProtocolRestJsonClientBuilder baseBuilder(URI endpointOverride, ProfileFile profileFile) { + ProtocolRestJsonClientBuilder builder = + ProtocolRestJsonClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(AnonymousCredentialsProvider.create()); + + if (endpointOverride != null) { + builder.endpointOverride(endpointOverride); + } + + return builder; + } + + private static ProfileFile profileWithIgnore(String ignoreValue, String endpointUrl) { + StringBuilder content = new StringBuilder(); + content.append("[default]\n"); + content.append("ignore_configured_endpoint_urls = ").append(ignoreValue).append("\n"); + if (endpointUrl != null) { + content.append("endpoint_url = ").append(endpointUrl).append("\n"); + } + return ProfileFile.builder() + .type(ProfileFile.Type.CONFIGURATION) + .content(content.toString()) + .build(); + } + + private static ProfileFile profileWithEndpoint(String endpointUrl) { + String content = "[default]\n" + + "endpoint_url = " + endpointUrl + "\n"; + return ProfileFile.builder() + .type(ProfileFile.Type.CONFIGURATION) + .content(content) + .build(); + } + + private static ProfileFile profileWithServicesSection(String endpointUrl) { + String content = "[default]\n" + + "services = dev\n\n" + + "[services dev]\n" + + "amazonprotocolrestjson =\n" + + " endpoint_url = " + endpointUrl + "\n"; + return ProfileFile.builder() + .type(ProfileFile.Type.CONFIGURATION) + .content(content) + .build(); + } +}