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"));
}
}