diff --git a/docs/src/main/sphinx/connector/metastores.md b/docs/src/main/sphinx/connector/metastores.md index c0f2a8d22c8b..4e3c5b1f8632 100644 --- a/docs/src/main/sphinx/connector/metastores.md +++ b/docs/src/main/sphinx/connector/metastores.md @@ -422,10 +422,25 @@ properties: - Nessie API endpoint URI (required). Example: ``https://localhost:19120/api/v1`` * - ``iceberg.nessie-catalog.ref`` - - The branch/tag to use for Nessie, defaults to ``main``. + - The branch/tag to use for Nessie. Defaults to ``main``. * - ``iceberg.nessie-catalog.default-warehouse-dir`` - Default warehouse directory for schemas created without an explicit ``location`` property. Example: ``/tmp`` + * - ``iceberg.nessie-catalog.read-timeout`` + - The read timeout :ref:`duration ` for requests + to the Nessie server. Defaults to ``25s``. + * - ``iceberg.nessie-catalog.connection-timeout`` + - The connection timeout :ref:`duration ` for + connection requests to the Nessie server. Defaults to ``5s``. + * - ``iceberg.nessie-catalog.enable-compression`` + - Configure whether compression should be enabled or not for + requests to the Nessie server. Defaults to ``true``. + * - ``iceberg.nessie-catalog.authentication.type`` + - The authentication type to use. + Available value is ``BEARER``. Defaults to no authentication. + * - ``iceberg.nessie-catalog.authentication.token`` + - The token to use with ``BEARER`` authentication. + Example: ``SXVLUXUhIExFQ0tFUiEK`` ``` ```text diff --git a/plugin/trino-iceberg/pom.xml b/plugin/trino-iceberg/pom.xml index c0f672f24d09..4445590b8fc7 100644 --- a/plugin/trino-iceberg/pom.xml +++ b/plugin/trino-iceberg/pom.xml @@ -24,6 +24,7 @@ TODO (https://github.com/trinodb/trino/issues/11294) remove when we upgrade to surefire with https://issues.apache.org/jira/browse/SUREFIRE-1967 --> instances + 21.1.2 0.71.0 @@ -577,6 +578,30 @@ test + + org.keycloak + keycloak-admin-client-jakarta + ${dep.keycloak.version} + test + + + org.jboss.resteasy + resteasy-jaxb-provider + + + org.jboss.spec.javax.ws.rs + jboss-jaxrs-api_3.0_spec + + + + + + org.keycloak + keycloak-core + ${dep.keycloak.version} + test + + org.testcontainers postgresql diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogConfig.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogConfig.java index 492d0b3fa289..4e7793c7cea5 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogConfig.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogConfig.java @@ -15,16 +15,37 @@ import io.airlift.configuration.Config; import io.airlift.configuration.ConfigDescription; +import io.airlift.configuration.ConfigSecuritySensitive; +import io.airlift.units.Duration; +import io.airlift.units.MinDuration; +import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.net.URI; +import java.util.Optional; + +import static io.trino.plugin.iceberg.catalog.nessie.IcebergNessieCatalogConfig.Security.BEARER; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.function.Predicate.isEqual; +import static org.projectnessie.client.NessieConfigConstants.DEFAULT_CONNECT_TIMEOUT_MILLIS; +import static org.projectnessie.client.NessieConfigConstants.DEFAULT_READ_TIMEOUT_MILLIS; public class IcebergNessieCatalogConfig { + public enum Security + { + BEARER, + } + private String defaultReferenceName = "main"; private String defaultWarehouseDir; private URI serverUri; + private Duration readTimeout = new Duration(DEFAULT_READ_TIMEOUT_MILLIS, MILLISECONDS); + private Duration connectionTimeout = new Duration(DEFAULT_CONNECT_TIMEOUT_MILLIS, MILLISECONDS); + private boolean enableCompression = true; + private Security security; + private Optional bearerToken = Optional.empty(); @NotNull public String getDefaultReferenceName() @@ -67,4 +88,84 @@ public IcebergNessieCatalogConfig setDefaultWarehouseDir(String defaultWarehouse this.defaultWarehouseDir = defaultWarehouseDir; return this; } + + @MinDuration("1ms") + public Duration getReadTimeout() + { + return readTimeout; + } + + @Config("iceberg.nessie-catalog.read-timeout") + @ConfigDescription("The read timeout for the client.") + public IcebergNessieCatalogConfig setReadTimeout(Duration readTimeout) + { + this.readTimeout = readTimeout; + return this; + } + + @MinDuration("1ms") + public Duration getConnectionTimeout() + { + return connectionTimeout; + } + + @Config("iceberg.nessie-catalog.connection-timeout") + @ConfigDescription("The connection timeout for the client.") + public IcebergNessieCatalogConfig setConnectionTimeout(Duration connectionTimeout) + { + this.connectionTimeout = connectionTimeout; + return this; + } + + public boolean isCompressionEnabled() + { + return enableCompression; + } + + @Config("iceberg.nessie-catalog.enable-compression") + @ConfigDescription("Configure whether compression should be enabled or not.") + public IcebergNessieCatalogConfig setCompressionEnabled(boolean enableCompression) + { + this.enableCompression = enableCompression; + return this; + } + + public Optional getSecurity() + { + return Optional.ofNullable(security); + } + + @Config("iceberg.nessie-catalog.authentication.type") + @ConfigDescription("The authentication type to use") + public IcebergNessieCatalogConfig setSecurity(Security security) + { + this.security = security; + return this; + } + + public Optional getBearerToken() + { + return bearerToken; + } + + @Config("iceberg.nessie-catalog.authentication.token") + @ConfigDescription("The token to use with BEARER authentication") + @ConfigSecuritySensitive + public IcebergNessieCatalogConfig setBearerToken(String token) + { + this.bearerToken = Optional.ofNullable(token); + return this; + } + + @AssertTrue(message = "'iceberg.nessie-catalog.authentication.token' must be configured only with 'iceberg.nessie-catalog.authentication.type' BEARER") + public boolean isTokenConfiguredWithoutType() + { + return getSecurity().filter(isEqual(BEARER)).isPresent() || getBearerToken().isEmpty(); + } + + @AssertTrue(message = "'iceberg.nessie-catalog.authentication.token' must be configured with 'iceberg.nessie-catalog.authentication.type' BEARER") + public boolean isMissingTokenForBearerAuth() + { + return getSecurity().filter(isEqual(BEARER)).isEmpty() || getBearerToken().isPresent(); + } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogModule.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogModule.java index f0e5de3ec30a..7a2008dfc0c4 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogModule.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogModule.java @@ -23,9 +23,11 @@ import io.trino.plugin.iceberg.catalog.TrinoCatalogFactory; import org.apache.iceberg.nessie.NessieIcebergClient; import org.projectnessie.client.api.NessieApiV1; +import org.projectnessie.client.auth.BearerAuthenticationProvider; import org.projectnessie.client.http.HttpClientBuilder; import static io.airlift.configuration.ConfigBinder.configBinder; +import static java.lang.Math.toIntExact; import static org.weakref.jmx.guice.ExportBinder.newExporter; public class IcebergNessieCatalogModule @@ -45,10 +47,16 @@ protected void setup(Binder binder) @Singleton public static NessieIcebergClient createNessieIcebergClient(IcebergNessieCatalogConfig icebergNessieCatalogConfig) { - return new NessieIcebergClient( - HttpClientBuilder.builder() - .withUri(icebergNessieCatalogConfig.getServerUri()) - .build(NessieApiV1.class), + HttpClientBuilder builder = HttpClientBuilder.builder() + .withUri(icebergNessieCatalogConfig.getServerUri()) + .withDisableCompression(!icebergNessieCatalogConfig.isCompressionEnabled()) + .withReadTimeout(toIntExact(icebergNessieCatalogConfig.getReadTimeout().toMillis())) + .withConnectionTimeout(toIntExact(icebergNessieCatalogConfig.getConnectionTimeout().toMillis())); + + icebergNessieCatalogConfig.getBearerToken() + .ifPresent(token -> builder.withAuthentication(BearerAuthenticationProvider.create(token))); + + return new NessieIcebergClient(builder.build(NessieApiV1.class), icebergNessieCatalogConfig.getDefaultReferenceName(), null, ImmutableMap.of()); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPlugin.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPlugin.java index a57c3de254f9..5fcc3c7a0751 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPlugin.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPlugin.java @@ -288,6 +288,59 @@ public void testNessieCatalog() .shutdown(); } + @Test + public void testNessieCatalogWithBearerAuth() + { + ConnectorFactory factory = getConnectorFactory(); + + factory.create( + "test", + Map.of( + "iceberg.catalog.type", "nessie", + "iceberg.nessie-catalog.default-warehouse-dir", "/tmp", + "iceberg.nessie-catalog.uri", "http://foo:1234", + "iceberg.nessie-catalog.authentication.type", "BEARER", + "iceberg.nessie-catalog.authentication.token", "someToken"), + new TestingConnectorContext()) + .shutdown(); + } + + @Test + public void testNessieCatalogWithNoAuthAndAccessToken() + { + ConnectorFactory factory = getConnectorFactory(); + + assertThatThrownBy(() -> factory.create( + "test", + Map.of( + "iceberg.catalog.type", "nessie", + "iceberg.nessie-catalog.uri", "nessieUri", + "iceberg.nessie-catalog.default-warehouse-dir", "/tmp", + "iceberg.nessie-catalog.authentication.token", "someToken"), + new TestingConnectorContext()) + .shutdown()) + .isInstanceOf(ApplicationConfigurationException.class) + .hasMessageContaining("'iceberg.nessie-catalog.authentication.token' must be configured only with 'iceberg.nessie-catalog.authentication.type' BEARER"); + } + + @Test + public void testNessieCatalogWithNoAccessToken() + { + ConnectorFactory factory = getConnectorFactory(); + + assertThatThrownBy(() -> factory.create( + "test", + Map.of( + "iceberg.catalog.type", "nessie", + "iceberg.nessie-catalog.uri", "nessieUri", + "iceberg.nessie-catalog.default-warehouse-dir", "/tmp", + "iceberg.nessie-catalog.authentication.type", "BEARER"), + new TestingConnectorContext()) + .shutdown()) + .isInstanceOf(ApplicationConfigurationException.class) + .hasMessageContaining("'iceberg.nessie-catalog.authentication.token' must be configured with 'iceberg.nessie-catalog.authentication.type' BEARER"); + } + private static ConnectorFactory getConnectorFactory() { return getOnlyElement(new IcebergPlugin().getConnectorFactories()); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogConfig.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogConfig.java index 805b66dea860..d01eff2aff21 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogConfig.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogConfig.java @@ -14,14 +14,19 @@ package io.trino.plugin.iceberg.catalog.nessie; import com.google.common.collect.ImmutableMap; +import io.airlift.units.Duration; import org.junit.jupiter.api.Test; import java.net.URI; import java.util.Map; +import java.util.concurrent.TimeUnit; import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.projectnessie.client.NessieConfigConstants.DEFAULT_CONNECT_TIMEOUT_MILLIS; +import static org.projectnessie.client.NessieConfigConstants.DEFAULT_READ_TIMEOUT_MILLIS; public class TestIcebergNessieCatalogConfig { @@ -31,7 +36,12 @@ public void testDefaults() assertRecordedDefaults(recordDefaults(IcebergNessieCatalogConfig.class) .setDefaultWarehouseDir(null) .setServerUri(null) - .setDefaultReferenceName("main")); + .setDefaultReferenceName("main") + .setCompressionEnabled(true) + .setConnectionTimeout(new Duration(DEFAULT_CONNECT_TIMEOUT_MILLIS, MILLISECONDS)) + .setReadTimeout(new Duration(DEFAULT_READ_TIMEOUT_MILLIS, MILLISECONDS)) + .setSecurity(null) + .setBearerToken(null)); } @Test @@ -41,12 +51,22 @@ public void testExplicitPropertyMapping() .put("iceberg.nessie-catalog.default-warehouse-dir", "/tmp") .put("iceberg.nessie-catalog.uri", "http://localhost:xxx/api/v1") .put("iceberg.nessie-catalog.ref", "someRef") + .put("iceberg.nessie-catalog.enable-compression", "false") + .put("iceberg.nessie-catalog.connection-timeout", "2s") + .put("iceberg.nessie-catalog.read-timeout", "5m") + .put("iceberg.nessie-catalog.authentication.type", "BEARER") + .put("iceberg.nessie-catalog.authentication.token", "bearerToken") .buildOrThrow(); IcebergNessieCatalogConfig expected = new IcebergNessieCatalogConfig() .setDefaultWarehouseDir("/tmp") .setServerUri(URI.create("http://localhost:xxx/api/v1")) - .setDefaultReferenceName("someRef"); + .setDefaultReferenceName("someRef") + .setCompressionEnabled(false) + .setConnectionTimeout(new Duration(2, TimeUnit.SECONDS)) + .setReadTimeout(new Duration(5, TimeUnit.MINUTES)) + .setSecurity(IcebergNessieCatalogConfig.Security.BEARER) + .setBearerToken("bearerToken"); assertFullMapping(properties, expected); } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogWithBearerAuth.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogWithBearerAuth.java new file mode 100644 index 000000000000..85ddbcbaa524 --- /dev/null +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogWithBearerAuth.java @@ -0,0 +1,81 @@ +/* + * 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.plugin.iceberg.catalog.nessie; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.trino.plugin.iceberg.IcebergQueryRunner; +import io.trino.plugin.iceberg.containers.KeycloakContainer; +import io.trino.plugin.iceberg.containers.NessieContainer; +import io.trino.testing.AbstractTestQueryFramework; +import io.trino.testing.QueryRunner; +import io.trino.testing.sql.TestTable; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.Network; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; + +import static com.google.common.io.MoreFiles.deleteRecursively; +import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; + +public class TestIcebergNessieCatalogWithBearerAuth + extends AbstractTestQueryFramework +{ + @Override + protected QueryRunner createQueryRunner() + throws Exception + { + Network network = closeAfterClass(Network.newNetwork()); + + KeycloakContainer keycloakContainer = closeAfterClass(KeycloakContainer.builder().withNetwork(network).build()); + keycloakContainer.start(); + + Map envVars = ImmutableMap.builder() + .putAll(NessieContainer.DEFAULT_ENV_VARS) + .put("QUARKUS_OIDC_AUTH_SERVER_URL", KeycloakContainer.SERVER_URL + "/realms/master") + .put("QUARKUS_OIDC_CLIENT_ID", "projectnessie") + .put("NESSIE_SERVER_AUTHENTICATION_ENABLED", "true") + .buildOrThrow(); + + NessieContainer nessieContainer = closeAfterClass(NessieContainer.builder().withEnvVars(envVars).withNetwork(network).build()); + nessieContainer.start(); + + Path tempDir = Files.createTempDirectory("test_trino_nessie_catalog"); + closeAfterClass(() -> deleteRecursively(tempDir, ALLOW_INSECURE)); + + Map properties = ImmutableMap.builder() + .put("iceberg.catalog.type", "nessie") + .put("iceberg.nessie-catalog.uri", nessieContainer.getRestApiUri()) + .put("iceberg.nessie-catalog.default-warehouse-dir", tempDir.toString()) + .put("iceberg.nessie-catalog.authentication.type", "BEARER") + .put("iceberg.nessie-catalog.authentication.token", keycloakContainer.getAccessToken()) + .buildOrThrow(); + + return IcebergQueryRunner.builder() + .setBaseDataDir(Optional.of(tempDir)) + .setIcebergProperties(properties) + .build(); + } + + @Test + public void testWithValidAccessToken() + { + try (TestTable table = new TestTable(getQueryRunner()::execute, "test_valid_access_token", "(a INT, b VARCHAR)", ImmutableList.of("(1, 'a')"))) { + assertQuery("SELECT * FROM " + table.getName(), "VALUES(1, 'a')"); + } + } +} diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/KeycloakContainer.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/KeycloakContainer.java new file mode 100644 index 000000000000..43d5801b4218 --- /dev/null +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/KeycloakContainer.java @@ -0,0 +1,123 @@ +/* + * 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.plugin.iceberg.containers; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import io.airlift.log.Logger; +import io.trino.testing.containers.BaseTestContainer; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.KeycloakBuilder; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.representations.idm.RealmRepresentation; +import org.testcontainers.containers.Network; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class KeycloakContainer + extends BaseTestContainer +{ + private static final Logger log = Logger.get(KeycloakContainer.class); + + public static final String DEFAULT_IMAGE = "quay.io/keycloak/keycloak:21.1.2"; + public static final String DEFAULT_HOST_NAME = "keycloak"; + + public static final String DEFAULT_USER_NAME = "admin"; + public static final String DEFAULT_PASSWORD = "admin"; + + public static final int PORT = 8080; + public static final String SERVER_URL = "http://" + DEFAULT_HOST_NAME + ":" + PORT; + + public static Builder builder() + { + return new Builder(); + } + + private KeycloakContainer( + String image, + String hostName, + Set exposePorts, + Map filesToMount, + Map envVars, + Optional network, + int retryLimit) + { + super(image, hostName, exposePorts, filesToMount, envVars, network, retryLimit); + } + + @Override + protected void setupContainer() + { + super.setupContainer(); + withRunCommand(ImmutableList.of("start-dev")); + } + + @Override + public void start() + { + super.start(); + log.info("Keycloak container started with URL: %s", getUrl()); + } + + public String getUrl() + { + return "http://" + getMappedHostAndPortForExposedPort(PORT); + } + + public String getAccessToken() + { + String realm = "master"; + String clientId = "admin-cli"; + + try (Keycloak keycloak = KeycloakBuilder.builder() + .serverUrl(getUrl()) + .realm(realm) + .clientId(clientId) + .username(DEFAULT_USER_NAME) + .password(DEFAULT_PASSWORD) + .build()) { + RealmResource master = keycloak.realms().realm(realm); + RealmRepresentation masterRep = master.toRepresentation(); + // change access token lifespan from 1 minute (default) to 1 hour + // to keep the token alive in case testcase takes more than a minute to finish execution. + masterRep.setAccessTokenLifespan(3600); + master.update(masterRep); + return keycloak.tokenManager().grantToken().getToken(); + } + } + + public static class Builder + extends BaseTestContainer.Builder + { + private Builder() + { + this.image = DEFAULT_IMAGE; + this.hostName = DEFAULT_HOST_NAME; + this.exposePorts = ImmutableSet.of(PORT); + this.envVars = ImmutableMap.of( + "KEYCLOAK_ADMIN", DEFAULT_USER_NAME, + "KEYCLOAK_ADMIN_PASSWORD", DEFAULT_PASSWORD, + "KC_HOSTNAME_URL", SERVER_URL); + } + + @Override + public KeycloakContainer build() + { + return new KeycloakContainer(image, hostName, exposePorts, filesToMount, envVars, network, startupRetryLimit); + } + } +} diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/NessieContainer.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/NessieContainer.java index d5f27d8f0ff9..a50765d83c5b 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/NessieContainer.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/NessieContainer.java @@ -34,12 +34,23 @@ public class NessieContainer public static final int PORT = 19121; + public static final Map DEFAULT_ENV_VARS = ImmutableMap.of( + "QUARKUS_HTTP_PORT", String.valueOf(PORT), + "NESSIE_VERSION_STORE_TYPE", VERSION_STORE_TYPE); + public static Builder builder() { return new Builder(); } - private NessieContainer(String image, String hostName, Set exposePorts, Map filesToMount, Map envVars, Optional network, int retryLimit) + private NessieContainer( + String image, + String hostName, + Set exposePorts, + Map filesToMount, + Map envVars, + Optional network, + int retryLimit) { super(image, hostName, exposePorts, filesToMount, envVars, network, retryLimit); } @@ -64,7 +75,7 @@ private Builder() this.image = DEFAULT_IMAGE; this.hostName = DEFAULT_HOST_NAME; this.exposePorts = ImmutableSet.of(PORT); - this.envVars = ImmutableMap.of("QUARKUS_HTTP_PORT", String.valueOf(PORT), "NESSIE_VERSION_STORE_TYPE", VERSION_STORE_TYPE); + this.envVars = DEFAULT_ENV_VARS; } @Override