Commit 4be04b0e authored by Scott Frederick's avatar Scott Frederick

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
parent a80c4ad3
[[build-image]] [[build-image]]
== Packaging OCI Images == 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. Images can be built using the `build-image` goal.
NOTE: For security reasons, images build and run as non-root users. 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. 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 <<repackage, war packaging>>.
The easiest way to get started is to invoke `mvn spring-boot:build-image` on a project. 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: It is possible to automate the creation of an image whenever the `package` phase is invoked, as shown in the following example:
......
...@@ -68,6 +68,30 @@ public class BuildImageTests extends AbstractArchiveIntegrationTests { ...@@ -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 @TestTemplate
void whenBuildImageIsInvokedWithCustomImageName(MavenBuild mavenBuild) { void whenBuildImageIsInvokedWithCustomImageName(MavenBuild mavenBuild) {
mavenBuild.project("build-image-custom-name").goals("package") mavenBuild.project("build-image-custom-name").goals("package")
...@@ -191,12 +215,6 @@ public class BuildImageTests extends AbstractArchiveIntegrationTests { ...@@ -191,12 +215,6 @@ public class BuildImageTests extends AbstractArchiveIntegrationTests {
.containsPattern("Builder lifecycle '.*' failed with status code")); .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 @TestTemplate
void failsWithBuildpackNotInBuilder(MavenBuild mavenBuild) { void failsWithBuildpackNotInBuilder(MavenBuild mavenBuild) {
mavenBuild.project("build-image-bad-buildpack").goals("package") mavenBuild.project("build-image-bad-buildpack").goals("package")
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId> <groupId>org.springframework.boot.maven.it</groupId>
<artifactId>build-image-war</artifactId> <artifactId>build-image-war-packaging</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version> <version>0.0.1.BUILD-SNAPSHOT</version>
<packaging>war</packaging> <packaging>war</packaging>
<properties> <properties>
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -19,6 +19,10 @@ package org.test; ...@@ -19,6 +19,10 @@ package org.test;
public class SampleApplication { public class SampleApplication {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
System.out.println("Launched");
synchronized(args) {
args.wait(); // Prevent exit"
}
} }
} }
...@@ -218,23 +218,26 @@ public class BuildImageMojo extends AbstractPackagerMojo { ...@@ -218,23 +218,26 @@ public class BuildImageMojo extends AbstractPackagerMojo {
} }
private TarArchive getApplicationContent(Owner owner, Libraries libraries) { 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); return new PackagedTarArchive(owner, libraries, packager);
} }
private File getJarFile() { private File getArchiveFile() {
// We can use 'project.getArtifact().getFile()' because that was done in a // We can use 'project.getArtifact().getFile()' because that was done in a
// forked lifecycle and is now null // forked lifecycle and is now null
StringBuilder name = new StringBuilder(this.finalName); StringBuilder name = new StringBuilder(this.finalName);
if (StringUtils.hasText(this.classifier)) { if (StringUtils.hasText(this.classifier)) {
name.append("-").append(this.classifier); name.append("-").append(this.classifier);
} }
name.append(".jar"); File archiveFile = new File(this.sourceDirectory, name.toString() + ".jar");
File jarFile = new File(this.sourceDirectory, name.toString()); if (archiveFile.exists()) {
if (!jarFile.exists()) { return archiveFile;
throw new IllegalStateException("Executable jar file required for building image");
} }
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) { private BuildRequest customize(BuildRequest request) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment