Skip to content

Commit

Permalink
Merge pull request #30309 from iocanel/deploy-cli
Browse files Browse the repository at this point in the history
Introduce deploy CLI commands
  • Loading branch information
iocanel authored Mar 1, 2023
2 parents cfbb0a4 + 244021e commit 3ffbd93
Show file tree
Hide file tree
Showing 47 changed files with 1,482 additions and 273 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package io.quarkus.deployment.util;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;

public class DeploymentUtil {

public static final String DEPLOY = "quarkus.%s.deploy";
private static final Pattern QUARKUS_DEPLOY_PATTERN = Pattern.compile("quarkus\\.([^\\.]+)\\.deploy");

/**
* Get the available deployers.
* The list is obtained by checking for properties `quarkus.xxx.deploy`.
* These properties have a default value and thus they will be found regardless of the
* actual user configuration, so we check for property names instead.
*
* @return a {@link List} with all available deployers.
*/
public static List<String> getDeployers() {
Config config = ConfigProvider.getConfig();
return StreamSupport.stream(config.getPropertyNames().spliterator(), false)
.map(p -> QUARKUS_DEPLOY_PATTERN.matcher(p))
.filter(Matcher::matches)
.map(m -> m.group(1))
.collect(Collectors.toList());
}

/**
* @return a {@link Predicate} that tests if deploye is enabled.
*/
public static Predicate<String> isDeployExplicitlyEnabled() {
return deployer -> ConfigProvider.getConfig().getOptionalValue(String.format(DEPLOY, deployer), Boolean.class)
.orElse(false);
}

/**
* @return the name of the first deployer that is explicitly enabled
*/
public static Optional<String> getEnabledDeployer() {
return getDeployers().stream().filter(isDeployExplicitlyEnabled()).findFirst();
}

/**
* Check if any of the specified deployers is enabled.
*
* @param the name of the speicifed deployers.
* @return true if the specified deploy is explicitly enabled, fasle otherwise.
*/
public static boolean isDeploymentEnabled(String... deployer) {
return getDeployers().stream().filter(isDeployExplicitlyEnabled()).anyMatch(d -> Arrays.asList(deployer).contains(d));
}

/**
* @return true if deployment is explicitly enabled using: `quarkus.<deployment target>.deploy=true`.
*/
public static boolean isDeploymentEnabled() {
return getEnabledDeployer().isPresent();
}
}
63 changes: 63 additions & 0 deletions devtools/cli/src/main/java/io/quarkus/cli/BuildToolContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package io.quarkus.cli;

import java.nio.file.Path;
import java.util.List;

import io.quarkus.cli.common.BuildOptions;
import io.quarkus.cli.common.PropertiesOptions;
import io.quarkus.cli.common.RunModeOption;
import io.quarkus.devtools.project.BuildTool;
import io.quarkus.devtools.project.QuarkusProjectHelper;

