Commit a80c4ad3 authored by Scott Frederick's avatar Scott Frederick

Support image building with Gradle and war packaging

This commit updates the Gradle image building task to support building
images from executable and non-executable war files.

Fixes gh-23825
parent f09630f7
[[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 `bootBuildImage` task.
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.
The task is automatically created when the `java` plugin is applied and is an instance of {boot-build-image-javadoc}[`BootBuildImage`].
NOTE: The `bootBuildImage` task is not supported with projects using <<packaging-executable-wars, war packaging>>.
The task is automatically created when the `java` or `war` plugin is applied and is an instance of {boot-build-image-javadoc}[`BootBuildImage`].
NOTE: The `bootBuildImage` task can not be used with a <<packaging-executable-configuring-launch-script, fully executable Spring Boot archive>> that includes a launch script.
Disable launch script configuration in the `bootJar` task when building a jar file that is intended to be used with `bootBuildImage`.
......
......@@ -123,7 +123,7 @@ final class JavaPluginAction implements PluginApplicationAction {
project.getTasks().register(SpringBootPlugin.BOOT_BUILD_IMAGE_TASK_NAME, BootBuildImage.class, (buildImage) -> {
buildImage.setDescription("Builds an OCI image of the application using the output of the bootJar task");
buildImage.setGroup(BasePlugin.BUILD_GROUP);
buildImage.getJar().set(bootJar.get().getArchiveFile());
buildImage.getArchiveFile().set(bootJar.get().getArchiveFile());
buildImage.getTargetJavaVersion().set(javaPluginConvention(project).getTargetCompatibility());
});
}
......
/*
* 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.
......@@ -22,7 +22,6 @@ import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.RegularFile;
import org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact;
import org.gradle.api.plugins.BasePlugin;
import org.gradle.api.plugins.WarPlugin;
......@@ -56,8 +55,8 @@ class WarPluginAction implements PluginApplicationAction {
@Override
public void execute(Project project) {
disableWarTask(project);
disableBootBuildImageTask(project);
TaskProvider<BootWar> bootWar = configureBootWarTask(project);
configureBootBuildImageTask(project, bootWar);
configureArtifactPublication(bootWar);
}
......@@ -65,11 +64,6 @@ class WarPluginAction implements PluginApplicationAction {
project.getTasks().named(WarPlugin.WAR_TASK_NAME).configure((war) -> war.setEnabled(false));
}
private void disableBootBuildImageTask(Project project) {
project.getTasks().named(SpringBootPlugin.BOOT_BUILD_IMAGE_TASK_NAME, BootBuildImage.class)
.configure((buildImage) -> buildImage.getJar().set((RegularFile) null));
}
private TaskProvider<BootWar> configureBootWarTask(Project project) {
Configuration developmentOnly = project.getConfigurations()
.getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME);
......@@ -103,6 +97,11 @@ class WarPluginAction implements PluginApplicationAction {
return configurations.getByName(WarPlugin.PROVIDED_RUNTIME_CONFIGURATION_NAME);
}
private void configureBootBuildImageTask(Project project, TaskProvider<BootWar> bootWar) {
project.getTasks().named(SpringBootPlugin.BOOT_BUILD_IMAGE_TASK_NAME, BootBuildImage.class)
.configure((buildImage) -> buildImage.getArchiveFile().set(bootWar.get().getArchiveFile()));
}
private void configureArtifactPublication(TaskProvider<BootWar> bootWar) {
LazyPublishArtifact artifact = new LazyPublishArtifact(bootWar);
this.singlePublishedArtifact.addCandidate(artifact);
......
......@@ -68,7 +68,7 @@ public class BootBuildImage extends DefaultTask {
private final Property<String> projectVersion;
private RegularFileProperty jar;
private RegularFileProperty archiveFile;
private Property<JavaVersion> targetJavaVersion;
......@@ -95,7 +95,7 @@ public class BootBuildImage extends DefaultTask {
private final DockerSpec docker = new DockerSpec();
public BootBuildImage() {
this.jar = getProject().getObjects().fileProperty();
this.archiveFile = getProject().getObjects().fileProperty();
this.targetJavaVersion = getProject().getObjects().property(JavaVersion.class);
this.projectName = getProject().getName();
this.projectVersion = getProject().getObjects().property(String.class);
......@@ -106,13 +106,22 @@ public class BootBuildImage extends DefaultTask {
}
/**
* Returns the property for the jar file from which the image will be built.
* @return the jar property
* Returns the property for the archive file from which the image will be built.
* @return the archive file property
*/
@Input
public RegularFileProperty getArchiveFile() {
return this.archiveFile;
}
/**
* Returns the property for the archive file from which the image will be built.
* @return the archive file property
* @deprecated since 2.5.0 in favor of {@link #getArchiveFile()}
*/
@Input
@Optional
public RegularFileProperty getJar() {
return this.jar;
return this.archiveFile;
}
/**
......@@ -396,17 +405,14 @@ public class BootBuildImage extends DefaultTask {
@TaskAction
void buildImage() throws DockerEngineException, IOException {
if (!this.jar.isPresent()) {
throw new GradleException("Executable jar file required for building image");
}
BuildRequest request = createRequest();
Builder builder = new Builder(this.docker.asDockerConfiguration());
BuildRequest request = createRequest();
builder.build(request);
}
BuildRequest createRequest() {
return customize(BuildRequest.of(determineImageReference(),
(owner) -> new ZipFileTarArchive(this.jar.get().getAsFile(), owner)));
(owner) -> new ZipFileTarArchive(this.archiveFile.get().getAsFile(), owner)));
}
private ImageReference determineImageReference() {
......
......@@ -79,6 +79,47 @@ class BootBuildImageIntegrationTests {
}
}
@TestTemplate
void buildsImageWithWarPackaging() throws IOException {
writeMainClass();
writeLongNameResource();
BuildResult result = this.gradleBuild.build("bootBuildImage", "-PapplyWarPlugin",
"--pullPolicy=IF_NOT_PRESENT");
String projectName = this.gradleBuild.getProjectDir().getName();
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("docker.io/library/" + projectName);
File buildLibs = new File(this.gradleBuild.getProjectDir(), "build/libs");
assertThat(buildLibs.listFiles())
.containsExactly(new File(buildLibs, this.gradleBuild.getProjectDir().getName() + ".war"));
ImageReference imageReference = ImageReference.of(ImageName.of(projectName));
try (GenericContainer<?> container = new GenericContainer<>(imageReference.toString())) {
container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start();
}
finally {
new DockerApi().image().remove(imageReference, false);
}
}
@TestTemplate
void buildsImageWithWarPackagingAndJarConfiguration() throws IOException {
writeMainClass();
writeLongNameResource();
BuildResult result = this.gradleBuild.build("bootBuildImage", "--pullPolicy=IF_NOT_PRESENT");
String projectName = this.gradleBuild.getProjectDir().getName();
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("docker.io/library/" + projectName);
File buildLibs = new File(this.gradleBuild.getProjectDir(), "build/libs");
assertThat(buildLibs.listFiles())
.containsExactly(new File(buildLibs, this.gradleBuild.getProjectDir().getName() + ".war"));
ImageReference imageReference = ImageReference.of(ImageName.of(projectName));
try (GenericContainer<?> container = new GenericContainer<>(imageReference.toString())) {
container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start();
}
finally {
new DockerApi().image().remove(imageReference, false);
}
}
@TestTemplate
void buildsImageWithCustomName() throws IOException {
writeMainClass();
......@@ -267,15 +308,6 @@ class BootBuildImageIntegrationTests {
assertThat(result.getOutput()).contains("requires docker.publishRegistry");
}
@TestTemplate
void failsWithWarPackaging() throws IOException {
writeMainClass();
writeLongNameResource();
BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage", "-PapplyWarPlugin");
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.FAILED);
assertThat(result.getOutput()).contains("Executable jar file required for building image");
}
@TestTemplate
void failsWithBuildpackNotInBuilder() throws IOException {
writeMainClass();
......@@ -285,26 +317,6 @@ class BootBuildImageIntegrationTests {
assertThat(result.getOutput()).contains("'urn:cnb:builder:example/does-not-exist:0.0.1' not found in builder");
}
@TestTemplate
void buildsImageWithWarPackagingAndJarConfiguration() throws IOException {
writeMainClass();
writeLongNameResource();
BuildResult result = this.gradleBuild.build("bootBuildImage", "--pullPolicy=IF_NOT_PRESENT");
String projectName = this.gradleBuild.getProjectDir().getName();
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("docker.io/library/" + projectName);
File buildLibs = new File(this.gradleBuild.getProjectDir(), "build/libs");
assertThat(buildLibs.listFiles())
.containsExactly(new File(buildLibs, this.gradleBuild.getProjectDir().getName() + ".war"));
ImageReference imageReference = ImageReference.of(ImageName.of(projectName));
try (GenericContainer<?> container = new GenericContainer<>(imageReference.toString())) {
container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start();
}
finally {
new DockerApi().image().remove(imageReference, false);
}
}
private void writeMainClass() throws IOException {
File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example");
examplePackage.mkdirs();
......
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