org.wildfly.plugin.provision.ApplicationImageMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of wildfly-maven-plugin Show documentation
Show all versions of wildfly-maven-plugin Show documentation
A maven plugin that allows various management operations to be executed on WildFly Application
Server.
/*
* JBoss, Home of Professional Open Source.
* Copyright 2022, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.wildfly.plugin.provision;
import static java.lang.String.format;
import static java.lang.String.join;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.Arrays;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.wildfly.plugin.common.PropertyNames;
/**
* Build (and push) an application image containing the provisioned server and the deployment.
*
* The {@code image} goal extends the {@code package} goal, building and pushing the image occurs after the server
* is provisioned and the deployment deployed in it.
*
* The {@code image} goal relies on a Docker binary to execute all image commands (build, login, push).
*
* @since 4.0
*/
@Mojo(name = "image", requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, defaultPhase = LifecyclePhase.PACKAGE)
public class ApplicationImageMojo extends PackageServerMojo {
public static final int DOCKER_CMD_CHECK_TIMEOUT = 3000;
/**
* The configuration of the application image.
*
* The {@code image} goal accepts the following configuration:
*
*
*
* <image>
* <!-- (optional) set it to false to skip build the application image (true by default) -->
* <build>true</build>
*
* <!-- (optional) set it to true to (login and) push the application image to the container registry (false by default).
*
* If user and password are not specified, the image goal will not attempt to login to the container
* registry prior to pushing the image.
* The login to the container registry must then be performed before Maven is run.
* -->
* <push>true</push>
*
* <!-- (optional) The binary used to perform image commands (build, login, push) (default is "docker") -->
* <docker-binary>docker</docker-binary>
*
* <!-- (optional) the JDK version used by the application. Allowed values are "11" and "17". If unspecified, the "latest" tag is used to determine the JDK version used by WildFly runtime image -->
* <jdk-version>11</jdk-version>
*
* <!-- (optional) The group part of the name of the application image -->
* <group>${user.name}</group>
*
* <!-- (optional) The name part of the application image. If not set, the value of the artifactId (in lower case) is used -->
* <name>${project.artifactId}</name>
*
* <!-- (optional) The tag part of the application image (default is "latest") -->
* <tag>latest</tag>
*
* <!-- (optional) The container registry. If set, the registry is added to the application name.
* If the image is pushed and the registry is not set, it defaults to "docker.io" to login to the registry
* -->
* <registry>quay.io</registry>
*
* <!-- (optional) The user name to login to the container registry (if push is enabled). -->
* <user>${user.name}</user>
*
* <!-- (optional) The password login to the container registry (if push is enabled) -->
* <password>${my.secret.password}</password>
* </image>
*
*/
@Parameter(alias = "image")
private ApplicationImageInfo image;
@Override
protected String getGoal() {
return "image";
}
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
// when the application image is built, the deployment step is skipped.
// This allows to create 2 different Docker layers (1 for the server and 1 for the deployments)
this.skipDeployment = true;
super.execute();
if (image == null) {
image = new ApplicationImageInfo();
}
try {
// The Dockerfile is always generated when the image goal is run.
// This allows the user to then use the generated Dockerfile in other contexts than Maven.
String runtimeImage = this.image.getWildFlyRuntimeImage();
getLog().info(format("Generating Dockerfile %s from base image %s",
Paths.get(project.getBuild().getDirectory()).resolve("Dockerfile"),
runtimeImage));
generateDockerfile(runtimeImage, Paths.get(project.getBuild().getDirectory()), provisioningDir);
if (!image.build) {
return;
}
// Check if the binary was set via a property
image.setDockerBinary(project.getProperties().getProperty(PropertyNames.WILDFLY_IMAGE_BINARY,
System.getProperty(PropertyNames.WILDFLY_IMAGE_BINARY)));
final String imageBinary = image.getDockerBinary();
if (imageBinary == null) {
throw new MojoExecutionException("Could not locate a binary to build the image with. Please check your " +
"installation and either set the path to the binary in your PATH environment variable or define the " +
"define the fully qualified path in your configuration, /path/to/docker . "
+
"The path can also be defined with the -Dwildfly.image.binary=/path/to/docker system property.");
}
if (!isImageBinaryAvailable(imageBinary)) {
throw new MojoExecutionException(
String.format("Unable to build application image with %1$s. Please check your %1$s installation",
imageBinary));
}
String image = this.image.getApplicationImageName(project.getArtifactId());
boolean buildSuccess = buildApplicationImage(image);
if (!buildSuccess) {
throw new MojoExecutionException(String.format("Unable to build application image %s", image));
}
getLog().info(String.format("Successfully built application image %s", image));
if (this.image.push) {
logToRegistry();
boolean pushSuccess = pushApplicationImage(image);
if (!pushSuccess) {
throw new MojoExecutionException(String.format("Unable to push application image %s", image));
}
getLog().info(String.format("Successfully pushed application image %s", image));
}
} catch (IOException e) {
throw new MojoExecutionException(e.getLocalizedMessage(), e);
}
}
private void logToRegistry() throws MojoExecutionException {
String registry = image.registry;
if (registry == null) {
getLog().info("Registry was not set. Using docker.io");
}
if (image.user != null && image.password != null) {
String[] dockerArgs = new String[] {
"login", registry,
"-u", image.user,
"-p", image.password
};
boolean loginSuccessful = ExecUtil.exec(getLog(), image.getDockerBinary(), dockerArgs);
if (!loginSuccessful) {
throw new MojoExecutionException(
String.format("Could not log to the container registry with the command %s %s %s",
image.getDockerBinary(),
String.join(" ", Arrays.copyOf(dockerArgs, dockerArgs.length - 1)),
"*******"));
}
}
}
private boolean buildApplicationImage(String image) throws IOException {
getLog().info(format("Building application image %s using %s.", image, this.image.getDockerBinary()));
String[] dockerArgs = new String[] { "build", "-t", image, "." };
getLog().info(format("Executing the following command to build application image: '%s %s'",
this.image.getDockerBinary(), join(" ", dockerArgs)));
return ExecUtil.exec(getLog(), Paths.get(project.getBuild().getDirectory()).toFile(), this.image.getDockerBinary(),
dockerArgs);
}
private boolean pushApplicationImage(String image) {
getLog().info(format("Pushing application image %s using %s.", image, this.image.getDockerBinary()));
String[] dockerArgs = new String[] { "push", image };
getLog().info(format("Executing the following command to push application image: '%s %s'", this.image.getDockerBinary(),
join(" ", dockerArgs)));
return ExecUtil.exec(getLog(), Paths.get(project.getBuild().getDirectory()).toFile(), this.image.getDockerBinary(),
dockerArgs);
}
private void generateDockerfile(String runtimeImage, Path targetDir, String wildflyDirectory)
throws IOException, MojoExecutionException {
Path jbossHome = Path.of(wildflyDirectory);
// Docker requires the source file be relative to the context directory. From the documentation:
// The path must be inside the context of the build; you cannot COPY ../something /something, because
// the first step of a docker build is to send the context directory (and subdirectories) to the docker daemon.
if (jbossHome.isAbsolute()) {
jbossHome = targetDir.relativize(jbossHome);
}
String targetName = getDeploymentTargetName();
Files.writeString(targetDir.resolve("Dockerfile"),
"FROM " + runtimeImage + "\n" +
"COPY --chown=jboss:root " + jbossHome + " $JBOSS_HOME\n" +
"RUN chmod -R ug+rwX $JBOSS_HOME\n" +
"COPY --chown=jboss:root " + getDeploymentContent().getFileName()
+ " $JBOSS_HOME/standalone/deployments/" + targetName,
StandardCharsets.UTF_8);
}
private boolean isImageBinaryAvailable(String imageBinary) {
try {
if (!ExecUtil.execSilentWithTimeout(Duration.ofMillis(DOCKER_CMD_CHECK_TIMEOUT), imageBinary, "-v")) {
getLog().warn(format("'%1$s -v' returned an error code. Make sure your %1$s binary is correct", imageBinary));
return false;
}
} catch (Exception e) {
getLog().warn(format("No %s binary found or general error: %s", imageBinary, e));
return false;
}
return true;
}
}