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 7ba2003b7a64..e8a5f1867e36 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 BuildRequest withEnv(String name, String value) { 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 BuildRequest withEnv(Map env) { 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 ImageReference getBuilder() { 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 ba4d7c94102d..777f01cb9151 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 void build(BuildRequest request) throws DockerEngineException, IOExceptio 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 @@ private Image pullBuilder(BuildRequest request) throws IOException { return builderImage; } - private ImageReference getRunImageReference(Stack stack) { + 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 ImageReference getRunImageReferenceForStack(Stack stack) { String name = stack.getRunImage().getImage(); - Assert.state(StringUtils.hasText(name), "Run image must be specified"); - return ImageReference.of(name).inTaggedForm(); + 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, ImageReference name) throws IOException { - Consumer progressConsumer = this.log.pullingRunImage(request, name); + 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 1b343adf7d25..222624efdff4 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 @@ private Phase createPhase() { 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 ab42cde7e9e0..154927413728 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 @@ * 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 a6b08425b589..266fd7d1b996 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 @@ void forJarFileWhenJarFileIsMissingThrowsException() { assertThatIllegalArgumentException() .isThrownBy(() -> BuildRequest.forJarFile(new File(this.tempDir, "missing.jar"))) .withMessage("JarFile must exist"); - } @Test @@ -106,6 +105,21 @@ void withBuilderWhenHasDigestUpdatesBuilder() throws IOException { "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 912253eb0fa3..e46a0a4e6ca7 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 @@ void buildInvokesBuilderWithDefaultImageTags() throws Exception { 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 @@ private DockerApi mockDockerApiLifecycleError() throws IOException { 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 298fdb72a205..d97fccbaa257 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 @@ private DockerApi mockDockerApi() { 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 @@ private Lifecycle createLifecycle() throws IOException { 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 @@ private IOConsumer withExpectedConfig(String name) { 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 0227085f0f55..5b224027675b 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 @@ * Tests for {@link ImageReference}. * * @author Phillip Webb + * @author Scott Frederick */ class ImageReferenceTests { @@ -223,6 +224,26 @@ void inTaggedFormWhenHasTagUsesTag() { 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 000000000000..c2a3d83152d3 --- /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 9572eba14848..dbbb51dda518 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 e26bbcf6a5bd..7f5c288341de 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 7e8266aad8ed..6f1a24d05047 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 7c4192ce81a5..1008610293c7 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 b657594c65a3..71b05174d95a 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 efe91b384817..787ba46cb7d8 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 void setBuilder(String builder) { 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 @@ private ImageReference determineImageReference() { 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 @@ private BuildRequest customizeBuilder(BuildRequest request) { 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 921c45c17f8e..396ea98a8a16 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 @@ void buildsImageWithCustomName() throws IOException { } @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 @@ void buildsImageWithCommandLineOptions() throws IOException { 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 986f62d27353..485e32e08de0 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 @@ void whenBuilderIsConfiguredThenRequestUsesSpecifiedBuilder() { 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 bf3cecec27ca..000000000000 --- 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 000000000000..56e5bce571d1 --- /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 58905f9d04c2..e306457624c4 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 21d528ff592b..fb706c51ca65 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 9a3d20155675..744d6592efb1 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 62c41cd9d5c1..41292dc13c60 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 @@ void whenBuildImageIsInvokedWithCommandLineParameters(MavenBuild mavenBuild) { .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 @@ void whenBuildImageIsInvokedWithCommandLineParameters(MavenBuild mavenBuild) { } @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 b246fc61bb14..7b37388b62ed 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 d678f23371ca..fab1b6660ae1 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 @@ private BuildRequest getBuildRequest(Libraries libraries) { 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 ef5a4b608836..ada2452471ff 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 @@ void setBuilder(String builder) { 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 @@ private BuildRequest customize(BuildRequest request) { 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 4330f56ed630..292cc8f7e7b5 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 @@ * Tests for {@link Image}. * * @author Phillip Webb + * @author Scott Frederick */ class ImageTests { @@ -58,6 +59,7 @@ void getBuildRequestWhenNoCustomizationsUsesDefaults() { 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 @@ void getBuildRequestWhenHasBuilderUsesBuilder() { 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();