From 9ff2c1fc53e13ee0a515f21ce215acc765086076 Mon Sep 17 00:00:00 2001 From: Rishi Date: Tue, 6 Oct 2026 19:35:49 -0400 Subject: [PATCH] Spec: Add named storage configurations to the management API Adds an optional `storageConfigInfos` array to `Catalog` and `UpdateCatalogRequest`, the `StorageConfigInfos` list schema, and the `/catalogs/{catalogName}/storage-configs[/{storageConfigName}]` endpoints for managing one named storage configuration at a time. This is the API part of #5556. The server does not implement it yet: the new endpoints answer with the generated 501 default, and the server ignores `storageConfigInfos`. `storageConfigInfos` is `nullable: true` and `openApiNullable` is disabled for the model module, so an omitted array stays `null` instead of becoming an empty list, and the field keeps a plain `List` type. Existing callers of the all-args constructors pass `null` for the new argument. --- api/management-model/build.gradle.kts | 5 + ...stractSemanticModelCatalogHandlerTest.java | 3 +- .../polaris/service/it/env/ManagementApi.java | 3 +- ...larisManagementServiceIntegrationTest.java | 26 ++-- .../it/GcpCatalogFederationIntegrationIT.java | 3 +- .../service/admin/ManagementServiceTest.java | 42 ++++-- .../admin/PolarisOverlappingCatalogTest.java | 3 +- .../admin/PolarisS3InteroperabilityTest.java | 3 +- ...3CredentialVendingMechanismRoutesTest.java | 3 +- .../CommitTransactionMetadataCleanupTest.java | 2 +- .../iceberg/CommitTransactionTest.java | 2 +- .../EntityIdempotencyUpdateTableTest.java | 3 +- .../iceberg/IcebergAllowedLocationTest.java | 3 +- .../iceberg/IcebergOverlappingTableTest.java | 3 +- .../iceberg/IcebergTableLocationTest.java | 3 +- .../EntityIdempotencyCreateTableTest.java | 3 +- .../S3CredentialVendingMechanismCdiTest.java | 3 +- ...VendingMechanismThirdMechanismCdiTest.java | 3 +- spec/polaris-management-service.yml | 122 ++++++++++++++++++ 19 files changed, 201 insertions(+), 37 deletions(-) diff --git a/api/management-model/build.gradle.kts b/api/management-model/build.gradle.kts index 3e2f3120e7f..9ff9032e776 100644 --- a/api/management-model/build.gradle.kts +++ b/api/management-model/build.gradle.kts @@ -67,6 +67,11 @@ openApiGenerate { configOptions.put("generateBuilders", "true") configOptions.put("generateConstructorWithAllArgs", "true") configOptions.put("hideGenerationTimestamp", "true") + // Disable the JsonNullable wrapper for `nullable: true` properties: we only use `nullable: + // true` (on Catalog/UpdateCatalogRequest#storageConfigInfos) to make the Java codegen emit a + // `null` default for an omitted array instead of its usual `new ArrayList<>()`, and want the + // plain `List` type it would otherwise generate, not `JsonNullable<...>`. + configOptions.put("openApiNullable", "false") additionalProperties.put("apiNamePrefix", "Polaris") additionalProperties.put("apiNameSuffix", "Api") additionalProperties.put("metricsPrefix", "polaris") diff --git a/extensions/semantic-models/src/test/java/org/apache/polaris/service/catalog/semanticmodel/AbstractSemanticModelCatalogHandlerTest.java b/extensions/semantic-models/src/test/java/org/apache/polaris/service/catalog/semanticmodel/AbstractSemanticModelCatalogHandlerTest.java index 29f020f97be..f1fafec1da0 100644 --- a/extensions/semantic-models/src/test/java/org/apache/polaris/service/catalog/semanticmodel/AbstractSemanticModelCatalogHandlerTest.java +++ b/extensions/semantic-models/src/test/java/org/apache/polaris/service/catalog/semanticmodel/AbstractSemanticModelCatalogHandlerTest.java @@ -136,7 +136,8 @@ private void createCatalogNamespaceAndTable() { 1, FileStorageConfigInfo.builder() .setStorageType(StorageConfigInfo.StorageTypeEnum.FILE) - .build()); + .build(), + null); try (Response response = services .catalogsApi() diff --git a/integration-tests/src/main/java/org/apache/polaris/service/it/env/ManagementApi.java b/integration-tests/src/main/java/org/apache/polaris/service/it/env/ManagementApi.java index a8ca8673b78..8fa0dcd53ff 100644 --- a/integration-tests/src/main/java/org/apache/polaris/service/it/env/ManagementApi.java +++ b/integration-tests/src/main/java/org/apache/polaris/service/it/env/ManagementApi.java @@ -220,7 +220,8 @@ public void updateCatalog(Catalog catalog, Map catalogProps) { new UpdateCatalogRequest( catalog.getEntityVersion(), catalogProps, - catalog.getStorageConfigInfo())))) { + catalog.getStorageConfigInfo(), + null)))) { assertThat(response.getStatus()).isEqualTo(OK.getStatusCode()); } } diff --git a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisManagementServiceIntegrationTest.java b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisManagementServiceIntegrationTest.java index e5cf286a765..fb760210ac2 100644 --- a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisManagementServiceIntegrationTest.java +++ b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisManagementServiceIntegrationTest.java @@ -450,7 +450,10 @@ public void testUpdateCatalogWithoutDefaultBaseLocationInUpdate() { // default-base-location. UpdateCatalogRequest updateRequest = new UpdateCatalogRequest( - fetchedCatalog.getEntityVersion(), Map.of("foo", "bar"), null /* storageConfigIno */); + fetchedCatalog.getEntityVersion(), + Map.of("foo", "bar"), + null /* storageConfigIno */, + null); // Successfully update Catalog updatedCatalog; @@ -587,7 +590,8 @@ public void testCreateAndUpdateAzureCatalog() { new UpdateCatalogRequest( fetchedCatalog.getEntityVersion(), Map.of("default-base-location", "abfss://newcontainer@acct1.dfs.core.windows.net/"), - modifiedStorageConfig); + modifiedStorageConfig, + null); try (Response response = managementApi.request("v1/catalogs/" + catalogName).put(Entity.json(badUpdateRequest))) { assertThat(response) @@ -604,7 +608,8 @@ public void testCreateAndUpdateAzureCatalog() { new UpdateCatalogRequest( fetchedCatalog.getEntityVersion(), Map.of("default-base-location", "abfss://newcontainer@acct1.dfs.core.windows.net/"), - storageConfig); + storageConfig, + null); // 200 successful update try (Response response = @@ -680,7 +685,8 @@ public void testCreateListUpdateAndDeleteCatalog() { new UpdateCatalogRequest( fetchedCatalog.getEntityVersion(), Map.of("default-base-location", "s3://newbucket/"), - invalidModifiedStorageConfig); + invalidModifiedStorageConfig, + null); try (Response response = managementApi .request("v1/catalogs/{cat}", Map.of("cat", catalogName)) @@ -707,7 +713,8 @@ public void testCreateListUpdateAndDeleteCatalog() { new UpdateCatalogRequest( fetchedCatalog.getEntityVersion(), Map.of("default-base-location", "s3://newbucket/"), - validModifiedStorageConfig); + validModifiedStorageConfig, + null); // 200 successful update try (Response response = @@ -795,7 +802,8 @@ public void testUpdateCatalogChangeAwsAccountIdRejected() { new UpdateCatalogRequest( fetchedCatalog.getEntityVersion(), Map.of("default-base-location", "s3://bucket1/"), - differentAccountConfig); + differentAccountConfig, + null); try (Response response = managementApi @@ -847,7 +855,8 @@ public void testUpdateCatalogChangeRoleWithinSameAccountAllowed() { new UpdateCatalogRequest( fetchedCatalog.getEntityVersion(), Map.of("default-base-location", "s3://bucket1/"), - updatedConfig); + updatedConfig, + null); try (Response response = managementApi @@ -898,7 +907,8 @@ public void testUpdateCatalogChangeExternalIdRejected() { new UpdateCatalogRequest( fetchedCatalog.getEntityVersion(), Map.of("default-base-location", "s3://bucket1/"), - configWithDifferentExternalId); + configWithDifferentExternalId, + null); try (Response response = managementApi diff --git a/runtime/service/src/cloudTest/java/org/apache/polaris/service/it/GcpCatalogFederationIntegrationIT.java b/runtime/service/src/cloudTest/java/org/apache/polaris/service/it/GcpCatalogFederationIntegrationIT.java index 4115c6fe7e6..793a9866268 100644 --- a/runtime/service/src/cloudTest/java/org/apache/polaris/service/it/GcpCatalogFederationIntegrationIT.java +++ b/runtime/service/src/cloudTest/java/org/apache/polaris/service/it/GcpCatalogFederationIntegrationIT.java @@ -193,7 +193,8 @@ private void createCatalog() { .setGcsServiceAccount(SERVICE_ACCOUNT) .setStorageType(StorageConfigInfo.StorageTypeEnum.GCS) .setAllowedLocations(List.of(BASE_LOCATION)) - .build()); + .build(), + null); managementApi.createCatalog(catalog); } diff --git a/runtime/service/src/test/java/org/apache/polaris/service/admin/ManagementServiceTest.java b/runtime/service/src/test/java/org/apache/polaris/service/admin/ManagementServiceTest.java index fde669bd085..f1bd15b5f27 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/admin/ManagementServiceTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/admin/ManagementServiceTest.java @@ -218,7 +218,8 @@ public void testUpdateCatalogWithDisallowedStorageConfig() { new UpdateCatalogRequest( fetchedCatalog.getEntityVersion(), Map.of("default-base-location", "file:///tmp/path/to/data/"), - fileStorage); + fileStorage, + null); // failure to update assertThatThrownBy( @@ -241,7 +242,8 @@ public void testUpdateCatalogWithDisallowedStorageConfig() { .setAllowedLocations(List.of("s3://bucket/path/to/data")) .setRoleArn("arn:aws:iam::123456789012:role/my-role") .setEndpoint("http://example.com") - .build()); + .build(), + null); assertThatThrownBy( () -> services @@ -525,7 +527,8 @@ public void testUpdateCatalogChangeAwsAccountIdBlockedByDefault() { AwsStorageConfigInfo.builder(StorageConfigInfo.StorageTypeEnum.S3) .setAllowedLocations(List.of("s3://bucket/path/to/data")) .setRoleArn("arn:aws:iam::999999999999:role/other-role") - .build()); + .build(), + null); assertThatThrownBy( () -> services @@ -581,7 +584,8 @@ public void testUpdateCatalogChangesWithinSameAccountAllowed() { AwsStorageConfigInfo.builder(StorageConfigInfo.StorageTypeEnum.S3) .setAllowedLocations(List.of("s3://bucket/path/to/data")) .setRoleArn("arn:aws:iam::123456789012:role/other-role") - .build()); + .build(), + null); try (Response response = services .catalogsApi() @@ -635,7 +639,8 @@ public void testUpdateCatalogChangeExternalIdBlockedByDefault() { .setAllowedLocations(List.of("s3://bucket/path/to/data")) .setRoleArn("arn:aws:iam::123456789012:role/my-role") .setExternalId("different-external-id") - .build()); + .build(), + null); assertThatThrownBy( () -> services @@ -706,7 +711,8 @@ public void testUpdateCatalogStorageConfigChangesAllowedWithFeatureFlag() { .setAllowedLocations(List.of("s3://bucket/path/to/data")) .setRoleArn("arn:aws:iam::999999999999:role/other-role") .setExternalId("different-external-id") - .build()); + .build(), + null); try (Response response = flagEnabledServices .catalogsApi() @@ -901,7 +907,8 @@ public void testDisallowedMechanismIsRejectedOnUpdateToo() { new UpdateCatalogRequest( fetched.getEntityVersion(), Map.of("default-base-location", "s3://second-bucket/base/empty-stay"), - secondMechanismConfig().build()); + secondMechanismConfig().build(), + null); assertThatThrownBy( () -> stsOnlyUnrestricted @@ -941,7 +948,8 @@ public void theLiteralDefaultIsRefusedLikeAnyUnknownValueAtCreateAndUpdate() { new UpdateCatalogRequest( fetched.getEntityVersion(), Map.of("default-base-location", "s3://second-bucket/base/stays-empty"), - namesDefault); + namesDefault, + null); assertThatThrownBy( () -> svc.catalogsApi() @@ -971,7 +979,8 @@ public void anEmptyMechanismCanBeUpdatedToAnExplicitSts() { new UpdateCatalogRequest( fetched.getEntityVersion(), Map.of("default-base-location", "s3://second-bucket/base/empty-then-sts"), - explicitSts); + explicitSts, + null); try (Response response = svc.catalogsApi() .updateCatalog("empty-then-sts", toSts, svc.realmContext(), svc.securityContext())) { @@ -1017,7 +1026,8 @@ public void anExplicitStsCanBeUpdatedBackToTheEmptyField() { new UpdateCatalogRequest( fetched.getEntityVersion(), Map.of("default-base-location", "s3://second-bucket/base/sts-then-empty"), - backToDefault); + backToDefault, + null); try (Response response = svc.catalogsApi() .updateCatalog("sts-then-empty", toEmpty, svc.realmContext(), svc.securityContext())) { @@ -1076,7 +1086,8 @@ public void changingTheMechanismIsAcceptedAndValidatedByTheNewMechanism() { .setCredentialVendingMechanism(TEST_MECHANISM) .setAllowedLocations(List.of("s3://second-bucket/base/")) .setEndpoint("https://s3.example.test") - .build()); + .build(), + null); try (Response response = svc.catalogsApi() .updateCatalog( @@ -1117,7 +1128,8 @@ public void emptyMechanismCatalogEndpointStaysMutable() { .setAllowedLocations(List.of("s3://second-bucket/base/")) .setEndpoint("https://s3.other.example.com:1234") .setPathStyleAccess(true) - .build()); + .build(), + null); try (Response response = svc.catalogsApi() .updateCatalog( @@ -1157,7 +1169,8 @@ public void updatingACatalogToAnAllowlistedButUninstalledMechanismIsRefused() { AwsStorageConfigInfo.builder(StorageConfigInfo.StorageTypeEnum.S3) .setCredentialVendingMechanism("UNINSTALLED_MECHANISM") .setAllowedLocations(List.of("s3://second-bucket/base/")) - .build()); + .build(), + null); assertThatThrownBy( () -> svc.catalogsApi() @@ -1223,7 +1236,8 @@ public void theMechanismValidatesTheConfigAtCreateAndAtUpdate() { new UpdateCatalogRequest( fetched.getEntityVersion(), Map.of("default-base-location", "s3://second-bucket/base/needs-endpoint"), - base.setEndpoint(null).build()); + base.setEndpoint(null).build(), + null); assertThatThrownBy( () -> svc.catalogsApi() diff --git a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisOverlappingCatalogTest.java b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisOverlappingCatalogTest.java index 164628ac1a9..04524ffdc78 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisOverlappingCatalogTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisOverlappingCatalogTest.java @@ -94,7 +94,8 @@ private Response createCatalog( System.currentTimeMillis(), System.currentTimeMillis(), 1, - config); + config, + null); return services .catalogsApi() .createCatalog( diff --git a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisS3InteroperabilityTest.java b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisS3InteroperabilityTest.java index 2f05b30fca1..296b5ffb660 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisS3InteroperabilityTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisS3InteroperabilityTest.java @@ -106,7 +106,8 @@ private PolarisCatalog createCatalog(String catalogName, String scheme) { 1725487592064L, 1725487592064L, 1, - config); + config, + null); try (Response response = services .catalogsApi() diff --git a/runtime/service/src/test/java/org/apache/polaris/service/admin/S3CredentialVendingMechanismRoutesTest.java b/runtime/service/src/test/java/org/apache/polaris/service/admin/S3CredentialVendingMechanismRoutesTest.java index 85816786f3b..2c5fb99d01f 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/admin/S3CredentialVendingMechanismRoutesTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/admin/S3CredentialVendingMechanismRoutesTest.java @@ -341,7 +341,8 @@ void theRealmKillSwitchRefusesTheCatalogWithNotEnabled(boolean skipSubscoping) { new UpdateCatalogRequest( fetched.getEntityVersion(), Map.of("default-base-location", "s3://bucket/base/mechkill"), - fetched.getStorageConfigInfo()); + fetched.getStorageConfigInfo(), + null); assertThatThrownBy( () -> svc.catalogsApi() diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/CommitTransactionMetadataCleanupTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/CommitTransactionMetadataCleanupTest.java index 1789afb730a..36ed77ad07c 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/CommitTransactionMetadataCleanupTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/CommitTransactionMetadataCleanupTest.java @@ -163,7 +163,7 @@ private void createCatalogAndNamespace(TestServices services, String catalogLoca .build(); Catalog catalogObject = new Catalog( - Catalog.TypeEnum.INTERNAL, catalog, propertiesBuilder.build(), 0L, 0L, 1, config); + Catalog.TypeEnum.INTERNAL, catalog, propertiesBuilder.build(), 0L, 0L, 1, config, null); try (Response response = services .catalogsApi() diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/CommitTransactionTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/CommitTransactionTest.java index cc670fe7237..d8c14ed041d 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/CommitTransactionTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/CommitTransactionTest.java @@ -217,7 +217,7 @@ private void createCatalogAndNamespace( .build(); Catalog catalogObject = new Catalog( - Catalog.TypeEnum.INTERNAL, catalog, propertiesBuilder.build(), 0L, 0L, 1, config); + Catalog.TypeEnum.INTERNAL, catalog, propertiesBuilder.build(), 0L, 0L, 1, config, null); try (Response response = services .catalogsApi() diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/EntityIdempotencyUpdateTableTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/EntityIdempotencyUpdateTableTest.java index 92c6b2630af..a96685ed1d0 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/EntityIdempotencyUpdateTableTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/EntityIdempotencyUpdateTableTest.java @@ -207,7 +207,8 @@ private static void createCatalog(TestServices services, String catalogLocation) 1725487592064L, 1725487592064L, 1, - storageConfig); + storageConfig, + null); try (Response response = services .catalogsApi() diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergAllowedLocationTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergAllowedLocationTest.java index 639cc0ec1b6..80e77f33d6b 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergAllowedLocationTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergAllowedLocationTest.java @@ -852,7 +852,8 @@ private void createCatalog( 1725487592064L, 1725487592064L, 1, - config); + config, + null); try (Response response = services .catalogsApi() diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergOverlappingTableTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergOverlappingTableTest.java index 22990dce4d7..2af4c679524 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergOverlappingTableTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergOverlappingTableTest.java @@ -186,7 +186,8 @@ private void createCatalogAndNamespace( 1725487592064L, 1725487592064L, 1, - config); + config, + null); try (Response response = services .catalogsApi() diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergTableLocationTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergTableLocationTest.java index f604e3283d3..2512236ca92 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergTableLocationTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergTableLocationTest.java @@ -114,7 +114,8 @@ private void createCatalogAndNamespace( 1725487592064L, 1725487592064L, 1, - config); + config, + null); try (Response response = services .catalogsApi() diff --git a/runtime/service/src/test/java/org/apache/polaris/service/idempotency/EntityIdempotencyCreateTableTest.java b/runtime/service/src/test/java/org/apache/polaris/service/idempotency/EntityIdempotencyCreateTableTest.java index 6b945bbd420..ad66e0a068c 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/idempotency/EntityIdempotencyCreateTableTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/idempotency/EntityIdempotencyCreateTableTest.java @@ -188,7 +188,8 @@ private static void createCatalog(TestServices services, String catalogLocation) 1725487592064L, 1725487592064L, 1, - storageConfig); + storageConfig, + null); try (Response response = services .catalogsApi() diff --git a/runtime/service/src/test/java/org/apache/polaris/service/storage/S3CredentialVendingMechanismCdiTest.java b/runtime/service/src/test/java/org/apache/polaris/service/storage/S3CredentialVendingMechanismCdiTest.java index 1092e8434c6..bb19b868e98 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/storage/S3CredentialVendingMechanismCdiTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/storage/S3CredentialVendingMechanismCdiTest.java @@ -544,7 +544,8 @@ private static UpdateCatalogRequest updateToUninstalledMechanismRequest( .setCredentialVendingMechanism(UNINSTALLED_MECHANISM) .setRoleArn("arn:aws:iam::123456789012:role/r") .setAllowedLocations(List.of("s3://bucket/base/" + name + "/")) - .build()); + .build(), + null); } private static void createTable(CatalogApi catalogApi, String catalog, String ns, String table) { diff --git a/runtime/service/src/test/java/org/apache/polaris/service/storage/S3CredentialVendingMechanismThirdMechanismCdiTest.java b/runtime/service/src/test/java/org/apache/polaris/service/storage/S3CredentialVendingMechanismThirdMechanismCdiTest.java index 924723b16b3..6c8494ab81d 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/storage/S3CredentialVendingMechanismThirdMechanismCdiTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/storage/S3CredentialVendingMechanismThirdMechanismCdiTest.java @@ -188,7 +188,8 @@ void anInstalledThirdMechanismVendsWhenAllowlistedAndIsRefusedWithoutDispatchWhe List.of( "s3://bucket/base/" + catalog + "/", "s3://bucket/base/" + catalog + "-extra/")) - .build()); + .build(), + null); try (Response r = managementApi .request("v1/catalogs/{name}", Map.of("name", catalog)) diff --git a/spec/polaris-management-service.yml b/spec/polaris-management-service.yml index 13c5d9ee124..0b12987ae8b 100644 --- a/spec/polaris-management-service.yml +++ b/spec/polaris-management-service.yml @@ -589,6 +589,105 @@ paths: 404: description: "The principal role does not exist" + /catalogs/{catalogName}/storage-configs: + parameters: + - name: catalogName + in: path + description: The catalog that holds the named storage configurations + required: true + schema: + type: string + minLength: 1 + maxLength: 256 + pattern: '^(?!\s*[sS][yY][sS][tT][eE][mM]\$).*$' + get: + operationId: listStorageConfigs + description: List the named storage configurations held by a catalog. The catalog's default storageConfigInfo is not included. + responses: + 200: + description: The named storage configurations of the catalog + content: + application/json: + schema: + $ref: "#/components/schemas/StorageConfigInfos" + 403: + description: "The caller does not have permission to read catalog details" + 404: + description: "The catalog does not exist" + + /catalogs/{catalogName}/storage-configs/{storageConfigName}: + parameters: + - name: catalogName + in: path + description: The catalog that holds the named storage configuration + required: true + schema: + type: string + minLength: 1 + maxLength: 256 + pattern: '^(?!\s*[sS][yY][sS][tT][eE][mM]\$).*$' + - name: storageConfigName + in: path + description: The name of the named storage configuration. Surrounding whitespace is trimmed; the result must match ^[a-zA-Z0-9_-]{1,128}$. + required: true + schema: + type: string + minLength: 1 + maxLength: 128 + get: + operationId: getStorageConfig + description: Get one named storage configuration of a catalog + responses: + 200: + description: The named storage configuration + content: + application/json: + schema: + $ref: "#/components/schemas/StorageConfigInfo" + 403: + description: "The caller does not have permission to read catalog details" + 404: + description: "The catalog or the named storage configuration does not exist" + + put: + operationId: putStorageConfig + description: Create or replace one named storage configuration of a catalog, leaving the catalog's other storage configurations unchanged. If the payload sets storageName, it must equal the storageConfigName path parameter; if it does not, the path parameter is used as the name. + requestBody: + description: The storage configuration to store under this name + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/StorageConfigInfo" + responses: + 200: + description: The stored named storage configuration + content: + application/json: + schema: + $ref: "#/components/schemas/StorageConfigInfo" + 400: + description: "The storage configuration is invalid, or replacing it would make a change that is not allowed" + 403: + description: "The caller does not have permission to update catalog details" + 404: + description: "The catalog does not exist" + 409: + description: "The catalog was modified concurrently; retry" + + delete: + operationId: deleteStorageConfig + description: Remove one named storage configuration from a catalog + responses: + 204: + description: "Success, no content" + 403: + description: "The caller does not have permission to update catalog details" + 404: + description: "The catalog or the named storage configuration does not exist" + 409: + description: "The catalog was modified concurrently; retry" + /catalogs/{catalogName}/catalog-roles: parameters: - name: catalogName @@ -828,6 +927,17 @@ components: required: - catalogs + StorageConfigInfos: + type: object + description: A list of named storage configurations + properties: + storageConfigInfos: + type: array + items: + $ref: "#/components/schemas/StorageConfigInfo" + required: + - storageConfigInfos + CreateCatalogRequest: type: object description: Request to create a new catalog @@ -879,6 +989,12 @@ components: description: The version of the catalog object used to determine if the catalog metadata has changed storageConfigInfo: $ref: "#/components/schemas/StorageConfigInfo" + storageConfigInfos: + type: array + nullable: true + description: Optional list of additional named storage configurations held by the catalog, alongside its default storageConfigInfo. Each entry is identified by its storageName, which is required. Names are trimmed, must match ^[a-zA-Z0-9_-]{1,128}$, are case-sensitive, must be unique within the list, and must differ from the storageName of the default storageConfigInfo. Unlike the default storageConfigInfo, each entry must list at least one allowedLocations entry. + items: + $ref: "#/components/schemas/StorageConfigInfo" required: - name - type @@ -1303,6 +1419,12 @@ components: type: string storageConfigInfo: $ref: "#/components/schemas/StorageConfigInfo" + storageConfigInfos: + type: array + nullable: true + description: Optional replacement set of named storage configurations. When present, replaces the catalog's entire set of named storage configurations (an empty array removes them all); when absent, the existing set is left unaltered. Entries follow the same rules as Catalog.storageConfigInfos. + items: + $ref: "#/components/schemas/StorageConfigInfo" Principals: description: A list of Principals