This is an automated email from the ASF dual-hosted git repository.

adutra pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git


The following commit(s) were added to refs/heads/main by this push:
     new 66ec47f2 Introduce Polaris Admin Tool (with bootstrap and purge 
commands) (#605)
66ec47f2 is described below

commit 66ec47f2bdabb3779338781a50c676e8e48d58cf
Author: Alexandre Dutra <[email protected]>
AuthorDate: Tue Jan 14 14:19:15 2025 +0100

    Introduce Polaris Admin Tool (with bootstrap and purge commands) (#605)
---
 LICENSE-BINARY-DIST                                |  2 +
 .../EclipseLinkPolarisMetaStoreManagerFactory.java |  5 +-
 gradle/projects.main.properties                    |  1 +
 .../LocalPolarisMetaStoreManagerFactory.java       | 46 ++++++-------
 .../core/persistence/MetaStoreManagerFactory.java  |  3 +-
 .../persistence/PolarisCredentialsBootstrap.java   | 60 ++++++++--------
 .../PolarisCredentialsBootstrapTest.java           | 22 ++++++
 quarkus/admin/README.md                            | 27 ++++++++
 quarkus/{server => admin}/build.gradle.kts         | 45 ++++++++----
 .../admin/src/main/docker/Dockerfile.jvm           | 42 +++++++-----
 .../polaris/service/quarkus/admin/BaseCommand.java | 54 +++++++++++++++
 .../service/quarkus/admin/BootstrapCommand.java    | 80 ++++++++++++++++++++++
 .../service/quarkus/admin/PolarisAdminTool.java    | 54 +++++++++++++++
 .../quarkus/admin/PolarisVersionProvider.java      | 30 ++++++++
 .../service/quarkus/admin/PurgeCommand.java        | 49 +++++++++++++
 .../src/main/resources/application.properties      |  5 +-
 .../quarkus/admin/BootstrapCommandTest.java        | 47 +++++++++++++
 .../service/quarkus/admin/PurgeCommandTest.java    | 36 ++++++++++
 quarkus/server/README.md                           | 12 +++-
 quarkus/server/build.gradle.kts                    | 17 +++++
 .../src/main/resources/application.properties      |  1 +
 .../quarkus/catalog/BasePolarisCatalogTest.java    |  4 +-
 .../test/PolarisIntegrationTestFixture.java        |  4 +-
 .../InMemoryPolarisMetaStoreManagerFactory.java    | 11 ++-
 24 files changed, 567 insertions(+), 90 deletions(-)

diff --git a/LICENSE-BINARY-DIST b/LICENSE-BINARY-DIST
index 1165f96e..205a3eed 100644
--- a/LICENSE-BINARY-DIST
+++ b/LICENSE-BINARY-DIST
@@ -280,6 +280,7 @@ commons-io:commons-io
 commons-logging:commons-logging
 commons-net:commons-net
 dev.failsafe:failsafe
+info.picocli:picocli
 io.airlift:aircompressor
 io.grpc:grpc-alts
 io.grpc:grpc-api
@@ -400,6 +401,7 @@ io.quarkus:quarkus-micrometer-registry-prometheus
 io.quarkus:quarkus-mutiny
 io.quarkus:quarkus-netty
 io.quarkus:quarkus-opentelemetry
+io.quarkus:quarkus-picocli
 io.quarkus:quarkus-reactive-routes
 io.quarkus:quarkus-rest
 io.quarkus:quarkus-rest-common
diff --git 
a/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/EclipseLinkPolarisMetaStoreManagerFactory.java
 
b/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/EclipseLinkPolarisMetaStoreManagerFactory.java
index ade392be..5e27dddc 100644
--- 
a/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/EclipseLinkPolarisMetaStoreManagerFactory.java
+++ 
b/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/EclipseLinkPolarisMetaStoreManagerFactory.java
@@ -20,6 +20,7 @@ package 
org.apache.polaris.extension.persistence.impl.eclipselink;
 
 import io.smallrye.common.annotation.Identifier;
 import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 import java.nio.file.Path;
@@ -28,6 +29,7 @@ import org.apache.polaris.core.PolarisConfigurationStore;
 import org.apache.polaris.core.PolarisDiagnostics;
 import org.apache.polaris.core.context.RealmContext;
 import org.apache.polaris.core.persistence.LocalPolarisMetaStoreManagerFactory;
+import org.apache.polaris.core.persistence.PolarisCredentialsBootstrap;
 import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
 import org.apache.polaris.core.persistence.PolarisMetaStoreSession;
 import org.apache.polaris.core.storage.PolarisStorageIntegrationProvider;
@@ -70,6 +72,7 @@ public class EclipseLinkPolarisMetaStoreManagerFactory
   protected PolarisMetaStoreSession createMetaStoreSession(
       @Nonnull PolarisEclipseLinkStore store,
       @Nonnull RealmContext realmContext,
+      @Nullable PolarisCredentialsBootstrap credentialsBootstrap,
       @Nonnull PolarisDiagnostics diagnostics) {
     return new PolarisEclipseLinkMetaStoreSessionImpl(
         store,
@@ -77,7 +80,7 @@ public class EclipseLinkPolarisMetaStoreManagerFactory
         realmContext,
         configurationFile(),
         persistenceUnitName(),
-        secretsGenerator(realmContext),
+        secretsGenerator(realmContext, credentialsBootstrap),
         diagnostics);
   }
 
diff --git a/gradle/projects.main.properties b/gradle/projects.main.properties
index a6158793..193d7670 100644
--- a/gradle/projects.main.properties
+++ b/gradle/projects.main.properties
@@ -25,6 +25,7 @@ polaris-api-management-service=api/management-service
 polaris-service-common=service/common
 polaris-quarkus-service=quarkus/service
 polaris-quarkus-server=quarkus/server
+polaris-quarkus-admin=quarkus/admin
 polaris-eclipselink=extension/persistence/eclipselink
 polaris-jpa-model=extension/persistence/jpa-model
 polaris-tests=integration-tests
diff --git 
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/LocalPolarisMetaStoreManagerFactory.java
 
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/LocalPolarisMetaStoreManagerFactory.java
index bdea45ed..59d326dd 100644
--- 
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/LocalPolarisMetaStoreManagerFactory.java
+++ 
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/LocalPolarisMetaStoreManagerFactory.java
@@ -19,6 +19,7 @@
 package org.apache.polaris.core.persistence;
 
 import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
 import java.time.Clock;
 import java.util.HashMap;
 import java.util.List;
@@ -58,8 +59,6 @@ public abstract class 
LocalPolarisMetaStoreManagerFactory<StoreType>
   private final PolarisDiagnostics diagnostics;
   private final Clock clock;
 
-  private boolean bootstrap;
-
   protected LocalPolarisMetaStoreManagerFactory(
       PolarisConfigurationStore configurationStore, PolarisDiagnostics 
diagnostics, Clock clock) {
     this.configurationStore = configurationStore;
@@ -72,21 +71,26 @@ public abstract class 
LocalPolarisMetaStoreManagerFactory<StoreType>
   protected abstract PolarisMetaStoreSession createMetaStoreSession(
       @Nonnull StoreType store,
       @Nonnull RealmContext realmContext,
+      @Nullable PolarisCredentialsBootstrap credentialsBootstrap,
       @Nonnull PolarisDiagnostics diagnostics);
 
-  protected PrincipalSecretsGenerator secretsGenerator(RealmContext 
realmContext) {
-    if (bootstrap) {
-      return 
PrincipalSecretsGenerator.bootstrap(realmContext.getRealmIdentifier());
+  protected PrincipalSecretsGenerator secretsGenerator(
+      RealmContext realmContext, @Nullable PolarisCredentialsBootstrap 
credentialsBootstrap) {
+    if (credentialsBootstrap != null) {
+      return PrincipalSecretsGenerator.bootstrap(
+          realmContext.getRealmIdentifier(), credentialsBootstrap);
     } else {
       return PrincipalSecretsGenerator.RANDOM_SECRETS;
     }
   }
 
-  private void initializeForRealm(RealmContext realmContext) {
+  private void initializeForRealm(
+      RealmContext realmContext, PolarisCredentialsBootstrap 
credentialsBootstrap) {
     final StoreType backingStore = createBackingStore(diagnostics);
     sessionSupplierMap.put(
         realmContext.getRealmIdentifier(),
-        () -> createMetaStoreSession(backingStore, realmContext, diagnostics));
+        () ->
+            createMetaStoreSession(backingStore, realmContext, 
credentialsBootstrap, diagnostics));
 
     PolarisMetaStoreManager metaStoreManager =
         new PolarisMetaStoreManagerImpl(realmContext, diagnostics, 
configurationStore, clock);
@@ -94,23 +98,19 @@ public abstract class 
LocalPolarisMetaStoreManagerFactory<StoreType>
   }
 
   @Override
-  public synchronized Map<String, PrincipalSecretsResult> 
bootstrapRealms(List<String> realms) {
+  public synchronized Map<String, PrincipalSecretsResult> bootstrapRealms(
+      List<String> realms, PolarisCredentialsBootstrap credentialsBootstrap) {
     Map<String, PrincipalSecretsResult> results = new HashMap<>();
 
-    bootstrap = true;
-    try {
-      for (String realm : realms) {
-        RealmContext realmContext = () -> realm;
-        if 
(!metaStoreManagerMap.containsKey(realmContext.getRealmIdentifier())) {
-          initializeForRealm(realmContext);
-          PrincipalSecretsResult secretsResult =
-              bootstrapServiceAndCreatePolarisPrincipalForRealm(
-                  realmContext, 
metaStoreManagerMap.get(realmContext.getRealmIdentifier()));
-          results.put(realmContext.getRealmIdentifier(), secretsResult);
-        }
+    for (String realm : realms) {
+      RealmContext realmContext = () -> realm;
+      if (!metaStoreManagerMap.containsKey(realmContext.getRealmIdentifier())) 
{
+        initializeForRealm(realmContext, credentialsBootstrap);
+        PrincipalSecretsResult secretsResult =
+            bootstrapServiceAndCreatePolarisPrincipalForRealm(
+                realmContext, 
metaStoreManagerMap.get(realmContext.getRealmIdentifier()));
+        results.put(realmContext.getRealmIdentifier(), secretsResult);
       }
-    } finally {
-      bootstrap = false;
     }
 
     return results;
@@ -134,7 +134,7 @@ public abstract class 
LocalPolarisMetaStoreManagerFactory<StoreType>
   public synchronized PolarisMetaStoreManager getOrCreateMetaStoreManager(
       RealmContext realmContext) {
     if (!metaStoreManagerMap.containsKey(realmContext.getRealmIdentifier())) {
-      initializeForRealm(realmContext);
+      initializeForRealm(realmContext, null);
       checkPolarisServiceBootstrappedForRealm(
           realmContext, 
metaStoreManagerMap.get(realmContext.getRealmIdentifier()));
     }
@@ -145,7 +145,7 @@ public abstract class 
LocalPolarisMetaStoreManagerFactory<StoreType>
   public synchronized Supplier<PolarisMetaStoreSession> 
getOrCreateSessionSupplier(
       RealmContext realmContext) {
     if (!sessionSupplierMap.containsKey(realmContext.getRealmIdentifier())) {
-      initializeForRealm(realmContext);
+      initializeForRealm(realmContext, null);
       checkPolarisServiceBootstrappedForRealm(
           realmContext, 
metaStoreManagerMap.get(realmContext.getRealmIdentifier()));
     } else {
diff --git 
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/MetaStoreManagerFactory.java
 
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/MetaStoreManagerFactory.java
index 5d4691a5..4e37f034 100644
--- 
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/MetaStoreManagerFactory.java
+++ 
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/MetaStoreManagerFactory.java
@@ -37,7 +37,8 @@ public interface MetaStoreManagerFactory {
 
   EntityCache getOrCreateEntityCache(RealmContext realmContext);
 
-  Map<String, PrincipalSecretsResult> bootstrapRealms(List<String> realms);
+  Map<String, PrincipalSecretsResult> bootstrapRealms(
+      List<String> realms, PolarisCredentialsBootstrap credentialsBootstrap);
 
   /** Purge all metadata for the realms provided */
   void purgeRealms(List<String> realms);
diff --git 
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisCredentialsBootstrap.java
 
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisCredentialsBootstrap.java
index fb702812..dc4f5b87 100644
--- 
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisCredentialsBootstrap.java
+++ 
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisCredentialsBootstrap.java
@@ -35,6 +35,9 @@ import org.apache.polaris.core.entity.PolarisPrincipalSecrets;
  */
 public class PolarisCredentialsBootstrap {
 
+  public static final PolarisCredentialsBootstrap EMPTY =
+      new PolarisCredentialsBootstrap(new HashMap<>());
+
   /**
    * Parse credentials from the system property {@code 
polaris.bootstrap.credentials} or the
    * environment variable {@code POLARIS_BOOTSTRAP_CREDENTIALS}, whichever is 
set.
@@ -55,35 +58,38 @@ public class PolarisCredentialsBootstrap {
    * </pre>
    */
   public static PolarisCredentialsBootstrap fromString(@Nullable String 
credentialsString) {
+    return credentialsString != null && !credentialsString.isBlank()
+        ? 
fromList(Splitter.on(';').trimResults().splitToList(credentialsString))
+        : EMPTY;
+  }
+
+  /**
+   * Parse a list of credentials; each element should be in the format: {@code
+   * realm,principal,clientId,clientSecret}.
+   */
+  public static PolarisCredentialsBootstrap fromList(List<String> 
credentialsList) {
     Map<String, Map<String, Map.Entry<String, String>>> credentials = new 
HashMap<>();
-    if (credentialsString != null && !credentialsString.isBlank()) {
-      Splitter.on(';')
-          .trimResults()
-          .splitToList(credentialsString)
-          .forEach(
-              quadruple -> {
-                if (!quadruple.isBlank()) {
-                  List<String> parts = 
Splitter.on(',').trimResults().splitToList(quadruple);
-                  if (parts.size() != 4) {
-                    throw new IllegalArgumentException("Invalid credentials 
format: " + quadruple);
-                  }
-                  String realmName = parts.get(0);
-                  String principalName = parts.get(1);
-                  String clientId = parts.get(2);
-                  String clientSecret = parts.get(3);
-                  credentials
-                      .computeIfAbsent(realmName, k -> new HashMap<>())
-                      .merge(
-                          principalName,
-                          new SimpleEntry<>(clientId, clientSecret),
-                          (a, b) -> {
-                            throw new IllegalArgumentException(
-                                "Duplicate principal: " + principalName);
-                          });
-                }
-              });
+    for (String quadruple : credentialsList) {
+      if (!quadruple.isBlank()) {
+        List<String> parts = 
Splitter.on(',').trimResults().splitToList(quadruple);
+        if (parts.size() != 4) {
+          throw new IllegalArgumentException("Invalid credentials format: " + 
quadruple);
+        }
+        String realmName = parts.get(0);
+        String principalName = parts.get(1);
+        String clientId = parts.get(2);
+        String clientSecret = parts.get(3);
+        credentials
+            .computeIfAbsent(realmName, k -> new HashMap<>())
+            .merge(
+                principalName,
+                new SimpleEntry<>(clientId, clientSecret),
+                (a, b) -> {
+                  throw new IllegalArgumentException("Duplicate principal: " + 
principalName);
+                });
+      }
     }
-    return new PolarisCredentialsBootstrap(credentials);
+    return credentials.isEmpty() ? EMPTY : new 
PolarisCredentialsBootstrap(credentials);
   }
 
   @VisibleForTesting final Map<String, Map<String, Map.Entry<String, String>>> 
credentials;
diff --git 
a/polaris-core/src/test/java/org/apache/polaris/core/persistence/PolarisCredentialsBootstrapTest.java
 
b/polaris-core/src/test/java/org/apache/polaris/core/persistence/PolarisCredentialsBootstrapTest.java
index bd4c57c9..4cba20a7 100644
--- 
a/polaris-core/src/test/java/org/apache/polaris/core/persistence/PolarisCredentialsBootstrapTest.java
+++ 
b/polaris-core/src/test/java/org/apache/polaris/core/persistence/PolarisCredentialsBootstrapTest.java
@@ -22,6 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.util.Comparator;
+import java.util.List;
 import org.apache.polaris.core.entity.PolarisPrincipalSecrets;
 import org.junit.jupiter.api.Test;
 
@@ -87,6 +88,27 @@ class PolarisCredentialsBootstrapTest {
         .contains(new PolarisPrincipalSecrets(123, "client2a", "secret2a", 
"secret2a"));
   }
 
+  @Test
+  void getSecretsValidList() {
+    PolarisCredentialsBootstrap credentials =
+        PolarisCredentialsBootstrap.fromList(
+            List.of(
+                "realm1,user1a,client1a,secret1a",
+                "realm1,user1b,client1b,secret1b",
+                "realm2,user2a,client2a,secret2a"));
+    assertThat(credentials.getSecrets("realm1", 123, "nonexistent")).isEmpty();
+    assertThat(credentials.getSecrets("nonexistent", 123, "user1a")).isEmpty();
+    assertThat(credentials.getSecrets("realm1", 123, "user1a"))
+        .usingValueComparator(comparator)
+        .contains(new PolarisPrincipalSecrets(123, "client1a", "secret1a", 
"secret1a"));
+    assertThat(credentials.getSecrets("realm1", 123, "user1b"))
+        .usingValueComparator(comparator)
+        .contains(new PolarisPrincipalSecrets(123, "client1b", "secret1b", 
"secret1b"));
+    assertThat(credentials.getSecrets("realm2", 123, "user2a"))
+        .usingValueComparator(comparator)
+        .contains(new PolarisPrincipalSecrets(123, "client2a", "secret2a", 
"secret2a"));
+  }
+
   @Test
   void getSecretsValidSystemProperty() {
     PolarisCredentialsBootstrap credentials = 
PolarisCredentialsBootstrap.fromEnvironment();
diff --git a/quarkus/admin/README.md b/quarkus/admin/README.md
new file mode 100644
index 00000000..1058f91f
--- /dev/null
+++ b/quarkus/admin/README.md
@@ -0,0 +1,27 @@
+# Polaris Admin Tool
+
+This module contains a maintenance tool for performing administrative tasks on 
the Polaris database.
+It is a Quarkus application that can be used to perform various maintenance 
tasks targeting the
+Polaris database directly.
+
+Building this module will create a runnable uber-jar that can be executed from 
the command line.
+
+To also build the Docker image, you can use the following command:
+
+```shell
+./gradlew :polaris-quarkus-admin:assemble -Dquarkus.container-image.build=true
+```
+
+## Running the Admin Tool
+
+The admin tool can be run from the command line using the following command:
+
+```shell
+java -jar polaris-quarkus-admin-<version>-runner.jar --help
+```
+
+Using the Docker image, you can run the admin tool with the following command:
+
+```shell
+docker run --rm -it apache/polaris-admin-tool:<version> --help
+```
\ No newline at end of file
diff --git a/quarkus/server/build.gradle.kts b/quarkus/admin/build.gradle.kts
similarity index 59%
copy from quarkus/server/build.gradle.kts
copy to quarkus/admin/build.gradle.kts
index 7f08e41c..f96066c3 100644
--- a/quarkus/server/build.gradle.kts
+++ b/quarkus/admin/build.gradle.kts
@@ -17,40 +17,61 @@
  * under the License.
  */
 
+import io.quarkus.gradle.tasks.QuarkusBuild
+
 plugins {
   alias(libs.plugins.quarkus)
   alias(libs.plugins.openapi.generator)
   id("polaris-server")
   id("polaris-license-report")
-  id("distribution")
 }
 
 dependencies {
   implementation(project(":polaris-core"))
+  implementation(project(":polaris-version"))
   implementation(project(":polaris-api-management-service"))
   implementation(project(":polaris-api-iceberg-service"))
   implementation(project(":polaris-service-common"))
   implementation(project(":polaris-quarkus-service"))
 
-  // enforce the Quarkus _platform_ here, to get a consistent and validated 
set of dependencies
   implementation(enforcedPlatform(libs.quarkus.bom))
+  implementation("io.quarkus:quarkus-picocli")
   implementation("io.quarkus:quarkus-container-image-docker")
 
+  implementation("org.jboss.slf4j:slf4j-jboss-logmanager")
+
   // override dnsjava version in dependencies due to 
https://github.com/dnsjava/dnsjava/issues/329
   implementation(platform(libs.dnsjava))
-}
 
-tasks.named("distZip") { dependsOn("quarkusBuild") }
+  testImplementation(enforcedPlatform(libs.quarkus.bom))
+  testImplementation("io.quarkus:quarkus-junit5")
+}
 
-tasks.named("distTar") { dependsOn("quarkusBuild") }
+quarkus {
+  quarkusBuildProperties.put("quarkus.package.type", "uber-jar")
+  // Pull manifest attributes from the "main" `jar` task to get the
+  // release-information into the jars generated by Quarkus.
+  quarkusBuildProperties.putAll(
+    provider {
+      tasks
+        .named("jar", Jar::class.java)
+        .get()
+        .manifest
+        .attributes
+        .map { e -> "quarkus.package.jar.manifest.attributes.\"${e.key}\"" to 
e.value.toString() }
+        .toMap()
+    }
+  )
+}
 
-distributions {
-  main {
-    contents {
-      from(project.layout.buildDirectory.dir("quarkus-app"))
-      from("../../NOTICE")
-      from("../../LICENSE-BINARY-DIST").rename("LICENSE-BINARY-DIST", 
"LICENSE")
-      exclude("lib/main/io.quarkus.quarkus-container-image*")
+publishing {
+  publications {
+    named<MavenPublication>("maven") {
+      val quarkusBuild = tasks.getByName<QuarkusBuild>("quarkusBuild")
+      artifact(quarkusBuild.runnerJar) {
+        classifier = "runner"
+        builtBy(quarkusBuild)
+      }
     }
   }
 }
diff --git a/gradle/projects.main.properties 
b/quarkus/admin/src/main/docker/Dockerfile.jvm
similarity index 50%
copy from gradle/projects.main.properties
copy to quarkus/admin/src/main/docker/Dockerfile.jvm
index a6158793..9f992a30 100644
--- a/gradle/projects.main.properties
+++ b/quarkus/admin/src/main/docker/Dockerfile.jvm
@@ -7,7 +7,7 @@
 # "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
+#  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
@@ -16,23 +16,27 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-#
+FROM registry.access.redhat.com/ubi9/openjdk-21:1.20-2.1726695192
+
+LABEL org.opencontainers.image.source=https://github.com/apache/polaris
+LABEL org.opencontainers.image.description="Apache Polaris (incubating) Admin 
Tool"
+LABEL org.opencontainers.image.licenses=Apache-2.0
+
+ENV LANGUAGE='en_US:en'
+
+USER root
+RUN groupadd --gid 10001 polaris \
+      && useradd --uid 10000 --gid polaris polaris \
+      && chown -R polaris:polaris /opt/jboss/container \
+      && chown -R polaris:polaris /deployments
+
+USER polaris
+WORKDIR /deployments
+ENV USER=polaris
+ENV UID=10000
+ENV HOME=/home/polaris
+ENV PWD=/deployments
 
-polaris-core=polaris-core
-polaris-api-iceberg-service=api/iceberg-service
-polaris-api-management-model=api/management-model
-polaris-api-management-service=api/management-service
-polaris-service-common=service/common
-polaris-quarkus-service=quarkus/service
-polaris-quarkus-server=quarkus/server
-polaris-eclipselink=extension/persistence/eclipselink
-polaris-jpa-model=extension/persistence/jpa-model
-polaris-tests=integration-tests
-aggregated-license-report=aggregated-license-report
-polaris-immutables=tools/immutables
-polaris-container-spec-helper=tools/container-spec-helper
-polaris-version=tools/version
+COPY --chown=polaris:polaris build/*-runner.jar 
/deployments/polaris-server-admin-tool.jar
 
-polaris-config-docs-annotations=tools/config-docs/annotations
-polaris-config-docs-generator=tools/config-docs/generator
-polaris-config-docs-site=tools/config-docs/site
+ENTRYPOINT [ "java", "-jar", "/deployments/polaris-server-admin-tool.jar" ]
\ No newline at end of file
diff --git 
a/quarkus/admin/src/main/java/org/apache/polaris/service/quarkus/admin/BaseCommand.java
 
b/quarkus/admin/src/main/java/org/apache/polaris/service/quarkus/admin/BaseCommand.java
new file mode 100644
index 00000000..6b690c76
--- /dev/null
+++ 
b/quarkus/admin/src/main/java/org/apache/polaris/service/quarkus/admin/BaseCommand.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.polaris.service.quarkus.admin;
+
+import jakarta.inject.Inject;
+import java.util.concurrent.Callable;
+import org.apache.polaris.core.persistence.MetaStoreManagerFactory;
+import 
org.apache.polaris.service.quarkus.persistence.QuarkusPersistenceConfiguration;
+import picocli.CommandLine.Model.CommandSpec;
+import picocli.CommandLine.Spec;
+
+public abstract class BaseCommand implements Callable<Integer> {
+
+  public static final Integer EXIT_CODE_BOOTSTRAP_ERROR = 3;
+  public static final Integer EXIT_CODE_PURGE_ERROR = 4;
+
+  @Inject QuarkusPersistenceConfiguration persistenceConfiguration;
+
+  @Inject MetaStoreManagerFactory metaStoreManagerFactory;
+
+  @Spec CommandSpec spec;
+
+  protected void warnOnInMemory() {
+    if (persistenceConfiguration.type().equalsIgnoreCase("in-memory")) {
+      spec.commandLine()
+          .getErr()
+          .println(
+              spec.commandLine()
+                  .getColorScheme()
+                  .errorText(
+                      """
+                      
*********************************************************************************************
+                      ** Running the Admin Tool on a Polaris instance with 
in-memory persistence is meaningless! **
+                      
*********************************************************************************************
+                      """));
+    }
+  }
+}
diff --git 
a/quarkus/admin/src/main/java/org/apache/polaris/service/quarkus/admin/BootstrapCommand.java
 
b/quarkus/admin/src/main/java/org/apache/polaris/service/quarkus/admin/BootstrapCommand.java
new file mode 100644
index 00000000..5d9f5bec
--- /dev/null
+++ 
b/quarkus/admin/src/main/java/org/apache/polaris/service/quarkus/admin/BootstrapCommand.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.polaris.service.quarkus.admin;
+
+import java.util.List;
+import java.util.Map;
+import 
org.apache.polaris.core.auth.PolarisSecretsManager.PrincipalSecretsResult;
+import org.apache.polaris.core.persistence.PolarisCredentialsBootstrap;
+import picocli.CommandLine;
+
[email protected](
+    name = "bootstrap",
+    mixinStandardHelpOptions = true,
+    description = "Bootstraps realms and principal credentials.")
+public class BootstrapCommand extends BaseCommand {
+
+  @CommandLine.Option(
+      names = {"-r", "--realm"},
+      required = true,
+      description = "The name of a realm to bootstrap.")
+  List<String> realms;
+
+  @CommandLine.Option(
+      names = {"-c", "--credential"},
+      description =
+          "Principal credentials to bootstrap. Must be of the form 
'realm,userName,clientId,clientSecret'.")
+  List<String> credentials;
+
+  @Override
+  public Integer call() {
+    warnOnInMemory();
+
+    PolarisCredentialsBootstrap credentialsBootstrap =
+        credentials == null || credentials.isEmpty()
+            ? PolarisCredentialsBootstrap.EMPTY
+            : PolarisCredentialsBootstrap.fromList(credentials);
+
+    // Execute the bootstrap
+    Map<String, PrincipalSecretsResult> results =
+        metaStoreManagerFactory.bootstrapRealms(realms, credentialsBootstrap);
+
+    // Log any errors:
+    boolean success = true;
+    for (Map.Entry<String, PrincipalSecretsResult> result : 
results.entrySet()) {
+      if (!result.getValue().isSuccess()) {
+        String realm = result.getKey();
+        spec.commandLine()
+            .getErr()
+            .printf(
+                "Bootstrapping '%s' failed: %s%n",
+                realm, result.getValue().getReturnStatus().toString());
+        success = false;
+      }
+    }
+
+    if (success) {
+      spec.commandLine().getOut().println("Bootstrap completed successfully.");
+      return 0;
+    } else {
+      spec.commandLine().getErr().println("Bootstrap encountered errors during 
operation.");
+      return EXIT_CODE_BOOTSTRAP_ERROR;
+    }
+  }
+}
diff --git 
a/quarkus/admin/src/main/java/org/apache/polaris/service/quarkus/admin/PolarisAdminTool.java
 
b/quarkus/admin/src/main/java/org/apache/polaris/service/quarkus/admin/PolarisAdminTool.java
new file mode 100644
index 00000000..3b332735
--- /dev/null
+++ 
b/quarkus/admin/src/main/java/org/apache/polaris/service/quarkus/admin/PolarisAdminTool.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.polaris.service.quarkus.admin;
+
+import io.quarkus.picocli.runtime.annotations.TopCommand;
+import java.io.PrintWriter;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.HelpCommand;
+
+@TopCommand
+@Command(
+    name = "polaris-quarkus-admin-runner.jar",
+    mixinStandardHelpOptions = true,
+    versionProvider = PolarisVersionProvider.class,
+    description = "Polaris Admin Tool",
+    subcommands = {
+      HelpCommand.class,
+      BootstrapCommand.class,
+      PurgeCommand.class,
+    })
+public class PolarisAdminTool extends BaseCommand {
+
+  @Override
+  public Integer call() {
+    return info();
+  }
+
+  private int info() {
+    warnOnInMemory();
+
+    PrintWriter out = spec.commandLine().getOut();
+
+    out.println("Polaris administration & maintenance tool.");
+    out.println("Use the 'help' command.");
+    out.println();
+    return 0;
+  }
+}
diff --git 
a/quarkus/admin/src/main/java/org/apache/polaris/service/quarkus/admin/PolarisVersionProvider.java
 
b/quarkus/admin/src/main/java/org/apache/polaris/service/quarkus/admin/PolarisVersionProvider.java
new file mode 100644
index 00000000..8c252113
--- /dev/null
+++ 
b/quarkus/admin/src/main/java/org/apache/polaris/service/quarkus/admin/PolarisVersionProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.polaris.service.quarkus.admin;
+
+import org.apache.polaris.version.PolarisVersion;
+import picocli.CommandLine.IVersionProvider;
+
+public class PolarisVersionProvider implements IVersionProvider {
+
+  @Override
+  public String[] getVersion() {
+    return new String[] {PolarisVersion.polarisVersionString()};
+  }
+}
diff --git 
a/quarkus/admin/src/main/java/org/apache/polaris/service/quarkus/admin/PurgeCommand.java
 
b/quarkus/admin/src/main/java/org/apache/polaris/service/quarkus/admin/PurgeCommand.java
new file mode 100644
index 00000000..63963c12
--- /dev/null
+++ 
b/quarkus/admin/src/main/java/org/apache/polaris/service/quarkus/admin/PurgeCommand.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.polaris.service.quarkus.admin;
+
+import java.util.List;
+import picocli.CommandLine;
+
[email protected](
+    name = "purge",
+    mixinStandardHelpOptions = true,
+    description = "Purge principal credentials.")
+public class PurgeCommand extends BaseCommand {
+
+  @CommandLine.Option(
+      names = {"-r", "--realm"},
+      required = true,
+      description = "The name of a realm to purge.")
+  List<String> realms;
+
+  @Override
+  public Integer call() {
+    warnOnInMemory();
+
+    try {
+      metaStoreManagerFactory.purgeRealms(realms);
+      spec.commandLine().getOut().println("Purge completed successfully.");
+      return 0;
+    } catch (Exception e) {
+      spec.commandLine().getErr().println("Purge encountered errors during 
operation.");
+      return EXIT_CODE_PURGE_ERROR;
+    }
+  }
+}
diff --git a/quarkus/server/src/main/resources/application.properties 
b/quarkus/admin/src/main/resources/application.properties
similarity index 85%
copy from quarkus/server/src/main/resources/application.properties
copy to quarkus/admin/src/main/resources/application.properties
index 3f5018a7..8f963cc5 100644
--- a/quarkus/server/src/main/resources/application.properties
+++ b/quarkus/admin/src/main/resources/application.properties
@@ -17,10 +17,11 @@
 # under the License.
 #
 
-quarkus.application.name=Apache Polaris Server (incubating)
+quarkus.application.name=Apache Polaris Admin Tool (incubating)
 
 quarkus.container-image.build=false
 quarkus.container-image.push=false
 quarkus.container-image.registry=docker.io
 quarkus.container-image.group=apache
-quarkus.container-image.name=polaris
+quarkus.container-image.name=polaris-admin-tool
+quarkus.container-image.additional-tags=latest
diff --git 
a/quarkus/admin/src/test/java/org/apache/polaris/service/quarkus/admin/BootstrapCommandTest.java
 
b/quarkus/admin/src/test/java/org/apache/polaris/service/quarkus/admin/BootstrapCommandTest.java
new file mode 100644
index 00000000..0e964c07
--- /dev/null
+++ 
b/quarkus/admin/src/test/java/org/apache/polaris/service/quarkus/admin/BootstrapCommandTest.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.polaris.service.quarkus.admin;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.quarkus.test.junit.main.Launch;
+import io.quarkus.test.junit.main.LaunchResult;
+import io.quarkus.test.junit.main.QuarkusMainTest;
+import org.junit.jupiter.api.Test;
+
+@QuarkusMainTest
+class BootstrapCommandTest {
+
+  @Test
+  @Launch(
+      value = {
+        "bootstrap",
+        "-r",
+        "realm1",
+        "-r",
+        "realm2",
+        "-c",
+        "realm1,root,root,s3cr3t",
+        "-c",
+        "realm2,root,root,s3cr3t"
+      })
+  public void testBootstrap(LaunchResult result) {
+    assertThat(result.getOutput()).contains("Bootstrap completed 
successfully.");
+  }
+}
diff --git 
a/quarkus/admin/src/test/java/org/apache/polaris/service/quarkus/admin/PurgeCommandTest.java
 
b/quarkus/admin/src/test/java/org/apache/polaris/service/quarkus/admin/PurgeCommandTest.java
new file mode 100644
index 00000000..902cf8a1
--- /dev/null
+++ 
b/quarkus/admin/src/test/java/org/apache/polaris/service/quarkus/admin/PurgeCommandTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.polaris.service.quarkus.admin;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.quarkus.test.junit.main.Launch;
+import io.quarkus.test.junit.main.LaunchResult;
+import io.quarkus.test.junit.main.QuarkusMainTest;
+import org.junit.jupiter.api.Test;
+
+@QuarkusMainTest
+class PurgeCommandTest {
+
+  @Test
+  @Launch(value = {"purge", "-r", "realm1", "-r", "realm2"})
+  public void testPurge(LaunchResult result) {
+    assertThat(result.getOutput()).contains("Purge completed successfully.");
+  }
+}
diff --git a/quarkus/server/README.md b/quarkus/server/README.md
index 7fb773ec..bac32962 100644
--- a/quarkus/server/README.md
+++ b/quarkus/server/README.md
@@ -8,5 +8,15 @@ To also build the Docker image, you can use the following 
command (a running Doc
 required):
 
 ```shell
-./gradlew :polaris-quarkus-server:build -Dquarkus.container-image.build=true
+./gradlew :polaris-quarkus-server:assemble -Dquarkus.container-image.build=true
 ```
+
+If you need to customize the Docker image, for example to push to a local 
registry, you can use the
+following command:
+
+```shell
+./gradlew :polaris-quarkus-server:build -Dquarkus.container-image.build=true \
+  -Dquarkus.container-image.registry=localhost:5001 \
+  -Dquarkus.container-image.group=apache \
+  -Dquarkus.container-image.name=polaris-local
+```
\ No newline at end of file
diff --git a/quarkus/server/build.gradle.kts b/quarkus/server/build.gradle.kts
index 7f08e41c..83e94435 100644
--- a/quarkus/server/build.gradle.kts
+++ b/quarkus/server/build.gradle.kts
@@ -40,6 +40,23 @@ dependencies {
   implementation(platform(libs.dnsjava))
 }
 
+quarkus {
+  quarkusBuildProperties.put("quarkus.package.type", "fast-jar")
+  // Pull manifest attributes from the "main" `jar` task to get the
+  // release-information into the jars generated by Quarkus.
+  quarkusBuildProperties.putAll(
+    provider {
+      tasks
+        .named("jar", Jar::class.java)
+        .get()
+        .manifest
+        .attributes
+        .map { e -> "quarkus.package.jar.manifest.attributes.\"${e.key}\"" to 
e.value.toString() }
+        .toMap()
+    }
+  )
+}
+
 tasks.named("distZip") { dependsOn("quarkusBuild") }
 
 tasks.named("distTar") { dependsOn("quarkusBuild") }
diff --git a/quarkus/server/src/main/resources/application.properties 
b/quarkus/server/src/main/resources/application.properties
index 3f5018a7..3c628e38 100644
--- a/quarkus/server/src/main/resources/application.properties
+++ b/quarkus/server/src/main/resources/application.properties
@@ -24,3 +24,4 @@ quarkus.container-image.push=false
 quarkus.container-image.registry=docker.io
 quarkus.container-image.group=apache
 quarkus.container-image.name=polaris
+quarkus.container-image.additional-tags=latest
diff --git 
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/BasePolarisCatalogTest.java
 
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/BasePolarisCatalogTest.java
index 20d0fd0b..f4c44545 100644
--- 
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/BasePolarisCatalogTest.java
+++ 
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/BasePolarisCatalogTest.java
@@ -81,6 +81,7 @@ import org.apache.polaris.core.entity.PolarisEntityType;
 import org.apache.polaris.core.entity.PrincipalEntity;
 import org.apache.polaris.core.entity.TaskEntity;
 import org.apache.polaris.core.persistence.MetaStoreManagerFactory;
+import org.apache.polaris.core.persistence.PolarisCredentialsBootstrap;
 import org.apache.polaris.core.persistence.PolarisEntityManager;
 import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
 import org.apache.polaris.core.persistence.PolarisMetaStoreSession;
@@ -310,7 +311,8 @@ public class BasePolarisCatalogTest extends 
CatalogTests<BasePolarisCatalog> {
       }
 
       @Override
-      public Map<String, PrincipalSecretsResult> bootstrapRealms(List<String> 
realms) {
+      public Map<String, PrincipalSecretsResult> bootstrapRealms(
+          List<String> realms, PolarisCredentialsBootstrap 
credentialsBootstrap) {
         throw new NotImplementedException("Bootstrapping realms is not 
supported");
       }
 
diff --git 
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/test/PolarisIntegrationTestFixture.java
 
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/test/PolarisIntegrationTestFixture.java
index 68f32758..b7e38dd4 100644
--- 
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/test/PolarisIntegrationTestFixture.java
+++ 
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/test/PolarisIntegrationTestFixture.java
@@ -40,6 +40,7 @@ import org.apache.polaris.core.entity.PolarisEntityConstants;
 import org.apache.polaris.core.entity.PolarisEntitySubType;
 import org.apache.polaris.core.entity.PolarisEntityType;
 import org.apache.polaris.core.entity.PolarisPrincipalSecrets;
+import org.apache.polaris.core.persistence.PolarisCredentialsBootstrap;
 import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
 import org.apache.polaris.core.persistence.PolarisMetaStoreSession;
 import 
org.apache.polaris.service.persistence.InMemoryPolarisMetaStoreManagerFactory;
@@ -95,7 +96,8 @@ public class PolarisIntegrationTestFixture {
 
   private PolarisPrincipalSecrets fetchAdminSecrets() {
     if (!(helper.metaStoreManagerFactory instanceof 
InMemoryPolarisMetaStoreManagerFactory)) {
-      helper.metaStoreManagerFactory.bootstrapRealms(List.of(realm));
+      helper.metaStoreManagerFactory.bootstrapRealms(
+          List.of(realm), PolarisCredentialsBootstrap.fromEnvironment());
     }
 
     RealmContext realmContext = () -> realm;
diff --git 
a/service/common/src/main/java/org/apache/polaris/service/persistence/InMemoryPolarisMetaStoreManagerFactory.java
 
b/service/common/src/main/java/org/apache/polaris/service/persistence/InMemoryPolarisMetaStoreManagerFactory.java
index 0c3cc6ec..995e84b9 100644
--- 
a/service/common/src/main/java/org/apache/polaris/service/persistence/InMemoryPolarisMetaStoreManagerFactory.java
+++ 
b/service/common/src/main/java/org/apache/polaris/service/persistence/InMemoryPolarisMetaStoreManagerFactory.java
@@ -20,6 +20,7 @@ package org.apache.polaris.service.persistence;
 
 import io.smallrye.common.annotation.Identifier;
 import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 import java.time.Clock;
@@ -33,6 +34,7 @@ import org.apache.polaris.core.PolarisDiagnostics;
 import 
org.apache.polaris.core.auth.PolarisSecretsManager.PrincipalSecretsResult;
 import org.apache.polaris.core.context.RealmContext;
 import org.apache.polaris.core.persistence.LocalPolarisMetaStoreManagerFactory;
+import org.apache.polaris.core.persistence.PolarisCredentialsBootstrap;
 import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
 import org.apache.polaris.core.persistence.PolarisMetaStoreSession;
 import org.apache.polaris.core.persistence.PolarisTreeMapMetaStoreSessionImpl;
@@ -75,9 +77,13 @@ public class InMemoryPolarisMetaStoreManagerFactory
   protected PolarisMetaStoreSession createMetaStoreSession(
       @Nonnull PolarisTreeMapStore store,
       @Nonnull RealmContext realmContext,
+      @Nullable PolarisCredentialsBootstrap credentialsBootstrap,
       @Nonnull PolarisDiagnostics diagnostics) {
     return new PolarisTreeMapMetaStoreSessionImpl(
-        store, storageIntegration, secretsGenerator(realmContext), 
diagnostics);
+        store,
+        storageIntegration,
+        secretsGenerator(realmContext, credentialsBootstrap),
+        diagnostics);
   }
 
   @Override
@@ -102,7 +108,8 @@ public class InMemoryPolarisMetaStoreManagerFactory
 
   private void bootstrapRealmAndPrintCredentials(String realmId) {
     Map<String, PrincipalSecretsResult> results =
-        this.bootstrapRealms(Collections.singletonList(realmId));
+        this.bootstrapRealms(
+            Collections.singletonList(realmId), 
PolarisCredentialsBootstrap.fromEnvironment());
     bootstrappedRealms.add(realmId);
 
     PrincipalSecretsResult principalSecrets = results.get(realmId);


Reply via email to