Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti
- Added support for `register table` overwrite semantics in the Iceberg REST catalog flow (`overwrite=true`) for internal Polaris catalogs. With overwrite enabled, existing table pointers can be updated to a new metadata location while preserving default behavior for `overwrite=false`.
- Added `REGISTER_TABLE_OVERWRITE` authorization operation mapped to `TABLE_FULL_METADATA` for deterministic overwrite authorization.
- Added Polaris Spark 4.0 client.

- Python CLI: added `gcp` as an external catalog authentication type for Iceberg REST federation, enabling CLI creation of GCP-authenticated catalogs such as BigLake without passing Google credential secrets through command-line flags.
### Changes

- Added REPL support to Polaris CLI.
Expand Down
5 changes: 5 additions & 0 deletions client/python/apache_polaris/cli/command/catalogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
PolarisCatalog,
CatalogProperties,
BearerAuthenticationParameters,
GcpAuthenticationParameters,
ImplicitAuthenticationParameters,
OAuthClientCredentialsParameters,
SigV4AuthenticationParameters,
Expand Down Expand Up @@ -357,6 +358,10 @@ def _build_connection_config_info(
authentication_type=self.catalog_authentication_type.upper(),
bearer_token=SecretStr(self.catalog_bearer_token),
)
elif self.catalog_authentication_type == AuthenticationType.GCP.value:
auth_params = GcpAuthenticationParameters(
authentication_type=self.catalog_authentication_type.upper()
)
elif self.catalog_authentication_type == AuthenticationType.SIGV4.value:
auth_params = SigV4AuthenticationParameters(
authentication_type=self.catalog_authentication_type.upper(),
Expand Down
6 changes: 6 additions & 0 deletions client/python/apache_polaris/cli/command/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ def _serialize_authentication_info(self, auth_params: Any) -> Dict[str, Any]:
if auth_params.bearer_token
else None
)
elif auth_type == AuthenticationType.GCP.value:
# No extra flags are required for GCP external-catalog authentication.
pass
elif auth_type == AuthenticationType.SIGV4.value:
auth_data.update(
{
Expand Down Expand Up @@ -811,6 +814,9 @@ def _map_authentication_properties(
)
elif auth_type == AuthenticationType.BEARER.value:
auth_args["catalog_bearer_token"] = auth_data.get("token")
elif auth_type == AuthenticationType.GCP.value:
# GCP external-catalog auth is server-side/ambient and carries no client secrets.
pass
elif auth_type == AuthenticationType.SIGV4.value:
auth_args.update(
{
Expand Down
1 change: 1 addition & 0 deletions client/python/apache_polaris/cli/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class AuthenticationType(Enum):

OAUTH = "oauth"
BEARER = "bearer"
GCP = "gcp"
SIGV4 = "sigv4"
IMPLICIT = "implicit"

Expand Down
2 changes: 1 addition & 1 deletion client/python/apache_polaris/cli/options/option_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class OptionTree:
Argument(
Arguments.CATALOG_AUTHENTICATION_TYPE,
str,
"Authentication type [OAUTH, BEARER, SIGV4, IMPLICIT]",
"Authentication type [OAUTH, BEARER, GCP, SIGV4, IMPLICIT]",
lower=True,
choices=[at.value for at in AuthenticationType],
group="External Catalog Federation: General Options",
Expand Down
54 changes: 54 additions & 0 deletions client/python/tests/test_catalogs_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,10 @@ def test_catalog_create_external_options(self) -> None:
call_args.catalog.connection_config_info.authentication_parameters.bearer_token.get_secret_value(),
"b",
)
self.assertEqual(
call_args.catalog.connection_config_info.authentication_parameters.authentication_type,
"BEARER",
)

def test_catalog_storage_type_exclusivity(self) -> None:
mock_client = self.build_mock_client()
Expand Down Expand Up @@ -658,6 +662,56 @@ def test_external_catalog_oauth(self) -> None:
self.assertEqual(call_args.catalog.storage_config_info.storage_type, "GCS")
self.assertEqual(call_args.catalog.properties.default_base_location, "dbl")

def test_external_catalog_gcp(self) -> None:
mock_client = self.build_mock_client()
self.mock_execute(
mock_client,
[
"catalogs",
"create",
"my-catalog",
"--type",
"external",
"--storage-type",
"gcs",
"--default-base-location",
"dbl",
"--catalog-connection-type",
"iceberg-rest",
"--iceberg-remote-catalog-name",
"biglake",
"--catalog-uri",
"https://biglake.googleapis.com/iceberg/v1/restcatalog",
"--catalog-authentication-type",
"gcp",
"--property",
"header.x-goog-user-project=my-billing-project",
],
)
call_args = mock_client.create_catalog.call_args[0][0]
self.assertEqual(call_args.catalog.name, "my-catalog")
self.assertEqual(call_args.catalog.type, "EXTERNAL")
self.assertEqual(
call_args.catalog.connection_config_info.connection_type, "ICEBERG_REST"
)
self.assertEqual(
call_args.catalog.connection_config_info.remote_catalog_name, "biglake"
)
self.assertEqual(
call_args.catalog.connection_config_info.uri,
"https://biglake.googleapis.com/iceberg/v1/restcatalog",
)
self.assertEqual(
call_args.catalog.connection_config_info.authentication_parameters.authentication_type,
"GCP",
)
self.assertEqual(call_args.catalog.storage_config_info.storage_type, "GCS")
self.assertEqual(call_args.catalog.properties.default_base_location, "dbl")
self.assertEqual(
call_args.catalog.properties.additional_properties,
{"header.x-goog-user-project": "my-billing-project"},
)

def test_external_catalog_hadoop(self) -> None:
mock_client = self.build_mock_client()
self.mock_execute(
Expand Down
2 changes: 1 addition & 1 deletion site/content/in-dev/unreleased/command-line-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ External Catalog Federation: General Options:
--iceberg-remote-catalog-name ICEBERG_REMOTE_CATALOG_NAME The remote catalog name when federating to an Iceberg REST catalog
--hadoop-warehouse HADOOP_WAREHOUSE The warehouse to use when federating to a HADOOP catalog
--hive-warehouse HIVE_WAREHOUSE The warehouse to use when federating to a HIVE catalog
--catalog-authentication-type {oauth,bearer,sigv4,implicit} Authentication type [OAUTH, BEARER, SIGV4, IMPLICIT]
--catalog-authentication-type {oauth,bearer,gcp,sigv4,implicit} Authentication type [OAUTH, BEARER, GCP, SIGV4, IMPLICIT]
--catalog-service-identity-type {aws_iam} Service identity type [AWS_IAM]
--catalog-uri CATALOG_URI The URI of the external catalog

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ REST implementation), enabling a Polaris service to access table and view entiti
- **REST endpoint:** The remote service must expose the Iceberg REST specification. Configure
firewalls so Polaris can reach the base URI you provide in the connection config.
- **Authentication:** Polaris forwards requests using the credentials defined in
`ConnectionConfigInfo.AuthenticationParameters`. OAuth2 client credentials, bearer tokens, and AWS
SigV4 are supported; choose the scheme the remote service expects.
`ConnectionConfigInfo.AuthenticationParameters`. OAuth2 client credentials, bearer tokens, GCP,
and AWS SigV4 are supported; choose the scheme the remote service expects.

## Feature configuration

Expand Down