diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/CommandEntities.java b/clients/cli/src/main/java/org/apache/gravitino/cli/CommandEntities.java index 2dd50974ea9..47a03b7beb4 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/CommandEntities.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/CommandEntities.java @@ -47,6 +47,7 @@ public class CommandEntities { VALID_ENTITIES.add(SCHEMA); VALID_ENTITIES.add(TABLE); VALID_ENTITIES.add(COLUMN); + VALID_ENTITIES.add(MODEL); VALID_ENTITIES.add(USER); VALID_ENTITIES.add(GROUP); VALID_ENTITIES.add(TAG); diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java b/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java index e90c5259638..25532e33fb5 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java @@ -59,6 +59,7 @@ public class ErrorMessages { public static final String UNKNOWN_ROLE = "Unknown role."; public static final String ROLE_EXISTS = "Role already exists."; public static final String TABLE_EXISTS = "Table already exists."; + public static final String MODEL_EXISTS = "Model already exists."; public static final String INVALID_SET_COMMAND = "Unsupported combination of options either use --name, --user, --group or --property and --value."; public static final String INVALID_REMOVE_COMMAND = diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java b/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java index c21d21af483..a3b206dfdd1 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java @@ -99,7 +99,7 @@ public String getSchemaName() { /** * Retrieves the model name from the second part of the full name option. * - * @return The model name, or null if not found + * @return The model name, or null if not found. */ public String getModelName() { return getNamePart(2); diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java index 67c5c7c2563..a16454af3d6 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java @@ -140,6 +140,8 @@ private void executeCommand() { handleCatalogCommand(); } else if (entity.equals(CommandEntities.METALAKE)) { handleMetalakeCommand(); + } else if (entity.equals(CommandEntities.MODEL)) { + handleModelCommand(); } else if (entity.equals(CommandEntities.TOPIC)) { handleTopicCommand(); } else if (entity.equals(CommandEntities.FILESET)) { @@ -1152,6 +1154,9 @@ private void handleFilesetCommand() { } } + /** + * Handles the command execution for Models based on command type and the command line options. + */ private void handleModelCommand() { String url = getUrl(); String auth = getAuth(); @@ -1182,7 +1187,11 @@ private void handleModelCommand() { switch (command) { case CommandActions.DETAILS: - newModelDetails(url, ignore, metalake, catalog, schema, model).handle(); + if (line.hasOption(GravitinoOptions.AUDIT)) { + newModelAudit(url, ignore, metalake, catalog, schema, model).handle(); + } else { + newModelDetails(url, ignore, metalake, catalog, schema, model).handle(); + } break; case CommandActions.CREATE: diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java b/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java index ef775a4c9b1..8df9498d97b 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java @@ -81,6 +81,7 @@ import org.apache.gravitino.cli.commands.MetalakeDetails; import org.apache.gravitino.cli.commands.MetalakeDisable; import org.apache.gravitino.cli.commands.MetalakeEnable; +import org.apache.gravitino.cli.commands.ModelAudit; import org.apache.gravitino.cli.commands.ModelDetails; import org.apache.gravitino.cli.commands.OwnerDetails; import org.apache.gravitino.cli.commands.RegisterModel; @@ -917,6 +918,11 @@ protected ListModel newListModel( return new ListModel(url, ignore, metalake, catalog, schema); } + protected ModelAudit newModelAudit( + String url, boolean ignore, String metalake, String catalog, String schema, String model) { + return new ModelAudit(url, ignore, metalake, catalog, schema, model); + } + protected ModelDetails newModelDetails( String url, boolean ignore, String metalake, String catalog, String schema, String model) { return new ModelDetails(url, ignore, metalake, catalog, schema, model); diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ModelAudit.java b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ModelAudit.java new file mode 100644 index 00000000000..841afd2de9e --- /dev/null +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ModelAudit.java @@ -0,0 +1,90 @@ +/* + * 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.gravitino.cli.commands; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.cli.ErrorMessages; +import org.apache.gravitino.client.GravitinoClient; +import org.apache.gravitino.exceptions.NoSuchCatalogException; +import org.apache.gravitino.exceptions.NoSuchMetalakeException; +import org.apache.gravitino.exceptions.NoSuchModelException; +import org.apache.gravitino.model.Model; +import org.apache.gravitino.model.ModelCatalog; + +/** Displays the audit information of a model. */ +public class ModelAudit extends AuditCommand { + + protected final String metalake; + protected final String catalog; + protected final String schema; + protected final String model; + + /** + * Displays the audit information of a model. + * + * @param url The URL of the Gravitino server. + * @param ignoreVersions If true don't check the client/server versions match. + * @param metalake The name of the metalake. + * @param catalog The name of the catalog. + * @param schema The name of the schema. + * @param model The name of the model. + */ + public ModelAudit( + String url, + boolean ignoreVersions, + String metalake, + String catalog, + String schema, + String model) { + super(url, ignoreVersions); + this.metalake = metalake; + this.catalog = catalog; + this.schema = schema; + this.model = model; + } + + /** Displays the audit information of a model. */ + @Override + public void handle() { + NameIdentifier name = NameIdentifier.of(schema, model); + Model result; + + try (GravitinoClient client = buildClient(this.metalake)) { + ModelCatalog modelCatalog = client.loadCatalog(catalog).asModelCatalog(); + result = modelCatalog.getModel(name); + } catch (NoSuchMetalakeException err) { + System.err.println(ErrorMessages.UNKNOWN_METALAKE); + return; + } catch (NoSuchCatalogException err) { + System.err.println(ErrorMessages.UNKNOWN_CATALOG); + return; + } catch (NoSuchModelException err) { + System.err.println(ErrorMessages.UNKNOWN_MODEL); + return; + } catch (Exception exp) { + System.err.println(exp.getMessage()); + return; + } + + if (result != null) { + displayAuditInfo(result.auditInfo()); + } + } +} diff --git a/clients/cli/src/main/resources/model_help.txt b/clients/cli/src/main/resources/model_help.txt new file mode 100644 index 00000000000..04e9b8262ef --- /dev/null +++ b/clients/cli/src/main/resources/model_help.txt @@ -0,0 +1,8 @@ +gcli model [details] + +Please set the metalake in the Gravitino configuration file or the environment variable before running any of these commands. + +Example commands + +Show model audit information +gcli model details --name catalog_postgres.hr --audit \ No newline at end of file diff --git a/clients/cli/src/test/java/org/apache/gravitino/cli/TestModelCommand.java b/clients/cli/src/test/java/org/apache/gravitino/cli/TestModelCommands.java similarity index 90% rename from clients/cli/src/test/java/org/apache/gravitino/cli/TestModelCommand.java rename to clients/cli/src/test/java/org/apache/gravitino/cli/TestModelCommands.java index d222655b641..e486c41a9d1 100644 --- a/clients/cli/src/test/java/org/apache/gravitino/cli/TestModelCommand.java +++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestModelCommands.java @@ -38,13 +38,14 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; import org.apache.gravitino.cli.commands.ListModel; +import org.apache.gravitino.cli.commands.ModelAudit; import org.apache.gravitino.cli.commands.ModelDetails; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.testcontainers.shaded.com.google.common.base.Joiner; -public class TestModelCommand { +public class TestModelCommands { private final Joiner joiner = Joiner.on(", ").skipNulls(); private CommandLine mockCommandLine; private Options mockOptions; @@ -267,4 +268,25 @@ void testModelDetailsCommandWithoutModel() { + joiner.join(Collections.singletonList(CommandEntities.MODEL)), output); } + + @Test + void testModelAuditCommand() { + ModelAudit mockAudit = mock(ModelAudit.class); + when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); + when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.model"); + when(mockCommandLine.hasOption(GravitinoOptions.AUDIT)).thenReturn(true); + + GravitinoCommandLine commandLine = + spy( + new GravitinoCommandLine( + mockCommandLine, mockOptions, CommandEntities.MODEL, CommandActions.DETAILS)); + doReturn(mockAudit) + .when(commandLine) + .newModelAudit( + GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", "catalog", "schema", "model"); + commandLine.handleCommandLine(); + verify(mockAudit).handle(); + } } diff --git a/docs/cli.md b/docs/cli.md index 64d720f2e8a..0cc7dee4af9 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -23,11 +23,11 @@ alias gcli='java -jar ../../cli/build/libs/gravitino-cli-*-incubating-SNAPSHOT.j Or you use the `gcli.sh` script found in the `clients/cli/bin/` directory to run the CLI. ## Usage - +f The general structure for running commands with the Gravitino CLI is `gcli entity command [options]`. ```bash - usage: gcli [metalake|catalog|schema|table|column|user|group|tag|topic|fileset] [list|details|create|delete|update|set|remove|properties|revoke|grant] [options] + usage: gcli [metalake|catalog|schema|model|table|column|user|group|tag|topic|fileset] [list|details|create|delete|update|set|remove|properties|revoke|grant] [options] Options usage: gcli -a,--audit display audit information