From 6119d69679a9ce5174f9ec016c6ef5aa49ea5566 Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Thu, 11 Jun 2020 16:20:39 -0500 Subject: [PATCH] Add runImage option for image building This commit adds a runImage property to the Maven plugin build-image goal and the Gradle bootBuildImage task. The property allows the user to override the run image reference provided in the builder metadata with an alternate run image. The runImage property can be specified in the build file or on the command line. Fixes gh-21534 --- .../platform/build/BuildRequest.java | 52 +++++-- .../buildpack/platform/build/Builder.java | 47 ++++--- .../buildpack/platform/build/Lifecycle.java | 9 +- .../platform/docker/type/ImageReference.java | 15 +- .../platform/build/BuildRequestTests.java | 16 ++- .../platform/build/BuilderTests.java | 44 +++++- .../platform/build/LifecycleTests.java | 10 +- .../docker/type/ImageReferenceTests.java | 21 +++ .../build/image-with-run-image-digest.json | 132 ++++++++++++++++++ .../build/lifecycle-creator-clean-cache.json | 2 +- .../platform/build/lifecycle-creator.json | 2 +- .../docs/asciidoc/packaging-oci-image.adoc | 15 +- .../packaging/boot-build-image-builder.gradle | 1 + .../boot-build-image-builder.gradle.kts | 1 + .../gradle/tasks/bundling/BootBuildImage.java | 30 ++++ .../BootBuildImageIntegrationTests.java | 7 +- .../tasks/bundling/BootBuildImageTests.java | 11 ++ ...nTests-buildsImageWithCustomBuilder.gradle | 12 -- ...dsImageWithCustomBuilderAndRunImage.gradle | 13 ++ ...tionTests-buildsImageWithCustomName.gradle | 6 +- ...egrationTests-failsWithBuilderError.gradle | 6 +- .../docs/asciidoc/packaging-oci-image.adoc | 22 +-- .../boot/maven/BuildImageTests.java | 6 +- .../build-image-custom-builder/pom.xml | 1 + .../boot/maven/BuildImageMojo.java | 13 +- .../org/springframework/boot/maven/Image.java | 12 ++ .../boot/maven/ImageTests.java | 10 ++ 27 files changed, 427 insertions(+), 89 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-run-image-digest.json delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomBuilder.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomBuilderAndRunImage.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java index 7ba2003b7a..e8a5f1867e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java @@ -46,6 +46,8 @@ public class BuildRequest { private final ImageReference builder; + private final ImageReference runImage; + private final Creator creator; private final Map env; @@ -60,6 +62,7 @@ public class BuildRequest { this.name = name.inTaggedForm(); this.applicationContent = applicationContent; this.builder = DEFAULT_BUILDER; + this.runImage = null; this.env = Collections.emptyMap(); this.cleanCache = false; this.verboseLogging = false; @@ -67,10 +70,12 @@ public class BuildRequest { } BuildRequest(ImageReference name, Function applicationContent, ImageReference builder, - Creator creator, Map env, boolean cleanCache, boolean verboseLogging) { + ImageReference runImage, Creator creator, Map env, boolean cleanCache, + boolean verboseLogging) { this.name = name; this.applicationContent = applicationContent; this.builder = builder; + this.runImage = runImage; this.creator = creator; this.env = env; this.cleanCache = cleanCache; @@ -84,20 +89,29 @@ public class BuildRequest { */ public BuildRequest withBuilder(ImageReference builder) { Assert.notNull(builder, "Builder must not be null"); - builder = (builder.getDigest() != null) ? builder : builder.inTaggedForm(); - return new BuildRequest(this.name, this.applicationContent, builder, this.creator, this.env, this.cleanCache, - this.verboseLogging); + return new BuildRequest(this.name, this.applicationContent, builder.inTaggedOrDigestForm(), this.runImage, + this.creator, this.env, this.cleanCache, this.verboseLogging); } /** - * Return a new {@link BuildRequest} with an updated builder. + * Return a new {@link BuildRequest} with an updated run image. + * @param runImageName the run image to use + * @return an updated build request + */ + public BuildRequest withRunImage(ImageReference runImageName) { + return new BuildRequest(this.name, this.applicationContent, this.builder, runImageName.inTaggedOrDigestForm(), + this.creator, this.env, this.cleanCache, this.verboseLogging); + } + + /** + * Return a new {@link BuildRequest} with an updated creator. * @param creator the new {@code Creator} to use * @return an updated build request */ public BuildRequest withCreator(Creator creator) { Assert.notNull(creator, "Creator must not be null"); - return new BuildRequest(this.name, this.applicationContent, this.builder, creator, this.env, this.cleanCache, - this.verboseLogging); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, creator, this.env, + this.cleanCache, this.verboseLogging); } /** @@ -111,12 +125,12 @@ public class BuildRequest { Assert.hasText(value, "Value must not be empty"); Map env = new LinkedHashMap<>(this.env); env.put(name, value); - return new BuildRequest(this.name, this.applicationContent, this.builder, this.creator, + return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, Collections.unmodifiableMap(env), this.cleanCache, this.verboseLogging); } /** - * Return a new {@link BuildRequest} with an additional env variables. + * Return a new {@link BuildRequest} with additional env variables. * @param env the additional variables * @return an updated build request */ @@ -124,27 +138,27 @@ public class BuildRequest { Assert.notNull(env, "Env must not be null"); Map updatedEnv = new LinkedHashMap<>(this.env); updatedEnv.putAll(env); - return new BuildRequest(this.name, this.applicationContent, this.builder, this.creator, + return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, Collections.unmodifiableMap(updatedEnv), this.cleanCache, this.verboseLogging); } /** - * Return a new {@link BuildRequest} with an specific clean cache settings. + * Return a new {@link BuildRequest} with an updated clean cache setting. * @param cleanCache if the cache should be cleaned * @return an updated build request */ public BuildRequest withCleanCache(boolean cleanCache) { - return new BuildRequest(this.name, this.applicationContent, this.builder, this.creator, this.env, cleanCache, - this.verboseLogging); + return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, + cleanCache, this.verboseLogging); } /** - * Return a new {@link BuildRequest} with an specific verbose logging settings. + * Return a new {@link BuildRequest} with an updated verbose logging setting. * @param verboseLogging if verbose logging should be used * @return an updated build request */ public BuildRequest withVerboseLogging(boolean verboseLogging) { - return new BuildRequest(this.name, this.applicationContent, this.builder, this.creator, this.env, + return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, this.cleanCache, verboseLogging); } @@ -175,6 +189,14 @@ public class BuildRequest { return this.builder; } + /** + * Return the run image that should be used, if provided. + * @return the run image + */ + public ImageReference getRunImage() { + return this.runImage; + } + /** * Return the {@link Creator} the builder should use. * @return the {@code Creator} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java index ba4d7c9410..777f01cb91 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java @@ -63,15 +63,12 @@ public class Builder { Image builderImage = pullBuilder(request); BuilderMetadata builderMetadata = BuilderMetadata.fromImage(builderImage); BuildOwner buildOwner = BuildOwner.fromEnv(builderImage.getConfig().getEnv()); - StackId stackId = StackId.fromImage(builderImage); - ImageReference runImageReference = getRunImageReference(builderMetadata.getStack()); - Image runImage = pullRunImage(request, runImageReference); - assertHasExpectedStackId(runImage, stackId); + request = determineRunImage(request, builderImage, builderMetadata.getStack()); EphemeralBuilder builder = new EphemeralBuilder(buildOwner, builderImage, builderMetadata, request.getCreator(), request.getEnv()); this.docker.image().load(builder.getArchive(), UpdateListener.none()); try { - executeLifecycle(request, runImageReference, builder); + executeLifecycle(request, builder); } finally { this.docker.image().remove(builder.getName(), true); @@ -87,29 +84,41 @@ public class Builder { return builderImage; } - private ImageReference getRunImageReference(Stack stack) { - String name = stack.getRunImage().getImage(); - Assert.state(StringUtils.hasText(name), "Run image must be specified"); - return ImageReference.of(name).inTaggedForm(); + private BuildRequest determineRunImage(BuildRequest request, Image builderImage, Stack builderStack) + throws IOException { + if (request.getRunImage() == null) { + ImageReference runImage = getRunImageReferenceForStack(builderStack); + request = request.withRunImage(runImage); + } + Image runImage = pullRunImage(request); + assertStackIdsMatch(runImage, builderImage); + return request; } - private Image pullRunImage(BuildRequest request, ImageReference name) throws IOException { - Consumer progressConsumer = this.log.pullingRunImage(request, name); + private ImageReference getRunImageReferenceForStack(Stack stack) { + String name = stack.getRunImage().getImage(); + Assert.state(StringUtils.hasText(name), "Run image must be specified in the builder image stack"); + return ImageReference.of(name).inTaggedOrDigestForm(); + } + + private Image pullRunImage(BuildRequest request) throws IOException { + ImageReference runImage = request.getRunImage(); + Consumer progressConsumer = this.log.pullingRunImage(request, runImage); TotalProgressPullListener listener = new TotalProgressPullListener(progressConsumer); - Image image = this.docker.image().pull(name, listener); + Image image = this.docker.image().pull(runImage, listener); this.log.pulledRunImage(request, image); return image; } - private void assertHasExpectedStackId(Image image, StackId stackId) { - StackId pulledStackId = StackId.fromImage(image); - Assert.state(pulledStackId.equals(stackId), - "Run image stack '" + pulledStackId + "' does not match builder stack '" + stackId + "'"); + private void assertStackIdsMatch(Image runImage, Image builderImage) { + StackId runImageStackId = StackId.fromImage(runImage); + StackId builderImageStackId = StackId.fromImage(builderImage); + Assert.state(runImageStackId.equals(builderImageStackId), + "Run image stack '" + runImageStackId + "' does not match builder stack '" + builderImageStackId + "'"); } - private void executeLifecycle(BuildRequest request, ImageReference runImageReference, EphemeralBuilder builder) - throws IOException { - try (Lifecycle lifecycle = new Lifecycle(this.log, this.docker, request, runImageReference, builder)) { + private void executeLifecycle(BuildRequest request, EphemeralBuilder builder) throws IOException { + try (Lifecycle lifecycle = new Lifecycle(this.log, this.docker, request, builder)) { lifecycle.execute(); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java index 1b343adf7d..222624efdf 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java @@ -48,8 +48,6 @@ class Lifecycle implements Closeable { private final BuildRequest request; - private final ImageReference runImageReference; - private final EphemeralBuilder builder; private final LifecycleVersion lifecycleVersion; @@ -73,15 +71,12 @@ class Lifecycle implements Closeable { * @param log build output log * @param docker the Docker API * @param request the request to process - * @param runImageReference a reference to run image that should be used * @param builder the ephemeral builder used to run the phases */ - Lifecycle(BuildLog log, DockerApi docker, BuildRequest request, ImageReference runImageReference, - EphemeralBuilder builder) { + Lifecycle(BuildLog log, DockerApi docker, BuildRequest request, EphemeralBuilder builder) { this.log = log; this.docker = docker; this.request = request; - this.runImageReference = runImageReference; this.builder = builder; this.lifecycleVersion = LifecycleVersion.parse(builder.getBuilderMetadata().getLifecycle().getVersion()); this.platformVersion = ApiVersion.parse(builder.getBuilderMetadata().getLifecycle().getApi().getPlatform()); @@ -125,7 +120,7 @@ class Lifecycle implements Closeable { phase.withLogLevelArg(); phase.withArgs("-app", Directory.APPLICATION); phase.withArgs("-platform", Directory.PLATFORM); - phase.withArgs("-run-image", this.runImageReference); + phase.withArgs("-run-image", this.request.getRunImage()); phase.withArgs("-layers", Directory.LAYERS); phase.withArgs("-cache-dir", Directory.CACHE); phase.withArgs("-launch-cache", Directory.LAUNCH_CACHE); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageReference.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageReference.java index ab42cde7e9..1549274137 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageReference.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageReference.java @@ -27,6 +27,7 @@ import org.springframework.util.ObjectUtils; * A reference to a Docker image of the form {@code "imagename[:tag|@digest]"}. * * @author Phillip Webb + * @author Scott Frederick * @since 2.3.0 * @see ImageName * @see "Image reference '" + this + "' cannot contain a digest"); - return new ImageReference(this.name, (this.tag != null) ? this.tag : LATEST, this.digest); + return new ImageReference(this.name, (this.tag != null) ? this.tag : LATEST, null); + } + + /** + * Return an {@link ImageReference} containing either a tag or a digest. If neither + * the digest or the tag has been defined then tag {@code latest} is used. + * @return the image reference in tagged or digest form + */ + public ImageReference inTaggedOrDigestForm() { + if (this.digest != null) { + return this; + } + return inTaggedForm(); } /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java index a6b08425b5..266fd7d1b9 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java @@ -82,7 +82,6 @@ public class BuildRequestTests { assertThatIllegalArgumentException() .isThrownBy(() -> BuildRequest.forJarFile(new File(this.tempDir, "missing.jar"))) .withMessage("JarFile must exist"); - } @Test @@ -106,6 +105,21 @@ public class BuildRequestTests { "docker.io/spring/builder:@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); } + @Test + void withRunImageUpdatesRunImage() throws IOException { + BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")) + .withRunImage(ImageReference.of("example.com/custom/run-image:latest")); + assertThat(request.getRunImage().toString()).isEqualTo("example.com/custom/run-image:latest"); + } + + @Test + void withRunImageWhenHasDigestUpdatesRunImage() throws IOException { + BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")).withRunImage(ImageReference + .of("example.com/custom/run-image:@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d")); + assertThat(request.getRunImage().toString()).isEqualTo( + "example.com/custom/run-image:@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); + } + @Test void withCreatorUpdatesCreator() throws IOException { BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java index 912253eb0f..e46a0a4e6c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java @@ -106,6 +106,47 @@ class BuilderTests { verify(docker.image()).remove(archive.getValue().getTag(), true); } + @Test + void buildInvokesBuilderWithRunImageInDigestForm() throws Exception { + TestPrintStream out = new TestPrintStream(); + DockerApi docker = mockDockerApi(); + Image builderImage = loadImage("image-with-run-image-digest.json"); + Image runImage = loadImage("run-image.json"); + given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any())) + .willAnswer(withPulledImage(builderImage)); + given(docker.image().pull(eq(ImageReference.of( + "docker.io/cloudfoundry/run:@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d")), + any())).willAnswer(withPulledImage(runImage)); + Builder builder = new Builder(BuildLog.to(out), docker); + BuildRequest request = getTestRequest(); + builder.build(request); + assertThat(out.toString()).contains("Running creator"); + assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); + ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); + verify(docker.image()).load(archive.capture(), any()); + verify(docker.image()).remove(archive.getValue().getTag(), true); + } + + @Test + void buildInvokesBuilderWithRunImageFromRequest() throws Exception { + TestPrintStream out = new TestPrintStream(); + DockerApi docker = mockDockerApi(); + Image builderImage = loadImage("image.json"); + Image runImage = loadImage("run-image.json"); + given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any())) + .willAnswer(withPulledImage(builderImage)); + given(docker.image().pull(eq(ImageReference.of("example.com/custom/run:latest")), any())) + .willAnswer(withPulledImage(runImage)); + Builder builder = new Builder(BuildLog.to(out), docker); + BuildRequest request = getTestRequest().withRunImage(ImageReference.of("example.com/custom/run:latest")); + builder.build(request); + assertThat(out.toString()).contains("Running creator"); + assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); + ArgumentCaptor archive = ArgumentCaptor.forClass(ImageArchive.class); + verify(docker.image()).load(archive.capture(), any()); + verify(docker.image()).remove(archive.getValue().getTag(), true); + } + @Test void buildWhenStackIdDoesNotMatchThrowsException() throws Exception { TestPrintStream out = new TestPrintStream(); @@ -175,8 +216,7 @@ class BuilderTests { private BuildRequest getTestRequest() { TarArchive content = mock(TarArchive.class); ImageReference name = ImageReference.of("my-application"); - BuildRequest request = BuildRequest.of(name, (owner) -> content); - return request; + return BuildRequest.of(name, (owner) -> content); } private Image loadImage(String name) throws IOException { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java index 298fdb72a2..d97fccbaa2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java @@ -150,7 +150,7 @@ class LifecycleTests { private BuildRequest getTestRequest() { TarArchive content = mock(TarArchive.class); ImageReference name = ImageReference.of("my-application"); - return BuildRequest.of(name, (owner) -> content); + return BuildRequest.of(name, (owner) -> content).withRunImage(ImageReference.of("cloudfoundry/run")); } private Lifecycle createLifecycle() throws IOException { @@ -159,8 +159,7 @@ class LifecycleTests { private Lifecycle createLifecycle(BuildRequest request) throws IOException { EphemeralBuilder builder = mockEphemeralBuilder(); - return new TestLifecycle(BuildLog.to(this.out), this.docker, request, ImageReference.of("cloudfoundry/run"), - builder); + return new TestLifecycle(BuildLog.to(this.out), this.docker, request, builder); } private EphemeralBuilder mockEphemeralBuilder() throws IOException { @@ -208,9 +207,8 @@ class LifecycleTests { static class TestLifecycle extends Lifecycle { - TestLifecycle(BuildLog log, DockerApi docker, BuildRequest request, ImageReference runImageReference, - EphemeralBuilder builder) { - super(log, docker, request, runImageReference, builder); + TestLifecycle(BuildLog log, DockerApi docker, BuildRequest request, EphemeralBuilder builder) { + super(log, docker, request, builder); } @Override diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceTests.java index 0227085f0f..5b22402767 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceTests.java @@ -28,6 +28,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException; * Tests for {@link ImageReference}. * * @author Phillip Webb + * @author Scott Frederick */ class ImageReferenceTests { @@ -223,6 +224,26 @@ class ImageReferenceTests { assertThat(reference.inTaggedForm().toString()).isEqualTo("docker.io/library/ubuntu:bionic"); } + @Test + void inTaggedOrDigestFormWhenHasDigestUsesDigest() { + ImageReference reference = ImageReference + .of("ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); + assertThat(reference.inTaggedOrDigestForm().toString()).isEqualTo( + "docker.io/library/ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); + } + + @Test + void inTaggedOrDigestFormWhenHasTagUsesTag() { + ImageReference reference = ImageReference.of("ubuntu:bionic"); + assertThat(reference.inTaggedOrDigestForm().toString()).isEqualTo("docker.io/library/ubuntu:bionic"); + } + + @Test + void inTaggedOrDigestFormWhenHasNoTagOrDigestUsesLatest() { + ImageReference reference = ImageReference.of("ubuntu"); + assertThat(reference.inTaggedOrDigestForm().toString()).isEqualTo("docker.io/library/ubuntu:latest"); + } + @Test void equalsAndHashCode() { ImageReference r1 = ImageReference.of("ubuntu:bionic"); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-run-image-digest.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-run-image-digest.json new file mode 100644 index 0000000000..c2a3d83152 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-run-image-digest.json @@ -0,0 +1,132 @@ +{ + "Id": "sha256:44cc64492fb6a6d78d3e6d087f380ae6e479aa1b2c79823b32cdacfcc2f3d715", + "RepoTags": [ + "paketo-buildpacks/cnb:base", + "paketo-buildpacks/builder:base-platform-api-0.2" + ], + "RepoDigests": [ + "paketo-buidpacks/cnb@sha256:5b03a853e636b78c44e475bbc514e2b7b140cc41cca8ab907e9753431ae8c0b0" + ], + "Parent": "", + "Comment": "", + "Created": "1980-01-01T00:00:01Z", + "Container": "", + "ContainerConfig": { + "Hostname": "", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": null, + "Image": "", + "Volumes": null, + "WorkingDir": "", + "Entrypoint": null, + "OnBuild": null, + "Labels": null + }, + "DockerVersion": "", + "Author": "", + "Config": { + "Hostname": "", + "Domainname": "", + "User": "1000:1000", + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "CNB_USER_ID=1000", + "CNB_GROUP_ID=1000", + "CNB_STACK_ID=io.buildpacks.stacks.bionic" + ], + "Cmd": [ + "/bin/bash" + ], + "ArgsEscaped": true, + "Image": "sha256:2d153261a5e359c632a17377cfb5d1986c27b96c8b6e95334bf80f1029dbd4bb", + "Volumes": null, + "WorkingDir": "/layers", + "Entrypoint": null, + "OnBuild": null, + "Labels": { + "io.buildpacks.builder.metadata": "{\"description\":\"Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang\",\"buildpacks\":[{\"id\":\"org.cloudfoundry.googlestackdriver\",\"version\":\"v1.1.11\"},{\"id\":\"org.cloudfoundry.springboot\",\"version\":\"v1.2.13\"},{\"id\":\"org.cloudfoundry.debug\",\"version\":\"v1.2.11\"},{\"id\":\"org.cloudfoundry.tomcat\",\"version\":\"v1.3.18\"},{\"id\":\"org.cloudfoundry.go\",\"version\":\"v0.0.4\"},{\"id\":\"org.cloudfoundry.openjdk\",\"version\":\"v1.2.14\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"version\":\"v1.2.15\"},{\"id\":\"org.cloudfoundry.jvmapplication\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"version\":\"v1.1.11\"},{\"id\":\"org.cloudfoundry.archiveexpanding\",\"version\":\"v1.0.102\"},{\"id\":\"org.cloudfoundry.jmx\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.nodejs\",\"version\":\"v2.0.8\"},{\"id\":\"org.cloudfoundry.jdbc\",\"version\":\"v1.1.14\"},{\"id\":\"org.cloudfoundry.procfile\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.dotnet-core\",\"version\":\"v0.0.6\"},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.distzip\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.101\"},{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.89\"},{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.1.3\"},{\"id\":\"org.cloudfoundry.yarn-install\",\"version\":\"0.1.10\"},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.118\"},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.68\"},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.115\"},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.127\"},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.122\"},{\"id\":\"org.cloudfoundry.icu\",\"version\":\"0.0.43\"},{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.158\"}],\"stack\":{\"runImage\":{\"image\":\"cloudfoundry/run:@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d\",\"mirrors\":null}},\"lifecycle\":{\"version\":\"0.7.2\",\"api\":{\"buildpack\":\"0.2\",\"platform\":\"0.3\"}},\"createdBy\":{\"name\":\"Pack CLI\",\"version\":\"v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)\"}}", + "io.buildpacks.buildpack.layers": "{\"org.cloudfoundry.archiveexpanding\":{\"v1.0.102\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:73b1a8ac1f7fca3d545766ce7fd3c56b40a63724ab78e464d71a29da0c6ac31c\"}},\"org.cloudfoundry.azureapplicationinsights\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0a2f7c467efbb8b1ac222f09013b88b68f3c117ec6b6e9dc95564be50f271ab\"}},\"org.cloudfoundry.buildsystem\":{\"v1.2.15\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:3f50d3a0e1a969a9606b59e5295842d731e425108cb349ce6c69a5b30ea1bab9\"}},\"org.cloudfoundry.debug\":{\"v1.2.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:04559213a01cfac69a8d6a6facb58b8681666525c74f605207c40a61a0f4c9b7\"}},\"org.cloudfoundry.dep\":{\"0.0.101\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:6aae3a2d671d369eec34dc9146ef267d06c87461f271fbfbe9136775ecf5dfb8\"}},\"org.cloudfoundry.distzip\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0715e661e13d7d3ded5bdc068edd01e5b3aa0e2805152f4c8a1428b4e0673df\"}},\"org.cloudfoundry.dotnet-core\":{\"v0.0.6\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.158\",\"optional\":true},{\"id\":\"org.cloudfoundry.icu\",\"version\":\"0.0.43\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.127\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.118\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.122\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.68\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.115\"}]}],\"layerDiffID\":\"sha256:aa0effdf787ecfe74d60d6771006717fd1a9ce1ce0a8161624baa61b68120357\"}},\"org.cloudfoundry.dotnet-core-aspnet\":{\"0.0.118\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:a06615b5adc1a3afb7abd524e82f6900a28910927fcf0d4e9b85fd1fcbeb53ad\"}},\"org.cloudfoundry.dotnet-core-build\":{\"0.0.68\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:26d6f1e76275d17860005f7ab9b74fdd2283fcf84e0446bd88d49a6b4e9609f9\"}},\"org.cloudfoundry.dotnet-core-conf\":{\"0.0.115\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:55f7c052cf70c8ca01b8e241c0c5c8a9675599d4904c69bfb961a472e246238d\"}},\"org.cloudfoundry.dotnet-core-runtime\":{\"0.0.127\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:d9958b816a9ad179fca8c18d17c07e9814b152d461c685e1443bec6f990ab990\"}},\"org.cloudfoundry.dotnet-core-sdk\":{\"0.0.122\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:52142799a4b687fe6e5cf397c41064499ea6cc554b94904d46c1acade998e11f\"}},\"org.cloudfoundry.go\":{\"v0.0.4\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.89\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.101\"}]}],\"layerDiffID\":\"sha256:352a299d6af4773322ed3643d8f98b01aad6f15d838d1852e52a0a3ca56c6efb\"}},\"org.cloudfoundry.go-compiler\":{\"0.0.105\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:cb21f14e306d94e437c5418d275bcc6efcea6bc9b3d26a400bdf54fa62242c24\"}},\"org.cloudfoundry.go-mod\":{\"0.0.89\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:c9da8171f5ca048109ffba5e940e3a7d2db567eda281f92b0eb483173df06add\"}},\"org.cloudfoundry.googlestackdriver\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:ff29efc56c31eeccc79a33c6e4abd7b1ab3547d95e1cf83974af65a493576c41\"}},\"org.cloudfoundry.icu\":{\"0.0.43\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:48063dcdd043f9c88604d10fe9542569be8f8111d46806c96b08d77763ffa347\"}},\"org.cloudfoundry.jdbc\":{\"v1.1.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a9c9bbbd69c212b7ab3c1a7f03011ccc4d99a6fce1bf1c785325c7bcad789e62\"}},\"org.cloudfoundry.jmx\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:da62dec6eb4ed884952a1b867fd89e3bfe3c510e5c849cc0ac7050ff867a2469\"}},\"org.cloudfoundry.jvmapplication\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c10732392b97c121a78a5f20201c2a5e834a2b8677196cdd49260a489a54fd22\"}},\"org.cloudfoundry.node-engine\":{\"0.0.158\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:70cf83155575fdb607f23ace41e31b1d5cb1c24dbbbf56f71c383b583724d339\"},\"0.0.163\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:11486cb955594f9d43909b60f94209bb6854f502a5a093207b657afbaa38a777\"}},\"org.cloudfoundry.nodejs\":{\"v2.0.8\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.yarn-install\",\"version\":\"0.1.10\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.1.3\"}]}],\"layerDiffID\":\"sha256:76fe727e4aafc7f56f01282296ab736521c38b9d19c1ae5ebb193f9cd55fa109\"}},\"org.cloudfoundry.npm\":{\"0.1.3\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:243bbd007cb0ee99b704bfe0cf62e1301baa4095ab4c39b01293787a0e4234f1\"}},\"org.cloudfoundry.openjdk\":{\"v1.2.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:486b2abf434bb90cf04bab74f2f8bd2eb488ff90632b56eac4bddcbbf02e8151\"}},\"org.cloudfoundry.procfile\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b7b78159dfdaa0dd484c58652e02fa6b755abfd0adb88f106d16178144e46f33\"}},\"org.cloudfoundry.springautoreconfiguration\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c185540c10fea822c6db1b987fcfe22b55a4662648124b98475db4c9dcddb2ab\"}},\"org.cloudfoundry.springboot\":{\"v1.2.13\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b87e68574cc7dccbe974fa760702ef650711036bf144fd9da1f3a2d8f6ac335f\"}},\"org.cloudfoundry.tomcat\":{\"v1.3.18\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:467c0082c57b80b48487a9b8429887c0744ddc5b066b3f7678866bde89b78ab2\"}},\"org.cloudfoundry.yarn-install\":{\"0.1.10\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:6aefa0ba7ce01584b4a531b18e36470298cee3b30ecae0e0c64b532a5cebd6e7\"}}}", + "io.buildpacks.buildpack.order": "[{\"group\":[{\"id\":\"org.cloudfoundry.openjdk\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"optional\":true},{\"id\":\"org.cloudfoundry.jvmapplication\"},{\"id\":\"org.cloudfoundry.tomcat\",\"optional\":true},{\"id\":\"org.cloudfoundry.springboot\",\"optional\":true},{\"id\":\"org.cloudfoundry.distzip\",\"optional\":true},{\"id\":\"org.cloudfoundry.procfile\",\"optional\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"optional\":true},{\"id\":\"org.cloudfoundry.debug\",\"optional\":true},{\"id\":\"org.cloudfoundry.googlestackdriver\",\"optional\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"optional\":true},{\"id\":\"org.cloudfoundry.jmx\",\"optional\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"optional\":true}]},{\"group\":[{\"id\":\"org.cloudfoundry.nodejs\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.dotnet-core\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.procfile\"}]}]", + "io.buildpacks.stack.id": "io.buildpacks.stacks.bionic", + "io.buildpacks.stack.mixins": "[\"build:git\",\"build:build-essential\"]" + } + }, + "Architecture": "amd64", + "Os": "linux", + "Size": 688884758, + "VirtualSize": 688884758, + "GraphDriver": { + "Data": { + "LowerDir": "/var/lib/docker/overlay2/6a79181b2840da2706624f46ce5abd4448973b4f951925d5a276b273256063b2/diff:/var/lib/docker/overlay2/429419a203100f60ab16ec6c879fce975c8138422b9053f80accd6124c730fc2/diff:/var/lib/docker/overlay2/6e45ed6daf4f4f3b90fd1ec5fa958775000875661d3e8be3f1af218d192b058d/diff:/var/lib/docker/overlay2/22928ad308cdd55b3fe849d92b6e38c6bc303ba7c9beb8c0e79aa958e16b1864/diff:/var/lib/docker/overlay2/2ca9ec213226a1604f57c8e141d6f1168134a5cb2ccd8f91ee9be5a39036e6bf/diff:/var/lib/docker/overlay2/96ae944fe00ec20cf5b4441b112ebcc9395faaf08108c9ee38c62e1da33af1c8/diff:/var/lib/docker/overlay2/13ee52e300e476e27350c9ac6274dedf26af85c3079b42a41f9dfc92eff57a80/diff:/var/lib/docker/overlay2/223edb4cc62a2ba2b8bda866905a55c4798c6c32e31d22d60e6ed4f3169ce85e/diff:/var/lib/docker/overlay2/a41235cd7277299cb74ead47def3771885948719e24075ea3bf37580f3af7ae2/diff:/var/lib/docker/overlay2/ed0438e8e2c27b9d62ad21a0761237c350a2ffc9e52f47c019e4f627091c832e/diff:/var/lib/docker/overlay2/0c27c8229b31eafc57ab739b44962dcc07b72f3d8950888873ecb3cfd385032f/diff:/var/lib/docker/overlay2/0957cbcca052cd58bcf9a3d945b0e6876b0df79c1c534da1872c3415a019427d/diff:/var/lib/docker/overlay2/b621414d53d71349c07df8ed45e3e04b2e97bfbaf4bf0d86463f46e0f810eeb4/diff:/var/lib/docker/overlay2/ad521bc47f0bb44262358cf47c3d81a544d098494cf24a5b510620d34eb9c353/diff:/var/lib/docker/overlay2/081501d5bfbd927e69c10eb320513c7c0d5f00bea8cf9e55faa90579fd33adf4/diff:/var/lib/docker/overlay2/fb1ba66bee5568f5700c72865d020d4171a62bfdd099c3cc05b9a253d36a35a4/diff:/var/lib/docker/overlay2/06bcc6b3adeca727d554f1a745ee33242dfe1b3c6392023ac947666057303288/diff:/var/lib/docker/overlay2/1c5397d63d893202dffde29013ee826fb695bda26c718ee03ddde376be4da0a3/diff:/var/lib/docker/overlay2/76075fb7fd3c6b3fb116fb3b464e220918e56d94461c61af9a1aff288ebdba60/diff:/var/lib/docker/overlay2/43d1026bb7b618393912ecc9ddf57b604336184d5f8dc70bcf6332b5f08a3e8d/diff:/var/lib/docker/overlay2/ee27d1fba3deaca0556f7bab171cb3368f169011dd132cf335b5308728f6db8f/diff:/var/lib/docker/overlay2/464d3ec8d86ff31dcb5063ea25521368ea8e9c7964f65e15ff5e0e1ecdbe991e/diff:/var/lib/docker/overlay2/a4a80c33c8b78f68bdc9dbd5903cc2ba1d48e78b9a97d43acb018823ece8e6cb/diff:/var/lib/docker/overlay2/6494f2f1693cff8b16d51fa95620eb0bb691a76fb39b5175d953649577791297/diff:/var/lib/docker/overlay2/9d49e146f82eb5fc4fd81613538e9c5f5f95091fbbc8c49729c6c9140ae356de/diff:/var/lib/docker/overlay2/2934818c52bcd017abe000e71342d67fbc9ccb7dbc165ce05e3250e2110229a5/diff:/var/lib/docker/overlay2/651ca06b2bf75e2122855264287fc937f30d2b49229d628909895be7128b4eb6/diff:/var/lib/docker/overlay2/c93bab59be44fa1b66689dc059d26742d00d2e787d06c3236e1f116199c9807e/diff:/var/lib/docker/overlay2/d0a8e2a0c7e0df172f7a8ebe75e2dce371bb6cc65531b06799bc677c5b5e3627/diff:/var/lib/docker/overlay2/7d14bac240e0d7936351e3fac80b7fbe2a209f4de8992091c4f75e41f9627852/diff:/var/lib/docker/overlay2/d6b192ea137a4ae95e309d263ee8c890e35da02aacd9bdcf5adbd4c28a0c0a3f/diff:/var/lib/docker/overlay2/335bfb632ab7723e25fb5dc7b67389e6ec38178ef10bfbf83337501403e61574/diff:/var/lib/docker/overlay2/0293c7e3472da58f51cbdf15fb293ff71e32c1f80f83f00fb09f8941deef5e43/diff:/var/lib/docker/overlay2/55faa8b47bcb0dd29c3836580f451a0461dd499065af9c830beff6e8329ab484/diff:/var/lib/docker/overlay2/afcb6e109c1ba7d71b8a8b7e573d4ce04f22da3fe0ee523359db5cfb95e65bb6/diff:/var/lib/docker/overlay2/b42eefd9bf6629ae9d16e7aba6ba3939d37816aba7a0999f6d639012a3119be1/diff:/var/lib/docker/overlay2/a9832c8f81ee889a622ce4d95d9f4bab2f91d30e18f69bfd7cfc385c781068d4/diff:/var/lib/docker/overlay2/224041c135f13881a98b9e833584bedab81d5650061457f522a1ebd1daa2c77a/diff:/var/lib/docker/overlay2/73dfd4e2075fccb239b3d5e9b33b32b8e410bdc3cd5a620b41346f44cc5c51f7/diff:/var/lib/docker/overlay2/b3924ed7c91730f6714d33c455db888604b59ab093033b3f59ac16ecdd777987/diff:/var/lib/docker/overlay2/e36a32cd0ab20b216a8db1a8a166b17464399e4d587d22504088a7a6ef0a68a4/diff:/var/lib/docker/overlay2/3334e94fe191333b65f571912c0fcfbbf31aeb090a2fb9b4cfdbc32a37c0fe5f/diff", + "MergedDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/merged", + "UpperDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/diff", + "WorkDir": "/var/lib/docker/overlay2/f5d133c5929da8cc8266cbbc3e36f924f4a9c835f943fb436445a26b7e1bcc56/work" + }, + "Name": "overlay2" + }, + "RootFS": { + "Type": "layers", + "Layers": [ + "sha256:c8be1b8f4d60d99c281fc2db75e0f56df42a83ad2f0b091621ce19357e19d853", + "sha256:977183d4e9995d9cd5ffdfc0f29e911ec9de777bcb0f507895daa1068477f76f", + "sha256:6597da2e2e52f4d438ad49a14ca79324f130a9ea08745505aa174a8db51cb79d", + "sha256:16542a8fc3be1bfaff6ed1daa7922e7c3b47b6c3a8d98b7fca58b9517bb99b75", + "sha256:2df36adfe1af661aebb75a0db796b074bb8f861fbc8f98f6f642570692b3b133", + "sha256:f499c7d34e01d860492ef1cc34b7d7e1319b3c3c81ee7d23258b21605b5902ca", + "sha256:c4bf1d4e5d4adb566b173a0769d247f67c5dd8ff90dfdcebd8c7060f1c06caa9", + "sha256:15259abd479904cbe0d8d421e5b05b2e5745e2bf82e62cdd7fb6d3eafbe4168a", + "sha256:6aa3691a73805f608e5fce69fb6bc89aec8362f58a6b4be2682515e9cfa3cc1a", + "sha256:2d6ad1b66f5660dd860c1fe2d90d26398fcfab4dc1c87c3d5e7c0fc24f8d6fb2", + "sha256:ff29efc56c31eeccc79a33c6e4abd7b1ab3547d95e1cf83974af65a493576c41", + "sha256:b87e68574cc7dccbe974fa760702ef650711036bf144fd9da1f3a2d8f6ac335f", + "sha256:04559213a01cfac69a8d6a6facb58b8681666525c74f605207c40a61a0f4c9b7", + "sha256:467c0082c57b80b48487a9b8429887c0744ddc5b066b3f7678866bde89b78ab2", + "sha256:352a299d6af4773322ed3643d8f98b01aad6f15d838d1852e52a0a3ca56c6efb", + "sha256:486b2abf434bb90cf04bab74f2f8bd2eb488ff90632b56eac4bddcbbf02e8151", + "sha256:3f50d3a0e1a969a9606b59e5295842d731e425108cb349ce6c69a5b30ea1bab9", + "sha256:c10732392b97c121a78a5f20201c2a5e834a2b8677196cdd49260a489a54fd22", + "sha256:c185540c10fea822c6db1b987fcfe22b55a4662648124b98475db4c9dcddb2ab", + "sha256:73b1a8ac1f7fca3d545766ce7fd3c56b40a63724ab78e464d71a29da0c6ac31c", + "sha256:da62dec6eb4ed884952a1b867fd89e3bfe3c510e5c849cc0ac7050ff867a2469", + "sha256:76fe727e4aafc7f56f01282296ab736521c38b9d19c1ae5ebb193f9cd55fa109", + "sha256:a9c9bbbd69c212b7ab3c1a7f03011ccc4d99a6fce1bf1c785325c7bcad789e62", + "sha256:b7b78159dfdaa0dd484c58652e02fa6b755abfd0adb88f106d16178144e46f33", + "sha256:aa0effdf787ecfe74d60d6771006717fd1a9ce1ce0a8161624baa61b68120357", + "sha256:a0a2f7c467efbb8b1ac222f09013b88b68f3c117ec6b6e9dc95564be50f271ab", + "sha256:a0715e661e13d7d3ded5bdc068edd01e5b3aa0e2805152f4c8a1428b4e0673df", + "sha256:6aae3a2d671d369eec34dc9146ef267d06c87461f271fbfbe9136775ecf5dfb8", + "sha256:cb21f14e306d94e437c5418d275bcc6efcea6bc9b3d26a400bdf54fa62242c24", + "sha256:c9da8171f5ca048109ffba5e940e3a7d2db567eda281f92b0eb483173df06add", + "sha256:11486cb955594f9d43909b60f94209bb6854f502a5a093207b657afbaa38a777", + "sha256:243bbd007cb0ee99b704bfe0cf62e1301baa4095ab4c39b01293787a0e4234f1", + "sha256:6aefa0ba7ce01584b4a531b18e36470298cee3b30ecae0e0c64b532a5cebd6e7", + "sha256:a06615b5adc1a3afb7abd524e82f6900a28910927fcf0d4e9b85fd1fcbeb53ad", + "sha256:26d6f1e76275d17860005f7ab9b74fdd2283fcf84e0446bd88d49a6b4e9609f9", + "sha256:55f7c052cf70c8ca01b8e241c0c5c8a9675599d4904c69bfb961a472e246238d", + "sha256:d9958b816a9ad179fca8c18d17c07e9814b152d461c685e1443bec6f990ab990", + "sha256:52142799a4b687fe6e5cf397c41064499ea6cc554b94904d46c1acade998e11f", + "sha256:48063dcdd043f9c88604d10fe9542569be8f8111d46806c96b08d77763ffa347", + "sha256:70cf83155575fdb607f23ace41e31b1d5cb1c24dbbbf56f71c383b583724d339", + "sha256:6cf0f8f815d5371cf5c04e7ebf76c62467948d693b8343184d1446036980d261", + "sha256:7cbffcbb09fc5e9d00372e80990016609c09cc3113429ddc951c4a19b1a5ec72", + "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" + ] + }, + "Metadata": { + "LastTagTime": "0001-01-01T00:00:00Z" + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-clean-cache.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-clean-cache.json index 9572eba148..dbbb51dda5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-clean-cache.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-clean-cache.json @@ -1,7 +1,7 @@ { "User" : "root", "Image" : "pack.local/ephemeral-builder", - "Cmd" : [ "/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "-skip-restore", "docker.io/library/my-application:latest" ], + "Cmd" : [ "/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "-skip-restore", "docker.io/library/my-application:latest" ], "Labels" : { "author" : "spring-boot" }, diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator.json index e26bbcf6a5..7f5c288341 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator.json @@ -1,7 +1,7 @@ { "User" : "root", "Image" : "pack.local/ephemeral-builder", - "Cmd" : [ "/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "docker.io/library/my-application:latest" ], + "Cmd" : [ "/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "docker.io/library/my-application:latest" ], "Labels" : { "author" : "spring-boot" }, 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 7e8266aad8..6f1a24d050 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 @@ -50,6 +50,11 @@ The following table summarizes the available properties and their default values | Name of the Builder image to use. | `gcr.io/paketo-buildpacks/builder:base-platform-api-0.3` +| `runImage` +| `--runImage` +| Name of the run image to use. +| No default value, indicating the run image specified in Builder metadata should be used. + | `imageName` | `--imageName` | {spring-boot-api}/buildpack/platform/docker/type/ImageReference.html#of-java.lang.String-[Image name] for the generated image. @@ -79,8 +84,8 @@ The following table summarizes the available properties and their default values [[build-image-example-custom-image-builder]] -==== Custom Image Builder -If you need to customize the builder used to create the image, configure the task as shown in the following example: +==== Custom Image Builder and Run Image +If you need to customize the builder used to create the image or the run image used to launch the built image, configure the task as shown in the following example: [source,groovy,indent=0,subs="verbatim,attributes",role="primary"] .Groovy @@ -94,13 +99,13 @@ include::../gradle/packaging/boot-build-image-builder.gradle[tags=builder] include::../gradle/packaging/boot-build-image-builder.gradle.kts[tags=builder] ---- -This configuration will use a builder image with the name `mine/java-cnb-builder` and the tag `latest`. +This configuration will use a builder image with the name `mine/java-cnb-builder` and the tag `latest`, and the run image named `mine/java-cnb-run` and the tag `latest`. -The builder can be specified on the command line as well, as shown in this example: +The builder and run image can be specified on the command line as well, as shown in this example: [indent=0] ---- -$ gradle bootBuildImage --builder=mine/java-cnb-builder +$ gradle bootBuildImage --builder=mine/java-cnb-builder --runImage=mine/java-cnb-run ---- diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-builder.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-builder.gradle index 7c4192ce81..1008610293 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-builder.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-builder.gradle @@ -10,5 +10,6 @@ bootJar { // tag::builder[] bootBuildImage { builder = "mine/java-cnb-builder" + runImage = "mine/java-cnb-run" } // end::builder[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-builder.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-builder.gradle.kts index b657594c65..71b05174d9 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-builder.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-builder.gradle.kts @@ -12,5 +12,6 @@ tasks.getByName("bootJar") { // tag::builder[] tasks.getByName("bootBuildImage") { builder = "mine/java-cnb-builder" + runImage = "mine/java-cnb-run" } // end::builder[] 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 efe91b3848..787ba46cb7 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 @@ -61,6 +61,8 @@ public class BootBuildImage extends DefaultTask { private String builder; + private String runImage; + private Map environment = new HashMap<>(); private boolean cleanCache; @@ -133,6 +135,26 @@ public class BootBuildImage extends DefaultTask { this.builder = builder; } + /** + * Returns the run image that will be included in the built image. When {@code null}, + * the run image bundled with the builder will be used. + * @return the run image + */ + @Input + @Optional + public String getRunImage() { + return this.runImage; + } + + /** + * Sets the run image that will be included in the built image. + * @param runImage the run image + */ + @Option(option = "runImage", description = "The name of the run image to use") + public void setRunImage(String runImage) { + this.runImage = runImage; + } + /** * Returns the environment that will be used when building the image. * @return the environment @@ -228,6 +250,7 @@ public class BootBuildImage extends DefaultTask { private BuildRequest customize(BuildRequest request) { request = customizeBuilder(request); + request = customizeRunImage(request); request = customizeEnvironment(request); request = customizeCreator(request); request = request.withCleanCache(this.cleanCache); @@ -242,6 +265,13 @@ public class BootBuildImage extends DefaultTask { return request; } + private BuildRequest customizeRunImage(BuildRequest request) { + if (StringUtils.hasText(this.runImage)) { + return request.withRunImage(ImageReference.of(this.runImage)); + } + return request; + } + private BuildRequest customizeEnvironment(BuildRequest request) { if (this.environment != null && !this.environment.isEmpty()) { request = request.withEnv(this.environment); 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 921c45c17f..396ea98a8a 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 @@ -89,13 +89,14 @@ class BootBuildImageIntegrationTests { } @TestTemplate - void buildsImageWithCustomBuilder() throws IOException { + void buildsImageWithCustomBuilderAndRunImage() throws IOException { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage"); assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("example/test-image-custom"); assertThat(result.getOutput()).contains("paketo-buildpacks/builder:full-cf-platform-api-0.3"); + assertThat(result.getOutput()).contains("paketo-buildpacks/run:full-cnb-cf"); ImageReference imageReference = ImageReference.of(ImageName.of("example/test-image-custom")); try (GenericContainer container = new GenericContainer<>(imageReference.toString())) { container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start(); @@ -110,10 +111,12 @@ class BootBuildImageIntegrationTests { writeMainClass(); writeLongNameResource(); BuildResult result = this.gradleBuild.build("bootBuildImage", "--imageName=example/test-image-cmd", - "--builder=gcr.io/paketo-buildpacks/builder:full-cf-platform-api-0.3"); + "--builder=gcr.io/paketo-buildpacks/builder:full-cf-platform-api-0.3", + "--runImage=gcr.io/paketo-buildpacks/run:full-cnb-cf"); assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("example/test-image-cmd"); assertThat(result.getOutput()).contains("paketo-buildpacks/builder:full-cf-platform-api-0.3"); + assertThat(result.getOutput()).contains("paketo-buildpacks/run:full-cnb-cf"); ImageReference imageReference = ImageReference.of(ImageName.of("example/test-image-cmd")); try (GenericContainer container = new GenericContainer<>(imageReference.toString())) { container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java index 986f62d273..485e32e08d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java @@ -183,4 +183,15 @@ class BootBuildImageTests { assertThat(this.buildImage.createRequest().getBuilder().getName()).isEqualTo("test/builder"); } + @Test + void whenNoRunImageIsConfiguredThenRequestUsesDefaultRunImage() { + assertThat(this.buildImage.createRequest().getRunImage()).isNull(); + } + + @Test + void whenRunImageIsConfiguredThenRequestUsesSpecifiedRunImage() { + this.buildImage.setRunImage("example.com/test/run:1.0"); + assertThat(this.buildImage.createRequest().getRunImage().getName()).isEqualTo("test/run"); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomBuilder.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomBuilder.gradle deleted file mode 100644 index bf3cecec27..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomBuilder.gradle +++ /dev/null @@ -1,12 +0,0 @@ -plugins { - id 'java' - id 'org.springframework.boot' version '{version}' -} - -sourceCompatibility = '1.8' -targetCompatibility = '1.8' - - bootBuildImage { - imageName = "example/test-image-custom" - builder = "gcr.io/paketo-buildpacks/builder:full-cf-platform-api-0.3" - } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomBuilderAndRunImage.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomBuilderAndRunImage.gradle new file mode 100644 index 0000000000..56e5bce571 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomBuilderAndRunImage.gradle @@ -0,0 +1,13 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +sourceCompatibility = '1.8' +targetCompatibility = '1.8' + +bootBuildImage { + imageName = "example/test-image-custom" + builder = "gcr.io/paketo-buildpacks/builder:full-cf-platform-api-0.3" + runImage = "gcr.io/paketo-buildpacks/run:full-cnb-cf" +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomName.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomName.gradle index 58905f9d04..e306457624 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomName.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithCustomName.gradle @@ -6,6 +6,6 @@ plugins { sourceCompatibility = '1.8' targetCompatibility = '1.8' - bootBuildImage { - imageName = "example/test-image-name" - } +bootBuildImage { + imageName = "example/test-image-name" +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithBuilderError.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithBuilderError.gradle index 21d528ff59..fb706c51ca 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithBuilderError.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-failsWithBuilderError.gradle @@ -6,6 +6,6 @@ plugins { sourceCompatibility = '1.8' targetCompatibility = '1.8' - bootBuildImage { - environment = ["BP_JVM_VERSION" : "13.9.9"] - } +bootBuildImage { + environment = ["BP_JVM_VERSION": "13.9.9"] +} 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 9a3d201556..744d6592ef 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 @@ -43,13 +43,13 @@ The following table shows the environment variables and their values: |=== | Environment variable | Description -| DOCKER_HOST +| DOCKER_HOST | URL containing the host and port for the Docker daemon - e.g. `tcp://192.168.99.100:2376` -| DOCKER_TLS_VERIFY +| DOCKER_TLS_VERIFY | Enable secure HTTPS protocol when set to `1` (optional) -| DOCKER_CERT_PATH +| DOCKER_CERT_PATH | Path to certificate and key files for HTTPS (required if `DOCKER_TLS_VERIFY=1`, ignored otherwise) |=== @@ -64,7 +64,7 @@ The builder includes multiple {buildpacks-reference}/concepts/components/buildpa 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 `image` parameter allows configuration of the builder and how it should operate on the project. The following table summarizes the available parameters and their default values: |=== @@ -75,6 +75,11 @@ The following table summarizes the available parameters and their default values | `spring-boot.build-image.builder` | `gcr.io/paketo-buildpacks/builder:base-platform-api-0.3` +| `runImage` +| Name of the run image to use. +| `spring-boot.build-image.runImage` +| No default value, indicating the run image specified in Builder metadata should be used. + | `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` @@ -109,7 +114,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 the plugin as shown in the following example: +If you need to customize the builder used to create the image or the run image used to launch the built image, configure the plugin as shown in the following example: [source,xml,indent=0,subs="verbatim,attributes"] ---- @@ -123,6 +128,7 @@ If you need to customize the builder used to create the image, configure the plu mine/java-cnb-builder + mine/java-cnb-run @@ -131,13 +137,13 @@ If you need to customize the builder used to create the image, configure the plu ---- -This configuration will use a builder image with the name `mine/java-cnb-builder` and the tag `latest`. +This configuration will use a builder image with the name `mine/java-cnb-builder` and the tag `latest`, and the run image named `mine/java-cnb-run` and the tag `latest`. -The builder can be specified on the command line as well, as shown in this example: +The builder and run image 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 + $ mvn spring-boot:build-image -Dspring-boot.build-image.builder=mine/java-cnb-builder -Dspring-boot.build-image.runImage=mine/java-cnb-run ---- 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 62c41cd9d5..41292dc13c 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 @@ -95,11 +95,12 @@ public class BuildImageTests extends AbstractArchiveIntegrationTests { .systemProperty("spring-boot.build-image.imageName", "example.com/test/cmd-property-name:v1") .systemProperty("spring-boot.build-image.builder", "gcr.io/paketo-buildpacks/builder:full-cf-platform-api-0.3") + .systemProperty("spring-boot.build-image.runImage", "gcr.io/paketo-buildpacks/run:full-cnb-cf") .execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("example.com/test/cmd-property-name:v1") .contains("paketo-buildpacks/builder:full-cf-platform-api-0.3") - .contains("Successfully built image"); + .contains("paketo-buildpacks/run:full-cnb-cf").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(); @@ -111,10 +112,11 @@ public class BuildImageTests extends AbstractArchiveIntegrationTests { } @TestTemplate - void whenBuildImageIsInvokedWithCustomBuilderImage(MavenBuild mavenBuild) { + void whenBuildImageIsInvokedWithCustomBuilderImageAndRunImage(MavenBuild mavenBuild) { mavenBuild.project("build-image-custom-builder").goals("package").execute((project) -> { assertThat(buildLog(project)).contains("Building image") .contains("paketo-buildpacks/builder:full-cf-platform-api-0.3") + .contains("paketo-buildpacks/run:full-cnb-cf") .contains("docker.io/library/build-image-v2-builder:0.0.1.BUILD-SNAPSHOT") .contains("Successfully built image"); ImageReference imageReference = ImageReference diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-custom-builder/pom.xml b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-custom-builder/pom.xml index b246fc61bb..7b37388b62 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-custom-builder/pom.xml +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-custom-builder/pom.xml @@ -24,6 +24,7 @@ gcr.io/paketo-buildpacks/builder:full-cf-platform-api-0.3 + gcr.io/paketo-buildpacks/run:full-cnb-cf 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 d678f23371..fab1b6660a 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 @@ -94,7 +94,7 @@ public class BuildImageMojo extends AbstractPackagerMojo { private String classifier; /** - * Image configuration, with `builder`, `name`, `env`, `cleanCache` and + * Image configuration, with `builder`, `runImage`, `name`, `env`, `cleanCache` and * `verboseLogging` options. * @since 2.3.0 */ @@ -115,6 +115,14 @@ public class BuildImageMojo extends AbstractPackagerMojo { @Parameter(property = "spring-boot.build-image.builder", readonly = true) String imageBuilder; + /** + * Alias for {@link Image#runImage} to support configuration via command-line + * property. + * @since 2.3.1 + */ + @Parameter(property = "spring-boot.build-image.runImage", readonly = true) + String runImage; + @Override public void execute() throws MojoExecutionException { if (this.project.getPackaging().equals("pom")) { @@ -149,6 +157,9 @@ public class BuildImageMojo extends AbstractPackagerMojo { if (image.builder == null && this.imageBuilder != null) { image.setBuilder(this.imageBuilder); } + if (image.runImage == null && this.runImage != null) { + image.setRunImage(this.runImage); + } 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 ef5a4b6088..ada2452471 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 @@ -47,6 +47,11 @@ public class Image { */ String builder; + /** + * The run image used to launch the built image. + */ + String runImage; + /** * Environment properties that should be passed to the builder. */ @@ -70,6 +75,10 @@ public class Image { this.builder = builder; } + void setRunImage(String runImage) { + this.runImage = runImage; + } + BuildRequest getBuildRequest(Artifact artifact, Function applicationContent) { return customize(BuildRequest.of(getOrDeduceName(artifact), applicationContent)); } @@ -86,6 +95,9 @@ public class Image { if (StringUtils.hasText(this.builder)) { request = request.withBuilder(ImageReference.of(this.builder)); } + if (StringUtils.hasText(this.runImage)) { + request = request.withRunImage(ImageReference.of(this.runImage)); + } if (this.env != null && !this.env.isEmpty()) { request = request.withEnv(this.env); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ImageTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ImageTests.java index 4330f56ed6..292cc8f7e7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ImageTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ImageTests.java @@ -36,6 +36,7 @@ import static org.assertj.core.api.Assertions.entry; * Tests for {@link Image}. * * @author Phillip Webb + * @author Scott Frederick */ class ImageTests { @@ -58,6 +59,7 @@ class ImageTests { BuildRequest request = new Image().getBuildRequest(createArtifact(), mockApplicationContent()); assertThat(request.getName().toString()).isEqualTo("docker.io/library/my-app:0.0.1-SNAPSHOT"); assertThat(request.getBuilder().toString()).contains("paketo-buildpacks/builder"); + assertThat(request.getRunImage()).isNull(); assertThat(request.getEnv()).isEmpty(); assertThat(request.isCleanCache()).isFalse(); assertThat(request.isVerboseLogging()).isFalse(); @@ -71,6 +73,14 @@ class ImageTests { assertThat(request.getBuilder().toString()).isEqualTo("docker.io/springboot/builder:2.2.x"); } + @Test + void getBuildRequestWhenHasRunImageUsesRunImage() { + Image image = new Image(); + image.runImage = "springboot/run:latest"; + BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); + assertThat(request.getRunImage().toString()).isEqualTo("docker.io/springboot/run:latest"); + } + @Test void getBuildRequestWhenHasEnvUsesEnv() { Image image = new Image();