diff --git a/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/TrinoS3ProxyServerModule.java b/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/TrinoS3ProxyServerModule.java index d0c761da..fd7d662e 100644 --- a/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/TrinoS3ProxyServerModule.java +++ b/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/TrinoS3ProxyServerModule.java @@ -35,6 +35,7 @@ import io.trino.s3.proxy.server.security.SecurityResponse; import io.trino.s3.proxy.server.signing.SigningController; import io.trino.s3.proxy.server.signing.SigningControllerConfig; +import org.glassfish.jersey.server.model.Resource; import java.util.Optional; import java.util.ServiceLoader; @@ -52,11 +53,14 @@ public class TrinoS3ProxyServerModule @Override protected void setup(Binder binder) { + configBinder(binder).bindConfig(SigningControllerConfig.class); + TrinoS3ProxyConfig builtConfig = buildConfigObject(TrinoS3ProxyConfig.class); + jaxrsBinder(binder).bind(TrinoS3ProxyResource.class); + jaxrsBinder(binder).bindInstance(buildResourceAtPath(TrinoS3ProxyResource.class, builtConfig.getS3Path())); jaxrsBinder(binder).bind(TrinoStsResource.class); + jaxrsBinder(binder).bindInstance(buildResourceAtPath(TrinoStsResource.class, builtConfig.getStsPath())); - configBinder(binder).bindConfig(SigningControllerConfig.class); - configBinder(binder).bindConfig(TrinoS3ProxyConfig.class); binder.bind(SigningController.class).in(Scopes.SINGLETON); binder.bind(CredentialsController.class).in(Scopes.SINGLETON); binder.bind(SecurityController.class).in(Scopes.SINGLETON); @@ -96,4 +100,9 @@ private void installPlugins() install(plugin.module()); }); } + + private static Resource buildResourceAtPath(Class resourceClass, String resourcePathPrefix) + { + return Resource.builder(resourceClass).path(resourcePathPrefix).build(); + } } diff --git a/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/rest/TrinoS3ProxyConfig.java b/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/rest/TrinoS3ProxyConfig.java index 639670fc..5f813dd1 100644 --- a/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/rest/TrinoS3ProxyConfig.java +++ b/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/rest/TrinoS3ProxyConfig.java @@ -21,19 +21,49 @@ public class TrinoS3ProxyConfig { - private Optional hostName = Optional.empty(); + private Optional s3HostName = Optional.empty(); + private String s3Path = "/api/v1/s3Proxy/s3"; + private String stsPath = "/api/v1/s3Proxy/sts"; - @Config("s3proxy.hostname") - @ConfigDescription("Hostname to use for REST operations, virtual-host style addressing is only supported if this is set") - public TrinoS3ProxyConfig setHostName(String hostName) + @Config("s3proxy.s3.hostname") + @ConfigDescription("Hostname to use for S3 REST operations, virtual-host style addressing is only supported if this is set") + public TrinoS3ProxyConfig setS3HostName(String s3HostName) { - this.hostName = Optional.ofNullable(hostName); + this.s3HostName = Optional.ofNullable(s3HostName); return this; } @NotNull - public Optional getHostName() + public Optional getS3HostName() { - return hostName; + return s3HostName; + } + + @Config("s3proxy.s3.path") + @ConfigDescription("URL Path for S3 operations, optional") + public TrinoS3ProxyConfig setS3Path(String s3Path) + { + this.s3Path = s3Path; + return this; + } + + @NotNull + public String getS3Path() + { + return s3Path; + } + + @Config("s3proxy.sts.path") + @ConfigDescription("URL Path for STS operations, optional") + public TrinoS3ProxyConfig setStsPath(String stsPath) + { + this.stsPath = stsPath; + return this; + } + + @NotNull + public String getStsPath() + { + return stsPath; } } diff --git a/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/rest/TrinoS3ProxyResource.java b/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/rest/TrinoS3ProxyResource.java index 9c67ae79..7643a908 100644 --- a/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/rest/TrinoS3ProxyResource.java +++ b/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/rest/TrinoS3ProxyResource.java @@ -33,7 +33,6 @@ import static io.trino.s3.proxy.server.rest.RequestBuilder.fromRequest; import static java.util.Objects.requireNonNull; -@Path(TrinoS3ProxyRestConstants.S3_PATH) public class TrinoS3ProxyResource { private final SigningController signingController; @@ -45,7 +44,7 @@ public TrinoS3ProxyResource(SigningController signingController, TrinoS3ProxyCli { this.signingController = requireNonNull(signingController, "signingController is null"); this.proxyClient = requireNonNull(proxyClient, "proxyClient is null"); - this.serverHostName = requireNonNull(trinoS3ProxyConfig, "restConfig is null").getHostName(); + this.serverHostName = trinoS3ProxyConfig.getS3HostName(); } @GET diff --git a/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/rest/TrinoS3ProxyRestConstants.java b/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/rest/TrinoS3ProxyRestConstants.java deleted file mode 100644 index ab6126d5..00000000 --- a/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/rest/TrinoS3ProxyRestConstants.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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 io.trino.s3.proxy.server.rest; - -public final class TrinoS3ProxyRestConstants -{ - private TrinoS3ProxyRestConstants() {} - - public static final String BASE_PATH = "/api/v1/s3Proxy/"; - public static final String S3_PATH = BASE_PATH + "s3"; - public static final String STS_PATH = BASE_PATH + "sts"; -} diff --git a/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/rest/TrinoStsResource.java b/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/rest/TrinoStsResource.java index e139b3b7..14fdc709 100644 --- a/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/rest/TrinoStsResource.java +++ b/trino-s3-proxy/src/main/java/io/trino/s3/proxy/server/rest/TrinoStsResource.java @@ -25,7 +25,6 @@ import io.trino.s3.proxy.server.signing.SigningMetadata; import io.trino.s3.proxy.server.signing.SigningServiceType; import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; @@ -43,7 +42,6 @@ import static io.trino.s3.proxy.server.signing.SigningController.formatResponseInstant; import static java.util.Objects.requireNonNull; -@Path(TrinoS3ProxyRestConstants.STS_PATH) public class TrinoStsResource { private static final Logger log = Logger.get(TrinoStsResource.class); diff --git a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestStsRequests.java b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/AbstractTestStsRequests.java similarity index 86% rename from trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestStsRequests.java rename to trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/AbstractTestStsRequests.java index 8abe51a6..faee94dc 100644 --- a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestStsRequests.java +++ b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/AbstractTestStsRequests.java @@ -13,14 +13,11 @@ */ package io.trino.s3.proxy.server; -import com.google.inject.Inject; import io.airlift.http.server.testing.TestingHttpServer; import io.trino.s3.proxy.server.credentials.Credential; import io.trino.s3.proxy.server.credentials.Credentials; import io.trino.s3.proxy.server.credentials.CredentialsProvider; -import io.trino.s3.proxy.server.rest.TrinoS3ProxyRestConstants; -import io.trino.s3.proxy.server.testing.TestingUtil.ForTesting; -import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTest; +import io.trino.s3.proxy.server.rest.TrinoS3ProxyConfig; import org.junit.jupiter.api.Test; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.endpoints.Endpoint; @@ -36,18 +33,16 @@ import static java.util.concurrent.CompletableFuture.completedFuture; import static org.assertj.core.api.Assertions.assertThat; -@TrinoS3ProxyTest -public class TestStsRequests +public abstract class AbstractTestStsRequests { private final StsClient stsClient; private final CredentialsProvider credentialsProvider; - @Inject - public TestStsRequests(@ForTesting Credentials testingCredentials, TestingHttpServer httpServer, CredentialsProvider credentialsProvider) + public AbstractTestStsRequests(Credentials testingCredentials, TestingHttpServer httpServer, CredentialsProvider credentialsProvider, TrinoS3ProxyConfig s3ProxyConfig) { this.credentialsProvider = requireNonNull(credentialsProvider, "credentialsProvider is null"); - URI localProxyServerUri = httpServer.getBaseUrl().resolve(TrinoS3ProxyRestConstants.STS_PATH); + URI localProxyServerUri = httpServer.getBaseUrl().resolve(s3ProxyConfig.getStsPath()); Endpoint endpoint = Endpoint.builder().url(localProxyServerUri).build(); stsClient = StsClient.builder() diff --git a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/LocalServer.java b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/LocalServer.java index 467a847a..44857edb 100644 --- a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/LocalServer.java +++ b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/LocalServer.java @@ -17,7 +17,7 @@ import io.airlift.http.server.testing.TestingHttpServer; import io.airlift.log.Logger; import io.trino.s3.proxy.server.credentials.Credentials; -import io.trino.s3.proxy.server.rest.TrinoS3ProxyRestConstants; +import io.trino.s3.proxy.server.rest.TrinoS3ProxyConfig; import io.trino.s3.proxy.server.testing.TestingTrinoS3ProxyServer; import io.trino.s3.proxy.server.testing.TestingUtil.ForTesting; @@ -38,9 +38,10 @@ public static void main(String[] args) TestingHttpServer httpServer = trinoS3ProxyServer.getInjector().getInstance(TestingHttpServer.class); Credentials testingCredentials = trinoS3ProxyServer.getInjector().getInstance(Key.get(Credentials.class, ForTesting.class)); + TrinoS3ProxyConfig s3ProxyConfig = trinoS3ProxyServer.getInjector().getInstance(TrinoS3ProxyConfig.class); log.info(""); - log.info("Endpoint: %s", httpServer.getBaseUrl().resolve(TrinoS3ProxyRestConstants.S3_PATH)); + log.info("Endpoint: %s", httpServer.getBaseUrl().resolve(s3ProxyConfig.getS3Path())); log.info("Access Key: %s", testingCredentials.emulated().accessKey()); log.info("Secret Key: %s", testingCredentials.emulated().secretKey()); log.info(""); diff --git a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestGenericRestRequests.java b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestGenericRestRequests.java index 459b8285..ce2b567b 100644 --- a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestGenericRestRequests.java +++ b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestGenericRestRequests.java @@ -21,7 +21,7 @@ import io.airlift.units.Duration; import io.trino.s3.proxy.server.credentials.Credential; import io.trino.s3.proxy.server.credentials.Credentials; -import io.trino.s3.proxy.server.rest.TrinoS3ProxyRestConstants; +import io.trino.s3.proxy.server.rest.TrinoS3ProxyConfig; import io.trino.s3.proxy.server.testing.ManagedS3MockContainer.ForS3MockContainer; import io.trino.s3.proxy.server.testing.TestingCredentialsRolesProvider; import io.trino.s3.proxy.server.testing.TestingTrinoS3ProxyServer; @@ -100,9 +100,10 @@ public TestGenericRestRequests( TestingCredentialsRolesProvider credentialsRolesProvider, @ForTesting HttpClient httpClient, @ForTesting Credentials testingCredentials, - @ForS3MockContainer S3Client storageClient) + @ForS3MockContainer S3Client storageClient, + TrinoS3ProxyConfig trinoS3ProxyConfig) { - baseUri = httpServer.getBaseUrl(); + baseUri = httpServer.getBaseUrl().resolve(trinoS3ProxyConfig.getS3Path()); this.credentialsRolesProvider = requireNonNull(credentialsRolesProvider, "credentialsRolesProvider is null"); this.httpClient = requireNonNull(httpClient, "httpClient is null"); this.testingCredentials = requireNonNull(testingCredentials, "testingCredentials is null"); @@ -141,7 +142,6 @@ public void testPutObject() private StatusResponse doPutObject(String content, String sha256) { URI uri = UriBuilder.fromUri(baseUri) - .replacePath(TrinoS3ProxyRestConstants.S3_PATH) .path("foo") .path("bar") .build(); @@ -167,7 +167,6 @@ private StatusResponse doPutObject(String content, String sha256) private StatusResponse doAwsChunkedUpload(String content) { URI uri = UriBuilder.fromUri(baseUri) - .replacePath(TrinoS3ProxyRestConstants.S3_PATH) .path("two") .path("test") .build(); diff --git a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestProxiedAssumedRoleRequests.java b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestProxiedAssumedRoleRequests.java index 92ee6cc8..87784844 100644 --- a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestProxiedAssumedRoleRequests.java +++ b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestProxiedAssumedRoleRequests.java @@ -16,7 +16,7 @@ import com.google.inject.Inject; import io.airlift.http.server.testing.TestingHttpServer; import io.trino.s3.proxy.server.credentials.Credentials; -import io.trino.s3.proxy.server.rest.TrinoS3ProxyRestConstants; +import io.trino.s3.proxy.server.rest.TrinoS3ProxyConfig; import io.trino.s3.proxy.server.testing.ManagedS3MockContainer.ForS3MockContainer; import io.trino.s3.proxy.server.testing.TestingCredentialsRolesProvider; import io.trino.s3.proxy.server.testing.TestingUtil.ForTesting; @@ -51,9 +51,10 @@ public TestProxiedAssumedRoleRequests( @ForTesting Credentials testingCredentials, TestingCredentialsRolesProvider credentialsController, @ForS3MockContainer S3Client storageClient, - @ForS3MockContainer List configuredBuckets) + @ForS3MockContainer List configuredBuckets, + TrinoS3ProxyConfig trinoS3ProxyConfig) { - this(buildClient(httpServer, testingCredentials), testingCredentials, credentialsController, storageClient, configuredBuckets); + this(buildClient(httpServer, testingCredentials, trinoS3ProxyConfig.getS3Path(), trinoS3ProxyConfig.getStsPath()), testingCredentials, credentialsController, storageClient, configuredBuckets); } protected TestProxiedAssumedRoleRequests( @@ -75,11 +76,11 @@ public void validateCount() credentialsController.resetAssumedRoles(); } - protected static S3Client buildClient(TestingHttpServer httpServer, Credentials credentials) + protected static S3Client buildClient(TestingHttpServer httpServer, Credentials credentials, String s3Path, String stsPath) { URI baseUrl = httpServer.getBaseUrl(); - URI localProxyServerUri = baseUrl.resolve(TrinoS3ProxyRestConstants.S3_PATH); - URI localStsServerUri = baseUrl.resolve(TrinoS3ProxyRestConstants.STS_PATH); + URI localProxyServerUri = baseUrl.resolve(s3Path); + URI localStsServerUri = baseUrl.resolve(stsPath); AwsBasicCredentials awsBasicCredentials = AwsBasicCredentials.create(credentials.emulated().accessKey(), credentials.emulated().secretKey()); diff --git a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestProxiedEmulatedAndRemoteAssumedRoleRequests.java b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestProxiedEmulatedAndRemoteAssumedRoleRequests.java index 5de0b577..376a8faf 100644 --- a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestProxiedEmulatedAndRemoteAssumedRoleRequests.java +++ b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestProxiedEmulatedAndRemoteAssumedRoleRequests.java @@ -16,6 +16,7 @@ import com.google.inject.Inject; import io.airlift.http.server.testing.TestingHttpServer; import io.trino.s3.proxy.server.credentials.Credentials; +import io.trino.s3.proxy.server.rest.TrinoS3ProxyConfig; import io.trino.s3.proxy.server.testing.ManagedS3MockContainer.ForS3MockContainer; import io.trino.s3.proxy.server.testing.TestingCredentialsRolesProvider; import io.trino.s3.proxy.server.testing.TestingTrinoS3ProxyServerModule.ForTestingRemoteCredentials; @@ -34,8 +35,9 @@ public TestProxiedEmulatedAndRemoteAssumedRoleRequests( TestingCredentialsRolesProvider credentialsController, @ForS3MockContainer S3Client storageClient, @ForS3MockContainer List configuredBuckets, - @ForTestingRemoteCredentials Credentials remoteCredentials) + @ForTestingRemoteCredentials Credentials remoteCredentials, + TrinoS3ProxyConfig trinoS3ProxyConfig) { - super(buildClient(httpServer, remoteCredentials), testingCredentials, credentialsController, storageClient, configuredBuckets); + super(buildClient(httpServer, remoteCredentials, trinoS3ProxyConfig.getS3Path(), trinoS3ProxyConfig.getStsPath()), testingCredentials, credentialsController, storageClient, configuredBuckets); } } diff --git a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestProxiedRequests.java b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestProxiedRequests.java index 04ef35aa..e08c1406 100644 --- a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestProxiedRequests.java +++ b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestProxiedRequests.java @@ -15,16 +15,28 @@ import com.google.inject.Inject; import io.trino.s3.proxy.server.testing.ManagedS3MockContainer.ForS3MockContainer; +import io.trino.s3.proxy.server.testing.TestingTrinoS3ProxyServer; +import io.trino.s3.proxy.server.testing.harness.BuilderFilter; import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTest; import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTestCommonModules.WithConfiguredBuckets; import software.amazon.awssdk.services.s3.S3Client; import java.util.List; -@TrinoS3ProxyTest(filters = WithConfiguredBuckets.class) +@TrinoS3ProxyTest(filters = {WithConfiguredBuckets.class, TestProxiedRequests.Filter.class}) public class TestProxiedRequests extends AbstractTestProxiedRequests { + public static class Filter + implements BuilderFilter + { + @Override + public TestingTrinoS3ProxyServer.Builder filter(TestingTrinoS3ProxyServer.Builder builder) + { + return builder.withProperty("s3proxy.s3.path", "/api/some/s3/path"); + } + } + @Inject public TestProxiedRequests(S3Client s3Client, @ForS3MockContainer S3Client storageClient, @ForS3MockContainer List configuredBuckets) { diff --git a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestProxiedRequestsWithEmptyPath.java b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestProxiedRequestsWithEmptyPath.java new file mode 100644 index 00000000..8bf9eb87 --- /dev/null +++ b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestProxiedRequestsWithEmptyPath.java @@ -0,0 +1,45 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.trino.s3.proxy.server; + +import com.google.inject.Inject; +import io.trino.s3.proxy.server.testing.ManagedS3MockContainer.ForS3MockContainer; +import io.trino.s3.proxy.server.testing.TestingTrinoS3ProxyServer; +import io.trino.s3.proxy.server.testing.harness.BuilderFilter; +import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTest; +import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTestCommonModules.WithConfiguredBuckets; +import software.amazon.awssdk.services.s3.S3Client; + +import java.util.List; + +@TrinoS3ProxyTest(filters = {WithConfiguredBuckets.class, TestProxiedRequestsWithEmptyPath.Filter.class}) +public class TestProxiedRequestsWithEmptyPath + extends AbstractTestProxiedRequests +{ + public static class Filter + implements BuilderFilter + { + @Override + public TestingTrinoS3ProxyServer.Builder filter(TestingTrinoS3ProxyServer.Builder builder) + { + return builder.withProperty("s3proxy.s3.path", ""); + } + } + + @Inject + public TestProxiedRequestsWithEmptyPath(S3Client s3Client, @ForS3MockContainer S3Client storageClient, @ForS3MockContainer List configuredBuckets) + { + super(s3Client, storageClient, configuredBuckets); + } +} diff --git a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestRemoteSessionProxiedRequests.java b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestRemoteSessionProxiedRequests.java index 54509f6c..68e3a63c 100644 --- a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestRemoteSessionProxiedRequests.java +++ b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestRemoteSessionProxiedRequests.java @@ -16,32 +16,34 @@ import com.google.inject.Inject; import io.airlift.http.server.testing.TestingHttpServer; import io.trino.s3.proxy.server.credentials.Credentials; +import io.trino.s3.proxy.server.rest.TrinoS3ProxyConfig; import io.trino.s3.proxy.server.testing.ManagedS3MockContainer.ForS3MockContainer; import io.trino.s3.proxy.server.testing.TestingTrinoS3ProxyServerModule.ForTestingRemoteCredentials; import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTest; -import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTestCommonModules; +import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTestCommonModules.WithConfiguredBuckets; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.services.s3.S3Client; import java.util.List; +import java.util.Optional; import static io.trino.s3.proxy.server.testing.TestingUtil.clientBuilder; -@TrinoS3ProxyTest(filters = TrinoS3ProxyTestCommonModules.WithConfiguredBuckets.class) +@TrinoS3ProxyTest(filters = WithConfiguredBuckets.class) public class TestRemoteSessionProxiedRequests extends AbstractTestProxiedRequests { @Inject - public TestRemoteSessionProxiedRequests(@ForS3MockContainer S3Client storageClient, @ForTestingRemoteCredentials Credentials remoteCredentials, TestingHttpServer httpServer, @ForS3MockContainer List configuredBuckets) + public TestRemoteSessionProxiedRequests(@ForS3MockContainer S3Client storageClient, @ForTestingRemoteCredentials Credentials remoteCredentials, TestingHttpServer httpServer, @ForS3MockContainer List configuredBuckets, TrinoS3ProxyConfig trinoS3ProxyConfig) { - super(buildInternalClient(remoteCredentials, httpServer), storageClient, configuredBuckets); + super(buildInternalClient(remoteCredentials, httpServer, trinoS3ProxyConfig.getS3Path()), storageClient, configuredBuckets); } - private static S3Client buildInternalClient(Credentials credentials, TestingHttpServer httpServer) + private static S3Client buildInternalClient(Credentials credentials, TestingHttpServer httpServer, String s3Path) { AwsBasicCredentials awsBasicCredentials = AwsBasicCredentials.create(credentials.emulated().accessKey(), credentials.emulated().secretKey()); - return clientBuilder(httpServer.getBaseUrl()) + return clientBuilder(httpServer.getBaseUrl(), Optional.of(s3Path)) .credentialsProvider(() -> awsBasicCredentials) .build(); } diff --git a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestStsRequestsWithEmptyPath.java b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestStsRequestsWithEmptyPath.java new file mode 100644 index 00000000..28ac1f83 --- /dev/null +++ b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestStsRequestsWithEmptyPath.java @@ -0,0 +1,45 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.trino.s3.proxy.server; + +import com.google.inject.Inject; +import io.airlift.http.server.testing.TestingHttpServer; +import io.trino.s3.proxy.server.credentials.Credentials; +import io.trino.s3.proxy.server.credentials.CredentialsProvider; +import io.trino.s3.proxy.server.rest.TrinoS3ProxyConfig; +import io.trino.s3.proxy.server.testing.TestingTrinoS3ProxyServer; +import io.trino.s3.proxy.server.testing.TestingUtil.ForTesting; +import io.trino.s3.proxy.server.testing.harness.BuilderFilter; +import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTest; + +@TrinoS3ProxyTest(filters = TestStsRequestsWithEmptyPath.Filter.class) +public class TestStsRequestsWithEmptyPath + extends AbstractTestStsRequests +{ + public static class Filter + implements BuilderFilter + { + @Override + public TestingTrinoS3ProxyServer.Builder filter(TestingTrinoS3ProxyServer.Builder builder) + { + return builder.withProperty("s3proxy.sts.path", ""); + } + } + + @Inject + public TestStsRequestsWithEmptyPath(@ForTesting Credentials testingCredentials, TestingHttpServer httpServer, CredentialsProvider credentialsProvider, TrinoS3ProxyConfig s3ProxyConfig) + { + super(testingCredentials, httpServer, credentialsProvider, s3ProxyConfig); + } +} diff --git a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestStsRequestsWithPath.java b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestStsRequestsWithPath.java new file mode 100644 index 00000000..c7b62e75 --- /dev/null +++ b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/TestStsRequestsWithPath.java @@ -0,0 +1,45 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.trino.s3.proxy.server; + +import com.google.inject.Inject; +import io.airlift.http.server.testing.TestingHttpServer; +import io.trino.s3.proxy.server.credentials.Credentials; +import io.trino.s3.proxy.server.credentials.CredentialsProvider; +import io.trino.s3.proxy.server.rest.TrinoS3ProxyConfig; +import io.trino.s3.proxy.server.testing.TestingTrinoS3ProxyServer; +import io.trino.s3.proxy.server.testing.TestingUtil.ForTesting; +import io.trino.s3.proxy.server.testing.harness.BuilderFilter; +import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTest; + +@TrinoS3ProxyTest(filters = TestStsRequestsWithPath.Filter.class) +public class TestStsRequestsWithPath + extends AbstractTestStsRequests +{ + public static class Filter + implements BuilderFilter + { + @Override + public TestingTrinoS3ProxyServer.Builder filter(TestingTrinoS3ProxyServer.Builder builder) + { + return builder.withProperty("s3proxy.sts.path", "/api/some/sts/path"); + } + } + + @Inject + public TestStsRequestsWithPath(@ForTesting Credentials testingCredentials, TestingHttpServer httpServer, CredentialsProvider credentialsProvider, TrinoS3ProxyConfig s3ProxyConfig) + { + super(testingCredentials, httpServer, credentialsProvider, s3ProxyConfig); + } +} diff --git a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/credentials/TestAssumingRoles.java b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/credentials/TestAssumingRoles.java index 55f78043..7bb712c3 100644 --- a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/credentials/TestAssumingRoles.java +++ b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/credentials/TestAssumingRoles.java @@ -15,6 +15,7 @@ import com.google.inject.Inject; import io.airlift.http.server.testing.TestingHttpServer; +import io.trino.s3.proxy.server.rest.TrinoS3ProxyConfig; import io.trino.s3.proxy.server.testing.TestingCredentialsRolesProvider; import io.trino.s3.proxy.server.testing.TestingUtil.ForTesting; import io.trino.s3.proxy.server.testing.harness.TrinoS3ProxyTest; @@ -26,6 +27,7 @@ import software.amazon.awssdk.services.s3.model.ListBucketsResponse; import software.amazon.awssdk.services.s3.model.S3Exception; +import java.net.URI; import java.util.Optional; import static io.trino.s3.proxy.server.testing.TestingUtil.clientBuilder; @@ -39,15 +41,15 @@ public class TestAssumingRoles private static final String ARN = "test-arn"; private final TestingCredentialsRolesProvider credentialsController; - private final TestingHttpServer httpServer; + private final URI localS3URI; private final Credentials testingCredentials; @Inject - public TestAssumingRoles(TestingCredentialsRolesProvider credentialsController, TestingHttpServer httpServer, @ForTesting Credentials testingCredentials) + public TestAssumingRoles(TestingCredentialsRolesProvider credentialsController, TestingHttpServer httpServer, @ForTesting Credentials testingCredentials, TrinoS3ProxyConfig trinoS3ProxyConfig) { this.credentialsController = requireNonNull(credentialsController, "credentialsController is null"); - this.httpServer = requireNonNull(httpServer, "testingHttpServer is null"); this.testingCredentials = requireNonNull(testingCredentials, "testingCredentials is null"); + this.localS3URI = httpServer.getBaseUrl().resolve(trinoS3ProxyConfig.getS3Path()); } @AfterEach @@ -62,7 +64,7 @@ public void testStsSession() EmulatedAssumedRole emulatedAssumedRole = credentialsController.assumeEmulatedRole(testingCredentials.emulated(), "us-east-1", ARN, Optional.empty(), Optional.empty(), Optional.empty()) .orElseThrow(() -> new RuntimeException("Failed to assume role")); - try (S3Client client = clientBuilder(httpServer.getBaseUrl()) + try (S3Client client = clientBuilder(localS3URI) .credentialsProvider(() -> AwsSessionCredentials.create(emulatedAssumedRole.credential().accessKey(), emulatedAssumedRole.credential().secretKey(), emulatedAssumedRole.session())) .build()) { // valid assumed role session - should work diff --git a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/TestingS3ClientProvider.java b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/TestingS3ClientProvider.java index fdbfb9a5..236bd9e5 100644 --- a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/TestingS3ClientProvider.java +++ b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/TestingS3ClientProvider.java @@ -13,28 +13,21 @@ */ package io.trino.s3.proxy.server.testing; -import com.google.inject.BindingAnnotation; import com.google.inject.Inject; import com.google.inject.Provider; import io.airlift.http.server.testing.TestingHttpServer; import io.trino.s3.proxy.server.credentials.Credential; import io.trino.s3.proxy.server.credentials.Credentials; -import io.trino.s3.proxy.server.rest.TrinoS3ProxyResource; +import io.trino.s3.proxy.server.rest.TrinoS3ProxyConfig; import io.trino.s3.proxy.server.testing.TestingUtil.ForTesting; import jakarta.ws.rs.core.UriBuilder; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.services.s3.S3Client; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; import java.net.URI; import java.util.Optional; import static io.trino.s3.proxy.server.testing.TestingUtil.clientBuilder; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.util.Objects.requireNonNull; public class TestingS3ClientProvider @@ -44,20 +37,30 @@ public class TestingS3ClientProvider private final Credential testingCredentials; private final boolean forcePathStyle; - @Retention(RUNTIME) - @Target({FIELD, PARAMETER, METHOD}) - @BindingAnnotation - public @interface ForS3ClientProvider {} + public record TestingS3ClientConfig(Optional hostName, String s3Path) + { + public TestingS3ClientConfig + { + requireNonNull(hostName, "hostName is null"); + requireNonNull(s3Path, "s3Path is null"); + } + + @Inject + public TestingS3ClientConfig(TrinoS3ProxyConfig config) + { + this(config.getS3HostName(), config.getS3Path()); + } + } @Inject - public TestingS3ClientProvider(TestingHttpServer httpServer, @ForTesting Credentials testingCredentials, @ForS3ClientProvider Optional hostName) + public TestingS3ClientProvider(TestingHttpServer httpServer, @ForTesting Credentials testingCredentials, TestingS3ClientConfig clientConfig) { URI localProxyServerUri = httpServer.getBaseUrl(); - this.proxyUri = requireNonNull(hostName, "hostName is null") - .map(serverHostName -> UriBuilder.newInstance().host(serverHostName).port(localProxyServerUri.getPort()).scheme("http").path(TrinoS3ProxyResource.class).build()) - .orElse(UriBuilder.fromUri(localProxyServerUri).path(TrinoS3ProxyResource.class).build()); + this.proxyUri = clientConfig.hostName() + .map(serverHostName -> UriBuilder.newInstance().host(serverHostName).port(localProxyServerUri.getPort()).scheme("http").path(clientConfig.s3Path()).build()) + .orElse(UriBuilder.fromUri(localProxyServerUri).path(clientConfig.s3Path()).build()); this.testingCredentials = requireNonNull(testingCredentials, "testingCredentials is null").emulated(); - this.forcePathStyle = hostName.isEmpty(); + this.forcePathStyle = clientConfig.hostName().isEmpty(); } @Override diff --git a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/TestingTrinoS3ProxyServer.java b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/TestingTrinoS3ProxyServer.java index 6184ce31..571259e5 100644 --- a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/TestingTrinoS3ProxyServer.java +++ b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/TestingTrinoS3ProxyServer.java @@ -109,12 +109,6 @@ public Builder withProperty(String key, String value) return this; } - public Builder withServerHostName(String serverHostName) - { - properties.put("s3proxy.hostname", serverHostName); - return this; - } - public TestingTrinoS3ProxyServer buildAndStart() { return start(modules.build(), properties.buildKeepingLast()); diff --git a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/TestingUtil.java b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/TestingUtil.java index c33e1a5b..e2dd9307 100644 --- a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/TestingUtil.java +++ b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/TestingUtil.java @@ -16,7 +16,6 @@ import com.google.inject.BindingAnnotation; import io.trino.s3.proxy.server.credentials.Credential; import io.trino.s3.proxy.server.credentials.Credentials; -import io.trino.s3.proxy.server.rest.TrinoS3ProxyRestConstants; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.S3ClientBuilder; @@ -24,6 +23,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.net.URI; +import java.util.Optional; import java.util.UUID; import static java.lang.annotation.ElementType.FIELD; @@ -47,8 +47,12 @@ public final class TestingUtil public static S3ClientBuilder clientBuilder(URI baseUrl) { - URI localProxyServerUri = baseUrl.resolve(TrinoS3ProxyRestConstants.S3_PATH); + return clientBuilder(baseUrl, Optional.empty()); + } + public static S3ClientBuilder clientBuilder(URI baseUrl, Optional urlPath) + { + URI localProxyServerUri = urlPath.map(baseUrl::resolve).orElse(baseUrl); return S3Client.builder() .region(Region.US_EAST_1) .endpointOverride(localProxyServerUri); diff --git a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/harness/TrinoS3ProxyTestCommonModules.java b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/harness/TrinoS3ProxyTestCommonModules.java index 70d91235..1897409c 100644 --- a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/harness/TrinoS3ProxyTestCommonModules.java +++ b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/harness/TrinoS3ProxyTestCommonModules.java @@ -20,7 +20,6 @@ import io.trino.s3.proxy.server.remote.RemoteS3Facade; import io.trino.s3.proxy.server.testing.ContainerS3Facade; import io.trino.s3.proxy.server.testing.ManagedS3MockContainer.ForS3MockContainer; -import io.trino.s3.proxy.server.testing.TestingS3ClientProvider.ForS3ClientProvider; import io.trino.s3.proxy.server.testing.TestingTrinoS3ProxyServer; import io.trino.s3.proxy.server.testing.TestingUtil.ForTesting; @@ -66,10 +65,7 @@ public static class WithVirtualHostEnabledProxy @Override public TestingTrinoS3ProxyServer.Builder filter(TestingTrinoS3ProxyServer.Builder builder) { - return builder - .withServerHostName(LOCALHOST_DOMAIN) - .addModule( - binder -> OptionalBinder.newOptionalBinder(binder, Key.get(String.class, ForS3ClientProvider.class)).setBinding().toInstance(LOCALHOST_DOMAIN)); + return builder.withProperty("s3proxy.s3.hostname", LOCALHOST_DOMAIN); } } diff --git a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/harness/TrinoS3ProxyTestExtension.java b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/harness/TrinoS3ProxyTestExtension.java index 65e91009..780018a5 100644 --- a/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/harness/TrinoS3ProxyTestExtension.java +++ b/trino-s3-proxy/src/test/java/io/trino/s3/proxy/server/testing/harness/TrinoS3ProxyTestExtension.java @@ -15,14 +15,12 @@ import com.google.inject.Injector; import com.google.inject.Key; -import com.google.inject.Module; import com.google.inject.Scopes; import io.trino.s3.proxy.server.remote.RemoteS3Facade; import io.trino.s3.proxy.server.testing.ContainerS3Facade; import io.trino.s3.proxy.server.testing.ManagedS3MockContainer; import io.trino.s3.proxy.server.testing.ManagedS3MockContainer.ForS3MockContainer; import io.trino.s3.proxy.server.testing.TestingS3ClientProvider; -import io.trino.s3.proxy.server.testing.TestingS3ClientProvider.ForS3ClientProvider; import io.trino.s3.proxy.server.testing.TestingTrinoS3ProxyServer; import io.trino.s3.proxy.server.testing.TestingUtil.ForTesting; import org.junit.jupiter.api.extension.ExtensionContext; @@ -63,7 +61,6 @@ public Object createTestInstance(TestInstanceFactoryContext factoryContext, Exte TestingTrinoS3ProxyServer trinoS3ProxyServer = builder .withMockS3Container() .addModule(binder -> { - newOptionalBinder(binder, Key.get(String.class, ForS3ClientProvider.class)); binder.bind(S3Client.class).annotatedWith(ForS3MockContainer.class).toProvider(ManagedS3MockContainer.class); newOptionalBinder(binder, Key.get(RemoteS3Facade.class, ForTesting.class)) .setDefault() @@ -75,6 +72,7 @@ public Object createTestInstance(TestInstanceFactoryContext factoryContext, Exte Injector injector = trinoS3ProxyServer.getInjector() .createChildInjector(binder -> { + binder.bind(TestingS3ClientProvider.TestingS3ClientConfig.class).in(Scopes.SINGLETON); binder.bind(TestingS3ClientProvider.class).in(Scopes.SINGLETON); binder.bind(S3Client.class).toProvider(TestingS3ClientProvider.class).in(Scopes.SINGLETON); binder.bind(TestingTrinoS3ProxyServer.class).toInstance(trinoS3ProxyServer); @@ -93,17 +91,6 @@ public void preDestroyTestInstance(ExtensionContext context) } } - @SuppressWarnings({"unchecked", "rawtypes"}) - private static Module instantiateModule(Class moduleClass) - { - try { - return (Module) moduleClass.getConstructor().newInstance(); - } - catch (Exception e) { - throw new RuntimeException("Could not instantiate module: " + moduleClass.getName(), e); - } - } - @SuppressWarnings({"unchecked", "rawtypes"}) private static BuilderFilter instantiateBuilderFilter(Class builderFilterClass) {