44 */
55package org .wildfly .plugin .provision ;
66
7+ import static com .google .cloud .tools .jib .api .buildplan .FileEntriesLayer .DEFAULT_MODIFICATION_TIME_PROVIDER ;
78import static java .lang .String .format ;
89import static java .lang .String .join ;
910
1011import java .io .IOException ;
11- import java .nio .charset .StandardCharsets ;
12- import java .nio .file .Files ;
1312import java .nio .file .Path ;
1413import java .nio .file .Paths ;
15- import java .time .Duration ;
1614import java .util .ArrayList ;
1715import java .util .List ;
1816import java .util .Map ;
3331import org .wildfly .plugin .common .PropertyNames ;
3432import 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.
5160@ SuppressWarnings ({ "deprecated" , "removal" })
5261public 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 * -->
8592 * <push>true</push>
8693 *
87- * <!-- (optional) The binary used to perform image commands (build, login, push) (default is "docker") -->
88- * <docker-binary>docker</docker-binary>
89- *
90- * <!-- (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 -->
94+ * <!-- (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 -->
9195 * <jdk-version>11</jdk-version>
9296 *
9397 * <!-- (optional) The group part of the name of the application image -->
@@ -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 ) {
0 commit comments