diff --git a/api/src/main/java/org/apache/gravitino/SupportsCatalogs.java b/api/src/main/java/org/apache/gravitino/SupportsCatalogs.java
index 088279b69f4..203be19fe49 100644
--- a/api/src/main/java/org/apache/gravitino/SupportsCatalogs.java
+++ b/api/src/main/java/org/apache/gravitino/SupportsCatalogs.java
@@ -18,6 +18,7 @@
*/
package org.apache.gravitino;
+import java.util.Collections;
import java.util.Map;
import org.apache.gravitino.annotation.Evolving;
import org.apache.gravitino.exceptions.CatalogAlreadyExistsException;
@@ -26,6 +27,8 @@
import org.apache.gravitino.exceptions.NoSuchCatalogException;
import org.apache.gravitino.exceptions.NoSuchMetalakeException;
import org.apache.gravitino.exceptions.NonEmptyEntityException;
+import org.apache.gravitino.secret.SecretBinding;
+import org.apache.gravitino.secret.SecretReference;
/**
* Client interface for supporting catalogs. It includes methods for listing, loading, creating,
@@ -84,22 +87,66 @@ default boolean catalogExists(String catalogName) {
* the created catalog is the managed catalog, like model, fileset catalog. For the details of the
* provider definition, see {@link CatalogProvider}.
*
- * @param catalogName the name of the catalog.
- * @param type the type of the catalog.
- * @param provider the provider of the catalog, or null if the catalog is a managed catalog.
- * @param comment the comment of the catalog.
- * @param properties the properties of the catalog.
- * @return The created catalog.
- * @throws NoSuchMetalakeException If the metalake does not exist.
- * @throws CatalogAlreadyExistsException If the catalog already exists.
+ *
Delegates to {@link #createCatalog(String, Catalog.Type, String, String, Map, Map, Map)}
+ * with empty secret maps.
+ *
+ * @param catalogName the name of the catalog
+ * @param type the type of the catalog
+ * @param provider the provider of the catalog, or null if the catalog is a managed catalog
+ * @param comment the comment of the catalog
+ * @param properties the properties of the catalog
+ * @return the created catalog
+ * @throws NoSuchMetalakeException if the metalake does not exist
+ * @throws CatalogAlreadyExistsException if the catalog already exists
*/
- Catalog createCatalog(
+ default Catalog createCatalog(
String catalogName,
Catalog.Type type,
String provider,
String comment,
Map properties)
- throws NoSuchMetalakeException, CatalogAlreadyExistsException;
+ throws NoSuchMetalakeException, CatalogAlreadyExistsException {
+ return createCatalog(
+ catalogName,
+ type,
+ provider,
+ comment,
+ properties,
+ Collections.emptyMap(),
+ Collections.emptyMap());
+ }
+
+ /**
+ * Create a catalog with optional secret maps.
+ *
+ * The default implementation rejects create-time secrets. Implementations that support secrets
+ * must override this method.
+ *
+ * @param catalogName the name of the catalog
+ * @param type the type of the catalog
+ * @param provider the provider of the catalog, or null if managed
+ * @param comment the comment of the catalog
+ * @param properties the properties of the catalog
+ * @param secretBindings optional property key → binding ({@code provider} + {@code plaintext})
+ * for write-through
+ * @param secretReferences optional property key → secret locator ({@code provider} plus
+ * provider-specific attributes)
+ * @return the created catalog
+ * @throws NoSuchMetalakeException if the metalake does not exist
+ * @throws CatalogAlreadyExistsException if the catalog already exists
+ * @throws UnsupportedOperationException if create-time secrets are not supported
+ */
+ default Catalog createCatalog(
+ String catalogName,
+ Catalog.Type type,
+ String provider,
+ String comment,
+ Map properties,
+ Map secretBindings,
+ Map secretReferences)
+ throws NoSuchMetalakeException, CatalogAlreadyExistsException {
+ throw new UnsupportedOperationException("Creating a catalog with secrets is not supported");
+ }
/**
* Create a managed catalog with specified catalog name, type, comment, and properties.
diff --git a/api/src/main/java/org/apache/gravitino/SupportsSchemas.java b/api/src/main/java/org/apache/gravitino/SupportsSchemas.java
index 42284ed24db..cc059d9f095 100644
--- a/api/src/main/java/org/apache/gravitino/SupportsSchemas.java
+++ b/api/src/main/java/org/apache/gravitino/SupportsSchemas.java
@@ -20,12 +20,15 @@
package org.apache.gravitino;
+import java.util.Collections;
import java.util.Map;
import org.apache.gravitino.annotation.Evolving;
import org.apache.gravitino.exceptions.NoSuchCatalogException;
import org.apache.gravitino.exceptions.NoSuchSchemaException;
import org.apache.gravitino.exceptions.NonEmptySchemaException;
import org.apache.gravitino.exceptions.SchemaAlreadyExistsException;
+import org.apache.gravitino.secret.SecretBinding;
+import org.apache.gravitino.secret.SecretReference;
/**
* The client interface to support schema operations. The server side should use the other one with
@@ -93,15 +96,48 @@ default boolean schemaExists(String schemaName) {
* need the schema with default values applied, use the {@link #loadSchema(String)} method after
* creation.
*
+ * Delegates to {@link #createSchema(String, String, Map, Map, Map)} with empty secret maps.
+ *
+ * @param schemaName The name of the schema.
+ * @param comment The comment of the schema.
+ * @param properties The properties of the schema.
+ * @return The schema as defined by the caller, without all default values.
+ * @throws NoSuchCatalogException If the catalog does not exist.
+ * @throws SchemaAlreadyExistsException If the schema already exists.
+ */
+ default Schema createSchema(String schemaName, String comment, Map properties)
+ throws NoSuchCatalogException, SchemaAlreadyExistsException {
+ return createSchema(
+ schemaName, comment, properties, Collections.emptyMap(), Collections.emptyMap());
+ }
+
+ /**
+ * Creates a schema with optional secret maps.
+ *
+ * The default implementation rejects create-time secrets. Implementations that support secrets
+ * must override this method.
+ *
* @param schemaName The name of the schema.
* @param comment The comment of the schema.
* @param properties The properties of the schema.
+ * @param secretBindings optional property key → binding ({@code provider} + {@code plaintext})
+ * for write-through
+ * @param secretReferences optional property key → secret locator ({@code provider} plus
+ * provider-specific attributes)
* @return The schema as defined by the caller, without all default values.
* @throws NoSuchCatalogException If the catalog does not exist.
* @throws SchemaAlreadyExistsException If the schema already exists.
+ * @throws UnsupportedOperationException if create-time secrets are not supported
*/
- Schema createSchema(String schemaName, String comment, Map properties)
- throws NoSuchCatalogException, SchemaAlreadyExistsException;
+ default Schema createSchema(
+ String schemaName,
+ String comment,
+ Map properties,
+ Map secretBindings,
+ Map secretReferences)
+ throws NoSuchCatalogException, SchemaAlreadyExistsException {
+ throw new UnsupportedOperationException("Creating a schema with secrets is not supported");
+ }
/**
* Load metadata properties for a schema.
diff --git a/api/src/main/java/org/apache/gravitino/file/Fileset.java b/api/src/main/java/org/apache/gravitino/file/Fileset.java
index 7089213984c..2f93630c9e8 100644
--- a/api/src/main/java/org/apache/gravitino/file/Fileset.java
+++ b/api/src/main/java/org/apache/gravitino/file/Fileset.java
@@ -231,7 +231,7 @@ default String storageLocation() {
* location and the value is the storage location path.
*/
default Map storageLocations() {
- throw new UnsupportedOperationException("Not implemented");
+ throw new UnsupportedOperationException("Fileset does not support storageLocations.");
}
/**
diff --git a/api/src/main/java/org/apache/gravitino/file/FilesetCatalog.java b/api/src/main/java/org/apache/gravitino/file/FilesetCatalog.java
index 68bddf96edb..54922a69f8f 100644
--- a/api/src/main/java/org/apache/gravitino/file/FilesetCatalog.java
+++ b/api/src/main/java/org/apache/gravitino/file/FilesetCatalog.java
@@ -170,7 +170,7 @@ default Fileset createMultipleLocationFileset(
Map secretBindings,
Map secretReferences)
throws NoSuchSchemaException, FilesetAlreadyExistsException {
- throw new UnsupportedOperationException("Not implemented");
+ throw new UnsupportedOperationException("Creating a fileset with secrets is not supported");
}
/**
@@ -258,6 +258,6 @@ default String getFileLocation(NameIdentifier ident, String subPath)
*/
default String getFileLocation(NameIdentifier ident, String subPath, String locationName)
throws NoSuchFilesetException, NoSuchLocationNameException {
- throw new UnsupportedOperationException("Not implemented");
+ throw new UnsupportedOperationException("getFileLocation is not supported");
}
}
diff --git a/clients/client-java/src/main/java/org/apache/gravitino/client/BaseSchemaCatalog.java b/clients/client-java/src/main/java/org/apache/gravitino/client/BaseSchemaCatalog.java
index 0553abfc4bf..7ea349b22b5 100644
--- a/clients/client-java/src/main/java/org/apache/gravitino/client/BaseSchemaCatalog.java
+++ b/clients/client-java/src/main/java/org/apache/gravitino/client/BaseSchemaCatalog.java
@@ -42,6 +42,8 @@
import org.apache.gravitino.dto.responses.DropResponse;
import org.apache.gravitino.dto.responses.EntityListResponse;
import org.apache.gravitino.dto.responses.SchemaResponse;
+import org.apache.gravitino.dto.secret.SecretBindingDTO;
+import org.apache.gravitino.dto.secret.SecretReferenceDTO;
import org.apache.gravitino.exceptions.NoSuchCatalogException;
import org.apache.gravitino.exceptions.NoSuchPolicyException;
import org.apache.gravitino.exceptions.NoSuchSchemaException;
@@ -56,6 +58,8 @@
import org.apache.gravitino.policy.Policy;
import org.apache.gravitino.policy.SupportsPolicies;
import org.apache.gravitino.rest.RESTUtils;
+import org.apache.gravitino.secret.SecretBinding;
+import org.apache.gravitino.secret.SecretReference;
import org.apache.gravitino.tag.SupportsTags;
import org.apache.gravitino.tag.Tag;
@@ -167,7 +171,7 @@ public String[] listSchemas(String parentSchema)
}
/**
- * Create a new schema with specified identifier, comment and metadata.
+ * Create a new schema with specified identifier, comment and properties.
*
* @param schemaName The name identifier of the schema.
* @param comment The comment of the schema.
@@ -179,8 +183,40 @@ public String[] listSchemas(String parentSchema)
@Override
public Schema createSchema(String schemaName, String comment, Map properties)
throws NoSuchCatalogException, SchemaAlreadyExistsException {
+ return createSchema(
+ schemaName, comment, properties, Collections.emptyMap(), Collections.emptyMap());
+ }
+
+ /**
+ * Create a new schema with specified identifier, comment, properties, and optional secret maps.
+ *
+ * @param schemaName The name identifier of the schema.
+ * @param comment The comment of the schema.
+ * @param properties The properties of the schema.
+ * @param secretBindings Optional property key → binding ({@code provider} + {@code plaintext})
+ * for write-through.
+ * @param secretReferences Optional property key → secret locator ({@code provider} plus
+ * provider-specific attributes).
+ * @return The created {@link Schema}.
+ * @throws NoSuchCatalogException if the catalog with specified namespace does not exist.
+ * @throws SchemaAlreadyExistsException if the schema with specified identifier already exists.
+ */
+ @Override
+ public Schema createSchema(
+ String schemaName,
+ String comment,
+ Map properties,
+ Map secretBindings,
+ Map secretReferences)
+ throws NoSuchCatalogException, SchemaAlreadyExistsException {
- SchemaCreateRequest req = new SchemaCreateRequest(schemaName, comment, properties);
+ SchemaCreateRequest req =
+ new SchemaCreateRequest(
+ schemaName,
+ comment,
+ properties,
+ SecretBindingDTO.fromSecretBindings(secretBindings),
+ SecretReferenceDTO.fromSecretReferences(secretReferences));
req.validate();
SchemaResponse resp =
diff --git a/clients/client-java/src/main/java/org/apache/gravitino/client/FilesetCatalog.java b/clients/client-java/src/main/java/org/apache/gravitino/client/FilesetCatalog.java
index 40dc2d72869..ad61a82b401 100644
--- a/clients/client-java/src/main/java/org/apache/gravitino/client/FilesetCatalog.java
+++ b/clients/client-java/src/main/java/org/apache/gravitino/client/FilesetCatalog.java
@@ -137,8 +137,38 @@ public Fileset loadFileset(NameIdentifier ident) throws NoSuchFilesetException {
* @param type The type of the fileset.
* @param storageLocations The location names and storage locations of the fileset.
* @param properties The properties of the fileset.
- * @param secretBindings Optional property key → binding ({ provider} + { plaintext}) for
- * write-through.
+ * @return The created fileset metadata
+ * @throws NoSuchSchemaException If the schema does not exist.
+ * @throws FilesetAlreadyExistsException If the fileset already exists.
+ */
+ @Override
+ public Fileset createMultipleLocationFileset(
+ NameIdentifier ident,
+ String comment,
+ Fileset.Type type,
+ Map storageLocations,
+ Map properties)
+ throws NoSuchSchemaException, FilesetAlreadyExistsException {
+ return createMultipleLocationFileset(
+ ident,
+ comment,
+ type,
+ storageLocations,
+ properties,
+ Collections.emptyMap(),
+ Collections.emptyMap());
+ }
+
+ /**
+ * Create a fileset metadata with multiple storage locations in the catalog.
+ *
+ * @param ident A fileset identifier.
+ * @param comment The comment of the fileset.
+ * @param type The type of the fileset.
+ * @param storageLocations The location names and storage locations of the fileset.
+ * @param properties The properties of the fileset.
+ * @param secretBindings Optional property key → binding ({@code provider} + {@code plaintext})
+ * for write-through.
* @param secretReferences Optional property key → secret locator ({@code provider} plus
* provider-specific attributes).
* @return The created fileset metadata
diff --git a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
index 16b222ba8d6..120435b3b0d 100644
--- a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
+++ b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
@@ -21,6 +21,7 @@
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -67,6 +68,8 @@
import org.apache.gravitino.policy.PolicyChange;
import org.apache.gravitino.policy.PolicyContent;
import org.apache.gravitino.policy.PolicyOperations;
+import org.apache.gravitino.secret.SecretBinding;
+import org.apache.gravitino.secret.SecretReference;
import org.apache.gravitino.tag.Tag;
import org.apache.gravitino.tag.TagChange;
import org.apache.gravitino.tag.TagOperations;
@@ -139,7 +142,29 @@ public Catalog createCatalog(
String comment,
Map properties)
throws NoSuchMetalakeException, CatalogAlreadyExistsException {
- return getMetalake().createCatalog(catalogName, type, provider, comment, properties);
+ return createCatalog(
+ catalogName,
+ type,
+ provider,
+ comment,
+ properties,
+ Collections.emptyMap(),
+ Collections.emptyMap());
+ }
+
+ @Override
+ public Catalog createCatalog(
+ String catalogName,
+ Catalog.Type type,
+ String provider,
+ String comment,
+ Map properties,
+ Map secretBindings,
+ Map secretReferences)
+ throws NoSuchMetalakeException, CatalogAlreadyExistsException {
+ return getMetalake()
+ .createCatalog(
+ catalogName, type, provider, comment, properties, secretBindings, secretReferences);
}
@Override
diff --git a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
index f88a33a227d..671f87eaa8f 100644
--- a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
+++ b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
@@ -95,6 +95,8 @@
import org.apache.gravitino.dto.responses.TagResponse;
import org.apache.gravitino.dto.responses.UserListResponse;
import org.apache.gravitino.dto.responses.UserResponse;
+import org.apache.gravitino.dto.secret.SecretBindingDTO;
+import org.apache.gravitino.dto.secret.SecretReferenceDTO;
import org.apache.gravitino.exceptions.CatalogAlreadyExistsException;
import org.apache.gravitino.exceptions.CatalogInUseException;
import org.apache.gravitino.exceptions.GroupAlreadyExistsException;
@@ -128,6 +130,8 @@
import org.apache.gravitino.policy.PolicyContent;
import org.apache.gravitino.policy.PolicyOperations;
import org.apache.gravitino.rest.RESTUtils;
+import org.apache.gravitino.secret.SecretBinding;
+import org.apache.gravitino.secret.SecretReference;
import org.apache.gravitino.tag.Tag;
import org.apache.gravitino.tag.TagChange;
import org.apache.gravitino.tag.TagOperations;
@@ -255,8 +259,52 @@ public Catalog createCatalog(
String comment,
Map properties)
throws NoSuchMetalakeException, CatalogAlreadyExistsException {
+ return createCatalog(
+ catalogName,
+ type,
+ provider,
+ comment,
+ properties,
+ Collections.emptyMap(),
+ Collections.emptyMap());
+ }
+
+ /**
+ * Create a new catalog with specified identifier, type, comment, properties, and optional secret
+ * maps.
+ *
+ * @param catalogName The identifier of the catalog.
+ * @param type The type of the catalog.
+ * @param provider The provider of the catalog.
+ * @param comment The comment of the catalog.
+ * @param properties The properties of the catalog.
+ * @param secretBindings Optional property key → binding ({@code provider} + {@code plaintext})
+ * for write-through.
+ * @param secretReferences Optional property key → secret locator ({@code provider} plus
+ * provider-specific attributes).
+ * @return The created {@link Catalog}.
+ * @throws NoSuchMetalakeException if the metalake with specified namespace does not exist.
+ * @throws CatalogAlreadyExistsException if the catalog with specified identifier already exists.
+ */
+ @Override
+ public Catalog createCatalog(
+ String catalogName,
+ Catalog.Type type,
+ String provider,
+ String comment,
+ Map properties,
+ Map secretBindings,
+ Map secretReferences)
+ throws NoSuchMetalakeException, CatalogAlreadyExistsException {
CatalogCreateRequest req =
- new CatalogCreateRequest(catalogName, type, provider, comment, properties);
+ new CatalogCreateRequest(
+ catalogName,
+ type,
+ provider,
+ comment,
+ properties,
+ SecretBindingDTO.fromSecretBindings(secretBindings),
+ SecretReferenceDTO.fromSecretReferences(secretReferences));
req.validate();
CatalogResponse resp =
diff --git a/clients/client-python/gravitino/api/supports_schemas.py b/clients/client-python/gravitino/api/supports_schemas.py
index 7a61400b6f9..b81d74c1539 100644
--- a/clients/client-python/gravitino/api/supports_schemas.py
+++ b/clients/client-python/gravitino/api/supports_schemas.py
@@ -16,12 +16,17 @@
# under the License.
from abc import ABC, abstractmethod
-from typing import Dict, List, Optional
+from types import MappingProxyType
+from typing import Dict, List, Mapping, Optional
from gravitino.api.schema import Schema
from gravitino.api.schema_change import SchemaChange
+from gravitino.api.secret import SecretBinding, SecretReference
from gravitino.exceptions.base import NoSuchSchemaException
+_EMPTY_SECRET_BINDINGS: Mapping[str, SecretBinding] = MappingProxyType({})
+_EMPTY_SECRET_REFERENCES: Mapping[str, SecretReference] = MappingProxyType({})
+
class SupportsSchemas(ABC):
"""
@@ -79,7 +84,12 @@ def schema_exists(self, schema_name: str) -> bool:
@abstractmethod
def create_schema(
- self, schema_name: str, comment: str, properties: Dict[str, str]
+ self,
+ schema_name: str,
+ comment: str,
+ properties: Dict[str, str],
+ secret_bindings: Mapping[str, SecretBinding] = _EMPTY_SECRET_BINDINGS,
+ secret_references: Mapping[str, SecretReference] = _EMPTY_SECRET_REFERENCES,
) -> Schema:
"""Create a schema in the catalog.
@@ -87,6 +97,8 @@ def create_schema(
schema_name: The name of the schema.
comment: The comment of the schema.
properties: The properties of the schema.
+ secret_bindings: Optional property key → binding (provider + plaintext) for write-through.
+ secret_references: Optional property key → locator attributes.
Raises:
NoSuchCatalogException: If the catalog does not exist.
diff --git a/clients/client-python/gravitino/client/base_schema_catalog.py b/clients/client-python/gravitino/client/base_schema_catalog.py
index a2cbae094b5..1d21c45e8c3 100644
--- a/clients/client-python/gravitino/client/base_schema_catalog.py
+++ b/clients/client-python/gravitino/client/base_schema_catalog.py
@@ -16,7 +16,8 @@
# under the License.
import logging
-from typing import Dict, List, Optional
+from types import MappingProxyType
+from typing import Dict, List, Mapping, Optional
from gravitino.api.catalog import Catalog
from gravitino.api.function.function import Function
@@ -28,6 +29,7 @@
from gravitino.api.metadata_objects import MetadataObjects
from gravitino.api.schema import Schema
from gravitino.api.schema_change import SchemaChange
+from gravitino.api.secret import SecretBinding, SecretReference
from gravitino.api.supports_schemas import SupportsSchemas
from gravitino.api.tag.supports_tags import SupportsTags
from gravitino.api.tag.tag import Tag
@@ -54,6 +56,9 @@
logger = logging.getLogger(__name__)
+_EMPTY_SECRET_BINDINGS: Mapping[str, SecretBinding] = MappingProxyType({})
+_EMPTY_SECRET_REFERENCES: Mapping[str, SecretReference] = MappingProxyType({})
+
class BaseSchemaCatalog(
CatalogDTO,
@@ -159,6 +164,8 @@ def create_schema(
schema_name: str = None,
comment: str = None,
properties: Dict[str, str] = None,
+ secret_bindings: Mapping[str, SecretBinding] = _EMPTY_SECRET_BINDINGS,
+ secret_references: Mapping[str, SecretReference] = _EMPTY_SECRET_REFERENCES,
) -> Schema:
"""Create a new schema with specified identifier, comment and metadata.
@@ -166,6 +173,8 @@ def create_schema(
schema_name: The name of the schema.
comment: The comment of the schema.
properties: The properties of the schema.
+ secret_bindings: Optional property key → binding (provider + plaintext) for write-through.
+ secret_references: Optional property key → locator attributes.
Raises:
NoSuchCatalogException if the catalog with specified namespace does not exist.
@@ -174,7 +183,13 @@ def create_schema(
Returns:
The created Schema.
"""
- req = SchemaCreateRequest(encode_string(schema_name), comment, properties)
+ req = SchemaCreateRequest(
+ encode_string(schema_name),
+ comment,
+ properties,
+ secret_bindings,
+ secret_references,
+ )
req.validate()
resp = self.rest_client.post(
diff --git a/clients/client-python/gravitino/client/gravitino_client.py b/clients/client-python/gravitino/client/gravitino_client.py
index 3e936d2eb0f..303877f3d4b 100644
--- a/clients/client-python/gravitino/client/gravitino_client.py
+++ b/clients/client-python/gravitino/client/gravitino_client.py
@@ -17,7 +17,8 @@
from __future__ import annotations
-from typing import Dict, List, Optional
+from types import MappingProxyType
+from typing import Dict, List, Mapping, Optional
from gravitino.api.authorization.group import Group
from gravitino.api.authorization.owner import Owner
@@ -32,6 +33,7 @@
from gravitino.api.job.job_template_change import JobTemplateChange
from gravitino.api.job.supports_jobs import SupportsJobs
from gravitino.api.metadata_object import MetadataObject
+from gravitino.api.secret import SecretBinding, SecretReference
from gravitino.api.tag.tag_operations import TagOperations
from gravitino.auth.auth_data_provider import AuthDataProvider
from gravitino.client.gravitino_client_base import GravitinoClientBase
@@ -39,6 +41,9 @@
from ..api.tag.tag import Tag
+_EMPTY_SECRET_BINDINGS: Mapping[str, SecretBinding] = MappingProxyType({})
+_EMPTY_SECRET_REFERENCES: Mapping[str, SecretReference] = MappingProxyType({})
+
class GravitinoClient(GravitinoClientBase, SupportsJobs, TagOperations):
"""Gravitino Client for a user to interact with the Gravitino API, allowing the client to list,
@@ -103,9 +108,17 @@ def create_catalog(
provider: str,
comment: str,
properties: Dict[str, str],
+ secret_bindings: Mapping[str, SecretBinding] = _EMPTY_SECRET_BINDINGS,
+ secret_references: Mapping[str, SecretReference] = _EMPTY_SECRET_REFERENCES,
) -> Catalog:
return self.get_metalake().create_catalog(
- name, catalog_type, provider, comment, properties
+ name,
+ catalog_type,
+ provider,
+ comment,
+ properties,
+ secret_bindings,
+ secret_references,
)
def alter_catalog(self, name: str, *changes: CatalogChange):
diff --git a/clients/client-python/gravitino/client/gravitino_metalake.py b/clients/client-python/gravitino/client/gravitino_metalake.py
index beb4baf96aa..a48b720f4a6 100644
--- a/clients/client-python/gravitino/client/gravitino_metalake.py
+++ b/clients/client-python/gravitino/client/gravitino_metalake.py
@@ -16,7 +16,8 @@
# under the License.
# pylint: disable=too-many-lines
import logging
-from typing import Dict, List, Optional
+from types import MappingProxyType
+from typing import Dict, List, Mapping, Optional
from gravitino.api.authorization.group import Group
from gravitino.api.authorization.owner import Owner
@@ -31,6 +32,7 @@
from gravitino.api.job.job_template_change import JobTemplateChange
from gravitino.api.job.supports_jobs import SupportsJobs
from gravitino.api.metadata_object import MetadataObject
+from gravitino.api.secret import SecretBinding, SecretReference
from gravitino.api.tag.tag import Tag
from gravitino.api.tag.tag_operations import TagOperations
from gravitino.client.dto_converters import DTOConverters
@@ -104,6 +106,9 @@
logger = logging.getLogger(__name__)
+_EMPTY_SECRET_BINDINGS: Mapping[str, SecretBinding] = MappingProxyType({})
+_EMPTY_SECRET_REFERENCES: Mapping[str, SecretReference] = MappingProxyType({})
+
class GravitinoMetalake(
MetalakeDTO,
@@ -217,6 +222,8 @@ def create_catalog(
provider: str,
comment: str,
properties: Dict[str, str],
+ secret_bindings: Mapping[str, SecretBinding] = _EMPTY_SECRET_BINDINGS,
+ secret_references: Mapping[str, SecretReference] = _EMPTY_SECRET_REFERENCES,
) -> Catalog:
"""Create a new catalog with specified name, catalog type, comment and properties.
@@ -228,6 +235,8 @@ def create_catalog(
None provider. For the details, please refer to the Catalog.Type.
comment: The comment of the catalog.
properties: The properties of the catalog.
+ secret_bindings: Optional property key → binding (provider + plaintext) for write-through.
+ secret_references: Optional property key → locator attributes.
Raises:
NoSuchMetalakeException if the metalake does not exist.
@@ -243,6 +252,8 @@ def create_catalog(
provider=provider,
comment=comment,
properties=properties,
+ secret_bindings=secret_bindings,
+ secret_references=secret_references,
)
catalog_create_request.validate()
@@ -251,6 +262,7 @@ def create_catalog(
url, json=catalog_create_request, error_handler=CATALOG_ERROR_HANDLER
)
catalog_resp = CatalogResponse.from_json(response.body, infer_missing=True)
+ catalog_resp.validate()
return DTOConverters.to_catalog(
self.name(), catalog_resp.catalog(), self.rest_client
diff --git a/clients/client-python/gravitino/dto/requests/catalog_create_request.py b/clients/client-python/gravitino/dto/requests/catalog_create_request.py
index 885011118dc..f0cfdb62a1e 100644
--- a/clients/client-python/gravitino/dto/requests/catalog_create_request.py
+++ b/clients/client-python/gravitino/dto/requests/catalog_create_request.py
@@ -16,13 +16,18 @@
# under the License.
from dataclasses import dataclass, field
-from typing import Optional, Dict
+from types import MappingProxyType
+from typing import Dict, Mapping, Optional
from dataclasses_json import config
from gravitino.api.catalog import Catalog
+from gravitino.api.secret import SecretBinding, SecretReference
from gravitino.rest.rest_message import RESTRequest
+_EMPTY_SECRET_BINDINGS: Mapping[str, SecretBinding] = MappingProxyType({})
+_EMPTY_SECRET_REFERENCES: Mapping[str, SecretReference] = MappingProxyType({})
+
@dataclass
class CatalogCreateRequest(RESTRequest):
@@ -41,6 +46,12 @@ class CatalogCreateRequest(RESTRequest):
_properties: Optional[Dict[str, str]] = field(
metadata=config(field_name="properties")
)
+ _secret_bindings: Dict[str, SecretBinding] = field(
+ default_factory=dict, metadata=config(field_name="secretBindings")
+ )
+ _secret_references: Dict[str, SecretReference] = field(
+ default_factory=dict, metadata=config(field_name="secretReferences")
+ )
def __init__(
self,
@@ -49,12 +60,16 @@ def __init__(
provider: str = None,
comment: str = None,
properties: Dict[str, str] = None,
+ secret_bindings: Mapping[str, SecretBinding] = _EMPTY_SECRET_BINDINGS,
+ secret_references: Mapping[str, SecretReference] = _EMPTY_SECRET_REFERENCES,
):
self._name = name
self._type = catalog_type
self._provider = provider
self._comment = comment
self._properties = properties
+ self._secret_bindings = dict(secret_bindings)
+ self._secret_references = dict(secret_references)
def validate(self):
"""Validates the fields of the request.
diff --git a/clients/client-python/gravitino/dto/requests/schema_create_request.py b/clients/client-python/gravitino/dto/requests/schema_create_request.py
index 3baae7e857a..6cbcad81f32 100644
--- a/clients/client-python/gravitino/dto/requests/schema_create_request.py
+++ b/clients/client-python/gravitino/dto/requests/schema_create_request.py
@@ -16,12 +16,17 @@
# under the License.
from dataclasses import dataclass, field
-from typing import Optional, Dict
+from types import MappingProxyType
+from typing import Dict, Mapping, Optional
from dataclasses_json import config
+from gravitino.api.secret import SecretBinding, SecretReference
from gravitino.rest.rest_message import RESTRequest
+_EMPTY_SECRET_BINDINGS: Mapping[str, SecretBinding] = MappingProxyType({})
+_EMPTY_SECRET_REFERENCES: Mapping[str, SecretReference] = MappingProxyType({})
+
@dataclass
class SchemaCreateRequest(RESTRequest):
@@ -32,14 +37,32 @@ class SchemaCreateRequest(RESTRequest):
_properties: Optional[Dict[str, str]] = field(
metadata=config(field_name="properties")
)
+ _secret_bindings: Dict[str, SecretBinding] = field(
+ default_factory=dict, metadata=config(field_name="secretBindings")
+ )
+ _secret_references: Dict[str, SecretReference] = field(
+ default_factory=dict, metadata=config(field_name="secretReferences")
+ )
def __init__(
- self, name: str, comment: Optional[str], properties: Optional[Dict[str, str]]
+ self,
+ name: str,
+ comment: Optional[str],
+ properties: Optional[Dict[str, str]],
+ secret_bindings: Mapping[str, SecretBinding] = _EMPTY_SECRET_BINDINGS,
+ secret_references: Mapping[str, SecretReference] = _EMPTY_SECRET_REFERENCES,
):
self._name = name
self._comment = comment
self._properties = properties
+ self._secret_bindings = dict(secret_bindings)
+ self._secret_references = dict(secret_references)
def validate(self):
+ """Validates the request.
+
+ Raises:
+ IllegalArgumentException if the request is invalid.
+ """
if not self._name:
raise ValueError('"name" field is required and cannot be empty')
diff --git a/common/src/main/java/org/apache/gravitino/dto/requests/CatalogCreateRequest.java b/common/src/main/java/org/apache/gravitino/dto/requests/CatalogCreateRequest.java
index 3223fce6c8c..acf8442c4d0 100644
--- a/common/src/main/java/org/apache/gravitino/dto/requests/CatalogCreateRequest.java
+++ b/common/src/main/java/org/apache/gravitino/dto/requests/CatalogCreateRequest.java
@@ -19,8 +19,10 @@
package org.apache.gravitino.dto.requests;
import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Preconditions;
+import java.util.Collections;
import java.util.Map;
import javax.annotation.Nullable;
import lombok.EqualsAndHashCode;
@@ -29,6 +31,8 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.CatalogProvider;
+import org.apache.gravitino.dto.secret.SecretBindingDTO;
+import org.apache.gravitino.dto.secret.SecretReferenceDTO;
import org.apache.gravitino.rest.RESTRequest;
/** Represents a request to create a catalog. */
@@ -54,6 +58,32 @@ public class CatalogCreateRequest implements RESTRequest {
@JsonProperty("properties")
private final Map properties;
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ @JsonProperty("secretBindings")
+ private final Map secretBindings;
+
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ @JsonProperty("secretReferences")
+ private final Map secretReferences;
+
+ /**
+ * Constructor for CatalogCreateRequest without secret maps.
+ *
+ * @param name The name of the catalog.
+ * @param type The type of the catalog.
+ * @param provider The provider of the catalog.
+ * @param comment The comment for the catalog.
+ * @param properties The properties for the catalog.
+ */
+ public CatalogCreateRequest(
+ String name,
+ Catalog.Type type,
+ String provider,
+ String comment,
+ Map properties) {
+ this(name, type, provider, comment, properties, Collections.emptyMap(), Collections.emptyMap());
+ }
+
/**
* Constructor for CatalogCreateRequest.
*
@@ -62,6 +92,10 @@ public class CatalogCreateRequest implements RESTRequest {
* @param provider The provider of the catalog.
* @param comment The comment for the catalog.
* @param properties The properties for the catalog.
+ * @param secretBindings Optional property key → binding DTO ({@code provider} + {@code
+ * plaintext}) for write-through secrets.
+ * @param secretReferences Optional property key → secret locator DTO ({@code provider} plus
+ * provider-specific attributes).
*/
@JsonCreator
public CatalogCreateRequest(
@@ -69,11 +103,17 @@ public CatalogCreateRequest(
@JsonProperty("type") Catalog.Type type,
@JsonProperty("provider") String provider,
@JsonProperty("comment") String comment,
- @JsonProperty("properties") Map properties) {
+ @JsonProperty("properties") Map properties,
+ @JsonProperty("secretBindings") Map secretBindings,
+ @JsonProperty("secretReferences") Map secretReferences) {
this.name = name;
this.type = type;
this.comment = comment;
this.properties = properties;
+ // Match FilesetCreateRequest field defaults when JSON omits secret maps (@JsonCreator passes
+ // null for absent properties).
+ this.secretBindings = secretBindings == null ? Collections.emptyMap() : secretBindings;
+ this.secretReferences = secretReferences == null ? Collections.emptyMap() : secretReferences;
if (StringUtils.isNotBlank(provider)) {
this.provider = provider;
diff --git a/common/src/main/java/org/apache/gravitino/dto/requests/SchemaCreateRequest.java b/common/src/main/java/org/apache/gravitino/dto/requests/SchemaCreateRequest.java
index ec103c0629d..37456860120 100644
--- a/common/src/main/java/org/apache/gravitino/dto/requests/SchemaCreateRequest.java
+++ b/common/src/main/java/org/apache/gravitino/dto/requests/SchemaCreateRequest.java
@@ -18,14 +18,19 @@
*/
package org.apache.gravitino.dto.requests;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Preconditions;
+import java.util.Collections;
import java.util.Map;
import javax.annotation.Nullable;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.dto.secret.SecretBindingDTO;
+import org.apache.gravitino.dto.secret.SecretReferenceDTO;
import org.apache.gravitino.rest.RESTRequest;
/** Represents a request to create a schema. */
@@ -45,22 +50,53 @@ public class SchemaCreateRequest implements RESTRequest {
@JsonProperty("properties")
private final Map properties;
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ @JsonProperty("secretBindings")
+ private final Map secretBindings;
+
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ @JsonProperty("secretReferences")
+ private final Map secretReferences;
+
/** Default constructor for Jackson deserialization. */
public SchemaCreateRequest() {
- this(null, null, null);
+ this(null, null, null, Collections.emptyMap(), Collections.emptyMap());
}
/**
- * Creates a new SchemaCreateRequest.
+ * Creates a new SchemaCreateRequest without secret maps.
*
* @param name The name of the schema.
* @param comment The comment of the schema.
* @param properties The properties of the schema.
*/
public SchemaCreateRequest(String name, String comment, Map properties) {
+ this(name, comment, properties, Collections.emptyMap(), Collections.emptyMap());
+ }
+
+ /**
+ * Creates a new SchemaCreateRequest.
+ *
+ * @param name The name of the schema.
+ * @param comment The comment of the schema.
+ * @param properties The properties of the schema.
+ * @param secretBindings Optional property key → binding DTO ({@code provider} + {@code
+ * plaintext}) for write-through secrets.
+ * @param secretReferences Optional property key → secret locator DTO ({@code provider} plus
+ * provider-specific attributes).
+ */
+ @JsonCreator
+ public SchemaCreateRequest(
+ @JsonProperty("name") String name,
+ @JsonProperty("comment") String comment,
+ @JsonProperty("properties") Map properties,
+ @JsonProperty("secretBindings") Map secretBindings,
+ @JsonProperty("secretReferences") Map secretReferences) {
this.name = name;
this.comment = comment;
this.properties = properties;
+ this.secretBindings = secretBindings == null ? Collections.emptyMap() : secretBindings;
+ this.secretReferences = secretReferences == null ? Collections.emptyMap() : secretReferences;
}
/**
diff --git a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
index def00e8cbc1..14f9061e06c 100644
--- a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
+++ b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
@@ -745,8 +745,20 @@ private void initGravitinoServerComponents() {
this.credentialOperationDispatcher =
new CredentialOperationDispatcher(catalogManager, entityStore, idGenerator, secretManager);
+ // Fileset dispatcher is created before schema dispatcher so schema can take it directly.
+ FilesetOperationDispatcher filesetOperationDispatcher =
+ new FilesetOperationDispatcher(catalogManager, entityStore, idGenerator, secretManager);
+ FilesetNormalizeDispatcher filesetNormalizeDispatcher =
+ new FilesetNormalizeDispatcher(filesetOperationDispatcher, catalogManager);
+ this.internalFilesetDispatcher = filesetNormalizeDispatcher;
+ FilesetEventDispatcher filesetEventDispatcher =
+ new FilesetEventDispatcher(eventBus, filesetNormalizeDispatcher);
+ this.filesetDispatcher = new FilesetHookDispatcher(filesetEventDispatcher);
+ catalogManager.setFilesetDispatcher(filesetNormalizeDispatcher);
+
SchemaOperationDispatcher schemaOperationDispatcher =
- new SchemaOperationDispatcher(catalogManager, entityStore, idGenerator, secretManager);
+ new SchemaOperationDispatcher(
+ catalogManager, entityStore, idGenerator, secretManager, filesetNormalizeDispatcher);
this.internalSchemaDispatcher = schemaOperationDispatcher;
SchemaNormalizeDispatcher schemaNormalizeDispatcher =
new SchemaNormalizeDispatcher(schemaOperationDispatcher, catalogManager);
@@ -782,15 +794,6 @@ private void initGravitinoServerComponents() {
new PartitionNormalizeDispatcher(partitionOperationDispatcher, catalogManager);
this.partitionDispatcher = new PartitionEventDispatcher(eventBus, partitionNormalizeDispatcher);
- FilesetOperationDispatcher filesetOperationDispatcher =
- new FilesetOperationDispatcher(catalogManager, entityStore, idGenerator, secretManager);
- FilesetNormalizeDispatcher filesetNormalizeDispatcher =
- new FilesetNormalizeDispatcher(filesetOperationDispatcher, catalogManager);
- this.internalFilesetDispatcher = filesetNormalizeDispatcher;
- FilesetEventDispatcher filesetEventDispatcher =
- new FilesetEventDispatcher(eventBus, filesetNormalizeDispatcher);
- this.filesetDispatcher = new FilesetHookDispatcher(filesetEventDispatcher);
-
TopicOperationDispatcher topicOperationDispatcher =
new TopicOperationDispatcher(catalogManager, entityStore, idGenerator, secretManager);
TopicNormalizeDispatcher topicNormalizeDispatcher =
diff --git a/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java b/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
index 72c0139cf43..07135a4a2e5 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
@@ -25,6 +25,7 @@
import static org.apache.gravitino.catalog.PropertiesMetadataHelpers.validatePropertyForCreate;
import static org.apache.gravitino.connector.BaseCatalogPropertiesMetadata.PROPERTY_METALAKE_IN_USE;
import static org.apache.gravitino.metalake.MetalakeManager.checkMetalake;
+import static org.apache.gravitino.utils.NameIdentifierUtil.ofFileset;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
@@ -100,13 +101,18 @@
import org.apache.gravitino.messaging.TopicCatalog;
import org.apache.gravitino.meta.AuditInfo;
import org.apache.gravitino.meta.CatalogEntity;
+import org.apache.gravitino.meta.FilesetEntity;
import org.apache.gravitino.meta.SchemaEntity;
import org.apache.gravitino.model.ModelCatalog;
import org.apache.gravitino.rel.SupportsPartitions;
import org.apache.gravitino.rel.Table;
import org.apache.gravitino.rel.TableCatalog;
import org.apache.gravitino.rel.ViewCatalog;
+import org.apache.gravitino.secret.SecretBinding;
import org.apache.gravitino.secret.SecretManager;
+import org.apache.gravitino.secret.SecretMaterial;
+import org.apache.gravitino.secret.SecretPropertyUtils;
+import org.apache.gravitino.secret.SecretReference;
import org.apache.gravitino.storage.IdGenerator;
import org.apache.gravitino.storage.relational.SupportsEntityChangeLog;
import org.apache.gravitino.utils.ClassLoaderKey;
@@ -364,6 +370,8 @@ private ModelCatalog asModels() {
@SuppressWarnings("UnusedVariable")
private final SecretManager secretManager;
+ private FilesetDispatcher filesetDispatcher;
+
private final List> removalListeners = Lists.newArrayList();
private final ConcurrentHashMap localMutationCounts =
new ConcurrentHashMap<>();
@@ -379,7 +387,7 @@ private ModelCatalog asModels() {
* @param config The configuration for the manager.
* @param store The entity store to use.
* @param idGenerator The id generator to use.
- * @param secretManager The secret manager used by catalog operations.
+ * @param secretManager The secret manager to use for create-time secret bindings/references.
*/
public CatalogManager(
Config config, EntityStore store, IdGenerator idGenerator, SecretManager secretManager) {
@@ -429,6 +437,16 @@ public CatalogManager(
}
}
+ /**
+ * Sets the {@link FilesetDispatcher} used when force-dropping a catalog. Must be called after the
+ * fileset dispatcher is constructed (CatalogManager is created first).
+ *
+ * @param filesetDispatcher The fileset dispatcher.
+ */
+ public void setFilesetDispatcher(FilesetDispatcher filesetDispatcher) {
+ this.filesetDispatcher = Preconditions.checkNotNull(filesetDispatcher);
+ }
+
/**
* Closes the CatalogManager and releases any resources associated with it. This method
* invalidates all cached catalog instances and clears the cache.
@@ -595,10 +613,31 @@ public Catalog createCatalog(
String comment,
Map properties)
throws NoSuchMetalakeException, CatalogAlreadyExistsException {
+ return createCatalog(
+ ident, type, provider, comment, properties, Collections.emptyMap(), Collections.emptyMap());
+ }
+
+ @Override
+ public Catalog createCatalog(
+ NameIdentifier ident,
+ Catalog.Type type,
+ String provider,
+ String comment,
+ Map properties,
+ Map secretBindings,
+ Map secretReferences)
+ throws NoSuchMetalakeException, CatalogAlreadyExistsException {
NameIdentifier metalakeIdent = NameIdentifier.of(ident.namespace().levels());
- Map mergedConfig = buildCatalogConf(provider, properties);
+ Map mergedConfig =
+ SecretPropertyUtils.copyEntityProperties(
+ buildCatalogConf(provider, properties), secretBindings, secretReferences);
long uid = idGenerator.nextId();
+
+ List secretMaterials =
+ secretManager.assembleSecretMaterials(
+ properties, mergedConfig, "catalog", uid, secretBindings, secretReferences);
+
StringIdentifier stringId = StringIdentifier.fromId(uid);
Instant now = Instant.now();
String creator = PrincipalUtils.getCurrentPrincipal().getName();
@@ -627,6 +666,8 @@ public Catalog createCatalog(
checkMetalake(metalakeIdent, store);
boolean needClean = true;
try {
+ secretManager.writeSecrets(secretMaterials);
+
store.put(e, false /* overwrite */);
CatalogWrapper wrapper =
catalogCache.get(ident, id -> createCatalogWrapper(e, mergedConfig));
@@ -635,7 +676,10 @@ public Catalog createCatalog(
return wrapper.catalog;
} catch (EntityAlreadyExistsException e1) {
+ // Catalog already exists: do not delete it, but roll back secrets written for this
+ // attempt (needClean is false so finally will not roll back).
needClean = false;
+ secretManager.rollbackSecrets(secretMaterials);
LOG.warn("Catalog {} already exists", ident, e1);
throw new CatalogAlreadyExistsException("Catalog %s already exists", ident);
@@ -652,6 +696,9 @@ public Catalog createCatalog(
} finally {
if (needClean) {
+ // Create failed after writeSecrets (or writeSecrets itself failed — rollback is
+ // best-effort / idempotent with writeSecrets' own partial cleanup).
+ secretManager.rollbackSecrets(secretMaterials);
// since we put the catalog entity into the store but failed to create the catalog
// instance,
// we need to clean up the entity stored.
@@ -955,29 +1002,69 @@ public boolean dropCatalog(NameIdentifier ident, boolean force)
"Catalog %s has schemas, please drop them first or use force option", ident);
}
- if (isManagedStorageCatalog(catalogWrapper)) {
+ // Drop filesets via FilesetDispatcher first so each fileset cleans its own
+ // write-through secrets. Do not snapshot/delete fileset secrets here.
+ for (SchemaEntity schema : schemaEntities) {
+ NameIdentifier schemaIdent = schema.nameIdentifier();
+ Namespace filesetNs =
+ Namespace.of(
+ schemaIdent.namespace().level(0),
+ schemaIdent.namespace().level(1),
+ schemaIdent.name());
+ List filesets =
+ store.list(filesetNs, FilesetEntity.class, EntityType.FILESET);
+ for (FilesetEntity fileset : filesets) {
+ filesetDispatcher.dropFileset(
+ ofFileset(
+ filesetNs.level(0),
+ filesetNs.level(1),
+ filesetNs.level(2),
+ fileset.name()));
+ }
+ }
+
+ // Snapshot schema properties (write-through secret URNs) before entities are removed.
+ // store.delete(cascade) only soft-deletes meta rows and does not call secret providers.
+ List