diff --git a/polaris-service/src/main/java/org/apache/polaris/service/catalog/ExtendedConfigResponse.java b/polaris-service/src/main/java/org/apache/polaris/service/catalog/ExtendedConfigResponse.java new file mode 100644 index 000000000..da1407084 --- /dev/null +++ b/polaris-service/src/main/java/org/apache/polaris/service/catalog/ExtendedConfigResponse.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.service.catalog; + +import com.google.common.base.MoreObjects; +import java.util.List; +import java.util.Map; +import org.apache.iceberg.rest.responses.ConfigResponse; + +// TODO: Replace with Iceberg's ConfigResponse after integrating Iceberg 1.7.0 +// Related PR: https://github.com/apache/iceberg/pull/10929#issuecomment-2418591566 +public class ExtendedConfigResponse extends ConfigResponse { + private final ConfigResponse configResponse; + private List endpoints; + + public ExtendedConfigResponse(ConfigResponse configResponse, List endpoints) { + this.configResponse = configResponse; + this.endpoints = endpoints; + } + + public List endpoints() { + return endpoints; + } + + public void setEndpoints(List endpoints) { + this.endpoints = endpoints; + } + + @Override + public Map defaults() { + return configResponse.defaults(); + } + + @Override + public Map overrides() { + return configResponse.overrides(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("defaults", this.defaults()) + .add("overrides", this.overrides()) + .add("endpoints", this.endpoints()) + .toString(); + } + + public static Builder extendedBuilder() { + return new Builder(); + } + + public static class Builder { + private final ConfigResponse.Builder configBuilder; + private List endpoints; + + public Builder() { + this.configBuilder = ConfigResponse.builder(); + } + + public Builder withDefault(String key, String value) { + configBuilder.withDefault(key, value); + return this; + } + + public Builder withDefaults(Map defaultsToAdd) { + configBuilder.withDefaults(defaultsToAdd); + return this; + } + + public Builder withOverride(String key, String value) { + configBuilder.withOverride(key, value); + return this; + } + + public Builder withOverrides(Map overridesToAdd) { + configBuilder.withOverrides(overridesToAdd); + return this; + } + + public Builder withEndpoints(List endpoints) { + this.endpoints = endpoints; + return this; + } + + public ExtendedConfigResponse build() { + ConfigResponse configResponse = configBuilder.build(); + return new ExtendedConfigResponse(configResponse, endpoints); + } + } +} diff --git a/polaris-service/src/main/java/org/apache/polaris/service/catalog/ExtendedResourcePaths.java b/polaris-service/src/main/java/org/apache/polaris/service/catalog/ExtendedResourcePaths.java new file mode 100644 index 000000000..5e6a6b0a3 --- /dev/null +++ b/polaris-service/src/main/java/org/apache/polaris/service/catalog/ExtendedResourcePaths.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.service.catalog; + +import org.apache.iceberg.rest.ResourcePaths; + +// TODO: Replace with Iceberg's ResourcePaths after integrating Iceberg 1.7.0 +// Related PR: https://github.com/apache/iceberg/pull/10929#issuecomment-2418591566 +public class ExtendedResourcePaths extends ResourcePaths { + public static final String V1_NAMESPACES = "/v1/{prefix}/namespaces"; + public static final String V1_NAMESPACE = "/v1/{prefix}/namespaces/{namespace}"; + public static final String V1_NAMESPACE_PROPERTIES = + "/v1/{prefix}/namespaces/{namespace}/properties"; + public static final String V1_TABLES = "/v1/{prefix}/namespaces/{namespace}/tables"; + public static final String V1_TABLE = "/v1/{prefix}/namespaces/{namespace}/tables/{table}"; + public static final String V1_TABLE_REGISTER = "/v1/{prefix}/namespaces/{namespace}/register"; + public static final String V1_TABLE_METRICS = + "/v1/{prefix}/namespaces/{namespace}/tables/{table}/metrics"; + public static final String V1_TABLE_RENAME = "/v1/{prefix}/tables/rename"; + public static final String V1_TRANSACTIONS_COMMIT = "/v1/{prefix}/transactions/commit"; + public static final String V1_VIEWS = "/v1/{prefix}/namespaces/{namespace}/views"; + public static final String V1_VIEW = "/v1/{prefix}/namespaces/{namespace}/views/{view}"; + public static final String V1_VIEW_RENAME = "/v1/{prefix}/views/rename"; + + public ExtendedResourcePaths(String prefix) { + super(prefix); + } +} diff --git a/polaris-service/src/main/java/org/apache/polaris/service/catalog/IcebergCatalogAdapter.java b/polaris-service/src/main/java/org/apache/polaris/service/catalog/IcebergCatalogAdapter.java index 5488f820a..378a4e4c8 100644 --- a/polaris-service/src/main/java/org/apache/polaris/service/catalog/IcebergCatalogAdapter.java +++ b/polaris-service/src/main/java/org/apache/polaris/service/catalog/IcebergCatalogAdapter.java @@ -27,6 +27,7 @@ import java.net.URLEncoder; import java.nio.charset.Charset; import java.util.EnumSet; +import java.util.List; import java.util.Map; import java.util.Optional; import org.apache.iceberg.catalog.Catalog; @@ -44,7 +45,6 @@ import org.apache.iceberg.rest.requests.RenameTableRequest; import org.apache.iceberg.rest.requests.ReportMetricsRequest; import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest; -import org.apache.iceberg.rest.responses.ConfigResponse; import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal; import org.apache.polaris.core.auth.PolarisAuthorizer; import org.apache.polaris.core.context.CallContext; @@ -456,11 +456,37 @@ public Response getConfig(String warehouse, SecurityContext securityContext) { EntityCacheEntry resolvedReferenceCatalog = resolver.getResolvedReferenceCatalog(); Map properties = PolarisEntity.of(resolvedReferenceCatalog.getEntity()).getPropertiesAsMap(); + List endpoints = + List.of( + // namespace endpoints + String.join(" ", "GET", ExtendedResourcePaths.V1_NAMESPACES), + String.join(" ", "GET", ExtendedResourcePaths.V1_NAMESPACE), + String.join(" ", "POST", ExtendedResourcePaths.V1_NAMESPACES), + String.join(" ", "POST", ExtendedResourcePaths.V1_NAMESPACE_PROPERTIES), + String.join(" ", "DELETE", ExtendedResourcePaths.V1_NAMESPACE), + String.join(" ", "POST", ExtendedResourcePaths.V1_TRANSACTIONS_COMMIT), + // table endpoints + String.join(" ", "GET", ExtendedResourcePaths.V1_TABLES), + String.join(" ", "GET", ExtendedResourcePaths.V1_TABLE), + String.join(" ", "POST", ExtendedResourcePaths.V1_TABLES), + String.join(" ", "POST", ExtendedResourcePaths.V1_TABLE), + String.join(" ", "DELETE", ExtendedResourcePaths.V1_TABLE), + String.join(" ", "POST", ExtendedResourcePaths.V1_TABLE_RENAME), + String.join(" ", "POST", ExtendedResourcePaths.V1_TABLE_REGISTER), + String.join(" ", "POST", ExtendedResourcePaths.V1_TABLE_METRICS), + // view endpoints + String.join(" ", "GET", ExtendedResourcePaths.V1_VIEWS), + String.join(" ", "GET", ExtendedResourcePaths.V1_VIEW), + String.join(" ", "POST", ExtendedResourcePaths.V1_VIEWS), + String.join(" ", "POST", ExtendedResourcePaths.V1_VIEW), + String.join(" ", "DELETE", ExtendedResourcePaths.V1_VIEW), + String.join(" ", "POST", ExtendedResourcePaths.V1_VIEW_RENAME)); return Response.ok( - ConfigResponse.builder() + ExtendedConfigResponse.extendedBuilder() .withDefaults(properties) // catalog properties are defaults .withOverrides(ImmutableMap.of("prefix", warehouse)) + .withEndpoints(endpoints) .build()) .build(); } diff --git a/spec/rest-catalog-open-api.yaml b/spec/rest-catalog-open-api.yaml index c7c2ce63a..a85fe5e73 100644 --- a/spec/rest-catalog-open-api.yaml +++ b/spec/rest-catalog-open-api.yaml @@ -100,6 +100,37 @@ paths: Common catalog configuration settings are documented at https://iceberg.apache.org/docs/latest/configuration/#catalog-properties + + The catalog configuration also holds an optional `endpoints` field that contains information about the endpoints + supported by the server. If a server does not send the `endpoints` field, a default set of endpoints is assumed: + + - GET /v1/{prefix}/namespaces + + - POST /v1/{prefix}/namespaces + + - GET /v1/{prefix}/namespaces/{namespace} + + - DELETE /v1/{prefix}/namespaces/{namespace} + + - POST /v1/{prefix}/namespaces/{namespace}/properties + + - GET /v1/{prefix}/namespaces/{namespace}/tables + + - POST /v1/{prefix}/namespaces/{namespace}/tables + + - GET /v1/{prefix}/namespaces/{namespace}/tables/{table} + + - POST /v1/{prefix}/namespaces/{namespace}/tables/{table} + + - DELETE /v1/{prefix}/namespaces/{namespace}/tables/{table} + + - POST /v1/{prefix}/namespaces/{namespace}/register + + - POST /v1/{prefix}/namespaces/{namespace}/tables/{table}/metrics + + - POST /v1/{prefix}/tables/rename + + - POST /v1/{prefix}/transactions/commit " responses: 200: @@ -114,7 +145,14 @@ paths: }, "defaults": { "clients": "4" - } + }, + "endpoints": [ + "GET /v1/{prefix}/namespaces/{namespace}", + "GET /v1/{prefix}/namespaces", + "POST /v1/{prefix}/namespaces", + "GET /v1/{prefix}/namespaces/{namespace}/tables/{table}", + "GET /v1/{prefix}/namespaces/{namespace}/views/{view}" + ] } 400: $ref: '#/components/responses/BadRequestErrorResponse' @@ -1645,6 +1683,19 @@ components: type: string description: Properties that should be used as default configuration; applied before client configuration. + endpoints: + type: array + items: + type: string + description: A list of endpoints that the server supports. The format of each endpoint must be " ". + The HTTP verb and the resource path must be separated by a space character. + example: [ + "GET /v1/{prefix}/namespaces/{namespace}", + "GET /v1/{prefix}/namespaces", + "POST /v1/{prefix}/namespaces", + "GET /v1/{prefix}/namespaces/{namespace}/tables/{table}", + "GET /v1/{prefix}/namespaces/{namespace}/views/{view}" + ] CreateNamespaceRequest: type: object