You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polaris.apache.org by yu...@apache.org on 2026/04/02 18:55:27 UTC

(polaris) branch main updated: Core: Use record for connection credentials (#4103)

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

yufei 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 ab61cef978 Core: Use record for connection credentials (#4103)
ab61cef978 is described below

commit ab61cef97806b599af6b3fb7c7c1f7ebce7f12a4
Author: Yufei Gu <yu...@apache.org>
AuthorDate: Thu Apr 2 11:55:22 2026 -0700

    Core: Use record for connection credentials (#4103)
---
 .../connection/ConnectionCredentials.java          | 53 ++++++----------------
 .../DefaultPolarisCredentialManager.java           |  2 +-
 .../BearerConnectionCredentialVendor.java          | 11 +++--
 .../ImplicitConnectionCredentialVendor.java        |  6 +--
 .../connection/OAuthClientCredentialVendor.java    | 11 +++--
 .../SigV4ConnectionCredentialVendor.java           | 16 ++++---
 .../DefaultPolarisCredentialManagerTest.java       | 19 +++-----
 7 files changed, 48 insertions(+), 70 deletions(-)

diff --git a/polaris-core/src/main/java/org/apache/polaris/core/credentials/connection/ConnectionCredentials.java b/polaris-core/src/main/java/org/apache/polaris/core/credentials/connection/ConnectionCredentials.java
index 1d86bf03c0..06d0a54466 100644
--- a/polaris-core/src/main/java/org/apache/polaris/core/credentials/connection/ConnectionCredentials.java
+++ b/polaris-core/src/main/java/org/apache/polaris/core/credentials/connection/ConnectionCredentials.java
@@ -18,12 +18,11 @@
  */
 package org.apache.polaris.core.credentials.connection;
 
-import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import java.time.Instant;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
 import org.apache.polaris.core.storage.StorageAccessConfig;
-import org.apache.polaris.immutables.PolarisImmutable;
 
 /**
  * Encapsulates credentials and configuration needed to connect to external federated catalogs.
@@ -33,52 +32,28 @@ import org.apache.polaris.immutables.PolarisImmutable;
  * other Iceberg REST catalogs).
  *
  * <p>Credentials may be temporary and include an expiration time.
- *
- * <p><b>Note:</b> This interface currently includes only {@code credentials} and {@code expiresAt}.
- * Additional fields like {@code extraProperties} and {@code internalProperties} (similar to {@link
- * StorageAccessConfig}) are not included for now but can be added later if needed for more complex
- * credential scenarios.
  */
-@PolarisImmutable
-public interface ConnectionCredentials {
-  /** Sensitive credential properties (e.g., access keys, tokens). */
-  Map<String, String> credentials();
+public record ConnectionCredentials(Map<String, String> credentials, Optional<Instant> expiresAt) {
 
-  /** Optional expiration time for the credentials. */
-  Optional<Instant> expiresAt();
+  public static final ConnectionCredentials EMPTY =
+      new ConnectionCredentials(Map.of(), Optional.empty());
 
   /**
-   * Get a credential value by property key.
-   *
-   * @param key the credential property to retrieve
-   * @return the credential value, or null if not present
+   * Creates ConnectionCredentials from a map of catalog access properties. Expiration timestamps
+   * are extracted and stored separately; credential properties are stored in the credentials map.
    */
-  default String get(CatalogAccessProperty key) {
-    return credentials().get(key.getPropertyName());
-  }
-
-  static ConnectionCredentials.Builder builder() {
-    return ImmutableConnectionCredentials.builder();
-  }
-
-  interface Builder {
-    @CanIgnoreReturnValue
-    Builder putCredential(String key, String value);
-
-    @CanIgnoreReturnValue
-    Builder expiresAt(Instant expiresAt);
-
-    default Builder put(CatalogAccessProperty key, String value) {
+  public static ConnectionCredentials of(Map<CatalogAccessProperty, String> properties) {
+    Map<String, String> credentials = new HashMap<>();
+    Instant expiresAt = null;
+    for (var entry : properties.entrySet()) {
+      CatalogAccessProperty key = entry.getKey();
       if (key.isExpirationTimestamp()) {
-        expiresAt(Instant.ofEpochMilli(Long.parseLong(value)));
+        expiresAt = Instant.ofEpochMilli(Long.parseLong(entry.getValue()));
       }
-
       if (key.isCredential()) {
-        return putCredential(key.getPropertyName(), value);
+        credentials.put(key.getPropertyName(), entry.getValue());
       }
-      return this;
     }
-
-    ConnectionCredentials build();
+    return new ConnectionCredentials(Map.copyOf(credentials), Optional.ofNullable(expiresAt));
   }
 }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/credentials/DefaultPolarisCredentialManager.java b/runtime/service/src/main/java/org/apache/polaris/service/credentials/DefaultPolarisCredentialManager.java
index 491be17164..39723aea50 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/credentials/DefaultPolarisCredentialManager.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/credentials/DefaultPolarisCredentialManager.java
@@ -85,7 +85,7 @@ public class DefaultPolarisCredentialManager implements PolarisCredentialManager
     } catch (UnsatisfiedResolutionException e) {
       // Silently ignore if no vendor found for this auth type for now to pass tests
       // TODO: add connection credential vendor for other auth types
-      return ConnectionCredentials.builder().build();
+      return ConnectionCredentials.EMPTY;
     } catch (ResolutionException e) {
       // No vendor found or ambiguous vendors
       // Multiple vendors found - need @Priority to disambiguate
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/BearerConnectionCredentialVendor.java b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/BearerConnectionCredentialVendor.java
index 75e1551f76..6de92c16c0 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/BearerConnectionCredentialVendor.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/BearerConnectionCredentialVendor.java
@@ -23,6 +23,7 @@ import jakarta.annotation.Nonnull;
 import jakarta.annotation.Priority;
 import jakarta.enterprise.context.RequestScoped;
 import jakarta.inject.Inject;
+import java.util.Map;
 import org.apache.polaris.core.connection.AuthenticationType;
 import org.apache.polaris.core.connection.BearerAuthenticationParametersDpo;
 import org.apache.polaris.core.connection.ConnectionConfigInfoDpo;
@@ -77,9 +78,11 @@ public class BearerConnectionCredentialVendor implements ConnectionCredentialVen
     // Bearer tokens don't expire from Polaris's perspective - set expiration to Long.MAX_VALUE
     // to indicate infinite validity. The token itself may have an expiration, but that's managed
     // by the token issuer, not Polaris.
-    return ConnectionCredentials.builder()
-        .put(CatalogAccessProperty.BEARER_TOKEN, bearerToken)
-        .put(CatalogAccessProperty.EXPIRES_AT_MS, String.valueOf(Long.MAX_VALUE))
-        .build();
+    return ConnectionCredentials.of(
+        Map.of(
+            CatalogAccessProperty.BEARER_TOKEN,
+            bearerToken,
+            CatalogAccessProperty.EXPIRES_AT_MS,
+            String.valueOf(Long.MAX_VALUE)));
   }
 }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/ImplicitConnectionCredentialVendor.java b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/ImplicitConnectionCredentialVendor.java
index ecef5f5bf1..23a142386e 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/ImplicitConnectionCredentialVendor.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/ImplicitConnectionCredentialVendor.java
@@ -22,6 +22,7 @@ import com.google.common.base.Preconditions;
 import jakarta.annotation.Nonnull;
 import jakarta.annotation.Priority;
 import jakarta.enterprise.context.RequestScoped;
+import java.util.Map;
 import org.apache.polaris.core.connection.AuthenticationType;
 import org.apache.polaris.core.connection.ConnectionConfigInfoDpo;
 import org.apache.polaris.core.connection.ImplicitAuthenticationParametersDpo;
@@ -61,8 +62,7 @@ public class ImplicitConnectionCredentialVendor implements ConnectionCredentialV
 
     // Return empty credentials for implicit (no authentication) type with expiration
     // Set expiration to Long.MAX_VALUE to indicate infinite validity
-    return ConnectionCredentials.builder()
-        .put(CatalogAccessProperty.EXPIRES_AT_MS, String.valueOf(Long.MAX_VALUE))
-        .build();
+    return ConnectionCredentials.of(
+        Map.of(CatalogAccessProperty.EXPIRES_AT_MS, String.valueOf(Long.MAX_VALUE)));
   }
 }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/OAuthClientCredentialVendor.java b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/OAuthClientCredentialVendor.java
index 993613259d..d43db39d35 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/OAuthClientCredentialVendor.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/OAuthClientCredentialVendor.java
@@ -24,6 +24,7 @@ import jakarta.annotation.Nonnull;
 import jakarta.annotation.Priority;
 import jakarta.enterprise.context.RequestScoped;
 import jakarta.inject.Inject;
+import java.util.Map;
 import org.apache.polaris.core.connection.AuthenticationType;
 import org.apache.polaris.core.connection.ConnectionConfigInfoDpo;
 import org.apache.polaris.core.connection.OAuthClientCredentialsParametersDpo;
@@ -84,9 +85,11 @@ public class OAuthClientCredentialVendor implements ConnectionCredentialVendor {
     // OAuth credentials don't expire from Polaris's perspective - set expiration to Long.MAX_VALUE
     // to indicate infinite validity. If the credential expires, users need to update the catalog
     // entity to rotate the credential.
-    return ConnectionCredentials.builder()
-        .put(CatalogAccessProperty.OAUTH2_CREDENTIAL, credential)
-        .put(CatalogAccessProperty.EXPIRES_AT_MS, String.valueOf(Long.MAX_VALUE))
-        .build();
+    return ConnectionCredentials.of(
+        Map.of(
+            CatalogAccessProperty.OAUTH2_CREDENTIAL,
+            credential,
+            CatalogAccessProperty.EXPIRES_AT_MS,
+            String.valueOf(Long.MAX_VALUE)));
   }
 }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/SigV4ConnectionCredentialVendor.java b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/SigV4ConnectionCredentialVendor.java
index e0e230e4b9..eba30f2159 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/SigV4ConnectionCredentialVendor.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/SigV4ConnectionCredentialVendor.java
@@ -24,6 +24,8 @@ import jakarta.annotation.Nonnull;
 import jakarta.annotation.Priority;
 import jakarta.enterprise.context.RequestScoped;
 import jakarta.inject.Inject;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Optional;
 import org.apache.polaris.core.connection.AuthenticationType;
 import org.apache.polaris.core.connection.ConnectionConfigInfoDpo;
@@ -92,7 +94,7 @@ public class SigV4ConnectionCredentialVendor implements ConnectionCredentialVend
     Optional<ServiceIdentityCredential> serviceCredentialOpt =
         serviceIdentityProvider.getServiceIdentityCredential(connectionConfig.getServiceIdentity());
     if (serviceCredentialOpt.isEmpty()) {
-      return ConnectionCredentials.builder().build();
+      return ConnectionCredentials.EMPTY;
     }
 
     // Validate and cast service identity credential
@@ -124,19 +126,19 @@ public class SigV4ConnectionCredentialVendor implements ConnectionCredentialVend
     AssumeRoleResponse response = stsClient.assumeRole(requestBuilder.build());
 
     // Build connection credentials from AWS temporary credentials
-    ConnectionCredentials.Builder builder = ConnectionCredentials.builder();
-    builder.put(CatalogAccessProperty.AWS_ACCESS_KEY_ID, response.credentials().accessKeyId());
-    builder.put(
+    Map<CatalogAccessProperty, String> props = new HashMap<>();
+    props.put(CatalogAccessProperty.AWS_ACCESS_KEY_ID, response.credentials().accessKeyId());
+    props.put(
         CatalogAccessProperty.AWS_SECRET_ACCESS_KEY, response.credentials().secretAccessKey());
-    builder.put(CatalogAccessProperty.AWS_SESSION_TOKEN, response.credentials().sessionToken());
+    props.put(CatalogAccessProperty.AWS_SESSION_TOKEN, response.credentials().sessionToken());
     Optional.ofNullable(response.credentials().expiration())
         .ifPresent(
             expiration ->
-                builder.put(
+                props.put(
                     CatalogAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS,
                     String.valueOf(expiration.toEpochMilli())));
 
-    return builder.build();
+    return ConnectionCredentials.of(props);
   }
 
   @VisibleForTesting
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/credentials/DefaultPolarisCredentialManagerTest.java b/runtime/service/src/test/java/org/apache/polaris/service/credentials/DefaultPolarisCredentialManagerTest.java
index c98738a2c5..76b65c9014 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/credentials/DefaultPolarisCredentialManagerTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/credentials/DefaultPolarisCredentialManagerTest.java
@@ -71,14 +71,11 @@ public class DefaultPolarisCredentialManagerTest {
         @NotNull ConnectionConfigInfoDpo connectionConfig) {
 
       // Return test credentials
-      return ConnectionCredentials.builder()
-          .putCredential(
-              CatalogAccessProperty.AWS_ACCESS_KEY_ID.getPropertyName(), "sigv4-access-key")
-          .putCredential(
-              CatalogAccessProperty.AWS_SECRET_ACCESS_KEY.getPropertyName(), "sigv4-secret-key")
-          .putCredential(
-              CatalogAccessProperty.AWS_SESSION_TOKEN.getPropertyName(), "sigv4-session-token")
-          .build();
+      return ConnectionCredentials.of(
+          Map.of(
+              CatalogAccessProperty.AWS_ACCESS_KEY_ID, "sigv4-access-key",
+              CatalogAccessProperty.AWS_SECRET_ACCESS_KEY, "sigv4-secret-key",
+              CatalogAccessProperty.AWS_SESSION_TOKEN, "sigv4-session-token"));
     }
   }
 
@@ -91,10 +88,8 @@ public class DefaultPolarisCredentialManagerTest {
     public @NotNull ConnectionCredentials getConnectionCredentials(
         @NotNull ConnectionConfigInfoDpo connectionConfig) {
 
-      return ConnectionCredentials.builder()
-          .putCredential(
-              CatalogAccessProperty.AWS_ACCESS_KEY_ID.getPropertyName(), "oauth-access-key")
-          .build();
+      return ConnectionCredentials.of(
+          Map.of(CatalogAccessProperty.AWS_ACCESS_KEY_ID, "oauth-access-key"));
     }
   }