From 4be04b0ea2bee718c2497bea708f50e5333fb5ab Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Wed, 24 Feb 2021 14:49:22 -0600 Subject: [PATCH] Support image building with Maven and war packaging This commit updates the Maven image building goal to support building images from executable and non-executable war files. Fixes gh-23823 --- .../docs/asciidoc/packaging-oci-image.adoc | 4 +-- .../boot/maven/BuildImageTests.java | 30 +++++++++++++++---- .../build-image-war-packaging/pom.xml | 2 +- .../main/java/org/test/SampleApplication.java | 6 +++- .../boot/maven/BuildImageMojo.java | 17 ++++++----- 5 files changed, 41 insertions(+), 18 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc index d8f67fe82d..4ba97837ab 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc @@ -1,13 +1,11 @@ [[build-image]] == Packaging OCI Images -The plugin can create an https://github.com/opencontainers/image-spec[OCI image] from an executable jar file using https://buildpacks.io/[Cloud Native Buildpacks] (CNB). +The plugin can create an https://github.com/opencontainers/image-spec[OCI image] from a jar or war file using https://buildpacks.io/[Cloud Native Buildpacks] (CNB). Images can be built using the `build-image` goal. NOTE: For security reasons, images build and run as non-root users. See the {buildpacks-reference}/reference/spec/platform-api/#users[CNB specification] for more details. -NOTE: The `build-image` goal is not supported with projects using <>. - The easiest way to get started is to invoke `mvn spring-boot:build-image` on a project. It is possible to automate the creation of an image whenever the `package` phase is invoked, as shown in the following example: diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/BuildImageTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/BuildImageTests.java index 31335275df..89c40b06f9 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/BuildImageTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/BuildImageTests.java @@ -68,6 +68,30 @@ public class BuildImageTests extends AbstractArchiveIntegrationTests { }); } + @TestTemplate + void whenBuildImageIsInvokedWithWarPackaging(MavenBuild mavenBuild) { + mavenBuild.project("build-image-war-packaging").goals("package") + .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") + .prepare(this::writeLongNameResource).execute((project) -> { + File war = new File(project, "target/build-image-war-packaging-0.0.1.BUILD-SNAPSHOT.war"); + assertThat(war).isFile(); + File original = new File(project, + "target/build-image-war-packaging-0.0.1.BUILD-SNAPSHOT.war.original"); + assertThat(original).doesNotExist(); + assertThat(buildLog(project)).contains("Building image") + .contains("docker.io/library/build-image-war-packaging:0.0.1.BUILD-SNAPSHOT") + .contains("Successfully built image"); + ImageReference imageReference = ImageReference.of(ImageName.of("build-image-war-packaging"), + "0.0.1.BUILD-SNAPSHOT"); + try (GenericContainer container = new GenericContainer<>(imageReference.toString())) { + container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start(); + } + finally { + removeImage(imageReference); + } + }); + } + @TestTemplate void whenBuildImageIsInvokedWithCustomImageName(MavenBuild mavenBuild) { mavenBuild.project("build-image-custom-name").goals("package") @@ -191,12 +215,6 @@ public class BuildImageTests extends AbstractArchiveIntegrationTests { .containsPattern("Builder lifecycle '.*' failed with status code")); } - @TestTemplate - void failsWithWarPackaging(MavenBuild mavenBuild) { - mavenBuild.project("build-image-war-packaging").goals("package").executeAndFail( - (project) -> assertThat(buildLog(project)).contains("Executable jar file required for building image")); - } - @TestTemplate void failsWithBuildpackNotInBuilder(MavenBuild mavenBuild) { mavenBuild.project("build-image-bad-buildpack").goals("package") diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-war-packaging/pom.xml b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-war-packaging/pom.xml index 6c009af94c..5b7c0de349 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-war-packaging/pom.xml +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-war-packaging/pom.xml @@ -3,7 +3,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.springframework.boot.maven.it - build-image-war + build-image-war-packaging 0.0.1.BUILD-SNAPSHOT war diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-war-packaging/src/main/java/org/test/SampleApplication.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-war-packaging/src/main/java/org/test/SampleApplication.java index 61b70979ba..5053809ef1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-war-packaging/src/main/java/org/test/SampleApplication.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-war-packaging/src/main/java/org/test/SampleApplication.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,10 @@ package org.test; public class SampleApplication { public static void main(String[] args) throws Exception { + System.out.println("Launched"); + synchronized(args) { + args.wait(); // Prevent exit" + } } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java index 72d6980deb..c23ce8ef02 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java @@ -218,23 +218,26 @@ public class BuildImageMojo extends AbstractPackagerMojo { } private TarArchive getApplicationContent(Owner owner, Libraries libraries) { - ImagePackager packager = getConfiguredPackager(() -> new ImagePackager(getJarFile())); + ImagePackager packager = getConfiguredPackager(() -> new ImagePackager(getArchiveFile())); return new PackagedTarArchive(owner, libraries, packager); } - private File getJarFile() { + private File getArchiveFile() { // We can use 'project.getArtifact().getFile()' because that was done in a // forked lifecycle and is now null StringBuilder name = new StringBuilder(this.finalName); if (StringUtils.hasText(this.classifier)) { name.append("-").append(this.classifier); } - name.append(".jar"); - File jarFile = new File(this.sourceDirectory, name.toString()); - if (!jarFile.exists()) { - throw new IllegalStateException("Executable jar file required for building image"); + File archiveFile = new File(this.sourceDirectory, name.toString() + ".jar"); + if (archiveFile.exists()) { + return archiveFile; } - return jarFile; + archiveFile = new File(this.sourceDirectory, name.toString() + ".war"); + if (archiveFile.exists()) { + return archiveFile; + } + throw new IllegalStateException("A jar or war file is required for building image"); } private BuildRequest customize(BuildRequest request) {