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

shaofengshi 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 73c786323 [#5131] Support tags in CLI (#5161)
73c786323 is described below

commit 73c78632383e2c9c50c153528f488c294e83fd96
Author: Justin Mclean <[email protected]>
AuthorDate: Mon Nov 11 11:46:19 2024 +1100

    [#5131] Support tags in CLI (#5161)
    
    ### What changes were proposed in this pull request?
    
    Add support for tags in the CLI.
    
    ### Why are the changes needed?
    
    To add support for tags in CLI.
    
    Fix: # (issue)
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes, it expands on the command line and adds new commands dealing with
    tags.
    
    ### How was this patch tested?
    
    Compiled locally.
---
 .../org/apache/gravitino/cli/CommandEntities.java  |   2 +
 .../org/apache/gravitino/cli/ErrorMessages.java    |   6 ++
 .../java/org/apache/gravitino/cli/FullName.java    |  46 +++++++++
 .../apache/gravitino/cli/GravitinoCommandLine.java |  68 ++++++++++++
 .../org/apache/gravitino/cli/GravitinoOptions.java |  10 +-
 .../{UpdateCatalogName.java => CreateTag.java}     |  34 +++---
 .../commands/{DeleteUser.java => DeleteTag.java}   |  26 ++---
 .../apache/gravitino/cli/commands/DeleteUser.java  |   2 +-
 .../commands/{DeleteUser.java => ListAllTags.java} |  29 ++----
 .../gravitino/cli/commands/ListEntityTags.java     | 103 +++++++++++++++++++
 .../{DeleteUser.java => ListTagProperties.java}    |  35 +++----
 ...dateCatalogName.java => RemoveTagProperty.java} |  36 ++++---
 .../{DeleteUser.java => SetTagProperty.java}       |  45 ++++----
 .../commands/{DeleteUser.java => TagDetails.java}  |  31 +++---
 .../apache/gravitino/cli/commands/TagEntity.java   | 109 ++++++++++++++++++++
 .../apache/gravitino/cli/commands/UntagEntity.java | 114 +++++++++++++++++++++
 .../gravitino/cli/commands/UpdateCatalogName.java  |   4 +
 ...pdateCatalogName.java => UpdateTagComment.java} |  36 ++++---
 .../{UpdateCatalogName.java => UpdateTagName.java} |  30 +++---
 .../apache/gravitino/cli/TestCommandEntities.java  |   1 +
 .../org/apache/gravitino/cli/TestFulllName.java    |  44 ++++++++
 docs/cli.md                                        |  95 ++++++++++++++++-
 22 files changed, 750 insertions(+), 156 deletions(-)

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 a7703b157..12d869c42 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
@@ -33,6 +33,7 @@ public class CommandEntities {
   public static final String COLUMN = "column";
   public static final String USER = "user";
   public static final String GROUP = "group";
+  public static final String TAG = "tag";
 
   private static final HashSet<String> VALID_ENTITIES = new HashSet<>();
 
@@ -44,6 +45,7 @@ public class CommandEntities {
     VALID_ENTITIES.add(COLUMN);
     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 c3da72ce8..f599964c9 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
@@ -37,4 +37,10 @@ public class ErrorMessages {
   public static final String USER_EXISTS = "User already exists.";
   public static final String UNKNOWN_GROUP = "Unknown group.";
   public static final String GROUP_EXISTS = "Group already exists.";
+  public static final String UNKNOWN_TAG = "Unknown tag.";
+  public static final String TAG_EXISTS = "Tag already exists.";
+  public static final String INVALID_SET_COMMAND =
+      "Unsupported combination of options either use --name or --property and 
--value.";
+  public static final String INVALID_REMOVE_COMMAND =
+      "Unsupported combination of options either use --name or --property.";
 }
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 a56fdc299..48b68bb44 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
@@ -129,4 +129,50 @@ public class FullName {
     System.err.println(ErrorMessages.MISSING_NAME);
     return null;
   }
+
+  /**
+   * Helper method to determine a specific part of the full name exits.
+   *
+   * @param partNo The part of the name to obtain.
+   * @return True if the part exitsts.
+   */
+  public boolean hasNamePart(int partNo) {
+    /* Extract the name part from the full name if available. */
+    if (line.hasOption(GravitinoOptions.NAME)) {
+      String[] names = line.getOptionValue(GravitinoOptions.NAME).split("\\.");
+      int length = names.length;
+      int position = partNo;
+
+      return position <= length;
+    }
+
+    return false;
+  }
+
+  /**
+   * Does the catalog name exist?
+   *
+   * @return True if the catalog name exists, or false if it does not.
+   */
+  public boolean hasCatalogName() {
+    return hasNamePart(1);
+  }
+
+  /**
+   * Does the schema name exist?
+   *
+   * @return True if the schema name exists, or false if it does not.
+   */
+  public boolean hasSchemaName() {
+    return hasNamePart(2);
+  }
+
+  /**
+   * Does the table name exist?
+   *
+   * @return True if the table name exists, or false if it does not.
+   */
+  public boolean hasTableName() {
+    return hasNamePart(3);
+  }
 }
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 d14ec4755..7e95d59fc 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
@@ -30,41 +30,53 @@ import org.apache.gravitino.cli.commands.CreateCatalog;
 import org.apache.gravitino.cli.commands.CreateGroup;
 import org.apache.gravitino.cli.commands.CreateMetalake;
 import org.apache.gravitino.cli.commands.CreateSchema;
+import org.apache.gravitino.cli.commands.CreateTag;
 import org.apache.gravitino.cli.commands.CreateUser;
 import org.apache.gravitino.cli.commands.DeleteCatalog;
 import org.apache.gravitino.cli.commands.DeleteGroup;
 import org.apache.gravitino.cli.commands.DeleteMetalake;
 import org.apache.gravitino.cli.commands.DeleteSchema;
 import org.apache.gravitino.cli.commands.DeleteTable;
+import org.apache.gravitino.cli.commands.DeleteTag;
 import org.apache.gravitino.cli.commands.DeleteUser;
 import org.apache.gravitino.cli.commands.GroupDetails;
+import org.apache.gravitino.cli.commands.ListAllTags;
 import org.apache.gravitino.cli.commands.ListCatalogProperties;
 import org.apache.gravitino.cli.commands.ListCatalogs;
 import org.apache.gravitino.cli.commands.ListColumns;
+import org.apache.gravitino.cli.commands.ListEntityTags;
 import org.apache.gravitino.cli.commands.ListGroups;
 import org.apache.gravitino.cli.commands.ListMetalakeProperties;
 import org.apache.gravitino.cli.commands.ListMetalakes;
 import org.apache.gravitino.cli.commands.ListSchema;
 import org.apache.gravitino.cli.commands.ListSchemaProperties;
 import org.apache.gravitino.cli.commands.ListTables;
+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.RemoveCatalogProperty;
 import org.apache.gravitino.cli.commands.RemoveMetalakeProperty;
 import org.apache.gravitino.cli.commands.RemoveSchemaProperty;
+import org.apache.gravitino.cli.commands.RemoveTagProperty;
 import org.apache.gravitino.cli.commands.SchemaAudit;
 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.SetSchemaProperty;
+import org.apache.gravitino.cli.commands.SetTagProperty;
 import org.apache.gravitino.cli.commands.TableAudit;
 import org.apache.gravitino.cli.commands.TableDetails;
+import org.apache.gravitino.cli.commands.TagDetails;
+import org.apache.gravitino.cli.commands.TagEntity;
+import org.apache.gravitino.cli.commands.UntagEntity;
 import org.apache.gravitino.cli.commands.UpdateCatalogComment;
 import org.apache.gravitino.cli.commands.UpdateCatalogName;
 import org.apache.gravitino.cli.commands.UpdateMetalakeComment;
 import org.apache.gravitino.cli.commands.UpdateMetalakeName;
+import org.apache.gravitino.cli.commands.UpdateTagComment;
+import org.apache.gravitino.cli.commands.UpdateTagName;
 import org.apache.gravitino.cli.commands.UserDetails;
 
 /* Gravitino Command line */
@@ -167,6 +179,8 @@ public class GravitinoCommandLine {
       handleUserCommand();
     } else if (entity.equals(CommandEntities.GROUP)) {
       handleGroupCommand();
+    } else if (entity.equals(CommandEntities.TAG)) {
+      handleTagCommand();
     }
   }
 
