diff --git a/api/iceberg-service/build.gradle.kts b/api/iceberg-service/build.gradle.kts index 0a581a2a4..d8a75d064 100644 --- a/api/iceberg-service/build.gradle.kts +++ b/api/iceberg-service/build.gradle.kts @@ -47,16 +47,33 @@ dependencies { implementation("com.fasterxml.jackson.core:jackson-databind") } +val policyManagementModels = + listOf( + "CatalogIdentifier", + "CreatePolicyRequest", + "EntityIdentifier", + "LoadPolicyResult", + "NamespaceIdentifier", + "Policy", + "PolicyContent", + "SetPolicyRequest", + "TableLikeIdentifier", + "UnsetPolicyRequest", + "UpdatePolicyRequest", + ) + .joinToString(",") + openApiGenerate { - inputSpec = "$rootDir/spec/rest-catalog-open-api.yaml" + inputSpec = "$rootDir/spec/polaris-catalog-open-api.yaml" generatorName = "jaxrs-resteasy" outputDir = "$projectDir/build/generated" apiPackage = "org.apache.polaris.service.catalog.api" + modelPackage = "org.apache.polaris.service.types" ignoreFileOverride = "$rootDir/.openapi-generator-ignore" removeOperationIdPrefix = true templateDir = "$rootDir/server-templates" globalProperties.put("apis", "") - globalProperties.put("models", "false") + globalProperties.put("models", policyManagementModels) globalProperties.put("apiDocs", "false") globalProperties.put("modelTests", "false") configOptions.put("resourceName", "catalog") @@ -64,6 +81,9 @@ openApiGenerate { configOptions.put("useBeanValidation", "false") configOptions.put("sourceFolder", "src/main/java") configOptions.put("useJakartaEe", "true") + configOptions.put("generateBuilders", "true") + configOptions.put("generateConstructorWithAllArgs", "true") + configOptions.put("openApiNullable", "false") openapiNormalizer.put("REFACTOR_ALLOF_WITH_PROPERTIES_ONLY", "true") additionalProperties.put("apiNamePrefix", "IcebergRest") additionalProperties.put("apiNameSuffix", "") diff --git a/spec/polaris-api/policy-management-api.yaml b/spec/polaris-api/policy-management-api.yaml new file mode 100644 index 000000000..539a3e7c4 --- /dev/null +++ b/spec/polaris-api/policy-management-api.yaml @@ -0,0 +1,528 @@ +# +# 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. +# + +--- +paths: + /v1/{prefix}/namespaces/{namespace}/policies: + parameters: + - $ref: '#/components/parameters/prefix' + - $ref: '#/components/parameters/namespace' + + post: + tags: + - Catalog API + summary: 'Create a policy in the given namespace' + operationId: createPolicy + description: + Create a policy in the given namespace + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreatePolicyRequest' + responses: + 200: + $ref: '#/components/responses/CreatePolicyResponse' + 400: + $ref: '#/components/responses/BadRequestErrorResponse' + 401: + $ref: '#/components/responses/UnauthorizedResponse' + 403: + $ref: '#/components/responses/ForbiddenResponse' + 419: + $ref: '#/components/responses/AuthenticationTimeoutResponse' + 503: + $ref: '#/components/responses/ServiceUnavailableResponse' + 5XX: + $ref: '#/components/responses/ServerErrorResponse' + + /v1/{prefix}/namespaces/{namespace}/policies/{policy}: + parameters: + - $ref: '#/components/parameters/prefix' + - $ref: '#/components/parameters/namespace' + - $ref: '#/components/parameters/policy' + + get: + tags: + - Catalog API + summary: 'Get a policy' + operationId: getPolicy + description: + Get a policy from the catalog + + + The response contains the policy's metadata and content + responses: + 200: + $ref: '#/components/responses/LoadPolicyResponse' + 400: + $ref: '#/components/responses/BadRequestErrorResponse' + 401: + $ref: '#/components/responses/UnauthorizedResponse' + 403: + $ref: '#/components/responses/ForbiddenResponse' + 404: + description: + Not Found - NoSuchPolicyException, policy to get does not exist + content: + application/json: + schema: + $ref: '#/components/schemas/IcebergErrorResponse' + examples: + PolicyToGetDoesNotExist: + $ref: '#/components/examples/NoSuchPolicyError' + 419: + $ref: '#/components/responses/AuthenticationTimeoutResponse' + 503: + $ref: '#/components/responses/ServiceUnavailableResponse' + 5XX: + $ref: '#/components/responses/ServerErrorResponse' + + put: + tags: + - Catalog API + summary: Update a policy + operationId: updatePolicy + description: + Update a policy + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdatePolicyRequest' + + responses: + 200: + $ref: '#/components/responses/UpdatePolicyResponse' + 400: + $ref: '#/components/responses/BadRequestErrorResponse' + 401: + $ref: '#/components/responses/UnauthorizedResponse' + 403: + $ref: '#/components/responses/ForbiddenResponse' + 404: + description: + Not Found - NoSuchPolicyException, policy to get does not exist + content: + application/json: + schema: + $ref: '#/components/schemas/IcebergErrorResponse' + examples: + PolicyToUpdateDoesNotExist: + $ref: '#/components/examples/NoSuchPolicyError' + 419: + $ref: '#/components/responses/AuthenticationTimeoutResponse' + 503: + $ref: '#/components/responses/ServiceUnavailableResponse' + 5XX: + $ref: '#/components/responses/ServerErrorResponse' + + delete: + tags: + - Catalog API + summary: Delete a policy from the catalog + operationId: deletePolicy + description: Remove a policy from the catalog + responses: + 204: + description: Success, no content + 400: + $ref: '#/components/responses/BadRequestErrorResponse' + 401: + $ref: '#/components/responses/UnauthorizedResponse' + 403: + $ref: '#/components/responses/ForbiddenResponse' + 404: + description: + Not Found - NoSuchPolicyException, policy to get does not exist + content: + application/json: + schema: + $ref: '#/components/schemas/IcebergErrorResponse' + examples: + PolicyToDeleteDoesNotExist: + $ref: '#/components/examples/NoSuchPolicyError' + 419: + $ref: '#/components/responses/AuthenticationTimeoutResponse' + 503: + $ref: '#/components/responses/ServiceUnavailableResponse' + 5XX: + $ref: '#/components/responses/ServerErrorResponse' + + /v1/{prefix}/namespaces/{namespace}/policies/{policy}/mappings: + parameters: + - $ref: '#/components/parameters/prefix' + - $ref: '#/components/parameters/namespace' + - $ref: '#/components/parameters/policy' + + post: + tags: + - Catalog API + summary: Create a mapping between a policy and a resource entity + operationId: setPolicy + description: + Create a mapping between a policy and a resource entity + + A resource entity can be a catalog, namespace, or any table-like entity + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SetPolicyRequest' + + responses: + 204: + description: Success, no content + 400: + $ref: '#/components/responses/BadRequestErrorResponse' + 401: + $ref: '#/components/responses/UnauthorizedResponse' + 403: + $ref: '#/components/responses/ForbiddenResponse' + 404: + description: + Not Found - NoSuchPolicyException, NoSuchEntityException + content: + application/json: + schema: + $ref: '#/components/schemas/IcebergErrorResponse' + examples: + PolicyToSetDoesNotExist: + $ref: '#/components/examples/NoSuchPolicyError' + EntityToSetDoesNotExist: + $ref: '#/components/examples/NoSuchEntityError' + 409: + description: Conflict - The mapping already exists + content: + application/json: + schema: + $ref: '#/components/schemas/IcebergErrorResponse' + examples: + MappingAlreadyExistsError: + $ref: '#/components/examples/MappingAlreadyExistsError' + 419: + $ref: '#/components/responses/AuthenticationTimeoutResponse' + 503: + $ref: '#/components/responses/ServiceUnavailableResponse' + 5XX: + $ref: '#/components/responses/ServerErrorResponse' + + delete: + tags: + - Catalog API + summary: Remove a mapping between a policy and a resource entity + operationId: unsetPolicy + description: + Remove a mapping between a policy and a resource entity + + A resource entity can be a catalog, namespace, or any table-like entity + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnsetPolicyRequest' + + responses: + 204: + description: Success, no content + 400: + $ref: '#/components/responses/BadRequestErrorResponse' + 401: + $ref: '#/components/responses/UnauthorizedResponse' + 403: + $ref: '#/components/responses/ForbiddenResponse' + 404: + description: + Not Found - NoSuchPolicyException, NoSuchEntityException, NoSuchMappingException + content: + application/json: + schema: + $ref: '#/components/schemas/IcebergErrorResponse' + examples: + PolicyToUnsetDoesNotExist: + $ref: '#/components/examples/NoSuchPolicyError' + EntityToUnsetDoesNotExist: + $ref: '#/components/examples/NoSuchEntityError' + MappingToUnsetDoesNotExist: + $ref: '#/components/examples/NoSuchMappingError' + 419: + $ref: '#/components/responses/AuthenticationTimeoutResponse' + 503: + $ref: '#/components/responses/ServiceUnavailableResponse' + 5XX: + $ref: '#/components/responses/ServerErrorResponse' + + +components: + parameters: + namespace: + $ref: '../rest-catalog-open-api.yaml#/components/parameters/namespace' + + prefix: + $ref: '../rest-catalog-open-api.yaml#/components/parameters/prefix' + + policy: + name: policy + in: path + description: a policy name + required: true + schema: + type: string + example: 'compaction' + + schemas: + Namespace: + $ref: '../rest-catalog-open-api.yaml#/components/schemas/Namespace' + + IcebergErrorResponse: + $ref: '../rest-catalog-open-api.yaml#/components/schemas/IcebergErrorResponse' + + Policy: + type: object + required: + - owner_id + - policy-id + - policy-type + - name + - content + - version + properties: + owner-id: + type: string + policy-id: + type: string + policy-type: + type: string + name: + type: string + description: + type: string + content: + $ref: '#/components/schemas/PolicyContent' + version: + type: integer + created-at-ms: + type: integer + format: int64 + updated-at-ms: + type: integer + format: int64 + + PolicyContent: {} + + CreatePolicyRequest: + type: object + required: + - name + - type + - content + properties: + name: + type: string + type: + type: string + description: + type: string + content: + $ref: '#/components/schemas/PolicyContent' + + LoadPolicyResult: + type: object + properties: + policy: + $ref: '#/components/schemas/Policy' + + UpdatePolicyRequest: + type: object + properties: + description: + type: string + content: + $ref: '#/components/schemas/PolicyContent' + + SetPolicyRequest: + type: object + required: + - entity + properties: + entity: + $ref: '#/components/schemas/EntityIdentifier' + parameters: + type: object + additionalProperties: + type: string + + UnsetPolicyRequest: + type: object + required: + - entity + properties: + entity: + $ref: '#/components/schemas/EntityIdentifier' + + CatalogIdentifier: + allOf: + - $ref: '#/components/schemas/EntityIdentifier' + - type: object + required: + - catalog + properties: + catalog: + type: string + + NamespaceIdentifier: + allOf: + - $ref: '#/components/schemas/EntityIdentifier' + - type: object + required: + - catalog + - namespace + properties: + catalog: + type: string + nullable: false + namespace: + $ref: '#/components/schemas/Namespace' + + TableLikeIdentifier: + allOf: + - $ref: '#/components/schemas/EntityIdentifier' + - type: object + required: + - catalog + - namespace + - name + properties: + catalog: + type: string + nullable: false + namespace: + $ref: '#/components/schemas/Namespace' + name: + type: string + nullable: false + + EntityIdentifier: + type: object + discriminator: + propertyName: type + mapping: + catalog: '#/components/schemas/CatalogIdentifier' + namespace: '#/components/schemas/NamespaceIdentifier' + table-like: '#/components/schemas/TableLikeIdentifier' + properties: + type: + type: string + enum: + - catalog + - namespace + - table-like + required: + - type + + responses: + BadRequestErrorResponse: + $ref: '../rest-catalog-open-api.yaml#/components/responses/BadRequestErrorResponse' + + UnauthorizedResponse: + $ref: '../rest-catalog-open-api.yaml#/components/responses/UnauthorizedResponse' + + ForbiddenResponse: + $ref: '../rest-catalog-open-api.yaml#/components/responses/ForbiddenResponse' + + AuthenticationTimeoutResponse: + $ref: '../rest-catalog-open-api.yaml#/components/responses/AuthenticationTimeoutResponse' + + ServiceUnavailableResponse: + $ref: '../rest-catalog-open-api.yaml#/components/responses/ServiceUnavailableResponse' + + ServerErrorResponse: + $ref: '../rest-catalog-open-api.yaml#/components/responses/ServerErrorResponse' + + IcebergErrorResponse: + $ref: '../rest-catalog-open-api.yaml#/components/responses/IcebergErrorResponse' + + CreatePolicyResponse: + description: Policy object result after creating a policy + content: + application/json: + schema: + $ref: '#/components/schemas/LoadPolicyResult' + + LoadPolicyResponse: + description: Policy object result when getting a policy + content: + application/json: + schema: + $ref: '#/components/schemas/LoadPolicyResult' + + UpdatePolicyResponse: + description: + Response used when a policy is successfully updated + + The updated policy JSON is returned in the policy field + content: + application/json: + schema: + $ref: '#/components/schemas/LoadPolicyResult' + + examples: + NoSuchPolicyError: + summary: The requested policy does not exist + value: { + "error": { + "message": "The given policy does not exist", + "type": "NoSuchPolicyException", + "code": 404 + } + } + + NoSuchEntityError: + summary: The requested entity does not exist + value: { + "error": { + "message": "The given entity does not exist", + "type": "NoSuchEntityException", + "code": 404 + } + } + + NoSuchMappingError: + summary: The requested mapping between policy and entity does not exist + value: { + "error": { + "message": "The given mapping between policy and entity does not exist", + "type": "NoSuchMappingException", + "code": 404 + } + } + + MappingAlreadyExistsError: + summary: The mapping between the given policy and entity already exists + value: { + "error": { + "message": "The mapping between the given policy and entity does not exist", + "type": "MappingAlreadyExistsError", + "code": 409 + } + } diff --git a/spec/polaris-catalog-open-api.yaml b/spec/polaris-catalog-open-api.yaml new file mode 100644 index 000000000..b062c7116 --- /dev/null +++ b/spec/polaris-catalog-open-api.yaml @@ -0,0 +1,129 @@ +# +# 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. +# + +--- +openapi: 3.0.3 +info: + title: Apache Iceberg REST Catalog API + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 0.0.1 + description: + Defines the specification for the first version of the REST Catalog API. + Implementations should ideally support both Iceberg table specs v1 and v2, with priority given to v2. +servers: + - url: "{scheme}://{host}/{basePath}" + description: Server URL when the port can be inferred from the scheme + variables: + scheme: + description: The scheme of the URI, either http or https. + default: https + host: + description: The host address for the specified server + default: localhost + basePath: + description: Optional prefix to be appended to all routes + default: "" + - url: "{scheme}://{host}:{port}/{basePath}" + description: Generic base server URL, with all parts configurable + variables: + scheme: + description: The scheme of the URI, either http or https. + default: https + host: + description: The host address for the specified server + default: localhost + port: + description: The port used when addressing the host + default: "443" + basePath: + description: Optional prefix to be appended to all routes + default: "" +# All routes are currently configured using an Authorization header. +security: + - OAuth2: [catalog] + - BearerAuth: [] + +paths: + /v1/{prefix}/namespaces: + $ref: './rest-catalog-open-api.yaml#/paths/~1v1~1{prefix}~1namespaces' + /v1/{prefix}/namespaces/{namespace}: + $ref: './rest-catalog-open-api.yaml#/paths/~1v1~1{prefix}~1namespaces~1{namespace}' + /v1/{prefix}/namespaces/{namespace}/properties: + $ref: './rest-catalog-open-api.yaml#/paths/~1v1~1{prefix}~1namespaces~1{namespace}~1properties' + /v1/{prefix}/namespaces/{namespace}/register: + $ref: './rest-catalog-open-api.yaml#/paths/~1v1~1{prefix}~1namespaces~1{namespace}~1register' + /v1/{prefix}/namespaces/{namespace}/tables: + $ref: './rest-catalog-open-api.yaml#/paths/~1v1~1{prefix}~1namespaces~1{namespace}~1tables' + /v1/{prefix}/namespaces/{namespace}/tables/{table}: + $ref: './rest-catalog-open-api.yaml#/paths/~1v1~1{prefix}~1namespaces~1{namespace}~1tables~1{table}' + /v1/{prefix}/namespaces/{namespace}/tables/{table}/credentials: + $ref: './rest-catalog-open-api.yaml#/paths/~1v1~1{prefix}~1namespaces~1{namespace}~1tables~1{table}~1credentials' + /v1/{prefix}/namespaces/{namespace}/tables/{table}/metrics: + $ref: './rest-catalog-open-api.yaml#/paths/~1v1~1{prefix}~1namespaces~1{namespace}~1tables~1{table}~1metrics' + /v1/{prefix}/namespaces/{namespace}/tables/{table}/notifications: + $ref: './rest-catalog-open-api.yaml#/paths/~1v1~1{prefix}~1namespaces~1{namespace}~1tables~1{table}~1notifications' + /v1/{prefix}/namespaces/{namespace}/views: + $ref: './rest-catalog-open-api.yaml#/paths/~1v1~1{prefix}~1namespaces~1{namespace}~1views' + /v1/{prefix}/namespaces/{namespace}/views/{view}: + $ref: './rest-catalog-open-api.yaml#/paths/~1v1~1{prefix}~1namespaces~1{namespace}~1views~1{view}' + /v1/{prefix}/tables/rename: + $ref: './rest-catalog-open-api.yaml#/paths/~1v1~1{prefix}~1tables~1rename' + /v1/{prefix}/transactions/commit: + $ref: './rest-catalog-open-api.yaml#/paths/~1v1~1{prefix}~1transactions~1commit' + /v1/{prefix}/views/rename: + $ref: './rest-catalog-open-api.yaml#/paths/~1v1~1{prefix}~1views~1rename' + /v1/config: + $ref: './rest-catalog-open-api.yaml#/paths/~1v1~1config' + /v1/oauth/tokens: + $ref: './rest-catalog-open-api.yaml#/paths/~1v1~1oauth~1tokens' + + # policy management apis + /v1/{prefix}/namespaces/{namespace}/policies: + $ref: './polaris-api/policy-management-api.yaml#/paths/~1v1~1{prefix}~1namespaces~1{namespace}~1policies' + /v1/{prefix}/namespaces/{namespace}/policies/{policy}: + $ref: './polaris-api/policy-management-api.yaml#/paths/~1v1~1{prefix}~1namespaces~1{namespace}~1policies~1{policy}' + /v1/{prefix}/namespaces/{namespace}/policies/{policy}/mappings: + $ref: './polaris-api/policy-management-api.yaml#/paths/~1v1~1{prefix}~1namespaces~1{namespace}~1policies~1{policy}~1mappings' + + +components: + securitySchemes: + OAuth2: + type: oauth2 + description: + This scheme is used for OAuth2 authorization. + + + For unauthorized requests, services should return an appropriate 401 or + 403 response. Implementations must not return altered success (200) + responses when a request is unauthenticated or unauthorized. + + If a separate authorization server is used, substitute the tokenUrl with + the full token path of the external authorization server, and use the + resulting token to access the resources defined in the spec. + flows: + clientCredentials: + tokenUrl: /v1/oauth/tokens + scopes: + catalog: Allows interacting with the Config and Catalog APIs + BearerAuth: + type: http + scheme: bearer