diff --git a/devtools/cli/src/main/java/io/quarkus/cli/Create.java b/devtools/cli/src/main/java/io/quarkus/cli/Create.java index fca521414d6f36..a1decff74abdc5 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/Create.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/Create.java @@ -34,6 +34,10 @@ public class Create extends BaseSubCommand implements Callable { "--example" }, order = 4, description = "Choose a specific example for the generated Quarkus application.") String example; + @CommandLine.Option(names = { "-c", + "--app-config" }, order = 11, description = "Application configs to be set in the application.properties/yml file. (key1=value1,key2=value2)") + private String appConfig; + @CommandLine.ArgGroup() TargetBuildTool targetBuildTool = new TargetBuildTool(); @@ -119,6 +123,7 @@ public Integer call() throws Exception { .example(example) .extensions(extensions) .noCode(noCode) + .appConfig(appConfig) .execute().isSuccess(); if (status) { diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliTest.java index f06d46d5322570..d118565d9f6045 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliTest.java @@ -6,8 +6,11 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; import java.util.Comparator; +import java.util.List; +import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -261,6 +264,24 @@ public void testCreateMaven() throws Exception { Assertions.assertTrue(pom.contains("quarkus-resteasy")); } + @Test + public void testCreateWithAppConfig() throws Exception { + Path project = workspace.resolve("my-project-with-configs"); + + List configs = Arrays.asList("custom.app.config1=val1", + "custom.app.config2=val2", "lib.config=val3"); + + execute("create", "--app-config=" + StringUtils.join(configs, ",")); + + Assertions.assertEquals(CommandLine.ExitCode.OK, exitCode); + Assertions.assertTrue(screen.contains("Project code-with-quarkus created")); + + Assertions.assertTrue(project.resolve("src/main/resources/application.properties").toFile().exists()); + String propertiesFile = readString(project.resolve("src/main/resources/application.properties")); + + configs.forEach(conf -> Assertions.assertTrue(propertiesFile.contains(conf))); + } + @Test public void testCreateGradleDefaults() throws Exception { Path project = workspace.resolve("code-with-quarkus"); diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java index 1862be5324a8b6..444af3f2875d0b 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java @@ -187,6 +187,9 @@ public class CreateProjectMojo extends AbstractMojo { @Parameter(property = "enableRegistryClient") private boolean enableRegistryClient; + @Parameter(property = "appConfig") + private String appConfig; + @Override public void execute() throws MojoExecutionException { @@ -290,7 +293,8 @@ public void execute() throws MojoExecutionException { .packageName(packageName) .extensions(extensions) .example(example) - .noCode(noCode); + .noCode(noCode) + .appConfig(appConfig); if (path != null) { createProject.setValue("path", path); } diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/core/strategy/SmartConfigMergeCodestartFileStrategyHandler.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/core/strategy/SmartConfigMergeCodestartFileStrategyHandler.java index 432b7d207aa01a..d0e3f452c96d62 100644 --- a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/core/strategy/SmartConfigMergeCodestartFileStrategyHandler.java +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/core/strategy/SmartConfigMergeCodestartFileStrategyHandler.java @@ -20,6 +20,7 @@ final class SmartConfigMergeCodestartFileStrategyHandler implements CodestartFil private static final ObjectMapper YAML_MAPPER = new ObjectMapper( new YAMLFactory().configure(YAMLGenerator.Feature.WRITE_DOC_START_MARKER, false)); + private static final String APP_CONFIG = "app-config"; @Override public String name() { @@ -32,7 +33,7 @@ public void process(Path targetDirectory, String relativePath, List checkNotEmptyCodestartFiles(codestartFiles); final String configType = getConfigType(data); - final Map config = new HashMap<>(); + final Map config = initConfigMap(data); for (TargetFile codestartFile : codestartFiles) { final String content = codestartFile.getContent(); if (!content.trim().isEmpty()) { @@ -87,4 +88,14 @@ private static String getConfigType(Map data) { final Optional config = CodestartData.getInputCodestartForType(data, CodestartType.CONFIG); return config.orElseThrow(() -> new CodestartException("Config type is required")); } + + @SuppressWarnings("unchecked") + private Map initConfigMap(final Map data) { + if (data.get(APP_CONFIG) instanceof Map) { + return NestedMaps.unflatten((Map) data.get(APP_CONFIG)); + } + + return new HashMap<>(); + } + } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartData.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartData.java index 14240b7cf274cc..ecd29e08dcb55c 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartData.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/codestarts/quarkus/QuarkusCodestartData.java @@ -43,7 +43,9 @@ public enum QuarkusDataKey implements DataKey { RESTEASY_REACTIVE_CODESTART_RESOURCE_CLASS_NAME("resteasy-reactive-codestart.resource.class-name"), SPRING_WEB_CODESTART_RESOURCE_PATH("spring-web-codestart.resource.path"), - SPRING_WEB_CODESTART_RESOURCE_CLASS_NAME("spring-web-codestart.resource.class-name"); + SPRING_WEB_CODESTART_RESOURCE_CLASS_NAME("spring-web-codestart.resource.class-name"), + + APP_CONFIG("app-config"); private final String key; diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java index 0f5626222fff23..f3c1266ddab588 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java @@ -1,5 +1,6 @@ package io.quarkus.devtools.commands; +import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.APP_CONFIG; import static io.quarkus.devtools.project.codegen.ProjectGenerator.*; import static java.util.Objects.requireNonNull; @@ -13,6 +14,7 @@ import io.quarkus.platform.tools.ToolsConstants; import io.quarkus.platform.tools.ToolsUtils; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -21,6 +23,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.lang.model.SourceVersion; +import org.apache.commons.lang3.StringUtils; /** * Instances of this class are not thread-safe. They are created per invocation. @@ -105,6 +108,16 @@ public CreateProject resourcePath(String resourcePath) { return this; } + public CreateProject appConfig(String appConfigAsString) { + Map configMap = Collections.emptyMap(); + + if (StringUtils.isNoneBlank(appConfigAsString)) { + configMap = ToolsUtils.stringToMap(appConfigAsString, ",", "="); + } + setValue(APP_CONFIG.key(), configMap); + return this; + } + /** * Use packageName instead as this one is only working with RESTEasy and SpringWeb */ diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java index acf3fe531c8d4b..27b822d1ae6522 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java @@ -2,6 +2,7 @@ import static io.quarkus.devtools.commands.CreateProject.EXAMPLE; import static io.quarkus.devtools.commands.CreateProject.EXTRA_CODESTARTS; +import static io.quarkus.devtools.codestarts.quarkus.QuarkusCodestartData.QuarkusDataKey.APP_CONFIG; import static io.quarkus.devtools.commands.CreateProject.NO_BUILDTOOL_WRAPPER; import static io.quarkus.devtools.commands.CreateProject.NO_CODE; import static io.quarkus.devtools.commands.CreateProject.NO_DOCKERFILES; @@ -99,6 +100,7 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws .noDockerfiles(invocation.getValue(NO_DOCKERFILES, false)) .addData(platformData) .addData(LegacySupport.convertFromLegacy(invocation.getValues())) + .putData(APP_CONFIG.key(), invocation.getValue(APP_CONFIG.key(), Collections.emptyMap())) .messageWriter(invocation.log()) .build(); invocation.log().info("-----------"); diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/tools/ToolsUtils.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/tools/ToolsUtils.java index 82a8c02a45aee8..8e2fc59613cf6d 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/tools/ToolsUtils.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/tools/ToolsUtils.java @@ -15,9 +15,11 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; +import org.apache.commons.lang3.StringUtils; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; @@ -39,6 +41,29 @@ public static String getProperty(String name, String defaultValue) { return System.getProperty(name, defaultValue); } + public static Map stringToMap( + String str, String entrySeparator, String keyValueSeparator) { + HashMap result = new HashMap<>(); + for (String entry : StringUtils.splitByWholeSeparator(str, entrySeparator)) { + String[] pair = StringUtils.splitByWholeSeparator(entry, keyValueSeparator, 2); + + if (pair.length > 0 && StringUtils.isBlank(pair[0])) { + throw new IllegalArgumentException("Entry with empty key " + entry); + } + + switch (pair.length) { + case 1: + result.put(pair[0].trim(), ""); + break; + case 2: + result.put(pair[0].trim(), pair[1].trim()); + break; + } + } + + return result; + } + public static boolean isNullOrEmpty(String arg) { return arg == null || arg.isEmpty(); } diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java index b2863ae12ed2f0..b2e304b238665b 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java @@ -16,6 +16,7 @@ import java.util.Properties; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.maven.model.Build; import org.apache.maven.model.Dependency; import org.apache.maven.model.DependencyManagement; @@ -31,6 +32,7 @@ import org.codehaus.plexus.util.xml.Xpp3Dom; import org.jboss.logmanager.LogManager; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import com.google.common.base.Charsets; @@ -359,6 +361,38 @@ public void testProjectGenerationFromScratchWithCustomDependencies() throws Exce && d.getVersion().equalsIgnoreCase("2.5"))).isTrue(); } + @Test + public void testProjectGenerationFromScratchWithAppConfigParameter() throws MavenInvocationException, IOException { + testDir = initEmptyProject("projects/project-generation-with-config-param"); + assertThat(testDir).isDirectory(); + invoker = initInvoker(testDir); + + Properties properties = new Properties(); + properties.put("projectGroupId", "org.acme"); + properties.put("projectArtifactId", "acme"); + properties.put("projectVersion", "1.0.0-SNAPSHOT"); + + List configs = Arrays.asList("custom.app.config1=val1", + "custom.app.config2=val2", "lib.config=val3"); + properties.put("appConfig", StringUtils.join(configs, ", ")); + + InvocationResult result = setup(properties); + + assertThat(result.getExitCode()).isZero(); + + // As the directory is not empty (log) navigate to the artifactID directory + testDir = new File(testDir, "acme"); + + assertThat(new File(testDir, "pom.xml")).isFile(); + assertThat(new File(testDir, "src/main/java")).isDirectory(); + + String file = Files + .asCharSource(new File(testDir, "src/main/resources/application.properties"), Charsets.UTF_8) + .read(); + configs.forEach(conf -> Assertions.assertTrue(file.contains(conf))); + + } + /** * Reproducer for https://github.com/quarkusio/quarkus/issues/671 */