From 08e96427deb59c24a7d93f673284db3287538e25 Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Tue, 17 Mar 2020 18:40:37 -0500 Subject: [PATCH] Add command-line properties for Maven build-image options This commit adds support for setting the image name and builder parameters of the Maven spring-boot:build-image goal using command-line properties as an alternative to plugin configuration in pom.xml. Per Maven conventions, a value in pom.xml configuration will override a command-line property when both are provided. Fixes gh-20520 --- .../docs/asciidoc/packaging-oci-image.adoc | 28 ++++++++-- .../boot/maven/BuildImageTests.java | 54 +++++++++++++------ .../boot/maven/AbstractPackagerMojo.java | 4 +- .../boot/maven/BuildImageMojo.java | 20 +++++++ .../org/springframework/boot/maven/Image.java | 9 ++++ 5 files changed, 94 insertions(+), 21 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 82c25f4a09..c3834b4f44 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 @@ -40,29 +40,34 @@ By default, the plugin chooses a builder image. The name of the generated image is deduced from project properties. The `image` parameter allows to configure how the builder should operate on the project. -The following table summarizes the available properties and their default values: +The following table summarizes the available parameters and their default values: |=== -| Property | Description | Default value +| Parameter | Description | User property | Default value | `builder` | Name of the Builder image to use. +| `spring-boot.build-image.builder` | `cloudfoundry/cnb:0.0.53-bionic` | `name` | {spring-boot-api}/buildpack/platform/docker/type/ImageReference.html#of-java.lang.String-[Image name] for the generated image. +| `spring-boot.build-image.imageName` | `docker.io/library/${project.artifactId}:${project.version}` | `env` | Environment variables that should be passed to the builder. | +| | `cleanCache` | Whether to clean the cache before building. +| | `false` | `verboseLogging` | Enables verbose logging of builder operations. +| | `false` |=== @@ -79,7 +84,7 @@ include::goals/build-image.adoc[leveloffset=+1] [[build-image-example-custom-image-builder]] ==== Custom Image Builder -If you need to customize the builder used to create the image, configure yours as shown in the following example: +If you need to customize the builder used to create the image, configure the plugin as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- @@ -103,6 +108,13 @@ If you need to customize the builder used to create the image, configure yours a This configuration will use a builder image with the name `mine/java-cnb-builder` and the tag `latest`. +The builder can be specified on the command line as well, as shown in this example: + +[indent=0] +---- + $ mvn spring-boot:build-image -Dspring-boot.build-image:builder=mine/java-cnb-builder +---- + If the builder exposes configuration options, those can be set using the `env` attributes, as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] @@ -158,4 +170,12 @@ You can take control over the name, as shown in the following example: ---- Note that this configuration does not provide an explicit tag so `latest` is used. -It is possible to specify a tag as well, either using `${project.version}`, any property available in the build or a hardcoded version. \ No newline at end of file +It is possible to specify a tag as well, either using `${project.version}`, any property available in the build or a hardcoded version. + +The image name can be specified on the command line as well, as shown in this example: + +[indent=0] +---- + $ mvn spring-boot:build-image -Dspring-boot.build-image:imageName=example.com/library/v1 +---- + 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 911d49a2f5..1e7595a0a0 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 @@ -67,21 +67,45 @@ public class BuildImageTests extends AbstractArchiveIntegrationTests { @TestTemplate void whenBuildImageIsInvokedWithCustomImageName(MavenBuild mavenBuild) { - mavenBuild.project("build-image-custom-name").goals("package").execute((project) -> { - File jar = new File(project, "target/build-image-custom-name-0.0.1.BUILD-SNAPSHOT.jar"); - assertThat(jar).isFile(); - File original = new File(project, "target/build-image-custom-name-0.0.1.BUILD-SNAPSHOT.jar.original"); - assertThat(original).doesNotExist(); - assertThat(buildLog(project)).contains("Building image") - .contains("example.com/test/build-image:0.0.1.BUILD-SNAPSHOT").contains("Successfully built image"); - ImageReference imageReference = ImageReference.of("example.com/test/build-image:0.0.1.BUILD-SNAPSHOT"); - try (GenericContainer container = new GenericContainer<>(imageReference.toString())) { - container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start(); - } - finally { - removeImage(imageReference); - } - }); + mavenBuild.project("build-image-custom-name").goals("package") + .systemProperty("spring-boot.build-image.imageName", "example.com/test/property-ignored:pom-preferred") + .execute((project) -> { + File jar = new File(project, "target/build-image-custom-name-0.0.1.BUILD-SNAPSHOT.jar"); + assertThat(jar).isFile(); + File original = new File(project, + "target/build-image-custom-name-0.0.1.BUILD-SNAPSHOT.jar.original"); + assertThat(original).doesNotExist(); + assertThat(buildLog(project)).contains("Building image") + .contains("example.com/test/build-image:0.0.1.BUILD-SNAPSHOT") + .contains("Successfully built image"); + ImageReference imageReference = ImageReference + .of("example.com/test/build-image: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 whenBuildImageIsInvokedWithCommandLineParameters(MavenBuild mavenBuild) { + mavenBuild.project("build-image").goals("package") + .systemProperty("spring-boot.build-image.imageName", "example.com/test/cmd-property-name:v1") + .systemProperty("spring-boot.build-image.builder", "cloudfoundry/cnb:0.0.43-bionic") + .execute((project) -> { + assertThat(buildLog(project)).contains("Building image") + .contains("example.com/test/cmd-property-name:v1") + .contains("cloudfoundry/cnb:0.0.43-bionic").contains("Successfully built image"); + ImageReference imageReference = ImageReference.of("example.com/test/cmd-property-name:v1"); + try (GenericContainer container = new GenericContainer<>(imageReference.toString())) { + container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start(); + } + finally { + removeImage(imageReference); + } + }); } @TestTemplate diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java index f6474ef181..e3e3225b3a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java @@ -81,8 +81,8 @@ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo /** * The type of archive (which corresponds to how the dependencies are laid out inside - * it). Possible values are JAR, LAYERED_JAR, WAR, ZIP, DIR, NONE. Defaults to a guess - * based on the archive type. + * it). Possible values are JAR, WAR, ZIP, DIR, NONE. Defaults to a guess based on the + * archive type. * @since 1.0.0 */ @Parameter(property = "spring-boot.repackage.layout") 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 fc8caa9b81..5d8ba37a70 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 @@ -101,6 +101,20 @@ public class BuildImageMojo extends AbstractPackagerMojo { @Parameter private Image image; + /** + * Alias for {@link Image#name to support configuration via command-line property. + * @since 2.3.0 + */ + @Parameter(property = "spring-boot.build-image.imageName", readonly = true) + String imageName; + + /** + * Alias for {@link Image#builder to support configuration via command-line property. + * @since 2.3.0 + */ + @Parameter(property = "spring-boot.build-image.builder", readonly = true) + String imageBuilder; + @Override public void execute() throws MojoExecutionException { if (this.project.getPackaging().equals("pom")) { @@ -129,6 +143,12 @@ public class BuildImageMojo extends AbstractPackagerMojo { private BuildRequest getBuildRequest(Libraries libraries) { Function content = (owner) -> getApplicationContent(owner, libraries); Image image = (this.image != null) ? this.image : new Image(); + if (image.name == null && this.imageName != null) { + image.setName(this.imageName); + } + if (image.builder == null && this.imageBuilder != null) { + image.setBuilder(this.imageBuilder); + } return customize(image.getBuildRequest(this.project.getArtifact(), content)); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Image.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Image.java index a19434d5bb..ef5a4b6088 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Image.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Image.java @@ -32,6 +32,7 @@ import org.springframework.util.StringUtils; * Image configuration options. * * @author Phillip Webb + * @author Scott Frederick * @since 2.3.0 */ public class Image { @@ -61,6 +62,14 @@ public class Image { */ boolean verboseLogging; + void setName(String name) { + this.name = name; + } + + void setBuilder(String builder) { + this.builder = builder; + } + BuildRequest getBuildRequest(Artifact artifact, Function applicationContent) { return customize(BuildRequest.of(getOrDeduceName(artifact), applicationContent)); }