From 5fe2434d86e4c32d9afcf58f7ba90b5a249ae899 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 14 Jun 2024 11:54:19 +0100 Subject: [PATCH] Improve Config CLI --- .../src/main/java/io/quarkus/cli/Config.java | 8 ++- .../quarkus/cli/config/BaseConfigCommand.java | 17 ++++++ .../java/io/quarkus/cli/config/Encrypt.java | 11 ++-- .../io/quarkus/cli/config/RemoveConfig.java | 44 +++++++++++++++ .../java/io/quarkus/cli/config/SetConfig.java | 54 ++++++------------ .../io/quarkus/cli/config/EncryptTest.java | 6 +- .../quarkus/cli/config/RemoveConfigTest.java | 56 +++++++++++++++++++ .../io/quarkus/cli/config/SetConfigTest.java | 45 +++++---------- 8 files changed, 164 insertions(+), 77 deletions(-) create mode 100644 devtools/cli/src/main/java/io/quarkus/cli/config/RemoveConfig.java create mode 100644 devtools/cli/src/test/java/io/quarkus/cli/config/RemoveConfigTest.java diff --git a/devtools/cli/src/main/java/io/quarkus/cli/Config.java b/devtools/cli/src/main/java/io/quarkus/cli/Config.java index d86cf5cbed6c0..3de4f09df9ae8 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/Config.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/Config.java @@ -3,17 +3,23 @@ import java.util.List; import java.util.concurrent.Callable; +import io.quarkus.cli.common.HelpOption; import io.quarkus.cli.common.OutputOptionMixin; import io.quarkus.cli.config.Encrypt; +import io.quarkus.cli.config.RemoveConfig; import io.quarkus.cli.config.SetConfig; import picocli.CommandLine; import picocli.CommandLine.Command; -@Command(name = "config", header = "Manage Quarkus configuration", subcommands = { SetConfig.class, Encrypt.class }) +@Command(name = "config", header = "Manage Quarkus configuration", subcommands = { SetConfig.class, RemoveConfig.class, + Encrypt.class }) public class Config implements Callable { @CommandLine.Mixin(name = "output") protected OutputOptionMixin output; + @CommandLine.Mixin + protected HelpOption helpOption; + @CommandLine.Spec protected CommandLine.Model.CommandSpec spec; diff --git a/devtools/cli/src/main/java/io/quarkus/cli/config/BaseConfigCommand.java b/devtools/cli/src/main/java/io/quarkus/cli/config/BaseConfigCommand.java index 2cd4b16318d49..11618b7b6e37f 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/config/BaseConfigCommand.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/config/BaseConfigCommand.java @@ -3,14 +3,20 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Base64; +import java.util.List; +import io.quarkus.cli.common.HelpOption; import io.quarkus.cli.common.OutputOptionMixin; +import io.smallrye.config.ConfigValue; import picocli.CommandLine; public class BaseConfigCommand { @CommandLine.Mixin(name = "output") protected OutputOptionMixin output; + @CommandLine.Mixin + protected HelpOption helpOption; + @CommandLine.Spec protected CommandLine.Model.CommandSpec spec; @@ -29,4 +35,15 @@ protected Path projectRoot() { protected String encodeToString(byte[] data) { return Base64.getUrlEncoder().withoutPadding().encodeToString(data); } + + protected ConfigValue findKey(List lines, String name) { + ConfigValue configValue = ConfigValue.builder().withName(name).build(); + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + if (line.startsWith(configValue.getName() + "=")) { + return configValue.withValue(line.substring(name.length() + 1)).withLineNumber(i); + } + } + return configValue; + } } diff --git a/devtools/cli/src/main/java/io/quarkus/cli/config/Encrypt.java b/devtools/cli/src/main/java/io/quarkus/cli/config/Encrypt.java index ffd51e49a5280..708c40df12ef8 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/config/Encrypt.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/config/Encrypt.java @@ -15,16 +15,17 @@ import picocli.CommandLine.Command; import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; @Command(name = "encrypt", aliases = "enc", header = "Encrypt Secrets using AES/GCM/NoPadding algorithm by default") public class Encrypt extends BaseConfigCommand implements Callable { - @Option(required = true, names = { "-s", "--secret" }, description = "Secret") + @Parameters(index = "0", paramLabel = "SECRET", description = "The Secret value to encrypt") String secret; - @Option(names = { "-k", "--key" }, description = "Encryption Key") + @Option(names = { "-k", "--key" }, description = "The Encryption Key") String encryptionKey; - @Option(names = { "-f", "--format" }, description = "Encryption Key Format (base64 / plain)", defaultValue = "base64") + @Option(names = { "-f", "--format" }, description = "The Encryption Key Format (base64 / plain)", defaultValue = "base64") KeyFormat encryptionKeyFormat; @Option(hidden = true, names = { "-a", "--algorithm" }, description = "Algorithm", defaultValue = "AES") @@ -33,7 +34,7 @@ public class Encrypt extends BaseConfigCommand implements Callable { @Option(hidden = true, names = { "-m", "--mode" }, description = "Mode", defaultValue = "GCM") String mode; - @Option(hidden = true, names = { "-p", "--padding" }, description = "Algorithm", defaultValue = "NoPadding") + @Option(hidden = true, names = { "-p", "--padding" }, description = "Padding", defaultValue = "NoPadding") String padding; @Option(hidden = true, names = { "-q", "--quiet" }, defaultValue = "false") @@ -68,7 +69,7 @@ public Integer call() throws Exception { this.encryptedSecret = Base64.getUrlEncoder().withoutPadding().encodeToString((message.array())); if (!quiet) { System.out.println("Encrypted Secret: " + encryptedSecret); - System.out.println("Encryption Key: " + encryptionKey); + System.out.println("Encryption Key (" + encryptionKeyFormat + "): " + encryptionKey); } return 0; diff --git a/devtools/cli/src/main/java/io/quarkus/cli/config/RemoveConfig.java b/devtools/cli/src/main/java/io/quarkus/cli/config/RemoveConfig.java new file mode 100644 index 0000000000000..25271f497b943 --- /dev/null +++ b/devtools/cli/src/main/java/io/quarkus/cli/config/RemoveConfig.java @@ -0,0 +1,44 @@ +package io.quarkus.cli.config; + +import java.io.BufferedWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.Callable; + +import io.smallrye.config.ConfigValue; +import picocli.CommandLine; + +@CommandLine.Command(name = "remove", header = "Removes a configuration in application.properties") +public class RemoveConfig extends BaseConfigCommand implements Callable { + @CommandLine.Parameters(index = "0", arity = "1", paramLabel = "NAME", description = "Configuration name") + String name; + + @Override + public Integer call() throws Exception { + Path properties = projectRoot().resolve("src/main/resources/application.properties"); + if (!properties.toFile().exists()) { + System.out.println("Could not find an application.properties file"); + return 0; + } + + List lines = Files.readAllLines(properties); + + ConfigValue configValue = findKey(lines, name); + if (configValue.getLineNumber() != -1) { + System.out.println("Removing configuration " + name); + lines.remove(configValue.getLineNumber()); + } else { + System.out.println("Could not find configuration " + name); + } + + try (BufferedWriter writer = Files.newBufferedWriter(properties)) { + for (String i : lines) { + writer.write(i); + writer.newLine(); + } + } + + return 0; + } +} diff --git a/devtools/cli/src/main/java/io/quarkus/cli/config/SetConfig.java b/devtools/cli/src/main/java/io/quarkus/cli/config/SetConfig.java index a88915808094a..bfb2d0adc5514 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/config/SetConfig.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/config/SetConfig.java @@ -10,22 +10,27 @@ import io.smallrye.config.ConfigValue; import io.smallrye.config.Converters; import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; -@CommandLine.Command(name = "set") +@Command(name = "set", header = "Sets a configuration in application.properties") public class SetConfig extends BaseConfigCommand implements Callable { - @CommandLine.Option(required = true, names = { "-n", "--name" }, description = "Configuration name") + @Parameters(index = "0", arity = "1", paramLabel = "NAME", description = "Configuration name") String name; - @CommandLine.Option(names = { "-a", "--value" }, description = "Configuration value") + @Parameters(index = "1", arity = "0..1", paramLabel = "VALUE", description = "Configuration value") String value; - @CommandLine.Option(names = { "-k", "--encrypt" }, description = "Encrypt value") + @Option(names = { "-k", "--encrypt" }, description = "Encrypt the configuration value") boolean encrypt; @Override public Integer call() throws Exception { Path properties = projectRoot().resolve("src/main/resources/application.properties"); if (!properties.toFile().exists()) { - System.out.println("Could not find an application.properties file"); - return 0; + System.out.println("Could not find an application.properties file. Creating one..."); + Path resources = projectRoot().resolve("src/main/resources"); + Files.createDirectories(resources); + Files.createFile(resources.resolve("application.properties")); } List lines = Files.readAllLines(properties); @@ -37,7 +42,7 @@ public Integer call() throws Exception { if (value == null) { value = findKey(lines, name).getValue(); } - args.add("--secret=" + value); + args.add(value); if (value == null || value.length() == 0) { System.out.println("Cannot encrypt an empty value"); return -1; @@ -64,25 +69,13 @@ public Integer call() throws Exception { } } - int nameLineNumber = -1; - for (int i = 0; i < lines.size(); i++) { - String line = lines.get(i); - if (line.startsWith(name + "=")) { - nameLineNumber = i; - break; - } - } - - if (nameLineNumber != -1) { - if (value != null) { - System.out.println("Setting " + name + " to " + value); - lines.set(nameLineNumber, name + "=" + value); - } else { - System.out.println("Removing " + name); - lines.remove(nameLineNumber); - } + ConfigValue configValue = findKey(lines, name); + String actualValue = value != null ? value : "empty value"; + if (configValue.getLineNumber() != -1) { + System.out.println("Setting configuration " + name + " to " + actualValue); + lines.set(configValue.getLineNumber(), name + "=" + (value != null ? value : "")); } else { - System.out.println("Adding " + name + " with " + value); + System.out.println("Adding configuration " + name + " with " + actualValue); lines.add(name + "=" + (value != null ? value : "")); } @@ -95,15 +88,4 @@ public Integer call() throws Exception { return 0; } - - public static ConfigValue findKey(List lines, String name) { - ConfigValue configValue = ConfigValue.builder().withName(name).build(); - for (int i = 0; i < lines.size(); i++) { - final String line = lines.get(i); - if (line.startsWith(configValue.getName() + "=")) { - return configValue.withValue(line.substring(name.length() + 1)).withLineNumber(i); - } - } - return configValue; - } } diff --git a/devtools/cli/src/test/java/io/quarkus/cli/config/EncryptTest.java b/devtools/cli/src/test/java/io/quarkus/cli/config/EncryptTest.java index 1217e2f6d4e32..a879aa8226731 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/config/EncryptTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/config/EncryptTest.java @@ -18,7 +18,7 @@ class EncryptTest { @Test void encrypt() throws Exception { - CliDriver.Result result = CliDriver.execute(tempDir, "config", "encrypt", "--secret=12345678"); + CliDriver.Result result = CliDriver.execute(tempDir, "config", "encrypt", "12345678"); Scanner scanner = new Scanner(result.getStdout()); String secret = scanner.nextLine().split(": ")[1]; String encryptionKey = scanner.nextLine().split(": ")[1]; @@ -35,7 +35,7 @@ void encrypt() throws Exception { @Test void keyPlain() throws Exception { - CliDriver.Result result = CliDriver.execute(tempDir, "config", "encrypt", "--secret=12345678", "-f=plain", + CliDriver.Result result = CliDriver.execute(tempDir, "config", "encrypt", "12345678", "-f=plain", "--key=12345678"); Scanner scanner = new Scanner(result.getStdout()); String secret = scanner.nextLine().split(": ")[1]; @@ -62,7 +62,7 @@ void keyPlain() throws Exception { @Test void keyBase64() throws Exception { - CliDriver.Result result = CliDriver.execute(tempDir, "config", "encrypt", "--secret=12345678", "--key=12345678"); + CliDriver.Result result = CliDriver.execute(tempDir, "config", "encrypt", "12345678", "--key=12345678"); Scanner scanner = new Scanner(result.getStdout()); String secret = scanner.nextLine().split(": ")[1]; diff --git a/devtools/cli/src/test/java/io/quarkus/cli/config/RemoveConfigTest.java b/devtools/cli/src/test/java/io/quarkus/cli/config/RemoveConfigTest.java new file mode 100644 index 0000000000000..4199d4eceaaff --- /dev/null +++ b/devtools/cli/src/test/java/io/quarkus/cli/config/RemoveConfigTest.java @@ -0,0 +1,56 @@ +package io.quarkus.cli.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.FileOutputStream; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Properties; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import io.quarkus.cli.CliDriver; +import io.smallrye.config.PropertiesConfigSource; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; + +public class RemoveConfigTest { + @TempDir + Path tempDir; + + @BeforeEach + void setUp() throws Exception { + Path resources = tempDir.resolve("src/main/resources"); + Files.createDirectories(resources); + Files.createFile(resources.resolve("application.properties")); + } + + @Test + void removeConfiguration() throws Exception { + Path propertiesFile = tempDir.resolve("src/main/resources/application.properties"); + Properties properties = new Properties(); + try (InputStream inputStream = propertiesFile.toUri().toURL().openStream()) { + properties.load(inputStream); + } + properties.put("foo.bar", "1234"); + try (FileOutputStream outputStream = new FileOutputStream(propertiesFile.toFile())) { + properties.store(outputStream, ""); + } + CliDriver.Result result = CliDriver.execute(tempDir, "config", "remove", "foo.bar"); + assertEquals(0, result.getExitCode()); + assertTrue(result.getStdout().contains("Removing configuration foo.bar")); + assertTrue(config().getOptionalValue("foo.bar", String.class).isEmpty()); + } + + private SmallRyeConfig config() throws Exception { + PropertiesConfigSource propertiesConfigSource = new PropertiesConfigSource( + tempDir.resolve("src/main/resources/application.properties").toUri().toURL()); + return new SmallRyeConfigBuilder() + .withSources(propertiesConfigSource) + .build(); + } +} diff --git a/devtools/cli/src/test/java/io/quarkus/cli/config/SetConfigTest.java b/devtools/cli/src/test/java/io/quarkus/cli/config/SetConfigTest.java index 259b0afc8cece..061fabf5309c8 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/config/SetConfigTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/config/SetConfigTest.java @@ -2,7 +2,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.FileOutputStream; @@ -32,15 +31,15 @@ void setUp() throws Exception { } @Test - void createConfiguration() throws Exception { - CliDriver.Result result = CliDriver.execute(tempDir, "config", "set", "--name=foo.bar", "--value=1234"); + void addConfiguration() throws Exception { + CliDriver.Result result = CliDriver.execute(tempDir, "config", "set", "foo.bar", "1234"); assertEquals(0, result.getExitCode()); - assertTrue(result.getStdout().contains("Adding foo.bar with 1234")); + assertTrue(result.getStdout().contains("Adding configuration foo.bar with 1234")); assertEquals("1234", config().getRawValue("foo.bar")); } @Test - void updateConfiguration() throws Exception { + void setConfiguration() throws Exception { Path propertiesFile = tempDir.resolve("src/main/resources/application.properties"); Properties properties = new Properties(); try (InputStream inputStream = propertiesFile.toUri().toURL().openStream()) { @@ -50,35 +49,17 @@ void updateConfiguration() throws Exception { try (FileOutputStream outputStream = new FileOutputStream(propertiesFile.toFile())) { properties.store(outputStream, ""); } - CliDriver.Result result = CliDriver.execute(tempDir, "config", "set", "--name=foo.bar", "--value=5678"); + CliDriver.Result result = CliDriver.execute(tempDir, "config", "set", "foo.bar", "5678"); assertEquals(0, result.getExitCode()); - assertTrue(result.getStdout().contains("Setting foo.bar to 5678")); + assertTrue(result.getStdout().contains("Setting configuration foo.bar to 5678")); assertEquals("5678", config().getRawValue("foo.bar")); } @Test - void deleteConfiguration() throws Exception { - Path propertiesFile = tempDir.resolve("src/main/resources/application.properties"); - Properties properties = new Properties(); - try (InputStream inputStream = propertiesFile.toUri().toURL().openStream()) { - properties.load(inputStream); - } - properties.put("foo.bar", "1234"); - try (FileOutputStream outputStream = new FileOutputStream(propertiesFile.toFile())) { - properties.store(outputStream, ""); - } - - CliDriver.Result result = CliDriver.execute(tempDir, "config", "set", "--name=foo.bar"); - assertEquals(0, result.getExitCode()); - assertTrue(result.getStdout().contains("Removing foo.bar")); - assertNull(config().getConfigValue("foo.bar").getValue()); - } - - @Test - void createEncryptedConfiguration() throws Exception { - CliDriver.Result result = CliDriver.execute(tempDir, "config", "set", "--name=foo.bar", "--value=1234", "-k"); + void addEncryptedConfiguration() throws Exception { + CliDriver.Result result = CliDriver.execute(tempDir, "config", "set", "foo.bar", "1234", "-k"); assertEquals(0, result.getExitCode()); - assertTrue(result.getStdout().contains("Adding foo.bar")); + assertTrue(result.getStdout().contains("Adding configuration foo.bar")); SmallRyeConfig config = config(); assertEquals("1234", config.getConfigValue("foo.bar").getValue()); @@ -86,9 +67,9 @@ void createEncryptedConfiguration() throws Exception { String encryption = config.getRawValue("smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key"); assertNotNull(encryption); - result = CliDriver.execute(tempDir, "config", "set", "--name=foo.baz", "--value=5678", "-k"); + result = CliDriver.execute(tempDir, "config", "set", "foo.baz", "5678", "-k"); assertEquals(0, result.getExitCode()); - assertTrue(result.getStdout().contains("Adding foo.baz")); + assertTrue(result.getStdout().contains("Adding configuration foo.baz")); config = config(); @@ -98,7 +79,7 @@ void createEncryptedConfiguration() throws Exception { } @Test - void updateEncryptedConfiguration() throws Exception { + void setEncryptedConfiguration() throws Exception { Path propertiesFile = tempDir.resolve("src/main/resources/application.properties"); Properties properties = new Properties(); try (InputStream inputStream = propertiesFile.toUri().toURL().openStream()) { @@ -109,7 +90,7 @@ void updateEncryptedConfiguration() throws Exception { properties.store(outputStream, ""); } - CliDriver.Result result = CliDriver.execute(tempDir, "config", "set", "--name=foo.bar", "-k"); + CliDriver.Result result = CliDriver.execute(tempDir, "config", "set", "foo.bar", "-k"); assertEquals(0, result.getExitCode()); SmallRyeConfig config = config();