This is an automated email from the ASF dual-hosted git repository. nicoloboschi pushed a commit to branch branch-2.11 in repository https://gitbox.apache.org/repos/asf/pulsar.git
commit 4d0646b3cdff4fe7c85d94c7448a8e69f9395b6d Author: Nicolò Boschi <[email protected]> AuthorDate: Sat Sep 17 11:27:59 2022 +0200 [improve][cli] Pulsar shell: add command to set/get property of a config (#17651) (cherry picked from commit 4ec1009b42c226d6f9b9eb52b03e50bb4cdbb3cd) --- .../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 a6397fd6f84..aa385d0f7f6 100644 --- a/site2/docs/reference-cli-tools.md +++ b/site2/docs/reference-cli-tools.md @@ -1115,6 +1115,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`
