From a3a5cf34111889148a81b17745f22ffff284a5cf Mon Sep 17 00:00:00 2001 From: Saranya Somepalli Date: Mon, 6 Jul 2026 15:28:44 -0700 Subject: [PATCH 1/4] Remove ServiceMetadata usage from AwsClientEndpointProvider in generated client builders (#7094) * Remove ServiceMetadata usage from AwsClientEndpointProvider in generated client builders * Adding tests * Additional changes * Address PR comments --- .../poet/builder/BaseClientBuilderClass.java | 74 +++++++++--- ...test-bearer-auth-client-builder-class.java | 50 +++++--- .../builder/test-client-builder-class.java | 50 +++++--- ...-client-builder-endpoints-auth-params.java | 52 +++++--- ...lient-builder-internal-defaults-class.java | 50 +++++--- ...-composed-sync-default-client-builder.java | 50 +++++--- ...env-bearer-token-client-builder-class.java | 49 +++++--- .../test-h2-service-client-builder-class.java | 50 +++++--- ...dgeForH2-service-client-builder-class.java | 50 +++++--- ...ulti-auth-sigv4a-client-builder-class.java | 50 +++++--- ...test-no-auth-ops-client-builder-class.java | 50 +++++--- ...-no-auth-service-client-builder-class.java | 50 +++++--- .../test-query-client-builder-class.java | 52 +++++--- .../endpoint/AwsClientEndpointProvider.java | 35 ++++-- ...pointProviderResolveFromOverridesTest.java | 79 +++++++++++++ .../ClientEndpointResolutionTest.java | 111 ++++++++++++++++++ 16 files changed, 696 insertions(+), 206 deletions(-) create mode 100644 core/aws-core/src/test/java/software/amazon/awssdk/awscore/endpoint/AwsClientEndpointProviderResolveFromOverridesTest.java create mode 100644 test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/ClientEndpointResolutionTest.java diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java index d3c37f92a367..50a0c5aaa484 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java @@ -30,6 +30,7 @@ import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; import com.squareup.javapoet.WildcardTypeName; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -49,6 +50,7 @@ import software.amazon.awssdk.codegen.internal.Utils; import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; import software.amazon.awssdk.codegen.model.intermediate.OperationModel; +import software.amazon.awssdk.codegen.model.rules.endpoints.BuiltInParameter; import software.amazon.awssdk.codegen.model.service.ClientContextParam; import software.amazon.awssdk.codegen.poet.ClassSpec; import software.amazon.awssdk.codegen.poet.PoetExtension; @@ -86,7 +88,6 @@ import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.protocols.json.internal.unmarshall.SdkClientJsonProtocolAdvancedOption; -import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; import software.amazon.awssdk.utils.AttributeMap; import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.StringUtils; @@ -514,21 +515,57 @@ private MethodSpec finalizeServiceConfigurationMethod() { String serviceNameForSystemProperty = model.getNamingStrategy().getServiceNameForSystemProperties(); String serviceNameForProfileFile = model.getNamingStrategy().getServiceNameForProfileFile(); - builder.addCode("builder.lazyOptionIfAbsent($T.CLIENT_ENDPOINT_PROVIDER, c ->", SdkClientOption.class) - .addCode(" $T.builder()", AwsClientEndpointProvider.class) - .addCode(" .serviceEndpointOverrideEnvironmentVariable($S)", "AWS_ENDPOINT_URL_" + serviceNameForEnvVar) - .addCode(" .serviceEndpointOverrideSystemProperty($S)", "aws.endpointUrl" + serviceNameForSystemProperty) - .addCode(" .serviceProfileProperty($S)", serviceNameForProfileFile) - .addCode(" .serviceEndpointPrefix(serviceEndpointPrefix())") - .addCode(" .defaultProtocol($S)", "https") - .addCode(" .region(c.get($T.AWS_REGION))", AwsClientOption.class) - .addCode(" .profileFile(c.get($T.PROFILE_FILE_SUPPLIER))", SdkClientOption.class) - .addCode(" .profileName(c.get($T.PROFILE_NAME))", SdkClientOption.class) - .addCode(" .putAdvancedOption($T.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT,", ServiceMetadataAdvancedOption.class) - .addCode(" c.get($T.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT))", ServiceMetadataAdvancedOption.class) - .addCode(" .dualstackEnabled(c.get($T.DUALSTACK_ENDPOINT_ENABLED))", AwsClientOption.class) - .addCode(" .fipsEnabled(c.get($T.FIPS_ENDPOINT_ENABLED))", AwsClientOption.class) - .addCode(" .build());"); + builder.addCode("builder.lazyOptionIfAbsent($T.CLIENT_ENDPOINT_PROVIDER, c -> {\n", SdkClientOption.class) + .addCode(" $T<$T> overrideEndpoint = $T.builder()\n", + Optional.class, URI.class, + AwsClientEndpointProvider.class) + .addCode(" .serviceEndpointOverrideEnvironmentVariable($S)\n", "AWS_ENDPOINT_URL_" + serviceNameForEnvVar) + .addCode(" .serviceEndpointOverrideSystemProperty($S)\n", "aws.endpointUrl" + serviceNameForSystemProperty) + .addCode(" .serviceProfileProperty($S)\n", serviceNameForProfileFile) + .addCode(" .profileFile(c.get($T.PROFILE_FILE_SUPPLIER))\n", SdkClientOption.class) + .addCode(" .profileName(c.get($T.PROFILE_NAME))\n", SdkClientOption.class) + .addCode(" .resolveFromOverrides();\n") + .addCode(" if (overrideEndpoint.isPresent()) {\n") + .addCode(" return $T.create(overrideEndpoint.get(), true);\n", + ClassName.get("software.amazon.awssdk.core", "ClientEndpointProvider")) + .addCode(" }\n") + .addCode(" $T clientEndpointUri = null;\n", URI.class) + .addCode(" $T region = c.get($T.AWS_REGION);\n", + ClassName.get("software.amazon.awssdk.regions", "Region"), + AwsClientOption.class) + .addCode(" try {\n") + .addCode(" $T endpointParams = $T.builder()\n", + endpointRulesSpecUtils.parametersClassName(), endpointRulesSpecUtils.parametersClassName()) + .addCode(" .region(region)\n"); + + if (hasBuiltIn(BuiltInParameter.AWS_USE_DUAL_STACK)) { + builder.addCode(" .useDualStack(c.get($T.DUALSTACK_ENDPOINT_ENABLED))\n", AwsClientOption.class); + } + if (hasBuiltIn(BuiltInParameter.AWS_USE_FIPS)) { + builder.addCode(" .useFips(c.get($T.FIPS_ENDPOINT_ENABLED))\n", AwsClientOption.class); + } + + builder.addCode(" .build();\n") + .addCode(" $T endpoint = $T.joinLikeSync(defaultEndpointProvider().resolveEndpoint(endpointParams));\n", + ClassName.get("software.amazon.awssdk.endpoints", "Endpoint"), + ClassName.get("software.amazon.awssdk.utils", "CompletableFutureUtils")) + .addCode(" clientEndpointUri = endpoint.url();\n") + .addCode(" } catch (Exception e) {\n") + .addCode(" // Endpoint resolution failed. This is expected for services with required parameters\n") + .addCode(" // beyond region, dualstack, and FIPS. Use a placeholder that will be replaced at request time.\n") + .addCode(" return $T.create($T.create($S), false);\n", + ClassName.get("software.amazon.awssdk.core", "ClientEndpointProvider"), + URI.class, "https://localhost") + .addCode(" }\n") + .addCode(" if (clientEndpointUri.getHost() == null) {\n") + .addCode(" throw $T.create(\"Configured region (\" + region\n", + ClassName.get("software.amazon.awssdk.core.exception", "SdkClientException")) + .addCode(" + \") resulted in an invalid URI: \" + clientEndpointUri\n") + .addCode(" + \". This is usually caused by an invalid region configuration.\");\n") + .addCode(" }\n") + .addCode(" return $T.create(clientEndpointUri, false);\n", + ClassName.get("software.amazon.awssdk.core", "ClientEndpointProvider")) + .addCode("});\n"); if (model.getMetadata().isJsonProtocol()) { builder.addStatement("builder.option($1T.ENABLE_FAST_UNMARSHALLER, true)", @@ -1051,6 +1088,11 @@ private boolean hasClientContextParams() { return clientContextParams != null && !clientContextParams.isEmpty(); } + private boolean hasBuiltIn(BuiltInParameter builtIn) { + return model.getEndpointRuleSetModel().getParameters().values().stream() + .anyMatch(p -> builtIn.equals(p.getBuiltInEnum())); + } + private boolean hasSdkClientContextParams() { return model.getCustomizationConfig() != null && model.getCustomizationConfig().getCustomClientContextParams() != null diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-class.java index ee8f9a73d3e5..46cda07aba48 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-bearer-auth-client-builder-class.java @@ -1,10 +1,12 @@ package software.amazon.awssdk.services.json; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; @@ -15,13 +17,16 @@ import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.awscore.endpoint.AwsClientEndpointProvider; import software.amazon.awssdk.awscore.retry.AwsRetryStrategy; +import software.amazon.awssdk.core.ClientEndpointProvider; import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; @@ -29,15 +34,17 @@ import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.protocols.json.internal.unmarshall.SdkClientJsonProtocolAdvancedOption; -import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.retries.api.RetryStrategy; import software.amazon.awssdk.services.json.auth.scheme.JsonAuthSchemeProvider; import software.amazon.awssdk.services.json.auth.scheme.internal.JsonAuthSchemeInterceptor; +import software.amazon.awssdk.services.json.endpoints.JsonEndpointParams; import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; import software.amazon.awssdk.services.json.endpoints.internal.JsonRequestSetEndpointInterceptor; import software.amazon.awssdk.services.json.endpoints.internal.JsonResolveEndpointInterceptor; import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.Validate; /** @@ -96,20 +103,33 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); builder.lazyOptionIfAbsent( SdkClientOption.CLIENT_ENDPOINT_PROVIDER, - c -> AwsClientEndpointProvider - .builder() - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_JSON_SERVICE") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlJson") - .serviceProfileProperty("json_service") - .serviceEndpointPrefix(serviceEndpointPrefix()) - .defaultProtocol("https") - .region(c.get(AwsClientOption.AWS_REGION)) - .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) - .profileName(c.get(SdkClientOption.PROFILE_NAME)) - .putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, - c.get(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)) - .dualstackEnabled(c.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)) - .fipsEnabled(c.get(AwsClientOption.FIPS_ENDPOINT_ENABLED)).build()); + c -> { + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_JSON_SERVICE") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlJson").serviceProfileProperty("json_service") + .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) + .profileName(c.get(SdkClientOption.PROFILE_NAME)).resolveFromOverrides(); + if (overrideEndpoint.isPresent()) { + return ClientEndpointProvider.create(overrideEndpoint.get(), true); + } + URI clientEndpointUri = null; + Region region = c.get(AwsClientOption.AWS_REGION); + try { + JsonEndpointParams endpointParams = JsonEndpointParams.builder().region(region).build(); + Endpoint endpoint = CompletableFutureUtils.joinLikeSync(defaultEndpointProvider().resolveEndpoint( + endpointParams)); + clientEndpointUri = endpoint.url(); + } catch (Exception e) { + // Endpoint resolution failed. This is expected for services with required parameters + // beyond region, dualstack, and FIPS. Use a placeholder that will be replaced at request time. + return ClientEndpointProvider.create(URI.create("https://localhost"), false); + } + if (clientEndpointUri.getHost() == null) { + throw SdkClientException.create("Configured region (" + region + ") resulted in an invalid URI: " + + clientEndpointUri + ". This is usually caused by an invalid region configuration."); + } + return ClientEndpointProvider.create(clientEndpointUri, false); + }); builder.option(SdkClientJsonProtocolAdvancedOption.ENABLE_FAST_UNMARSHALLER, true); return builder.build(); } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java index a0bdac67d04d..325840ed20f6 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-class.java @@ -1,9 +1,11 @@ package software.amazon.awssdk.services.json; +import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import software.amazon.MyServiceHttpConfig; import software.amazon.MyServiceRetryPolicy; @@ -19,6 +21,7 @@ import software.amazon.awssdk.awscore.retry.AwsRetryStrategy; import software.amazon.awssdk.codegen.poet.plugins.InternalTestPlugin1; import software.amazon.awssdk.codegen.poet.plugins.InternalTestPlugin2; +import software.amazon.awssdk.core.ClientEndpointProvider; import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.checksums.RequestChecksumCalculation; import software.amazon.awssdk.core.checksums.RequestChecksumCalculationResolver; @@ -27,9 +30,11 @@ import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; @@ -38,17 +43,19 @@ import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.protocols.json.internal.unmarshall.SdkClientJsonProtocolAdvancedOption; -import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.retries.api.RetryStrategy; import software.amazon.awssdk.services.json.auth.scheme.JsonAuthSchemeProvider; import software.amazon.awssdk.services.json.auth.scheme.internal.JsonAuthSchemeInterceptor; import software.amazon.awssdk.services.json.endpoints.JsonClientContextParams; +import software.amazon.awssdk.services.json.endpoints.JsonEndpointParams; import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; import software.amazon.awssdk.services.json.endpoints.internal.JsonRequestSetEndpointInterceptor; import software.amazon.awssdk.services.json.endpoints.internal.JsonResolveEndpointInterceptor; import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; import software.amazon.awssdk.utils.AttributeMap; import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.Validate; /** @@ -192,20 +199,33 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon builder.option(SdkClientOption.SERVICE_CONFIGURATION, finalServiceConfig); builder.lazyOptionIfAbsent( SdkClientOption.CLIENT_ENDPOINT_PROVIDER, - c -> AwsClientEndpointProvider - .builder() - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_JSON_SERVICE") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlJson") - .serviceProfileProperty("json_service") - .serviceEndpointPrefix(serviceEndpointPrefix()) - .defaultProtocol("https") - .region(c.get(AwsClientOption.AWS_REGION)) - .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) - .profileName(c.get(SdkClientOption.PROFILE_NAME)) - .putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, - c.get(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)) - .dualstackEnabled(c.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)) - .fipsEnabled(c.get(AwsClientOption.FIPS_ENDPOINT_ENABLED)).build()); + c -> { + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_JSON_SERVICE") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlJson").serviceProfileProperty("json_service") + .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) + .profileName(c.get(SdkClientOption.PROFILE_NAME)).resolveFromOverrides(); + if (overrideEndpoint.isPresent()) { + return ClientEndpointProvider.create(overrideEndpoint.get(), true); + } + URI clientEndpointUri = null; + Region region = c.get(AwsClientOption.AWS_REGION); + try { + JsonEndpointParams endpointParams = JsonEndpointParams.builder().region(region).build(); + Endpoint endpoint = CompletableFutureUtils.joinLikeSync(defaultEndpointProvider().resolveEndpoint( + endpointParams)); + clientEndpointUri = endpoint.url(); + } catch (Exception e) { + // Endpoint resolution failed. This is expected for services with required parameters + // beyond region, dualstack, and FIPS. Use a placeholder that will be replaced at request time. + return ClientEndpointProvider.create(URI.create("https://localhost"), false); + } + if (clientEndpointUri.getHost() == null) { + throw SdkClientException.create("Configured region (" + region + ") resulted in an invalid URI: " + + clientEndpointUri + ". This is usually caused by an invalid region configuration."); + } + return ClientEndpointProvider.create(clientEndpointUri, false); + }); builder.option(SdkClientJsonProtocolAdvancedOption.ENABLE_FAST_UNMARSHALLER, true); SdkClientConfiguration clientConfig = config; builder.lazyOption(SdkClientOption.REQUEST_CHECKSUM_CALCULATION, c -> resolveRequestChecksumCalculation(clientConfig)); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-endpoints-auth-params.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-endpoints-auth-params.java index 360d3664eaad..26db5a5ed38e 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-endpoints-auth-params.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-endpoints-auth-params.java @@ -1,10 +1,12 @@ package software.amazon.awssdk.services.query; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; @@ -17,6 +19,7 @@ import software.amazon.awssdk.awscore.endpoints.AccountIdEndpointMode; import software.amazon.awssdk.awscore.endpoints.AccountIdEndpointModeResolver; import software.amazon.awssdk.awscore.retry.AwsRetryStrategy; +import software.amazon.awssdk.core.ClientEndpointProvider; import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.checksums.RequestChecksumCalculation; import software.amazon.awssdk.core.checksums.RequestChecksumCalculationResolver; @@ -25,9 +28,11 @@ import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; import software.amazon.awssdk.http.auth.aws.signer.RegionSet; @@ -37,16 +42,18 @@ import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.identity.spi.TokenIdentity; -import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.retries.api.RetryStrategy; import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeProvider; import software.amazon.awssdk.services.query.auth.scheme.internal.QueryAuthSchemeInterceptor; import software.amazon.awssdk.services.query.endpoints.QueryClientContextParams; +import software.amazon.awssdk.services.query.endpoints.QueryEndpointParams; import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; import software.amazon.awssdk.services.query.endpoints.internal.QueryRequestSetEndpointInterceptor; import software.amazon.awssdk.services.query.endpoints.internal.QueryResolveEndpointInterceptor; import software.amazon.awssdk.services.query.internal.QueryServiceClientConfigurationBuilder; import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.Validate; /** @@ -111,20 +118,35 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon builder.option(AwsClientOption.ACCOUNT_ID_ENDPOINT_MODE, resolveAccountIdEndpointMode(config)); builder.lazyOptionIfAbsent( SdkClientOption.CLIENT_ENDPOINT_PROVIDER, - c -> AwsClientEndpointProvider - .builder() - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_QUERY_SERVICE") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlQuery") - .serviceProfileProperty("query_service") - .serviceEndpointPrefix(serviceEndpointPrefix()) - .defaultProtocol("https") - .region(c.get(AwsClientOption.AWS_REGION)) - .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) - .profileName(c.get(SdkClientOption.PROFILE_NAME)) - .putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, - c.get(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)) - .dualstackEnabled(c.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)) - .fipsEnabled(c.get(AwsClientOption.FIPS_ENDPOINT_ENABLED)).build()); + c -> { + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_QUERY_SERVICE") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlQuery") + .serviceProfileProperty("query_service").profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) + .profileName(c.get(SdkClientOption.PROFILE_NAME)).resolveFromOverrides(); + if (overrideEndpoint.isPresent()) { + return ClientEndpointProvider.create(overrideEndpoint.get(), true); + } + URI clientEndpointUri = null; + Region region = c.get(AwsClientOption.AWS_REGION); + try { + QueryEndpointParams endpointParams = QueryEndpointParams.builder().region(region) + .useDualStack(c.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)) + .useFips(c.get(AwsClientOption.FIPS_ENDPOINT_ENABLED)).build(); + Endpoint endpoint = CompletableFutureUtils.joinLikeSync(defaultEndpointProvider().resolveEndpoint( + endpointParams)); + clientEndpointUri = endpoint.url(); + } catch (Exception e) { + // Endpoint resolution failed. This is expected for services with required parameters + // beyond region, dualstack, and FIPS. Use a placeholder that will be replaced at request time. + return ClientEndpointProvider.create(URI.create("https://localhost"), false); + } + if (clientEndpointUri.getHost() == null) { + throw SdkClientException.create("Configured region (" + region + ") resulted in an invalid URI: " + + clientEndpointUri + ". This is usually caused by an invalid region configuration."); + } + return ClientEndpointProvider.create(clientEndpointUri, false); + }); SdkClientConfiguration clientConfig = config; builder.lazyOption(SdkClientOption.REQUEST_CHECKSUM_CALCULATION, c -> resolveRequestChecksumCalculation(clientConfig)); builder.lazyOption(SdkClientOption.RESPONSE_CHECKSUM_VALIDATION, c -> resolveResponseChecksumValidation(clientConfig)); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-defaults-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-defaults-class.java index 5ecb09b82593..e66864159bbb 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-defaults-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-defaults-class.java @@ -1,10 +1,12 @@ package software.amazon.awssdk.services.json; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; @@ -13,28 +15,33 @@ import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.awscore.endpoint.AwsClientEndpointProvider; import software.amazon.awssdk.awscore.retry.AwsRetryStrategy; +import software.amazon.awssdk.core.ClientEndpointProvider; import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.protocols.json.internal.unmarshall.SdkClientJsonProtocolAdvancedOption; -import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.retries.api.RetryStrategy; import software.amazon.awssdk.services.json.auth.scheme.JsonAuthSchemeProvider; import software.amazon.awssdk.services.json.auth.scheme.internal.JsonAuthSchemeInterceptor; +import software.amazon.awssdk.services.json.endpoints.JsonEndpointParams; import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; import software.amazon.awssdk.services.json.endpoints.internal.JsonRequestSetEndpointInterceptor; import software.amazon.awssdk.services.json.endpoints.internal.JsonResolveEndpointInterceptor; import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.CompletableFutureUtils; /** * Internal base class for {@link DefaultJsonClientBuilder} and {@link DefaultJsonAsyncClientBuilder}. @@ -98,20 +105,33 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); builder.lazyOptionIfAbsent( SdkClientOption.CLIENT_ENDPOINT_PROVIDER, - c -> AwsClientEndpointProvider - .builder() - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_JSON_SERVICE") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlJson") - .serviceProfileProperty("json_service") - .serviceEndpointPrefix(serviceEndpointPrefix()) - .defaultProtocol("https") - .region(c.get(AwsClientOption.AWS_REGION)) - .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) - .profileName(c.get(SdkClientOption.PROFILE_NAME)) - .putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, - c.get(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)) - .dualstackEnabled(c.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)) - .fipsEnabled(c.get(AwsClientOption.FIPS_ENDPOINT_ENABLED)).build()); + c -> { + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_JSON_SERVICE") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlJson").serviceProfileProperty("json_service") + .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) + .profileName(c.get(SdkClientOption.PROFILE_NAME)).resolveFromOverrides(); + if (overrideEndpoint.isPresent()) { + return ClientEndpointProvider.create(overrideEndpoint.get(), true); + } + URI clientEndpointUri = null; + Region region = c.get(AwsClientOption.AWS_REGION); + try { + JsonEndpointParams endpointParams = JsonEndpointParams.builder().region(region).build(); + Endpoint endpoint = CompletableFutureUtils.joinLikeSync(defaultEndpointProvider().resolveEndpoint( + endpointParams)); + clientEndpointUri = endpoint.url(); + } catch (Exception e) { + // Endpoint resolution failed. This is expected for services with required parameters + // beyond region, dualstack, and FIPS. Use a placeholder that will be replaced at request time. + return ClientEndpointProvider.create(URI.create("https://localhost"), false); + } + if (clientEndpointUri.getHost() == null) { + throw SdkClientException.create("Configured region (" + region + ") resulted in an invalid URI: " + + clientEndpointUri + ". This is usually caused by an invalid region configuration."); + } + return ClientEndpointProvider.create(clientEndpointUri, false); + }); builder.option(SdkClientJsonProtocolAdvancedOption.ENABLE_FAST_UNMARSHALLER, true); return builder.build(); } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-sync-default-client-builder.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-sync-default-client-builder.java index 117e19038881..3336551a495a 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-sync-default-client-builder.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-sync-default-client-builder.java @@ -1,10 +1,12 @@ package software.amazon.awssdk.services.json; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; @@ -15,6 +17,7 @@ import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.awscore.endpoint.AwsClientEndpointProvider; import software.amazon.awssdk.awscore.retry.AwsRetryStrategy; +import software.amazon.awssdk.core.ClientEndpointProvider; import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.checksums.RequestChecksumCalculation; import software.amazon.awssdk.core.checksums.RequestChecksumCalculationResolver; @@ -23,9 +26,11 @@ import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; @@ -34,16 +39,18 @@ import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.protocols.json.internal.unmarshall.SdkClientJsonProtocolAdvancedOption; -import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.retries.api.RetryStrategy; import software.amazon.awssdk.services.json.auth.scheme.JsonAuthSchemeProvider; import software.amazon.awssdk.services.json.auth.scheme.internal.JsonAuthSchemeInterceptor; import software.amazon.awssdk.services.json.endpoints.JsonClientContextParams; +import software.amazon.awssdk.services.json.endpoints.JsonEndpointParams; import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; import software.amazon.awssdk.services.json.endpoints.internal.JsonRequestSetEndpointInterceptor; import software.amazon.awssdk.services.json.endpoints.internal.JsonResolveEndpointInterceptor; import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.Validate; /** @@ -115,20 +122,33 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon builder.option(SdkClientOption.SERVICE_CONFIGURATION, finalServiceConfig); builder.lazyOptionIfAbsent( SdkClientOption.CLIENT_ENDPOINT_PROVIDER, - c -> AwsClientEndpointProvider - .builder() - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_JSON_SERVICE") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlJson") - .serviceProfileProperty("json_service") - .serviceEndpointPrefix(serviceEndpointPrefix()) - .defaultProtocol("https") - .region(c.get(AwsClientOption.AWS_REGION)) - .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) - .profileName(c.get(SdkClientOption.PROFILE_NAME)) - .putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, - c.get(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)) - .dualstackEnabled(c.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)) - .fipsEnabled(c.get(AwsClientOption.FIPS_ENDPOINT_ENABLED)).build()); + c -> { + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_JSON_SERVICE") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlJson").serviceProfileProperty("json_service") + .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) + .profileName(c.get(SdkClientOption.PROFILE_NAME)).resolveFromOverrides(); + if (overrideEndpoint.isPresent()) { + return ClientEndpointProvider.create(overrideEndpoint.get(), true); + } + URI clientEndpointUri = null; + Region region = c.get(AwsClientOption.AWS_REGION); + try { + JsonEndpointParams endpointParams = JsonEndpointParams.builder().region(region).build(); + Endpoint endpoint = CompletableFutureUtils.joinLikeSync(defaultEndpointProvider().resolveEndpoint( + endpointParams)); + clientEndpointUri = endpoint.url(); + } catch (Exception e) { + // Endpoint resolution failed. This is expected for services with required parameters + // beyond region, dualstack, and FIPS. Use a placeholder that will be replaced at request time. + return ClientEndpointProvider.create(URI.create("https://localhost"), false); + } + if (clientEndpointUri.getHost() == null) { + throw SdkClientException.create("Configured region (" + region + ") resulted in an invalid URI: " + + clientEndpointUri + ". This is usually caused by an invalid region configuration."); + } + return ClientEndpointProvider.create(clientEndpointUri, false); + }); builder.option(SdkClientJsonProtocolAdvancedOption.ENABLE_FAST_UNMARSHALLER, true); SdkClientConfiguration clientConfig = config; builder.lazyOption(SdkClientOption.REQUEST_CHECKSUM_CALCULATION, c -> resolveRequestChecksumCalculation(clientConfig)); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-env-bearer-token-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-env-bearer-token-client-builder-class.java index 48ecf08535fa..309429696a29 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-env-bearer-token-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-env-bearer-token-client-builder-class.java @@ -1,5 +1,6 @@ package software.amazon.awssdk.services.json; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -17,15 +18,18 @@ import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.awscore.endpoint.AwsClientEndpointProvider; import software.amazon.awssdk.awscore.retry.AwsRetryStrategy; +import software.amazon.awssdk.core.ClientEndpointProvider; import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; @@ -33,16 +37,18 @@ import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.protocols.json.internal.unmarshall.SdkClientJsonProtocolAdvancedOption; -import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.retries.api.RetryStrategy; import software.amazon.awssdk.services.json.auth.scheme.JsonAuthSchemeProvider; import software.amazon.awssdk.services.json.auth.scheme.internal.JsonAuthSchemeInterceptor; +import software.amazon.awssdk.services.json.endpoints.JsonEndpointParams; import software.amazon.awssdk.services.json.endpoints.JsonEndpointProvider; import software.amazon.awssdk.services.json.endpoints.internal.JsonRequestSetEndpointInterceptor; import software.amazon.awssdk.services.json.endpoints.internal.JsonResolveEndpointInterceptor; import software.amazon.awssdk.services.json.internal.EnvironmentTokenSystemSettings; import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder; import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.Validate; /** @@ -113,20 +119,33 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); builder.lazyOptionIfAbsent( SdkClientOption.CLIENT_ENDPOINT_PROVIDER, - c -> AwsClientEndpointProvider - .builder() - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_JSON_SERVICE") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlJson") - .serviceProfileProperty("json_service") - .serviceEndpointPrefix(serviceEndpointPrefix()) - .defaultProtocol("https") - .region(c.get(AwsClientOption.AWS_REGION)) - .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) - .profileName(c.get(SdkClientOption.PROFILE_NAME)) - .putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, - c.get(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)) - .dualstackEnabled(c.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)) - .fipsEnabled(c.get(AwsClientOption.FIPS_ENDPOINT_ENABLED)).build()); + c -> { + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_JSON_SERVICE") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlJson").serviceProfileProperty("json_service") + .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) + .profileName(c.get(SdkClientOption.PROFILE_NAME)).resolveFromOverrides(); + if (overrideEndpoint.isPresent()) { + return ClientEndpointProvider.create(overrideEndpoint.get(), true); + } + URI clientEndpointUri = null; + Region region = c.get(AwsClientOption.AWS_REGION); + try { + JsonEndpointParams endpointParams = JsonEndpointParams.builder().region(region).build(); + Endpoint endpoint = CompletableFutureUtils.joinLikeSync(defaultEndpointProvider().resolveEndpoint( + endpointParams)); + clientEndpointUri = endpoint.url(); + } catch (Exception e) { + // Endpoint resolution failed. This is expected for services with required parameters + // beyond region, dualstack, and FIPS. Use a placeholder that will be replaced at request time. + return ClientEndpointProvider.create(URI.create("https://localhost"), false); + } + if (clientEndpointUri.getHost() == null) { + throw SdkClientException.create("Configured region (" + region + ") resulted in an invalid URI: " + + clientEndpointUri + ". This is usually caused by an invalid region configuration."); + } + return ClientEndpointProvider.create(clientEndpointUri, false); + }); builder.option(SdkClientJsonProtocolAdvancedOption.ENABLE_FAST_UNMARSHALLER, true); return builder.build(); } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-h2-service-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-h2-service-client-builder-class.java index 76c1cd2fc7eb..bf9a78a523a4 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-h2-service-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-h2-service-client-builder-class.java @@ -1,10 +1,12 @@ package software.amazon.awssdk.services.h2; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; @@ -13,13 +15,16 @@ import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.awscore.endpoint.AwsClientEndpointProvider; import software.amazon.awssdk.awscore.retry.AwsRetryStrategy; +import software.amazon.awssdk.core.ClientEndpointProvider; import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.http.Protocol; import software.amazon.awssdk.http.ProtocolNegotiation; import software.amazon.awssdk.http.SdkHttpConfigurationOption; @@ -29,16 +34,18 @@ import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.protocols.json.internal.unmarshall.SdkClientJsonProtocolAdvancedOption; -import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.retries.api.RetryStrategy; import software.amazon.awssdk.services.h2.auth.scheme.H2AuthSchemeProvider; import software.amazon.awssdk.services.h2.auth.scheme.internal.H2AuthSchemeInterceptor; +import software.amazon.awssdk.services.h2.endpoints.H2EndpointParams; import software.amazon.awssdk.services.h2.endpoints.H2EndpointProvider; import software.amazon.awssdk.services.h2.endpoints.internal.H2RequestSetEndpointInterceptor; import software.amazon.awssdk.services.h2.endpoints.internal.H2ResolveEndpointInterceptor; import software.amazon.awssdk.services.h2.internal.H2ServiceClientConfigurationBuilder; import software.amazon.awssdk.utils.AttributeMap; import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.CompletableFutureUtils; /** * Internal base class for {@link DefaultH2ClientBuilder} and {@link DefaultH2AsyncClientBuilder}. @@ -93,20 +100,33 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); builder.lazyOptionIfAbsent( SdkClientOption.CLIENT_ENDPOINT_PROVIDER, - c -> AwsClientEndpointProvider - .builder() - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_H2_SERVICE") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlH2") - .serviceProfileProperty("h2_service") - .serviceEndpointPrefix(serviceEndpointPrefix()) - .defaultProtocol("https") - .region(c.get(AwsClientOption.AWS_REGION)) - .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) - .profileName(c.get(SdkClientOption.PROFILE_NAME)) - .putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, - c.get(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)) - .dualstackEnabled(c.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)) - .fipsEnabled(c.get(AwsClientOption.FIPS_ENDPOINT_ENABLED)).build()); + c -> { + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_H2_SERVICE") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlH2").serviceProfileProperty("h2_service") + .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) + .profileName(c.get(SdkClientOption.PROFILE_NAME)).resolveFromOverrides(); + if (overrideEndpoint.isPresent()) { + return ClientEndpointProvider.create(overrideEndpoint.get(), true); + } + URI clientEndpointUri = null; + Region region = c.get(AwsClientOption.AWS_REGION); + try { + H2EndpointParams endpointParams = H2EndpointParams.builder().region(region).build(); + Endpoint endpoint = CompletableFutureUtils.joinLikeSync(defaultEndpointProvider().resolveEndpoint( + endpointParams)); + clientEndpointUri = endpoint.url(); + } catch (Exception e) { + // Endpoint resolution failed. This is expected for services with required parameters + // beyond region, dualstack, and FIPS. Use a placeholder that will be replaced at request time. + return ClientEndpointProvider.create(URI.create("https://localhost"), false); + } + if (clientEndpointUri.getHost() == null) { + throw SdkClientException.create("Configured region (" + region + ") resulted in an invalid URI: " + + clientEndpointUri + ". This is usually caused by an invalid region configuration."); + } + return ClientEndpointProvider.create(clientEndpointUri, false); + }); builder.option(SdkClientJsonProtocolAdvancedOption.ENABLE_FAST_UNMARSHALLER, true); return builder.build(); } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-h2-usePriorKnowledgeForH2-service-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-h2-usePriorKnowledgeForH2-service-client-builder-class.java index d3fc4996de98..3df3fafc1453 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-h2-usePriorKnowledgeForH2-service-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-h2-usePriorKnowledgeForH2-service-client-builder-class.java @@ -1,10 +1,12 @@ package software.amazon.awssdk.services.h2; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; @@ -13,13 +15,16 @@ import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.awscore.endpoint.AwsClientEndpointProvider; import software.amazon.awssdk.awscore.retry.AwsRetryStrategy; +import software.amazon.awssdk.core.ClientEndpointProvider; import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.http.Protocol; import software.amazon.awssdk.http.SdkHttpConfigurationOption; import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; @@ -28,16 +33,18 @@ import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.protocols.json.internal.unmarshall.SdkClientJsonProtocolAdvancedOption; -import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.retries.api.RetryStrategy; import software.amazon.awssdk.services.h2.auth.scheme.H2AuthSchemeProvider; import software.amazon.awssdk.services.h2.auth.scheme.internal.H2AuthSchemeInterceptor; +import software.amazon.awssdk.services.h2.endpoints.H2EndpointParams; import software.amazon.awssdk.services.h2.endpoints.H2EndpointProvider; import software.amazon.awssdk.services.h2.endpoints.internal.H2RequestSetEndpointInterceptor; import software.amazon.awssdk.services.h2.endpoints.internal.H2ResolveEndpointInterceptor; import software.amazon.awssdk.services.h2.internal.H2ServiceClientConfigurationBuilder; import software.amazon.awssdk.utils.AttributeMap; import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.CompletableFutureUtils; /** * Internal base class for {@link DefaultH2ClientBuilder} and {@link DefaultH2AsyncClientBuilder}. @@ -92,20 +99,33 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); builder.lazyOptionIfAbsent( SdkClientOption.CLIENT_ENDPOINT_PROVIDER, - c -> AwsClientEndpointProvider - .builder() - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_H2_SERVICE") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlH2") - .serviceProfileProperty("h2_service") - .serviceEndpointPrefix(serviceEndpointPrefix()) - .defaultProtocol("https") - .region(c.get(AwsClientOption.AWS_REGION)) - .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) - .profileName(c.get(SdkClientOption.PROFILE_NAME)) - .putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, - c.get(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)) - .dualstackEnabled(c.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)) - .fipsEnabled(c.get(AwsClientOption.FIPS_ENDPOINT_ENABLED)).build()); + c -> { + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_H2_SERVICE") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlH2").serviceProfileProperty("h2_service") + .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) + .profileName(c.get(SdkClientOption.PROFILE_NAME)).resolveFromOverrides(); + if (overrideEndpoint.isPresent()) { + return ClientEndpointProvider.create(overrideEndpoint.get(), true); + } + URI clientEndpointUri = null; + Region region = c.get(AwsClientOption.AWS_REGION); + try { + H2EndpointParams endpointParams = H2EndpointParams.builder().region(region).build(); + Endpoint endpoint = CompletableFutureUtils.joinLikeSync(defaultEndpointProvider().resolveEndpoint( + endpointParams)); + clientEndpointUri = endpoint.url(); + } catch (Exception e) { + // Endpoint resolution failed. This is expected for services with required parameters + // beyond region, dualstack, and FIPS. Use a placeholder that will be replaced at request time. + return ClientEndpointProvider.create(URI.create("https://localhost"), false); + } + if (clientEndpointUri.getHost() == null) { + throw SdkClientException.create("Configured region (" + region + ") resulted in an invalid URI: " + + clientEndpointUri + ". This is usually caused by an invalid region configuration."); + } + return ClientEndpointProvider.create(clientEndpointUri, false); + }); builder.option(SdkClientJsonProtocolAdvancedOption.ENABLE_FAST_UNMARSHALLER, true); return builder.build(); } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-multi-auth-sigv4a-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-multi-auth-sigv4a-client-builder-class.java index 75faf2cad7a8..6bad0e14b113 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-multi-auth-sigv4a-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-multi-auth-sigv4a-client-builder-class.java @@ -1,10 +1,12 @@ package software.amazon.awssdk.services.database; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; @@ -13,13 +15,16 @@ import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.awscore.endpoint.AwsClientEndpointProvider; import software.amazon.awssdk.awscore.retry.AwsRetryStrategy; +import software.amazon.awssdk.core.ClientEndpointProvider; import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; import software.amazon.awssdk.http.auth.aws.signer.RegionSet; @@ -28,15 +33,17 @@ import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.protocols.json.internal.unmarshall.SdkClientJsonProtocolAdvancedOption; -import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.retries.api.RetryStrategy; import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeProvider; import software.amazon.awssdk.services.database.auth.scheme.internal.DatabaseAuthSchemeInterceptor; +import software.amazon.awssdk.services.database.endpoints.DatabaseEndpointParams; import software.amazon.awssdk.services.database.endpoints.DatabaseEndpointProvider; import software.amazon.awssdk.services.database.endpoints.internal.DatabaseRequestSetEndpointInterceptor; import software.amazon.awssdk.services.database.endpoints.internal.DatabaseResolveEndpointInterceptor; import software.amazon.awssdk.services.database.internal.DatabaseServiceClientConfigurationBuilder; import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.CompletableFutureUtils; /** * Internal base class for {@link DefaultDatabaseClientBuilder} and {@link DefaultDatabaseAsyncClientBuilder}. @@ -92,20 +99,33 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); builder.lazyOptionIfAbsent( SdkClientOption.CLIENT_ENDPOINT_PROVIDER, - c -> AwsClientEndpointProvider - .builder() - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_DATABASE_SERVICE") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlDatabase") - .serviceProfileProperty("database_service") - .serviceEndpointPrefix(serviceEndpointPrefix()) - .defaultProtocol("https") - .region(c.get(AwsClientOption.AWS_REGION)) - .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) - .profileName(c.get(SdkClientOption.PROFILE_NAME)) - .putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, - c.get(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)) - .dualstackEnabled(c.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)) - .fipsEnabled(c.get(AwsClientOption.FIPS_ENDPOINT_ENABLED)).build()); + c -> { + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_DATABASE_SERVICE") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlDatabase") + .serviceProfileProperty("database_service").profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) + .profileName(c.get(SdkClientOption.PROFILE_NAME)).resolveFromOverrides(); + if (overrideEndpoint.isPresent()) { + return ClientEndpointProvider.create(overrideEndpoint.get(), true); + } + URI clientEndpointUri = null; + Region region = c.get(AwsClientOption.AWS_REGION); + try { + DatabaseEndpointParams endpointParams = DatabaseEndpointParams.builder().region(region).build(); + Endpoint endpoint = CompletableFutureUtils.joinLikeSync(defaultEndpointProvider().resolveEndpoint( + endpointParams)); + clientEndpointUri = endpoint.url(); + } catch (Exception e) { + // Endpoint resolution failed. This is expected for services with required parameters + // beyond region, dualstack, and FIPS. Use a placeholder that will be replaced at request time. + return ClientEndpointProvider.create(URI.create("https://localhost"), false); + } + if (clientEndpointUri.getHost() == null) { + throw SdkClientException.create("Configured region (" + region + ") resulted in an invalid URI: " + + clientEndpointUri + ". This is usually caused by an invalid region configuration."); + } + return ClientEndpointProvider.create(clientEndpointUri, false); + }); builder.option(SdkClientJsonProtocolAdvancedOption.ENABLE_FAST_UNMARSHALLER, true); return builder.build(); } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-no-auth-ops-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-no-auth-ops-client-builder-class.java index 72d4f526bfb3..f15531bda1cb 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-no-auth-ops-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-no-auth-ops-client-builder-class.java @@ -1,10 +1,12 @@ package software.amazon.awssdk.services.database; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; @@ -15,13 +17,16 @@ import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.awscore.endpoint.AwsClientEndpointProvider; import software.amazon.awssdk.awscore.retry.AwsRetryStrategy; +import software.amazon.awssdk.core.ClientEndpointProvider; import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; @@ -30,15 +35,17 @@ import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.protocols.json.internal.unmarshall.SdkClientJsonProtocolAdvancedOption; -import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.retries.api.RetryStrategy; import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeProvider; import software.amazon.awssdk.services.database.auth.scheme.internal.DatabaseAuthSchemeInterceptor; +import software.amazon.awssdk.services.database.endpoints.DatabaseEndpointParams; import software.amazon.awssdk.services.database.endpoints.DatabaseEndpointProvider; import software.amazon.awssdk.services.database.endpoints.internal.DatabaseRequestSetEndpointInterceptor; import software.amazon.awssdk.services.database.endpoints.internal.DatabaseResolveEndpointInterceptor; import software.amazon.awssdk.services.database.internal.DatabaseServiceClientConfigurationBuilder; import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.Validate; /** @@ -102,20 +109,33 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); builder.lazyOptionIfAbsent( SdkClientOption.CLIENT_ENDPOINT_PROVIDER, - c -> AwsClientEndpointProvider - .builder() - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_DATABASE_SERVICE") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlDatabase") - .serviceProfileProperty("database_service") - .serviceEndpointPrefix(serviceEndpointPrefix()) - .defaultProtocol("https") - .region(c.get(AwsClientOption.AWS_REGION)) - .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) - .profileName(c.get(SdkClientOption.PROFILE_NAME)) - .putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, - c.get(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)) - .dualstackEnabled(c.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)) - .fipsEnabled(c.get(AwsClientOption.FIPS_ENDPOINT_ENABLED)).build()); + c -> { + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_DATABASE_SERVICE") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlDatabase") + .serviceProfileProperty("database_service").profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) + .profileName(c.get(SdkClientOption.PROFILE_NAME)).resolveFromOverrides(); + if (overrideEndpoint.isPresent()) { + return ClientEndpointProvider.create(overrideEndpoint.get(), true); + } + URI clientEndpointUri = null; + Region region = c.get(AwsClientOption.AWS_REGION); + try { + DatabaseEndpointParams endpointParams = DatabaseEndpointParams.builder().region(region).build(); + Endpoint endpoint = CompletableFutureUtils.joinLikeSync(defaultEndpointProvider().resolveEndpoint( + endpointParams)); + clientEndpointUri = endpoint.url(); + } catch (Exception e) { + // Endpoint resolution failed. This is expected for services with required parameters + // beyond region, dualstack, and FIPS. Use a placeholder that will be replaced at request time. + return ClientEndpointProvider.create(URI.create("https://localhost"), false); + } + if (clientEndpointUri.getHost() == null) { + throw SdkClientException.create("Configured region (" + region + ") resulted in an invalid URI: " + + clientEndpointUri + ". This is usually caused by an invalid region configuration."); + } + return ClientEndpointProvider.create(clientEndpointUri, false); + }); builder.option(SdkClientJsonProtocolAdvancedOption.ENABLE_FAST_UNMARSHALLER, true); return builder.build(); } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-no-auth-service-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-no-auth-service-client-builder-class.java index 0be9c031d828..66c44739a50c 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-no-auth-service-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-no-auth-service-client-builder-class.java @@ -1,10 +1,12 @@ package software.amazon.awssdk.services.database; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; @@ -13,26 +15,31 @@ import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.awscore.endpoint.AwsClientEndpointProvider; import software.amazon.awssdk.awscore.retry.AwsRetryStrategy; +import software.amazon.awssdk.core.ClientEndpointProvider; import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.protocols.json.internal.unmarshall.SdkClientJsonProtocolAdvancedOption; -import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.retries.api.RetryStrategy; import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeProvider; import software.amazon.awssdk.services.database.auth.scheme.internal.DatabaseAuthSchemeInterceptor; +import software.amazon.awssdk.services.database.endpoints.DatabaseEndpointParams; import software.amazon.awssdk.services.database.endpoints.DatabaseEndpointProvider; import software.amazon.awssdk.services.database.endpoints.internal.DatabaseRequestSetEndpointInterceptor; import software.amazon.awssdk.services.database.endpoints.internal.DatabaseResolveEndpointInterceptor; import software.amazon.awssdk.services.database.internal.DatabaseServiceClientConfigurationBuilder; import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.CompletableFutureUtils; /** * Internal base class for {@link DefaultDatabaseClientBuilder} and {@link DefaultDatabaseAsyncClientBuilder}. @@ -84,20 +91,33 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); builder.lazyOptionIfAbsent( SdkClientOption.CLIENT_ENDPOINT_PROVIDER, - c -> AwsClientEndpointProvider - .builder() - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_DATABASE_SERVICE") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlDatabase") - .serviceProfileProperty("database_service") - .serviceEndpointPrefix(serviceEndpointPrefix()) - .defaultProtocol("https") - .region(c.get(AwsClientOption.AWS_REGION)) - .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) - .profileName(c.get(SdkClientOption.PROFILE_NAME)) - .putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, - c.get(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)) - .dualstackEnabled(c.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)) - .fipsEnabled(c.get(AwsClientOption.FIPS_ENDPOINT_ENABLED)).build()); + c -> { + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_DATABASE_SERVICE") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlDatabase") + .serviceProfileProperty("database_service").profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) + .profileName(c.get(SdkClientOption.PROFILE_NAME)).resolveFromOverrides(); + if (overrideEndpoint.isPresent()) { + return ClientEndpointProvider.create(overrideEndpoint.get(), true); + } + URI clientEndpointUri = null; + Region region = c.get(AwsClientOption.AWS_REGION); + try { + DatabaseEndpointParams endpointParams = DatabaseEndpointParams.builder().region(region).build(); + Endpoint endpoint = CompletableFutureUtils.joinLikeSync(defaultEndpointProvider().resolveEndpoint( + endpointParams)); + clientEndpointUri = endpoint.url(); + } catch (Exception e) { + // Endpoint resolution failed. This is expected for services with required parameters + // beyond region, dualstack, and FIPS. Use a placeholder that will be replaced at request time. + return ClientEndpointProvider.create(URI.create("https://localhost"), false); + } + if (clientEndpointUri.getHost() == null) { + throw SdkClientException.create("Configured region (" + region + ") resulted in an invalid URI: " + + clientEndpointUri + ". This is usually caused by an invalid region configuration."); + } + return ClientEndpointProvider.create(clientEndpointUri, false); + }); builder.option(SdkClientJsonProtocolAdvancedOption.ENABLE_FAST_UNMARSHALLER, true); return builder.build(); } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-query-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-query-client-builder-class.java index 19b8d5abbae1..69c84cb46ae9 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-query-client-builder-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-query-client-builder-class.java @@ -1,10 +1,12 @@ package software.amazon.awssdk.services.query; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; @@ -17,6 +19,7 @@ import software.amazon.awssdk.awscore.endpoints.AccountIdEndpointMode; import software.amazon.awssdk.awscore.endpoints.AccountIdEndpointModeResolver; import software.amazon.awssdk.awscore.retry.AwsRetryStrategy; +import software.amazon.awssdk.core.ClientEndpointProvider; import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.checksums.RequestChecksumCalculation; import software.amazon.awssdk.core.checksums.RequestChecksumCalculationResolver; @@ -25,9 +28,11 @@ import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; @@ -35,16 +40,18 @@ import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.identity.spi.IdentityProviders; import software.amazon.awssdk.identity.spi.TokenIdentity; -import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.retries.api.RetryStrategy; import software.amazon.awssdk.services.query.auth.scheme.QueryAuthSchemeProvider; import software.amazon.awssdk.services.query.auth.scheme.internal.QueryAuthSchemeInterceptor; import software.amazon.awssdk.services.query.endpoints.QueryClientContextParams; +import software.amazon.awssdk.services.query.endpoints.QueryEndpointParams; import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider; import software.amazon.awssdk.services.query.endpoints.internal.QueryRequestSetEndpointInterceptor; import software.amazon.awssdk.services.query.endpoints.internal.QueryResolveEndpointInterceptor; import software.amazon.awssdk.services.query.internal.QueryServiceClientConfigurationBuilder; import software.amazon.awssdk.utils.CollectionUtils; +import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.Validate; /** @@ -109,20 +116,35 @@ protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientCon builder.option(AwsClientOption.ACCOUNT_ID_ENDPOINT_MODE, resolveAccountIdEndpointMode(config)); builder.lazyOptionIfAbsent( SdkClientOption.CLIENT_ENDPOINT_PROVIDER, - c -> AwsClientEndpointProvider - .builder() - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_QUERY_SERVICE") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlQuery") - .serviceProfileProperty("query_service") - .serviceEndpointPrefix(serviceEndpointPrefix()) - .defaultProtocol("https") - .region(c.get(AwsClientOption.AWS_REGION)) - .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) - .profileName(c.get(SdkClientOption.PROFILE_NAME)) - .putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, - c.get(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)) - .dualstackEnabled(c.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)) - .fipsEnabled(c.get(AwsClientOption.FIPS_ENDPOINT_ENABLED)).build()); + c -> { + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_QUERY_SERVICE") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlQuery") + .serviceProfileProperty("query_service").profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) + .profileName(c.get(SdkClientOption.PROFILE_NAME)).resolveFromOverrides(); + if (overrideEndpoint.isPresent()) { + return ClientEndpointProvider.create(overrideEndpoint.get(), true); + } + URI clientEndpointUri = null; + Region region = c.get(AwsClientOption.AWS_REGION); + try { + QueryEndpointParams endpointParams = QueryEndpointParams.builder().region(region) + .useDualStack(c.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)) + .useFips(c.get(AwsClientOption.FIPS_ENDPOINT_ENABLED)).build(); + Endpoint endpoint = CompletableFutureUtils.joinLikeSync(defaultEndpointProvider().resolveEndpoint( + endpointParams)); + clientEndpointUri = endpoint.url(); + } catch (Exception e) { + // Endpoint resolution failed. This is expected for services with required parameters + // beyond region, dualstack, and FIPS. Use a placeholder that will be replaced at request time. + return ClientEndpointProvider.create(URI.create("https://localhost"), false); + } + if (clientEndpointUri.getHost() == null) { + throw SdkClientException.create("Configured region (" + region + ") resulted in an invalid URI: " + + clientEndpointUri + ". This is usually caused by an invalid region configuration."); + } + return ClientEndpointProvider.create(clientEndpointUri, false); + }); SdkClientConfiguration clientConfig = config; builder.lazyOption(SdkClientOption.REQUEST_CHECKSUM_CALCULATION, c -> resolveRequestChecksumCalculation(clientConfig)); builder.lazyOption(SdkClientOption.RESPONSE_CHECKSUM_VALIDATION, c -> resolveResponseChecksumValidation(clientConfig)); 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..2e660f06ea08 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 @@ -95,7 +95,8 @@ public boolean isEndpointOverridden() { return clientEndpoint.getValue().isEndpointOverridden; } - private ClientEndpoint resolveClientEndpoint(Builder builder) { + // TODO: Remove once all callers are migrated to use resolveFromOverrides + private static ClientEndpoint resolveClientEndpoint(Builder builder) { return OptionalUtils.firstPresent(clientEndpointFromClientOverride(builder), () -> clientEndpointFromEnvironment(builder), () -> clientEndpointFromServiceMetadata(builder)) @@ -107,14 +108,14 @@ private static SdkClientException failToLoadEndpointException() { AwsClientEndpointProvider.class.getName() + " for more information."); } - private Optional clientEndpointFromClientOverride(Builder builder) { + private static Optional clientEndpointFromClientOverride(Builder builder) { Optional result = Optional.ofNullable(builder.clientEndpointOverride) .map(uri -> new ClientEndpoint(uri, true)); result.ifPresent(e -> log.trace(() -> "Client was configured with endpoint override: " + e.clientEndpoint)); return result; } - private Optional clientEndpointFromEnvironment(Builder builder) { + private static Optional clientEndpointFromEnvironment(Builder builder) { if (builder.serviceEndpointOverrideEnvironmentVariable == null || builder.serviceEndpointOverrideSystemProperty == null || builder.serviceProfileProperty == null) { @@ -147,19 +148,19 @@ private Optional clientEndpointFromEnvironment(Builder builder) .map(uri -> new ClientEndpoint(uri, true)); } - private Optional systemProperty(String systemProperty) { + private static Optional systemProperty(String systemProperty) { // CHECKSTYLE:OFF - We have to read system properties directly here to match the load order of the other SDKs return createUri("system property " + systemProperty, Optional.ofNullable(System.getProperty(systemProperty))); // CHECKSTYLE:ON } - private Optional environmentVariable(String environmentVariable) { + private static Optional environmentVariable(String environmentVariable) { return createUri("environment variable " + environmentVariable, SystemSettingUtils.resolveEnvironmentVariable(environmentVariable)); } - private Optional profileProperty(Builder builder, String profileProperty) { + private static Optional profileProperty(Builder builder, String profileProperty) { initializeProfileFileDefaults(builder); return createUri("profile property " + profileProperty, Optional.ofNullable(builder.profileFile.get()) @@ -167,7 +168,7 @@ private Optional profileProperty(Builder builder, String profileProperty) { .flatMap(p -> p.property(profileProperty))); } - private Optional servicesProperty(Builder builder) { + private static Optional servicesProperty(Builder builder) { Optional profileFile = Optional.ofNullable(builder.profileFile.get()); Optional servicesSectionName = profileFile .flatMap(pf -> pf.profile(builder.profileName)) @@ -181,7 +182,7 @@ private Optional servicesProperty(Builder builder) { return createUri("services section property", serviceEndpoint); } - private Optional clientEndpointFromServiceMetadata(Builder builder) { + private static 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. @@ -258,7 +259,7 @@ private Optional clientEndpointFromServiceMetadata(Builder build return Optional.of(new ClientEndpoint(endpoint, false)); } - private Optional createUri(String source, Optional uri) { + private static Optional createUri(String source, Optional uri) { return uri.map(u -> { try { URI parsedUri = SdkUri.getInstance().newUri(uri.get()); @@ -270,7 +271,7 @@ private Optional createUri(String source, Optional uri) { }); } - private void initializeProfileFileDefaults(Builder builder) { + private static void initializeProfileFileDefaults(Builder builder) { if (builder.profileFile == null) { builder.profileFile = new Lazy<>(ProfileFile::defaultProfileFile)::getValue; } @@ -482,5 +483,19 @@ public Builder putAdvancedOption(ServiceMetadataAdvancedOption option, T public AwsClientEndpointProvider build() { return new AwsClientEndpointProvider(this); } + + /** + * Resolve an endpoint from overrides and environment configuration. + * Checks client override, system properties, environment variables, and profile configuration. + * Returns {@link Optional#empty()} if no override is configured, allowing callers to provide + * their own fallback. + */ + public Optional resolveFromOverrides() { + Builder copy = new Builder(this); + initializeProfileFileDefaults(copy); + return OptionalUtils.firstPresent(clientEndpointFromClientOverride(copy), + () -> clientEndpointFromEnvironment(copy)) + .map(endpoint -> endpoint.clientEndpoint); + } } } diff --git a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/endpoint/AwsClientEndpointProviderResolveFromOverridesTest.java b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/endpoint/AwsClientEndpointProviderResolveFromOverridesTest.java new file mode 100644 index 000000000000..8ca96cf9aefb --- /dev/null +++ b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/endpoint/AwsClientEndpointProviderResolveFromOverridesTest.java @@ -0,0 +1,79 @@ +/* + * 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.net.URI; +import java.util.Optional; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +class AwsClientEndpointProviderResolveFromOverridesTest { + + private static final String TEST_SYSTEM_PROPERTY = "aws.endpointUrlTestService"; + + @AfterEach + void cleanup() { + System.clearProperty(TEST_SYSTEM_PROPERTY); + } + + @Test + void resolveFromOverrides_withClientEndpointOverride_returnsOverrideUri() { + URI override = URI.create("https://custom-endpoint.example.com"); + + Optional result = AwsClientEndpointProvider.builder() + .clientEndpointOverride(override) + .resolveFromOverrides(); + + assertThat(result).isPresent(); + assertThat(result.get()).isEqualTo(override); + } + + @Test + void resolveFromOverrides_withSystemPropertyEndpoint_returnsEndpointUri() { + System.setProperty(TEST_SYSTEM_PROPERTY, "https://sys-prop-endpoint.example.com"); + + Optional result = AwsClientEndpointProvider.builder() + .serviceEndpointOverrideSystemProperty(TEST_SYSTEM_PROPERTY) + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_TEST_SERVICE") + .serviceProfileProperty("testservice") + .resolveFromOverrides(); + + assertThat(result).isPresent(); + assertThat(result.get()).isEqualTo(URI.create("https://sys-prop-endpoint.example.com")); + } + + @Test + void resolveFromOverrides_withNoOverrideOrEnvironment_returnsEmpty() { + Optional result = AwsClientEndpointProvider.builder() + .serviceEndpointOverrideSystemProperty("aws.endpointUrlNonExistent") + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_NON_EXISTENT") + .serviceProfileProperty("nonexistent") + .resolveFromOverrides(); + + assertThat(result).isEmpty(); + } + + @Test + void resolveFromOverrides_withNoParamsConfigured_returnsEmpty() { + Optional result = AwsClientEndpointProvider.builder() + .resolveFromOverrides(); + + assertThat(result).isEmpty(); + } + +} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/ClientEndpointResolutionTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/ClientEndpointResolutionTest.java new file mode 100644 index 000000000000..da9e0b3e34d0 --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/endpointproviders/ClientEndpointResolutionTest.java @@ -0,0 +1,111 @@ +/* + * 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.endpointproviders; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.EndpointCapturingInterceptor; +import software.amazon.awssdk.services.restjsonendpointproviders.RestJsonEndpointProvidersClient; + +/** + * Tests that the generated client builder resolves a default endpoint when no override is configured, + * and correctly uses endpoint overrides from client configuration, system properties, and environment sources. + */ +public class ClientEndpointResolutionTest { + + private static final String SERVICE_SYSTEM_PROPERTY = "aws.endpointUrlRestJsonEndpointProviders"; + + @AfterEach + void cleanup() { + System.clearProperty(SERVICE_SYSTEM_PROPERTY); + } + + @Test + void clientBuild_withRegionOnly_resolvesEndpointViaEndpoints2() { + EndpointCapturingInterceptor interceptor = new EndpointCapturingInterceptor(); + + RestJsonEndpointProvidersClient client = RestJsonEndpointProvidersClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(AnonymousCredentialsProvider.create()) + .overrideConfiguration(c -> c.addExecutionInterceptor(interceptor)) + .build(); + + try { + client.operationWithNoInputOrOutput(r -> {}); + } catch (EndpointCapturingInterceptor.CaptureCompletedException e) { + // Expected + } + + assertThat(interceptor.endpoints()) + .singleElement() + .asString() + .contains("us-west-2") + .contains("amazonaws.com"); + } + + @Test + void clientBuild_withSystemPropertyOverride_usesOverrideEndpoint() { + System.setProperty(SERVICE_SYSTEM_PROPERTY, "https://custom-override.example.com"); + + EndpointCapturingInterceptor interceptor = new EndpointCapturingInterceptor(); + + RestJsonEndpointProvidersClient client = RestJsonEndpointProvidersClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(AnonymousCredentialsProvider.create()) + .overrideConfiguration(c -> c.addExecutionInterceptor(interceptor)) + .build(); + + try { + client.operationWithNoInputOrOutput(r -> {}); + } catch (EndpointCapturingInterceptor.CaptureCompletedException e) { + // Expected + } + + assertThat(interceptor.endpoints()) + .singleElement() + .asString() + .startsWith("https://custom-override.example.com"); + } + + @Test + void clientBuild_withEndpointOverride_usesClientOverride() { + EndpointCapturingInterceptor interceptor = new EndpointCapturingInterceptor(); + + RestJsonEndpointProvidersClient client = RestJsonEndpointProvidersClient.builder() + .region(Region.US_WEST_2) + .endpointOverride(URI.create("https://my-override.example.com")) + .credentialsProvider(AnonymousCredentialsProvider.create()) + .overrideConfiguration(c -> c.addExecutionInterceptor(interceptor)) + .build(); + + try { + client.operationWithNoInputOrOutput(r -> {}); + } catch (EndpointCapturingInterceptor.CaptureCompletedException e) { + // Expected + } + + assertThat(interceptor.endpoints()) + .singleElement() + .asString() + .startsWith("https://my-override.example.com"); + } + +} From 2be9a06a45f63bb5a14d65ebec5bb2a81bf6134d Mon Sep 17 00:00:00 2001 From: Saranya Somepalli Date: Mon, 13 Jul 2026 10:30:56 -0700 Subject: [PATCH 2/4] Remove ServiceMetadata usage from RDS/Neptune/DocDB presigning interceptors (#7105) * Remove ServiceMetadata usage from RDS/Neptune/DocDB presigning interceptors * Add tests --- .../docdb/internal/RdsPresignInterceptor.java | 28 +++++++----------- .../internal/RdsPresignInterceptor.java | 28 +++++++----------- .../rds/internal/RdsPresignInterceptor.java | 28 +++++++----------- .../internal/PresignRequestHandlerTest.java | 29 +++++++++++++++++++ 4 files changed, 62 insertions(+), 51 deletions(-) diff --git a/services/docdb/src/main/java/software/amazon/awssdk/services/docdb/internal/RdsPresignInterceptor.java b/services/docdb/src/main/java/software/amazon/awssdk/services/docdb/internal/RdsPresignInterceptor.java index 55b0fd574e64..ae4597c5b6bd 100644 --- a/services/docdb/src/main/java/software/amazon/awssdk/services/docdb/internal/RdsPresignInterceptor.java +++ b/services/docdb/src/main/java/software/amazon/awssdk/services/docdb/internal/RdsPresignInterceptor.java @@ -25,9 +25,7 @@ import java.util.concurrent.CompletableFuture; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.awscore.AwsExecutionAttribute; -import software.amazon.awssdk.awscore.endpoint.AwsClientEndpointProvider; import software.amazon.awssdk.core.ClientEndpointProvider; -import software.amazon.awssdk.core.Protocol; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; @@ -35,7 +33,6 @@ import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; -import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.http.SdkHttpRequest; @@ -48,6 +45,7 @@ import software.amazon.awssdk.identity.spi.Identity; import software.amazon.awssdk.protocols.query.AwsQueryProtocolFactory; import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.docdb.endpoints.DocDbEndpointProvider; import software.amazon.awssdk.services.docdb.model.DocDbRequest; import software.amazon.awssdk.utils.CompletableFutureUtils; @@ -72,7 +70,6 @@ public abstract class RdsPresignInterceptor implements E .build()) .build(); - private static final String SERVICE_NAME = "rds"; private static final String PARAM_SOURCE_REGION = "SourceRegion"; private static final String PARAM_DESTINATION_REGION = "DestinationRegion"; private static final String PARAM_PRESIGNED_URL = "PreSignedUrl"; @@ -109,7 +106,7 @@ public final SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, SelectedAuthScheme selectedAuthScheme = executionAttributes.getAttribute(SELECTED_AUTH_SCHEME); String sourceRegion = presignableRequest.getSourceRegion(); String destinationRegion = selectedAuthScheme.authSchemeOption().signerProperty(AwsV4HttpSigner.REGION_NAME); - URI endpoint = createEndpoint(sourceRegion, SERVICE_NAME, executionAttributes); + URI endpoint = createEndpoint(sourceRegion, executionAttributes); SdkHttpFullRequest.Builder marshalledRequest = presignableRequest.marshall().toBuilder().uri(endpoint); SdkHttpFullRequest requestToPresign = @@ -218,17 +215,14 @@ private SdkHttpFullRequest toSdkHttpFullRequest(SignedRequest signedRequest) { .build(); } - private URI createEndpoint(String regionName, String serviceName, ExecutionAttributes attributes) { - return AwsClientEndpointProvider.builder() - .serviceEndpointPrefix(SERVICE_NAME) - .defaultProtocol(Protocol.HTTPS.toString()) - .region(Region.of(regionName)) - .profileFile(attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER)) - .profileName(attributes.getAttribute(SdkExecutionAttribute.PROFILE_NAME)) - .dualstackEnabled( - attributes.getAttribute(AwsExecutionAttribute.DUALSTACK_ENDPOINT_ENABLED)) - .fipsEnabled(attributes.getAttribute(AwsExecutionAttribute.FIPS_ENDPOINT_ENABLED)) - .build() - .clientEndpoint(); + private URI createEndpoint(String regionName, ExecutionAttributes attributes) { + return CompletableFutureUtils.joinLikeSync( + DocDbEndpointProvider.defaultProvider() + .resolveEndpoint(p -> p.region(Region.of(regionName)) + .useDualStack(attributes.getAttribute( + AwsExecutionAttribute.DUALSTACK_ENDPOINT_ENABLED)) + .useFips(attributes.getAttribute( + AwsExecutionAttribute.FIPS_ENDPOINT_ENABLED))) + ).url(); } } diff --git a/services/neptune/src/main/java/software/amazon/awssdk/services/neptune/internal/RdsPresignInterceptor.java b/services/neptune/src/main/java/software/amazon/awssdk/services/neptune/internal/RdsPresignInterceptor.java index 34f8fe30247c..8d536a863dde 100644 --- a/services/neptune/src/main/java/software/amazon/awssdk/services/neptune/internal/RdsPresignInterceptor.java +++ b/services/neptune/src/main/java/software/amazon/awssdk/services/neptune/internal/RdsPresignInterceptor.java @@ -25,9 +25,7 @@ import java.util.concurrent.CompletableFuture; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.awscore.AwsExecutionAttribute; -import software.amazon.awssdk.awscore.endpoint.AwsClientEndpointProvider; import software.amazon.awssdk.core.ClientEndpointProvider; -import software.amazon.awssdk.core.Protocol; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; @@ -35,7 +33,6 @@ import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; -import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.http.SdkHttpRequest; @@ -48,6 +45,7 @@ import software.amazon.awssdk.identity.spi.Identity; import software.amazon.awssdk.protocols.query.AwsQueryProtocolFactory; import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.neptune.endpoints.NeptuneEndpointProvider; import software.amazon.awssdk.services.neptune.model.NeptuneRequest; import software.amazon.awssdk.utils.CompletableFutureUtils; @@ -73,7 +71,6 @@ public abstract class RdsPresignInterceptor implements .build()) .build(); - private static final String SERVICE_NAME = "rds"; private static final String PARAM_SOURCE_REGION = "SourceRegion"; private static final String PARAM_DESTINATION_REGION = "DestinationRegion"; private static final String PARAM_PRESIGNED_URL = "PreSignedUrl"; @@ -110,7 +107,7 @@ public final SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, SelectedAuthScheme selectedAuthScheme = executionAttributes.getAttribute(SELECTED_AUTH_SCHEME); String sourceRegion = presignableRequest.getSourceRegion(); String destinationRegion = selectedAuthScheme.authSchemeOption().signerProperty(AwsV4HttpSigner.REGION_NAME); - URI endpoint = createEndpoint(sourceRegion, SERVICE_NAME, executionAttributes); + URI endpoint = createEndpoint(sourceRegion, executionAttributes); SdkHttpFullRequest.Builder marshalledRequest = presignableRequest.marshall().toBuilder().uri(endpoint); SdkHttpFullRequest requestToPresign = @@ -219,17 +216,14 @@ private SdkHttpFullRequest toSdkHttpFullRequest(SignedRequest signedRequest) { .build(); } - private URI createEndpoint(String regionName, String serviceName, ExecutionAttributes attributes) { - return AwsClientEndpointProvider.builder() - .serviceEndpointPrefix(SERVICE_NAME) - .defaultProtocol(Protocol.HTTPS.toString()) - .region(Region.of(regionName)) - .profileFile(attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER)) - .profileName(attributes.getAttribute(SdkExecutionAttribute.PROFILE_NAME)) - .dualstackEnabled( - attributes.getAttribute(AwsExecutionAttribute.DUALSTACK_ENDPOINT_ENABLED)) - .fipsEnabled(attributes.getAttribute(AwsExecutionAttribute.FIPS_ENDPOINT_ENABLED)) - .build() - .clientEndpoint(); + private URI createEndpoint(String regionName, ExecutionAttributes attributes) { + return CompletableFutureUtils.joinLikeSync( + NeptuneEndpointProvider.defaultProvider() + .resolveEndpoint(p -> p.region(Region.of(regionName)) + .useDualStack(attributes.getAttribute( + AwsExecutionAttribute.DUALSTACK_ENDPOINT_ENABLED)) + .useFips(attributes.getAttribute( + AwsExecutionAttribute.FIPS_ENDPOINT_ENABLED))) + ).url(); } } diff --git a/services/rds/src/main/java/software/amazon/awssdk/services/rds/internal/RdsPresignInterceptor.java b/services/rds/src/main/java/software/amazon/awssdk/services/rds/internal/RdsPresignInterceptor.java index 3e55f5ae9f24..d6f593b16f75 100644 --- a/services/rds/src/main/java/software/amazon/awssdk/services/rds/internal/RdsPresignInterceptor.java +++ b/services/rds/src/main/java/software/amazon/awssdk/services/rds/internal/RdsPresignInterceptor.java @@ -25,9 +25,7 @@ import java.util.concurrent.CompletableFuture; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.awscore.AwsExecutionAttribute; -import software.amazon.awssdk.awscore.endpoint.AwsClientEndpointProvider; import software.amazon.awssdk.core.ClientEndpointProvider; -import software.amazon.awssdk.core.Protocol; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; @@ -35,7 +33,6 @@ import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; -import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.http.SdkHttpRequest; @@ -48,6 +45,7 @@ import software.amazon.awssdk.identity.spi.Identity; import software.amazon.awssdk.protocols.query.AwsQueryProtocolFactory; import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.rds.endpoints.RdsEndpointProvider; import software.amazon.awssdk.services.rds.model.RdsRequest; import software.amazon.awssdk.utils.CompletableFutureUtils; @@ -72,7 +70,6 @@ public abstract class RdsPresignInterceptor implements Exe .build()) .build(); - private static final String SERVICE_NAME = "rds"; private static final String PARAM_SOURCE_REGION = "SourceRegion"; private static final String PARAM_DESTINATION_REGION = "DestinationRegion"; private static final String PARAM_PRESIGNED_URL = "PreSignedUrl"; @@ -109,7 +106,7 @@ public final SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, SelectedAuthScheme selectedAuthScheme = executionAttributes.getAttribute(SELECTED_AUTH_SCHEME); String sourceRegion = presignableRequest.getSourceRegion(); String destinationRegion = selectedAuthScheme.authSchemeOption().signerProperty(AwsV4HttpSigner.REGION_NAME); - URI endpoint = createEndpoint(sourceRegion, SERVICE_NAME, executionAttributes); + URI endpoint = createEndpoint(sourceRegion, executionAttributes); SdkHttpFullRequest.Builder marshalledRequest = presignableRequest.marshall().toBuilder().uri(endpoint); SdkHttpFullRequest requestToPresign = @@ -217,17 +214,14 @@ private SdkHttpFullRequest toSdkHttpFullRequest(SignedRequest signedRequest) { .build(); } - private URI createEndpoint(String regionName, String serviceName, ExecutionAttributes attributes) { - return AwsClientEndpointProvider.builder() - .serviceEndpointPrefix(SERVICE_NAME) - .defaultProtocol(Protocol.HTTPS.toString()) - .region(Region.of(regionName)) - .profileFile(attributes.getAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER)) - .profileName(attributes.getAttribute(SdkExecutionAttribute.PROFILE_NAME)) - .dualstackEnabled( - attributes.getAttribute(AwsExecutionAttribute.DUALSTACK_ENDPOINT_ENABLED)) - .fipsEnabled(attributes.getAttribute(AwsExecutionAttribute.FIPS_ENDPOINT_ENABLED)) - .build() - .clientEndpoint(); + private URI createEndpoint(String regionName, ExecutionAttributes attributes) { + return CompletableFutureUtils.joinLikeSync( + RdsEndpointProvider.defaultProvider() + .resolveEndpoint(p -> p.region(Region.of(regionName)) + .useDualStack(attributes.getAttribute( + AwsExecutionAttribute.DUALSTACK_ENDPOINT_ENABLED)) + .useFips(attributes.getAttribute( + AwsExecutionAttribute.FIPS_ENDPOINT_ENABLED))) + ).url(); } } diff --git a/services/rds/src/test/java/software/amazon/awssdk/services/rds/internal/PresignRequestHandlerTest.java b/services/rds/src/test/java/software/amazon/awssdk/services/rds/internal/PresignRequestHandlerTest.java index fff1e649fc40..866f4b03dc58 100644 --- a/services/rds/src/test/java/software/amazon/awssdk/services/rds/internal/PresignRequestHandlerTest.java +++ b/services/rds/src/test/java/software/amazon/awssdk/services/rds/internal/PresignRequestHandlerTest.java @@ -100,6 +100,9 @@ public void testExpectations(TestCase testCase) { if (testCase.expectedUri != null) { assertEquals(normalize(URI.create(testCase.expectedUri)), normalize(presignedUrlAsUri)); } + if (testCase.expectedPresignedUrlHost != null) { + assertEquals(testCase.expectedPresignedUrlHost, presignedUrlAsUri.getHost()); + } } else { assertFalse(rawQueryParameters.containsKey("PreSignedUrl")); } @@ -169,6 +172,24 @@ public static List testCases() { builder("StartDBInstanceAutomatedBackupsReplication Without SourceRegion Does NOT Send PresignedUrl") .clientConsumer(c -> c.startDBInstanceAutomatedBackupsReplication(r -> r.kmsKeyId(TEST_KMS_KEY_ID))) .shouldContainPreSignedUrl(false) + .build(), + + builder("CopyDbClusterSnapshot - With FIPS enabled resolves to fips endpoint") + .clientConfigure(c -> c.region(Region.US_EAST_1).fipsEnabled(true)) + .clientConsumer(c -> c.copyDBClusterSnapshot(makeTestRequestBuilder() + .sourceRegion("us-west-2") + .build())) + .shouldContainPreSignedUrl(true) + .expectedPresignedUrlHost("rds-fips.us-west-2.amazonaws.com") + .build(), + + builder("CopyDbClusterSnapshot - With dualstack enabled resolves to dualstack endpoint") + .clientConfigure(c -> c.region(Region.US_EAST_1).dualstackEnabled(true)) + .clientConsumer(c -> c.copyDBClusterSnapshot(makeTestRequestBuilder() + .sourceRegion("us-west-2") + .build())) + .shouldContainPreSignedUrl(true) + .expectedPresignedUrlHost("rds.us-west-2.api.aws") .build() ); } @@ -296,6 +317,7 @@ static class TestCase { private final String expectedDestinationRegion; private final Clock signingClockOverride; private final String expectedUri; + private final String expectedPresignedUrlHost; TestCase(TestCaseBuilder builder) { this.name = Validate.notNull(builder.name, "name"); @@ -305,6 +327,7 @@ static class TestCase { this.expectedDestinationRegion = builder.expectedDestinationRegion; this.signingClockOverride = builder.signingClockOverride; this.expectedUri = builder.expectedUri; + this.expectedPresignedUrlHost = builder.expectedPresignedUrlHost; } } @@ -316,6 +339,7 @@ static class TestCaseBuilder { private String expectedDestinationRegion; private Clock signingClockOverride; private String expectedUri; + private String expectedPresignedUrlHost; private TestCaseBuilder name(String name) { this.name = name; @@ -352,6 +376,11 @@ public TestCaseBuilder expectedUri(String expectedUri) { return this; } + public TestCaseBuilder expectedPresignedUrlHost(String expectedPresignedUrlHost) { + this.expectedPresignedUrlHost = expectedPresignedUrlHost; + return this; + } + public TestCase build() { return new TestCase(this); } From e49686a86836330a081d8844c7b46e2037fbb9cd Mon Sep 17 00:00:00 2001 From: Saranya Somepalli Date: Wed, 15 Jul 2026 14:51:51 -0700 Subject: [PATCH 3/4] Remove ServiceMetadata usage from DefaultS3Presigner, S3Utilities,and DefaultPollyPresigner (#7110) * Remove ServiceMetadata usage from DefaultS3Presigner, S3Utilities, and DefaultPollyPresigner * Add tests and minor cleanup * cache endpoint at construction time in DefaultPollyPresigner * Address PR feedback * update comments --- .../presigner/DefaultPollyPresigner.java | 41 +++++++++------- .../presigner/DefaultPollyPresignerTest.java | 20 ++++++++ .../awssdk/services/s3/S3Utilities.java | 31 ++++++------ .../internal/signing/DefaultS3Presigner.java | 47 +++++++++++-------- .../awssdk/services/s3/InvalidRegionTest.java | 11 ++--- 5 files changed, 91 insertions(+), 59 deletions(-) diff --git a/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java b/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java index 0ed43bf8df63..bc42d3440d0f 100644 --- a/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java +++ b/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java @@ -26,6 +26,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.Supplier; @@ -70,6 +71,7 @@ import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain; import software.amazon.awssdk.services.polly.auth.scheme.PollyAuthSchemeProvider; +import software.amazon.awssdk.services.polly.endpoints.PollyEndpointProvider; import software.amazon.awssdk.services.polly.internal.presigner.model.transform.SynthesizeSpeechRequestMarshaller; import software.amazon.awssdk.services.polly.model.PollyRequest; import software.amazon.awssdk.services.polly.presigner.PollyPresigner; @@ -94,6 +96,7 @@ public final class DefaultPollyPresigner implements PollyPresigner { private final URI endpointOverride; private final Boolean dualstackEnabled; private final Boolean fipsEnabled; + private final URI resolvedEndpoint; private DefaultPollyPresigner(BuilderImpl builder) { this.signingClock = builder.signingClock != null ? builder.signingClock @@ -126,6 +129,7 @@ private DefaultPollyPresigner(BuilderImpl builder) { .build() .isFipsEnabled() .orElse(false); + this.resolvedEndpoint = resolveEndpoint(); } IdentityProvider credentialsProvider() { @@ -334,27 +338,28 @@ private void applyOverrideHeadersAndQueryParams(SdkHttpFullRequest.Builder httpR } private void applyEndpoint(SdkHttpFullRequest.Builder httpRequestBuilder) { - URI uri = resolveEndpoint(); - httpRequestBuilder.protocol(uri.getScheme()) - .host(uri.getHost()) - .port(uri.getPort()); + httpRequestBuilder.protocol(resolvedEndpoint.getScheme()) + .host(resolvedEndpoint.getHost()) + .port(resolvedEndpoint.getPort()); } private URI resolveEndpoint() { - return AwsClientEndpointProvider.builder() - .clientEndpointOverride(endpointOverride) - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_POLLY") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlPolly") - .serviceProfileProperty("polly") - .serviceEndpointPrefix(SERVICE_NAME) - .defaultProtocol("https") - .region(region) - .profileFile(profileFile) - .profileName(profileName) - .dualstackEnabled(dualstackEnabled) - .fipsEnabled(fipsEnabled) - .build() - .clientEndpoint(); + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .clientEndpointOverride(endpointOverride) + .serviceEndpointOverrideEnvironmentVariable( + "AWS_ENDPOINT_URL_POLLY") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlPolly") + .serviceProfileProperty("polly") + .profileFile(profileFile) + .profileName(profileName) + .resolveFromOverrides(); + + return overrideEndpoint.orElseGet(() -> CompletableFutureUtils.joinLikeSync( + PollyEndpointProvider.defaultProvider() + .resolveEndpoint(p -> p.region(region) + .useDualStack(dualstackEnabled) + .useFips(fipsEnabled)) + ).url()); } public static class BuilderImpl implements PollyPresigner.Builder { diff --git a/services/polly/src/test/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresignerTest.java b/services/polly/src/test/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresignerTest.java index a56acb3be2c6..c62aee14c089 100644 --- a/services/polly/src/test/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresignerTest.java +++ b/services/polly/src/test/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresignerTest.java @@ -191,6 +191,26 @@ void presign_includesRequestLevelQueryParams_included() { assertThat(presignedSynthesizeSpeechRequest.httpRequest().rawQueryParameters().keySet()).contains("QueryParam1"); } + @Test + void presign_noEndpointOverride_usesDefaultEndpoint() { + PollyPresigner presigner = DefaultPollyPresigner.builder() + .region(Region.US_EAST_1) + .credentialsProvider(credentialsProvider) + .build(); + + SynthesizeSpeechPresignRequest presignRequest = SynthesizeSpeechPresignRequest.builder() + .synthesizeSpeechRequest(BASIC_SYNTHESIZE_SPEECH_REQUEST) + .signatureDuration(Duration.ofHours(3)) + .build(); + + PresignedSynthesizeSpeechRequest presigned = presigner.presignSynthesizeSpeech(presignRequest); + + URL presignedUrl = presigned.url(); + assertThat(presignedUrl.getProtocol()).isEqualTo("https"); + assertThat(presignedUrl.getHost()).isEqualTo("polly.us-east-1.amazonaws.com"); + assertThat(presignedUrl.getPath()).isEqualTo("/v1/speech"); + } + @Test void presign_endpointOverriden() { PollyPresigner presigner = DefaultPollyPresigner.builder() diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java index cb3064798a99..962e1083e5f2 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java @@ -425,22 +425,25 @@ private Region resolveRegionForGetUrl(GetUrlRequest getUrlRequest) { } /** - * If endpoint is not present, construct a default endpoint using the region information. + * Resolve the client endpoint provider for request marshalling. Checks client override, + * environment variables, system properties, and profile configuration. If no override is found, + * a localhost placeholder is used — the actual endpoint is resolved by S3EndpointProvider + * during the getUrl flow. */ private ClientEndpointProvider clientEndpointProvider(URI overrideEndpoint, Region region) { - return AwsClientEndpointProvider.builder() - .clientEndpointOverride(overrideEndpoint) - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_S3") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlS3") - .serviceProfileProperty("s3") - .serviceEndpointPrefix(SERVICE_NAME) - .defaultProtocol("https") - .region(region) - .profileFile(profileFile) - .profileName(profileName) - .dualstackEnabled(s3Configuration.dualstackEnabled()) - .fipsEnabled(fipsEnabled) - .build(); + Optional resolvedOverride = AwsClientEndpointProvider.builder() + .clientEndpointOverride(overrideEndpoint) + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_S3") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlS3") + .serviceProfileProperty("s3") + .profileFile(profileFile) + .profileName(profileName) + .resolveFromOverrides(); + + return resolvedOverride + .map(uri -> ClientEndpointProvider.create(uri, true)) + // Need an endpoint to marshall but this will be overwritten later with Endpoints 2.0 resolution + .orElseGet(() -> ClientEndpointProvider.create(URI.create("https://localhost"), false)); } private URI getEndpointOverride(GetUrlRequest request) { diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java index 48e09c49a6ba..2956c00dada3 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java @@ -21,6 +21,7 @@ import static software.amazon.awssdk.utils.CollectionUtils.mergeLists; import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely; +import java.net.URI; import java.time.Clock; import java.time.Duration; import java.time.Instant; @@ -45,6 +46,7 @@ import software.amazon.awssdk.awscore.internal.defaultsmode.DefaultsModeConfiguration; import software.amazon.awssdk.awscore.presigner.PresignRequest; import software.amazon.awssdk.awscore.presigner.PresignedRequest; +import software.amazon.awssdk.core.ClientEndpointProvider; import software.amazon.awssdk.core.ClientType; import software.amazon.awssdk.core.RequestOverrideConfiguration; import software.amazon.awssdk.core.SdkBytes; @@ -53,6 +55,7 @@ import software.amazon.awssdk.core.client.builder.SdkDefaultClientBuilder; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.http.ExecutionContext; import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; @@ -251,28 +254,32 @@ private List initializeInterceptors() { * Copied from {@link AwsDefaultClientBuilder}. */ private SdkClientConfiguration createClientConfiguration() { - AwsClientEndpointProvider endpointProvider = - AwsClientEndpointProvider.builder() - .clientEndpointOverride(endpointOverride()) - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_S3") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlS3") - .serviceProfileProperty("s3") - .serviceEndpointPrefix(SERVICE_NAME) - .defaultProtocol("https") - .region(region()) - .profileFile(profileFileSupplier()) - .profileName(profileName()) - .dualstackEnabled(serviceConfiguration.dualstackEnabled()) - .fipsEnabled(fipsEnabled()) - .build(); - - // Make sure the endpoint resolver can actually resolve an endpoint, so that we fail now instead of - // when a request is made. - endpointProvider.clientEndpoint(); + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .clientEndpointOverride(endpointOverride()) + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_S3") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlS3") + .serviceProfileProperty("s3") + .profileFile(profileFileSupplier()) + .profileName(profileName()) + .resolveFromOverrides(); + + ClientEndpointProvider endpointProvider; + if (overrideEndpoint.isPresent()) { + endpointProvider = ClientEndpointProvider.create(overrideEndpoint.get(), true); + } else { + // Validate region at construction time to fail fast for invalid regions (e.g., US_EAST_1 with underscores). + URI testEndpoint = URI.create("https://s3." + region().id() + ".amazonaws.com"); + if (testEndpoint.getHost() == null) { + throw SdkClientException.create("Configured region (" + region() + ") resulted in an invalid URI: " + + testEndpoint + ". This is usually caused by an invalid region " + + "configuration."); + } + // Need an endpoint to marshall but this will be overwritten later with Endpoints 2.0 resolution + endpointProvider = ClientEndpointProvider.create(URI.create("https://localhost"), false); + } return SdkClientConfiguration.builder() - .option(SdkClientOption.CLIENT_ENDPOINT_PROVIDER, - endpointProvider) + .option(SdkClientOption.CLIENT_ENDPOINT_PROVIDER, endpointProvider) .build(); } diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/InvalidRegionTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/InvalidRegionTest.java index 4eef66bf2e22..7831a78c17eb 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/InvalidRegionTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/InvalidRegionTest.java @@ -32,9 +32,7 @@ public void invalidS3UtilitiesRegionAtClientGivesHelpfulMessage() { assertThatThrownBy(() -> utilities.getUrl(r -> r.bucket("foo").key("bar"))) .isInstanceOf(SdkClientException.class) - .hasMessageContaining("US_EAST_1") - .hasMessageContaining("region") - .hasMessageContaining("us-east-1"); + .hasMessageContaining("Invalid region"); } @Test @@ -43,9 +41,7 @@ public void invalidS3UtilitiesRegionAtRequestGivesHelpfulMessage() { assertThatThrownBy(() -> utilities.getUrl(r -> r.bucket("foo").key("bar").region(Region.of("US_WEST_2")))) .isInstanceOf(SdkClientException.class) - .hasMessageContaining("US_WEST_2") - .hasMessageContaining("region") - .hasMessageContaining("us-west-2"); + .hasMessageContaining("Invalid region"); } @Test @@ -77,7 +73,8 @@ public void invalidS3ArnRegionAtRequestGivesHelpfulMessage() { public void invalidS3PresignerRegionAtClientGivesHelpfulMessage() { assertThatThrownBy(() -> S3Presigner.builder().region(Region.of("US_EAST_1")).build()) .isInstanceOf(SdkClientException.class) - .hasMessageContaining("Configured region (US_EAST_1) and tags ([]) resulted in an invalid URI"); + .hasMessageContaining("Configured region (US_EAST_1)") + .hasMessageContaining("invalid URI"); } @Test From dacad55e0ce7b34b6145ccab7d573ce7ebd67fcf Mon Sep 17 00:00:00 2001 From: Saranya Somepalli Date: Tue, 21 Jul 2026 09:13:34 -0700 Subject: [PATCH 4/4] Remove ServiceMetadata dependency from HelpfulUnknownHostExceptionInterceptor (#7168) * Remove ServiceMetadata dependency from HelpfulUnknownHostExceptionInterceptor --- ...elpfulUnknownHostExceptionInterceptor.java | 95 ++----------------- ...ulUnknownHostExceptionInterceptorTest.java | 74 ++------------- 2 files changed, 17 insertions(+), 152 deletions(-) diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/interceptor/HelpfulUnknownHostExceptionInterceptor.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/interceptor/HelpfulUnknownHostExceptionInterceptor.java index 8a4eab2e5673..dbb4086c06ae 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/interceptor/HelpfulUnknownHostExceptionInterceptor.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/interceptor/HelpfulUnknownHostExceptionInterceptor.java @@ -16,20 +16,11 @@ package software.amazon.awssdk.awscore.interceptor; import java.net.UnknownHostException; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; import software.amazon.awssdk.annotations.SdkProtectedApi; -import software.amazon.awssdk.awscore.AwsExecutionAttribute; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; -import software.amazon.awssdk.regions.PartitionMetadata; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.regions.RegionMetadata; -import software.amazon.awssdk.regions.ServiceMetadata; -import software.amazon.awssdk.regions.ServicePartitionMetadata; /** * This interceptor will monitor for {@link UnknownHostException}s and provide the customer with additional information they can @@ -37,90 +28,20 @@ */ @SdkProtectedApi public final class HelpfulUnknownHostExceptionInterceptor implements ExecutionInterceptor { + private static final String ERROR_MESSAGE = + "Received an UnknownHostException when attempting to interact with a service. " + + "See cause for the exact endpoint that is failing to resolve. " + + "If this is happening on an endpoint that previously worked, " + + "there may be a network connectivity issue or your DNS cache " + + "could be storing endpoints for too long."; + @Override public Throwable modifyException(Context.FailedExecution context, ExecutionAttributes executionAttributes) { if (!hasCause(context.exception(), UnknownHostException.class)) { return context.exception(); } - StringBuilder error = new StringBuilder(); - error.append("Received an UnknownHostException when attempting to interact with a service. See cause for the " - + "exact endpoint that is failing to resolve. "); - - Optional globalRegionErrorDetails = getGlobalRegionErrorDetails(executionAttributes); - - if (globalRegionErrorDetails.isPresent()) { - error.append(globalRegionErrorDetails.get()); - } else { - error.append("If this is happening on an endpoint that previously worked, there may be a network connectivity " - + "issue or your DNS cache could be storing endpoints for too long."); - } - - return SdkClientException.builder().message(error.toString()).cause(context.exception()).build(); - } - - /** - * If the customer is interacting with a global service (one with a single endpoint/region for an entire partition), this - * will return error details that can instruct the customer on how to configure their client for success. - */ - private Optional getGlobalRegionErrorDetails(ExecutionAttributes executionAttributes) { - Region clientRegion = clientRegion(executionAttributes); - if (clientRegion.isGlobalRegion()) { - return Optional.empty(); - } - - List globalPartitionsForService = globalPartitionsForService(executionAttributes); - if (globalPartitionsForService.isEmpty()) { - return Optional.empty(); - } - - String clientPartition = Optional.ofNullable(clientRegion.metadata()) - .map(RegionMetadata::partition) - .map(PartitionMetadata::id) - .orElse(null); - - Optional globalRegionForClientRegion = - globalPartitionsForService.stream() - .filter(p -> p.partition().id().equals(clientPartition)) - .findAny() - .flatMap(ServicePartitionMetadata::globalRegion); - - if (!globalRegionForClientRegion.isPresent()) { - String globalRegionsForThisService = globalPartitionsForService.stream() - .map(ServicePartitionMetadata::globalRegion) - .filter(Optional::isPresent) - .map(Optional::get) - .filter(Region::isGlobalRegion) - .map(Region::id) - .collect(Collectors.joining("/")); - - return Optional.of("This specific service may be a global service, in which case you should configure a global " - + "region like " + globalRegionsForThisService + " on the client."); - } - - Region globalRegion = globalRegionForClientRegion.get(); - - return Optional.of("This specific service is global in the same partition as the region configured on this client (" - + clientRegion + "). If this is the first time you're trying to talk to this service in this region, " - + "you should try configuring the global region on your client, instead: " + globalRegion); - } - - /** - * Retrieve the region configured on the client. - */ - private Region clientRegion(ExecutionAttributes executionAttributes) { - return executionAttributes.getAttribute(AwsExecutionAttribute.AWS_REGION); - } - - /** - * Retrieve all global partitions for the AWS service that we're interacting with. - */ - private List globalPartitionsForService(ExecutionAttributes executionAttributes) { - return ServiceMetadata.of(executionAttributes.getAttribute(AwsExecutionAttribute.ENDPOINT_PREFIX)) - .servicePartitions() - .stream() - .filter(sp -> sp.globalRegion().isPresent()) - .collect(Collectors.toList()); + return SdkClientException.builder().message(ERROR_MESSAGE).cause(context.exception()).build(); } private boolean hasCause(Throwable thrown, Class cause) { diff --git a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/interceptor/HelpfulUnknownHostExceptionInterceptorTest.java b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/interceptor/HelpfulUnknownHostExceptionInterceptorTest.java index 76dc2cc3c465..686f0253b903 100644 --- a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/interceptor/HelpfulUnknownHostExceptionInterceptorTest.java +++ b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/interceptor/HelpfulUnknownHostExceptionInterceptorTest.java @@ -6,14 +6,12 @@ import java.net.UnknownHostException; import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import software.amazon.awssdk.awscore.AwsExecutionAttribute; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.interceptor.InterceptorContext; import software.amazon.awssdk.core.internal.interceptor.DefaultFailedExecutionContext; -import software.amazon.awssdk.regions.Region; public class HelpfulUnknownHostExceptionInterceptorTest { private static final ExecutionInterceptor INTERCEPTOR = new HelpfulUnknownHostExceptionInterceptor(); @@ -31,76 +29,26 @@ public void modifyException_supportsNestedUnknownHostExceptions() { exception = new IllegalArgumentException(exception); exception = new UnsupportedOperationException(exception); - assertThat(modifyException(exception, Region.AWS_GLOBAL)).isInstanceOf(SdkClientException.class); + assertThat(modifyException(exception)).isInstanceOf(SdkClientException.class); } @Test - public void modifyException_returnsGenericHelp_forGlobalRegions() { + public void modifyException_returnsNetworkHelp_forUnknownHostException() { UnknownHostException exception = new UnknownHostException(); - assertThat(modifyException(exception, Region.AWS_GLOBAL)) + assertThat(modifyException(exception)) .isInstanceOf(SdkClientException.class) .hasMessageContaining("network"); } @Test - public void modifyException_returnsGenericHelp_forUnknownServices() { - UnknownHostException exception = new UnknownHostException(); - assertThat(modifyException(exception, Region.US_EAST_1, "millems-hotdog-stand")) - .isInstanceOf(SdkClientException.class) - .satisfies(t -> doesNotHaveMessageContaining(t, "global")) - .hasMessageContaining("network"); - } - - @Test - public void modifyException_returnsGenericHelp_forUnknownServicesInUnknownRegions() { - UnknownHostException exception = new UnknownHostException(); - assertThat(modifyException(exception, Region.of("cn-north-99"), "millems-hotdog-stand")) - .isInstanceOf(SdkClientException.class) - .satisfies(t -> doesNotHaveMessageContaining(t, "global")) - .hasMessageContaining("network"); - } - - @Test - public void modifyException_returnsGenericHelp_forServicesRegionalizedInAllPartitions() { - UnknownHostException exception = new UnknownHostException(); - assertThat(modifyException(exception, Region.US_EAST_1, "dynamodb")) - .isInstanceOf(SdkClientException.class) - .satisfies(t -> doesNotHaveMessageContaining(t, "global")) - .hasMessageContaining("network"); - } - - @Test - public void modifyException_returnsGenericGlobalRegionHelp_forServicesGlobalInSomePartitionOtherThanTheClientPartition() { - UnknownHostException exception = new UnknownHostException(); - assertThat(modifyException(exception, Region.of("cn-north-99"), "iam")) - .isInstanceOf(SdkClientException.class) - .satisfies(t -> doesNotHaveMessageContaining(t, "network")) - .hasMessageContaining("aws-global") - .hasMessageContaining("aws-cn-global"); - } - - @Test - public void modifyException_returnsSpecificGlobalRegionHelp_forServicesGlobalInTheClientRegionPartition() { - UnknownHostException exception = new UnknownHostException(); - assertThat(modifyException(exception, Region.of("cn-north-1"), "iam")) - .isInstanceOf(SdkClientException.class) - .satisfies(t -> doesNotHaveMessageContaining(t, "aws-global")) - .hasMessageContaining("aws-cn-global"); - } - - private void doesNotHaveMessageContaining(Throwable throwable, String value) { - assertThat(throwable.getMessage()).doesNotContain(value); + public void modifyException_preservesOriginalExceptionAsCause() { + UnknownHostException exception = new UnknownHostException("iam.us-east-1.amazonaws.com"); + Throwable result = modifyException(exception); + assertThat(result).isInstanceOf(SdkClientException.class); + assertThat(result.getCause()).isSameAs(exception); } private Throwable modifyException(Throwable throwable) { - return modifyException(throwable, null); - } - - private Throwable modifyException(Throwable throwable, Region clientRegion) { - return modifyException(throwable, clientRegion, null); - } - - private Throwable modifyException(Throwable throwable, Region clientRegion, String serviceEndpointPrefix) { SdkRequest sdkRequest = Mockito.mock(SdkRequest.class); DefaultFailedExecutionContext context = @@ -109,10 +57,6 @@ private Throwable modifyException(Throwable throwable, Region clientRegion, Stri .exception(throwable) .build(); - ExecutionAttributes executionAttributes = - new ExecutionAttributes().putAttribute(AwsExecutionAttribute.AWS_REGION, clientRegion) - .putAttribute(AwsExecutionAttribute.ENDPOINT_PREFIX, serviceEndpointPrefix); - - return INTERCEPTOR.modifyException(context, executionAttributes); + return INTERCEPTOR.modifyException(context, new ExecutionAttributes()); } } \ No newline at end of file