/**
* Context class that holds all the required info that is passed to {@link BuildToolDelegatingCommand}.
* In many cases a subcommand delegates to the parent. It's cleaner to keep all the state in a single
* class to pass along.
*/
public class BuildToolContext {

private final Path projectRoot;
private final RunModeOption runModeOption;
private final BuildOptions buildOptions;
private final PropertiesOptions propertiesOptions;
private final List<String> forcedExtensions;
private final List<String> params;

public BuildToolContext(Path projectRoot, RunModeOption runModeOption, BuildOptions buildOptions,
PropertiesOptions propertiesOptions, List<String> forcedExtensions, List<String> params) {
this.projectRoot = projectRoot;
this.runModeOption = runModeOption;
this.buildOptions = buildOptions;
this.propertiesOptions = propertiesOptions;
this.forcedExtensions = forcedExtensions;
this.params = params;
}

public Path getProjectRoot() {
return projectRoot;
}

public RunModeOption getRunModeOption() {
return runModeOption;
}

public BuildOptions getBuildOptions() {
return buildOptions;
}

public PropertiesOptions getPropertiesOptions() {
return propertiesOptions;
}

public List<String> getForcedExtensions() {
return forcedExtensions;
}

public List<String> getParams() {
return params;
}

public BuildTool getBuildTool() {
return QuarkusProjectHelper.detectExistingBuildTool(projectRoot); // nullable
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package io.quarkus.cli;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;

import io.quarkus.cli.build.BuildSystemRunner;
import io.quarkus.cli.common.BuildOptions;
import io.quarkus.cli.common.HelpOption;
import io.quarkus.cli.common.OutputOptionMixin;
import io.quarkus.cli.common.PropertiesOptions;
import io.quarkus.cli.common.RunModeOption;
import io.quarkus.cli.registry.ToggleRegistryClientMixin;
import io.quarkus.cli.utils.GradleInitScript;
import io.quarkus.devtools.project.BuildTool;
import picocli.CommandLine;
import picocli.CommandLine.ExitCode;
import picocli.CommandLine.Parameters;

/**
* A cli command that delegates to the quarkus build system.
*/
public class BuildToolDelegatingCommand implements Callable<Integer> {

private static final String GRADLE_NO_BUILD_CACHE = "--no-build-cache";
private static final String GRADLE_NO_DAEMON = "--no-daemon";

@CommandLine.Spec
protected CommandLine.Model.CommandSpec spec;

@CommandLine.Mixin(name = "output")
protected OutputOptionMixin output;

@CommandLine.Mixin
protected ToggleRegistryClientMixin registryClient;

@CommandLine.Mixin
protected HelpOption helpOption;

@CommandLine.ArgGroup(exclusive = false, validate = false)
protected PropertiesOptions propertiesOptions = new PropertiesOptions();

@CommandLine.Mixin
private RunModeOption runMode;

@CommandLine.ArgGroup(order = 1, exclusive = false, validate = false, heading = "%nBuild options:%n")
private BuildOptions buildOptions = new BuildOptions();

@Parameters(description = "Additional parameters passed to the build system")
private List<String> params = new ArrayList<>();

private Path projectRoot;

private Path projectRoot() {
if (projectRoot == null) {
projectRoot = output.getTestDirectory();
if (projectRoot == null) {
projectRoot = Paths.get(System.getProperty("user.dir")).toAbsolutePath();
}
}
return projectRoot;
}

public BuildSystemRunner getRunner(BuildToolContext context) {
return BuildSystemRunner.getRunner(output, context.getPropertiesOptions(), registryClient, context.getProjectRoot(),
context.getBuildTool());
}

public void populateContext(BuildToolContext context) {
}

public Optional<BuildToolDelegatingCommand> getParentCommand() {
return Optional.empty();
}

public final Integer call(BuildToolContext context) throws Exception {
try {
populateContext(context);
prepare(context);
BuildSystemRunner runner = getRunner(context);
if (getParentCommand().isPresent()) {
return getParentCommand().get().call(context);
}

if (context.getRunModeOption().isDryRun()) {
output.info("Dry run option detected. Target command(s):");
}
String action = getAction(context)
.orElseThrow(() -> new IllegalStateException("Unknown action for " + runner.getBuildTool().name()));
BuildSystemRunner.BuildCommandArgs commandArgs = runner.prepareAction(action, context.getBuildOptions(),
context.getRunModeOption(), context.getParams());
if (context.getRunModeOption().isDryRun()) {
output.info(" " + commandArgs.showCommand());
return ExitCode.OK;
}
return runner.run(commandArgs);
} catch (Exception e) {
return output.handleCommandException(e, "Unable to build: " + e.getMessage());
}
}

@Override
public final Integer call() throws Exception {
return call(new BuildToolContext(projectRoot(), runMode, buildOptions, propertiesOptions, new ArrayList<>(),
new ArrayList<>(params)));
}

public void prepare(BuildToolContext context) {
BuildSystemRunner runner = getRunner(context);
if (runner.getBuildTool() == BuildTool.GRADLE) {
prepareGradle(context);
}

if (runner.getBuildTool() == BuildTool.MAVEN) {
prepareMaven(context);
}
}

public void prepareMaven(BuildToolContext context) {
BuildSystemRunner runner = getRunner(context);
BuildSystemRunner.BuildCommandArgs compileArgs = runner.prepareAction("compiler:compile", context.getBuildOptions(),
context.getRunModeOption(),
context.getParams());

//Let's check if we need to precompile and if so, include the command to the dry-run output.
//Note, that if the command should delegate to a the parent command dry-run output should be handled by the parent.
if (getParentCommand().isPresent()) {
return;
} else if (context.getRunModeOption().isDryRun()) {
output.info(" " + compileArgs.showCommand());
} else {
int compileExitCode = runner.run(compileArgs);
if (compileExitCode != ExitCode.OK) {
throw new RuntimeException("Failed to compile. Compilation exited with exit code:" + compileExitCode);
}
}
}

public void prepareGradle(BuildToolContext context) {
if (!context.getParams().contains(GRADLE_NO_BUILD_CACHE)) {
context.getParams().add(GRADLE_NO_BUILD_CACHE);
}

if (!context.getParams().contains(GRADLE_NO_DAEMON)) {
context.getParams().add(GRADLE_NO_DAEMON);
}
if (!context.getForcedExtensions().isEmpty()) {
GradleInitScript.populateForExtensions(context.getForcedExtensions(), context.getParams());
}
}

public Map<BuildTool, String> getActionMapping() {
return Collections.emptyMap();
}

public Optional<String> getAction(BuildToolContext context) {
return getParentCommand().map(cmd -> cmd.getAction(context))
.orElse(Optional.ofNullable(getActionMapping().get(getRunner(context).getBuildTool())));
}
}
30 changes: 30 additions & 0 deletions devtools/cli/src/main/java/io/quarkus/cli/Deploy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.quarkus.cli;

import java.util.Map;

import io.quarkus.cli.deploy.Kind;
import io.quarkus.cli.deploy.Knative;
import io.quarkus.cli.deploy.Kubernetes;
import io.quarkus.cli.deploy.Minikube;
import io.quarkus.cli.deploy.Openshift;
import io.quarkus.devtools.project.BuildTool;
import picocli.CommandLine;

@CommandLine.Command(name = "deploy", sortOptions = false, mixinStandardHelpOptions = false, header = "Deploy application.", subcommands = {
Kubernetes.class, Openshift.class, Knative.class, Kind.class, Minikube.class,
}, headerHeading = "%n", commandListHeading = "%nCommands:%n", synopsisHeading = "%nUsage: ", optionListHeading = "%nOptions:%n")
public class Deploy extends BuildToolDelegatingCommand {

private static final Map<BuildTool, String> ACTION_MAPPING = Map.of(BuildTool.MAVEN, "quarkus:deploy",
BuildTool.GRADLE, "deploy");

@Override
public Map<BuildTool, String> getActionMapping() {
return ACTION_MAPPING;
}

@Override
public String toString() {
return "Deploy {}";
}
}
3 changes: 2 additions & 1 deletion devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
import picocli.CommandLine.UnmatchedArgumentException;

@CommandLine.Command(name = "quarkus", subcommands = {
Create.class, Build.class, Dev.class, Test.class, ProjectExtensions.class, Image.class, Registry.class, Info.class,
Create.class, Build.class, Dev.class, Test.class, ProjectExtensions.class, Image.class, Deploy.class, Registry.class,
Info.class,
Update.class,
Completion.class }, scope = ScopeType.INHERIT, sortOptions = false, showDefaultValues = true, versionProvider = Version.class, subcommandsRepeatable = false, mixinStandardHelpOptions = false, commandListHeading = "%nCommands:%n", synopsisHeading = "%nUsage: ", optionListHeading = "Options:%n", headerHeading = "%n", parameterListHeading = "%n")
public class QuarkusCli implements QuarkusApplication, Callable<Integer> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import io.quarkus.cli.common.HelpOption;
import io.quarkus.cli.common.OutputOptionMixin;
Expand All @@ -30,7 +28,6 @@ public class BaseBuildCommand {
protected PropertiesOptions propertiesOptions = new PropertiesOptions();

Path projectRoot;
protected List<String> forcedExtensions = new ArrayList<>();

public Path projectRoot() {
if (projectRoot == null) {
Expand All @@ -47,10 +44,6 @@ public BuildSystemRunner getRunner() {
return BuildSystemRunner.getRunner(output, propertiesOptions, registryClient, projectRoot(), buildTool);
}

public List<String> getForcedExtensions() {
return forcedExtensions;
}

/**
* Commands using `@ParentCommand` need to set the ouput.
* This is needed for testing purposes.
Expand Down
Loading

0 comments on commit 3ffbd93

Please sign in to comment.