You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ap...@apache.org on 2023/05/12 14:40:23 UTC

[ignite-3] branch main updated: IGNITE-19244 Add file path completion to SSL config questions (#2069)

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

apkhmv pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 3252680632 IGNITE-19244 Add file path completion to SSL config questions (#2069)
3252680632 is described below

commit 3252680632b5c067a75c8a467dd8bd3c2ed4788b
Author: Vadim Pakhnushev <86...@users.noreply.github.com>
AuthorDate: Fri May 12 17:40:17 2023 +0300

    IGNITE-19244 Add file path completion to SSL config questions (#2069)
---
 .../questions/ItConnectToClusterTestBase.java      | 14 +------
 .../questions/ConnectToClusterQuestion.java        | 20 ++++++---
 .../flow/question/JlineQuestionWriterReader.java   | 24 +++--------
 .../question/JlineQuestionWriterReaderFactory.java | 47 ++++++++++++++++++++++
 .../cli/core/flow/question/QuestionAsker.java      | 36 ++++++++++++++---
 .../core/flow/question/QuestionAskerFactory.java   |  8 ++--
 .../core/flow/question/QuestionWriterReader.java   |  5 ++-
 ...eader.java => QuestionWriterReaderFactory.java} | 14 +++----
 .../cli/core/repl/executor/ReplExecutor.java       | 29 +++----------
 .../internal/cli/commands/flow/FlowTest.java       |  6 +--
 10 files changed, 119 insertions(+), 84 deletions(-)

diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/questions/ItConnectToClusterTestBase.java b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/questions/ItConnectToClusterTestBase.java
index d9d3c3a62f..3ae37f3def 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/questions/ItConnectToClusterTestBase.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/questions/ItConnectToClusterTestBase.java
@@ -26,13 +26,9 @@ import java.nio.file.Path;
 import org.apache.ignite.internal.cli.commands.CliCommandTestInitializedIntegrationBase;
 import org.apache.ignite.internal.cli.commands.TopLevelCliReplCommand;
 import org.apache.ignite.internal.cli.config.TestStateConfigProvider;
-import org.apache.ignite.internal.cli.core.flow.question.JlineQuestionWriterReader;
+import org.apache.ignite.internal.cli.core.flow.question.JlineQuestionWriterReaderFactory;
 import org.apache.ignite.internal.cli.core.flow.question.QuestionAskerFactory;
 import org.apache.ignite.internal.cli.core.repl.prompt.PromptProvider;
-import org.jline.reader.LineReader;
-import org.jline.reader.LineReaderBuilder;
-import org.jline.reader.Parser;
-import org.jline.reader.impl.DefaultParser;
 import org.jline.terminal.Terminal;
 import org.jline.terminal.impl.DumbTerminal;
 import org.junit.jupiter.api.AfterEach;
@@ -65,13 +61,7 @@ class ItConnectToClusterTestBase extends CliCommandTestInitializedIntegrationBas
 
         input = Files.createTempFile(WORK_DIR, "input", "");
         terminal = new DumbTerminal(Files.newInputStream(input), new FileOutputStream(FileDescriptor.out));
-        Parser parser = new DefaultParser().escapeChars(null);
-
-        LineReader reader = LineReaderBuilder.builder()
-                .terminal(terminal)
-                .parser(parser)
-                .build();
-        QuestionAskerFactory.setReadWriter(new JlineQuestionWriterReader(reader));
+        QuestionAskerFactory.setWriterReaderFactory(new JlineQuestionWriterReaderFactory(terminal));
     }
 
     @AfterEach
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/questions/ConnectToClusterQuestion.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/questions/ConnectToClusterQuestion.java
index d2ee01e19f..8f2c02ce79 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/questions/ConnectToClusterQuestion.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/questions/ConnectToClusterQuestion.java
@@ -168,17 +168,25 @@ public class ConnectToClusterQuestion {
 
         return Flows.<Void, SslConfig>acceptQuestion(question, () -> {
             SslConfig config = new SslConfig();
-            config.trustStorePath(escapeWindowsPath(enterString("trust store path")));
-            config.trustStorePassword(enterString("trust store password"));
+            config.trustStorePath(escapeWindowsPath(enterFilePath("trust store path")));
+            config.trustStorePassword(enterPassword("trust store password"));
             return config;
         }).then(Flows.acceptQuestionFlow(question2, config -> {
-            config.keyStorePath(escapeWindowsPath(enterString("key store path")));
-            config.keyStorePassword(enterString("key store password"));
+            config.keyStorePath(escapeWindowsPath(enterFilePath("key store path")));
+            config.keyStorePassword(enterPassword("key store password"));
         }));
     }
 
-    private static String enterString(String question) {
-        return QuestionAskerFactory.newQuestionAsker().askQuestion("Enter " + question + ": ");
+    private static String enterFilePath(String question) {
+        return QuestionAskerFactory.newQuestionAsker()
+                .completeFilePaths(true)
+                .askQuestion("Enter " + question + ": ");
+    }
+
+    private static String enterPassword(String question) {
+        return QuestionAskerFactory.newQuestionAsker()
+                .maskInput(true)
+                .askQuestion("Enter " + question + ": ");
     }
 
     /** Escapes single backslashes to double backslashes, skips double backslashes if they are present. */
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/JlineQuestionWriterReader.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/JlineQuestionWriterReader.java
index da6340f0e7..3ebd96fa53 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/JlineQuestionWriterReader.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/JlineQuestionWriterReader.java
@@ -17,43 +17,29 @@
 
 package org.apache.ignite.internal.cli.core.flow.question;
 
-import java.util.function.Consumer;
 import org.apache.ignite.internal.cli.core.flow.FlowInterruptException;
 import org.jline.reader.EndOfFileException;
 import org.jline.reader.LineReader;
 import org.jline.reader.MaskingCallback;
 import org.jline.reader.UserInterruptException;
+import org.jline.reader.impl.SimpleMaskingCallback;
 
 /**
  * Implementation of {@link QuestionWriterReader} based on {@link LineReader}.
  */
 public class JlineQuestionWriterReader implements QuestionWriterReader {
     private final LineReader reader;
-    private final Consumer<Boolean> widgetsEnabler;
-
-    public JlineQuestionWriterReader(LineReader reader, Consumer<Boolean> widgetsEnabler) {
-        this.reader = reader;
-        this.widgetsEnabler = widgetsEnabler;
-    }
 
     public JlineQuestionWriterReader(LineReader reader) {
-        this(reader, enable -> {});
+        this.reader = reader;
     }
 
     /** {@inheritDoc} */
     @Override
-    public String readAnswer(String question) {
-        widgetsEnabler.accept(false);
-        reader.setVariable(LineReader.DISABLE_HISTORY, true);
-        String s = readLine(question);
-        reader.setVariable(LineReader.DISABLE_HISTORY, false);
-        widgetsEnabler.accept(true);
-        return s;
-    }
-
-    private String readLine(String question) {
+    public String readAnswer(String question, boolean maskInput) {
         try {
-            return reader.readLine(question, null, (MaskingCallback) null, null);
+            MaskingCallback callback = maskInput ? new SimpleMaskingCallback('*') : null;
+            return reader.readLine(question, null, callback, null);
         } catch (UserInterruptException /* Ctrl-C pressed */ | EndOfFileException /* Ctrl-D pressed */ ignored) {
             throw new FlowInterruptException();
         }
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/JlineQuestionWriterReaderFactory.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/JlineQuestionWriterReaderFactory.java
new file mode 100644
index 0000000000..6b3baa615d
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/JlineQuestionWriterReaderFactory.java
@@ -0,0 +1,47 @@
+/*
+ * 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.ignite.internal.cli.core.flow.question;
+
+import org.jline.builtins.Completers.FileNameCompleter;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReaderBuilder;
+import org.jline.reader.Parser;
+import org.jline.reader.impl.DefaultParser;
+import org.jline.terminal.Terminal;
+
+/** Creates {@link QuestionWriterReader} using custom {@link LineReader}. **/
+public class JlineQuestionWriterReaderFactory implements QuestionWriterReaderFactory {
+    private final Terminal terminal;
+
+    public JlineQuestionWriterReaderFactory(Terminal terminal) {
+        this.terminal = terminal;
+    }
+
+    @Override
+    public QuestionWriterReader createWriterReader(boolean completeFilePaths) {
+        Parser parser = new DefaultParser().escapeChars(null);
+        LineReaderBuilder builder = LineReaderBuilder.builder()
+                .terminal(terminal)
+                .parser(parser);
+        if (completeFilePaths) {
+            builder.completer(new FileNameCompleter());
+        }
+        LineReader reader = builder.build();
+        return new JlineQuestionWriterReader(reader);
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionAsker.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionAsker.java
index ec64c573d4..f817d5bd96 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionAsker.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionAsker.java
@@ -24,10 +24,14 @@ import java.util.List;
  */
 public class QuestionAsker {
 
-    private final QuestionWriterReader readWriter;
+    private final QuestionWriterReaderFactory writerReaderFactory;
 
-    public QuestionAsker(QuestionWriterReader readWriter) {
-        this.readWriter = readWriter;
+    private boolean completeFilePaths;
+
+    private boolean maskInput;
+
+    public QuestionAsker(QuestionWriterReaderFactory writerReaderFactory) {
+        this.writerReaderFactory = writerReaderFactory;
     }
 
     /**
@@ -40,7 +44,7 @@ public class QuestionAsker {
      * @return value of answer.
      */
     public <I, O> O askQuestion(String question, I input, List<QuestionAnswer<I, O>> answers) {
-        String userAnswer = readWriter.readAnswer(question);
+        String userAnswer = askQuestion(question);
 
         for (QuestionAnswer<I, O> answer : answers) {
             if (answer.isAnswer(userAnswer)) {
@@ -57,6 +61,28 @@ public class QuestionAsker {
      * @return value of answer.
      */
     public String askQuestion(String question) {
-        return readWriter.readAnswer(question);
+        QuestionWriterReader readWriter = writerReaderFactory.createWriterReader(completeFilePaths);
+        return readWriter.readAnswer(question, maskInput);
+    }
+
+    /**
+     * Enables file paths completion.
+     *
+     * @param completeFilePaths If {@code true}, file paths will be completed.
+     */
+    public QuestionAsker completeFilePaths(boolean completeFilePaths) {
+        this.completeFilePaths = completeFilePaths;
+        return this;
     }
+
+    /**
+     * Enables input masking.
+     *
+     * @param maskInput If {@code true}, user input will be masked with asterisks.
+     */
+    public QuestionAsker maskInput(boolean maskInput) {
+        this.maskInput = maskInput;
+        return this;
+    }
+
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionAskerFactory.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionAskerFactory.java
index 81337f9464..fba82e578c 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionAskerFactory.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionAskerFactory.java
@@ -21,13 +21,13 @@ package org.apache.ignite.internal.cli.core.flow.question;
  * Factory of {@link QuestionAsker}.
  */
 public class QuestionAskerFactory {
-    private static volatile QuestionWriterReader questionWriterReader;
+    private static volatile QuestionWriterReaderFactory writerReaderFactory;
 
     public static QuestionAsker newQuestionAsker() {
-        return new QuestionAsker(questionWriterReader);
+        return new QuestionAsker(writerReaderFactory);
     }
 
-    public static void setReadWriter(QuestionWriterReader questionWriterReader) {
-        QuestionAskerFactory.questionWriterReader = questionWriterReader;
+    public static void setWriterReaderFactory(QuestionWriterReaderFactory writerReaderFactory) {
+        QuestionAskerFactory.writerReaderFactory = writerReaderFactory;
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionWriterReader.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionWriterReader.java
index 6ce79776db..9dc37fa7c8 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionWriterReader.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionWriterReader.java
@@ -24,8 +24,9 @@ public interface QuestionWriterReader {
     /**
      * Write question to output and read answer from input.
      *
-     * @param question question to ask.
+     * @param question Question to ask.
+     * @param maskInput If {@code true}, user input will be masked with asterisks.
      * @return user answer.
      */
-    String readAnswer(String question);
+    String readAnswer(String question, boolean maskInput);
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionWriterReader.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionWriterReaderFactory.java
similarity index 70%
copy from modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionWriterReader.java
copy to modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionWriterReaderFactory.java
index 6ce79776db..0198102ac1 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionWriterReader.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/flow/question/QuestionWriterReaderFactory.java
@@ -17,15 +17,13 @@
 
 package org.apache.ignite.internal.cli.core.flow.question;
 
-/**
- * Question writer and reader interface.
- */
-public interface QuestionWriterReader {
+/** Factory for creating instances of {@link QuestionWriterReader}. */
+public interface QuestionWriterReaderFactory {
     /**
-     * Write question to output and read answer from input.
+     * Creates an instance of {@link QuestionWriterReader}.
      *
-     * @param question question to ask.
-     * @return user answer.
+     * @param completeFilePaths If {@code true}, this instance will complete input as file paths.
+     * @return Question reader.
      */
-    String readAnswer(String question);
+    QuestionWriterReader createWriterReader(boolean completeFilePaths);
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/ReplExecutor.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/ReplExecutor.java
index 574eeedeb5..945c801f07 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/ReplExecutor.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/ReplExecutor.java
@@ -21,7 +21,6 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
 import java.util.function.Supplier;
 import org.apache.ignite.internal.cli.commands.node.NodeNameOrUrl;
 import org.apache.ignite.internal.cli.config.StateFolderProvider;
@@ -29,7 +28,7 @@ import org.apache.ignite.internal.cli.core.converters.NodeNameOrUrlConverter;
 import org.apache.ignite.internal.cli.core.exception.ExceptionHandlers;
 import org.apache.ignite.internal.cli.core.exception.handler.PicocliExecutionExceptionHandler;
 import org.apache.ignite.internal.cli.core.exception.handler.ReplExceptionHandlers;
-import org.apache.ignite.internal.cli.core.flow.question.JlineQuestionWriterReader;
+import org.apache.ignite.internal.cli.core.flow.question.JlineQuestionWriterReaderFactory;
 import org.apache.ignite.internal.cli.core.flow.question.QuestionAskerFactory;
 import org.apache.ignite.internal.cli.core.repl.Repl;
 import org.apache.ignite.internal.cli.core.repl.completer.DynamicCompleterActivationPoint;
@@ -88,7 +87,7 @@ public class ReplExecutor {
         this.nodeNameRegistry = nodeNameRegistry;
     }
 
-    private static TailTipWidgets createTailTipWidgets(SystemRegistryImpl registry, LineReader reader) {
+    private static void createTailTipWidgets(SystemRegistryImpl registry, LineReader reader) {
         TailTipWidgets widgets = new TailTipWidgets(reader, registry::commandDescription, 5,
                 TailTipWidgets.TipType.COMPLETER);
         widgets.enable();
@@ -101,7 +100,6 @@ public class ReplExecutor {
         });
         // Workaround for jline issue where TailTipWidgets will produce NPE when passed a bracket
         registry.setScriptDescription(cmdLine -> null);
-        return widgets;
     }
 
     /**
@@ -152,32 +150,15 @@ public class ReplExecutor {
         }
     }
 
-    private static void setupWidgets(Repl repl, SystemRegistryImpl registry, LineReader reader) {
-        Consumer<Boolean> widgetsEnabler;
+    private void setupWidgets(Repl repl, SystemRegistryImpl registry, LineReader reader) {
         if (repl.isTailTipWidgetsEnabled()) {
-            TailTipWidgets widgets = createTailTipWidgets(registry, reader);
-            widgetsEnabler = enable -> {
-                if (enable) {
-                    widgets.enable();
-                } else {
-                    widgets.disable();
-                }
-            };
+            createTailTipWidgets(registry, reader);
         } else if (repl.isAutosuggestionsWidgetsEnabled()) {
             AutosuggestionWidgets widgets = new AutosuggestionWidgets(reader);
             widgets.enable();
-            widgetsEnabler = enable -> {
-                if (enable) {
-                    widgets.enable();
-                } else {
-                    widgets.disable();
-                }
-            };
-        } else {
-            widgetsEnabler = enable -> {};
         }
 
-        QuestionAskerFactory.setReadWriter(new JlineQuestionWriterReader(reader, widgetsEnabler));
+        QuestionAskerFactory.setWriterReaderFactory(new JlineQuestionWriterReaderFactory(terminal));
     }
 
     private LineReader createReader(Completer completer) {
diff --git a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/flow/FlowTest.java b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/flow/FlowTest.java
index 700330e890..d72613a933 100644
--- a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/flow/FlowTest.java
+++ b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/flow/FlowTest.java
@@ -38,11 +38,10 @@ import org.apache.ignite.internal.cli.core.flow.Flow;
 import org.apache.ignite.internal.cli.core.flow.Flowable;
 import org.apache.ignite.internal.cli.core.flow.builder.FlowBuilder;
 import org.apache.ignite.internal.cli.core.flow.builder.Flows;
-import org.apache.ignite.internal.cli.core.flow.question.JlineQuestionWriterReader;
+import org.apache.ignite.internal.cli.core.flow.question.JlineQuestionWriterReaderFactory;
 import org.apache.ignite.internal.cli.core.flow.question.QuestionAnswer;
 import org.apache.ignite.internal.cli.core.flow.question.QuestionAskerFactory;
 import org.apache.ignite.internal.cli.core.repl.context.CommandLineContextProvider;
-import org.jline.reader.impl.LineReaderImpl;
 import org.jline.terminal.Terminal;
 import org.jline.terminal.impl.DumbTerminal;
 import org.junit.jupiter.api.AfterEach;
@@ -100,8 +99,7 @@ class FlowTest {
         input = Files.createTempFile("input", "");
         input.toFile().deleteOnExit();
         terminal = new DumbTerminal(Files.newInputStream(input), new FileOutputStream(FileDescriptor.out));
-        LineReaderImpl reader = new LineReaderImpl(terminal);
-        QuestionAskerFactory.setReadWriter(new JlineQuestionWriterReader(reader));
+        QuestionAskerFactory.setWriterReaderFactory(new JlineQuestionWriterReaderFactory(terminal));
         out = new StringWriter();
         errOut = new StringWriter();
         CommandLineContextProvider.setWriters(new PrintWriter(out), new PrintWriter(errOut));