You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pulsar.apache.org by ni...@apache.org on 2022/09/17 09:28:06 UTC

[pulsar] branch master updated: [improve][cli] Pulsar shell: add command to set/get property of a config (#17651)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 4ec1009b42c [improve][cli] Pulsar shell: add command to set/get property of a config (#17651)
4ec1009b42c is described below

commit 4ec1009b42c226d6f9b9eb52b03e50bb4cdbb3cd
Author: Nicolò Boschi <bo...@gmail.com>
AuthorDate: Sat Sep 17 11:27:59 2022 +0200

    [improve][cli] Pulsar shell: add command to set/get property of a config (#17651)
---
 .../java/org/apache/pulsar/shell/ConfigShell.java  | 100 ++++++++++++++++++--
 .../java/org/apache/pulsar/shell/PulsarShell.java  |  12 +--
 .../apache/pulsar/shell/config/ConfigStore.java    |  72 +++++++++++++++
 .../pulsar/shell/config/FileConfigStore.java       |  20 +---
 .../org/apache/pulsar/shell/ConfigShellTest.java   | 102 ++++++++++++++++++---
 site2/docs/reference-cli-tools.md                  |  30 ++++++
 6 files changed, 290 insertions(+), 46 deletions(-)

diff --git a/pulsar-client-tools/src/main/java/org/apache/pulsar/shell/ConfigShell.java b/pulsar-client-tools/src/main/java/org/apache/pulsar/shell/ConfigShell.java
index 7aa87f07055..72710498017 100644
--- a/pulsar-client-tools/src/main/java/org/apache/pulsar/shell/ConfigShell.java
+++ b/pulsar-client-tools/src/main/java/org/apache/pulsar/shell/ConfigShell.java
@@ -42,6 +42,7 @@ import java.util.stream.Collectors;
 import lombok.Getter;
 import lombok.SneakyThrows;
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.pulsar.shell.config.ConfigStore;
 
 /**
@@ -79,11 +80,12 @@ public class ConfigShell implements ShellCommandsProvider {
     private final ConfigStore configStore;
     private final ObjectMapper writer = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
     @Getter
-    private String currentConfig = DEFAULT_CONFIG;
+    private String currentConfig;
 
-    public ConfigShell(PulsarShell pulsarShell) {
+    public ConfigShell(PulsarShell pulsarShell, String currentConfig) {
         this.configStore = pulsarShell.getConfigStore();
         this.pulsarShell = pulsarShell;
+        this.currentConfig = currentConfig;
     }
 
     @Override
@@ -114,6 +116,8 @@ public class ConfigShell implements ShellCommandsProvider {
         commands.put("delete", new CmdConfigDelete());
         commands.put("use", new CmdConfigUse());
         commands.put("view", new CmdConfigView());
+        commands.put("set-property", new CmdConfigSetProperty());
+        commands.put("get-property", new CmdConfigGetProperty());
         commands.forEach((k, v) -> jcommander.addCommand(k, v));
     }
 
@@ -336,18 +340,98 @@ public class ConfigShell implements ShellCommandsProvider {
                 return false;
             }
 
-            configStore.putConfig(new ConfigStore.ConfigEntry(name, value));
-            if (currentConfig.equals(name)) {
-                final Properties properties = new Properties();
-                properties.load(new StringReader(value));
-                pulsarShell.reload(properties);
-            }
+            final ConfigStore.ConfigEntry entry = new ConfigStore.ConfigEntry(name, value);
+            configStore.putConfig(entry);
+            reloadIfCurrent(entry);
             return true;
         }
 
+
+
         abstract boolean verifyCondition();
     }
 
+    private void reloadIfCurrent(ConfigStore.ConfigEntry entry) throws Exception {
+        if (currentConfig.equals(entry.getName())) {
+            final Properties properties = new Properties();
+            properties.load(new StringReader(entry.getValue()));
+            pulsarShell.reload(properties);
+        }
+    }
+
+
+    @Parameters(commandDescription = "Set a configuration property by name")
+    private class CmdConfigSetProperty implements RunnableWithResult {
+
+        @Parameter(description = "Name of the config", required = true)
+        @JCommanderCompleter.ParameterCompleter(type = JCommanderCompleter.ParameterCompleter.Type.CONFIGS)
+        private String name;
+
+        @Parameter(names = {"-p", "--property"}, required = true, description = "Name of the property to update")
+        protected String propertyName;
+
+        @Parameter(names = {"-v", "--value"}, description = "New value for the property")
+        protected String propertyValue;
+
+        @Override
+        @SneakyThrows
+        public boolean run() {
+            if (StringUtils.isBlank(propertyName)) {
+                print("-p parameter is required");
+                return false;
+            }
+
+            if (propertyValue == null) {
+                print("-v parameter is required. you can pass an empty value to empty the property. (-v= )");
+                return false;
+            }
+
+
+            final ConfigStore.ConfigEntry config = configStore.getConfig(this.name);
+            if (config == null) {
+                print("Config " + name + " not found");
+                return false;
+            }
+            ConfigStore.setProperty(config, propertyName, propertyValue);
+            print("Property " + propertyName + " set for config " + name);
+            configStore.putConfig(config);
+            reloadIfCurrent(config);
+            return true;
+        }
+    }
+
+    @Parameters(commandDescription = "Get a configuration property by name")
+    private class CmdConfigGetProperty implements RunnableWithResult {
+
+        @Parameter(description = "Name of the config", required = true)
+        @JCommanderCompleter.ParameterCompleter(type = JCommanderCompleter.ParameterCompleter.Type.CONFIGS)
+        private String name;
+
+        @Parameter(names = {"-p", "--property"}, required = true, description = "Name of the property")
+        protected String propertyName;
+
+        @Override
+        @SneakyThrows
+        public boolean run() {
+            if (StringUtils.isBlank(propertyName)) {
+                print("-p parameter is required");
+                return false;
+            }
+
+            final ConfigStore.ConfigEntry config = configStore.getConfig(this.name);
+            if (config == null) {
+                print("Config " + name + " not found");
+                return false;
+            }
+            final String value = ConfigStore.getProperty(config, propertyName);
+            if (!StringUtils.isBlank(value)) {
+                print(value);
+            }
+            return true;
+        }
+    }
+
+
 
     <T> void print(List<T> items) {
         for (T item : items) {
diff --git a/pulsar-client-tools/src/main/java/org/apache/pulsar/shell/PulsarShell.java b/pulsar-client-tools/src/main/java/org/apache/pulsar/shell/PulsarShell.java
index cbc043fe0c7..24a222e0e7f 100644
--- a/pulsar-client-tools/src/main/java/org/apache/pulsar/shell/PulsarShell.java
+++ b/pulsar-client-tools/src/main/java/org/apache/pulsar/shell/PulsarShell.java
@@ -129,10 +129,9 @@ public class PulsarShell {
     private final JCommander mainCommander;
     private final MainOptions mainOptions;
     private JCommander shellCommander;
-    private final String[] args;
     private Function<Map<String, ShellCommandsProvider>, InteractiveLineReader> readerBuilder;
     private InteractiveLineReader reader;
-    private ConfigShell configShell;
+    private final ConfigShell configShell;
 
     public PulsarShell(String args[]) throws IOException {
         this(args, new Properties());
@@ -175,12 +174,14 @@ public class PulsarShell {
                 defaultConfig);
 
         final ConfigStore.ConfigEntry lastUsed = configStore.getLastUsed();
+        String configName = ConfigStore.DEFAULT_CONFIG;
         if (lastUsed != null) {
             properties.load(new StringReader(lastUsed.getValue()));
+            configName = lastUsed.getName();
         } else if (defaultConfig != null) {
             properties.load(new StringReader(defaultConfig.getValue()));
         }
-        this.args = args;
+        configShell = new ConfigShell(this, configName);
     }
 
     private static File computePulsarShellFile() {
@@ -266,7 +267,7 @@ public class PulsarShell {
                             new AttributedStringBuilder().style(AttributedStyle.BOLD).append("quit").toAnsi());
             output(welcomeMessage, terminal);
             String promptMessage;
-            if (configShell != null && configShell.getCurrentConfig() != null) {
+            if (configShell.getCurrentConfig() != null) {
                 promptMessage = String.format("%s(%s)",
                         configShell.getCurrentConfig(), getHostFromUrl(serviceUrl));
             } else {
@@ -567,9 +568,6 @@ public class PulsarShell {
         final Map<String, ShellCommandsProvider> providerMap = new HashMap<>();
         registerProvider(createAdminShell(properties), commander, providerMap);
         registerProvider(createClientShell(properties), commander, providerMap);
-        if (configShell == null) {
-            configShell = new ConfigShell(this);
-        }
         registerProvider(configShell, commander, providerMap);
         return providerMap;
     }
diff --git a/pulsar-client-tools/src/main/java/org/apache/pulsar/shell/config/ConfigStore.java b/pulsar-client-tools/src/main/java/org/apache/pulsar/shell/config/ConfigStore.java
index cd994cc5354..4dc6bbb2c51 100644
--- a/pulsar-client-tools/src/main/java/org/apache/pulsar/shell/config/ConfigStore.java
+++ b/pulsar-client-tools/src/main/java/org/apache/pulsar/shell/config/ConfigStore.java
@@ -19,7 +19,10 @@
 package org.apache.pulsar.shell.config;
 
 import java.io.IOException;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Scanner;
+import java.util.Set;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
@@ -50,4 +53,73 @@ public interface ConfigStore {
     void setLastUsed(String name) throws IOException;
 
     ConfigEntry getLastUsed() throws IOException;
+
+    static void cleanupValue(ConfigEntry entry) {
+        StringBuilder builder = new StringBuilder();
+        try (Scanner scanner = new Scanner(entry.getValue());) {
+            while (scanner.hasNextLine()) {
+                String line = scanner.nextLine().trim();
+                if (line.isBlank() || line.startsWith("#")) {
+                    continue;
+                }
+                builder.append(line);
+                builder.append(System.lineSeparator());
+            }
+        }
+        entry.setValue(builder.toString());
+    }
+
+    static void setProperty(ConfigEntry entry, String propertyName, String propertyValue) {
+        Set<String> keys = new HashSet<>();
+        StringBuilder builder = new StringBuilder();
+        try (Scanner scanner = new Scanner(entry.getValue());) {
+            while (scanner.hasNextLine()) {
+                String line = scanner.nextLine().trim();
+                if (line.isBlank() || line.startsWith("#")) {
+                    continue;
+                }
+                final String[] split = line.split("=", 2);
+                if (split.length > 0) {
+                    final String property = split[0];
+                    if (!keys.add(property)) {
+                        continue;
+                    }
+                    if (property.equals(propertyName)) {
+                        line = property + "=" + propertyValue;
+                    }
+                }
+                builder.append(line);
+                builder.append(System.lineSeparator());
+            }
+            if (!keys.contains(propertyName)) {
+                builder.append(propertyName + "=" + propertyValue);
+                builder.append(System.lineSeparator());
+            }
+        }
+        entry.setValue(builder.toString());
+    }
+
+    static String getProperty(ConfigEntry entry, String propertyName) {
+        try (Scanner scanner = new Scanner(entry.getValue());) {
+            while (scanner.hasNextLine()) {
+                String line = scanner.nextLine().trim();
+                if (line.isBlank() || line.startsWith("#")) {
+                    continue;
+                }
+                final String[] split = line.split("=", 2);
+                if (split.length > 0) {
+                    final String property = split[0];
+                    if (property.equals(propertyName)) {
+                        if (split.length > 1) {
+                            return split[1];
+                        }
+                        return null;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+
 }
diff --git a/pulsar-client-tools/src/main/java/org/apache/pulsar/shell/config/FileConfigStore.java b/pulsar-client-tools/src/main/java/org/apache/pulsar/shell/config/FileConfigStore.java
index f7d558dba98..0d50eb0240c 100644
--- a/pulsar-client-tools/src/main/java/org/apache/pulsar/shell/config/FileConfigStore.java
+++ b/pulsar-client-tools/src/main/java/org/apache/pulsar/shell/config/FileConfigStore.java
@@ -29,7 +29,6 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Scanner;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
@@ -61,7 +60,7 @@ public class FileConfigStore implements ConfigStore {
         }
         if (defaultConfig != null) {
             this.defaultConfig = new ConfigEntry(defaultConfig.getName(), defaultConfig.getValue());
-            cleanupValue(this.defaultConfig);
+            ConfigStore.cleanupValue(this.defaultConfig);
         } else {
             this.defaultConfig = null;
         }
@@ -88,26 +87,11 @@ public class FileConfigStore implements ConfigStore {
         if (DEFAULT_CONFIG.equals(entry.getName())) {
             throw new IllegalArgumentException("'" + DEFAULT_CONFIG + "' can't be modified.");
         }
-        cleanupValue(entry);
+        ConfigStore.cleanupValue(entry);
         fileConfig.configs.put(entry.getName(), entry);
         write();
     }
 
-    private static void cleanupValue(ConfigEntry entry) {
-        StringBuilder builder = new StringBuilder();
-        try (Scanner scanner = new Scanner(entry.getValue());) {
-            while (scanner.hasNextLine()) {
-                String line = scanner.nextLine().trim();
-                if (line.startsWith("#")) {
-                    continue;
-                }
-                builder.append(line);
-                builder.append(System.lineSeparator());
-            }
-        }
-        entry.setValue(builder.toString());
-    }
-
     @Override
     public ConfigEntry getConfig(String name) {
         if (DEFAULT_CONFIG.equals(name)) {
diff --git a/pulsar-client-tools/src/test/java/org/apache/pulsar/shell/ConfigShellTest.java b/pulsar-client-tools/src/test/java/org/apache/pulsar/shell/ConfigShellTest.java
index f5b22af299f..db186ff4a85 100644
--- a/pulsar-client-tools/src/test/java/org/apache/pulsar/shell/ConfigShellTest.java
+++ b/pulsar-client-tools/src/test/java/org/apache/pulsar/shell/ConfigShellTest.java
@@ -46,7 +46,7 @@ public class ConfigShellTest {
 
     private PulsarShell pulsarShell;
     private ConfigShell configShell;
-    private final List<String> output = new ArrayList<>();
+    private List<String> output;
 
     @BeforeMethod(alwaysRun = true)
     public void before() throws Exception {
@@ -58,9 +58,13 @@ public class ConfigShellTest {
         when(pulsarShell.getConfigStore()).thenReturn(
                 new FileConfigStore(tempJson.toFile(),
                         new ConfigStore.ConfigEntry(ConfigStore.DEFAULT_CONFIG, "#comment\ndefault-config=true")));
-        configShell = new ConfigShell(pulsarShell);
+        configShell = new ConfigShell(pulsarShell, ConfigStore.DEFAULT_CONFIG);
         configShell.setupState(new Properties());
+        output = new ArrayList<>();
+        setConsole();
+    }
 
+    private void setConsole() {
         configShell.getJCommander().setConsole(new Console() {
             @Override
             public void print(String msg) {
@@ -79,30 +83,29 @@ public class ConfigShellTest {
                 return new char[0];
             }
         });
-
     }
 
     @Test
     public void testDefault() throws Exception {
-        assertTrue(configShell.runCommand(new String[]{"list"}));
+        assertTrue(runCommand(new String[]{"list"}));
         assertEquals(output, Arrays.asList("default (*)"));
         output.clear();
-        assertTrue(configShell.runCommand(new String[]{"view", "default"}));
+        assertTrue(runCommand(new String[]{"view", "default"}));
         assertEquals(output.get(0), "default-config=true\n");
         output.clear();
 
         final Path newClientConf = Files.createTempFile("client", ".conf");
-        assertFalse(configShell.runCommand(new String[]{"create", "default",
+        assertFalse(runCommand(new String[]{"create", "default",
                 "--file", newClientConf.toFile().getAbsolutePath()}));
         assertEquals(output, Arrays.asList("Config 'default' already exists."));
         output.clear();
 
-        assertFalse(configShell.runCommand(new String[]{"update", "default",
+        assertFalse(runCommand(new String[]{"update", "default",
                 "--file", newClientConf.toFile().getAbsolutePath()}));
         assertEquals(output, Arrays.asList("'default' can't be updated."));
         output.clear();
 
-        assertFalse(configShell.runCommand(new String[]{"delete", "default"}));
+        assertFalse(runCommand(new String[]{"delete", "default"}));
         assertEquals(output, Arrays.asList("'default' can't be deleted."));
     }
 
@@ -113,14 +116,14 @@ public class ConfigShellTest {
         final byte[] content = ("webServiceUrl=http://localhost:8081/\n" +
                 "brokerServiceUrl=pulsar://localhost:6651/\n").getBytes(StandardCharsets.UTF_8);
         Files.write(newClientConf, content);
-        assertTrue(configShell.runCommand(new String[]{"create", "myclient",
+        assertTrue(runCommand(new String[]{"create", "myclient",
                 "--file", newClientConf.toFile().getAbsolutePath()}));
         assertTrue(output.isEmpty());
         output.clear();
 
         assertNull(pulsarShell.getConfigStore().getLastUsed());
 
-        assertTrue(configShell.runCommand(new String[]{"use", "myclient"}));
+        assertTrue(runCommand(new String[]{"use", "myclient"}));
         assertTrue(output.isEmpty());
         output.clear();
         assertEquals(pulsarShell.getConfigStore().getLastUsed(), pulsarShell.getConfigStore()
@@ -128,17 +131,90 @@ public class ConfigShellTest {
 
         verify(pulsarShell).reload(any());
 
-        assertTrue(configShell.runCommand(new String[]{"list"}));
+        assertTrue(runCommand(new String[]{"list"}));
         assertEquals(output, Arrays.asList("default", "myclient (*)"));
         output.clear();
 
-        assertFalse(configShell.runCommand(new String[]{"delete", "myclient"}));
+        assertFalse(runCommand(new String[]{"delete", "myclient"}));
         assertEquals(output, Arrays.asList("'myclient' is currently used and it can't be deleted."));
         output.clear();
 
-        assertTrue(configShell.runCommand(new String[]{"update", "myclient",
+        assertTrue(runCommand(new String[]{"update", "myclient",
                 "--file", newClientConf.toFile().getAbsolutePath()}));
         assertTrue(output.isEmpty());
         verify(pulsarShell, times(2)).reload(any());
     }
+
+    @Test
+    public void testSetGetProperty() throws Exception {
+        final Path newClientConf = Files.createTempFile("client", ".conf");
+
+        final byte[] content = ("webServiceUrl=http://localhost:8081/\n" +
+                "brokerServiceUrl=pulsar://localhost:6651/\n").getBytes(StandardCharsets.UTF_8);
+        Files.write(newClientConf, content);
+        assertTrue(runCommand(new String[]{"create", "myclient",
+                "--file", newClientConf.toFile().getAbsolutePath()}));
+        assertTrue(output.isEmpty());
+        output.clear();
+
+        assertTrue(runCommand(new String[]{"use", "myclient"}));
+        assertTrue(output.isEmpty());
+        output.clear();
+
+        assertTrue(runCommand(new String[]{"get-property", "-p", "webServiceUrl", "myclient"}));
+        assertEquals(output.get(0), "http://localhost:8081/");
+        output.clear();
+
+        assertTrue(runCommand(new String[]{"set-property", "-p", "newConf",
+                "-v", "myValue", "myclient"}));
+        verify(pulsarShell, times(2)).reload(any());
+        output.clear();
+
+        assertTrue(runCommand(new String[]{"get-property", "-p", "newConf", "myclient"}));
+        assertEquals(output.get(0), "myValue");
+        output.clear();
+
+        assertTrue(runCommand(new String[]{"view", "myclient"}));
+        assertEquals(output.get(0), "webServiceUrl=http://localhost:8081/\nbrokerServiceUrl" +
+                "=pulsar://localhost:6651/\nnewConf=myValue\n");
+        output.clear();
+
+        assertTrue(runCommand(new String[]{"set-property", "-p", "newConf",
+                "-v", "myValue2", "myclient"}));
+        verify(pulsarShell, times(3)).reload(any());
+        output.clear();
+
+        assertTrue(runCommand(new String[]{"get-property", "-p", "newConf", "myclient"}));
+        assertEquals(output.get(0), "myValue2");
+        output.clear();
+
+
+        assertTrue(runCommand(new String[]{"view", "myclient"}));
+        assertEquals(output.get(0), "webServiceUrl=http://localhost:8081/\nbrokerServiceUrl" +
+                "=pulsar://localhost:6651/\nnewConf=myValue2\n");
+        output.clear();
+
+        assertTrue(runCommand(new String[]{"set-property", "-p", "newConf",
+                "-v", "", "myclient"}));
+        verify(pulsarShell, times(4)).reload(any());
+        output.clear();
+        assertTrue(runCommand(new String[]{"view", "myclient"}));
+        assertEquals(output.get(0), "webServiceUrl=http://localhost:8081/\nbrokerServiceUrl" +
+                "=pulsar://localhost:6651/\nnewConf=\n");
+        output.clear();
+
+        assertTrue(runCommand(new String[]{"get-property", "-p", "newConf", "myclient"}));
+        assertTrue(output.isEmpty());
+        output.clear();
+
+    }
+
+    private boolean runCommand(String[] x) throws Exception {
+        try {
+            return configShell.runCommand(x);
+        } finally {
+            configShell.setupState(null);
+            setConsole();
+        }
+    }
 }
\ No newline at end of file
diff --git a/site2/docs/reference-cli-tools.md b/site2/docs/reference-cli-tools.md
index dd094782b42..8d172c8a3b4 100644
--- a/site2/docs/reference-cli-tools.md
+++ b/site2/docs/reference-cli-tools.md
@@ -1014,6 +1014,36 @@ Options
 | `--url`  | URL of the config.       |  |
 | `--value`  | Inline value of the config. Base64-encoded value is supported with the prefix `base64:`. |  |
 
+#### `set-property`
+
+Set a value for a specified configuration property.
+
+```bash
+default(localhost)> config set-property -p webServiceUrl -v http://<cluster-hostname> mycluster
+```
+
+Options
+
+| Flag               | Description                 | Default         |
+|--------------------|-----------------------------|-----------------|
+| `-p`, `--property` | Property name to update.    |  | 
+| `-v`, `--value`    | New value for the property. |  |
+
+
+#### `get-property`
+
+Get the value for a specified configuration property.
+
+```bash
+default(localhost)> config get-property -p webServiceUrl mycluster
+```
+
+Options
+
+| Flag               | Description                 | Default         |
+|--------------------|-----------------------------|-----------------|
+| `-p`, `--property` | Property name to update.    |  | 
+
 
 #### `view`