Skip to content

Commit de36978

Browse files
committed
Replace the docker/podman binary by Jib to build the image
Signed-off-by: Jeff Mesnil <[email protected]>
1 parent fa33d98 commit de36978

File tree

5 files changed

+124
-132
lines changed

5 files changed

+124
-132
lines changed

plugin/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,18 @@
8787
</build>
8888

8989
<dependencies>
90+
<dependency>
91+
<groupId>com.google.cloud.tools</groupId>
92+
<artifactId>jib-core</artifactId>
93+
</dependency>
94+
<dependency>
95+
<groupId>org.apache.commons</groupId>
96+
<artifactId>commons-compress</artifactId>
97+
</dependency>
98+
<dependency>
99+
<groupId>commons-io</groupId>
100+
<artifactId>commons-io</artifactId>
101+
</dependency>
90102
<!-- Needed my maven-core -->
91103
<dependency>
92104
<groupId>javax.inject</groupId>

plugin/src/main/java/org/wildfly/plugin/common/PropertyNames.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ public interface PropertyNames {
106106

107107
String WILDFLY_FEATURE_PACK_LOCATION = "wildfly.feature-pack.location";
108108

109+
String WILDFLY_IMAGE_ARCHS = "wildfly.image.archs";
110+
109111
String WILDFLY_IMAGE_BINARY = "wildfly.image.binary";
110112

111113
String WILDFLY_LAYERS_CONFIGURATION_FILE_NAME = "wildfly.provisioning.layers.configuration.file.name";

plugin/src/main/java/org/wildfly/plugin/provision/ApplicationImageMojo.java

Lines changed: 93 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@
44
*/
55
package org.wildfly.plugin.provision;
66

7+
import static com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer.DEFAULT_MODIFICATION_TIME_PROVIDER;
78
import static java.lang.String.format;
89
import static java.lang.String.join;
910

1011
import java.io.IOException;
11-
import java.nio.charset.StandardCharsets;
12-
import java.nio.file.Files;
1312
import java.nio.file.Path;
1413
import java.nio.file.Paths;
15-
import java.time.Duration;
1614
import java.util.ArrayList;
1715
import java.util.List;
1816
import java.util.Map;
@@ -33,13 +31,24 @@
3331
import org.wildfly.plugin.common.PropertyNames;
3432
import org.wildfly.plugin.core.Constants;
3533

34+
import com.google.cloud.tools.jib.api.Containerizer;
35+
import com.google.cloud.tools.jib.api.DockerDaemonImage;
36+
import com.google.cloud.tools.jib.api.ImageReference;
37+
import com.google.cloud.tools.jib.api.Jib;
38+
import com.google.cloud.tools.jib.api.JibContainerBuilder;
39+
import com.google.cloud.tools.jib.api.RegistryImage;
40+
import com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;
41+
import com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;
42+
import com.google.cloud.tools.jib.api.buildplan.FilePermissions;
43+
import com.google.cloud.tools.jib.api.buildplan.FilePermissionsProvider;
44+
import com.google.cloud.tools.jib.api.buildplan.OwnershipProvider;
45+
3646
/**
3747
* Build (and push) an application image containing the provisioned server and the deployment.
3848
* <p>
3949
* The {@code image} goal extends the {@code package} goal, building and pushing the image occurs after the server
4050
* is provisioned and the deployment deployed in it.
4151
* <p>
42-
* The {@code image} goal relies on a Docker binary to execute all image commands (build, login, push).
4352
*
4453
* <p>
4554
* Note that if a WildFly Bootable JAR is packaged, it is ignored when building the image.
@@ -51,8 +60,6 @@
5160
@SuppressWarnings({ "deprecated", "removal" })
5261
public class ApplicationImageMojo extends PackageServerMojo {
5362

54-
public static final int DOCKER_CMD_CHECK_TIMEOUT = 3000;
55-
5663
/**
5764
* Provides a reference to the settings file.
5865
*/
@@ -84,10 +91,7 @@ public class ApplicationImageMojo extends PackageServerMojo {
8491
* --&gt;
8592
* &lt;push&gt;true&lt;/push&gt;
8693
*
87-
* &lt;!-- (optional) The binary used to perform image commands (build, login, push) (default is "docker") --&gt;
88-
* &lt;docker-binary&gt;docker&lt;/docker-binary&gt;
89-
*
90-
* &lt;!-- (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 --&gt;
94+
* &lt;!-- (optional) the JDK version used by the application. Allowed values are "11", "17", and "21". If unspecified, the "latest" tag is used to determine the JDK version used by WildFly runtime image --&gt;
9195
* &lt;jdk-version&gt;11&lt;/jdk-version&gt;
9296
*
9397
* &lt;!-- (optional) The group part of the name of the application image --&gt;
@@ -213,9 +217,23 @@ public class ApplicationImageMojo extends PackageServerMojo {
213217
*
214218
* @since 5.0.1
215219
*/
220+
@Deprecated(forRemoval = true, since = "5.0.2")
216221
@Parameter(alias = "docker-binary", property = PropertyNames.WILDFLY_IMAGE_BINARY)
217222
private String dockerBinary;
218223

224+
/**
225+
* The list of architectures of the images (If not set, it defaults to {@code amd64,arm64}).
226+
*
227+
* Multiple architectures does not work with Docker or Podman.
228+
* To build multiarch images, you must specify a registry and push the image ({@code registry} must be set and {@code push}
229+
* must be true).
230+
* Only `linux` operating system is supported.
231+
*
232+
* @since 5.0.1
233+
*/
234+
@Parameter(alias = "archs", property = PropertyNames.WILDFLY_IMAGE_ARCHS)
235+
private String archs = "amd64,arm64";
236+
219237
/**
220238
* Adds labels to the generated Dockerfile. Each label will be added as a new line with the prefix of {@code LABEL}.
221239
* For example:
@@ -250,6 +268,11 @@ protected String getGoal() {
250268
return "image";
251269
}
252270

271+
private static final FilePermissionsProvider PERMISSIONS_PROVIDER = (sourcePath, destinationPath) -> FilePermissions
272+
.fromOctalString("775");
273+
274+
private static final OwnershipProvider JBOSS_ROOT_OWNER = (sourcePath, destinationPath) -> "jboss:root";
275+
253276
@Override
254277
public void execute() throws MojoExecutionException, MojoFailureException {
255278
// when the application image is built, the deployment step is skipped.
@@ -288,80 +311,26 @@ public void execute() throws MojoExecutionException, MojoFailureException {
288311
}
289312

290313
try {
291-
// The Dockerfile is always generated when the image goal is run.
292-
// This allows the user to then use the generated Dockerfile in other contexts than Maven.
293-
String runtimeImage = this.image.getWildFlyRuntimeImage();
294-
getLog().info(format("Generating Dockerfile %s from base image %s",
295-
Paths.get(project.getBuild().getDirectory()).resolve("Dockerfile"),
296-
runtimeImage));
297-
generateDockerfile(runtimeImage, Paths.get(project.getBuild().getDirectory()), provisioningDir);
298-
299314
if (!image.build) {
300315
return;
301316
}
302-
// Check if the binary was set via a property
303-
if (image.dockerBinary == null) {
304-
image.setDockerBinary(project.getProperties().getProperty(PropertyNames.WILDFLY_IMAGE_BINARY,
305-
System.getProperty(PropertyNames.WILDFLY_IMAGE_BINARY)));
306-
}
307-
308-
final String imageBinary = image.getDockerBinary();
309-
if (imageBinary == null) {
310-
throw new MojoExecutionException("Could not locate a binary to build the image with. Please check your " +
311-
"installation and either set the path to the binary in your PATH environment variable or define the " +
312-
"define the fully qualified path in your configuration, <docker-binary>/path/to/docker</docker-binary>. "
313-
+
314-
"The path can also be defined with the -Dwildfly.image.binary=/path/to/docker system property.");
315-
}
316-
if (!isImageBinaryAvailable(imageBinary)) {
317-
throw new MojoExecutionException(
318-
String.format("Unable to build application image with %1$s. Please check your %1$s installation",
319-
imageBinary));
320-
}
321317

318+
String runtimeImage = this.image.getWildFlyRuntimeImage();
322319
String image = this.image.getApplicationImageName(project.getArtifactId());
323320

324-
boolean buildSuccess = buildApplicationImage(image);
321+
Path serverDir = Paths.get(project.getBuild().getDirectory(), provisioningDir);
322+
// FIXME we should continue to support images built with docker/podman
323+
boolean buildSuccess = buildApplicationImageWithJib(image, runtimeImage, archs, serverDir, getDeploymentContent());
325324
if (!buildSuccess) {
326325
throw new MojoExecutionException(String.format("Unable to build application image %s", image));
327326
}
328327
getLog().info(String.format("Successfully built application image %s", image));
329-
330-
if (this.image.push) {
331-
logToRegistry();
332-
333-
boolean pushSuccess = pushApplicationImage(image);
334-
if (!pushSuccess) {
335-
throw new MojoExecutionException(String.format("Unable to push application image %s", image));
336-
}
337-
getLog().info(String.format("Successfully pushed application image %s", image));
338-
}
339328
} catch (IOException e) {
340329
throw new MojoExecutionException(e.getLocalizedMessage(), e);
341330
}
342331
}
343332

344-
private void logToRegistry() throws MojoExecutionException {
345-
String registry = image.registry;
346-
if (registry == null) {
347-
getLog().info(String.format("Registry was not set. Using default for %s.", image.getDockerBinary()));
348-
}
349-
if (image.user != null && image.password != null) {
350-
String[] dockerArgs = new String[] {
351-
"login", registry,
352-
"-u", image.user,
353-
"-p", image.password
354-
};
355-
boolean loginSuccessful = ExecUtil.exec(getLog(), image.getDockerBinary(), dockerArgs);
356-
if (!loginSuccessful) {
357-
throw new MojoExecutionException(
358-
String.format("Could not log to the container registry with the command: %s login %s -u %s -p *****",
359-
image.getDockerBinary(), registry, image.user));
360-
}
361-
}
362-
}
363-
364-
private boolean buildApplicationImage(String image) throws IOException {
333+
private boolean buildApplicationImageWithDocker(String image) throws IOException {
365334
getLog().info(format("Building application image %s using %s.", image, this.image.getDockerBinary()));
366335
String[] dockerArgs = new String[] { "build", "-t", image, "." };
367336

@@ -372,70 +341,70 @@ private boolean buildApplicationImage(String image) throws IOException {
372341

373342
}
374343

375-
private boolean pushApplicationImage(String image) {
376-
getLog().info(format("Pushing application image %s using %s.", image, this.image.getDockerBinary()));
377-
378-
String[] dockerArgs = new String[] { "push", image };
379-
380-
getLog().info(format("Executing the following command to push application image: '%s %s'", this.image.getDockerBinary(),
381-
join(" ", dockerArgs)));
382-
return ExecUtil.exec(getLog(), Paths.get(project.getBuild().getDirectory()).toFile(), this.image.getDockerBinary(),
383-
dockerArgs);
384-
}
385-
386-
private void generateDockerfile(String runtimeImage, Path targetDir, String wildflyDirectory)
387-
throws IOException, MojoExecutionException {
344+
private boolean buildApplicationImageWithJib(String image, String runtimeImage, String archs, Path wildflyDirectory,
345+
Path deployment)
346+
throws IOException {
388347

389-
Path jbossHome = Path.of(wildflyDirectory);
390-
// Docker requires the source file be relative to the context directory. From the documentation:
391-
// The <src> path must be inside the context of the build; you cannot COPY ../something /something, because
392-
// the first step of a docker build is to send the context directory (and subdirectories) to the docker daemon.
393-
if (jbossHome.isAbsolute()) {
394-
jbossHome = targetDir.relativize(jbossHome);
395-
}
348+
getLog().info(format("Building application image %s.", image));
349+
try {
350+
ImageReference imageRef = ImageReference.parse(image);
351+
Containerizer containerizer;
396352

397-
String targetName = getDeploymentTargetName();
353+
if (push) {
354+
RegistryImage registryImage = RegistryImage.named(image);
355+
if (user != null && password != null) {
356+
registryImage.addCredential(user, password);
357+
}
358+
containerizer = Containerizer.to(registryImage);
359+
} else {
360+
DockerDaemonImage dockerDaemon = DockerDaemonImage.named(imageRef);
361+
containerizer = Containerizer.to(dockerDaemon);
362+
}
398363

399-
// Create the Dockerfile content
400-
final StringBuilder dockerfileContent = new StringBuilder();
401-
dockerfileContent.append("FROM ").append(runtimeImage).append('\n');
402-
if (labels != null) {
403-
labels.forEach(
404-
(key, value) -> dockerfileContent.append("LABEL ").append(key).append("=\"")
405-
.append(value.replace("\"", "\\\"")).append("\"\n"));
406-
}
407-
dockerfileContent.append("COPY --chown=jboss:root ").append(jbossHome).append(" $JBOSS_HOME\n")
408-
.append("RUN chmod -R ug+rwX $JBOSS_HOME\n")
409-
.append("COPY --chown=jboss:root ").append(getDeploymentContent().getFileName())
410-
.append(" $JBOSS_HOME/standalone/deployments/").append(targetName);
411-
412-
final List<String> serverArgs = new ArrayList<>();
413-
if (!layers.isEmpty() && !layersConfigurationFileName.equals(Constants.STANDALONE_XML)) {
414-
serverArgs.add("-c=" + layersConfigurationFileName);
415-
} else if (!serverConfig.equals(Constants.STANDALONE_XML)) {
416-
serverArgs.add("-c=" + serverConfig);
417-
}
364+
FileEntriesLayer serverLayer = FileEntriesLayer.builder()
365+
.setName("wildfly")
366+
.addEntryRecursive(wildflyDirectory, AbsoluteUnixPath.get("/opt/server/"),
367+
PERMISSIONS_PROVIDER,
368+
DEFAULT_MODIFICATION_TIME_PROVIDER,
369+
JBOSS_ROOT_OWNER)
370+
.build();
371+
372+
FileEntriesLayer deploymentLayer = FileEntriesLayer.builder()
373+
.setName("deployment")
374+
.addEntryRecursive(deployment, AbsoluteUnixPath.get("/opt/server/standalone/deployments"),
375+
PERMISSIONS_PROVIDER,
376+
DEFAULT_MODIFICATION_TIME_PROVIDER,
377+
JBOSS_ROOT_OWNER)
378+
.build();
379+
380+
JibContainerBuilder builder = Jib.from(runtimeImage);
381+
382+
final List<String> serverArgs = new ArrayList<>();
383+
if (!layers.isEmpty() && !layersConfigurationFileName.equals(Constants.STANDALONE_XML)) {
384+
serverArgs.add("-c=" + layersConfigurationFileName);
385+
} else if (!serverConfig.equals(Constants.STANDALONE_XML)) {
386+
serverArgs.add("-c=" + serverConfig);
387+
}
418388

419-
if (!serverArgs.isEmpty()) {
420-
dockerfileContent.append('\n').append("ENV SERVER_ARGS=\"").append(String.join(",", serverArgs)).append('"');
421-
}
389+
if (!serverArgs.isEmpty()) {
390+
builder.addEnvironmentVariable("SERVER_ARGS", String.join(",", serverArgs));
391+
}
422392

423-
Files.writeString(targetDir.resolve("Dockerfile"), dockerfileContent, StandardCharsets.UTF_8);
424-
}
393+
builder.addFileEntriesLayer(serverLayer)
394+
.addFileEntriesLayer(deploymentLayer)
395+
.setLabels(labels);
396+
if (archs != null) {
397+
for (String arch : archs.split(",")) {
398+
builder.addPlatform(arch.trim(), "linux");
399+
}
400+
}
425401

426-
private boolean isImageBinaryAvailable(String imageBinary) {
427-
try {
428-
if (!ExecUtil.execSilentWithTimeout(Duration.ofMillis(DOCKER_CMD_CHECK_TIMEOUT), imageBinary, "-v")) {
402+
builder.containerize(containerizer);
429403

430-
getLog().warn(format("'%1$s -v' returned an error code. Make sure your %1$s binary is correct", imageBinary));
431-
return false;
432-
}
404+
return true;
433405
} catch (Exception e) {
434-
getLog().warn(format("No %s binary found or general error: %s", imageBinary, e));
435-
return false;
406+
throw new IOException(e);
436407
}
437-
438-
return true;
439408
}
440409

441410
private String decrypt(final Server server) {

pom.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696

9797
<version.org.eclipse.sisu>0.3.5</version.org.eclipse.sisu>
9898
<version.org.twdata.maven>2.3.1</version.org.twdata.maven>
99+
<version.jib-core>0.27.1</version.jib-core>
99100

100101
<!-- other dependencies -->
101102
<version.com.bernardomg.maven.skins>2.3.2</version.com.bernardomg.maven.skins>
@@ -235,6 +236,21 @@
235236

236237
<dependencyManagement>
237238
<dependencies>
239+
<dependency>
240+
<groupId>com.google.cloud.tools</groupId>
241+
<artifactId>jib-core</artifactId>
242+
<version>${version.jib-core}</version>
243+
</dependency>
244+
<dependency>
245+
<groupId>org.apache.commons</groupId>
246+
<artifactId>commons-compress</artifactId>
247+
<version>1.26.0</version>
248+
</dependency>
249+
<dependency>
250+
<groupId>commons-io</groupId>
251+
<artifactId>commons-io</artifactId>
252+
<version>2.16.1</version>
253+
</dependency>
238254
<dependency>
239255
<groupId>javax.inject</groupId>
240256
<artifactId>javax.inject</artifactId>

tests/standalone-tests/src/test/java/org/wildfly/plugin/provision/ImageTest.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import static org.wildfly.plugin.tests.AbstractWildFlyMojoTest.getPomFile;
99

1010
import java.io.ByteArrayOutputStream;
11-
import java.nio.file.Files;
1211
import java.nio.file.Path;
1312
import java.time.Duration;
1413
import java.util.List;
@@ -38,16 +37,10 @@ public void testBuildImage() throws Exception {
3837
imageMojo.execute();
3938
Path jbossHome = AbstractWildFlyMojoTest.getBaseDir().resolve("target").resolve("image-server");
4039
assertTrue(jbossHome.toFile().exists());
41-
Path dockerFile = AbstractWildFlyMojoTest.getBaseDir().resolve("target").resolve("Dockerfile");
42-
assertTrue(dockerFile.toFile().exists());
43-
List<String> dockerfileLines = Files.readAllLines(dockerFile);
44-
assertLineContains(dockerfileLines, 1, "LABEL description=\"This text illustrates \\");
45-
assertLineContains(dockerfileLines, 2, "that label-values can span multiple lines.\"");
46-
assertLineContains(dockerfileLines, 3, "LABEL quoted.line=\"I have \\\"quoted myself\\\" here.\"");
47-
assertLineContains(dockerfileLines, 4, "LABEL version=\"1.0\"");
4840
final ByteArrayOutputStream stdout = new ByteArrayOutputStream();
4941
assertTrue(ExecUtil.exec(stdout, binary, "inspect", "wildfly-maven-plugin/testing"));
5042
assertEnvironmentUnset(stdout, "SERVER_ARGS=-c=");
43+
// TODO assert labels are set on the stdout
5144
} finally {
5245

5346
exec(binary, "rmi", "wildfly-maven-plugin/testing");

0 commit comments

Comments
 (0)