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 3cd03fe2e [#5057] Added first part of CLI code (#5058)
3cd03fe2e is described below

commit 3cd03fe2ef0b3ccbfaae02165e4691b7c0becfea
Author: Justin Mclean <[email protected]>
AuthorDate: Thu Oct 24 17:19:15 2024 +1100

    [#5057] Added first part of CLI code (#5058)
    
    ### What changes were proposed in this pull request?
    
    Added initial CLI to list information about metalakes, catalogs, schema
    and tables.
    
    ### Why are the changes needed?
    
    For a CLI.
    
    Fix: #5057
    
    ### Does this PR introduce _any_ user-facing change?
    
    No, but it add a new API.
    
    ### How was this patch tested?
    
    Unit test pass locally.
---
 build.gradle.kts                                   |   3 +-
 clients/cli/README.md                              |  78 +++++++
 clients/cli/bin/gcli.sh                            |  22 ++
 clients/cli/build.gradle.kts                       |  60 ++++++
 .../org/apache/gravitino/cli/CommandActions.java   |  54 +++++
 .../org/apache/gravitino/cli/CommandEntities.java  |  54 +++++
 .../org/apache/gravitino/cli/ErrorMessages.java    |  32 +++
 .../java/org/apache/gravitino/cli/FullName.java    | 118 +++++++++++
 .../apache/gravitino/cli/GravitinoCommandLine.java | 225 +++++++++++++++++++++
 .../org/apache/gravitino/cli/GravitinoOptions.java |  78 +++++++
 .../main/java/org/apache/gravitino/cli/Main.java   | 100 +++++++++
 .../gravitino/cli/commands/AllMetalakeDetails.java |  60 ++++++
 .../gravitino/cli/commands/CatalogDetails.java     |  70 +++++++
 .../gravitino/cli/commands/ClientVersion.java      |  49 +++++
 .../org/apache/gravitino/cli/commands/Command.java |  72 +++++++
 .../gravitino/cli/commands/ListCatalogs.java       |  62 ++++++
 .../apache/gravitino/cli/commands/ListColumns.java |  88 ++++++++
 .../gravitino/cli/commands/ListMetalakes.java      |  61 ++++++
 .../apache/gravitino/cli/commands/ListSchema.java  |  69 +++++++
 .../apache/gravitino/cli/commands/ListTables.java  |  68 +++++++
 .../gravitino/cli/commands/MetalakeDetails.java    |  58 ++++++
 .../gravitino/cli/commands/SchemaDetails.java      |  78 +++++++
 .../gravitino/cli/commands/ServerVersion.java      |  50 +++++
 .../gravitino/cli/commands/TableCommand.java       |  76 +++++++
 .../gravitino/cli/commands/TableDetails.java       |  67 ++++++
 .../apache/gravitino/cli/TestCommandActions.java   |  69 +++++++
 .../apache/gravitino/cli/TestCommandEntities.java  |  69 +++++++
 .../org/apache/gravitino/cli/TestFulllName.java    |  84 ++++++++
 .../apache/gravitino/cli/TestGravitinoOptions.java |  59 ++++++
 .../java/org/apache/gravitino/cli/TestMain.java    | 120 +++++++++++
 docs/cli.md                                        | 155 ++++++++++++++
 docs/index.md                                      |   5 +-
 gradle/libs.versions.toml                          |   3 +
 settings.gradle.kts                                |   3 +-
 34 files changed, 2316 insertions(+), 3 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index d4eaee1e1..e2cc64326 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -349,7 +349,7 @@ subprojects {
     options.locale = "en_US"
 
     val projectName = project.name
-    if (projectName == "common" || projectName == "api" || projectName == 
"client-java" || projectName == "filesystem-hadoop3") {
+    if (projectName == "common" || projectName == "api" || projectName == 
"client-java" || projectName == "client-cli" || projectName == 
"filesystem-hadoop3") {
       options {
         (this as CoreJavadocOptions).addStringOption("Xwerror", "-quiet")
         isFailOnError = true
@@ -760,6 +760,7 @@ tasks {
     subprojects.forEach() {
       if (!it.name.startsWith("catalog") &&
         !it.name.startsWith("client") &&
+        !it.name.startsWith("cli") &&
         !it.name.startsWith("authorization") &&
         !it.name.startsWith("filesystem") &&
         !it.name.startsWith("spark") &&
diff --git a/clients/cli/README.md b/clients/cli/README.md
new file mode 100644
index 000000000..38f5b03dd
--- /dev/null
+++ b/clients/cli/README.md
@@ -0,0 +1,78 @@
+<!--
+  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.
+-->
+
+# Apache Gravitino Command Line Interface
+
+Apache Gravitino CLI is a command-line tool that interacts with the Gravitino 
server to manage and query entities like metalakes, catalogs, schemas, and 
tables. The tool provides options for listing information about Gravitino 
entities and in future versions support creating, deleting, and updating these 
entities.
+
+## Table of Contents
+
+- [Installation](#installation)
+- [Running Tests](#running-tests)
+- [Contributing](#contributing)
+- [License](#license)
+
+## Installation
+
+### Prerequisites
+
+Before you can build and run this project, it is suggested you have the 
following installed:
+
+- Java 8 or higher
+
+### Build the Project
+
+1. Clone the entire Gravitino repository:
+
+    ```bash
+    git clone https://github.com/apache/gravitino
+    ```
+
+2. Build the CLI sub-project using Gradle:
+
+    ```bash
+    ./gradlew :clients:cli:build
+    ```
+3. Create an alias:
+
+    ```bash
+    alias gcli='java -jar 
clients/cli/build/libs/gravitino-cli-*-incubating-SNAPSHOT.jar'
+    ```
+3. Test the command:
+    ```bash
+    gcli --help
+    ```
+
+## Running Tests
+
+This project includes a suite of unit tests to verify its functionality.
+
+To run the tests, execute the following command:
+
+```bash
+./gradlew :clients:cli:test
+```
+
+## Contributing
+
+We welcome contributions to the Gravitino CLI!
+
+## License
+
+This project is licensed under the Apache License 2.0.
diff --git a/clients/cli/bin/gcli.sh b/clients/cli/bin/gcli.sh
new file mode 100755
index 000000000..f0b36c107
--- /dev/null
+++ b/clients/cli/bin/gcli.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+#
+# 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.
+#
+
+java -jar ../../cli/build/libs/gravitino-cli-*-incubating-SNAPSHOT.jar "$@"
\ No newline at end of file
diff --git a/clients/cli/build.gradle.kts b/clients/cli/build.gradle.kts
new file mode 100644
index 000000000..9ce570ef8
--- /dev/null
+++ b/clients/cli/build.gradle.kts
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+plugins {
+  `maven-publish`
+  id("java")
+  id("idea")
+}
+
+dependencies {
+  implementation(libs.commons.cli.new)
+  implementation(libs.guava)
+  implementation(libs.slf4j.api)
+  implementation(libs.slf4j.simple)
+  implementation(project(":api"))
+  implementation(project(":clients:client-java"))
+  implementation(project(":common"))
+
+  testImplementation(libs.junit.jupiter.api)
+  testImplementation(libs.junit.jupiter.params)
+  testImplementation(libs.mockito.core)
+
+  testRuntimeOnly(libs.junit.jupiter.engine)
+}
+
+tasks.build {
+  dependsOn("javadoc")
+}
+
+tasks.clean {
+  delete("target")
+  delete("tmp")
+}
+
+tasks.jar {
+  manifest {
+    attributes["Main-Class"] = "org.apache.gravitino.cli.Main"
+  }
+  val dependencies = configurations
+    .runtimeClasspath
+    .get()
+    .map(::zipTree)
+  from(dependencies)
+  duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/CommandActions.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/CommandActions.java
new file mode 100644
index 000000000..1077f437c
--- /dev/null
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/CommandActions.java
@@ -0,0 +1,54 @@
+/*
+ * 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 java.util.HashSet;
+
+/**
+ * The {@code CommandActions} class defines a set of standard commands that 
can be used in the
+ * Gravitino CLI. It also can validate if a given command is a valid commands.
+ */
+public class CommandActions {
+  public static final String DETAILS = "details";
+  public static final String LIST = "list";
+  public static final String UPDATE = "update";
+  public static final String CREATE = "create";
+  public static final String DELETE = "delete";
+
+  private static final HashSet<String> VALID_COMMANDS = new HashSet<>();
+
+  static {
+    VALID_COMMANDS.add(DETAILS);
+    VALID_COMMANDS.add(LIST);
+    VALID_COMMANDS.add(UPDATE);
+    VALID_COMMANDS.add(CREATE);
+    VALID_COMMANDS.add(DELETE);
+  }
+
+  /**
+   * Checks if a given command is a valid command type.
+   *
+   * @param command The command to check.
+   * @return true if the command is valid, false otherwise.
+   */
+  public static boolean isValidCommand(String command) {
+    return VALID_COMMANDS.contains(command);
+  }
+}
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
new file mode 100644
index 000000000..66e679304
--- /dev/null
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/CommandEntities.java
@@ -0,0 +1,54 @@
+/*
+ * 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 java.util.HashSet;
+
+/**
+ * The {@code CommandEntities} class defines a set of standard entities that 
can be used in the
+ * Gravitino CLI. It also can validate if a given entity is a valid entity.
+ */
+public class CommandEntities {
+  public static final String METALAKE = "metalake";
+  public static final String CATALOG = "catalog";
+  public static final String SCHEMA = "schema";
+  public static final String TABLE = "table";
+  public static final String COLUMN = "column";
+
+  private static final HashSet<String> VALID_ENTITIES = new HashSet<>();
+
+  static {
+    VALID_ENTITIES.add(METALAKE);
+    VALID_ENTITIES.add(CATALOG);
+    VALID_ENTITIES.add(SCHEMA);
+    VALID_ENTITIES.add(TABLE);
+    VALID_ENTITIES.add(COLUMN);
+  }
+
+  /**
+   * Checks if a given command is a valid entity.
+   *
+   * @param entity The entity to check.
+   * @return true if the command is valid, false otherwise.
+   */
+  public static boolean isValidEntity(String entity) {
+    return VALID_ENTITIES.contains(entity);
+  }
+}
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
new file mode 100644
index 000000000..847b96516
--- /dev/null
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/* User friendly error messages. */
+public class ErrorMessages {
+  public static final String UNSUPPORTED_COMMAND = "Unsupported or unknown 
command.";
+  public static final String UNKNOWN_ENTITY = "Unknown entity.";
+  public static final String UNKNOWN_METALAKE = "Unknown metalake name.";
+  public static final String UNKNOWN_CATALOG = "Unknown catalog name.";
+  public static final String UNKNOWN_SCHEMA = "Unknown schema name.";
+  public static final String UNKNOWN_TABLE = "Unknown table name.";
+  public static final String MALFORMED_NAME = "Malformed entity name.";
+  public static final String MISSING_NAME = "Missing name.";
+}
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
new file mode 100644
index 000000000..32df42f48
--- /dev/null
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java
@@ -0,0 +1,118 @@
+/*
+ * 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 org.apache.commons.cli.CommandLine;
+
+/**
+ * Extracts different arts of a full name (dot seperated) from the command 
line input. This includes
+ * metalake, catalog, schema, and table names.
+ */
+public class FullName {
+  private final CommandLine line;
+  private String metalakeEnv;
+  private boolean matalakeSet = false;
+
+  /**
+   * Constructor for the {@code FullName} class.
+   *
+   * @param line The parsed command line arguments.
+   */
+  public FullName(CommandLine line) {
+    this.line = line;
+  }
+
+  /**
+   * Retrieves the metalake name from the command line options, the 
GRAVITINO_METALAKE environment
+   * variable.
+   *
+   * @return The metalake name, or null if not found.
+   */
+  public String getMetalakeName() {
+    // If specified on the command line use that
+    if (line.hasOption(GravitinoOptions.METALAKE)) {
+      return line.getOptionValue(GravitinoOptions.METALAKE);
+    }
+
+    // Cache the metalake environment variable
+    if (metalakeEnv == null && !matalakeSet) {
+      metalakeEnv = System.getenv("GRAVITINO_METALAKE");
+      matalakeSet = true;
+    }
+
+    // Check if the metalake name is set as an environment variable
+    if (metalakeEnv != null) {
+      return metalakeEnv;
+    }
+
+    return null;
+  }
+
+  /**
+   * Retrieves the catalog name from the second part of the full name option.
+   *
+   * @return The catalog name, or null if not found.
+   */
+  public String getCatalogName() {
+    return getNamePart(0);
+  }
+
+  /**
+   * Retrieves the schema name from the third part of the full name option.
+   *
+   * @return The schema name, or null if not found.
+   */
+  public String getSchemaName() {
+    return getNamePart(1);
+  }
+
+  /**
+   * Retrieves the table name from the fourth part of the full name option.
+   *
+   * @return The table name, or null if not found.
+   */
+  public String getTableName() {
+    return getNamePart(2);
+  }
+
+  /**
+   * Helper method to retrieve a specific part of the full name based on the 
position of the part.
+   *
+   * @param position The position of the name part in the full name string.
+   * @return The extracted part of the name, or {@code null} if the name part 
is missing or
+   *     malformed.
+   */
+  public String getNamePart(int position) {
+    /* Extract the name part from the full name if available. */
+    if (line.hasOption(GravitinoOptions.NAME)) {
+      String[] names = line.getOptionValue(GravitinoOptions.NAME).split("\\.");
+
+      if (names.length <= position) {
+        System.err.println(ErrorMessages.MALFORMED_NAME);
+        return null;
+      }
+
+      return names[position];
+    }
+
+    System.err.println(ErrorMessages.MISSING_NAME);
+    return null;
+  }
+}
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
new file mode 100644
index 000000000..e60c7662d
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java
@@ -0,0 +1,225 @@
+/*
+ * 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 org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+import org.apache.gravitino.cli.commands.CatalogDetails;
+import org.apache.gravitino.cli.commands.ClientVersion;
+import org.apache.gravitino.cli.commands.ListCatalogs;
+import org.apache.gravitino.cli.commands.ListColumns;
+import org.apache.gravitino.cli.commands.ListMetalakes;
+import org.apache.gravitino.cli.commands.ListSchema;
+import org.apache.gravitino.cli.commands.ListTables;
+import org.apache.gravitino.cli.commands.MetalakeDetails;
+import org.apache.gravitino.cli.commands.SchemaDetails;
+import org.apache.gravitino.cli.commands.ServerVersion;
+import org.apache.gravitino.cli.commands.TableDetails;
+
+/* Gravitino Command line */
+public class GravitinoCommandLine {
+
+  private final CommandLine line;
+  private final Options options;
+  private final String entity;
+  private final String command;
+  private String urlEnv;
+  private boolean urlSet = false;
+  private boolean ignore = false;
+
+  public static final String CMD = "gcli"; // recommended name
+  public static final String DEFAULT_URL = "http://localhost:8090";;
+
+  /**
+   * Gravitino Command line.
+   *
+   * @param line Parsed command line object.
+   * @param options Available options for the CLI.
+   * @param entity The entity to apply the command to e.g. metlake, catalog, 
schema, table etc etc.
+   * @param command The type of command to run i.e. list, details, update, 
delete, or create.
+   */
+  public GravitinoCommandLine(CommandLine line, Options options, String 
entity, String command) {
+    this.line = line;
+    this.options = options;
+    this.entity = entity;
+    this.command = command;
+  }
+
+  /** Handles the parsed command line arguments and executes the corresponding 
actions. */
+  public void handleCommandLine() {
+    /* Check if you should ignore client/version versions */
+    if (line.hasOption(GravitinoOptions.IGNORE)) {
+      ignore = true;
+    }
+
+    executeCommand();
+  }
+
+  /** Handles the parsed command line arguments and executes the corresponding 
actions. */
+  public void handleSimpleLine() {
+    /* Display command usage. */
+    if (line.hasOption(GravitinoOptions.HELP)) {
+      displayHelp(options);
+    }
+    /* Display Gravitino client version. */
+    else if (line.hasOption(GravitinoOptions.VERSION)) {
+      new ClientVersion(getUrl(), ignore).handle();
+    }
+    /* Display Gravitino server version. */
+    else if (line.hasOption(GravitinoOptions.SERVER)) {
+      new ServerVersion(getUrl(), ignore).handle();
+    }
+  }
+
+  /**
+   * Displays the help message for the command line tool.
+   *
+   * @param options The command options.
+   */
+  public static void displayHelp(Options options) {
+    HelpFormatter formatter = new HelpFormatter();
+    formatter.printHelp(CMD, options);
+  }
+
+  /** Executes the appropriate command based on the command type. */
+  private void executeCommand() {
+    if (entity.equals(CommandEntities.COLUMN)) {
+      handleColumnCommand();
+    } else if (entity.equals(CommandEntities.TABLE)) {
+      handleTableCommand();
+    } else if (entity.equals(CommandEntities.SCHEMA)) {
+      handleSchemaCommand();
+    } else if (entity.equals(CommandEntities.CATALOG)) {
+      handleCatalogCommand();
+    } else if (entity.equals(CommandEntities.METALAKE)) {
+      handleMetalakeCommand();
+    }
+  }
+
+  /**
+   * Handles the command execution for Metalakes based on command type and the 
command line options.
+   */
+  private void handleMetalakeCommand() {
+    String url = getUrl();
+    FullName name = new FullName(line);
+    String metalake = name.getMetalakeName();
+
+    if (CommandActions.DETAILS.equals(command)) {
+      new MetalakeDetails(url, ignore, metalake).handle();
+    } else if (CommandActions.LIST.equals(command)) {
+      new ListMetalakes(url, ignore).handle();
+    }
+  }
+
+  /**
+   * Handles the command execution for Catalogs based on command type and the 
command line options.
+   */
+  private void handleCatalogCommand() {
+    String url = getUrl();
+    FullName name = new FullName(line);
+    String metalake = name.getMetalakeName();
+
+    if (CommandActions.DETAILS.equals(command)) {
+      String catalog = name.getCatalogName();
+      new CatalogDetails(url, ignore, metalake, catalog).handle();
+    } else if (CommandActions.LIST.equals(command)) {
+      new ListCatalogs(url, ignore, metalake).handle();
+    }
+  }
+
+  /**
+   * Handles the command execution for Schemas based on command type and the 
command line options.
+   */
+  private void handleSchemaCommand() {
+    String url = getUrl();
+    FullName name = new FullName(line);
+    String metalake = name.getMetalakeName();
+    String catalog = name.getCatalogName();
+
+    if (CommandActions.DETAILS.equals(command)) {
+      String schema = name.getSchemaName();
+      new SchemaDetails(url, ignore, metalake, catalog, schema).handle();
+    } else if (CommandActions.LIST.equals(command)) {
+      new ListSchema(url, ignore, metalake, catalog).handle();
+    }
+  }
+
+  /**
+   * Handles the command execution for Tables based on command type and the 
command line options.
+   */
+  private void handleTableCommand() {
+    String url = getUrl();
+    FullName name = new FullName(line);
+    String metalake = name.getMetalakeName();
+    String catalog = name.getCatalogName();
+    String schema = name.getSchemaName();
+
+    if (CommandActions.DETAILS.equals(command)) {
+      String table = name.getTableName();
+      new TableDetails(url, ignore, metalake, catalog, schema, table).handle();
+    } else if (CommandActions.LIST.equals(command)) {
+      new ListTables(url, ignore, metalake, catalog, schema).handle();
+    }
+  }
+
+  /**
+   * Handles the command execution for Columns based on command type and the 
command line options.
+   */
+  private void handleColumnCommand() {
+    String url = getUrl();
+    FullName name = new FullName(line);
+    String metalake = name.getMetalakeName();
+    String catalog = name.getCatalogName();
+    String schema = name.getSchemaName();
+
+    if (CommandActions.LIST.equals(command)) {
+      String table = name.getTableName();
+      new ListColumns(url, ignore, metalake, catalog, schema, table).handle();
+    }
+  }
+
+  /**
+   * Retrieves the Gravitinno URL from the command line options or the 
GRAVITINO_URL environment
+   * variable.
+   *
+   * @return The Gravitinno URL, or null if not found.
+   */
+  public String getUrl() {
+    // If specified on the command line use that
+    if (line.hasOption(GravitinoOptions.URL)) {
+      return line.getOptionValue(GravitinoOptions.URL);
+    }
+
+    // Cache the Gravitino URL environment variable
+    if (urlEnv == null && !urlSet) {
+      urlEnv = System.getenv("GRAVITINO_URL");
+      urlSet = true;
+    }
+
+    // If set return the Gravitino URL environment variable
+    if (urlEnv != null) {
+      return urlEnv;
+    }
+
+    // Return the default localhost URL
+    return DEFAULT_URL;
+  }
+}
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
new file mode 100644
index 000000000..9f8d1579a
--- /dev/null
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java
@@ -0,0 +1,78 @@
+/*
+ * 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 org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+
+/* Gravitino Command line options */
+public class GravitinoOptions {
+  public static final String HELP = "help";
+  public static final String VERSION = "version";
+  public static final String SERVER = "server";
+  public static final String URL = "url";
+  public static final String NAME = "name";
+  public static final String METALAKE = "metalake";
+  public static final String IGNORE = "ignore";
+
+  /**
+   * Builds and returns the CLI options for Gravitino.
+   *
+   * @return Valid CLI command options.
+   */
+  public Options options() {
+    Options options = new Options();
+
+    // Add options using helper method to avoid repetition
+    options.addOption(createSimpleOption("h", HELP, "command help 
information"));
+    options.addOption(createSimpleOption("v", VERSION, "Gravitino client 
version"));
+    options.addOption(createSimpleOption("r", SERVER, "Gravitino server 
version"));
+    options.addOption(createArgOption("u", URL, "Gravitino URL (default: 
http://localhost:8090)"));
+    options.addOption(createArgOption("f", NAME, "full entity name (dot 
separated)"));
+    options.addOption(createArgOption("m", METALAKE, "Metalake name"));
+    options.addOption(createSimpleOption("i", IGNORE, "Ignore client/sever 
version check"));
+
+    return options;
+  }
+
+  /**
+   * Helper method to create an Option that does not require arguments.
+   *
+   * @param shortName The option name as a single letter
+   * @param longName The long option name.
+   * @param description The option description.
+   * @return The Option object.
+   */
+  public Option createSimpleOption(String shortName, String longName, String 
description) {
+    return new Option(shortName, longName, false, description);
+  }
+
+  /**
+   * Helper method to create an Option that requires an argument.
+   *
+   * @param shortName The option name as a single letter
+   * @param longName The long option name.
+   * @param description The option description.
+   * @return The Option object.
+   */
+  public Option createArgOption(String shortName, String longName, String 
description) {
+    return new Option(shortName, longName, true, description);
+  }
+}
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
new file mode 100644
index 000000000..68db676c5
--- /dev/null
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/Main.java
@@ -0,0 +1,100 @@
+/*
+ * 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 org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+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. */
+public class Main {
+
+  public static void main(String[] args) {
+    CommandLineParser parser = new DefaultParser();
+    Options options = new GravitinoOptions().options();
+
+    try {
+      CommandLine line = parser.parse(options, args);
+      String entity = resolveEntity(line);
+      String command = resolveCommand(line);
+      GravitinoCommandLine commandLine = new GravitinoCommandLine(line, 
options, entity, command);
+
+      if (entity != null && command != null) {
+        commandLine.handleCommandLine();
+      } else {
+        commandLine.handleSimpleLine();
+      }
+    } catch (ParseException exp) {
+      System.err.println("Error parsing command line: " + exp.getMessage());
+      GravitinoCommandLine.displayHelp(options);
+    }
+  }
+
+  /**
+   * Determines the command based on the command line input.
+   *
+   * @param line Parsed command line object.
+   * @return The command, one of 'details', 'list', 'create', 'delete' or 
'update'.
+   */
+  protected static String resolveCommand(CommandLine line) {
+
+    /* As the bare second argument. */
+    String[] args = line.getArgs();
+
+    if (args.length == 2) {
+      String action = args[1];
+      if (CommandActions.isValidCommand(action)) {
+        return action;
+      }
+    } else if (args.length == 1) {
+      return CommandActions.DETAILS; /* Default to 'details' command. */
+    } else if (args.length == 0) {
+      return null;
+    }
+
+    System.err.println(ErrorMessages.UNSUPPORTED_COMMAND);
+    return null;
+  }
+
+  /**
+   * Determines the entity to act upon based on the command line input.
+   *
+   * @param line Parsed command line object.
+   * @return The entity, e.g. metakalake, catalog, schema, table, etc.
+   */
+  protected static String resolveEntity(CommandLine line) {
+    /* As the bare first argument. */
+    String[] args = line.getArgs();
+
+    if (args.length >= 1) {
+      String entity = args[0];
+      if (CommandEntities.isValidEntity(entity)) {
+        return entity;
+      } else {
+        System.err.println(ErrorMessages.UNKNOWN_ENTITY);
+        return null;
+      }
+    }
+
+    return null;
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/AllMetalakeDetails.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/AllMetalakeDetails.java
new file mode 100644
index 000000000..91d5b1c1b
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/AllMetalakeDetails.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.cli.commands;
+
+import com.google.common.base.Joiner;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.gravitino.Metalake;
+import org.apache.gravitino.client.GravitinoAdminClient;
+
+public class AllMetalakeDetails extends Command {
+
+  /**
+   * Parameters needed to list all metalakes.
+   *
+   * @param url The URL of the Gravitino server.
+   * @param ignoreVersions If true don't check the client/server versions 
match.
+   */
+  public AllMetalakeDetails(String url, boolean ignoreVersions) {
+    super(url, ignoreVersions);
+  }
+
+  /** Displays the name and comment of all metalakes. */
+  public void handle() {
+    Metalake[] metalakes = new Metalake[0];
+    try {
+      GravitinoAdminClient client = buildAdminClient();
+      metalakes = client.listMetalakes();
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    List<String> metalakeDetails = new ArrayList<>();
+    for (int i = 0; i < metalakes.length; i++) {
+      metalakeDetails.add(metalakes[i].name() + "," + metalakes[i].comment());
+    }
+
+    String all = Joiner.on(System.lineSeparator()).join(metalakeDetails);
+
+    System.out.print(all.toString());
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CatalogDetails.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CatalogDetails.java
new file mode 100644
index 000000000..6df117d0c
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CatalogDetails.java
@@ -0,0 +1,70 @@
+/*
+ * 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.cli.ErrorMessages;
+import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+
+public class CatalogDetails extends Command {
+
+  protected final String metalake;
+  protected final String catalog;
+
+  /**
+   * 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 catalog The name of the catalog.
+   */
+  public CatalogDetails(String url, boolean ignoreVersions, String metalake, 
String catalog) {
+    super(url, ignoreVersions);
+    this.metalake = metalake;
+    this.catalog = catalog;
+  }
+
+  /** Displays the name and details of a specified catalog. */
+  public void handle() {
+    Catalog result = null;
+
+    try {
+      GravitinoClient client = buildClient(metalake);
+      result = client.loadCatalog(catalog);
+    } 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;
+    }
+
+    if (result != null) {
+      System.out.println(
+          result.name() + "," + result.type() + "," + result.provider() + "," 
+ result.comment());
+    }
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ClientVersion.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ClientVersion.java
new file mode 100644
index 000000000..6bfa41be4
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ClientVersion.java
@@ -0,0 +1,49 @@
+/*
+ * 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.client.GravitinoAdminClient;
+
+/** Displays the Gravitino client version. */
+public class ClientVersion extends Command {
+
+  /**
+   * Displays the client version.
+   *
+   * @param url The URL of the Gravitino server.
+   * @param ignoreVersions If true don't check the client/server versions 
match.
+   */
+  public ClientVersion(String url, boolean ignoreVersions) {
+    super(url, ignoreVersions);
+  }
+
+  /** Displays the client version. */
+  public void handle() {
+    String version = "unknown";
+    try {
+      GravitinoAdminClient client = buildAdminClient();
+      version = client.clientVersion().version();
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+    System.out.println("Apache Gravitino " + version);
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/Command.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/Command.java
new file mode 100644
index 000000000..040bd7141
--- /dev/null
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/Command.java
@@ -0,0 +1,72 @@
+/*
+ * 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.client.GravitinoAdminClient;
+import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+
+/* The base for all commands. */
+public abstract class Command {
+  private final String url;
+  private final boolean ignoreVersions;
+
+  /**
+   * Command constructor.
+   *
+   * @param url The URL of the Gravitino server.
+   * @param ignoreVersions If true don't check the client/server versions 
match.
+   */
+  public Command(String url, boolean ignoreVersions) {
+    this.url = url;
+    this.ignoreVersions = ignoreVersions;
+  }
+
+  /** All commands have a handle method to handle and run the required 
command. */
+  public abstract void handle();
+
+  /**
+   * Builds a {@link GravitinoClient} instance with the provided server URL 
and metalake.
+   *
+   * @param metalake The name of the metalake.
+   * @return A configured {@link GravitinoClient} instance.
+   * @throws NoSuchMetalakeException if the specified metalake does not exist.
+   */
+  protected GravitinoClient buildClient(String metalake) throws 
NoSuchMetalakeException {
+    if (ignoreVersions) {
+      return 
GravitinoClient.builder(url).withMetalake(metalake).withVersionCheckDisabled().build();
+    } else {
+      return GravitinoClient.builder(url).withMetalake(metalake).build();
+    }
+  }
+
+  /**
+   * Builds a {@link GravitinoAdminClient} instance with the server URL.
+   *
+   * @return A configured {@link GravitinoAdminClient} instance.
+   */
+  protected GravitinoAdminClient buildAdminClient() {
+    if (ignoreVersions) {
+      return 
GravitinoAdminClient.builder(url).withVersionCheckDisabled().build();
+    } else {
+      return GravitinoAdminClient.builder(url).build();
+    }
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java
new file mode 100644
index 000000000..48c27e822
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java
@@ -0,0 +1,62 @@
+/*
+ * 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 com.google.common.base.Joiner;
+import org.apache.gravitino.cli.ErrorMessages;
+import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+
+/* Lists all catalogs in a metalake. */
+public class ListCatalogs extends Command {
+
+  protected final String metalake;
+
+  /**
+   * Lists all catalogs 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.
+   */
+  public ListCatalogs(String url, boolean ignoreVersions, String metalake) {
+    super(url, ignoreVersions);
+    this.metalake = metalake;
+  }
+
+  /** Lists all catalogs in a metalake. */
+  public void handle() {
+    String[] catalogs = new String[0];
+    try {
+      GravitinoClient client = buildClient(metalake);
+      catalogs = client.listCatalogs();
+    } catch (NoSuchMetalakeException err) {
+      System.err.println(ErrorMessages.UNKNOWN_METALAKE);
+      return;
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    String all = Joiner.on(",").join(catalogs);
+
+    System.out.println(all.toString());
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListColumns.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListColumns.java
new file mode 100644
index 000000000..4b352b557
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListColumns.java
@@ -0,0 +1,88 @@
+/*
+ * 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.rel.Column;
+
+/** Displays the details of a table's columns. */
+public class ListColumns extends TableCommand {
+
+  protected final String schema;
+  protected final String table;
+
+  /**
+   * Displays the details of a table's columns.
+   *
+   * @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 schenma.
+   * @param table The name of the table.
+   */
+  public ListColumns(
+      String url,
+      boolean ignoreVersions,
+      String metalake,
+      String catalog,
+      String schema,
+      String table) {
+    super(url, ignoreVersions, metalake, catalog);
+    this.schema = schema;
+    this.table = table;
+  }
+
+  /** Displays the details of a table's columns. */
+  public void handle() {
+    Column[] columns = null;
+
+    try {
+      NameIdentifier name = NameIdentifier.of(schema, table);
+      columns = tableCatalog().loadTable(name).columns();
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    StringBuilder all = new StringBuilder();
+    for (int i = 0; i < columns.length; i++) {
+      String name = columns[i].name();
+      String dataType = columns[i].dataType().simpleString();
+      String comment = columns[i].comment();
+      String nullable = columns[i].nullable() ? "null" : "";
+      String autoIncrement = columns[i].autoIncrement() ? "auto" : "";
+      // TODO default values
+      all.append(
+          name
+              + ","
+              + dataType
+              + ","
+              + comment
+              + ","
+              + nullable
+              + ","
+              + autoIncrement
+              + System.lineSeparator());
+    }
+
+    System.out.print(all.toString());
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListMetalakes.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListMetalakes.java
new file mode 100644
index 000000000..e218bea2a
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListMetalakes.java
@@ -0,0 +1,61 @@
+/*
+ * 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 com.google.common.base.Joiner;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.gravitino.Metalake;
+import org.apache.gravitino.client.GravitinoAdminClient;
+
+/** Lists all metalakes. */
+public class ListMetalakes extends Command {
+
+  /**
+   * List all metalakes.
+   *
+   * @param url The URL of the Gravitino server.
+   * @param ignoreVersions If true don't check the client/server versions 
match.
+   */
+  public ListMetalakes(String url, boolean ignoreVersions) {
+    super(url, ignoreVersions);
+  }
+
+  /** Lists all metalakes. */
+  public void handle() {
+    Metalake[] metalakes = new Metalake[0];
+    try {
+      GravitinoAdminClient client = buildAdminClient();
+      metalakes = client.listMetalakes();
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    List<String> metalakeNames = new ArrayList<>();
+    for (int i = 0; i < metalakes.length; i++) {
+      metalakeNames.add(metalakes[i].name());
+    }
+
+    String all = Joiner.on(System.lineSeparator()).join(metalakeNames);
+
+    System.out.println(all.toString());
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java
new file mode 100644
index 000000000..a4d18a924
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java
@@ -0,0 +1,69 @@
+/*
+ * 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 com.google.common.base.Joiner;
+import org.apache.gravitino.cli.ErrorMessages;
+import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+
+/** List all schema names in a schema. */
+public class ListSchema extends Command {
+
+  protected final String metalake;
+  protected final String catalog;
+
+  /**
+   * Lists all schemas in 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 catalog The name of the catalog.
+   */
+  public ListSchema(String url, boolean ignoreVersions, String metalake, 
String catalog) {
+    super(url, ignoreVersions);
+    this.metalake = metalake;
+    this.catalog = catalog;
+  }
+
+  /** List all schema names in a schema. */
+  public void handle() {
+    String[] schemas = new String[0];
+    try {
+      GravitinoClient client = buildClient(metalake);
+      schemas = client.loadCatalog(catalog).asSchemas().listSchemas();
+    } 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;
+    }
+
+    String all = Joiner.on(",").join(schemas);
+
+    System.out.println(all.toString());
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListTables.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListTables.java
new file mode 100644
index 000000000..d1124c48b
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListTables.java
@@ -0,0 +1,68 @@
+/*
+ * 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 com.google.common.base.Joiner;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+
+/** List the names of all tables in a schema. */
+public class ListTables extends TableCommand {
+
+  protected final String schema;
+
+  /**
+   * List the names of all tables in a schema.
+   *
+   * @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 schenma.
+   */
+  public ListTables(
+      String url, boolean ignoreVersions, String metalake, String catalog, 
String schema) {
+    super(url, ignoreVersions, metalake, catalog);
+    this.schema = schema;
+  }
+
+  /** List the names of all tables in a schema. */
+  public void handle() {
+    NameIdentifier[] tables = null;
+    Namespace name = Namespace.of(schema);
+    try {
+      tables = tableCatalog().listTables(name);
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    List<String> tableNames = new ArrayList<>();
+    for (int i = 0; i < tables.length; i++) {
+      tableNames.add(tables[i].name());
+    }
+
+    String all = Joiner.on(System.lineSeparator()).join(tableNames);
+
+    System.out.println(all.toString());
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/MetalakeDetails.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/MetalakeDetails.java
new file mode 100644
index 000000000..46d61de8c
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/MetalakeDetails.java
@@ -0,0 +1,58 @@
+/*
+ * 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.cli.ErrorMessages;
+import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+
+/** Displays the details of a metalake. */
+public class MetalakeDetails extends Command {
+  protected final String metalake;
+
+  /**
+   * Displays metalake details.
+   *
+   * @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.
+   */
+  public MetalakeDetails(String url, boolean ignoreVersions, String metalake) {
+    super(url, ignoreVersions);
+    this.metalake = metalake;
+  }
+
+  /** Displays the name and comment of a metalake. */
+  public void handle() {
+    String comment = "";
+    try {
+      GravitinoClient client = buildClient(metalake);
+      comment = client.loadMetalake(metalake).comment();
+    } catch (NoSuchMetalakeException err) {
+      System.err.println(ErrorMessages.UNKNOWN_METALAKE);
+      return;
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    System.out.println(metalake + "," + comment);
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
new file mode 100644
index 000000000..9229d152d
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
@@ -0,0 +1,78 @@
+/*
+ * 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.Schema;
+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.NoSuchSchemaException;
+
+/** Displays the details of schema. */
+public class SchemaDetails extends Command {
+
+  protected final String metalake;
+  protected final String catalog;
+  protected final String schema;
+
+  /**
+   * Displays the details of a schema.
+   *
+   * @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 schenma.
+   */
+  public SchemaDetails(
+      String url, boolean ignoreVersions, String metalake, String catalog, 
String schema) {
+    super(url, ignoreVersions);
+    this.metalake = metalake;
+    this.catalog = catalog;
+    this.schema = schema;
+  }
+
+  /** Displays the name and comments of schema. */
+  public void handle() {
+    Schema result = null;
+
+    try {
+      GravitinoClient client = buildClient(metalake);
+      result = client.loadCatalog(catalog).asSchemas().loadSchema(schema);
+    } 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 (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    if (result != null) {
+      System.out.println(result.name() + "," + result.comment());
+    }
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ServerVersion.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ServerVersion.java
new file mode 100644
index 000000000..456226732
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ServerVersion.java
@@ -0,0 +1,50 @@
+/*
+ * 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.client.GravitinoAdminClient;
+
+/** Displays the Gravitino server version. */
+public class ServerVersion extends Command {
+
+  /**
+   * Displays the server version.
+   *
+   * @param url The URL of the Gravitino server.
+   * @param ignoreVersions If true don't check the client/server versions 
match.
+   */
+  public ServerVersion(String url, boolean ignoreVersions) {
+    super(url, ignoreVersions);
+  }
+
+  /** Displays the server version. */
+  public void handle() {
+    String version = "unknown";
+    try {
+      GravitinoAdminClient client = buildAdminClient();
+      version = client.serverVersion().version();
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    System.out.println("Apache Gravitino " + version);
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TableCommand.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TableCommand.java
new file mode 100644
index 000000000..cecff12f4
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TableCommand.java
@@ -0,0 +1,76 @@
+/*
+ * 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.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.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NoSuchTableException;
+import org.apache.gravitino.rel.TableCatalog;
+
+/* Common code for all table commands. */
+public class TableCommand extends Command {
+
+  protected final String metalake;
+  protected final String catalog;
+
+  /**
+   * Common code for all table commands.
+   *
+   * @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.
+   */
+  public TableCommand(String url, boolean ignoreVersions, String metalake, 
String catalog) {
+    super(url, ignoreVersions);
+    this.metalake = metalake;
+    this.catalog = catalog;
+  }
+
+  /* Overridden in parent - do nothing  */
+  public void handle() {}
+
+  /**
+   * Returns the table catalog for a given metalake and catalog.
+   *
+   * @return The TableCatalog or null if an error occurs.
+   */
+  public TableCatalog tableCatalog() {
+    try {
+      GravitinoClient client = buildClient(metalake);
+      return 
client.loadMetalake(metalake).loadCatalog(catalog).asTableCatalog();
+    } catch (NoSuchMetalakeException err) {
+      System.err.println(ErrorMessages.UNKNOWN_METALAKE);
+    } catch (NoSuchCatalogException err) {
+      System.err.println(ErrorMessages.UNKNOWN_CATALOG);
+    } catch (NoSuchSchemaException err) {
+      System.err.println(ErrorMessages.UNKNOWN_SCHEMA);
+    } catch (NoSuchTableException err) {
+      System.err.println(ErrorMessages.UNKNOWN_TABLE);
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+    }
+
+    return null;
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TableDetails.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TableDetails.java
new file mode 100644
index 000000000..fb3158529
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TableDetails.java
@@ -0,0 +1,67 @@
+/*
+ * 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.rel.Table;
+
+/** Displays the details of a table. */
+public class TableDetails extends TableCommand {
+
+  protected final String schema;
+  protected final String table;
+
+  /**
+   * Displays the details of a table.
+   *
+   * @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 schenma.
+   * @param table The name of the table.
+   */
+  public TableDetails(
+      String url,
+      boolean ignoreVersions,
+      String metalake,
+      String catalog,
+      String schema,
+      String table) {
+    super(url, ignoreVersions, metalake, catalog);
+    this.schema = schema;
+    this.table = table;
+  }
+
+  /** Displays the details of a table. */
+  public void handle() {
+    Table gTable = null;
+
+    try {
+      NameIdentifier name = NameIdentifier.of(table);
+      gTable = tableCatalog().loadTable(name);
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    System.out.println(gTable.name() + "," + gTable.comment());
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandActions.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandActions.java
new file mode 100644
index 000000000..a79f9961f
--- /dev/null
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandActions.java
@@ -0,0 +1,69 @@
+/*
+ * 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.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class TestCommandActions {
+
+  @Test
+  public void ValidCommands() {
+    assertTrue(
+        CommandActions.isValidCommand(CommandActions.DETAILS), "DETAILS should 
be a valid command");
+    assertTrue(
+        CommandActions.isValidCommand(CommandActions.LIST), "LIST should be a 
valid command");
+    assertTrue(
+        CommandActions.isValidCommand(CommandActions.UPDATE), "UPDATE should 
be a valid command");
+    assertTrue(
+        CommandActions.isValidCommand(CommandActions.CREATE), "CREATE should 
be a valid command");
+    assertTrue(
+        CommandActions.isValidCommand(CommandActions.DELETE), "DELETE should 
be a valid command");
+  }
+
+  @Test
+  public void invalidCommand() {
+    assertFalse(
+        CommandActions.isValidCommand("invalidCommand"), "An invalid command 
should return false");
+  }
+
+  @Test
+  public void nullCommand() {
+    assertFalse(
+        CommandActions.isValidCommand(null),
+        "Null should return false as it's not a valid command");
+  }
+
+  @Test
+  public void emptyCommand() {
+    assertFalse(
+        CommandActions.isValidCommand(""),
+        "Empty string should return false as it's not a valid command");
+  }
+
+  @Test
+  public void caseSensitive() {
+    assertFalse(
+        CommandActions.isValidCommand("DETAILS".toUpperCase()),
+        "Commands should be case-sensitive");
+  }
+}
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
new file mode 100644
index 000000000..423834bfc
--- /dev/null
+++ 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandEntities.java
@@ -0,0 +1,69 @@
+/*
+ * 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.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class TestCommandEntities {
+
+  @Test
+  public void validEntities() {
+    assertTrue(
+        CommandEntities.isValidEntity(CommandEntities.METALAKE),
+        "METALAKE should be a valid entity");
+    assertTrue(
+        CommandEntities.isValidEntity(CommandEntities.CATALOG), "CATALOG 
should be a valid entity");
+    assertTrue(
+        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.COLUMN), "COLUMN should 
be a valid entity");
+  }
+
+  @Test
+  public void invalidEntity() {
+    assertFalse(
+        CommandEntities.isValidEntity("invalidEntity"), "An invalid command 
should return false");
+  }
+
+  @Test
+  public void nullEntity() {
+    assertFalse(
+        CommandEntities.isValidEntity(null), "Null should return false as it's 
not a valid entity");
+  }
+
+  @Test
+  public void emptyEntity() {
+    assertFalse(
+        CommandEntities.isValidEntity(""),
+        "Empty string should return false as it's not a valid entity");
+  }
+
+  @Test
+  public void caseSensitive() {
+    assertFalse(
+        CommandEntities.isValidEntity("DETAILS".toUpperCase()),
+        "Entities should be case-sensitive");
+  }
+}
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
new file mode 100644
index 000000000..a53778437
--- /dev/null
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestFulllName.java
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.Options;
+import org.apache.gravitino.cli.FullName;
+import org.apache.gravitino.cli.GravitinoOptions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class TestFulllName {
+
+  private Options options;
+
+  @BeforeEach
+  public void setUp() {
+    options = new GravitinoOptions().options();
+  }
+
+  @Test
+  public void entityFromFullNameOption() throws Exception {
+    String[] args = {"--metalake", "metalakeA", "--name", 
"catalogB.schemaC.tableD"};
+    CommandLine commandLine = new DefaultParser().parse(options, args);
+    FullName fullName = new FullName(commandLine);
+
+    String metalakeName = fullName.getMetalakeName();
+    assertEquals("metalakeA", metalakeName);
+    String catalogName = fullName.getCatalogName();
+    assertEquals("catalogB", catalogName);
+    String schemaName = fullName.getSchemaName();
+    assertEquals("schemaC", schemaName);
+    String tableName = fullName.getTableName();
+    assertEquals("tableD", tableName);
+  }
+
+  @Test
+  public void entityNotFound() throws Exception {
+    String[] args = {};
+    CommandLine commandLine = new DefaultParser().parse(options, args);
+    FullName fullName = new FullName(commandLine);
+
+    String metalakeName = fullName.getMetalakeName();
+    assertNull(metalakeName);
+  }
+
+  @Test
+  public void malformedName() throws Exception {
+    String[] args = {"--name", "metalake.catalog"};
+    CommandLine commandLine = new DefaultParser().parse(options, args);
+    FullName fullName = new FullName(commandLine);
+    String tableName = fullName.getTableName();
+    assertNull(tableName);
+  }
+
+  @Test
+  public void missingName() throws Exception {
+    String[] args = {}; // No name provided
+    CommandLine commandLine = new DefaultParser().parse(options, args);
+    FullName fullName = new FullName(commandLine);
+
+    String namePart = fullName.getNamePart(3);
+    assertNull(namePart);
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestGravitinoOptions.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestGravitinoOptions.java
new file mode 100644
index 000000000..6fd12276d
--- /dev/null
+++ 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestGravitinoOptions.java
@@ -0,0 +1,59 @@
+/*
+ * 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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.commons.cli.Option;
+import org.junit.jupiter.api.Test;
+
+public class TestGravitinoOptions {
+
+  @Test
+  public void testCreateSimpleOption() {
+    GravitinoOptions gravitinoOptions = new GravitinoOptions();
+    Option helpOption =
+        gravitinoOptions.createSimpleOption("h", GravitinoOptions.HELP, "help 
message");
+
+    assertEquals("h", helpOption.getOpt(), "Simple option short name should be 
'h'.");
+    assertEquals("help", helpOption.getLongOpt(), "Simple option long name 
should be 'help'.");
+    assertFalse(helpOption.hasArg(), "Simple option should not require an 
argument.");
+    assertEquals(
+        "help message",
+        helpOption.getDescription(),
+        "Simple option should have correct description.");
+  }
+
+  @Test
+  public void testCreateArgOption() {
+    GravitinoOptions gravitinoOptions = new GravitinoOptions();
+    Option urlOption = gravitinoOptions.createArgOption("u", 
GravitinoOptions.URL, "url argument");
+
+    assertEquals("u", urlOption.getOpt(), "Argument option short name should 
be 'u'.");
+    assertEquals("url", urlOption.getLongOpt(), "Argument option long name 
should be 'url'.");
+    assertTrue(urlOption.hasArg(), "Argument option should require an 
argument.");
+    assertEquals(
+        "url argument",
+        urlOption.getDescription(),
+        "Argument option should have correct description.");
+  }
+}
diff --git a/clients/cli/src/test/java/org/apache/gravitino/cli/TestMain.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestMain.java
new file mode 100644
index 000000000..388d5da73
--- /dev/null
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestMain.java
@@ -0,0 +1,120 @@
+/*
+ * 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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class TestMain {
+
+  private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+  private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();
+  private final PrintStream originalOut = System.out;
+  private final PrintStream originalErr = System.err;
+
+  @BeforeEach
+  public void setUpStreams() {
+    System.setOut(new PrintStream(outContent));
+    System.setErr(new PrintStream(errContent));
+  }
+
+  @AfterEach
+  public void restoreStreams() {
+    System.setOut(originalOut);
+    System.setErr(originalErr);
+  }
+
+  @Test
+  public void withTwoArgsOnly() throws ParseException {
+    Options options = new GravitinoOptions().options();
+    CommandLineParser parser = new DefaultParser();
+    String[] args = {"metalake", "details"};
+    CommandLine line = parser.parse(options, args);
+
+    String command = Main.resolveCommand(line);
+    assertEquals(CommandActions.DETAILS, command);
+    String entity = Main.resolveEntity(line);
+    assertEquals(CommandEntities.METALAKE, entity);
+  }
+
+  @Test
+  public void defaultToDetailsOneArg() throws ParseException {
+    Options options = new GravitinoOptions().options();
+    CommandLineParser parser = new DefaultParser();
+    String[] args = {"metalake"};
+    CommandLine line = parser.parse(options, args);
+
+    String command = Main.resolveCommand(line);
+    assertEquals(CommandActions.DETAILS, command);
+    String entity = Main.resolveEntity(line);
+    assertEquals(CommandEntities.METALAKE, entity);
+  }
+
+  @Test
+  public void withNoArgs() throws ParseException {
+    Options options = new GravitinoOptions().options();
+    CommandLineParser parser = new DefaultParser();
+    String[] args = {};
+    CommandLine line = parser.parse(options, args);
+
+    String command = Main.resolveCommand(line);
+    assertNull(command);
+    String entity = Main.resolveEntity(line);
+    assertNull(entity);
+  }
+
+  @Test
+  @SuppressWarnings("DefaultCharset")
+  public void withHelpOption() throws ParseException, 
UnsupportedEncodingException {
+    Options options = new GravitinoOptions().options();
+    CommandLineParser parser = new DefaultParser();
+    String[] args = {"--help"};
+    CommandLine line = parser.parse(options, args);
+
+    GravitinoCommandLine commandLine = new GravitinoCommandLine(line, options, 
null, "help");
+    commandLine.handleSimpleLine();
+
+    assertTrue(outContent.toString().contains("usage:")); // Expected help 
output
+  }
+
+  @Test
+  @SuppressWarnings("DefaultCharset")
+  public void parseError() throws UnsupportedEncodingException {
+    String[] args = {"--invalidOption"};
+
+    Main.main(args);
+
+    assertTrue(errContent.toString().contains("Error parsing command line")); 
// Expect error
+    assertTrue(outContent.toString().contains("usage:")); // Expect help output
+  }
+}
diff --git a/docs/cli.md b/docs/cli.md
new file mode 100644
index 000000000..98a311fef
--- /dev/null
+++ b/docs/cli.md
@@ -0,0 +1,155 @@
+---
+title: 'Apache Gravitino Command Line Interface'
+slug: /cli
+keyword: cli
+last_update:
+  date: 2024-10-23
+  author: justinmclean
+license: 'This software is licensed under the Apache License version 2.'
+---
+
+This document primarily outlines how users can manage metadata within Apache 
Gravitino using the Command Line Interface (CLI). The CLI is accessible via a 
terminal window as an alternative to writing code or using the REST interface.
+
+Currently, you can view basic metadata information for metalakes, catalogs, 
schema, and tables. The ability to create, update, and delete metalakes and 
support additional entities in planned in the near future.
+
+## Running the CLI
+
+You can set up an alias for the command like so:
+
+```bash
+alias gcli='java -jar 
../../cli/build/libs/gravitino-cli-*-incubating-SNAPSHOT.jar'
+```
+
+Or use the `gcli.sh` script found in the `clients/cli/bin/` directory to run 
the CLI.
+
+## Usage
+
+ To run the Gravitino CLI, use the following command structure:
+
+ ```bash
+ usage: gcli [metalake|catalog|schema|table] 
[list|details|create|delete|update] [options]
+ Options
+ -f,--name <arg>       full entity name (dot separated)
+ -h,--help             command help information
+ -i,--ignore           Ignore client/sever version check
+ -m,--metalake <arg>   Metalake name
+ -r,--server           Gravitino server version
+ -u,--url <arg>        Gravitino URL (default: http://localhost:8090)
+ -v,--version          Gravitino client version
+ ```
+
+## Commands
+
+The following commands are available for entity management:
+
+- list: List available entities
+- details: Show detailed information about an entity
+- create: Create a new entity
+- delete: Delete an existing entity
+- update: Update an existing entity
+
+### Setting the Metalake name
+
+As dealing with one Metalake is a typical scenario, you can set the Metalake 
name in several ways.
+
+1. Passed in on the command line via the `--metalake` parameter.
+2. Set via the `GRAVITINO_METALAKE` environment variable.
+
+The command line option overrides the environment variable.
+
+## Setting the Gravitino URL
+
+As you need to set the Gravitino URL for every command, you can set the URL in 
several ways.
+
+1. Passed in on the command line via the `--url` parameter.
+2. Set via the 'GRAVITINO_URL' environment variable.
+
+The command line option overrides the environment variable.
+
+## Manage metadata
+
+All the commands are performed by using the [Java API](api/java-api) 
internally.
+
+### Display help
+
+To display help on command usage:
+
+```bash
+gcli --help
+```
+
+### Display client version
+
+To display the client version:
+
+```bash
+gcli --version
+```
+
+### Display server version
+
+To display the server version:
+
+```bash
+gcli --server
+```
+
+### Client/server version mismatch
+
+If the client and server are running different versions of the Gravitino 
software then you need an additional `--ignore` option for the command to run.
+
+### Metalake
+
+#### Show all metalakes
+
+```bash
+gcli metalake list
+```
+
+#### Show a metalake details
+
+```bash
+gcli metalake details --metalake metalake_demo 
+```
+
+### Catalog
+
+#### Show all catalogs in a metalake
+
+```bash
+gcli catalog list --metalake metalake_demo
+```
+
+#### Show a catalogs details
+
+```bash
+gcli catalog details --metalake metalake_demo --name catalog_postgres
+```
+
+### Schema
+
+#### Show all schemas in a catalog
+
+```bash
+gcli schema list --metalake metalake_demo --name catalog_postgres
+```
+
+#### Show a schema details
+
+```bash
+gcli schema details --metalake metalake_demo --name catalog_postgres.hr
+```
+
+### Table
+
+#### Show all tables
+
+```bash
+gcli table list --metalake metalake_demo --name catalog_postgres.hr
+```
+
+#### Show tables details
+
+```bash
+gcli column list --metalake metalake_demo --name 
catalog_postgres.hr.departments
+```
diff --git a/docs/index.md b/docs/index.md
index f2d7584e1..2e683453b 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -61,7 +61,10 @@ Also, you can find the complete REST API definition in
 [Gravitino Open API](./api/rest/gravitino-rest-api),
 Java SDK definition in [Gravitino Java 
doc](pathname:///docs/0.7.0-incubating-SNAPSHOT/api/java/index.html).
 
-Gravitino provides a web UI to manage the metadata. Visit the web UI in the 
browser via `http://<ip-address>:8090`. See [Gravitino web UI](./webui.md) for 
details.
+Gravitino also provides a web UI to manage the metadata. Visit the web UI in 
the browser via `http://<ip-address>:8090`.
+See [Gravitino web UI](./webui.md) for details.
+
+Gravitino also provides a Command Line Interface (CLI) to manage the metadata. 
See [Gravitino CLI](./cli.md) for details.
 
 Gravitino currently supports the following catalogs:
 
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 6a50fc2b4..472be136c 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -93,6 +93,7 @@ shadow-plugin = "8.1.1"
 kerby = "2.0.3"
 node-plugin = "7.0.1"
 commons-cli = "1.2"
+commons-cli-new = "1.9.0"
 sun-activation-version = "1.2.0"
 error-prone = "3.1.0"
 woodstox-core = "5.3.0"
@@ -122,6 +123,7 @@ junit-jupiter-api = { group = "org.junit.jupiter", name = 
"junit-jupiter-api", v
 junit-jupiter-params = { group = "org.junit.jupiter", name = 
"junit-jupiter-params", version.ref = "junit" }
 junit-jupiter-engine = { group = "org.junit.jupiter", name = 
"junit-jupiter-engine"}
 slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
+slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = 
"slf4j" }
 slf4j-jdk14 = { group = "org.slf4j", name = "slf4j-jdk14", version = "1.7.30" }
 log4j-slf4j2-impl = { group = "org.apache.logging.log4j", name = 
"log4j-slf4j2-impl", version.ref = "log4j" }
 log4j-api = { group = "org.apache.logging.log4j", name = "log4j-api", 
version.ref = "log4j" }
@@ -217,6 +219,7 @@ postgresql-driver = { group = "org.postgresql", name = 
"postgresql", version.ref
 minikdc = { group = "org.apache.hadoop", name = "hadoop-minikdc", version.ref 
= "hadoop-minikdc"}
 immutables-value = { module = "org.immutables:value", version.ref = 
"immutables-value" }
 commons-cli = { group = "commons-cli", name = "commons-cli", version.ref = 
"commons-cli" }
+commons-cli-new = { group = "commons-cli", name = "commons-cli", version.ref = 
"commons-cli-new" }
 sun-activation = { group = "com.sun.activation", name = "javax.activation", 
version.ref = "sun-activation-version" }
 kafka-clients = { group = "org.apache.kafka", name = "kafka-clients", 
version.ref = "kafka" }
 kafka = { group = "org.apache.kafka", name = "kafka_2.12", version.ref = 
"kafka" }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 6d08431f0..2eb340baa 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -46,7 +46,8 @@ include(
   "clients:client-java-runtime",
   "clients:filesystem-hadoop3",
   "clients:filesystem-hadoop3-runtime",
-  "clients:client-python"
+  "clients:client-python",
+  "clients:cli"
 )
 include("iceberg:iceberg-common")
 include("iceberg:iceberg-rest-server")


Reply via email to