This is an automated email from the ASF dual-hosted git repository.

jshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new d8b4407e3 [#5281] Add owner support to Gravitino CLI (#5293)
d8b4407e3 is described below

commit d8b4407e31d92608f02ac7023c96e2996ad4d0db
Author: Justin Mclean <[email protected]>
AuthorDate: Wed Nov 27 17:35:27 2024 +1100

    [#5281] Add owner support to Gravitino CLI (#5293)
    
    ### What changes were proposed in this pull request?
    
    Add owner support to Gravitino CLI
    
    ### Why are the changes needed?
    
    To continue adding to the CLI.
    
    Fix: #5281
    
    ### Does this PR introduce _any_ user-facing change?
    
    No, but it expands on the CLI.
    
    ### How was this patch tested?
    
    Compiled and tested locally.
---
 .../org/apache/gravitino/cli/ErrorMessages.java    |   6 +-
 .../apache/gravitino/cli/GravitinoCommandLine.java |  37 ++++++-
 .../org/apache/gravitino/cli/GravitinoOptions.java |   4 +-
 .../main/java/org/apache/gravitino/cli/Main.java   |   2 +-
 .../apache/gravitino/cli/TestableCommandLine.java  |  18 +++
 .../gravitino/cli/commands/OwnerDetails.java       |  94 ++++++++++++++++
 .../apache/gravitino/cli/commands/SetOwner.java    | 101 +++++++++++++++++
 .../apache/gravitino/cli/TestOwnerCommands.java    | 122 +++++++++++++++++++++
 docs/cli.md                                        |  25 ++++-
 9 files changed, 402 insertions(+), 7 deletions(-)

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 6836bd203..3c7870968 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
@@ -44,8 +44,12 @@ public class ErrorMessages {
   public static final String TAG_EMPTY = "Error: Must configure --tag option.";
   public static final String UNKNOWN_ROLE = "Unknown role.";
   public static final String ROLE_EXISTS = "Role already exists.";
+
   public static final String INVALID_SET_COMMAND =
-      "Unsupported combination of options either use --name or --property and 
--value.";
+      "Unsupported combination of options either use --name, --user, --group 
or --property and --value.";
   public static final String INVALID_REMOVE_COMMAND =
       "Unsupported combination of options either use --name or --property.";
+  public static final String INVALID_OWNER_COMMAND =
+      "Unsupported combination of options either use --user or --group.";
+  public static final String UNSUPPORTED_ACTION = "Entity doesn't support this 
action.";
 }
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 1cdbfaf25..a4b41b12e 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
@@ -112,7 +112,9 @@ public class GravitinoCommandLine extends 
TestableCommandLine {
 
   /** Executes the appropriate command based on the command type. */
   private void executeCommand() {
-    if (entity.equals(CommandEntities.COLUMN)) {
+    if (line.hasOption(GravitinoOptions.OWNER)) {
+      handleOwnerCommand();
+    } else if (entity.equals(CommandEntities.COLUMN)) {
       handleColumnCommand();
     } else if (entity.equals(CommandEntities.TABLE)) {
       handleTableCommand();
@@ -331,6 +333,8 @@ public class GravitinoCommandLine extends 
TestableCommandLine {
       if (role != null) {
         newAddRoleToUser(url, ignore, metalake, user, role).handle();
       }
+    } else {
+      System.err.println(ErrorMessages.UNSUPPORTED_ACTION);
     }
   }
 
@@ -360,6 +364,8 @@ public class GravitinoCommandLine extends 
TestableCommandLine {
       if (role != null) {
         newAddRoleToGroup(url, ignore, metalake, group, role).handle();
       }
+    } else {
+      System.err.println(ErrorMessages.UNSUPPORTED_ACTION);
     }
   }
 
@@ -392,7 +398,7 @@ public class GravitinoCommandLine extends 
TestableCommandLine {
       String value = line.getOptionValue(GravitinoOptions.VALUE);
       if (property != null && value != null) {
         newSetTagProperty(url, ignore, metalake, getOneTag(tags), property, 
value).handle();
-      } else if (name != null && property == null && value == null) {
+      } else if (property == null && value == null) {
         newTagEntity(url, ignore, metalake, name, tags).handle();
       }
     } else if (CommandActions.REMOVE.equals(command)) {
@@ -456,6 +462,33 @@ public class GravitinoCommandLine extends 
TestableCommandLine {
     }
   }
 
+  /**
+   * Handles the command execution for Objects based on command type and the 
command line options.
+   */
+  private void handleOwnerCommand() {
+    String url = getUrl();
+    FullName name = new FullName(line);
+    String metalake = name.getMetalakeName();
+    String entityName = line.getOptionValue(GravitinoOptions.NAME);
+
+    if (CommandActions.DETAILS.equals(command)) {
+      newOwnerDetails(url, ignore, metalake, entityName, entity).handle();
+    } else if (CommandActions.SET.equals(command)) {
+      String owner = line.getOptionValue(GravitinoOptions.USER);
+      String group = line.getOptionValue(GravitinoOptions.GROUP);
+
+      if (owner != null && group == null) {
+        newSetOwner(url, ignore, metalake, entityName, entity, owner, 
false).handle();
+      } else if (owner == null && group != null) {
+        newSetOwner(url, ignore, metalake, entityName, entity, group, 
true).handle();
+      } else {
+        System.err.println(ErrorMessages.INVALID_SET_COMMAND);
+      }
+    } else {
+      System.err.println(ErrorMessages.UNSUPPORTED_ACTION);
+    }
+  }
+
   /**
    * Retrieves the Gravitinno URL from the command line options or the 
GRAVITINO_URL environment
    * variable or the Gravitio config file.
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java
index 8f017ba72..dd9fedd7a 100644
--- a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java
@@ -40,6 +40,7 @@ public class GravitinoOptions {
   public static final String USER = "user";
   public static final String GROUP = "group";
   public static final String TAG = "tag";
+  public static final String OWNER = "owner";
   public static final String ROLE = "role";
   public static final String AUDIT = "audit";
   public static final String INDEX = "index";
@@ -78,7 +79,8 @@ public class GravitinoOptions {
             "z", PROVIDER, "provider one of hadoop, hive, mysql, postgres, 
iceberg, kafka"));
     options.addOption(createArgOption("l", USER, "user name"));
     options.addOption(createArgOption("g", GROUP, "group name"));
-    options.addOption(createArgsOption("t", TAG, "tag name"));
+    options.addOption(createArgOption("t", TAG, "tag name"));
+    options.addOption(createSimpleOption("o", OWNER, "entity owner"));
     options.addOption(createArgOption("r", ROLE, "role name"));
 
     // Properties option can have multiple values
diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/Main.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/Main.java
index 0d0eaddf7..e81362b20 100644
--- a/clients/cli/src/main/java/org/apache/gravitino/cli/Main.java
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/Main.java
@@ -25,7 +25,7 @@ import org.apache.commons.cli.DefaultParser;
 import org.apache.commons.cli.Options;
 import org.apache.commons.cli.ParseException;
 
-/* Entry point for teh Gravitino command line. */
+/* Entry point for the Gravitino command line. */
 public class Main {
 
   public static void main(String[] args) {
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 9a4882b3a..94435160d 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
@@ -59,6 +59,7 @@ import org.apache.gravitino.cli.commands.ListTagProperties;
 import org.apache.gravitino.cli.commands.ListUsers;
 import org.apache.gravitino.cli.commands.MetalakeAudit;
 import org.apache.gravitino.cli.commands.MetalakeDetails;
+import org.apache.gravitino.cli.commands.OwnerDetails;
 import org.apache.gravitino.cli.commands.RemoveCatalogProperty;
 import org.apache.gravitino.cli.commands.RemoveMetalakeProperty;
 import org.apache.gravitino.cli.commands.RemoveRoleFromGroup;
@@ -71,6 +72,7 @@ import org.apache.gravitino.cli.commands.SchemaDetails;
 import org.apache.gravitino.cli.commands.ServerVersion;
 import org.apache.gravitino.cli.commands.SetCatalogProperty;
 import org.apache.gravitino.cli.commands.SetMetalakeProperty;
+import org.apache.gravitino.cli.commands.SetOwner;
 import org.apache.gravitino.cli.commands.SetSchemaProperty;
 import org.apache.gravitino.cli.commands.SetTagProperty;
 import org.apache.gravitino.cli.commands.TableAudit;
@@ -424,4 +426,20 @@ public class TestableCommandLine {
       String url, boolean ignore, String metalake, String catalog, String 
schema, String table) {
     return new ListColumns(url, ignore, metalake, catalog, schema, table);
   }
+
+  protected SetOwner newSetOwner(
+      String url,
+      boolean ignore,
+      String metalake,
+      String entity,
+      String entityType,
+      String owner,
+      boolean isGroup) {
+    return new SetOwner(url, ignore, metalake, entity, entityType, owner, 
isGroup);
+  }
+
+  protected OwnerDetails newOwnerDetails(
+      String url, boolean ignore, String metalake, String entity, String 
entityType) {
+    return new OwnerDetails(url, ignore, metalake, entity, entityType);
+  }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/OwnerDetails.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/OwnerDetails.java
new file mode 100644
index 000000000..52cb45572
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/OwnerDetails.java
@@ -0,0 +1,94 @@
+/*
+ * 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 java.util.Optional;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.MetadataObjects;
+import org.apache.gravitino.authorization.Owner;
+import org.apache.gravitino.cli.CommandEntities;
+import org.apache.gravitino.cli.ErrorMessages;
+import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.NoSuchMetadataObjectException;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+
+public class OwnerDetails extends Command {
+
+  protected final String metalake;
+  protected final String entity;
+  protected final MetadataObject.Type entityType;
+
+  /**
+   * Displays the owner of an entity.
+   *
+   * @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 entity The name of the entity.
+   * @param entityType The type entity.
+   */
+  public OwnerDetails(
+      String url, boolean ignoreVersions, String metalake, String entity, 
String entityType) {
+    super(url, ignoreVersions);
+    this.metalake = metalake;
+    this.entity = entity;
+
+    if (entityType.equals(CommandEntities.METALAKE)) {
+      this.entityType = MetadataObject.Type.METALAKE;
+    } else if (entityType.equals(CommandEntities.CATALOG)) {
+      this.entityType = MetadataObject.Type.CATALOG;
+    } else if (entityType.equals(CommandEntities.SCHEMA)) {
+      this.entityType = MetadataObject.Type.SCHEMA;
+    } else if (entityType.equals(CommandEntities.TABLE)) {
+      this.entityType = MetadataObject.Type.TABLE;
+    } else if (entityType.equals(CommandEntities.COLUMN)) {
+      this.entityType = MetadataObject.Type.COLUMN;
+    } else {
+      this.entityType = null;
+    }
+  }
+
+  /** Displays the owner of an entity. */
+  @Override
+  public void handle() {
+    Optional<Owner> owner = null;
+    MetadataObject metadata = MetadataObjects.parse(entity, entityType);
+
+    try {
+      GravitinoClient client = buildClient(metalake);
+      owner = client.getOwner(metadata);
+    } catch (NoSuchMetalakeException err) {
+      System.err.println(ErrorMessages.UNKNOWN_METALAKE);
+      return;
+    } catch (NoSuchMetadataObjectException err) {
+      System.err.println(ErrorMessages.UNKNOWN_ENTITY);
+      return;
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    if (owner.isPresent()) {
+      System.out.println(owner.get().name());
+    } else {
+      System.out.println("No owner");
+    }
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SetOwner.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SetOwner.java
new file mode 100644
index 000000000..b10d7eea6
--- /dev/null
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SetOwner.java
@@ -0,0 +1,101 @@
+/*
+ * 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.MetadataObject;
+import org.apache.gravitino.MetadataObjects;
+import org.apache.gravitino.authorization.Owner;
+import org.apache.gravitino.cli.CommandEntities;
+import org.apache.gravitino.cli.ErrorMessages;
+import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.NoSuchMetadataObjectException;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+
+public class SetOwner extends Command {
+
+  protected final String metalake;
+  protected final String entity;
+  protected final MetadataObject.Type entityType;
+  protected final String owner;
+  protected final boolean isGroup;
+
+  /**
+   * Sets the owner of an entity.
+   *
+   * @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 entity The name of the entity.
+   * @param entityType The type entity.
+   * @param owner The name of the new owner.
+   * @param isGroup True if the owner is a group, false if it is not.
+   */
+  public SetOwner(
+      String url,
+      boolean ignoreVersions,
+      String metalake,
+      String entity,
+      String entityType,
+      String owner,
+      boolean isGroup) {
+    super(url, ignoreVersions);
+    this.metalake = metalake;
+    this.entity = entity;
+    this.owner = owner;
+    this.isGroup = isGroup;
+
+    if (entityType.equals(CommandEntities.METALAKE)) {
+      this.entityType = MetadataObject.Type.METALAKE;
+    } else if (entityType.equals(CommandEntities.CATALOG)) {
+      this.entityType = MetadataObject.Type.CATALOG;
+    } else if (entityType.equals(CommandEntities.SCHEMA)) {
+      this.entityType = MetadataObject.Type.SCHEMA;
+    } else if (entityType.equals(CommandEntities.TABLE)) {
+      this.entityType = MetadataObject.Type.TABLE;
+    } else if (entityType.equals(CommandEntities.COLUMN)) {
+      this.entityType = MetadataObject.Type.COLUMN;
+    } else {
+      this.entityType = null;
+    }
+  }
+
+  /** Sets the owner of an entity. */
+  @Override
+  public void handle() {
+    MetadataObject metadata = MetadataObjects.parse(entity, entityType);
+    Owner.Type ownerType = isGroup ? Owner.Type.GROUP : Owner.Type.USER;
+
+    try {
+      GravitinoClient client = buildClient(metalake);
+      client.setOwner(metadata, owner, ownerType);
+    } catch (NoSuchMetalakeException err) {
+      System.err.println(ErrorMessages.UNKNOWN_METALAKE);
+      return;
+    } catch (NoSuchMetadataObjectException err) {
+      System.err.println(ErrorMessages.UNKNOWN_ENTITY);
+      return;
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    System.out.println("Set owner to " + owner);
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestOwnerCommands.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestOwnerCommands.java
new file mode 100644
index 000000000..0c2b2cf91
--- /dev/null
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestOwnerCommands.java
@@ -0,0 +1,122 @@
+/*
+ * 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;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.gravitino.cli.commands.OwnerDetails;
+import org.apache.gravitino.cli.commands.SetOwner;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class TestOwnerCommands {
+  private CommandLine mockCommandLine;
+  private Options mockOptions;
+
+  @BeforeEach
+  void setUp() {
+    mockCommandLine = mock(CommandLine.class);
+    mockOptions = mock(Options.class);
+  }
+
+  @Test
+  void testSetOwnerUserCommand() {
+    SetOwner mockSetOwner = mock(SetOwner.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("postgres");
+    when(mockCommandLine.hasOption(GravitinoOptions.USER)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.USER)).thenReturn("admin");
+    when(mockCommandLine.hasOption(GravitinoOptions.GROUP)).thenReturn(false);
+    when(mockCommandLine.hasOption(GravitinoOptions.OWNER)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.CATALOG, 
CommandActions.SET));
+    doReturn(mockSetOwner)
+        .when(commandLine)
+        .newSetOwner(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            "metalake_demo",
+            "postgres",
+            "catalog",
+            "admin",
+            false);
+    commandLine.handleCommandLine();
+    verify(mockSetOwner).handle();
+  }
+
+  @Test
+  void testSetOwnerGroupCommand() {
+    SetOwner mockSetOwner = mock(SetOwner.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("postgres");
+    when(mockCommandLine.hasOption(GravitinoOptions.GROUP)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.GROUP)).thenReturn("ITdept");
+    when(mockCommandLine.hasOption(GravitinoOptions.USER)).thenReturn(false);
+    when(mockCommandLine.hasOption(GravitinoOptions.OWNER)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.CATALOG, 
CommandActions.SET));
+    doReturn(mockSetOwner)
+        .when(commandLine)
+        .newSetOwner(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            "metalake_demo",
+            "postgres",
+            "catalog",
+            "ITdept",
+            true);
+    commandLine.handleCommandLine();
+    verify(mockSetOwner).handle();
+  }
+
+  @Test
+  void testOwnerDetailsCommand() {
+    OwnerDetails mockOwnerDetails = mock(OwnerDetails.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("postgres");
+    when(mockCommandLine.hasOption(GravitinoOptions.OWNER)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.CATALOG, 
CommandActions.DETAILS));
+    doReturn(mockOwnerDetails)
+        .when(commandLine)
+        .newOwnerDetails(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"postgres", "catalog");
+    commandLine.handleCommandLine();
+    verify(mockOwnerDetails).handle();
+  }
+}
diff --git a/docs/cli.md b/docs/cli.md
index e37e92e10..dbbbca7cb 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -10,7 +10,7 @@ license: 'This software is licensed under the Apache License 
version 2.'
 
 This document provides guidance on managing metadata within Apache Gravitino 
using the Command Line Interface (CLI). The CLI offers a terminal based 
alternative to using code or the REST interface for metadata management.
 
-Currently, the CLI allows users to view metadata information for metalakes, 
catalogs, schemas, and tables. Future updates will expand on these capabilities 
to include roles, users, and tags.
+Currently, the CLI allows users to view metadata information for metalakes, 
catalogs, schemas, tables, users groups and tags. Future updates will expand on 
these capabilities to include roles, topics and filesets.
 
 ## Running the CLI
 
@@ -27,7 +27,8 @@ Or you use the `gcli.sh` script found in the 
`clients/cli/bin/` directory to run
 The general structure for running commands with the Gravitino CLI is `gcli 
entity command [options]`.
 
  ```bash
- usage: gcli [metalake|catalog|schema|table|column] 
[list|details|create|delete|update|set|remove|properties|revoke|grant] [options]
+ [options]
+ usage: gcli [metalake|catalog|schema|table|column|user|group|tag] 
[list|details|create|delete|update|set|remove|properties|revoke|grant] [options]
  Options
  -a,--audit              display audit information
  -c,--comment <arg>      entity comment
@@ -522,6 +523,26 @@ gcli tag update --tag tagA --rename newTag
 gcli tag update --tag tagA --comment "new comment"
 ```
 
+### Owners commands
+
+#### List an owner
+
+```bash
+gcli catalog details --owner --name postgres
+```
+
+#### Set an owner to a user
+
+```bash
+gcli catalog set --owner --user admin --name postgres
+```
+
+#### Set an owner to a group
+
+```bash
+gcli catalog set --owner --group groupA --name postgres
+```
+
 ### Role commands
 
 #### Display role details

Reply via email to