This is an automated email from the ASF dual-hosted git repository.
lzljs3620320 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new cce52577af [core] Support data token in RESTCatalog (#4944)
cce52577af is described below
commit cce52577afcfc70d4b1294fe3d41316b1c7b3d9f
Author: jerry <[email protected]>
AuthorDate: Mon Jan 20 21:43:17 2025 +0800
[core] Support data token in RESTCatalog (#4944)
---
.../org/apache/paimon/catalog/AbstractCatalog.java | 3 +-
.../org/apache/paimon/catalog/CatalogUtils.java | 4 +-
.../java/org/apache/paimon/rest/RESTCatalog.java | 40 +++++-
.../org/apache/paimon/rest/RESTCatalogOptions.java | 6 +
.../paimon/rest/RefreshCredentialFileIO.java | 148 +++++++++++++++++++++
.../java/org/apache/paimon/rest/ResourcePaths.java | 4 +
.../responses/GetTableCredentialsResponse.java | 60 +++++++++
.../org/apache/paimon/rest/MockRESTMessage.java | 7 +
.../org/apache/paimon/rest/RESTCatalogServer.java | 16 +++
.../org/apache/paimon/rest/RESTCatalogTest.java | 23 +++-
.../apache/paimon/rest/RESTObjectMapperTest.java | 11 ++
paimon-open-api/rest-catalog-open-api.yaml | 47 +++++++
.../paimon/open/api/RESTCatalogController.java | 28 ++++
13 files changed, 386 insertions(+), 11 deletions(-)
diff --git
a/paimon-core/src/main/java/org/apache/paimon/catalog/AbstractCatalog.java
b/paimon-core/src/main/java/org/apache/paimon/catalog/AbstractCatalog.java
index aa93b1ba32..72d09b785f 100644
--- a/paimon-core/src/main/java/org/apache/paimon/catalog/AbstractCatalog.java
+++ b/paimon-core/src/main/java/org/apache/paimon/catalog/AbstractCatalog.java
@@ -369,7 +369,8 @@ public abstract class AbstractCatalog implements Catalog {
SnapshotCommit.Factory commitFactory =
new RenamingSnapshotCommit.Factory(
lockFactory().orElse(null),
lockContext().orElse(null));
- return CatalogUtils.loadTable(this, identifier,
this::loadTableMetadata, commitFactory);
+ return CatalogUtils.loadTable(
+ this, identifier, fileIO(), this::loadTableMetadata,
commitFactory);
}
/**
diff --git
a/paimon-core/src/main/java/org/apache/paimon/catalog/CatalogUtils.java
b/paimon-core/src/main/java/org/apache/paimon/catalog/CatalogUtils.java
index fbd510692c..7282b308e7 100644
--- a/paimon-core/src/main/java/org/apache/paimon/catalog/CatalogUtils.java
+++ b/paimon-core/src/main/java/org/apache/paimon/catalog/CatalogUtils.java
@@ -171,6 +171,7 @@ public class CatalogUtils {
public static Table loadTable(
Catalog catalog,
Identifier identifier,
+ FileIO fileIO,
TableMetadata.Loader metadataLoader,
SnapshotCommit.Factory commitFactory)
throws Catalog.TableNotExistException {
@@ -189,8 +190,7 @@ public class CatalogUtils {
new CatalogEnvironment(
identifier, metadata.uuid(), catalog.catalogLoader(),
commitFactory);
Path path = new Path(schema.options().get(PATH.key()));
- FileStoreTable table =
- FileStoreTableFactory.create(catalog.fileIO(), path, schema,
catalogEnv);
+ FileStoreTable table = FileStoreTableFactory.create(fileIO, path,
schema, catalogEnv);
if (options.type() == TableType.OBJECT_TABLE) {
table = toObjectTable(catalog, table);
diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java
b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java
index 87b8e8cb96..e06ef012b8 100644
--- a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java
+++ b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java
@@ -109,6 +109,7 @@ public class RESTCatalog implements Catalog {
private final ResourcePaths resourcePaths;
private final AuthSession catalogAuth;
private final Options options;
+ private final boolean fileIORefreshCredentialEnable;
private final FileIO fileIO;
private volatile ScheduledExecutorService refreshExecutor = null;
@@ -130,13 +131,19 @@ public class RESTCatalog implements Catalog {
.merge(context.options().toMap()));
this.resourcePaths = ResourcePaths.forCatalogProperties(options);
+ this.fileIORefreshCredentialEnable =
+
options.get(RESTCatalogOptions.FILE_IO_REFRESH_CREDENTIAL_ENABLE);
try {
- String warehouseStr = options.get(CatalogOptions.WAREHOUSE);
- this.fileIO =
- FileIO.get(
- new Path(warehouseStr),
- CatalogContext.create(
- options, context.preferIO(),
context.fallbackIO()));
+ if (fileIORefreshCredentialEnable) {
+ this.fileIO = null;
+ } else {
+ String warehouseStr = options.get(CatalogOptions.WAREHOUSE);
+ this.fileIO =
+ FileIO.get(
+ new Path(warehouseStr),
+ CatalogContext.create(
+ options, context.preferIO(),
context.fallbackIO()));
+ }
} catch (IOException e) {
LOG.warn("Can not get FileIO from options.");
throw new RuntimeException(e);
@@ -149,6 +156,8 @@ public class RESTCatalog implements Catalog {
this.options = options;
this.resourcePaths = ResourcePaths.forCatalogProperties(options);
this.fileIO = fileIO;
+ this.fileIORefreshCredentialEnable =
+
options.get(RESTCatalogOptions.FILE_IO_REFRESH_CREDENTIAL_ENABLE);
}
@Override
@@ -168,12 +177,20 @@ public class RESTCatalog implements Catalog {
@Override
public FileIO fileIO() {
+ if (fileIORefreshCredentialEnable) {
+ throw new UnsupportedOperationException();
+ }
return fileIO;
}
@Override
public FileIO fileIO(Path path) {
- return fileIO;
+ try {
+ return FileIO.get(path, CatalogContext.create(options));
+ } catch (IOException e) {
+ LOG.warn("Can not get FileIO from options.");
+ throw new RuntimeException(e);
+ }
}
@Override
@@ -289,6 +306,7 @@ public class RESTCatalog implements Catalog {
return CatalogUtils.loadTable(
this,
identifier,
+ this.fileIO(identifier),
this::loadTableMetadata,
new RESTSnapshotCommitFactory(catalogLoader()));
}
@@ -645,4 +663,12 @@ public class RESTCatalog implements Catalog {
return refreshExecutor;
}
+
+ private FileIO fileIO(Identifier identifier) {
+ if (fileIORefreshCredentialEnable) {
+ return new RefreshCredentialFileIO(
+ resourcePaths, catalogAuth, options, client, identifier);
+ }
+ return fileIO;
+ }
}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java
b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java
index 843228fa07..61aed5f703 100644
--- a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java
+++ b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogOptions.java
@@ -77,4 +77,10 @@ public class RESTCatalogOptions {
.stringType()
.noDefaultValue()
.withDescription("REST Catalog auth token provider path.");
+
+ public static final ConfigOption<Boolean>
FILE_IO_REFRESH_CREDENTIAL_ENABLE =
+ ConfigOptions.key("file-io-refresh-credential.enabled")
+ .booleanType()
+ .defaultValue(false)
+ .withDescription("Whether to support file io refresh
credential.");
}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/rest/RefreshCredentialFileIO.java
b/paimon-core/src/main/java/org/apache/paimon/rest/RefreshCredentialFileIO.java
new file mode 100644
index 0000000000..b55486887d
--- /dev/null
+++
b/paimon-core/src/main/java/org/apache/paimon/rest/RefreshCredentialFileIO.java
@@ -0,0 +1,148 @@
+/*
+ * 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.paimon.rest;
+
+import org.apache.paimon.catalog.CatalogContext;
+import org.apache.paimon.catalog.Identifier;
+import org.apache.paimon.fs.FileIO;
+import org.apache.paimon.fs.FileStatus;
+import org.apache.paimon.fs.Path;
+import org.apache.paimon.fs.PositionOutputStream;
+import org.apache.paimon.fs.SeekableInputStream;
+import org.apache.paimon.options.CatalogOptions;
+import org.apache.paimon.options.Options;
+import org.apache.paimon.rest.auth.AuthSession;
+import org.apache.paimon.rest.responses.GetTableCredentialsResponse;
+
+import java.io.IOException;
+import java.util.Map;
+
+/** A {@link FileIO} to support refresh credential. */
+public class RefreshCredentialFileIO implements FileIO {
+
+ private static final long serialVersionUID = 1L;
+
+ private final ResourcePaths resourcePaths;
+ private final AuthSession catalogAuth;
+ protected Options options;
+ private final Identifier identifier;
+ private Long expireAtMillis;
+ private Map<String, String> credential;
+ private final transient RESTClient client;
+ private transient volatile FileIO lazyFileIO;
+
+ public RefreshCredentialFileIO(
+ ResourcePaths resourcePaths,
+ AuthSession catalogAuth,
+ Options options,
+ RESTClient client,
+ Identifier identifier) {
+ this.resourcePaths = resourcePaths;
+ this.catalogAuth = catalogAuth;
+ this.options = options;
+ this.identifier = identifier;
+ this.client = client;
+ }
+
+ @Override
+ public void configure(CatalogContext context) {
+ this.options = context.options();
+ }
+
+ @Override
+ public SeekableInputStream newInputStream(Path path) throws IOException {
+ return fileIO().newInputStream(path);
+ }
+
+ @Override
+ public PositionOutputStream newOutputStream(Path path, boolean overwrite)
throws IOException {
+ return fileIO().newOutputStream(path, overwrite);
+ }
+
+ @Override
+ public FileStatus getFileStatus(Path path) throws IOException {
+ return fileIO().getFileStatus(path);
+ }
+
+ @Override
+ public FileStatus[] listStatus(Path path) throws IOException {
+ return fileIO().listStatus(path);
+ }
+
+ @Override
+ public boolean exists(Path path) throws IOException {
+ return fileIO().exists(path);
+ }
+
+ @Override
+ public boolean delete(Path path, boolean recursive) throws IOException {
+ return fileIO().delete(path, recursive);
+ }
+
+ @Override
+ public boolean mkdirs(Path path) throws IOException {
+ return fileIO().mkdirs(path);
+ }
+
+ @Override
+ public boolean rename(Path src, Path dst) throws IOException {
+ return fileIO().rename(src, dst);
+ }
+
+ @Override
+ public boolean isObjectStore() {
+ try {
+ return fileIO().isObjectStore();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private FileIO fileIO() throws IOException {
+ if (lazyFileIO == null || shouldRefresh()) {
+ synchronized (this) {
+ if (lazyFileIO == null || shouldRefresh()) {
+ GetTableCredentialsResponse response = getCredential();
+ expireAtMillis = response.getExpiresAtMillis();
+ credential = response.getCredential();
+ Map<String, String> conf = RESTUtil.merge(options.toMap(),
credential);
+ Options updateCredentialOption = new Options(conf);
+ lazyFileIO =
+ FileIO.get(
+ new
Path(updateCredentialOption.get(CatalogOptions.WAREHOUSE)),
+
CatalogContext.create(updateCredentialOption));
+ }
+ }
+ }
+ return lazyFileIO;
+ }
+
+ // todo: handle exception
+ private GetTableCredentialsResponse getCredential() {
+ return client.get(
+ resourcePaths.tableCredentials(
+ identifier.getDatabaseName(),
identifier.getObjectName()),
+ GetTableCredentialsResponse.class,
+ catalogAuth.getHeaders());
+ }
+
+ private boolean shouldRefresh() {
+ return expireAtMillis != null && expireAtMillis >
System.currentTimeMillis();
+ }
+}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/rest/ResourcePaths.java
b/paimon-core/src/main/java/org/apache/paimon/rest/ResourcePaths.java
index d77475fe40..1e843f99cb 100644
--- a/paimon-core/src/main/java/org/apache/paimon/rest/ResourcePaths.java
+++ b/paimon-core/src/main/java/org/apache/paimon/rest/ResourcePaths.java
@@ -70,6 +70,10 @@ public class ResourcePaths {
return SLASH.join(V1, prefix, DATABASES, databaseName, TABLES,
"commit");
}
+ public String tableCredentials(String databaseName, String tableName) {
+ return SLASH.join(V1, prefix, DATABASES, databaseName, TABLES,
tableName, "credentials");
+ }
+
public String partitions(String databaseName, String tableName) {
return SLASH.join(V1, prefix, DATABASES, databaseName, TABLES,
tableName, "partitions");
}
diff --git
a/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetTableCredentialsResponse.java
b/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetTableCredentialsResponse.java
new file mode 100644
index 0000000000..2792940ff6
--- /dev/null
+++
b/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetTableCredentialsResponse.java
@@ -0,0 +1,60 @@
+/*
+ * 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.paimon.rest.responses;
+
+import org.apache.paimon.rest.RESTResponse;
+
+import
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonCreator;
+import
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonGetter;
+import
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Map;
+
+/** Response for table credentials. */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class GetTableCredentialsResponse implements RESTResponse {
+
+ private static final String FIELD_CREDENTIAL = "credential";
+ private static final String FIELD_EXPIREAT_MILLIS = "expiresAtMillis";
+
+ @JsonProperty(FIELD_CREDENTIAL)
+ private final Map<String, String> credential;
+
+ @JsonProperty(FIELD_EXPIREAT_MILLIS)
+ private long expiresAtMillis;
+
+ @JsonCreator
+ public GetTableCredentialsResponse(
+ @JsonProperty(FIELD_EXPIREAT_MILLIS) long expiresAtMillis,
+ @JsonProperty(FIELD_CREDENTIAL) Map<String, String> credential) {
+ this.expiresAtMillis = expiresAtMillis;
+ this.credential = credential;
+ }
+
+ @JsonGetter(FIELD_CREDENTIAL)
+ public Map<String, String> getCredential() {
+ return credential;
+ }
+
+ @JsonGetter(FIELD_EXPIREAT_MILLIS)
+ public long getExpiresAtMillis() {
+ return expiresAtMillis;
+ }
+}
diff --git
a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java
b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java
index d64dd86da7..576f494b88 100644
--- a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java
+++ b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java
@@ -32,6 +32,7 @@ import org.apache.paimon.rest.requests.RenameTableRequest;
import org.apache.paimon.rest.responses.AlterDatabaseResponse;
import org.apache.paimon.rest.responses.CreateDatabaseResponse;
import org.apache.paimon.rest.responses.GetDatabaseResponse;
+import org.apache.paimon.rest.responses.GetTableCredentialsResponse;
import org.apache.paimon.rest.responses.GetTableResponse;
import org.apache.paimon.rest.responses.GetViewResponse;
import org.apache.paimon.rest.responses.ListDatabasesResponse;
@@ -48,6 +49,7 @@ import org.apache.paimon.types.RowType;
import org.apache.paimon.view.ViewSchema;
import org.apache.paimon.shade.guava30.com.google.common.collect.ImmutableList;
+import org.apache.paimon.shade.guava30.com.google.common.collect.ImmutableMap;
import org.apache.paimon.shade.guava30.com.google.common.collect.Lists;
import java.util.ArrayList;
@@ -248,6 +250,11 @@ public class MockRESTMessage {
return new ListViewsResponse(ImmutableList.of("view"));
}
+ public static GetTableCredentialsResponse getTableCredentialsResponse() {
+ return new GetTableCredentialsResponse(
+ System.currentTimeMillis(), ImmutableMap.of("key", "value"));
+ }
+
private static ViewSchema viewSchema() {
List<DataField> fields =
Arrays.asList(
diff --git
a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java
b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java
index 7faa853be9..22fde48e99 100644
--- a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java
+++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java
@@ -42,6 +42,7 @@ import
org.apache.paimon.rest.responses.CreateDatabaseResponse;
import org.apache.paimon.rest.responses.ErrorResponse;
import org.apache.paimon.rest.responses.ErrorResponseResourceType;
import org.apache.paimon.rest.responses.GetDatabaseResponse;
+import org.apache.paimon.rest.responses.GetTableCredentialsResponse;
import org.apache.paimon.rest.responses.GetTableResponse;
import org.apache.paimon.rest.responses.GetViewResponse;
import org.apache.paimon.rest.responses.ListDatabasesResponse;
@@ -63,6 +64,7 @@ import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.util.List;
@@ -149,6 +151,10 @@ public class RESTCatalogServer {
resources.length == 3
&& "tables".equals(resources[1])
&& "commit".equals(resources[2]);
+ boolean isTableCredentials =
+ resources.length == 4
+ && "tables".equals(resources[1])
+ && "credentials".equals(resources[3]);
boolean isPartitions =
resources.length == 4
&& "tables".equals(resources[1])
@@ -202,6 +208,16 @@ public class RESTCatalogServer {
} else if (isPartitions) {
String tableName = resources[2];
return partitionsApiHandler(catalog, request,
databaseName, tableName);
+ } else if (isTableCredentials) {
+ GetTableCredentialsResponse
getTableCredentialsResponse =
+ new GetTableCredentialsResponse(
+ System.currentTimeMillis(),
+ ImmutableMap.of("key", "value"));
+ return new MockResponse()
+ .setResponseCode(200)
+ .setBody(
+ OBJECT_MAPPER.writeValueAsString(
+
getTableCredentialsResponse));
} else if (isTableRename) {
return renameTableApiHandler(catalog, request);
} else if (isTableCommit) {
diff --git
a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java
b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java
index d1ce64b6c5..752f492078 100644
--- a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java
+++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java
@@ -26,6 +26,7 @@ import org.apache.paimon.options.Options;
import org.apache.paimon.partition.Partition;
import org.apache.paimon.rest.exceptions.NotAuthorizedException;
import org.apache.paimon.schema.Schema;
+import org.apache.paimon.table.FileStoreTable;
import org.apache.paimon.types.DataField;
import org.apache.paimon.types.DataTypes;
@@ -49,12 +50,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
class RESTCatalogTest extends CatalogTestBase {
private RESTCatalogServer restCatalogServer;
+ private String initToken = "init_token";
@BeforeEach
@Override
public void setUp() throws Exception {
super.setUp();
- String initToken = "init_token";
restCatalogServer = new RESTCatalogServer(warehouse, initToken);
restCatalogServer.start();
Options options = new Options();
@@ -107,6 +108,26 @@ class RESTCatalogTest extends CatalogTestBase {
assertEquals(0, result.size());
}
+ @Test
+ void testRefreshFileIO() throws Exception {
+ Options options = new Options();
+ options.set(RESTCatalogOptions.URI, restCatalogServer.getUrl());
+ options.set(RESTCatalogOptions.TOKEN, initToken);
+ options.set(RESTCatalogOptions.THREAD_POOL_SIZE, 1);
+ options.set(RESTCatalogOptions.FILE_IO_REFRESH_CREDENTIAL_ENABLE,
true);
+ this.catalog = new RESTCatalog(CatalogContext.create(options));
+ List<Identifier> identifiers =
+ Lists.newArrayList(
+ Identifier.create("test_db_a", "test_table_a"),
+ Identifier.create("test_db_b", "test_table_b"),
+ Identifier.create("test_db_c", "test_table_c"));
+ for (Identifier identifier : identifiers) {
+ createTable(identifier, Maps.newHashMap(),
Lists.newArrayList("col1"));
+ FileStoreTable fileStoreTable = (FileStoreTable)
catalog.getTable(identifier);
+ assertEquals(true,
fileStoreTable.fileIO().exists(fileStoreTable.location()));
+ }
+ }
+
@Override
protected boolean supportsFormatTable() {
return true;
diff --git
a/paimon-core/src/test/java/org/apache/paimon/rest/RESTObjectMapperTest.java
b/paimon-core/src/test/java/org/apache/paimon/rest/RESTObjectMapperTest.java
index fa56f81118..4c3b622a8c 100644
--- a/paimon-core/src/test/java/org/apache/paimon/rest/RESTObjectMapperTest.java
+++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTObjectMapperTest.java
@@ -32,6 +32,7 @@ import org.apache.paimon.rest.responses.ConfigResponse;
import org.apache.paimon.rest.responses.CreateDatabaseResponse;
import org.apache.paimon.rest.responses.ErrorResponse;
import org.apache.paimon.rest.responses.GetDatabaseResponse;
+import org.apache.paimon.rest.responses.GetTableCredentialsResponse;
import org.apache.paimon.rest.responses.GetTableResponse;
import org.apache.paimon.rest.responses.GetViewResponse;
import org.apache.paimon.rest.responses.ListDatabasesResponse;
@@ -273,4 +274,14 @@ public class RESTObjectMapperTest {
ListViewsResponse parseData = OBJECT_MAPPER.readValue(responseStr,
ListViewsResponse.class);
assertEquals(response.getViews(), parseData.getViews());
}
+
+ @Test
+ public void getTableCredentialsResponseParseTest() throws Exception {
+ GetTableCredentialsResponse response =
MockRESTMessage.getTableCredentialsResponse();
+ String responseStr = OBJECT_MAPPER.writeValueAsString(response);
+ GetTableCredentialsResponse parseData =
+ OBJECT_MAPPER.readValue(responseStr,
GetTableCredentialsResponse.class);
+ assertEquals(response.getCredential(), parseData.getCredential());
+ assertEquals(response.getExpiresAtMillis(),
parseData.getExpiresAtMillis());
+ }
}
diff --git a/paimon-open-api/rest-catalog-open-api.yaml
b/paimon-open-api/rest-catalog-open-api.yaml
index 02ea7de8d0..128514d7a5 100644
--- a/paimon-open-api/rest-catalog-open-api.yaml
+++ b/paimon-open-api/rest-catalog-open-api.yaml
@@ -432,6 +432,43 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
"500":
description: Internal Server Error
+ /v1/{prefix}/databases/{database}/tables/{table}/credentials:
+ get:
+ tags:
+ - table
+ summary: List credentials
+ operationId: listCredentials
+ parameters:
+ - name: prefix
+ in: path
+ required: true
+ schema:
+ type: string
+ - name: database
+ in: path
+ required: true
+ schema:
+ type: string
+ - name: table
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GetTableCredentialsResponse'
+ "404":
+ description: Resource not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ "500":
+ description: Internal Server Error
/v1/{prefix}/databases/{database}/tables/{table}/partitions:
get:
tags:
@@ -1166,6 +1203,16 @@ components:
properties:
success:
type: boolean
+ GetTableCredentialsResponse:
+ type: object
+ properties:
+ expiresAt:
+ type: integer
+ format: int64
+ credentials:
+ type: object
+ additionalProperties:
+ type: string
AlterDatabaseRequest:
type: object
properties:
diff --git
a/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java
b/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java
index e657407a47..c8eae97dec 100644
---
a/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java
+++
b/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java
@@ -37,6 +37,7 @@ import org.apache.paimon.rest.responses.ConfigResponse;
import org.apache.paimon.rest.responses.CreateDatabaseResponse;
import org.apache.paimon.rest.responses.ErrorResponse;
import org.apache.paimon.rest.responses.GetDatabaseResponse;
+import org.apache.paimon.rest.responses.GetTableCredentialsResponse;
import org.apache.paimon.rest.responses.GetTableResponse;
import org.apache.paimon.rest.responses.GetViewResponse;
import org.apache.paimon.rest.responses.ListDatabasesResponse;
@@ -49,6 +50,7 @@ import org.apache.paimon.types.RowType;
import org.apache.paimon.view.ViewSchema;
import org.apache.paimon.shade.guava30.com.google.common.collect.ImmutableList;
+import org.apache.paimon.shade.guava30.com.google.common.collect.ImmutableMap;
import org.apache.paimon.shade.guava30.com.google.common.collect.Lists;
import io.swagger.v3.oas.annotations.Operation;
@@ -355,6 +357,32 @@ public class RESTCatalogController {
return new CommitTableResponse(true);
}
+ @Operation(
+ summary = "List credentials",
+ tags = {"table"})
+ @ApiResponses({
+ @ApiResponse(
+ responseCode = "200",
+ content = {
+ @Content(schema = @Schema(implementation =
GetTableCredentialsResponse.class))
+ }),
+ @ApiResponse(
+ responseCode = "404",
+ description = "Resource not found",
+ content = {@Content(schema = @Schema(implementation =
ErrorResponse.class))}),
+ @ApiResponse(
+ responseCode = "500",
+ content = {@Content(schema = @Schema())})
+ })
+ @GetMapping("/v1/{prefix}/databases/{database}/tables/{table}/credentials")
+ public GetTableCredentialsResponse listCredentials(
+ @PathVariable String prefix,
+ @PathVariable String database,
+ @PathVariable String table) {
+ return new GetTableCredentialsResponse(
+ System.currentTimeMillis(), ImmutableMap.of("key", "value"));
+ }
+
@Operation(
summary = "List partitions",
tags = {"partition"})