This is an automated email from the ASF dual-hosted git repository.
emaynard 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 8adebfdd4 Vend Azure credentials compatible with Iceberg 1.7 (#1252)
8adebfdd4 is described below
commit 8adebfdd4c438f5ee564320e9cc991afe51be49f
Author: Eric Maynard <[email protected]>
AuthorDate: Mon Mar 31 10:01:35 2025 -0700
Vend Azure credentials compatible with Iceberg 1.7 (#1252)
* update
* autolint
* fix
* autolint
* clean up
* autolint
* test
* autolint
* paranoid check
* typofix
---
.../storage/cache/StorageCredentialCacheEntry.java | 39 ++++++++--
.../storage/cache/StorageCredentialCacheTest.java | 90 ++++++++++++++++++++++
2 files changed, 122 insertions(+), 7 deletions(-)
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheEntry.java
b/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheEntry.java
index ae799457f..2649ee99c 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheEntry.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheEntry.java
@@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.Map;
import org.apache.polaris.core.persistence.dao.entity.ScopedCredentialsResult;
import org.apache.polaris.core.storage.PolarisCredentialProperty;
+import org.apache.polaris.core.storage.azure.AzureLocation;
/** A storage credential cached entry. */
public class StorageCredentialCacheEntry {
@@ -51,23 +52,47 @@ public class StorageCredentialCacheEntry {
return Long.MAX_VALUE;
}
+ /**
+ * Azure needs special handling, the credential key is dynamically generated
based on the storage
+ * account endpoint
+ */
+ private void handleAzureCredential(
+ HashMap<String, String> results, PolarisCredentialProperty
credentialProperty, String value) {
+ if (credentialProperty.equals(PolarisCredentialProperty.AZURE_SAS_TOKEN)) {
+ String host = credsMap.get(PolarisCredentialProperty.AZURE_ACCOUNT_HOST);
+ results.put(credentialProperty.getPropertyName() + host, value);
+
+ // Iceberg 1.7.x may expect the credential key to _not_ be suffixed with
endpoint
+ if (host.endsWith(AzureLocation.ADLS_ENDPOINT)) {
+ int suffixIndex = host.lastIndexOf(AzureLocation.ADLS_ENDPOINT) - 1;
+ if (suffixIndex > 0) {
+ String withSuffixStripped = host.substring(0, suffixIndex);
+ results.put(credentialProperty.getPropertyName() +
withSuffixStripped, value);
+ }
+ }
+
+ if (host.endsWith(AzureLocation.BLOB_ENDPOINT)) {
+ int suffixIndex = host.lastIndexOf(AzureLocation.BLOB_ENDPOINT) - 1;
+ if (suffixIndex > 0) {
+ String withSuffixStripped = host.substring(0, suffixIndex);
+ results.put(credentialProperty.getPropertyName() +
withSuffixStripped, value);
+ }
+ }
+ }
+ }
+
/**
* Get the map of string creds that is needed for the query engine.
*
* @return a map of string representing the subscoped creds info.
*/
public Map<String, String> convertToMapOfString() {
- Map<String, String> resCredsMap = new HashMap<>();
+ HashMap<String, String> resCredsMap = new HashMap<>();
if (!credsMap.isEmpty()) {
credsMap.forEach(
(key, value) -> {
- // only Azure needs special handle, the target key is dynamically
with storageaccount
- // endpoint appended
if (key.equals(PolarisCredentialProperty.AZURE_SAS_TOKEN)) {
- resCredsMap.put(
- key.getPropertyName()
- +
credsMap.get(PolarisCredentialProperty.AZURE_ACCOUNT_HOST),
- value);
+ handleAzureCredential(resCredsMap, key, value);
} else if
(!key.equals(PolarisCredentialProperty.AZURE_ACCOUNT_HOST)) {
resCredsMap.put(key.getPropertyName(), value);
}
diff --git
a/polaris-core/src/test/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheTest.java
b/polaris-core/src/test/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheTest.java
index aa22c4218..618ddddb5 100644
---
a/polaris-core/src/test/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheTest.java
+++
b/polaris-core/src/test/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheTest.java
@@ -45,6 +45,7 @@ import
org.apache.polaris.core.persistence.transactional.TransactionalPersistenc
import org.apache.polaris.core.persistence.transactional.TreeMapMetaStore;
import
org.apache.polaris.core.persistence.transactional.TreeMapTransactionalPersistenceImpl;
import org.apache.polaris.core.storage.PolarisCredentialProperty;
+import org.apache.polaris.core.storage.azure.AzureLocation;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
@@ -438,4 +439,93 @@ public class StorageCredentialCacheTest {
return Arrays.asList(polarisEntity1, polarisEntity2, polarisEntity3);
}
+
+ @Test
+ public void testAzureCredentialFormatting() {
+ storageCredentialCache = new StorageCredentialCache();
+ List<ScopedCredentialsResult> mockedScopedCreds =
+ List.of(
+ new ScopedCredentialsResult(
+ new EnumMap<>(
+ ImmutableMap.<PolarisCredentialProperty, String>builder()
+ .put(PolarisCredentialProperty.AZURE_SAS_TOKEN,
"sas_token_azure_1")
+ .put(PolarisCredentialProperty.AZURE_ACCOUNT_HOST,
"some_account")
+ .put(
+ PolarisCredentialProperty.EXPIRATION_TIME,
+ String.valueOf(Long.MAX_VALUE))
+ .buildOrThrow())),
+ new ScopedCredentialsResult(
+ new EnumMap<>(
+ ImmutableMap.<PolarisCredentialProperty, String>builder()
+ .put(PolarisCredentialProperty.AZURE_SAS_TOKEN,
"sas_token_azure_2")
+ .put(
+ PolarisCredentialProperty.AZURE_ACCOUNT_HOST,
+ "some_account." + AzureLocation.ADLS_ENDPOINT)
+ .put(
+ PolarisCredentialProperty.EXPIRATION_TIME,
+ String.valueOf(Long.MAX_VALUE))
+ .buildOrThrow())),
+ new ScopedCredentialsResult(
+ new EnumMap<>(
+ ImmutableMap.<PolarisCredentialProperty, String>builder()
+ .put(PolarisCredentialProperty.AZURE_SAS_TOKEN,
"sas_token_azure_3")
+ .put(
+ PolarisCredentialProperty.AZURE_ACCOUNT_HOST,
+ "some_account." + AzureLocation.BLOB_ENDPOINT)
+ .put(
+ PolarisCredentialProperty.EXPIRATION_TIME,
+ String.valueOf(Long.MAX_VALUE))
+ .buildOrThrow())));
+
+ Mockito.when(
+ metaStoreManager.getSubscopedCredsForEntity(
+ Mockito.any(),
+ Mockito.anyLong(),
+ Mockito.anyLong(),
+ Mockito.any(),
+ Mockito.anyBoolean(),
+ Mockito.anySet(),
+ Mockito.anySet()))
+ .thenReturn(mockedScopedCreds.get(0))
+ .thenReturn(mockedScopedCreds.get(1))
+ .thenReturn(mockedScopedCreds.get(2));
+ List<PolarisEntity> entityList = getPolarisEntities();
+
+ Map<String, String> noSuffixResult =
+ storageCredentialCache.getOrGenerateSubScopeCreds(
+ metaStoreManager,
+ callCtx,
+ entityList.get(0),
+ true,
+ new HashSet<>(Arrays.asList("s3://bucket1/path",
"s3://bucket2/path")),
+ new HashSet<>(Arrays.asList("s3://bucket3/path",
"s3://bucket4/path")));
+ Assertions.assertThat(noSuffixResult.size()).isEqualTo(2);
+
Assertions.assertThat(noSuffixResult).containsKey("adls.sas-token.some_account");
+
+ Map<String, String> adlsSuffixResult =
+ storageCredentialCache.getOrGenerateSubScopeCreds(
+ metaStoreManager,
+ callCtx,
+ entityList.get(1),
+ true,
+ new HashSet<>(Arrays.asList("s3://bucket1/path",
"s3://bucket2/path")),
+ new HashSet<>(Arrays.asList("s3://bucket3/path",
"s3://bucket4/path")));
+ Assertions.assertThat(adlsSuffixResult.size()).isEqualTo(3);
+
Assertions.assertThat(adlsSuffixResult).containsKey("adls.sas-token.some_account");
+ Assertions.assertThat(adlsSuffixResult)
+ .containsKey("adls.sas-token.some_account." +
AzureLocation.ADLS_ENDPOINT);
+
+ Map<String, String> blobSuffixResult =
+ storageCredentialCache.getOrGenerateSubScopeCreds(
+ metaStoreManager,
+ callCtx,
+ entityList.get(2),
+ true,
+ new HashSet<>(Arrays.asList("s3://bucket1/path",
"s3://bucket2/path")),
+ new HashSet<>(Arrays.asList("s3://bucket3/path",
"s3://bucket4/path")));
+ Assertions.assertThat(blobSuffixResult.size()).isEqualTo(3);
+
Assertions.assertThat(blobSuffixResult).containsKey("adls.sas-token.some_account");
+ Assertions.assertThat(blobSuffixResult)
+ .containsKey("adls.sas-token.some_account." +
AzureLocation.BLOB_ENDPOINT);
+ }
}