From a5a04057d89fcfd3f7bf5b1874c7e579bf0b92fd Mon Sep 17 00:00:00 2001 From: Quan Zhou Date: Wed, 24 Feb 2021 10:09:46 -0800 Subject: [PATCH] Add S3 Object Lambda customizations --- .../client/handler/AwsClientHandlerUtils.java | 5 +- .../SdkInternalExecutionAttribute.java | 7 + .../awssdk/services/s3/S3Utilities.java | 8 +- .../S3AccessPointEndpointResolver.java | 44 ++- .../endpoints/S3EndpointResolverContext.java | 15 +- .../endpoints/S3EndpointResolverFactory.java | 18 +- .../S3EndpointResolverFactoryContext.java | 73 ++++ ...3ObjectLambdaOperationEndpointBuilder.java | 75 ++++ ...ObjectLambdaOperationEndpointResolver.java | 114 ++++++ .../handlers/EndpointAddressInterceptor.java | 14 +- .../resource/S3AccessPointResource.java | 6 +- .../s3/internal/resource/S3ArnConverter.java | 22 ++ .../S3ObjectLambdaEndpointBuilder.java | 118 +++++++ .../resource/S3ObjectLambdaResource.java | 204 +++++++++++ .../s3/internal/resource/S3ResourceType.java | 7 +- .../S3AccessPointEndpointResolverTest.java | 105 ++++++ .../S3EndpointResolverFactoryTest.java | 27 +- ...ectLambdaOperationEndpointBuilderTest.java | 90 +++++ ...ctLambdaOperationEndpointResolverTest.java | 253 +++++++++++++ .../EndpointAddressInterceptorTest.java | 25 ++ .../internal/resource/S3ArnConverterTest.java | 49 +++ .../S3ObjectLambdaEndpointResolutionTest.java | 331 ++++++++++++++++++ .../resource/S3ObjectLambdaResourceTest.java | 288 +++++++++++++++ .../services/ExecutionAttributesTest.java | 72 ++++ 24 files changed, 1955 insertions(+), 15 deletions(-) create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3EndpointResolverFactoryContext.java create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3ObjectLambdaOperationEndpointBuilder.java create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3ObjectLambdaOperationEndpointResolver.java create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3ObjectLambdaEndpointBuilder.java create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3ObjectLambdaResource.java create mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/S3ObjectLambdaOperationEndpointBuilderTest.java create mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/S3ObjectLambdaOperationEndpointResolverTest.java create mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/resource/S3ObjectLambdaEndpointResolutionTest.java create mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/resource/S3ObjectLambdaResourceTest.java create mode 100644 test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/ExecutionAttributesTest.java diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/handler/AwsClientHandlerUtils.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/handler/AwsClientHandlerUtils.java index 5b17daee5b06..58dceed0ec10 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/handler/AwsClientHandlerUtils.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/handler/AwsClientHandlerUtils.java @@ -34,6 +34,7 @@ import software.amazon.awssdk.core.RequestOverrideConfiguration; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SdkResponse; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.client.handler.ClientExecutionParams; @@ -95,7 +96,9 @@ static ExecutionContext .putAttribute(SdkExecutionAttribute.SERVICE_NAME, clientConfig.option(SdkClientOption.SERVICE_NAME)) .putAttribute(SdkExecutionAttribute.OPERATION_NAME, executionParams.getOperationName()) .putAttribute(SdkExecutionAttribute.CLIENT_ENDPOINT, clientConfig.option(SdkClientOption.ENDPOINT)) - .putAttribute(SdkExecutionAttribute.ENDPOINT_OVERRIDDEN, clientConfig.option(SdkClientOption.ENDPOINT_OVERRIDDEN)); + .putAttribute(SdkExecutionAttribute.ENDPOINT_OVERRIDDEN, clientConfig.option(SdkClientOption.ENDPOINT_OVERRIDDEN)) + .putAttribute(SdkInternalExecutionAttribute.DISABLE_HOST_PREFIX_INJECTION, + clientConfig.option(SdkAdvancedClientOption.DISABLE_HOST_PREFIX_INJECTION)); ExecutionInterceptorChain executionInterceptorChain = new ExecutionInterceptorChain(clientConfig.option(SdkClientOption.EXECUTION_INTERCEPTORS)); diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkInternalExecutionAttribute.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkInternalExecutionAttribute.java index 1c72ddcd9600..27e8aedbbcb0 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkInternalExecutionAttribute.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkInternalExecutionAttribute.java @@ -33,6 +33,13 @@ public final class SdkInternalExecutionAttribute extends SdkExecutionAttribute { public static final ExecutionAttribute HTTP_CHECKSUM_REQUIRED = new ExecutionAttribute<>("HttpChecksumRequired"); + /** + * Whether host prefix injection has been disbabled on the client. + * See {@link software.amazon.awssdk.core.client.config.SdkAdvancedClientOption#DISABLE_HOST_PREFIX_INJECTION} + */ + public static final ExecutionAttribute DISABLE_HOST_PREFIX_INJECTION = + new ExecutionAttribute<>("DisableHostPrefixInjection"); + private SdkInternalExecutionAttribute() { } } 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 eab97ff86895..03637b5b3c6a 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 @@ -38,6 +38,7 @@ import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.internal.endpoints.S3EndpointResolverContext; import software.amazon.awssdk.services.s3.internal.endpoints.S3EndpointResolverFactory; +import software.amazon.awssdk.services.s3.internal.endpoints.S3EndpointResolverFactoryContext; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.GetUrlRequest; import software.amazon.awssdk.utils.Validate; @@ -168,7 +169,12 @@ public URL getUrl(GetUrlRequest getUrlRequest) { .serviceConfiguration(s3Configuration) .build(); - SdkHttpRequest httpRequest = S3EndpointResolverFactory.getEndpointResolver(getObjectRequest.bucket()) + S3EndpointResolverFactoryContext resolverFactoryContext = S3EndpointResolverFactoryContext.builder() + .bucketName(getObjectRequest.bucket()) + .originalRequest(getObjectRequest) + .build(); + + SdkHttpRequest httpRequest = S3EndpointResolverFactory.getEndpointResolver(resolverFactoryContext) .applyEndpointConfiguration(resolverContext) .sdkHttpRequest(); diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3AccessPointEndpointResolver.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3AccessPointEndpointResolver.java index 69440966843c..8a7a99f27b42 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3AccessPointEndpointResolver.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3AccessPointEndpointResolver.java @@ -24,6 +24,7 @@ import static software.amazon.awssdk.services.s3.internal.endpoints.S3EndpointUtils.removeFipsIfNeeded; import java.net.URI; +import java.util.Optional; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.arns.Arn; import software.amazon.awssdk.http.SdkHttpRequest; @@ -34,6 +35,8 @@ import software.amazon.awssdk.services.s3.internal.resource.S3AccessPointBuilder; import software.amazon.awssdk.services.s3.internal.resource.S3AccessPointResource; import software.amazon.awssdk.services.s3.internal.resource.S3ArnConverter; +import software.amazon.awssdk.services.s3.internal.resource.S3ObjectLambdaEndpointBuilder; +import software.amazon.awssdk.services.s3.internal.resource.S3ObjectLambdaResource; import software.amazon.awssdk.services.s3.internal.resource.S3OutpostAccessPointBuilder; import software.amazon.awssdk.services.s3.internal.resource.S3OutpostResource; import software.amazon.awssdk.services.s3.internal.resource.S3Resource; @@ -48,6 +51,7 @@ public final class S3AccessPointEndpointResolver implements S3EndpointResolver { private static final String S3_OUTPOSTS_NAME = "s3-outposts"; + private static final String S3_OBJECT_LAMBDA_NAME = "s3-object-lambda"; private S3AccessPointEndpointResolver() { } @@ -85,8 +89,7 @@ public ConfiguredS3SdkHttpRequest applyEndpointConfiguration(S3EndpointResolverC .build(); String signingServiceModification = s3EndpointResource.parentS3Resource() - .filter(r -> r instanceof S3OutpostResource) - .map(ignore -> S3_OUTPOSTS_NAME) + .flatMap(S3AccessPointEndpointResolver::resolveSigningService) .orElse(null); return ConfiguredS3SdkHttpRequest.builder() @@ -175,6 +178,8 @@ private URI getUriForAccessPointResource(S3EndpointResolverContext context, Stri if (isOutpostAccessPoint(s3EndpointResource)) { return getOutpostAccessPointUri(context, arnRegion, clientPartitionMetadata, s3EndpointResource); + } else if (isObjectLambdaAccessPoint(s3EndpointResource)) { + return getObjectLambdaAccessPointUri(context, arnRegion, clientPartitionMetadata, s3EndpointResource); } boolean dualstackEnabled = isDualstackEnabled(context.serviceConfiguration()); @@ -196,6 +201,10 @@ private boolean isOutpostAccessPoint(S3AccessPointResource s3EndpointResource) { return s3EndpointResource.parentS3Resource().filter(r -> r instanceof S3OutpostResource).isPresent(); } + private boolean isObjectLambdaAccessPoint(S3AccessPointResource s3EndpointResource) { + return s3EndpointResource.parentS3Resource().filter(r -> r instanceof S3ObjectLambdaResource).isPresent(); + } + private URI getOutpostAccessPointUri(S3EndpointResolverContext context, String arnRegion, PartitionMetadata clientPartitionMetadata, S3AccessPointResource s3EndpointResource) { if (isDualstackEnabled(context.serviceConfiguration())) { @@ -221,4 +230,35 @@ private URI getOutpostAccessPointUri(S3EndpointResolverContext context, String a .toUri(); } + private URI getObjectLambdaAccessPointUri(S3EndpointResolverContext context, String arnRegion, + PartitionMetadata clientPartitionMetadata, + S3AccessPointResource s3EndpointResource) { + if (isDualstackEnabled(context.serviceConfiguration())) { + throw new IllegalArgumentException("An Object Lambda Access Point ARN cannot be passed as a bucket parameter to " + + "an S3 operation if the S3 client has been configured with dualstack."); + } + + return S3ObjectLambdaEndpointBuilder.create() + .endpointOverride(context.endpointOverride()) + .accountId(s3EndpointResource.accountId().get()) + .region(arnRegion) + .accessPointName(s3EndpointResource.accessPointName()) + .protocol(context.request().protocol()) + .fipsEnabled(isFipsRegion(context.region().toString())) + .dualstackEnabled(isDualstackEnabled(context.serviceConfiguration())) + .domain(clientPartitionMetadata.dnsSuffix()) + .toUri(); + } + + private static Optional resolveSigningService(S3Resource resource) { + if (resource instanceof S3OutpostResource) { + return Optional.of(S3_OUTPOSTS_NAME); + } + + if (resource instanceof S3ObjectLambdaResource) { + return Optional.of(S3_OBJECT_LAMBDA_NAME); + } + + return Optional.empty(); + } } diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3EndpointResolverContext.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3EndpointResolverContext.java index fcf26f02d3a7..7b1726a107ba 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3EndpointResolverContext.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3EndpointResolverContext.java @@ -33,6 +33,7 @@ public final class S3EndpointResolverContext { private final Region region; private final S3Configuration serviceConfiguration; private final URI endpointOverride; + private final boolean disableHostPrefixInjection; private S3EndpointResolverContext(Builder builder) { this.request = builder.request; @@ -40,6 +41,7 @@ private S3EndpointResolverContext(Builder builder) { this.region = builder.region; this.serviceConfiguration = builder.serviceConfiguration; this.endpointOverride = builder.endpointOverride; + this.disableHostPrefixInjection = builder.disableHostPrefixInjection; } public static Builder builder() { @@ -66,6 +68,9 @@ public URI endpointOverride() { return endpointOverride; } + public boolean isDisableHostPrefixInjection() { + return disableHostPrefixInjection; + } @Override public boolean equals(Object o) { @@ -80,7 +85,8 @@ public boolean equals(Object o) { Objects.equals(request, that.request) && Objects.equals(originalRequest, that.originalRequest) && Objects.equals(region, that.region) && - Objects.equals(serviceConfiguration, that.serviceConfiguration); + Objects.equals(serviceConfiguration, that.serviceConfiguration) && + Objects.equals(disableHostPrefixInjection, that.disableHostPrefixInjection); } @Override @@ -91,6 +97,7 @@ public int hashCode() { hashCode = 31 * hashCode + Objects.hashCode(region()); hashCode = 31 * hashCode + Objects.hashCode(serviceConfiguration()); hashCode = 31 * hashCode + Objects.hashCode(endpointOverride()); + hashCode = 31 * hashCode + Objects.hashCode(isDisableHostPrefixInjection()); return hashCode; } @@ -108,6 +115,7 @@ public static final class Builder { private Region region; private S3Configuration serviceConfiguration; private URI endpointOverride; + private boolean disableHostPrefixInjection; private Builder() { } @@ -137,6 +145,11 @@ public Builder endpointOverride(URI endpointOverride) { return this; } + public Builder disableHostPrefixInjection(boolean disableHostPrefixInjection) { + this.disableHostPrefixInjection = disableHostPrefixInjection; + return this; + } + public S3EndpointResolverContext build() { return new S3EndpointResolverContext(this); } diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3EndpointResolverFactory.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3EndpointResolverFactory.java index 5db130d41c3c..bc6fecc1fc01 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3EndpointResolverFactory.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3EndpointResolverFactory.java @@ -15,7 +15,10 @@ package software.amazon.awssdk.services.s3.internal.endpoints; +import java.util.Optional; import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.services.s3.model.S3Request; +import software.amazon.awssdk.services.s3.model.WriteGetObjectResponseRequest; /** * Get endpoint resolver. @@ -25,14 +28,25 @@ public final class S3EndpointResolverFactory { private static final S3EndpointResolver ACCESS_POINT_ENDPOINT_RESOLVER = S3AccessPointEndpointResolver.create(); private static final S3EndpointResolver BUCKET_ENDPOINT_RESOLVER = S3BucketEndpointResolver.create(); + private static final S3EndpointResolver OBJECT_LAMBDA_OPERATION_RESOLVER = S3ObjectLambdaOperationEndpointResolver.create(); private S3EndpointResolverFactory() { } - public static S3EndpointResolver getEndpointResolver(String bucketName) { - if (bucketName != null && S3EndpointUtils.isArn(bucketName)) { + public static S3EndpointResolver getEndpointResolver(S3EndpointResolverFactoryContext context) { + Optional bucketName = context.bucketName(); + if (bucketName.isPresent() && S3EndpointUtils.isArn(bucketName.get())) { return ACCESS_POINT_ENDPOINT_RESOLVER; } + + if (isObjectLambdaRequest(context.originalRequest())) { + return OBJECT_LAMBDA_OPERATION_RESOLVER; + } + return BUCKET_ENDPOINT_RESOLVER; } + + private static boolean isObjectLambdaRequest(S3Request request) { + return request instanceof WriteGetObjectResponseRequest; + } } diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3EndpointResolverFactoryContext.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3EndpointResolverFactoryContext.java new file mode 100644 index 000000000000..73b2fc05a520 --- /dev/null +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3EndpointResolverFactoryContext.java @@ -0,0 +1,73 @@ +/* + * 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.s3.internal.endpoints; + +import java.util.Optional; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.services.s3.model.S3Request; + +@SdkInternalApi +public final class S3EndpointResolverFactoryContext { + private final String bucketName; + private final S3Request originalRequest; + + private S3EndpointResolverFactoryContext(DefaultBuilder builder) { + this.bucketName = builder.bucketName; + this.originalRequest = builder.originalRequest; + } + + public Optional bucketName() { + return Optional.ofNullable(bucketName); + } + + public S3Request originalRequest() { + return originalRequest; + } + + public static Builder builder() { + return new DefaultBuilder(); + } + + public interface Builder { + Builder bucketName(String bucketName); + + Builder originalRequest(S3Request originalRequest); + + S3EndpointResolverFactoryContext build(); + } + + private static final class DefaultBuilder implements Builder { + private String bucketName; + private S3Request originalRequest; + + @Override + public Builder bucketName(String bucketName) { + this.bucketName = bucketName; + return this; + } + + @Override + public Builder originalRequest(S3Request originalRequest) { + this.originalRequest = originalRequest; + return this; + } + + @Override + public S3EndpointResolverFactoryContext build() { + return new S3EndpointResolverFactoryContext(this); + } + } +} diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3ObjectLambdaOperationEndpointBuilder.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3ObjectLambdaOperationEndpointBuilder.java new file mode 100644 index 000000000000..72e0426d41b6 --- /dev/null +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3ObjectLambdaOperationEndpointBuilder.java @@ -0,0 +1,75 @@ +/* + * 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.s3.internal.endpoints; + +import java.net.URI; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.utils.Validate; + +/** + * Endpoint builder for operations specific to S3 Object Lambda. + */ +@SdkInternalApi +public class S3ObjectLambdaOperationEndpointBuilder { + private String region; + private String protocol; + private String domain; + + private S3ObjectLambdaOperationEndpointBuilder() { + } + + /** + * Create a new instance of this builder class. + */ + public static S3ObjectLambdaOperationEndpointBuilder create() { + return new S3ObjectLambdaOperationEndpointBuilder(); + } + + + public S3ObjectLambdaOperationEndpointBuilder region(String region) { + this.region = region; + return this; + } + + public S3ObjectLambdaOperationEndpointBuilder protocol(String protocol) { + this.protocol = protocol; + return this; + } + + public S3ObjectLambdaOperationEndpointBuilder domain(String domain) { + this.domain = domain; + return this; + } + + /** + * Generate an endpoint URI with no path that maps to the Object Lambdas Access Point information stored in this builder. + */ + public URI toUri() { + Validate.paramNotBlank(protocol, "protocol"); + Validate.paramNotBlank(domain, "domain"); + Validate.paramNotBlank(region, "region"); + + String servicePrefix = "s3-object-lambda"; + + String uriString = String.format("%s://%s.%s.%s", + protocol, + servicePrefix, + region, + domain); + + return URI.create(uriString); + } +} \ No newline at end of file diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3ObjectLambdaOperationEndpointResolver.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3ObjectLambdaOperationEndpointResolver.java new file mode 100644 index 000000000000..573328187367 --- /dev/null +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3ObjectLambdaOperationEndpointResolver.java @@ -0,0 +1,114 @@ +/* + * 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.s3.internal.endpoints; + +import java.net.URI; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.regions.PartitionMetadata; +import software.amazon.awssdk.services.s3.S3Configuration; +import software.amazon.awssdk.services.s3.internal.ConfiguredS3SdkHttpRequest; +import software.amazon.awssdk.services.s3.model.S3Request; +import software.amazon.awssdk.services.s3.model.WriteGetObjectResponseRequest; +import software.amazon.awssdk.utils.StringUtils; + +@SdkInternalApi +public class S3ObjectLambdaOperationEndpointResolver implements S3EndpointResolver { + private static final String SIGNING_NAME = "s3-object-lambda"; + + @Override + public ConfiguredS3SdkHttpRequest applyEndpointConfiguration(S3EndpointResolverContext context) { + S3Request originalRequest = (S3Request) context.originalRequest(); + validateObjectLambdaRequest(originalRequest); + + S3Configuration configuration = context.serviceConfiguration(); + validateConfiguration(configuration); + + SdkHttpRequest updatedRequest = context.request(); + + if (context.endpointOverride() == null) { + String newHost = getUriForObjectLambdaOperation(context).getHost(); + + if (!context.isDisableHostPrefixInjection()) { + newHost = applyHostPrefix(newHost, originalRequest); + } + + updatedRequest = updatedRequest.toBuilder() + .host(newHost) + .build(); + } + + return ConfiguredS3SdkHttpRequest.builder() + .sdkHttpRequest(updatedRequest) + .signingServiceModification(SIGNING_NAME) + .build(); + } + + public static S3ObjectLambdaOperationEndpointResolver create() { + return new S3ObjectLambdaOperationEndpointResolver(); + } + + private static void validateConfiguration(S3Configuration configuration) { + validateNotAccelerateEnabled(configuration); + validateNotDualStackEnabled(configuration); + } + + private static void validateObjectLambdaRequest(SdkRequest request) { + if (!(request instanceof WriteGetObjectResponseRequest)) { + String msg = String.format("%s is not an S3 Object Lambda operation", request); + throw new IllegalArgumentException(msg); + } + } + + private static void validateNotAccelerateEnabled(S3Configuration configuration) { + if (configuration.accelerateModeEnabled()) { + throw new IllegalArgumentException("S3 Object Lambda does not support accelerate endpoints"); + } + } + + private static void validateNotDualStackEnabled(S3Configuration configuration) { + if (configuration.dualstackEnabled()) { + throw new IllegalArgumentException("S3 Object Lambda does not support dualstack endpoints"); + } + } + + private static URI getUriForObjectLambdaOperation(S3EndpointResolverContext context) { + PartitionMetadata clientPartitionMetadata = PartitionMetadata.of(context.region()); + + return S3ObjectLambdaOperationEndpointBuilder.create() + .domain(clientPartitionMetadata.dnsSuffix()) + .protocol(context.request().protocol()) + .region(context.region().id()) + .toUri(); + } + + private static String applyHostPrefix(String host, S3Request request) { + String prefix = getPrefix(request); + if (!StringUtils.isBlank(prefix)) { + return prefix + "." + host; + } + return host; + } + + private static String getPrefix(S3Request request) { + if (request instanceof WriteGetObjectResponseRequest) { + return ((WriteGetObjectResponseRequest) request).requestRoute(); + } + + throw new RuntimeException("Unable to determine prefix for request " + request); + } +} diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/handlers/EndpointAddressInterceptor.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/handlers/EndpointAddressInterceptor.java index 6fad5f0dfc33..0235b2c77757 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/handlers/EndpointAddressInterceptor.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/handlers/EndpointAddressInterceptor.java @@ -23,11 +23,14 @@ 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.core.interceptor.SdkInternalExecutionAttribute; import software.amazon.awssdk.http.SdkHttpRequest; import software.amazon.awssdk.services.s3.S3Configuration; import software.amazon.awssdk.services.s3.internal.ConfiguredS3SdkHttpRequest; import software.amazon.awssdk.services.s3.internal.endpoints.S3EndpointResolverContext; import software.amazon.awssdk.services.s3.internal.endpoints.S3EndpointResolverFactory; +import software.amazon.awssdk.services.s3.internal.endpoints.S3EndpointResolverFactoryContext; +import software.amazon.awssdk.services.s3.model.S3Request; @SdkInternalApi public final class EndpointAddressInterceptor implements ExecutionInterceptor { @@ -43,6 +46,7 @@ public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, S3Configuration serviceConfiguration = (S3Configuration) executionAttributes.getAttribute(AwsSignerExecutionAttribute.SERVICE_CONFIG); + boolean disableHostPrefixInjection = isDisableHostPrefixInjection(executionAttributes); S3EndpointResolverContext resolverContext = S3EndpointResolverContext.builder() .request(context.httpRequest()) @@ -50,10 +54,15 @@ public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, .region(executionAttributes.getAttribute(AwsExecutionAttribute.AWS_REGION)) .endpointOverride(endpointOverride) .serviceConfiguration(serviceConfiguration) + .disableHostPrefixInjection(disableHostPrefixInjection) .build(); String bucketName = context.request().getValueForField("Bucket", String.class).orElse(null); - ConfiguredS3SdkHttpRequest configuredRequest = S3EndpointResolverFactory.getEndpointResolver(bucketName) + S3EndpointResolverFactoryContext resolverFactoryContext = S3EndpointResolverFactoryContext.builder() + .bucketName(bucketName) + .originalRequest((S3Request) context.request()) + .build(); + ConfiguredS3SdkHttpRequest configuredRequest = S3EndpointResolverFactory.getEndpointResolver(resolverFactoryContext) .applyEndpointConfiguration(resolverContext); configuredRequest.signingRegionModification().ifPresent( @@ -65,4 +74,7 @@ public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, return configuredRequest.sdkHttpRequest(); } + private static boolean isDisableHostPrefixInjection(ExecutionAttributes executionAttributes) { + return Boolean.TRUE.equals(executionAttributes.getAttribute(SdkInternalExecutionAttribute.DISABLE_HOST_PREFIX_INJECTION)); + } } diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3AccessPointResource.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3AccessPointResource.java index 8c081d6988ae..154bc141a74d 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3AccessPointResource.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3AccessPointResource.java @@ -158,9 +158,11 @@ public Builder toBuilder() { } private S3Resource validateParentS3Resource(S3Resource parentS3Resource) { - if (!S3ResourceType.OUTPOST.toString().equals(parentS3Resource.type())) { + String parentResourceType = parentS3Resource.type(); + if (!S3ResourceType.OUTPOST.toString().equals(parentResourceType) + && !S3ResourceType.OBJECT_LAMBDA.toString().equals(parentResourceType)) { throw new IllegalArgumentException("Invalid 'parentS3Resource' type. An S3 access point resource must be " + - "associated with an outpost parent resource."); + "associated with an outpost or object lambda parent resource."); } return parentS3Resource; } diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3ArnConverter.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3ArnConverter.java index a1980fbc6b6f..27badea322b1 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3ArnConverter.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3ArnConverter.java @@ -32,6 +32,7 @@ public final class S3ArnConverter implements ArnConverter { private static final S3ArnConverter INSTANCE = new S3ArnConverter(); private static final Pattern OBJECT_AP_PATTERN = Pattern.compile("^([0-9a-zA-Z-]+)/object/(.*)$"); + private static final String OBJECT_LAMBDA_SERVICE = "s3-object-lambda"; private S3ArnConverter() { } @@ -135,6 +136,10 @@ private S3Resource parseS3AccessPointArn(Arn arn) { .build(); } + if (OBJECT_LAMBDA_SERVICE.equals(arn.service())) { + return parseS3ObjectLambdaAccessPointArn(arn); + } + return S3AccessPointResource.builder() .partition(arn.partition()) .region(arn.region().orElse(null)) @@ -165,6 +170,23 @@ private S3Resource parseS3OutpostAccessPointArn(Arn arn) { .build(); } + private S3Resource parseS3ObjectLambdaAccessPointArn(Arn arn) { + if (arn.resource().qualifier().isPresent()) { + throw new IllegalArgumentException("S3 object lambda access point arn shouldn't contain any sub resources."); + } + + S3ObjectLambdaResource objectLambdaResource = S3ObjectLambdaResource.builder() + .accountId(arn.accountId().orElse(null)) + .region(arn.region().orElse(null)) + .partition(arn.partition()) + .accessPointName(arn.resource().resource()) + .build(); + return S3AccessPointResource.builder() + .accessPointName(objectLambdaResource.accessPointName()) + .parentS3Resource(objectLambdaResource) + .build(); + } + private boolean isV1Arn(Arn arn) { return !arn.accountId().isPresent() && !arn.region().isPresent(); diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3ObjectLambdaEndpointBuilder.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3ObjectLambdaEndpointBuilder.java new file mode 100644 index 000000000000..c1e243903e17 --- /dev/null +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3ObjectLambdaEndpointBuilder.java @@ -0,0 +1,118 @@ +/* + * 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.s3.internal.resource; + +import static software.amazon.awssdk.utils.HostnameValidator.validateHostnameCompliant; + +import java.net.URI; +import software.amazon.awssdk.annotations.SdkInternalApi; + +/** + * This class is used to construct an endpoint for an S3 Object Lambda access point. + */ +@SdkInternalApi +public class S3ObjectLambdaEndpointBuilder { + private URI endpointOverride; + private String accessPointName; + private String region; + private String accountId; + private String protocol; + private String domain; + private Boolean fipsEnabled; + private Boolean dualstackEnabled; + + private S3ObjectLambdaEndpointBuilder() { + } + + /** + * Create a new instance of this builder class. + */ + public static S3ObjectLambdaEndpointBuilder create() { + return new S3ObjectLambdaEndpointBuilder(); + } + + /** + * The endpoint override configured on the client (null if no endpoint override was set). + */ + public S3ObjectLambdaEndpointBuilder endpointOverride(URI endpointOverride) { + this.endpointOverride = endpointOverride; + return this; + } + + public S3ObjectLambdaEndpointBuilder accessPointName(String accessPointName) { + this.accessPointName = accessPointName; + return this; + } + + public S3ObjectLambdaEndpointBuilder region(String region) { + this.region = region; + return this; + } + + public S3ObjectLambdaEndpointBuilder accountId(String accountId) { + this.accountId = accountId; + return this; + } + + public S3ObjectLambdaEndpointBuilder protocol(String protocol) { + this.protocol = protocol; + return this; + } + + public S3ObjectLambdaEndpointBuilder domain(String domain) { + this.domain = domain; + return this; + } + + public S3ObjectLambdaEndpointBuilder fipsEnabled(Boolean fipsEnabled) { + this.fipsEnabled = fipsEnabled; + return this; + } + + public S3ObjectLambdaEndpointBuilder dualstackEnabled(Boolean dualstackEnabled) { + this.dualstackEnabled = dualstackEnabled; + return this; + } + + public URI toUri() { + validateHostnameCompliant(accountId, "accountId", "object lambda ARN"); + validateHostnameCompliant(accessPointName, "accessPointName", "object lambda ARN"); + + String fipsSegment = Boolean.TRUE.equals(fipsEnabled) ? "fips-" : ""; + + String uriString; + if (endpointOverride == null) { + if (Boolean.TRUE.equals(dualstackEnabled)) { + throw new IllegalStateException("S3 Object Lambda does not support Dual stack endpoints."); + } + + uriString = String.format("%s://%s-%s.s3-object-lambda.%s%s.%s", + protocol, accessPointName, accountId, fipsSegment, region, domain); + } else { + StringBuilder uriSuffix = new StringBuilder(endpointOverride.getHost()); + if (endpointOverride.getPort() > 0) { + uriSuffix.append(":").append(endpointOverride.getPort()); + } + if (endpointOverride.getPath() != null) { + uriSuffix.append(endpointOverride.getPath()); + } + + uriString = String.format("%s://%s-%s.%s", protocol, accessPointName, accountId, uriSuffix); + } + + return URI.create(uriString); + } +} diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3ObjectLambdaResource.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3ObjectLambdaResource.java new file mode 100644 index 000000000000..5dd78775be21 --- /dev/null +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3ObjectLambdaResource.java @@ -0,0 +1,204 @@ +/* + * 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.s3.internal.resource; + +import java.util.Optional; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.utils.Validate; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +/** + * An {@link S3Resource} that represents an S3 Object Lambda resource. + */ +@SdkInternalApi +public final class S3ObjectLambdaResource + implements S3Resource, ToCopyableBuilder { + + private final String partition; + private final String region; + private final String accountId; + private final String accessPointName; + + private S3ObjectLambdaResource(Builder b) { + this.partition = Validate.paramNotBlank(b.partition, "partition"); + this.region = Validate.paramNotBlank(b.region, "region"); + this.accountId = Validate.paramNotBlank(b.accountId, "accountId"); + this.accessPointName = Validate.paramNotBlank(b.accessPointName, "accessPointName"); + } + + /** + * Get a new builder for this class. + * @return A newly initialized instance of a builder. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Gets the resource type for this object lambda. + * @return This will always return "object_lambda". + */ + @Override + public String type() { + return S3ResourceType.OBJECT_LAMBDA.toString(); + } + + /** + * Gets the AWS partition name associated with this access point (e.g.: 'aws'). + * @return the name of the partition. + */ + @Override + public Optional partition() { + return Optional.ofNullable(partition); + } + + /** + * Gets the AWS region name associated with this bucket (e.g.: 'us-east-1'). + * @return the name of the region. + */ + @Override + public Optional region() { + return Optional.ofNullable(region); + } + + /** + * Gets the AWS account ID associated with this bucket. + * @return the AWS account ID. + */ + @Override + public Optional accountId() { + return Optional.ofNullable(accountId); + } + + /** + * Gets the name of the access point. + * @return the name of the access point. + */ + public String accessPointName() { + return this.accessPointName; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + S3ObjectLambdaResource that = (S3ObjectLambdaResource) o; + + if (partition != null ? !partition.equals(that.partition) : that.partition != null) { + return false; + } + if (region != null ? !region.equals(that.region) : that.region != null) { + return false; + } + if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) { + return false; + } + return accessPointName != null ? accessPointName.equals(that.accessPointName) : that.accessPointName == null; + } + + @Override + public int hashCode() { + int result = partition != null ? partition.hashCode() : 0; + result = 31 * result + (region != null ? region.hashCode() : 0); + result = 31 * result + (accountId != null ? accountId.hashCode() : 0); + result = 31 * result + (accessPointName != null ? accessPointName.hashCode() : 0); + return result; + } + + @Override + public S3ObjectLambdaResource.Builder toBuilder() { + return builder() + .partition(partition) + .region(region) + .accountId(accountId) + .accessPointName(accessPointName); + } + + /** + * A builder for {@link S3ObjectLambdaResource} objects. + */ + public static final class Builder implements CopyableBuilder { + private String partition; + private String region; + private String accountId; + private String accessPointName; + + private Builder() { + } + + public void setPartition(String partition) { + partition(partition); + } + + /** + * The AWS partition associated with the access point. + */ + public Builder partition(String partition) { + this.partition = partition; + return this; + } + + public void setRegion(String region) { + region(region); + } + + /** + * The AWS region associated with the access point. + */ + public Builder region(String region) { + this.region = region; + return this; + } + + public void setAccountId(String accountId) { + accountId(accountId); + } + + /** + * The AWS account ID associated with the access point. + */ + public Builder accountId(String accountId) { + this.accountId = accountId; + return this; + } + + public void setAccessPointName(String accessPointName) { + accessPointName(accessPointName); + } + + /** + * The name of the S3 access point. + */ + public Builder accessPointName(String accessPointName) { + this.accessPointName = accessPointName; + return this; + } + + /** + * Builds an instance of {@link S3ObjectLambdaResource}. + */ + @Override + public S3ObjectLambdaResource build() { + return new S3ObjectLambdaResource(this); + } + } +} diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3ResourceType.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3ResourceType.java index 48bed4aae022..3500acc45e62 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3ResourceType.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/resource/S3ResourceType.java @@ -39,7 +39,12 @@ public enum S3ResourceType { /** * An outpost access point. Implemented by {@link S3OutpostResource}. */ - OUTPOST("outpost"); + OUTPOST("outpost"), + + /** + * An object lambda access point. Implemented by {@link S3ObjectLambdaResource} + */ + OBJECT_LAMBDA("object-lambda"); private final String value; diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/S3AccessPointEndpointResolverTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/S3AccessPointEndpointResolverTest.java index b37df4beea9d..b96f9896c46d 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/S3AccessPointEndpointResolverTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/S3AccessPointEndpointResolverTest.java @@ -518,6 +518,107 @@ public void outpostAccessPointArn_ArnMissingAccesspointName_throwsIllegalArgumen .hasMessageContaining("Invalid format"); } + @Test + public void objectLambdaAccessPointArn_shouldConvertEndpoint() { + verifyAccesspointArn("http", + "arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint/myol", + "http://myol-123456789012.s3-object-lambda.us-west-2.amazonaws.com", + Region.of("us-west-2"), + S3Configuration.builder(), + Region.of("us-west-2")); + + verifyAccesspointArn("https", + "arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint/myol", + "https://myol-123456789012.s3-object-lambda.us-west-2.amazonaws.com", + Region.of("us-west-2"), + S3Configuration.builder(), + Region.of("us-west-2")); + } + + @Test + public void objectLambdaAccessPointArn_futureUnknownRegion_US_correctlyInfersPartition() { + verifyAccesspointArn("http", + "arn:aws:s3-object-lambda:us-future-2:123456789012:accesspoint/myol", + "http://myol-123456789012.s3-object-lambda.us-future-2.amazonaws.com", + Region.of("us-future-2"), + S3Configuration.builder(), + Region.of("us-future-2")); + } + + @Test + public void objectLambdaAccessPointArn_futureUnknownRegion_crossRegion_correctlyInfersPartition() { + verifyAccesspointArn("http", + "arn:aws:s3-object-lambda:us-future-2:123456789012:accesspoint/myol", + "http://myol-123456789012.s3-object-lambda.us-future-2.amazonaws.com", + Region.of("us-future-2"), + S3Configuration.builder().useArnRegionEnabled(true), + Region.of("us-future-1")); + } + + @Test + public void objectLambdaAccessPointArn_futureUnknownRegion_CN_correctlyInfersPartition() { + verifyAccesspointArn("http", + "arn:aws-cn:s3-object-lambda:cn-future-1:123456789012:accesspoint/myol", + "http://myol-123456789012.s3-object-lambda.cn-future-1.amazonaws.com.cn", + Region.of("cn-future-1"), + S3Configuration.builder(), + Region.of("cn-future-1")); + } + + @Test + public void objectLambdaAccessPointArn_futureUnknownRegionAndPartition_defaultsToAws() { + verifyAccesspointArn("http", + "arn:aws:s3-object-lambda:unknown:123456789012:accesspoint/myol", + "http://myol-123456789012.s3-object-lambda.unknown.amazonaws.com", + Region.of("unknown"), + S3Configuration.builder(), + Region.of("unknown")); + } + + @Test + public void objectLambdaAccessPointArn_invalidPartition_throwsIllegalArgumentException() { + assertThatThrownBy(() -> verifyAccesspointArn("http", + "arn:bar:s3-object-lambda:us-east-1:123456789012:accesspoint/myol", + null, + S3Configuration.builder())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("bar"); + } + + @Test + public void objectLambdaAccessPointArn_differentRegionWithoutUseArnRegion_throwsIllegalArgumentException() { + assertThatThrownBy(() -> verifyAccesspointArn("http", + "arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint/myol", + null, + S3Configuration.builder())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("region"); + } + + @Test + public void objectLambdaAccessPointArn_dualStackEnabled_throwsIllegalArgumentException() { + assertThatThrownBy(() -> verifyAccesspointArn("http", + "arn:aws:s3-object-lambda:us-east-1:123456789012:accesspoint/myol", + null, + Region.of("us-east-1"), + S3Configuration.builder().dualstackEnabled(true), + Region.of("us-east-1"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("dualstack"); + } + + @Test + public void objectLambdaAccessPointArn_accelerateEnabled_throwsIllegalArgumentException() { + assertThatThrownBy(() -> verifyAccesspointArn("http", + "arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint/myol", + null, + Region.of("us-east-1"), + S3Configuration.builder().accelerateModeEnabled(true), + Region.of("us-east-1"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("accelerate"); + } + private void verifyAccesspointArn(String protocol, String accessPointArn, String expectedEndpoint, S3Configuration.Builder builder) { verifyAccesspointArn(protocol, accessPointArn, expectedEndpoint, Region.US_EAST_1, builder, Region.US_EAST_1); @@ -555,6 +656,10 @@ private void assertSigningRegion(String accessPointArn, ConfiguredS3SdkHttpReque String expectedSigningName = "s3-outposts"; assertThat(sdkHttpFullRequest.signingServiceModification()).isPresent(); assertThat(sdkHttpFullRequest.signingServiceModification().get()).isEqualTo(expectedSigningName); + } else if (accessPointArn.contains(":s3-object-lambda")) { + String expectedSigningName = "s3-object-lambda"; + assertThat(sdkHttpFullRequest.signingServiceModification()).isPresent(); + assertThat(sdkHttpFullRequest.signingServiceModification().get()).isEqualTo(expectedSigningName); } else { assertThat(sdkHttpFullRequest.signingServiceModification()).isEmpty(); } diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/S3EndpointResolverFactoryTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/S3EndpointResolverFactoryTest.java index ad5f86b8dd06..fd994df2a822 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/S3EndpointResolverFactoryTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/S3EndpointResolverFactoryTest.java @@ -18,30 +18,49 @@ import static org.assertj.core.api.Assertions.assertThat; import org.junit.Test; +import software.amazon.awssdk.services.s3.model.S3Request; +import software.amazon.awssdk.services.s3.model.WriteGetObjectResponseRequest; public class S3EndpointResolverFactoryTest { @Test public void nullBucketName_returnsBucketEndpointResolver() { - assertThat(S3EndpointResolverFactory.getEndpointResolver(null)).isInstanceOf(S3BucketEndpointResolver.class); + assertThat(S3EndpointResolverFactory.getEndpointResolver(createContext(null, null))) + .isInstanceOf(S3BucketEndpointResolver.class); } @Test public void emptyBucketName_returnsBucketEndpointResolver() { String bucketName = ""; - assertThat(S3EndpointResolverFactory.getEndpointResolver(bucketName)).isInstanceOf(S3BucketEndpointResolver.class); + assertThat(S3EndpointResolverFactory.getEndpointResolver(createContext(bucketName, null))) + .isInstanceOf(S3BucketEndpointResolver.class); } @Test public void nonAccessPointBucketName_returnsBucketEndpointResolver() { String bucketName = "test-bucket"; - assertThat(S3EndpointResolverFactory.getEndpointResolver(bucketName)).isInstanceOf(S3BucketEndpointResolver.class); + assertThat(S3EndpointResolverFactory.getEndpointResolver(createContext(bucketName, null))) + .isInstanceOf(S3BucketEndpointResolver.class); } @Test public void accessPointBucketName_returnsAccessPointEndpointResolver() { String bucketName = "arn:aws:s3:us-east-1:12345678910:accesspoint/foobar"; - assertThat(S3EndpointResolverFactory.getEndpointResolver(bucketName)).isInstanceOf(S3AccessPointEndpointResolver.class); + assertThat(S3EndpointResolverFactory.getEndpointResolver(createContext(bucketName, null))) + .isInstanceOf(S3AccessPointEndpointResolver.class); } + @Test + public void objectLambdaOperation_returnsObjectLambdaOperationResolver() { + S3Request originalRequest = WriteGetObjectResponseRequest.builder().build(); + assertThat(S3EndpointResolverFactory.getEndpointResolver(createContext(null, originalRequest))) + .isInstanceOf(S3ObjectLambdaOperationEndpointResolver.class); + } + + private static S3EndpointResolverFactoryContext createContext(String bucketName, S3Request originalRequest) { + return S3EndpointResolverFactoryContext.builder() + .bucketName(bucketName) + .originalRequest(originalRequest) + .build(); + } } diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/S3ObjectLambdaOperationEndpointBuilderTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/S3ObjectLambdaOperationEndpointBuilderTest.java new file mode 100644 index 000000000000..e90ee736b93d --- /dev/null +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/S3ObjectLambdaOperationEndpointBuilderTest.java @@ -0,0 +1,90 @@ +package software.amazon.awssdk.services.s3.internal.endpoints; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import java.net.URI; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class S3ObjectLambdaOperationEndpointBuilderTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void toUri_buildsCorrectEndpoint() { + URI endpoint = S3ObjectLambdaOperationEndpointBuilder.create() + .domain("my-domain.com") + .protocol("https") + .region("us-west-2") + .toUri(); + + assertThat(endpoint.toString(), is("https://s3-object-lambda.us-west-2.my-domain.com")); + } + + @Test + public void toUri_disallowsNullProtocol() { + thrown.expect(NullPointerException.class); + + S3ObjectLambdaOperationEndpointBuilder.create() + .domain("my-domain.com") + .protocol(null) + .region("us-west-2") + .toUri(); + } + + @Test + public void toUri_disallowsEmptyProtocol() { + thrown.expect(IllegalArgumentException.class); + + S3ObjectLambdaOperationEndpointBuilder.create() + .domain("my-domain.com") + .protocol("") + .region("us-west-2") + .toUri(); + } + + @Test + public void toUri_disallowsNullRegion() { + thrown.expect(NullPointerException.class); + + S3ObjectLambdaOperationEndpointBuilder.create() + .domain("my-domain.com") + .protocol("https") + .region(null) + .toUri(); + } + + @Test + public void toUri_disallowsEmptyRegion() { + thrown.expect(IllegalArgumentException.class); + + S3ObjectLambdaOperationEndpointBuilder.create() + .domain("my-domain.com") + .protocol("https") + .region("") + .toUri(); + } + + @Test + public void toUri_disallowsNullDomain() { + thrown.expect(NullPointerException.class); + + S3ObjectLambdaOperationEndpointBuilder.create() + .domain(null) + .protocol("https") + .region("region") + .toUri(); + } + + @Test + public void toUri_disallowsEmptyDomain() { + thrown.expect(IllegalArgumentException.class); + + S3ObjectLambdaOperationEndpointBuilder.create() + .domain("") + .protocol("https") + .region("region") + .toUri(); + } +} diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/S3ObjectLambdaOperationEndpointResolverTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/S3ObjectLambdaOperationEndpointResolverTest.java new file mode 100644 index 000000000000..16689406db95 --- /dev/null +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/endpoints/S3ObjectLambdaOperationEndpointResolverTest.java @@ -0,0 +1,253 @@ +/* + * 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.s3.internal.endpoints; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.net.URI; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.regions.ServiceMetadata; +import software.amazon.awssdk.services.s3.S3Configuration; +import software.amazon.awssdk.services.s3.internal.ConfiguredS3SdkHttpRequest; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.S3Request; +import software.amazon.awssdk.services.s3.model.WriteGetObjectResponseRequest; +import software.amazon.awssdk.services.s3.utils.InterceptorTestUtils; + +public class S3ObjectLambdaOperationEndpointResolverTest { + private S3ObjectLambdaOperationEndpointResolver endpointResolver; + + @Before + public void setUp() { + endpointResolver = S3ObjectLambdaOperationEndpointResolver.create(); + } + + @Test + public void writeGetObjectResponse_shouldConvertEndpoint() { + String requestRoute = "route"; + String region = "us-west-2"; + String expectedHost = "route.s3-object-lambda.us-west-2.amazonaws.com"; + WriteGetObjectResponseRequest request = WriteGetObjectResponseRequest.builder() + .requestRoute(requestRoute) + .build(); + + verifyObjectLambdaEndpoint("https", request, region, null, URI.create("https://" + expectedHost), S3Configuration.builder()); + verifyObjectLambdaEndpoint("http", request, region, null, URI.create("http://" + expectedHost), S3Configuration.builder()); + } + + @Test + public void writeGetObjectResponse_preservesPathAndQueryParams() { + Map> expectedQueryParams = new HashMap<>(); + expectedQueryParams.put("foo", Collections.singletonList("bar")); + String path = "/path"; + + S3EndpointResolverContext context = S3EndpointResolverContext.builder() + .originalRequest(WriteGetObjectResponseRequest.builder() + .requestRoute("route") + .build()) + .region(Region.US_EAST_1) + .serviceConfiguration(S3Configuration.builder().build()) + .request(SdkHttpRequest.builder() + .method(SdkHttpMethod.GET) + .protocol("https") + .host("route.s3.us-east-1.amazonaws.com") + .encodedPath(path) + .rawQueryParameters(expectedQueryParams) + .build()) + .build(); + + ConfiguredS3SdkHttpRequest configuredS3SdkHttpRequest = endpointResolver.applyEndpointConfiguration(context); + SdkHttpRequest httpRequest = configuredS3SdkHttpRequest.sdkHttpRequest(); + + assertThat(httpRequest.encodedPath()).isEqualTo(path); + assertThat(httpRequest.rawQueryParameters()).isEqualTo(expectedQueryParams); + } + + @Test + public void writeGetObjectResponse_nonStandardPartitionRegion_shouldConvertEndpointWithCorrectDnsSuffix() { + String requestRoute = "route"; + String region = "cn-north-1"; + String expectedHost = "route.s3-object-lambda.cn-north-1.amazonaws.com.cn"; + WriteGetObjectResponseRequest request = WriteGetObjectResponseRequest.builder() + .requestRoute(requestRoute) + .build(); + + verifyObjectLambdaEndpoint("https", request, region, null, URI.create("https://" + expectedHost), S3Configuration.builder()); + verifyObjectLambdaEndpoint("http", request, region, null, URI.create("http://" + expectedHost), S3Configuration.builder()); + } + + @Test + public void writeGetObjectResponse_endpointOverridden_shouldConvertEndpoint() { + String requestRoute = "route"; + String region = "us-west-2"; + String endpointOverride = "my-endpoint.com"; + String expectedHost = "route.my-endpoint.com"; + WriteGetObjectResponseRequest request = WriteGetObjectResponseRequest.builder() + .requestRoute(requestRoute) + .build(); + + verifyObjectLambdaEndpoint("https", request, region, URI.create("https://" + endpointOverride), URI.create("https://" + expectedHost), S3Configuration.builder()); + verifyObjectLambdaEndpoint("http", request, region, URI.create("http://" + endpointOverride), URI.create("http://" + expectedHost), S3Configuration.builder()); + } + + @Test + @Ignore // TODO: Taken from the SEP but this test case is suspect. The SEP + // expects s3-external-1 to be translated to the us-east-1 region for this + // operation, which is weird. Currently, we do not do this. 's3-external-1' + // doesn't exist in endpoints.json, so we use heuristics to get the + // endpoint s3.s3-external-1.amazonaws.com. + public void writeGetObjectResponse_regionIsS3External_shouldConvertEndpoint() { + String requestRoute = "route"; + String region = "s3-external-1"; + String expectedHost = "route.s3-object-lambda.us-east-1.amazonaws.com"; + WriteGetObjectResponseRequest request = WriteGetObjectResponseRequest.builder() + .requestRoute(requestRoute) + .build(); + + verifyObjectLambdaEndpoint("https", request, region, null, URI.create("https://" + expectedHost), S3Configuration.builder()); + verifyObjectLambdaEndpoint("http", request, region, null, URI.create("http://" + expectedHost), S3Configuration.builder()); + } + + @Test + @Ignore // TODO: Taken from the SEP but this test case is suspect. The SEP + // expects aws-global to be translated to the us-east-1 region for this + // operation, which is weird. We do not do this; we lookup the endpoint + // from the region metadata which maps aws-global => s3.amazonaws.com, so + // the resulting endpoint is s3-object-lambda.amazonaws.com + public void writeGetObjectResponse_regionIsAwsGlobal_shouldConvertEndpoint() { + String requestRoute = "route"; + String region = "aws-global"; + String expectedHost = "route.s3-object-lambda.us-east-1.amazonaws.com"; + WriteGetObjectResponseRequest request = WriteGetObjectResponseRequest.builder() + .requestRoute(requestRoute) + .build(); + + verifyObjectLambdaEndpoint("https", request, region, null, URI.create("https://" + expectedHost), S3Configuration.builder()); + verifyObjectLambdaEndpoint("http", request, region, null, URI.create("http://" + expectedHost), S3Configuration.builder()); + } + + @Test + @Ignore // SDK doesn't resolve fips endpoints correctly + public void writeGetObjectResponse_regionIsFipsInPrefix_shouldConvertEndpoint() { + String requestRoute = "route"; + String region = "fips-us-gov-east-1"; + String expectedHost = "route.s3-object-lambda-fips.us-gov-east-1.amazonaws.com"; + WriteGetObjectResponseRequest request = WriteGetObjectResponseRequest.builder() + .requestRoute(requestRoute) + .build(); + + verifyObjectLambdaEndpoint("https", request, region, null, URI.create("https://" + expectedHost), S3Configuration.builder()); + verifyObjectLambdaEndpoint("http", request, region, null, URI.create("http://" + expectedHost), S3Configuration.builder()); + } + + @Test + @Ignore // SDK doesn't resolve fips endpoints correctly + public void writeGetObjectResponse_regionIsFipsInSuffix_shouldConvertEndpoint() { + String requestRoute = "route"; + String region = "us-gov-east-1-fips"; + String expectedHost = "route.s3-object-lambda-fips.us-gov-east-1.amazonaws.com"; + WriteGetObjectResponseRequest request = WriteGetObjectResponseRequest.builder() + .requestRoute(requestRoute) + .build(); + + verifyObjectLambdaEndpoint("https", request, region, null, URI.create("https://" + expectedHost), S3Configuration.builder()); + verifyObjectLambdaEndpoint("http", request, region, null, URI.create("http://" + expectedHost), S3Configuration.builder()); + } + + @Test + public void nonObjectLambdaRequest_throws() { + S3EndpointResolverContext context = S3EndpointResolverContext.builder() + .originalRequest(PutObjectRequest.builder().build()) + .region(Region.US_EAST_1) + .serviceConfiguration(S3Configuration.builder().build()) + .request(InterceptorTestUtils.sdkHttpRequest(URI.create("https://bucket.my-custom-domain.com"))) + .build(); + + assertThatThrownBy(() -> endpointResolver.applyEndpointConfiguration(context)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("is not an S3 Object Lambda operation"); + } + + @Test + public void objectLambdaOperation_dualStackEnabled_throws() { + S3EndpointResolverContext context = S3EndpointResolverContext.builder() + .originalRequest(WriteGetObjectResponseRequest.builder().build()) + .region(Region.US_EAST_1) + .serviceConfiguration(S3Configuration.builder().dualstackEnabled(true).build()) + .request(InterceptorTestUtils.sdkHttpRequest(URI.create("https://bucket.my-custom-domain.com"))) + .build(); + + assertThatThrownBy(() -> endpointResolver.applyEndpointConfiguration(context)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Object Lambda does not support dualstack"); + } + + @Test + public void objectLambdaOperation_accelerateEnabled_throws() { + S3EndpointResolverContext context = S3EndpointResolverContext.builder() + .originalRequest(WriteGetObjectResponseRequest.builder().build()) + .region(Region.US_EAST_1) + .serviceConfiguration(S3Configuration.builder().accelerateModeEnabled(true).build()) + .request(InterceptorTestUtils.sdkHttpRequest(URI.create("https://bucket.my-custom-domain.com"))) + .build(); + + assertThatThrownBy(() -> endpointResolver.applyEndpointConfiguration(context)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Object Lambda does not support accelerate"); + } + + private void verifyObjectLambdaEndpoint(String protocol, S3Request request, String region, URI endpointOverride, + URI expectedEndpoint, S3Configuration.Builder serviceConfigurationBuilder) { + URI defaultRequestEndpoint = ServiceMetadata.of("s3").endpointFor(Region.of(region)); + + String prefix = ""; + if (request instanceof WriteGetObjectResponseRequest) { + prefix = ((WriteGetObjectResponseRequest) request).requestRoute() + "."; + } + + defaultRequestEndpoint = URI.create(protocol + "://" + prefix + defaultRequestEndpoint.toString()); + + SdkHttpRequest httpRequest; + if (endpointOverride != null) { + endpointOverride = URI.create(protocol + "://" + prefix + endpointOverride.getHost()); + httpRequest = InterceptorTestUtils.sdkHttpRequest(endpointOverride); + } else { + httpRequest = InterceptorTestUtils.sdkHttpRequest(defaultRequestEndpoint); + } + + S3EndpointResolverContext context = S3EndpointResolverContext.builder() + .endpointOverride(endpointOverride) + .originalRequest(request) + .region(Region.of(region)) + .serviceConfiguration(serviceConfigurationBuilder.build()) + .request(httpRequest) + .build(); + + ConfiguredS3SdkHttpRequest configuredS3SdkHttpRequest = endpointResolver.applyEndpointConfiguration(context); + assertThat(configuredS3SdkHttpRequest.signingServiceModification().get()).isEqualTo("s3-object-lambda"); + assertThat(configuredS3SdkHttpRequest.signingRegionModification()).isEmpty(); + assertThat(configuredS3SdkHttpRequest.sdkHttpRequest().getUri()).isEqualTo(expectedEndpoint); + } +} diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/handlers/EndpointAddressInterceptorTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/handlers/EndpointAddressInterceptorTest.java index c6c20fb17057..97cdcd26d08b 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/handlers/EndpointAddressInterceptorTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/handlers/EndpointAddressInterceptorTest.java @@ -39,9 +39,11 @@ public class EndpointAddressInterceptorTest { private static final String AP_ARN = "arn:aws:s3:us-west-2:123456789012:accesspoint:foobar"; private static final String OUTPOSTS_ARN = "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456" + ":accesspoint:myaccesspoint"; + private static final String OBJECT_LAMBDA_ARN = "arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint/myol"; private static final String KEY = "test-key"; private static final String DEFAULT_SIGNING_NAME = "s3"; private static final String OUTPOSTS_SIGNING_NAME = "s3-outposts"; + private static final String OBJECT_LAMBDA_SIGNING_NAME = "s3-object-lambda"; private static final Region DEFAULT_REGION = Region.US_WEST_2; private EndpointAddressInterceptor interceptor; @@ -85,6 +87,29 @@ public void outpostAccessPointArn_crossRegion_ArnRegionEnabled_correctlyInfersPa assertThat(executionAttributes.getAttribute(SERVICE_SIGNING_NAME)).isEqualTo(OUTPOSTS_SIGNING_NAME); } + @Test + public void objectLambdaAccessPointArn_sameRegion_shouldRegion() { + ExecutionAttributes executionAttributes = createExecutionAttributes(S3Configuration.builder(), DEFAULT_REGION); + SdkHttpRequest sdkHttpFullRequest = interceptor.modifyHttpRequest(createContext(OBJECT_LAMBDA_ARN), executionAttributes); + + String expectedEndpoint = "http://myol-123456789012.s3-object-lambda.us-west-2.amazonaws.com"; + assertThat(sdkHttpFullRequest.getUri()).isEqualTo(uri(expectedEndpoint)); + assertThat(executionAttributes.getAttribute(SIGNING_REGION)).isEqualTo(Region.US_WEST_2); + assertThat(executionAttributes.getAttribute(SERVICE_SIGNING_NAME)).isEqualTo(OBJECT_LAMBDA_SIGNING_NAME); + } + + @Test + public void objectLambdaAccessPointArn_crossRegion_ArnRegionEnabled_correctlyInfersPartition() { + ExecutionAttributes executionAttributes = createExecutionAttributes(S3Configuration.builder().useArnRegionEnabled(true), + Region.US_EAST_1); + SdkHttpRequest sdkHttpFullRequest = interceptor.modifyHttpRequest(createContext(OBJECT_LAMBDA_ARN), executionAttributes); + + String expectedEndpoint = "http://myol-123456789012.s3-object-lambda.us-west-2.amazonaws.com"; + assertThat(sdkHttpFullRequest.getUri()).isEqualTo(uri(expectedEndpoint)); + assertThat(executionAttributes.getAttribute(SIGNING_REGION)).isEqualTo(Region.US_WEST_2); + assertThat(executionAttributes.getAttribute(SERVICE_SIGNING_NAME)).isEqualTo(OBJECT_LAMBDA_SIGNING_NAME); + } + private Context.ModifyHttpRequest createContext(String accessPointArn) { URI customUri = URI.create(String.format("http://s3-test.com/%s/%s", urlEncode(accessPointArn), KEY)); PutObjectRequest request = PutObjectRequest.builder().bucket(accessPointArn).key(KEY).build(); diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/resource/S3ArnConverterTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/resource/S3ArnConverterTest.java index 323bc3c945ef..14626584b210 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/resource/S3ArnConverterTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/resource/S3ArnConverterTest.java @@ -352,4 +352,53 @@ public void parseArn_malformedOutpostArn_shouldThrowException() { .resource("outpost:1:accesspoin1:1") .build()); } + + @Test + public void parseArn_objectLambda_slash() { + Arn arn = Arn.fromString("arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint/my-lambda"); + + S3Resource resource = S3_ARN_PARSER.convertArn(arn); + + assertThat(resource.type(), is("accesspoint")); + S3ObjectLambdaResource objectLambdaResource = (S3ObjectLambdaResource) resource.parentS3Resource().get(); + + assertThat(objectLambdaResource.type(), is("object-lambda")); + + assertThat(objectLambdaResource.region().get(), is("us-west-2")); + assertThat(objectLambdaResource.partition().get(), is("aws")); + assertThat(objectLambdaResource.accountId().get(), is("123456789012")); + assertThat(objectLambdaResource.accessPointName(), is("my-lambda")); + } + + @Test + public void parseArn_objectLambda_colon() { + Arn arn = Arn.fromString("arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint:my-lambda"); + + S3Resource resource = S3_ARN_PARSER.convertArn(arn); + + assertThat(resource.type(), is("accesspoint")); + S3ObjectLambdaResource objectLambdaResource = (S3ObjectLambdaResource) resource.parentS3Resource().get(); + assertThat(objectLambdaResource.type(), is("object-lambda")); + + assertThat(objectLambdaResource.region().get(), is("us-west-2")); + assertThat(objectLambdaResource.partition().get(), is("aws")); + assertThat(objectLambdaResource.accountId().get(), is("123456789012")); + assertThat(objectLambdaResource.accessPointName(), is("my-lambda")); + } + + @Test + public void parseArn_objectLambda_noName_slash() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("resource must not be blank or empty"); + + S3_ARN_PARSER.convertArn(Arn.fromString("arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint/")); + } + + @Test + public void parseArn_objectLambda_noName_colon() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("resource must not be blank or empty"); + + S3_ARN_PARSER.convertArn(Arn.fromString("arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint:")); + } } \ No newline at end of file diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/resource/S3ObjectLambdaEndpointResolutionTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/resource/S3ObjectLambdaEndpointResolutionTest.java new file mode 100644 index 000000000000..847be3701d4d --- /dev/null +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/resource/S3ObjectLambdaEndpointResolutionTest.java @@ -0,0 +1,331 @@ +/* + * 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.s3.internal.resource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static software.amazon.awssdk.services.s3.S3MockUtils.mockListObjectsResponse; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import org.junit.Before; +import org.junit.Test; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3ClientBuilder; +import software.amazon.awssdk.services.s3.S3Configuration; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient; + +public class S3ObjectLambdaEndpointResolutionTest { + + private MockSyncHttpClient mockHttpClient; + + @Before + public void setup() throws UnsupportedEncodingException { + mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse(mockListObjectsResponse()); + } + + // Invalid endpoints tests + + @Test + public void objectLambdaArn_crossRegionArn_throwsIllegalArgumentException() { + S3Client s3Client = clientBuilder().build(); + String objectLambdaArn = "arn:aws:s3-object-lambda:us-east-1:123456789012:accesspoint/myol"; + + assertThatThrownBy(() -> s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("does not match the region the client was configured with"); + + } + + @Test + public void objectLambdaArn_dualstackEnabled_throwsIllegalArgumentException() { + S3Client s3Client = clientBuilder().serviceConfiguration(S3Configuration.builder() + .dualstackEnabled(true) + .useArnRegionEnabled(true) + .build()).build(); + String objectLambdaArn = "arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint/myol"; + + assertThatThrownBy(() -> s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("dualstack"); + } + + @Test + public void objectLambdaArn_crossPartition_throwsIllegalArgumentException() { + S3Client s3Client = clientBuilder().serviceConfiguration(S3Configuration.builder() + .useArnRegionEnabled(true) + .build()).build(); + String objectLambdaArn = "arn:aws-cn:s3-object-lambda:cn-north-1:123456789012:accesspoint/myol"; + + assertThatThrownBy(() -> s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("does not match the partition the S3 client has been configured with"); + } + + @Test + public void objectLambdaArn_accelerateEnabled_throwsIllegalArgumentException() { + S3Client s3Client = clientBuilder().serviceConfiguration(S3Configuration.builder() + .accelerateModeEnabled(true) + .build()).build(); + String objectLambdaArn = "arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint/myol"; + + assertThatThrownBy(() -> s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("accelerate"); + } + + @Test + public void objectLambdaArn_nonS3Arn_throwsIllegalArgumentException() { + S3Client s3Client = clientBuilder().build(); + String objectLambdaArn = "arn:aws:sqs:us-west-2:123456789012:someresource"; + + assertThatThrownBy(() -> s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Unknown ARN type"); + } + + @Test + public void objectLambdaArn_nonAccessPointArn_throwsIllegalArgumentException() { + S3Client s3Client = clientBuilder().build(); + String objectLambdaArn = "arn:aws:s3-object-lambda:us-west-2:123456789012:bucket_name:mybucket"; + + assertThatThrownBy(() -> s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("does not appear to be a valid S3 access point ARN"); + } + + @Test + public void objectLambdaArn_missingRegion_throwsIllegalArgumentException() { + S3Client s3Client = clientBuilder().build(); + String objectLambdaArn = "arn:aws:s3-object-lambda::123456789012:accesspoint/myol"; + + assertThatThrownBy(() -> s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("region must not be blank or empty"); + } + + @Test + public void objectLambdaArn_missingAccountId_throwsIllegalArgumentException() { + S3Client s3Client = clientBuilder().build(); + String objectLambdaArn = "arn:aws:s3-object-lambda:us-west-2::accesspoint/myol"; + + assertThatThrownBy(() -> s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("accountId must not be blank or empty"); + } + + @Test + public void objectLambdaArn_accoutIdContainsInvalidCharacters_throwsIllegalArgumentException() { + S3Client s3Client = clientBuilder().build(); + String objectLambdaArn = "arn:aws:s3-object-lambda:us-west-2:123.45678.9012:accesspoint:mybucket"; + + assertThatThrownBy(() -> s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("component must only contain alphanumeric characters and dashes"); + } + + @Test + public void objectLambdaArn_missingAccessPointName_throwsIllegalArgumentException() { + S3Client s3Client = clientBuilder().build(); + String objectLambdaArn = "arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint"; + + assertThatThrownBy(() -> s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Unknown ARN type"); + } + + @Test + public void objectLambdaArn_accessPointNameContainsInvalidCharacters_star_throwsIllegalArgumentException() { + S3Client s3Client = clientBuilder().build(); + String objectLambdaArn = "arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint:*"; + + assertThatThrownBy(() -> s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("component must only contain alphanumeric characters and dashes"); + } + + @Test + public void objectLambdaArn_accessPointNameContainsInvalidCharacters_dot_throwsIllegalArgumentException() { + S3Client s3Client = clientBuilder().build(); + String objectLambdaArn = "arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint:my.bucket"; + + assertThatThrownBy(() -> s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("component must only contain alphanumeric characters and dashes"); + } + + @Test + public void objectLambdaArn_accessPointNameContainsSubResources_throwsIllegalArgumentException() { + S3Client s3Client = clientBuilder().build(); + String objectLambdaArn = "arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint:mybucket:object:foo"; + + assertThatThrownBy(() -> s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("S3 object lambda access point arn shouldn't contain any sub resources"); + } + + // Valid endpoint tests + + @Test + public void objectLambdaArn_normalCase_slashDelimiter_resolveEndpointCorrectly() { + URI expectedEndpoint = URI.create("myol-123456789012.s3-object-lambda.us-west-2.amazonaws.com"); + S3Client s3Client = clientBuilder().build(); + String objectLambdaArn = "arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint/myol"; + + s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build()); + assertEndpointMatches(mockHttpClient.getLastRequest(), expectedEndpoint); + } + + @Test + public void objectLambdaArn_normalCase_colonDelimiter_resolveEndpointCorrectly() { + URI expectedEndpoint = URI.create("myol-123456789012.s3-object-lambda.us-west-2.amazonaws.com"); + S3Client s3Client = clientBuilder().build(); + String objectLambdaArn = "arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint:myol"; + + s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build()); + assertEndpointMatches(mockHttpClient.getLastRequest(), expectedEndpoint); + } + + @Test + public void objectLambdaArn_crossRegion_useArnRegionTrue_resolveEndpointCorrectly() { + URI expectedEndpoint = URI.create("myol-123456789012.s3-object-lambda.us-east-1.amazonaws.com"); + S3Client s3Client = clientBuilder().serviceConfiguration(S3Configuration.builder() + .useArnRegionEnabled(true) + .build()).build(); + String objectLambdaArn = "arn:aws:s3-object-lambda:us-east-1:123456789012:accesspoint/myol"; + + s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build()); + assertEndpointMatches(mockHttpClient.getLastRequest(), expectedEndpoint); + } + + @Test + public void objectLambdaArn_externalRegion_useArnRegionTrue_resolveEndpointCorrectly() { + URI expectedEndpoint = URI.create("myol-123456789012.s3-object-lambda.us-east-1.amazonaws.com"); + S3Client s3Client = clientBuilder().region(Region.of("s3-external-1")) + .serviceConfiguration(S3Configuration.builder() + .useArnRegionEnabled(true) + .build()) + .build(); + String objectLambdaArn = "arn:aws:s3-object-lambda:us-east-1:123456789012:accesspoint/myol"; + + s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build()); + assertEndpointMatches(mockHttpClient.getLastRequest(), expectedEndpoint); + } + + @Test + public void objectLambdaArn_globalRegion_useArnRegionTrue_resolveEndpointCorrectly() { + URI expectedEndpoint = URI.create("myol-123456789012.s3-object-lambda.us-east-1.amazonaws.com"); + S3Client s3Client = clientBuilder().region(Region.of("aws-global")) + .serviceConfiguration(S3Configuration.builder() + .useArnRegionEnabled(true) + .build()) + .build(); + String objectLambdaArn = "arn:aws:s3-object-lambda:us-east-1:123456789012:accesspoint/myol"; + + s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build()); + assertEndpointMatches(mockHttpClient.getLastRequest(), expectedEndpoint); + } + + @Test + public void objectLambdaArn_cnPartitionSameRegion_useArnRegionTrue_resolveEndpointCorrectly() { + URI expectedEndpoint = URI.create("myol-123456789012.s3-object-lambda.cn-north-1.amazonaws.com.cn"); + S3Client s3Client = clientBuilder().region(Region.of("cn-north-1")) + .serviceConfiguration(S3Configuration.builder() + .useArnRegionEnabled(true) + .build()) + .build(); + String objectLambdaArn = "arn:aws-cn:s3-object-lambda:cn-north-1:123456789012:accesspoint/myol"; + + s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build()); + assertEndpointMatches(mockHttpClient.getLastRequest(), expectedEndpoint); + } + + @Test + public void objectLambdaArn_cnPartitionSameRegion_useArnRegionFalse_resolveEndpointCorrectly() { + URI expectedEndpoint = URI.create("myol-123456789012.s3-object-lambda.cn-north-1.amazonaws.com.cn"); + S3Client s3Client = clientBuilder().region(Region.of("cn-north-1")).build(); + String objectLambdaArn = "arn:aws-cn:s3-object-lambda:cn-north-1:123456789012:accesspoint/myol"; + + s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build()); + assertEndpointMatches(mockHttpClient.getLastRequest(), expectedEndpoint); + } + + @Test + public void objectLambdaArn_cnPartitionCrossRegion_useArnRegionTrue_resolveEndpointCorrectly() { + URI expectedEndpoint = URI.create("myol-123456789012.s3-object-lambda.cn-northwest-1.amazonaws.com.cn"); + S3Client s3Client = clientBuilder().region(Region.of("cn-north-1")) + .serviceConfiguration(S3Configuration.builder() + .useArnRegionEnabled(true) + .build()) + .build(); + String objectLambdaArn = "arn:aws-cn:s3-object-lambda:cn-northwest-1:123456789012:accesspoint/myol"; + + s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build()); + assertEndpointMatches(mockHttpClient.getLastRequest(), expectedEndpoint); + } + + @Test + public void objectLambdaArn_govRegion_useArnRegionTrue_resolveEndpointCorrectly() { + URI expectedEndpoint = URI.create("myol-123456789012.s3-object-lambda.us-gov-east-1.amazonaws.com"); + S3Client s3Client = clientBuilder().region(Region.of("us-gov-east-1")) + .serviceConfiguration(S3Configuration.builder() + .useArnRegionEnabled(true) + .build()) + .build(); + String objectLambdaArn = "arn:aws-us-gov:s3-object-lambda:us-gov-east-1:123456789012:accesspoint/myol"; + + s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build()); + assertEndpointMatches(mockHttpClient.getLastRequest(), expectedEndpoint); + } + + @Test + public void objectLambdaArn_customizeEndpoint_resolveEndpointCorrectly() throws Exception { + URI customEndpoint = URI.create("myol-123456789012.my-endpoint.com"); + S3Client s3Client = clientBuilder().endpointOverride(URI.create("http://my-endpoint.com")).build(); + String objectLambdaArn = "arn:aws:s3-object-lambda:us-west-2:123456789012:accesspoint/myol"; + + s3Client.getObject(GetObjectRequest.builder().bucket(objectLambdaArn).key("obj").build()); + assertEndpointMatches(mockHttpClient.getLastRequest(), customEndpoint); + } + + /** + * Assert that the provided request would have gone to the given endpoint. + * + * @param capturedRequest Request captured by mock HTTP client. + * @param endpoint Expected endpoint. + */ + private void assertEndpointMatches(SdkHttpRequest capturedRequest, URI endpoint) { + assertThat(capturedRequest.host()).isEqualTo(endpoint.toString()); + } + + /** + * @return Client builder instance preconfigured with credentials and region using the {@link #mockHttpClient} for transport. + */ + private S3ClientBuilder clientBuilder() { + return S3Client.builder() + .credentialsProvider(StaticCredentialsProvider + .create(AwsBasicCredentials.create("akid", "skid"))) + .region(Region.US_WEST_2) + .httpClient(mockHttpClient); + } + +} diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/resource/S3ObjectLambdaResourceTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/resource/S3ObjectLambdaResourceTest.java new file mode 100644 index 000000000000..12f972a20bb2 --- /dev/null +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/resource/S3ObjectLambdaResourceTest.java @@ -0,0 +1,288 @@ +/* + * 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.s3.internal.resource; + + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +/** + * Tests for {@link S3ObjectLambdaResource} + */ +public class S3ObjectLambdaResourceTest { + private static final String PARTITION = "partition"; + private static final String REGION = "region"; + private static final String ACCOUNT_ID = "123456789012"; + private static final String ACCESS_POINT_NAME = "object-lambda"; + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Test + public void build_allPropertiesSet_returnedFromObject() { + S3ObjectLambdaResource accessPoint = S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region(REGION) + .accountId(ACCOUNT_ID) + .accessPointName(ACCESS_POINT_NAME) + .build(); + assertThat(accessPoint.partition().get(), equalTo(PARTITION)); + assertThat(accessPoint.region().get(), equalTo(REGION)); + assertThat(accessPoint.accountId().get(), equalTo(ACCOUNT_ID)); + assertThat(accessPoint.accessPointName(), equalTo(ACCESS_POINT_NAME)); + } + + @Test + public void build_noPartitionSet_throwsNullPointerException() { + exception.expect(NullPointerException.class); + exception.expectMessage("partition"); + + S3ObjectLambdaResource.builder() + .region(REGION) + .accountId(ACCOUNT_ID) + .accessPointName(ACCESS_POINT_NAME) + .build(); + } + + @Test + public void build_noRegionSet_throwsNullPointerException() { + exception.expect(NullPointerException.class); + exception.expectMessage("region"); + + S3ObjectLambdaResource.builder() + .partition(PARTITION) + .accountId(ACCOUNT_ID) + .accessPointName(ACCESS_POINT_NAME) + .build(); + } + + @Test + public void build_noAccountIdSet_throwsNullPointerException() { + exception.expect(NullPointerException.class); + exception.expectMessage("account"); + + S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region(REGION) + .accessPointName(ACCESS_POINT_NAME) + .build(); + } + + @Test + public void build_noAccessPointNameSet_throwsNullPointerException() { + exception.expect(NullPointerException.class); + exception.expectMessage("accessPointName"); + + S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region(REGION) + .accountId(ACCOUNT_ID) + .build(); + } + + @Test + public void build_noPartitionSet_throwsIllegalArgumentException() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("partition"); + + S3ObjectLambdaResource.builder() + .partition("") + .region(REGION) + .accountId(ACCOUNT_ID) + .accessPointName(ACCESS_POINT_NAME) + .build(); + } + + @Test + public void build_noRegionSet_throwsIllegalArgumentException() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("region"); + + S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region("") + .accountId(ACCOUNT_ID) + .accessPointName(ACCESS_POINT_NAME) + .build(); + } + + @Test + public void build_noAccountIdSet_throwsIllegalArgumentException() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("account"); + + S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region(REGION) + .accountId("") + .accessPointName(ACCESS_POINT_NAME) + .build(); + } + + @Test + public void build_noAccessPointNameSet_throwsIllegalArgumentException() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("accessPointName"); + + S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region(REGION) + .accountId(ACCOUNT_ID) + .accessPointName("") + .build(); + } + + @Test + public void hashCode_sameValues_equal() { + S3ObjectLambdaResource resource1 = S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region(REGION) + .accountId(ACCOUNT_ID) + .accessPointName(ACCESS_POINT_NAME) + .build(); + + S3ObjectLambdaResource resource2 = S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region(REGION) + .accountId(ACCOUNT_ID) + .accessPointName(ACCESS_POINT_NAME) + .build(); + + assertThat(resource1.hashCode(), equalTo(resource2.hashCode())); + } + + @Test + public void hashCode_incorporatesAllMembers_notEqual() { + String dummy = "foo"; + + S3ObjectLambdaResource accessPoint = S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region(REGION) + .accountId(ACCOUNT_ID) + .accessPointName(ACCESS_POINT_NAME) + .build(); + + S3ObjectLambdaResource partitionDifferent = S3ObjectLambdaResource.builder() + .partition(dummy) + .region(REGION) + .accountId(ACCOUNT_ID) + .accessPointName(ACCESS_POINT_NAME) + .build(); + + assertThat(accessPoint.hashCode(), not(equalTo(partitionDifferent.hashCode()))); + + S3ObjectLambdaResource regionDifferent = S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region(dummy) + .accountId(ACCOUNT_ID) + .accessPointName(ACCESS_POINT_NAME) + .build(); + + assertThat(accessPoint.hashCode(), not(equalTo(regionDifferent.hashCode()))); + + S3ObjectLambdaResource accountDifferent = S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region(REGION) + .accountId(dummy) + .accessPointName(ACCESS_POINT_NAME) + .build(); + + assertThat(accessPoint.hashCode(), not(equalTo(accountDifferent.hashCode()))); + + S3ObjectLambdaResource accessPointNameDifferent = S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region(REGION) + .accountId(ACCOUNT_ID) + .accessPointName(dummy) + .build(); + + assertThat(accessPoint.hashCode(), not(equalTo(accessPointNameDifferent.hashCode()))); + } + + @Test + public void equals_sameValues_isTrue() { + S3ObjectLambdaResource resource1 = S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region(REGION) + .accountId(ACCOUNT_ID) + .accessPointName(ACCESS_POINT_NAME) + .build(); + + S3ObjectLambdaResource resource2 = S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region(REGION) + .accountId(ACCOUNT_ID) + .accessPointName(ACCESS_POINT_NAME) + .build(); + + assertThat(resource1.equals(resource2), is(true)); + } + + @Test + public void equals_incorporatesAllMembers_isFalse() { + String dummy = "foo"; + + S3ObjectLambdaResource accessPoint = S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region(REGION) + .accountId(ACCOUNT_ID) + .accessPointName(ACCESS_POINT_NAME) + .build(); + + S3ObjectLambdaResource partitionDifferent = S3ObjectLambdaResource.builder() + .partition(dummy) + .region(REGION) + .accountId(ACCOUNT_ID) + .accessPointName(ACCESS_POINT_NAME) + .build(); + + assertThat(accessPoint.equals(partitionDifferent), is(false)); + + S3ObjectLambdaResource regionDifferent = S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region(dummy) + .accountId(ACCOUNT_ID) + .accessPointName(ACCESS_POINT_NAME) + .build(); + + assertThat(accessPoint.equals(regionDifferent), is(false)); + + S3ObjectLambdaResource accountDifferent = S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region(REGION) + .accountId(dummy) + .accessPointName(ACCESS_POINT_NAME) + .build(); + + assertThat(accessPoint.equals(accountDifferent), is(false)); + + S3ObjectLambdaResource accessPointNameDifferent = S3ObjectLambdaResource.builder() + .partition(PARTITION) + .region(REGION) + .accountId(ACCOUNT_ID) + .accessPointName(dummy) + .build(); + + assertThat(accessPoint.equals(accessPointNameDifferent), is(false)); + } + +} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/ExecutionAttributesTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/ExecutionAttributesTest.java new file mode 100644 index 000000000000..7829b297f916 --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/ExecutionAttributesTest.java @@ -0,0 +1,72 @@ +package software.amazon.awssdk.services; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import java.util.concurrent.CompletionException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.ArgumentCaptor; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +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.SdkInternalExecutionAttribute; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonAsyncClient; +import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClient; + +public class ExecutionAttributesTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void syncClient_disableHostPrefixInjection_isPresent() { + ExecutionInterceptor interceptor = mock(ExecutionInterceptor.class); + ArgumentCaptor attributesCaptor = ArgumentCaptor.forClass(ExecutionAttributes.class); + doThrow(new RuntimeException("BOOM")).when(interceptor).beforeExecution(any(Context.BeforeExecution.class), attributesCaptor.capture()); + + ProtocolRestJsonClient sync = ProtocolRestJsonClient.builder() + .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("foo", "bar"))) + .region(Region.US_WEST_2) + .overrideConfiguration(c -> c.addExecutionInterceptor(interceptor) + .putAdvancedOption(SdkAdvancedClientOption.DISABLE_HOST_PREFIX_INJECTION, true)) + .build(); + + thrown.expect(RuntimeException.class); + try { + sync.allTypes(); + } finally { + ExecutionAttributes attributes = attributesCaptor.getValue(); + assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.DISABLE_HOST_PREFIX_INJECTION)).isTrue(); + sync.close(); + } + } + + @Test + public void asyncClient_disableHostPrefixInjection_isPresent() { + ExecutionInterceptor interceptor = mock(ExecutionInterceptor.class); + ArgumentCaptor attributesCaptor = ArgumentCaptor.forClass(ExecutionAttributes.class); + doThrow(new RuntimeException("BOOM")).when(interceptor).beforeExecution(any(Context.BeforeExecution.class), attributesCaptor.capture()); + + ProtocolRestJsonAsyncClient async = ProtocolRestJsonAsyncClient.builder() + .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("foo", "bar"))) + .region(Region.US_WEST_2) + .overrideConfiguration(c -> c.addExecutionInterceptor(interceptor) + .putAdvancedOption(SdkAdvancedClientOption.DISABLE_HOST_PREFIX_INJECTION, true)) + .build(); + + thrown.expect(CompletionException.class); + try { + async.allTypes().join(); + } finally { + ExecutionAttributes attributes = attributesCaptor.getValue(); + assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.DISABLE_HOST_PREFIX_INJECTION)).isTrue(); + async.close(); + } + } +}