From 0e5df2296e2661cc0b0b7b2d67de41b58f30f046 Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Tue, 15 Dec 2020 17:13:33 -0600 Subject: [PATCH] Fail with Gradle bootBuildImage and war packaging Prior to this commit, running the bootBuildImage Gradle task on a project configured for war packaging would result in a jar file being built and used in the image instead of the war file. With this commit an error will be thrown from the plugin in this case. Fixes gh-24521 --- .../docs/asciidoc/packaging-oci-image.adoc | 4 ++- .../boot/gradle/plugin/WarPluginAction.java | 8 +++++ .../gradle/tasks/bundling/BootBuildImage.java | 5 ++++ .../BootBuildImageIntegrationTests.java | 30 +++++++++++++++++++ ...WithWarPackagingAndJarConfiguration.gradle | 11 +++++++ .../BootBuildImageIntegrationTests.gradle | 4 +++ 6 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithWarPackagingAndJarConfiguration.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc index d28309132e..f665aad2b8 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc @@ -1,6 +1,6 @@ [[build-image]] == Packaging OCI Images -The plugin can create an https://github.com/opencontainers/image-spec[OCI image] from executable jars using https://buildpacks.io[Cloud Native Buildpacks] (CNB). +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). Images can be built using the `bootBuildImage` task. NOTE: For security reasons, images build and run as non-root users. @@ -8,6 +8,8 @@ See the {buildpacks-reference}/reference/spec/platform-api/#users[CNB specificat 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 <>. + NOTE: The `bootBuildImage` task can not be used with a <> 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`. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java index 4aa382c482..c9b15cee00 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java @@ -22,11 +22,13 @@ 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; import org.gradle.api.tasks.TaskProvider; +import org.springframework.boot.gradle.tasks.bundling.BootBuildImage; import org.springframework.boot.gradle.tasks.bundling.BootWar; /** @@ -51,6 +53,7 @@ class WarPluginAction implements PluginApplicationAction { @Override public void execute(Project project) { disableWarTask(project); + disableBootBuildImageTask(project); TaskProvider bootWar = configureBootWarTask(project); configureArtifactPublication(bootWar); } @@ -59,6 +62,11 @@ 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 configureBootWarTask(Project project) { return project.getTasks().register(SpringBootPlugin.BOOT_WAR_TASK_NAME, BootWar.class, (bootWar) -> { bootWar.setGroup(BasePlugin.BUILD_GROUP); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java index 787ba46cb7..cdbe8d019b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.Map; import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; import org.gradle.api.JavaVersion; import org.gradle.api.Project; import org.gradle.api.Task; @@ -79,6 +80,7 @@ public class BootBuildImage extends DefaultTask { * @return the jar property */ @Input + @Optional public RegularFileProperty getJar() { return this.jar; } @@ -226,6 +228,9 @@ 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"); + } Builder builder = new Builder(); BuildRequest request = createRequest(); builder.build(request); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java index 760abdde35..f958154fd3 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java @@ -153,6 +153,36 @@ class BootBuildImageIntegrationTests { .containsPattern("example/Invalid-Image-Name"); } + @TestTemplate + void failsWithWarPackaging() { + 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 buildsImageWithWarPackagingAndJarConfiguration() throws IOException { + writeMainClass(); + writeLongNameResource(); + BuildResult result = this.gradleBuild.build("bootBuildImage"); + String projectName = this.gradleBuild.getProjectDir().getName(); + assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).contains("docker.io/library/" + projectName); + assertThat(result.getOutput()).contains("paketo-buildpacks/builder"); + 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() { File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example"); examplePackage.mkdirs(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithWarPackagingAndJarConfiguration.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithWarPackagingAndJarConfiguration.gradle new file mode 100644 index 0000000000..5aa8c27efe --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithWarPackagingAndJarConfiguration.gradle @@ -0,0 +1,11 @@ +plugins { + id 'war' + id 'org.springframework.boot' version '{version}' +} + +bootBuildImage { + jar = bootWar.archiveFile +} + +sourceCompatibility = '1.8' +targetCompatibility = '1.8' diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.gradle index 8971742147..5e0df9a58c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.gradle @@ -3,5 +3,9 @@ plugins { id 'org.springframework.boot' version '{version}' } +if (project.hasProperty('applyWarPlugin')) { + apply plugin: 'war' +} + sourceCompatibility = '1.8' targetCompatibility = '1.8'