This is an automated email from the ASF dual-hosted git repository.
jerryshao pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-1.3 by this push:
new f80be5de07 [Cherry-pick to branch-1.3] [#13933] fix(spark-connector):
Reuse Basic credentials for routed Iceberg REST catalogs (#13935) (#13953)
f80be5de07 is described below
commit f80be5de079cdf9888c12ab3fb61dac24f9eeacd
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Oct 20 20:52:36 2026 +0800
[Cherry-pick to branch-1.3] [#13933] fix(spark-connector): Reuse Basic
credentials for routed Iceberg REST catalogs (#13935) (#13953)
**Cherry-pick Information:**
- Original commit: b8fa0f63526cac278b09af9c49eb9ab61f009170
- Target branch: `branch-1.3`
- Status: ✅ Clean cherry-pick (no conflicts)
Co-authored-by: Yuhui <[email protected]>
---
docs/spark-connector/spark-catalog-iceberg.md | 2 +
.../connector/iceberg/IcebergRestOAuthConfig.java | 28 +++++++++++
.../iceberg/TestIcebergRestOAuthConfig.java | 54 ++++++++++++++++++++++
3 files changed, 84 insertions(+)
diff --git a/docs/spark-connector/spark-catalog-iceberg.md
b/docs/spark-connector/spark-catalog-iceberg.md
index 056c70dbd4..a50a21f5b6 100644
--- a/docs/spark-connector/spark-catalog-iceberg.md
+++ b/docs/spark-connector/spark-catalog-iceberg.md
@@ -198,6 +198,8 @@ spark.sql.gravitino.iceberg.rest.rest.auth.basic.password
<password>
See [Connect Spark to Iceberg REST](../iceberg-rest-engine/spark.md) for the
full set of supported
`rest.auth.*` properties and how to configure them when connecting directly to
the IRC endpoint.
+When the Gravitino client uses Basic authentication
(`spark.sql.gravitino.authType=basic`), the connector reuses its user name and
password for IRC. Any `rest.auth.*` or `token` property set through
`spark.sql.gravitino.iceberg.rest.*` disables the reuse.
+
When the Gravitino client uses OAuth2, the connector reuses its OAuth2 client
configuration for IRC by
default. This avoids duplicating configuration when both endpoints accept the
same client identity. The
equivalent explicit setting is:
diff --git
a/spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/iceberg/IcebergRestOAuthConfig.java
b/spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/iceberg/IcebergRestOAuthConfig.java
index 6d419c3c4b..1a65415aef 100644
---
a/spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/iceberg/IcebergRestOAuthConfig.java
+++
b/spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/iceberg/IcebergRestOAuthConfig.java
@@ -19,6 +19,7 @@
package org.apache.gravitino.spark.connector.iceberg;
+import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.HashMap;
@@ -45,6 +46,9 @@ class IcebergRestOAuthConfig {
static final String CREDENTIAL = "credential";
static final String OAUTH2_SERVER_URI = "oauth2-server-uri";
static final String SCOPE = "scope";
+ static final String AUTH_TYPE_BASIC = "basic";
+ static final String BASIC_USERNAME = "rest.auth.basic.username";
+ static final String BASIC_PASSWORD = "rest.auth.basic.password";
// Properties whose presence alone implies the user is explicitly
configuring legacy OAuth2
// (triggering validation), as opposed to optional extras that ride along
with it. TOKEN is not
@@ -76,6 +80,10 @@ class IcebergRestOAuthConfig {
String authType =
sparkConf.get(GravitinoSparkConfig.GRAVITINO_AUTH_TYPE,
AuthProperties.SIMPLE_AUTH_TYPE);
+ if (AuthProperties.isBasic(authType)) {
+ return applyBasicCredentials(sparkConf, result);
+ }
+
boolean reuseOAuth2 =
sparkConf.getBoolean(GravitinoSparkConfig.GRAVITINO_ICEBERG_REUSE_OAUTH2, true)
&& AuthProperties.isOAuth2(authType);
@@ -107,6 +115,26 @@ class IcebergRestOAuthConfig {
return result;
}
+ // Reuses Gravitino's Basic credentials unless the caller already chose an
auth type.
+ private static Map<String, String> applyBasicCredentials(
+ SparkConf sparkConf, Map<String, String> config) {
+ if (StringUtils.isNotBlank(config.get(AUTH_TYPE))) {
+ return config;
+ }
+
+ String username =
sparkConf.get(GravitinoSparkConfig.GRAVITINO_BASIC_USERNAME, null);
+ String password =
sparkConf.get(GravitinoSparkConfig.GRAVITINO_BASIC_PASSWORD, null);
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password),
+ "Incomplete Iceberg REST Basic configuration; %s and %s are required",
+ GravitinoSparkConfig.GRAVITINO_BASIC_USERNAME,
+ GravitinoSparkConfig.GRAVITINO_BASIC_PASSWORD);
+ config.put(AUTH_TYPE, AUTH_TYPE_BASIC);
+ config.put(BASIC_USERNAME, username);
+ config.put(BASIC_PASSWORD, password);
+ return config;
+ }
+
private static void putIfConfigured(Map<String, String> config, String key,
String value) {
if (!config.containsKey(key) && StringUtils.isNotBlank(value)) {
config.put(key, value);
diff --git
a/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/iceberg/TestIcebergRestOAuthConfig.java
b/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/iceberg/TestIcebergRestOAuthConfig.java
index e4fae252e3..4abcd2fed7 100644
---
a/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/iceberg/TestIcebergRestOAuthConfig.java
+++
b/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/iceberg/TestIcebergRestOAuthConfig.java
@@ -193,6 +193,60 @@ public class TestIcebergRestOAuthConfig {
Assertions.assertTrue(exception.getMessage().contains("oauth2-server-uri"));
}
+ @Test
+ void testDerivesIcebergBasicConfig() {
+ SparkConf sparkConf = basicSparkConf("alice", "secret");
+
+ Map<String, String> result = IcebergRestOAuthConfig.resolve(sparkConf,
Collections.emptyMap());
+
+ Assertions.assertEquals("basic",
result.get(IcebergRestOAuthConfig.AUTH_TYPE));
+ Assertions.assertEquals("alice",
result.get(IcebergRestOAuthConfig.BASIC_USERNAME));
+ Assertions.assertEquals("secret",
result.get(IcebergRestOAuthConfig.BASIC_PASSWORD));
+ }
+
+ @Test
+ void testExplicitConfigTakesPrecedenceOverReusedBasic() {
+ SparkConf sparkConf = basicSparkConf("alice", "secret");
+ Map<String, String> explicit =
+ ImmutableMap.of("rest.auth.basic.username", "admin",
"rest.auth.basic.password", "pwd");
+
+ Map<String, String> result = IcebergRestOAuthConfig.resolve(sparkConf,
explicit);
+
+ Assertions.assertEquals(explicit, result);
+ }
+
+ @Test
+ void testExplicitTokenSkipsBasicReuse() {
+ SparkConf sparkConf = basicSparkConf("alice", "secret");
+ Map<String, String> explicit = ImmutableMap.of("token", "bearer-token");
+
+ Map<String, String> result = IcebergRestOAuthConfig.resolve(sparkConf,
explicit);
+
+ Assertions.assertEquals(explicit, result);
+ }
+
+ @Test
+ void testBasicReuseFailsWhenCredentialsAreIncomplete() {
+ SparkConf sparkConf = new SparkConf(false);
+ sparkConf.set(GravitinoSparkConfig.GRAVITINO_AUTH_TYPE, "basic");
+ sparkConf.set(GravitinoSparkConfig.GRAVITINO_BASIC_USERNAME, "alice");
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> IcebergRestOAuthConfig.resolve(sparkConf,
Collections.emptyMap()));
+
+ Assertions.assertTrue(exception.getMessage().contains("basic.password"));
+ }
+
+ private SparkConf basicSparkConf(String username, String password) {
+ SparkConf sparkConf = new SparkConf(false);
+ sparkConf.set(GravitinoSparkConfig.GRAVITINO_AUTH_TYPE, "basic");
+ sparkConf.set(GravitinoSparkConfig.GRAVITINO_BASIC_USERNAME, username);
+ sparkConf.set(GravitinoSparkConfig.GRAVITINO_BASIC_PASSWORD, password);
+ return sparkConf;
+ }
+
private SparkConf oauthSparkConf(String serverUri, String tokenPath) {
SparkConf sparkConf = new SparkConf(false);
sparkConf.set(GravitinoSparkConfig.GRAVITINO_AUTH_TYPE, "oauth2");