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
This commit is contained in:
parent
f09630f73c
commit
a80c4ad38d
|
@ -1,14 +1,12 @@
|
||||||
[[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 `bootBuildImage` task.
|
Images can be built using the `bootBuildImage` task.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
The task is automatically created when the `java` plugin is applied and is an instance of {boot-build-image-javadoc}[`BootBuildImage`].
|
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 is not supported with projects using <<packaging-executable-wars, war packaging>>.
|
|
||||||
|
|
||||||
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.
|
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`.
|
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) -> {
|
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.setDescription("Builds an OCI image of the application using the output of the bootJar task");
|
||||||
buildImage.setGroup(BasePlugin.BUILD_GROUP);
|
buildImage.setGroup(BasePlugin.BUILD_GROUP);
|
||||||
buildImage.getJar().set(bootJar.get().getArchiveFile());
|
buildImage.getArchiveFile().set(bootJar.get().getArchiveFile());
|
||||||
buildImage.getTargetJavaVersion().set(javaPluginConvention(project).getTargetCompatibility());
|
buildImage.getTargetJavaVersion().set(javaPluginConvention(project).getTargetCompatibility());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* 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.
|
||||||
|
@ -22,7 +22,6 @@ import org.gradle.api.Project;
|
||||||
import org.gradle.api.artifacts.Configuration;
|
import org.gradle.api.artifacts.Configuration;
|
||||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||||
import org.gradle.api.file.FileCollection;
|
import org.gradle.api.file.FileCollection;
|
||||||
import org.gradle.api.file.RegularFile;
|
|
||||||
import org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact;
|
import org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact;
|
||||||
import org.gradle.api.plugins.BasePlugin;
|
import org.gradle.api.plugins.BasePlugin;
|
||||||
import org.gradle.api.plugins.WarPlugin;
|
import org.gradle.api.plugins.WarPlugin;
|
||||||
|
@ -56,8 +55,8 @@ class WarPluginAction implements PluginApplicationAction {
|
||||||
@Override
|
@Override
|
||||||
public void execute(Project project) {
|
public void execute(Project project) {
|
||||||
disableWarTask(project);
|
disableWarTask(project);
|
||||||
disableBootBuildImageTask(project);
|
|
||||||
TaskProvider<BootWar> bootWar = configureBootWarTask(project);
|
TaskProvider<BootWar> bootWar = configureBootWarTask(project);
|
||||||
|
configureBootBuildImageTask(project, bootWar);
|
||||||
configureArtifactPublication(bootWar);
|
configureArtifactPublication(bootWar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,11 +64,6 @@ class WarPluginAction implements PluginApplicationAction {
|
||||||
project.getTasks().named(WarPlugin.WAR_TASK_NAME).configure((war) -> war.setEnabled(false));
|
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) {
|
private TaskProvider<BootWar> configureBootWarTask(Project project) {
|
||||||
Configuration developmentOnly = project.getConfigurations()
|
Configuration developmentOnly = project.getConfigurations()
|
||||||
.getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME);
|
.getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME);
|
||||||
|
@ -103,6 +97,11 @@ class WarPluginAction implements PluginApplicationAction {
|
||||||
return configurations.getByName(WarPlugin.PROVIDED_RUNTIME_CONFIGURATION_NAME);
|
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) {
|
private void configureArtifactPublication(TaskProvider<BootWar> bootWar) {
|
||||||
LazyPublishArtifact artifact = new LazyPublishArtifact(bootWar);
|
LazyPublishArtifact artifact = new LazyPublishArtifact(bootWar);
|
||||||
this.singlePublishedArtifact.addCandidate(artifact);
|
this.singlePublishedArtifact.addCandidate(artifact);
|
||||||
|
|
|
@ -68,7 +68,7 @@ public class BootBuildImage extends DefaultTask {
|
||||||
|
|
||||||
private final Property<String> projectVersion;
|
private final Property<String> projectVersion;
|
||||||
|
|
||||||
private RegularFileProperty jar;
|
private RegularFileProperty archiveFile;
|
||||||
|
|
||||||
private Property<JavaVersion> targetJavaVersion;
|
private Property<JavaVersion> targetJavaVersion;
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ public class BootBuildImage extends DefaultTask {
|
||||||
private final DockerSpec docker = new DockerSpec();
|
private final DockerSpec docker = new DockerSpec();
|
||||||
|
|
||||||
public BootBuildImage() {
|
public BootBuildImage() {
|
||||||
this.jar = getProject().getObjects().fileProperty();
|
this.archiveFile = getProject().getObjects().fileProperty();
|
||||||
this.targetJavaVersion = getProject().getObjects().property(JavaVersion.class);
|
this.targetJavaVersion = getProject().getObjects().property(JavaVersion.class);
|
||||||
this.projectName = getProject().getName();
|
this.projectName = getProject().getName();
|
||||||
this.projectVersion = getProject().getObjects().property(String.class);
|
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.
|
* Returns the property for the archive file from which the image will be built.
|
||||||
* @return the jar property
|
* @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
|
@Input
|
||||||
@Optional
|
|
||||||
public RegularFileProperty getJar() {
|
public RegularFileProperty getJar() {
|
||||||
return this.jar;
|
return this.archiveFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -396,17 +405,14 @@ public class BootBuildImage extends DefaultTask {
|
||||||
|
|
||||||
@TaskAction
|
@TaskAction
|
||||||
void buildImage() throws DockerEngineException, IOException {
|
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());
|
Builder builder = new Builder(this.docker.asDockerConfiguration());
|
||||||
|
BuildRequest request = createRequest();
|
||||||
builder.build(request);
|
builder.build(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
BuildRequest createRequest() {
|
BuildRequest createRequest() {
|
||||||
return customize(BuildRequest.of(determineImageReference(),
|
return customize(BuildRequest.of(determineImageReference(),
|
||||||
(owner) -> new ZipFileTarArchive(this.jar.get().getAsFile(), owner)));
|
(owner) -> new ZipFileTarArchive(this.archiveFile.get().getAsFile(), owner)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImageReference determineImageReference() {
|
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
|
@TestTemplate
|
||||||
void buildsImageWithCustomName() throws IOException {
|
void buildsImageWithCustomName() throws IOException {
|
||||||
writeMainClass();
|
writeMainClass();
|
||||||
|
@ -267,15 +308,6 @@ class BootBuildImageIntegrationTests {
|
||||||
assertThat(result.getOutput()).contains("requires docker.publishRegistry");
|
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
|
@TestTemplate
|
||||||
void failsWithBuildpackNotInBuilder() throws IOException {
|
void failsWithBuildpackNotInBuilder() throws IOException {
|
||||||
writeMainClass();
|
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");
|
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 {
|
private void writeMainClass() throws IOException {
|
||||||
File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example");
|
File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example");
|
||||||
examplePackage.mkdirs();
|
examplePackage.mkdirs();
|
||||||
|
|
Loading…
Reference in New Issue