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

nanda pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 8526f2e81f HDDS-11826. Interactive mode for ozone shell. (#7515)
8526f2e81f is described below

commit 8526f2e81f73b45cc6701ccb750fcb42c8823025
Author: Doroszlai, Attila <[email protected]>
AuthorDate: Fri Dec 6 08:32:34 2024 +0100

    HDDS-11826. Interactive mode for ozone shell. (#7515)
---
 .../hadoop/hdds/cli/ExtensibleParentCommand.java   |  2 +-
 .../org/apache/hadoop/hdds/cli/GenericCli.java     | 11 ++-
 hadoop-hdds/tools/pom.xml                          | 16 ++--
 hadoop-ozone/dist/src/main/license/bin/LICENSE.txt |  2 +
 hadoop-ozone/dist/src/main/license/jar-report.txt  |  2 +
 hadoop-ozone/tools/pom.xml                         | 27 +++++--
 .../reconfig/AbstractReconfigureSubCommand.java    |  1 +
 .../org/apache/hadoop/ozone/shell/Handler.java     |  2 +-
 .../org/apache/hadoop/ozone/shell/OzoneRatis.java  |  3 +-
 .../java/org/apache/hadoop/ozone/shell/REPL.java   | 90 ++++++++++++++++++++++
 .../java/org/apache/hadoop/ozone/shell/Shell.java  | 45 ++++++++++-
 pom.xml                                            | 11 +++
 12 files changed, 189 insertions(+), 23 deletions(-)

diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/cli/ExtensibleParentCommand.java
 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/cli/ExtensibleParentCommand.java
index 45bbb44005..d4fde1b75c 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/cli/ExtensibleParentCommand.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/cli/ExtensibleParentCommand.java
@@ -42,7 +42,7 @@ public interface ExtensibleParentCommand {
       ServiceLoader<?> subcommands = 
ServiceLoader.load(parentCommand.subcommandType());
       for (Object subcommand : subcommands) {
         final CommandLine.Command commandAnnotation = 
subcommand.getClass().getAnnotation(CommandLine.Command.class);
-        CommandLine subcommandCommandLine = new CommandLine(subcommand);
+        CommandLine subcommandCommandLine = new CommandLine(subcommand, 
cli.getFactory());
         cli.addSubcommand(commandAnnotation.name(), subcommandCommandLine);
       }
     }
diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/cli/GenericCli.java 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/cli/GenericCli.java
index a8ff931b23..a64c4bd84a 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/cli/GenericCli.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/cli/GenericCli.java
@@ -56,15 +56,20 @@ public class GenericCli implements Callable<Void>, 
GenericParentCommand {
   }
 
   public GenericCli(Class<?> type) {
-    cmd = new CommandLine(this);
+    this(type, CommandLine.defaultFactory());
+  }
+
+  public GenericCli(Class<?> type, CommandLine.IFactory factory) {
+    cmd = new CommandLine(this, factory);
     cmd.setExecutionExceptionHandler((ex, commandLine, parseResult) -> {
       printError(ex);
       return EXECUTION_ERROR_EXIT_CODE;
     });
 
     if (type != null) {
-      addSubcommands(getCmd(), type);
+      addSubcommands(cmd, type);
     }
+
     ExtensibleParentCommand.addSubcommands(cmd);
   }
 
@@ -75,7 +80,7 @@ public class GenericCli implements Callable<Void>, 
GenericParentCommand {
       if (subcommand.getParentType().equals(type)) {
         final Command commandAnnotation =
             subcommand.getClass().getAnnotation(Command.class);
-        CommandLine subcommandCommandLine = new CommandLine(subcommand);
+        CommandLine subcommandCommandLine = new CommandLine(subcommand, 
cli.getFactory());
         addSubcommands(subcommandCommandLine, subcommand.getClass());
         cli.addSubcommand(commandAnnotation.name(), subcommandCommandLine);
       }
diff --git a/hadoop-hdds/tools/pom.xml b/hadoop-hdds/tools/pom.xml
index 583c801bcd..5b77f394c9 100644
--- a/hadoop-hdds/tools/pom.xml
+++ b/hadoop-hdds/tools/pom.xml
@@ -179,20 +179,20 @@ https://maven.apache.org/xsd/maven-4.0.0.xsd";>
         <artifactId>maven-compiler-plugin</artifactId>
         <configuration>
           <annotationProcessorPaths>
-            <path>
-              <groupId>org.apache.ozone</groupId>
-              <artifactId>hdds-config</artifactId>
-              <version>${hdds.version}</version>
-            </path>
             <path>
               <groupId>org.kohsuke.metainf-services</groupId>
               <artifactId>metainf-services</artifactId>
               <version>${metainf-services.version}</version>
             </path>
+            <path>
+              <groupId>info.picocli</groupId>
+              <artifactId>picocli-codegen</artifactId>
+              <version>${picocli.version}</version>
+            </path>
           </annotationProcessorPaths>
           <annotationProcessors>
-            
<annotationProcessor>org.apache.hadoop.hdds.conf.ConfigFileGenerator</annotationProcessor>
             
<annotationProcessor>org.kohsuke.metainf_services.AnnotationProcessorImpl</annotationProcessor>
+            
<annotationProcessor>picocli.codegen.aot.graalvm.processor.NativeImageConfigGeneratorProcessor</annotationProcessor>
           </annotationProcessors>
         </configuration>
       </plugin>
@@ -207,8 +207,10 @@ https://maven.apache.org/xsd/maven-4.0.0.xsd";>
                 <restrictImports>
                   <reason>Only selected annotation processors are enabled, see 
configuration of maven-compiler-plugin.</reason>
                   <bannedImports>
-                    
<bannedImport>org.apache.hadoop.ozone.om.request.validation.RequestFeatureValidator</bannedImport>
+                    
<bannedImport>org.apache.hadoop.hdds.conf.Config</bannedImport>
+                    
<bannedImport>org.apache.hadoop.hdds.conf.ConfigGroup</bannedImport>
                     
<bannedImport>org.apache.hadoop.hdds.scm.metadata.Replicate</bannedImport>
+                    
<bannedImport>org.apache.hadoop.ozone.om.request.validation.RequestFeatureValidator</bannedImport>
                   </bannedImports>
                 </restrictImports>
               </rules>
diff --git a/hadoop-ozone/dist/src/main/license/bin/LICENSE.txt 
b/hadoop-ozone/dist/src/main/license/bin/LICENSE.txt
index 1c70fa0401..b291afc568 100644
--- a/hadoop-ozone/dist/src/main/license/bin/LICENSE.txt
+++ b/hadoop-ozone/dist/src/main/license/bin/LICENSE.txt
@@ -315,6 +315,7 @@ Apache License 2.0
    commons-validator:commons-validator
    commons-fileupload:commons-fileupload
    info.picocli:picocli
+   info.picocli:picocli-shell-jline3
    io.dropwizard.metrics:metrics-core
    io.grpc:grpc-api
    io.grpc:grpc-context
@@ -479,6 +480,7 @@ BSD 3-Clause
    com.google.re2j:re2j
    com.jcraft:jsch
    com.thoughtworks.paranamer:paranamer
+   org.jline:jline3
    org.ow2.asm:asm
    org.ow2.asm:asm-analysis
    org.ow2.asm:asm-commons
diff --git a/hadoop-ozone/dist/src/main/license/jar-report.txt 
b/hadoop-ozone/dist/src/main/license/jar-report.txt
index e6d9a1d2a2..be48c1d1fe 100644
--- a/hadoop-ozone/dist/src/main/license/jar-report.txt
+++ b/hadoop-ozone/dist/src/main/license/jar-report.txt
@@ -151,6 +151,7 @@ share/ozone/lib/jgrapht-core.jar
 share/ozone/lib/jgrapht-ext.jar
 share/ozone/lib/jgraphx.jar
 share/ozone/lib/jheaps.jar
+share/ozone/lib/jline.jar
 share/ozone/lib/jmespath-java.jar
 share/ozone/lib/jna.jar
 share/ozone/lib/jna-platform.jar
@@ -236,6 +237,7 @@ share/ozone/lib/ozone-s3gateway.jar
 share/ozone/lib/ozone-tools.jar
 share/ozone/lib/perfmark-api.jar
 share/ozone/lib/picocli.jar
+share/ozone/lib/picocli-shell-jline3.jar
 share/ozone/lib/protobuf-java.jar
 share/ozone/lib/protobuf-java.jar
 share/ozone/lib/protobuf-java-util.jar
diff --git a/hadoop-ozone/tools/pom.xml b/hadoop-ozone/tools/pom.xml
index 9244081225..8ea8ded01c 100644
--- a/hadoop-ozone/tools/pom.xml
+++ b/hadoop-ozone/tools/pom.xml
@@ -173,6 +173,14 @@ https://maven.apache.org/xsd/maven-4.0.0.xsd";>
       <groupId>info.picocli</groupId>
       <artifactId>picocli</artifactId>
     </dependency>
+    <dependency>
+      <groupId>info.picocli</groupId>
+      <artifactId>picocli-shell-jline3</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.jline</groupId>
+      <artifactId>jline</artifactId>
+    </dependency>
     <dependency>
       <groupId>jakarta.xml.bind</groupId>
       <artifactId>jakarta.xml.bind-api</artifactId>
@@ -275,21 +283,24 @@ https://maven.apache.org/xsd/maven-4.0.0.xsd";>
         <artifactId>maven-compiler-plugin</artifactId>
         <configuration>
           <annotationProcessorPaths>
-            <path>
-              <groupId>org.apache.ozone</groupId>
-              <artifactId>hdds-config</artifactId>
-              <version>${hdds.version}</version>
-            </path>
             <path>
               <groupId>org.kohsuke.metainf-services</groupId>
               <artifactId>metainf-services</artifactId>
               <version>${metainf-services.version}</version>
             </path>
+            <path>
+              <groupId>info.picocli</groupId>
+              <artifactId>picocli-codegen</artifactId>
+              <version>${picocli.version}</version>
+            </path>
           </annotationProcessorPaths>
           <annotationProcessors>
-            
<annotationProcessor>org.apache.hadoop.hdds.conf.ConfigFileGenerator</annotationProcessor>
             
<annotationProcessor>org.kohsuke.metainf_services.AnnotationProcessorImpl</annotationProcessor>
+            
<annotationProcessor>picocli.codegen.aot.graalvm.processor.NativeImageConfigGeneratorProcessor</annotationProcessor>
           </annotationProcessors>
+          <compilerArgs>
+            <arg>-Aproject=${project.groupId}/${project.artifactId}</arg>
+          </compilerArgs>
         </configuration>
       </plugin>
       <plugin>
@@ -303,8 +314,10 @@ https://maven.apache.org/xsd/maven-4.0.0.xsd";>
                 <restrictImports>
                   <reason>Only selected annotation processors are enabled, see 
configuration of maven-compiler-plugin.</reason>
                   <bannedImports>
-                    
<bannedImport>org.apache.hadoop.ozone.om.request.validation.RequestFeatureValidator</bannedImport>
+                    
<bannedImport>org.apache.hadoop.hdds.conf.Config</bannedImport>
+                    
<bannedImport>org.apache.hadoop.hdds.conf.ConfigGroup</bannedImport>
                     
<bannedImport>org.apache.hadoop.hdds.scm.metadata.Replicate</bannedImport>
+                    
<bannedImport>org.apache.hadoop.ozone.om.request.validation.RequestFeatureValidator</bannedImport>
                   </bannedImports>
                 </restrictImports>
               </rules>
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/reconfig/AbstractReconfigureSubCommand.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/reconfig/AbstractReconfigureSubCommand.java
index 0a2666d30e..b8ea45898d 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/reconfig/AbstractReconfigureSubCommand.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/reconfig/AbstractReconfigureSubCommand.java
@@ -28,6 +28,7 @@ import java.util.concurrent.Executors;
 /**
  * An abstract Class use to ReconfigureSubCommand.
  */
[email protected]
 public abstract class AbstractReconfigureSubCommand implements Callable<Void> {
   @CommandLine.ParentCommand
   private ReconfigureCommands parent;
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/Handler.java 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/Handler.java
index 92484936f2..d1755a6880 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/Handler.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/Handler.java
@@ -111,7 +111,7 @@ public abstract class Handler implements Callable<Void> {
     if (!enabled) {
       err().printf("Error: '%s' operation works only when security is " +
           "enabled. To enable security set ozone.security.enabled to " +
-          "true.%n", spec.qualifiedName());
+          "true.%n", spec.qualifiedName().trim());
     }
     return enabled;
   }
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/OzoneRatis.java
 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/OzoneRatis.java
index 5bc9826806..f471911c1f 100644
--- 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/OzoneRatis.java
+++ 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/OzoneRatis.java
@@ -17,6 +17,7 @@
  */
 package org.apache.hadoop.ozone.shell;
 
+import org.apache.hadoop.hdds.cli.GenericCli;
 import org.apache.hadoop.hdds.cli.HddsVersionProvider;
 import org.apache.hadoop.hdds.tracing.TracingUtil;
 import org.apache.ratis.shell.cli.sh.RatisShell;
@@ -30,7 +31,7 @@ import picocli.CommandLine;
         description = "Shell for running Ratis commands",
         versionProvider = HddsVersionProvider.class,
         mixinStandardHelpOptions = true)
-public class OzoneRatis extends Shell {
+public class OzoneRatis extends GenericCli {
 
   public OzoneRatis() {
     super(OzoneRatis.class);
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/REPL.java 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/REPL.java
new file mode 100644
index 0000000000..1484884634
--- /dev/null
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/REPL.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.hadoop.ozone.shell;
+
+import org.jline.console.SystemRegistry;
+import org.jline.console.impl.SystemRegistryImpl;
+import org.jline.reader.EndOfFileException;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReaderBuilder;
+import org.jline.reader.MaskingCallback;
+import org.jline.reader.Parser;
+import org.jline.reader.UserInterruptException;
+import org.jline.reader.impl.DefaultParser;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.TerminalBuilder;
+import org.jline.widget.TailTipWidgets;
+import org.jline.widget.TailTipWidgets.TipType;
+import picocli.CommandLine;
+import picocli.shell.jline3.PicocliCommands;
+import picocli.shell.jline3.PicocliCommands.PicocliCommandsFactory;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.function.Supplier;
+
+/**
+ * Interactive shell for Ozone commands.
+ * (REPL = Read-Eval-Print Loop)
+ */
+class REPL {
+
+  REPL(Shell shell, CommandLine cmd, PicocliCommandsFactory factory) {
+    Parser parser = new DefaultParser();
+    Supplier<Path> workDir = () -> Paths.get(System.getProperty("user.dir"));
+    TerminalBuilder terminalBuilder = TerminalBuilder.builder()
+        .dumb(true);
+    try (Terminal terminal = terminalBuilder.build()) {
+      factory.setTerminal(terminal);
+
+      PicocliCommands picocliCommands = new PicocliCommands(cmd);
+      picocliCommands.name(shell.name());
+      SystemRegistry registry = new SystemRegistryImpl(parser, terminal, 
workDir, null);
+      registry.setCommandRegistries(picocliCommands);
+      registry.register("help", picocliCommands);
+
+      LineReader reader = LineReaderBuilder.builder()
+          .terminal(terminal)
+          .completer(registry.completer())
+          .parser(parser)
+          .variable(LineReader.LIST_MAX, 50)
+          .build();
+
+      TailTipWidgets widgets = new TailTipWidgets(reader, 
registry::commandDescription, 5, TipType.COMPLETER);
+      widgets.enable();
+
+      String prompt = shell.prompt() + "> ";
+
+      while (true) {
+        try {
+          registry.cleanUp();
+          String line = reader.readLine(prompt, null, (MaskingCallback) null, 
null);
+          registry.execute(line);
+        } catch (UserInterruptException ignored) {
+          // ignore
+        } catch (EndOfFileException e) {
+          return;
+        } catch (Exception e) {
+          registry.trace(e);
+        }
+      }
+    } catch (Exception e) {
+      shell.printError(e);
+    }
+  }
+}
diff --git 
a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/Shell.java 
b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/Shell.java
index 97e160651b..44893f78e6 100644
--- a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/Shell.java
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/shell/Shell.java
@@ -20,6 +20,8 @@ package org.apache.hadoop.ozone.shell;
 
 import org.apache.hadoop.hdds.cli.GenericCli;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
+import picocli.CommandLine;
+import picocli.shell.jline3.PicocliCommands.PicocliCommandsFactory;
 
 /**
  * Ozone user interface commands.
@@ -27,6 +29,7 @@ import org.apache.hadoop.ozone.om.exceptions.OMException;
  * This class uses dispatch method to make calls
  * to appropriate handlers that execute the ozone functions.
  */
[email protected]
 public abstract class Shell extends GenericCli {
 
   public static final String OZONE_URI_DESCRIPTION =
@@ -46,15 +49,52 @@ public abstract class Shell extends GenericCli {
           "Any unspecified information will be identified from\n" +
           "the config files.\n";
 
+  private String name;
+
+  @CommandLine.Spec
+  private CommandLine.Model.CommandSpec spec;
+
+  @CommandLine.Option(names = { "--interactive" }, description = "Run in 
interactive mode")
+  private boolean interactive;
+
   public Shell() {
+    this(null);
   }
 
   public Shell(Class<?> type) {
-    super(type);
+    super(type, new PicocliCommandsFactory());
+  }
+
+  public String name() {
+    return name;
+  }
+
+  // override if custom prompt is needed
+  public String prompt() {
+    return name();
+  }
+
+  @Override
+  public void run(String[] argv) {
+    name = spec.name();
+
+    try {
+      // parse args to check if interactive mode is requested
+      getCmd().parseArgs(argv);
+    } catch (Exception ignored) {
+      // failure will be reported by regular, non-interactive run
+    }
+
+    if (interactive) {
+      spec.name(""); // use short name (e.g. "token get" instead of "ozone sh 
token get")
+      new REPL(this, getCmd(), (PicocliCommandsFactory) getCmd().getFactory());
+    } else {
+      super.run(argv);
+    }
   }
 
   @Override
-  protected void printError(Throwable errorArg) {
+  public void printError(Throwable errorArg) {
     OMException omException = null;
 
     if (errorArg instanceof OMException) {
@@ -77,4 +117,3 @@ public abstract class Shell extends GenericCli {
     }
   }
 }
-
diff --git a/pom.xml b/pom.xml
index 16023cb28b..869afebf49 100644
--- a/pom.xml
+++ b/pom.xml
@@ -301,6 +301,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xs
     
<restrict-imports.enforcer.version>2.6.0</restrict-imports.enforcer.version>
     <jgrapht.version>1.4.0</jgrapht.version>
     <jgraphx.version>3.9.12</jgraphx.version>
+    <jline.version>3.23.0</jline.version> <!-- should match version via 
picocli-shell-jline3 -->
 
     <spring.version>5.3.39</spring.version>
     <jooq.version>3.11.10</jooq.version>
@@ -323,6 +324,16 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xs
         <artifactId>picocli</artifactId>
         <version>${picocli.version}</version>
       </dependency>
+      <dependency>
+        <groupId>info.picocli</groupId>
+        <artifactId>picocli-shell-jline3</artifactId>
+        <version>${picocli.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.jline</groupId>
+        <artifactId>jline</artifactId>
+        <version>${jline.version}</version>
+      </dependency>
       <dependency>
         <groupId>org.apache.derby</groupId>
         <artifactId>derby</artifactId>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to