@@ -367,6 +381,60 @@ public class GravitinoCommandLine {
     }
   }
 
+  /** Handles the command execution for Tags based on command type and the 
command line options. */
+  protected void handleTagCommand() {
+    String url = getUrl();
+    FullName name = new FullName(line);
+    String metalake = name.getMetalakeName();
+    String tag = line.getOptionValue(GravitinoOptions.TAG);
+
+    if (CommandActions.DETAILS.equals(command)) {
+      new TagDetails(url, ignore, metalake, tag).handle();
+    } else if (CommandActions.LIST.equals(command)) {
+      if (!name.hasCatalogName()) {
+        new ListAllTags(url, ignore, metalake).handle();
+      } else {
+        new ListEntityTags(url, ignore, metalake, name).handle();
+      }
+    } else if (CommandActions.CREATE.equals(command)) {
+      String comment = line.getOptionValue(GravitinoOptions.COMMENT);
+      new CreateTag(url, ignore, metalake, tag, comment).handle();
+    } else if (CommandActions.DELETE.equals(command)) {
+      new DeleteTag(url, ignore, metalake, tag).handle();
+    } else if (CommandActions.SET.equals(command)) {
+      String property = line.getOptionValue(GravitinoOptions.PROPERTY);
+      String value = line.getOptionValue(GravitinoOptions.VALUE);
+
+      if (name == null && property != null && value != null) {
+        new SetTagProperty(url, ignore, metalake, tag, property, 
value).handle();
+      } else if (name != null && property == null && value == null) {
+        new TagEntity(url, ignore, metalake, name, tag).handle();
+      } else {
+        System.err.println(ErrorMessages.INVALID_SET_COMMAND);
+      }
+    } else if (CommandActions.REMOVE.equals(command)) {
+      String property = line.getOptionValue(GravitinoOptions.PROPERTY);
+      if (property != null) {
+        new RemoveTagProperty(url, ignore, metalake, tag, property).handle();
+      } else if (name != null) {
+        new UntagEntity(url, ignore, metalake, name, tag).handle();
+      } else {
+        System.err.println(ErrorMessages.INVALID_REMOVE_COMMAND);
+      }
+    } else if (CommandActions.PROPERTIES.equals(command)) {
+      new ListTagProperties(url, ignore, metalake, tag).handle();
+    } else if (CommandActions.UPDATE.equals(command)) {
+      if (line.hasOption(GravitinoOptions.COMMENT)) {
+        String comment = line.getOptionValue(GravitinoOptions.COMMENT);
+        new UpdateTagComment(url, ignore, metalake, tag, comment).handle();
+      }
+      if (line.hasOption(GravitinoOptions.RENAME)) {
+        String newName = line.getOptionValue(GravitinoOptions.RENAME);
+        new UpdateTagName(url, ignore, metalake, tag, newName).handle();
+      }
+    }
+  }
+
   /**
    * Handles the command execution for Columns based on command type and the 
command line options.
    */
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 d9ccbb4ad..a9449743c 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
@@ -39,6 +39,7 @@ public class GravitinoOptions {
   public static final String PROPERTIES = "properties";
   public static final String USER = "user";
   public static final String GROUP = "group";
+  public static final String TAG = "tag";
   public static final String AUDIT = "audit";
 
   /**
@@ -55,9 +56,9 @@ public class GravitinoOptions {
     options.addOption(createSimpleOption("s", SERVER, "Gravitino server 
version"));
     options.addOption(createArgOption("u", URL, "Gravitino URL (default: 
http://localhost:8090)"));
     options.addOption(createArgOption("n", NAME, "full entity name (dot 
separated)"));
-    options.addOption(createArgOption("m", METALAKE, "Metalake name"));
-    options.addOption(createSimpleOption("i", IGNORE, "Ignore client/sever 
version check"));
-    options.addOption(createSimpleOption("a", AUDIT, "Display audit 
information"));
+    options.addOption(createArgOption("m", METALAKE, "metalake name"));
+    options.addOption(createSimpleOption("i", IGNORE, "ignore client/sever 
version check"));
+    options.addOption(createSimpleOption("a", AUDIT, "display audit 
information"));
 
     // Create/update options
     options.addOption(createArgOption("r", RENAME, "new entity name"));
@@ -66,9 +67,10 @@ public class GravitinoOptions {
     options.addOption(createArgOption("V", VALUE, "property value"));
     options.addOption(
         createArgOption(
-            "t", PROVIDER, "provider one of hadoop, hive, mysql, postgres, 
iceberg, kafka"));
+            "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(createArgOption("t", TAG, "tag name"));
 
     // Properties option can have multiple values
     Option properties =
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
 b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateTag.java
similarity index 73%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateTag.java
index f314d0759..76f6f85b4 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateTag.java
@@ -19,50 +19,50 @@
 
 package org.apache.gravitino.cli.commands;
 
-import org.apache.gravitino.CatalogChange;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.exceptions.TagAlreadyExistsException;
 
-/** Update the name of a catalog. */
-public class UpdateCatalogName extends Command {
-
+public class CreateTag extends Command {
   protected final String metalake;
-  protected final String catalog;
-  protected final String name;
+  protected final String tag;
+  protected final String comment;
 
   /**
-   * Update the name of a catalog.
+   * Create a new tag.
    *
    * @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 name The new metalake name.
+   * @param tag The name of the tag.
+   * @param comment The comment of the tag.
    */
-  public UpdateCatalogName(
-      String url, boolean ignoreVersions, String metalake, String catalog, 
String name) {
+  public CreateTag(
+      String url, boolean ignoreVersions, String metalake, String tag, String 
comment) {
     super(url, ignoreVersions);
     this.metalake = metalake;
-    this.catalog = catalog;
-    this.name = name;
+    this.tag = tag;
+    this.comment = comment;
   }
 
-  /** Update the name of a catalog. */
+  /** Create a new tag. */
   @Override
   public void handle() {
     try {
       GravitinoClient client = buildClient(metalake);
-      CatalogChange change = CatalogChange.rename(name);
-      client.alterCatalog(catalog, change);
+      client.createTag(tag, comment, null);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
+    } catch (TagAlreadyExistsException err) {
+      System.err.println(ErrorMessages.TAG_EXISTS);
+      return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    System.out.println(catalog + " name changed.");
+    System.out.println(tag + " created");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteTag.java
similarity index 75%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteTag.java
index ae43866a1..83211eb21 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteTag.java
@@ -22,40 +22,40 @@ package org.apache.gravitino.cli.commands;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
-import org.apache.gravitino.exceptions.NoSuchUserException;
+import org.apache.gravitino.exceptions.NoSuchTagException;
 
-public class DeleteUser extends Command {
+public class DeleteTag extends Command {
 
   protected final String metalake;
-  protected final String user;
+  protected final String tag;
 
   /**
-   * Deletes a user.
+   * Delete a tag.
    *
    * @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 user The name of the user.
+   * @param tag The name of the tag.
    */
-  public DeleteUser(String url, boolean ignoreVersions, String metalake, 
String user) {
+  public DeleteTag(String url, boolean ignoreVersions, String metalake, String 
tag) {
     super(url, ignoreVersions);
     this.metalake = metalake;
-    this.user = user;
+    this.tag = tag;
   }
 
-  /** Delete a user. */
+  /** Delete a catalog. */
   @Override
   public void handle() {
     boolean deleted = false;
 
     try {
       GravitinoClient client = buildClient(metalake);
-      deleted = client.removeUser(user);
+      deleted = client.deleteTag(tag);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
-    } catch (NoSuchUserException err) {
-      System.err.println(ErrorMessages.UNKNOWN_USER);
+    } catch (NoSuchTagException err) {
+      System.err.println(ErrorMessages.UNKNOWN_TAG);
       return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
@@ -63,9 +63,9 @@ public class DeleteUser extends Command {
     }
 
     if (deleted) {
-      System.out.println(user + " deleted.");
+      System.out.println(tag + " deleted.");
     } else {
-      System.out.println(user + " not deleted.");
+      System.out.println(tag + " not deleted.");
     }
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java
index ae43866a1..c5ffe69e8 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java
@@ -30,7 +30,7 @@ public class DeleteUser extends Command {
   protected final String user;
 
   /**
-   * Deletes a user.
+   * Delete a user.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListAllTags.java
similarity index 71%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListAllTags.java
index ae43866a1..83e97b7ac 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListAllTags.java
@@ -22,50 +22,41 @@ package org.apache.gravitino.cli.commands;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
-import org.apache.gravitino.exceptions.NoSuchUserException;
 
-public class DeleteUser extends Command {
+/* Lists all tags in a metalake. */
+public class ListAllTags extends Command {
 
   protected final String metalake;
-  protected final String user;
 
   /**
-   * Deletes a user.
+   * Lists all tags in a metalake.
    *
    * @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 user The name of the user.
    */
-  public DeleteUser(String url, boolean ignoreVersions, String metalake, 
String user) {
+  public ListAllTags(String url, boolean ignoreVersions, String metalake) {
     super(url, ignoreVersions);
     this.metalake = metalake;
-    this.user = user;
   }
 
-  /** Delete a user. */
+  /** Lists all tags in a metalake. */
   @Override
   public void handle() {
-    boolean deleted = false;
-
+    String[] tags = new String[0];
     try {
       GravitinoClient client = buildClient(metalake);
-      deleted = client.removeUser(user);
+      tags = client.listTags();
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
-    } catch (NoSuchUserException err) {
-      System.err.println(ErrorMessages.UNKNOWN_USER);
-      return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    if (deleted) {
-      System.out.println(user + " deleted.");
-    } else {
-      System.out.println(user + " not deleted.");
-    }
+    String all = String.join(",", tags);
+
+    System.out.println(all.toString());
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListEntityTags.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListEntityTags.java
new file mode 100644
index 000000000..9defeee8b
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListEntityTags.java
@@ -0,0 +1,103 @@
+/*
+ * 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.Catalog;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Schema;
+import org.apache.gravitino.cli.ErrorMessages;
+import org.apache.gravitino.cli.FullName;
+import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NoSuchTableException;
+import org.apache.gravitino.rel.Table;
+
+/* Lists all tags in a metalake. */
+public class ListEntityTags extends Command {
+
+  protected String metalake;
+  protected FullName name;
+
+  /**
+   * Lists all tags in a metalake.
+   *
+   * @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 name The name of the entity.
+   */
+  public ListEntityTags(String url, boolean ignoreVersions, String metalake, 
FullName name) {
+    super(url, ignoreVersions);
+    this.metalake = metalake;
+    this.name = name;
+  }
+
+  /** Lists all tags in a metalake. */
+  @Override
+  public void handle() {
+    String[] tags = new String[0];
+    try {
+      GravitinoClient client = buildClient(metalake);
+
+      // TODO fileset and topic
+      if (name.hasTableName()) {
+        String catalog = name.getCatalogName();
+        String schema = name.getSchemaName();
+        String table = name.getTableName();
+        Table gTable =
+            client
+                .loadCatalog(catalog)
+                .asTableCatalog()
+                .loadTable(NameIdentifier.of(schema, table));
+        tags = gTable.supportsTags().listTags();
+      } else if (name.hasSchemaName()) {
+        String catalog = name.getCatalogName();
+        String schema = name.getSchemaName();
+        Schema gSchema = 
client.loadCatalog(catalog).asSchemas().loadSchema(schema);
+        tags = gSchema.supportsTags().listTags();
+      } else if (name.hasCatalogName()) {
+        String catalog = name.getCatalogName();
+        Catalog gCatalog = client.loadCatalog(catalog);
+        tags = gCatalog.supportsTags().listTags();
+      }
+    } catch (NoSuchMetalakeException err) {
+      System.err.println(ErrorMessages.UNKNOWN_METALAKE);
+      return;
+    } catch (NoSuchCatalogException err) {
+      System.err.println(ErrorMessages.UNKNOWN_CATALOG);
+      return;
+    } catch (NoSuchSchemaException err) {
+      System.err.println(ErrorMessages.UNKNOWN_SCHEMA);
+      return;
+    } catch (NoSuchTableException err) {
+      System.err.println(ErrorMessages.UNKNOWN_TABLE);
+      return;
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    String all = String.join(",", tags);
+
+    System.out.println(all.toString());
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListTagProperties.java
similarity index 70%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListTagProperties.java
index ae43866a1..14ea1d066 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListTagProperties.java
@@ -19,53 +19,52 @@
 
 package org.apache.gravitino.cli.commands;
 
+import java.util.Map;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
-import org.apache.gravitino.exceptions.NoSuchUserException;
+import org.apache.gravitino.exceptions.NoSuchTagException;
+import org.apache.gravitino.tag.Tag;
 
-public class DeleteUser extends Command {
+/** List the properties of a tag. */
+public class ListTagProperties extends ListProperties {
 
   protected final String metalake;
-  protected final String user;
+  protected final String tag;
 
   /**
-   * Deletes a user.
+   * List the properties of a tag.
    *
    * @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 user The name of the user.
+   * @param tag The name of the tag.
    */
-  public DeleteUser(String url, boolean ignoreVersions, String metalake, 
String user) {
+  public ListTagProperties(String url, boolean ignoreVersions, String 
metalake, String tag) {
     super(url, ignoreVersions);
     this.metalake = metalake;
-    this.user = user;
+    this.tag = tag;
   }
 
-  /** Delete a user. */
+  /** List the properties of a tag. */
   @Override
   public void handle() {
-    boolean deleted = false;
-
+    Tag gTag = null;
     try {
       GravitinoClient client = buildClient(metalake);
-      deleted = client.removeUser(user);
+      gTag = client.getTag(tag);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
-    } catch (NoSuchUserException err) {
-      System.err.println(ErrorMessages.UNKNOWN_USER);
+    } catch (NoSuchTagException err) {
+      System.err.println(ErrorMessages.UNKNOWN_TAG);
       return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    if (deleted) {
-      System.out.println(user + " deleted.");
-    } else {
-      System.out.println(user + " not deleted.");
-    }
+    Map<String, String> properties = gTag.properties();
+    printProperties(properties);
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/RemoveTagProperty.java
similarity index 68%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/RemoveTagProperty.java
index f314d0759..fc94f3bc1 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/RemoveTagProperty.java
@@ -19,50 +19,54 @@
 
 package org.apache.gravitino.cli.commands;
 
-import org.apache.gravitino.CatalogChange;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.exceptions.NoSuchTagException;
+import org.apache.gravitino.tag.TagChange;
 
-/** Update the name of a catalog. */
-public class UpdateCatalogName extends Command {
+/** Remove a property of a tag. */
+public class RemoveTagProperty extends Command {
 
   protected final String metalake;
-  protected final String catalog;
-  protected final String name;
+  protected final String tag;
+  protected final String property;
 
   /**
-   * Update the name of a catalog.
+   * Remove a property of a tag.
    *
    * @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 name The new metalake name.
+   * @param tag The name of the tag.
+   * @param property The name of the property.
    */
-  public UpdateCatalogName(
-      String url, boolean ignoreVersions, String metalake, String catalog, 
String name) {
+  public RemoveTagProperty(
+      String url, boolean ignoreVersions, String metalake, String tag, String 
property) {
     super(url, ignoreVersions);
     this.metalake = metalake;
-    this.catalog = catalog;
-    this.name = name;
+    this.tag = tag;
+    this.property = property;
   }
 
-  /** Update the name of a catalog. */
+  /** Remove a property of a catalog. */
   @Override
   public void handle() {
     try {
       GravitinoClient client = buildClient(metalake);
-      CatalogChange change = CatalogChange.rename(name);
-      client.alterCatalog(catalog, change);
+      TagChange change = TagChange.removeProperty(property);
+      client.alterTag(tag, change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
+    } catch (NoSuchTagException err) {
+      System.err.println(ErrorMessages.UNKNOWN_TAG);
+      return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    System.out.println(catalog + " name changed.");
+    System.out.println(property + " property removed.");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SetTagProperty.java
similarity index 63%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/SetTagProperty.java
index ae43866a1..5b0a73d3b 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SetTagProperty.java
@@ -22,50 +22,59 @@ package org.apache.gravitino.cli.commands;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
-import org.apache.gravitino.exceptions.NoSuchUserException;
+import org.apache.gravitino.exceptions.NoSuchTagException;
+import org.apache.gravitino.tag.TagChange;
 
-public class DeleteUser extends Command {
+/** Set a property of a tag. */
+public class SetTagProperty extends Command {
 
   protected final String metalake;
-  protected final String user;
+  protected final String tag;
+  protected final String property;
+  protected final String value;
 
   /**
-   * Deletes a user.
+   * Set a property of a tag.
    *
    * @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 user The name of the user.
+   * @param tag The name of the tag.
+   * @param property The name of the property.
+   * @param value The value of the property.
    */
-  public DeleteUser(String url, boolean ignoreVersions, String metalake, 
String user) {
+  public SetTagProperty(
+      String url,
+      boolean ignoreVersions,
+      String metalake,
+      String tag,
+      String property,
+      String value) {
     super(url, ignoreVersions);
     this.metalake = metalake;
-    this.user = user;
+    this.tag = tag;
+    this.property = property;
+    this.value = value;
   }
 
-  /** Delete a user. */
+  /** Set a property of a tag. */
   @Override
   public void handle() {
-    boolean deleted = false;
-
     try {
       GravitinoClient client = buildClient(metalake);
-      deleted = client.removeUser(user);
+      TagChange change = TagChange.setProperty(property, value);
+      client.alterTag(tag, change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
-    } catch (NoSuchUserException err) {
-      System.err.println(ErrorMessages.UNKNOWN_USER);
+    } catch (NoSuchTagException err) {
+      System.err.println(ErrorMessages.UNKNOWN_TAG);
       return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    if (deleted) {
-      System.out.println(user + " deleted.");
-    } else {
-      System.out.println(user + " not deleted.");
-    }
+    System.out.println(tag + " property set.");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TagDetails.java
similarity index 71%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/TagDetails.java
index ae43866a1..711d33049 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteUser.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TagDetails.java
@@ -21,51 +21,50 @@ package org.apache.gravitino.cli.commands;
 
 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.NoSuchUserException;
+import org.apache.gravitino.tag.Tag;
 
-public class DeleteUser extends Command {
+public class TagDetails extends Command {
 
   protected final String metalake;
-  protected final String user;
+  protected final String tag;
 
   /**
-   * Deletes a user.
+   * Displays the name and comment of a catalog.
    *
    * @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 user The name of the user.
+   * @param tag The name of the tag.
    */
-  public DeleteUser(String url, boolean ignoreVersions, String metalake, 
String user) {
+  public TagDetails(String url, boolean ignoreVersions, String metalake, 
String tag) {
     super(url, ignoreVersions);
     this.metalake = metalake;
-    this.user = user;
+    this.tag = tag;
   }
 
-  /** Delete a user. */
+  /** Displays the name and details of a specified tag. */
   @Override
   public void handle() {
-    boolean deleted = false;
+    Tag result = null;
 
     try {
       GravitinoClient client = buildClient(metalake);
-      deleted = client.removeUser(user);
+      result = client.getTag(tag);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
-    } catch (NoSuchUserException err) {
-      System.err.println(ErrorMessages.UNKNOWN_USER);
+    } catch (NoSuchCatalogException err) {
+      System.err.println(ErrorMessages.UNKNOWN_CATALOG);
       return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    if (deleted) {
-      System.out.println(user + " deleted.");
-    } else {
-      System.out.println(user + " not deleted.");
+    if (result != null) {
+      System.out.println(result.name() + "," + result.comment());
     }
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TagEntity.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TagEntity.java
new file mode 100644
index 000000000..08e1d2ca1
--- /dev/null
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TagEntity.java
@@ -0,0 +1,109 @@
+/*
+ * 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.Catalog;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Schema;
+import org.apache.gravitino.cli.ErrorMessages;
+import org.apache.gravitino.cli.FullName;
+import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NoSuchTableException;
+import org.apache.gravitino.rel.Table;
+
+public class TagEntity extends Command {
+  protected final String metalake;
+  protected final FullName name;
+  protected final String tag;
+
+  /**
+   * Tag an entity with an existing tag.
+   *
+   * @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 name The name of the entity.
+   * @param tag The name of the tag.
+   */
+  public TagEntity(String url, boolean ignoreVersions, String metalake, 
FullName name, String tag) {
+    super(url, ignoreVersions);
+    this.metalake = metalake;
+    this.name = name;
+    this.tag = tag;
+  }
+
+  /** Create a new tag. */
+  @Override
+  public void handle() {
+    String entity = "unknown";
+    String[] tags = new String[0];
+
+    try {
+      GravitinoClient client = buildClient(metalake);
+
+      // TODO fileset and topic
+      if (name.hasTableName()) {
+        String catalog = name.getCatalogName();
+        String schema = name.getSchemaName();
+        String table = name.getTableName();
+        Table gTable =
+            client
+                .loadCatalog(catalog)
+                .asTableCatalog()
+                .loadTable(NameIdentifier.of(schema, table));
+        tags = gTable.supportsTags().associateTags(new String[] {tag}, null);
+        entity = table;
+      } else if (name.hasSchemaName()) {
+        String catalog = name.getCatalogName();
+        String schema = name.getSchemaName();
+        Schema gSchema = 
client.loadCatalog(catalog).asSchemas().loadSchema(schema);
+        tags = gSchema.supportsTags().associateTags(new String[] {tag}, null);
+        entity = schema;
+      } else if (name.hasCatalogName()) {
+        String catalog = name.getCatalogName();
+        Catalog gCatalog = client.loadCatalog(catalog);
+        tags = gCatalog.supportsTags().associateTags(new String[] {tag}, null);
+        entity = catalog;
+      }
+    } catch (NoSuchMetalakeException err) {
+      System.err.println(ErrorMessages.UNKNOWN_METALAKE);
+      return;
+    } catch (NoSuchCatalogException err) {
+      System.err.println(ErrorMessages.UNKNOWN_CATALOG);
+      return;
+    } catch (NoSuchSchemaException err) {
+      System.err.println(ErrorMessages.UNKNOWN_SCHEMA);
+      return;
+    } catch (NoSuchTableException err) {
+      System.err.println(ErrorMessages.UNKNOWN_TABLE);
+      return;
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    String all = String.join(",", tags);
+
+    System.out.println(entity + " tagged with " + all);
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UntagEntity.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UntagEntity.java
new file mode 100644
index 000000000..91b9fcb15
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UntagEntity.java
@@ -0,0 +1,114 @@
+/*
+ * 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.Catalog;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Schema;
+import org.apache.gravitino.cli.ErrorMessages;
+import org.apache.gravitino.cli.FullName;
+import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NoSuchTableException;
+import org.apache.gravitino.rel.Table;
+
+public class UntagEntity extends Command {
+  protected final String metalake;
+  protected final FullName name;
+  protected final String tag;
+
+  /**
+   * Untag an entity with an existing tag.
+   *
+   * @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 name The name of the entity.
+   * @param tag The name of the tag.
+   */
+  public UntagEntity(
+      String url, boolean ignoreVersions, String metalake, FullName name, 
String tag) {
+    super(url, ignoreVersions);
+    this.metalake = metalake;
+    this.name = name;
+    this.tag = tag;
+  }
+
+  /** Create a new tag. */
+  @Override
+  public void handle() {
+    String entity = "unknown";
+    String[] tags = new String[0];
+
+    try {
+      GravitinoClient client = buildClient(metalake);
+
+      // TODO fileset and topic
+      if (name.hasTableName()) {
+        String catalog = name.getCatalogName();
+        String schema = name.getSchemaName();
+        String table = name.getTableName();
+        Table gTable =
+            client
+                .loadCatalog(catalog)
+                .asTableCatalog()
+                .loadTable(NameIdentifier.of(schema, table));
+        tags = gTable.supportsTags().associateTags(null, new String[] {tag});
+        entity = table;
+      } else if (name.hasSchemaName()) {
+        String catalog = name.getCatalogName();
+        String schema = name.getSchemaName();
+        Schema gSchema = 
client.loadCatalog(catalog).asSchemas().loadSchema(schema);
+        tags = gSchema.supportsTags().associateTags(null, new String[] {tag});
+        entity = schema;
+      } else if (name.hasCatalogName()) {
+        String catalog = name.getCatalogName();
+        Catalog gCatalog = client.loadCatalog(catalog);
+        tags = gCatalog.supportsTags().associateTags(null, new String[] {tag});
+        entity = catalog;
+      }
+    } catch (NoSuchMetalakeException err) {
+      System.err.println(ErrorMessages.UNKNOWN_METALAKE);
+      return;
+    } catch (NoSuchCatalogException err) {
+      System.err.println(ErrorMessages.UNKNOWN_CATALOG);
+      return;
+    } catch (NoSuchSchemaException err) {
+      System.err.println(ErrorMessages.UNKNOWN_TABLE);
+      return;
+    } catch (NoSuchTableException err) {
+      System.err.println(ErrorMessages.UNKNOWN_TABLE);
+      return;
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    String all = String.join(",", tags);
+
+    if (all.equals("")) {
+      all = "nothing";
+    }
+
+    System.out.println(entity + " removed tag " + tag + ", now tagged with " + 
all);
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
index f314d0759..e4339e246 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
@@ -22,6 +22,7 @@ package org.apache.gravitino.cli.commands;
 import org.apache.gravitino.CatalogChange;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 
 /** Update the name of a catalog. */
@@ -58,6 +59,9 @@ public class UpdateCatalogName extends Command {
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
+    } catch (NoSuchCatalogException err) {
+      System.err.println(ErrorMessages.UNKNOWN_CATALOG);
+      return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTagComment.java
similarity index 69%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTagComment.java
index f314d0759..17ad6f7e3 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTagComment.java
@@ -19,50 +19,54 @@
 
 package org.apache.gravitino.cli.commands;
 
-import org.apache.gravitino.CatalogChange;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.exceptions.NoSuchTagException;
+import org.apache.gravitino.tag.TagChange;
 
-/** Update the name of a catalog. */
-public class UpdateCatalogName extends Command {
+/** Update the comment of a tag. */
+public class UpdateTagComment extends Command {
 
   protected final String metalake;
-  protected final String catalog;
-  protected final String name;
+  protected final String tag;
+  protected final String comment;
 
   /**
-   * Update the name of a catalog.
+   * Update the comment of a tag.
    *
    * @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 name The new metalake name.
+   * @param tag The name of the tag.
+   * @param comment New metalake comment.
    */
-  public UpdateCatalogName(
-      String url, boolean ignoreVersions, String metalake, String catalog, 
String name) {
+  public UpdateTagComment(
+      String url, boolean ignoreVersions, String metalake, String tag, String 
comment) {
     super(url, ignoreVersions);
     this.metalake = metalake;
-    this.catalog = catalog;
-    this.name = name;
+    this.tag = tag;
+    this.comment = comment;
   }
 
-  /** Update the name of a catalog. */
+  /** Update the comment of a tag. */
   @Override
   public void handle() {
     try {
       GravitinoClient client = buildClient(metalake);
-      CatalogChange change = CatalogChange.rename(name);
-      client.alterCatalog(catalog, change);
+      TagChange change = TagChange.updateComment(comment);
+      client.alterTag(tag, change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
+    } catch (NoSuchTagException err) {
+      System.err.println(ErrorMessages.UNKNOWN_TAG);
+      return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    System.out.println(catalog + " name changed.");
+    System.out.println(tag + " comment changed.");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTagName.java
similarity index 73%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTagName.java
index f314d0759..890cd26cf 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTagName.java
@@ -19,32 +19,33 @@
 
 package org.apache.gravitino.cli.commands;
 
-import org.apache.gravitino.CatalogChange;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.exceptions.NoSuchTagException;
+import org.apache.gravitino.tag.TagChange;
 
-/** Update the name of a catalog. */
-public class UpdateCatalogName extends Command {
+/** Update the name of a tag. */
+public class UpdateTagName extends Command {
 
   protected final String metalake;
-  protected final String catalog;
+  protected final String tag;
   protected final String name;
 
   /**
-   * Update the name of a catalog.
+   * Update the name of a tag.
    *
    * @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 metalake The name of the tag.
+   * @param tag The name of the catalog.
    * @param name The new metalake name.
    */
-  public UpdateCatalogName(
-      String url, boolean ignoreVersions, String metalake, String catalog, 
String name) {
+  public UpdateTagName(
+      String url, boolean ignoreVersions, String metalake, String tag, String 
name) {
     super(url, ignoreVersions);
     this.metalake = metalake;
-    this.catalog = catalog;
+    this.tag = tag;
     this.name = name;
   }
 
@@ -53,16 +54,19 @@ public class UpdateCatalogName extends Command {
   public void handle() {
     try {
       GravitinoClient client = buildClient(metalake);
-      CatalogChange change = CatalogChange.rename(name);
-      client.alterCatalog(catalog, change);
+      TagChange change = TagChange.rename(name);
+      client.alterTag(tag, change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
+    } catch (NoSuchTagException err) {
+      System.err.println(ErrorMessages.UNKNOWN_TAG);
+      return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    System.out.println(catalog + " name changed.");
+    System.out.println(tag + " name changed.");
   }
 }
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandEntities.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandEntities.java
index 2ca43d060..cd2a55e2a 100644
--- 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandEntities.java
+++ 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandEntities.java
@@ -37,6 +37,7 @@ public class TestCommandEntities {
         CommandEntities.isValidEntity(CommandEntities.SCHEMA), "SCHEMA should 
be a valid entity");
     assertTrue(
         CommandEntities.isValidEntity(CommandEntities.TABLE), "TABLE should be 
a valid entity");
+    assertTrue(CommandEntities.isValidEntity(CommandEntities.TAG), "TAG should 
be a valid entity");
   }
 
   @Test
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestFulllName.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestFulllName.java
index 27108fd67..05e78b4c8 100644
--- a/clients/cli/src/test/java/org/apache/gravitino/cli/TestFulllName.java
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestFulllName.java
@@ -20,8 +20,10 @@
 package org.apache.gravitino.cli;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.DefaultParser;
@@ -89,4 +91,46 @@ public class TestFulllName {
     String namePart = fullName.getNamePart(3);
     assertNull(namePart);
   }
+
+  @Test
+  public void hasPartNameMetalake() throws Exception {
+    String[] args = {"metalake", "details", "--metalake", "metalake"};
+    CommandLine commandLine = new DefaultParser().parse(options, args);
+    FullName fullName = new FullName(commandLine);
+    assertFalse(fullName.hasCatalogName());
+    assertFalse(fullName.hasSchemaName());
+    assertFalse(fullName.hasTableName());
+  }
+
+  @Test
+  public void hasPartNameCatalog() throws Exception {
+    String[] args = {"catalog", "details", "--metalake", "metalake", "--name", 
"catalog"};
+    CommandLine commandLine = new DefaultParser().parse(options, args);
+    FullName fullName = new FullName(commandLine);
+    assertTrue(fullName.hasCatalogName());
+    assertFalse(fullName.hasSchemaName());
+    assertFalse(fullName.hasTableName());
+  }
+
+  @Test
+  public void hasPartNameSchema() throws Exception {
+    String[] args = {"schema", "details", "--metalake", "metalake", "--name", 
"catalog.schema"};
+    CommandLine commandLine = new DefaultParser().parse(options, args);
+    FullName fullName = new FullName(commandLine);
+    assertTrue(fullName.hasCatalogName());
+    assertTrue(fullName.hasSchemaName());
+    assertFalse(fullName.hasTableName());
+  }
+
+  @Test
+  public void hasPartNameTable() throws Exception {
+    String[] args = {
+      "table", "details", "--metalake", "metalake", "--name", 
"catalog.schema.table"
+    };
+    CommandLine commandLine = new DefaultParser().parse(options, args);
+    FullName fullName = new FullName(commandLine);
+    assertTrue(fullName.hasCatalogName());
+    assertTrue(fullName.hasSchemaName());
+    assertTrue(fullName.hasTableName());
+  }
 }
diff --git a/docs/cli.md b/docs/cli.md
index 663a9c6ec..750ab03a2 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -29,23 +29,24 @@ The general structure for running commands with the 
Gravitino CLI is `gcli entit
  ```bash
  usage: gcli [metalake|catalog|schema|table|column] 
[list|details|create|delete|update|set|remove|properties] [options]
  Options
+ -a,--audit              display audit information
  -c,--comment <arg>      entity comment
  -g,--group <arg>        group name
  -h,--help               command help information
- -i,--ignore             Ignore client/sever version check
+ -i,--ignore             ignore client/sever version check
  -l,--user <arg>         user name
- -a,--audit              display audit information
- -m,--metalake <arg>     Metalake name
+ -m,--metalake <arg>     metalake name
  -n,--name <arg>         full entity name (dot separated)
  -P,--property <arg>     property name
  -p,--properties <arg>   property name/value pairs
  -r,--rename <arg>       new entity name
  -s,--server             Gravitino server version
- -t,--provider <arg>     provider one of hadoop, hive, mysql, postgres,
-                         iceberg, kafka
+ -t,--tag <arg>          tag name
  -u,--url <arg>          Gravitino URL (default: http://localhost:8090)
  -v,--version            Gravitino client version
  -V,--value <arg>        property value
+ -z,--provider <arg>     provider one of hadoop, hive, mysql, postgres,
+                         iceberg, kafka
  ```
 
 ## Commands
@@ -147,6 +148,16 @@ For commands that accept multiple properties they can be 
specified in a couple o
 
 3. gcli --properties n1=v1 --properties n2=v2 --properties n3=v3
 
+### Setting properties and tags
+
+Different options are needed to add a tag and set a property of a tag with 
`gcli tag set`. To add a
+tag, specify the tag (via --tag) and the entity to tag (via --name). To set 
the property of a tag
+(via --tag) you need to specify the property (via --property) and value (via 
--value) you want to
+set.
+
+To delete a tag, again, you need to specify the tag and entity, to remove a 
tag's property you need
+to select the tag and property.
+
 ### Metalake commands
 
 #### Show all metalakes
@@ -410,3 +421,77 @@ gcli group list --metalake metalake_demo
 ```bash
 gcli group delete --metalake metalake_demo --group new_group
 ```
+
+### Tag commands
+
+#### Display a tag's details
+
+```bash
+gcli tag details --metalake metalake_demo --tag tagA
+```
+
+#### Create a tag
+
+```bash
+gcli tag create --metalake metalake_demo --tag tagA
+```
+
+#### List all tag
+
+```bash
+gcli tag list --metalake metalake_demo
+```
+
+#### Delete a tag
+
+```bash
+gcli tag delete --metalake metalake_demo --tag tagA
+```
+
+#### Add a tag to an entity
+
+```bash
+gcli tag set --metalake metalake_demo --name catalog_postgres.hr --tag tagA
+```
+
+#### Remove a tag from an entity
+
+```bash
+gcli tag remove --metalake metalake_demo --name catalog_postgres.hr --tag tagA
+```
+
+#### List all tags on an entity
+
+```bash
+gcli tag list --metalake metalake_demo --name catalog_postgres.hr
+```
+
+#### List the properties of a tag
+
+```bash
+gcli tag properties --metalake metalake_demo --tag tagA
+```
+
+#### Set a properties of a tag
+
+```bash
+gcli tag set --metalake metalake_demo --tag tagA --property test --value value
+```
+
+#### Delete a property of a tag
+
+```bash
+gcli tag remove --metalake metalake_demo --tag tagA --property test
+```
+
+#### Rename a tag
+
+```bash
+gcli tag update --metalake metalake_demo --tag tagA --rename newTag
+```
+
+#### Update a tag's comment
+
+```bash
+gcli tag update --metalake metalake_demo --tag tagA --comment "new comment"
+```

Reply via email to