From c7449b57ce3bb07da849ddd0efd7291a7678aad9 Mon Sep 17 00:00:00 2001 From: anshlykov Date: Mon, 3 Aug 2020 02:24:07 +0300 Subject: [PATCH 1/2] Add pullPolicy option for image building This commit adds a pullPolicy option to the configuration of the Maven plugin spring-boot:build-image goal and the Gradle plugin bootBuildImage task. The new option gives users control over pulling the builder image and run image from a remote image registry to the local Docker daemon. See gh-22736 --- .../platform/build/AbstractBuildLog.java | 18 +++- .../buildpack/platform/build/BuildLog.java | 15 ++++ .../platform/build/BuildRequest.java | 39 +++++++-- .../buildpack/platform/build/Builder.java | 47 +++++++---- .../buildpack/platform/build/ImageType.java | 47 +++++++++++ .../buildpack/platform/build/PullPolicy.java | 42 ++++++++++ .../buildpack/platform/docker/DockerApi.java | 19 ++++- .../transport/DockerEngineException.java | 2 +- .../platform/build/BuilderTests.java | 84 +++++++++++++++++++ .../platform/docker/DockerApiTests.java | 15 ++++ .../gradle/tasks/bundling/BootBuildImage.java | 30 +++++++ .../tasks/bundling/BootBuildImageTests.java | 12 +++ .../boot/maven/BuildImageMojo.java | 11 +++ .../org/springframework/boot/maven/Image.java | 13 +++ .../boot/maven/ImageTests.java | 10 +++ 15 files changed, 371 insertions(+), 33 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ImageType.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/PullPolicy.java diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/AbstractBuildLog.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/AbstractBuildLog.java index 7a78d5cbeb..e668917a70 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/AbstractBuildLog.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/AbstractBuildLog.java @@ -42,22 +42,32 @@ public abstract class AbstractBuildLog implements BuildLog { @Override public Consumer pullingBuilder(BuildRequest request, ImageReference imageReference) { - return getProgressConsumer(" > Pulling builder image '" + imageReference + "'"); + return pullingImage(imageReference, ImageType.BUILDER); } @Override public void pulledBuilder(BuildRequest request, Image image) { - log(" > Pulled builder image '" + getDigest(image) + "'"); + pulledImage(image, ImageType.BUILDER); } @Override public Consumer pullingRunImage(BuildRequest request, ImageReference imageReference) { - return getProgressConsumer(" > Pulling run image '" + imageReference + "'"); + return pullingImage(imageReference, ImageType.RUNNER); } @Override public void pulledRunImage(BuildRequest request, Image image) { - log(" > Pulled run image '" + getDigest(image) + "'"); + pulledImage(image, ImageType.RUNNER); + } + + @Override + public Consumer pullingImage(ImageReference imageReference, ImageType imageType) { + return getProgressConsumer(String.format(" > Pulling %s '%s'", imageType.getDescription(), imageReference)); + } + + @Override + public void pulledImage(Image image, ImageType imageType) { + log(String.format(" > Pulled %s '%s'", imageType.getDescription(), getDigest(image))); } @Override diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildLog.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildLog.java index cb82832b7a..da13f1eb22 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildLog.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildLog.java @@ -71,6 +71,21 @@ public interface BuildLog { */ void pulledRunImage(BuildRequest request, Image image); + /** + * Log that the image is being pulled. + * @param imageReference the image reference + * @param imageType the image type + * @return a consumer for progress update events + */ + Consumer pullingImage(ImageReference imageReference, ImageType imageType); + + /** + * Log that the image has been pulled. + * @param image the builder image that was pulled + * @param imageType the image type that was pulled + */ + void pulledImage(Image image, ImageType imageType); + /** * Log that the lifecycle is executing. * @param request the build request 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 e8a5f1867e..7f65984b53 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 @@ -32,6 +32,7 @@ import org.springframework.util.Assert; * * @author Phillip Webb * @author Scott Frederick + * @author Andrey Shlykov * @since 2.3.0 */ public class BuildRequest { @@ -56,6 +57,8 @@ public class BuildRequest { private final boolean verboseLogging; + private final PullPolicy pullPolicy; + BuildRequest(ImageReference name, Function applicationContent) { Assert.notNull(name, "Name must not be null"); Assert.notNull(applicationContent, "ApplicationContent must not be null"); @@ -66,12 +69,13 @@ public class BuildRequest { this.env = Collections.emptyMap(); this.cleanCache = false; this.verboseLogging = false; + this.pullPolicy = PullPolicy.ALWAYS; this.creator = Creator.withVersion(""); } BuildRequest(ImageReference name, Function applicationContent, ImageReference builder, ImageReference runImage, Creator creator, Map env, boolean cleanCache, - boolean verboseLogging) { + boolean verboseLogging, PullPolicy pullPolicy) { this.name = name; this.applicationContent = applicationContent; this.builder = builder; @@ -80,6 +84,7 @@ public class BuildRequest { this.env = env; this.cleanCache = cleanCache; this.verboseLogging = verboseLogging; + this.pullPolicy = pullPolicy; } /** @@ -90,7 +95,7 @@ public class BuildRequest { public BuildRequest withBuilder(ImageReference builder) { Assert.notNull(builder, "Builder must not be null"); return new BuildRequest(this.name, this.applicationContent, builder.inTaggedOrDigestForm(), this.runImage, - this.creator, this.env, this.cleanCache, this.verboseLogging); + this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy); } /** @@ -100,7 +105,7 @@ public class BuildRequest { */ public BuildRequest withRunImage(ImageReference runImageName) { return new BuildRequest(this.name, this.applicationContent, this.builder, runImageName.inTaggedOrDigestForm(), - this.creator, this.env, this.cleanCache, this.verboseLogging); + this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy); } /** @@ -111,7 +116,7 @@ public class BuildRequest { public BuildRequest withCreator(Creator creator) { Assert.notNull(creator, "Creator must not be null"); return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, creator, this.env, - this.cleanCache, this.verboseLogging); + this.cleanCache, this.verboseLogging, this.pullPolicy); } /** @@ -126,7 +131,7 @@ public class BuildRequest { Map env = new LinkedHashMap<>(this.env); env.put(name, value); return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, - Collections.unmodifiableMap(env), this.cleanCache, this.verboseLogging); + Collections.unmodifiableMap(env), this.cleanCache, this.verboseLogging, this.pullPolicy); } /** @@ -139,7 +144,7 @@ public class BuildRequest { Map updatedEnv = new LinkedHashMap<>(this.env); updatedEnv.putAll(env); return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, - Collections.unmodifiableMap(updatedEnv), this.cleanCache, this.verboseLogging); + Collections.unmodifiableMap(updatedEnv), this.cleanCache, this.verboseLogging, this.pullPolicy); } /** @@ -149,7 +154,7 @@ public class BuildRequest { */ public BuildRequest withCleanCache(boolean cleanCache) { return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, - cleanCache, this.verboseLogging); + cleanCache, this.verboseLogging, this.pullPolicy); } /** @@ -159,7 +164,17 @@ public class BuildRequest { */ public BuildRequest withVerboseLogging(boolean verboseLogging) { return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, - this.cleanCache, verboseLogging); + this.cleanCache, verboseLogging, this.pullPolicy); + } + + /** + * Return a new {@link BuildRequest} with the updated image pull policy. + * @param pullPolicy image pull policy {@link PullPolicy} + * @return an updated build request + */ + public BuildRequest withPullPolicy(PullPolicy pullPolicy) { + return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, + this.cleanCache, this.verboseLogging, pullPolicy); } /** @@ -229,6 +244,14 @@ public class BuildRequest { return this.verboseLogging; } + /** + * Return the image {@link PullPolicy} that the builder should use. + * @return image pull policy + */ + public PullPolicy getPullPolicy() { + return this.pullPolicy; + } + /** * Factory method to create a new {@link BuildRequest} from a JAR file. * @param jarFile the source jar file 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 cf83dddd15..3737d2e885 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 @@ -35,6 +35,7 @@ import org.springframework.util.StringUtils; * * @author Phillip Webb * @author Scott Frederick + * @author Andrey Shlykov * @since 2.3.0 */ public class Builder { @@ -60,7 +61,7 @@ public class Builder { public void build(BuildRequest request) throws DockerEngineException, IOException { Assert.notNull(request, "Request must not be null"); this.log.start(request); - Image builderImage = pullBuilder(request); + Image builderImage = getImage(request, ImageType.BUILDER); BuilderMetadata builderMetadata = BuilderMetadata.fromImage(builderImage); BuildOwner buildOwner = BuildOwner.fromEnv(builderImage.getConfig().getEnv()); request = determineRunImage(request, builderImage, builderMetadata.getStack()); @@ -75,22 +76,13 @@ public class Builder { } } - private Image pullBuilder(BuildRequest request) throws IOException { - ImageReference builderImageReference = request.getBuilder(); - Consumer progressConsumer = this.log.pullingBuilder(request, builderImageReference); - TotalProgressPullListener listener = new TotalProgressPullListener(progressConsumer); - Image builderImage = this.docker.image().pull(builderImageReference, listener); - this.log.pulledBuilder(request, builderImage); - return builderImage; - } - 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); + Image runImage = getImage(request, ImageType.RUNNER); assertStackIdsMatch(runImage, builderImage); return request; } @@ -101,12 +93,35 @@ public class Builder { return ImageReference.of(name).inTaggedOrDigestForm(); } - private Image pullRunImage(BuildRequest request) throws IOException { - ImageReference runImage = request.getRunImage(); - Consumer progressConsumer = this.log.pullingRunImage(request, runImage); + private Image getImage(BuildRequest request, ImageType imageType) throws IOException { + ImageReference imageReference = (imageType == ImageType.BUILDER) ? request.getBuilder() : request.getRunImage(); + + Image image; + if (request.getPullPolicy() != PullPolicy.ALWAYS) { + try { + image = this.docker.image().inspect(imageReference); + } + catch (DockerEngineException exception) { + if (request.getPullPolicy() == PullPolicy.IF_NOT_PRESENT && exception.getStatusCode() == 404) { + image = pullImage(imageReference, imageType); + } + else { + throw exception; + } + } + } + else { + image = pullImage(imageReference, imageType); + } + + return image; + } + + private Image pullImage(ImageReference reference, ImageType imageType) throws IOException { + Consumer progressConsumer = this.log.pullingImage(reference, imageType); TotalProgressPullListener listener = new TotalProgressPullListener(progressConsumer); - Image image = this.docker.image().pull(runImage, listener); - this.log.pulledRunImage(request, image); + Image image = this.docker.image().pull(reference, listener); + this.log.pulledImage(image, imageType); return image; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ImageType.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ImageType.java new file mode 100644 index 0000000000..68713962b8 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ImageType.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.buildpack.platform.build; + +/** + * Image types. + * + * @author Andrey Shlykov + * @since 2.4.0 + */ +public enum ImageType { + + /** + * Builder image. + */ + BUILDER("builder image"), + + /** + * Run image. + */ + RUNNER("run image"); + + private final String description; + + ImageType(String description) { + this.description = description; + } + + public String getDescription() { + return this.description; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/PullPolicy.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/PullPolicy.java new file mode 100644 index 0000000000..6969687b4b --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/PullPolicy.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.buildpack.platform.build; + +/** + * Image pull policy. + * + * @author Andrey Shlykov + * @since 2.4.0 + */ +public enum PullPolicy { + + /** + * Always pull the image. + */ + ALWAYS, + + /** + * Never pull the image. + */ + NEVER, + + /** + * Pull the image if it does not already exist in registry. + */ + IF_NOT_PRESENT + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java index 8986c06b9e..053aeeb6e8 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java @@ -158,10 +158,7 @@ public class DockerApi { listener.onUpdate(event); }); } - URI imageUri = buildUrl("/images/" + reference.withDigest(digestCapture.getCapturedDigest()) + "/json"); - try (Response response = http().get(imageUri)) { - return Image.of(response.getContent()); - } + return inspect(reference.withDigest(digestCapture.getCapturedDigest())); } finally { listener.onFinish(); @@ -202,6 +199,20 @@ public class DockerApi { http().delete(uri); } + /** + * Inspect an image. + * @param reference the image reference + * @return the image from the local repository + * @throws IOException on IO error + */ + public Image inspect(ImageReference reference) throws IOException { + Assert.notNull(reference, "Reference must not be null"); + URI imageUri = buildUrl("/images/" + reference + "/json"); + try (Response response = http().get(imageUri)) { + return Image.of(response.getContent()); + } + } + } /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/DockerEngineException.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/DockerEngineException.java index 075b5314bf..b7780f8605 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/DockerEngineException.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/DockerEngineException.java @@ -38,7 +38,7 @@ public class DockerEngineException extends RuntimeException { private final Message responseMessage; - DockerEngineException(String host, URI uri, int statusCode, String reasonPhrase, Errors errors, + public DockerEngineException(String host, URI uri, int statusCode, String reasonPhrase, Errors errors, Message responseMessage) { super(buildMessage(host, uri, statusCode, reasonPhrase, errors, responseMessage)); this.statusCode = statusCode; 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 99789bb4ce..dd9669f0ee 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 @@ -19,6 +19,7 @@ package org.springframework.boot.buildpack.platform.build; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; +import java.net.URI; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -29,6 +30,7 @@ import org.springframework.boot.buildpack.platform.docker.DockerApi.ContainerApi import org.springframework.boot.buildpack.platform.docker.DockerApi.ImageApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi; import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListener; +import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException; import org.springframework.boot.buildpack.platform.docker.type.ContainerReference; import org.springframework.boot.buildpack.platform.docker.type.ContainerStatus; import org.springframework.boot.buildpack.platform.docker.type.Image; @@ -44,6 +46,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** @@ -147,6 +151,86 @@ class BuilderTests { verify(docker.image()).remove(archive.getValue().getTag(), true); } + @Test + void buildInvokesBuilderWithNeverPullPolicy() 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("docker.io/cloudfoundry/run:base-cnb")), any())) + .willAnswer(withPulledImage(runImage)); + given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)))) + .willReturn(builderImage); + given(docker.image().inspect(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")))) + .willReturn(runImage); + Builder builder = new Builder(BuildLog.to(out), docker); + BuildRequest request = getTestRequest().withPullPolicy(PullPolicy.NEVER); + 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); + verify(docker.image(), never()).pull(any(), any()); + verify(docker.image(), times(2)).inspect(any()); + } + + @Test + void buildInvokesBuilderWithAlwaysPullPolicy() 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("docker.io/cloudfoundry/run:base-cnb")), any())) + .willAnswer(withPulledImage(runImage)); + given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)))) + .willReturn(builderImage); + given(docker.image().inspect(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")))) + .willReturn(runImage); + Builder builder = new Builder(BuildLog.to(out), docker); + BuildRequest request = getTestRequest().withPullPolicy(PullPolicy.ALWAYS); + 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); + verify(docker.image(), times(2)).pull(any(), any()); + verify(docker.image(), never()).inspect(any()); + } + + @Test + void buildInvokesBuilderWithIfNotPresentPullPolicy() 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("docker.io/cloudfoundry/run:base-cnb")), any())) + .willAnswer(withPulledImage(runImage)); + given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)))).willThrow( + new DockerEngineException("docker://localhost/", new URI("example"), 404, "NOT FOUND", null, null)) + .willReturn(builderImage); + given(docker.image().inspect(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")))).willThrow( + new DockerEngineException("docker://localhost/", new URI("example"), 404, "NOT FOUND", null, null)) + .willReturn(runImage); + Builder builder = new Builder(BuildLog.to(out), docker); + BuildRequest request = getTestRequest().withPullPolicy(PullPolicy.IF_NOT_PRESENT); + 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); + verify(docker.image(), times(2)).inspect(any()); + verify(docker.image(), times(2)).pull(any(), any()); + } + @Test void buildWhenStackIdDoesNotMatchThrowsException() throws Exception { TestPrintStream out = new TestPrintStream(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java index c256afeb1c..dc72c38622 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java @@ -217,6 +217,21 @@ class DockerApiTests { verify(http()).delete(removeUri); } + @Test + void inspectWhenReferenceIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> this.api.inspect(null)) + .withMessage("Reference must not be null"); + } + + @Test + void inspectInspectImage() throws Exception { + ImageReference reference = ImageReference.of("gcr.io/paketo-buildpacks/builder:base"); + URI imageUri = new URI(IMAGES_URL + "/gcr.io/paketo-buildpacks/builder:base/json"); + given(http().get(imageUri)).willReturn(responseOf("type/image.json")); + Image image = this.api.inspect(reference); + assertThat(image.getLayers()).hasSize(46); + } + } @Nested diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java index 787ba46cb7..dfdc211f2d 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 @@ -34,6 +34,7 @@ import org.gradle.api.tasks.options.Option; import org.springframework.boot.buildpack.platform.build.BuildRequest; import org.springframework.boot.buildpack.platform.build.Builder; import org.springframework.boot.buildpack.platform.build.Creator; +import org.springframework.boot.buildpack.platform.build.PullPolicy; import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException; import org.springframework.boot.buildpack.platform.docker.type.ImageName; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; @@ -69,6 +70,8 @@ public class BootBuildImage extends DefaultTask { private boolean verboseLogging; + private PullPolicy pullPolicy; + public BootBuildImage() { this.jar = getProject().getObjects().fileProperty(); this.targetJavaVersion = getProject().getObjects().property(JavaVersion.class); @@ -224,6 +227,25 @@ public class BootBuildImage extends DefaultTask { this.verboseLogging = verboseLogging; } + /** + * Returns image pull policy that will be used when building the image. + * @return whether images should be pulled + */ + @Input + @Optional + public PullPolicy getPullPolicy() { + return this.pullPolicy; + } + + /** + * Sets image pull policy that will be used when building the image. + * @param pullPolicy image pull policy {@link PullPolicy} + */ + @Option(option = "pullPolicy", description = "The image pull policy") + public void setPullPolicy(PullPolicy pullPolicy) { + this.pullPolicy = pullPolicy; + } + @TaskAction void buildImage() throws DockerEngineException, IOException { Builder builder = new Builder(); @@ -255,6 +277,7 @@ public class BootBuildImage extends DefaultTask { request = customizeCreator(request); request = request.withCleanCache(this.cleanCache); request = request.withVerboseLogging(this.verboseLogging); + request = customizePullPolicy(request); return request; } @@ -290,6 +313,13 @@ public class BootBuildImage extends DefaultTask { return request; } + private BuildRequest customizePullPolicy(BuildRequest request) { + if (this.pullPolicy != null) { + request = request.withPullPolicy(this.pullPolicy); + } + return request; + } + private String translateTargetJavaVersion() { return this.targetJavaVersion.get().getMajorVersion() + ".*"; } 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 485e32e08d..6ec40ca0b5 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 @@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.buildpack.platform.build.BuildRequest; +import org.springframework.boot.buildpack.platform.build.PullPolicy; import static org.assertj.core.api.Assertions.assertThat; @@ -194,4 +195,15 @@ class BootBuildImageTests { assertThat(this.buildImage.createRequest().getRunImage().getName()).isEqualTo("test/run"); } + @Test + void whenUsingDefaultConfigurationThenRequestHasNoPullDisabled() { + assertThat(this.buildImage.createRequest().getPullPolicy()).isEqualTo(PullPolicy.ALWAYS); + } + + @Test + void whenNoPullIsEnabledThenRequestHasNoPullEnabled() { + this.buildImage.setPullPolicy(PullPolicy.NEVER); + assertThat(this.buildImage.createRequest().getPullPolicy()).isEqualTo(PullPolicy.NEVER); + } + } 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 fab1b6660a..d633979318 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 @@ -42,6 +42,7 @@ import org.springframework.boot.buildpack.platform.build.BuildLog; import org.springframework.boot.buildpack.platform.build.BuildRequest; import org.springframework.boot.buildpack.platform.build.Builder; import org.springframework.boot.buildpack.platform.build.Creator; +import org.springframework.boot.buildpack.platform.build.PullPolicy; import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.TarArchive; @@ -123,6 +124,13 @@ public class BuildImageMojo extends AbstractPackagerMojo { @Parameter(property = "spring-boot.build-image.runImage", readonly = true) String runImage; + /** + * Alias for {@link Image#pullPolicy} to support configuration via command-line + * property. + */ + @Parameter(property = "spring-boot.build-image.pullPolicy", readonly = true) + PullPolicy pullPolicy; + @Override public void execute() throws MojoExecutionException { if (this.project.getPackaging().equals("pom")) { @@ -160,6 +168,9 @@ public class BuildImageMojo extends AbstractPackagerMojo { if (image.runImage == null && this.runImage != null) { image.setRunImage(this.runImage); } + if (image.pullPolicy == null && this.pullPolicy != null) { + image.setPullPolicy(this.pullPolicy); + } 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 ada2452471..71ce39d19f 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 @@ -22,6 +22,7 @@ import java.util.function.Function; import org.apache.maven.artifact.Artifact; import org.springframework.boot.buildpack.platform.build.BuildRequest; +import org.springframework.boot.buildpack.platform.build.PullPolicy; import org.springframework.boot.buildpack.platform.docker.type.ImageName; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.io.Owner; @@ -67,6 +68,11 @@ public class Image { */ boolean verboseLogging; + /** + * If images should be pulled from a remote repository during image build. + */ + PullPolicy pullPolicy; + void setName(String name) { this.name = name; } @@ -79,6 +85,10 @@ public class Image { this.runImage = runImage; } + public void setPullPolicy(PullPolicy pullPolicy) { + this.pullPolicy = pullPolicy; + } + BuildRequest getBuildRequest(Artifact artifact, Function applicationContent) { return customize(BuildRequest.of(getOrDeduceName(artifact), applicationContent)); } @@ -103,6 +113,9 @@ public class Image { } request = request.withCleanCache(this.cleanCache); request = request.withVerboseLogging(this.verboseLogging); + if (this.pullPolicy != null) { + request = request.withPullPolicy(this.pullPolicy); + } return request; } 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 292cc8f7e7..cdf5163001 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 @@ -26,6 +26,7 @@ import org.apache.maven.artifact.versioning.VersionRange; import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.build.BuildRequest; +import org.springframework.boot.buildpack.platform.build.PullPolicy; import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.TarArchive; @@ -63,6 +64,7 @@ class ImageTests { assertThat(request.getEnv()).isEmpty(); assertThat(request.isCleanCache()).isFalse(); assertThat(request.isVerboseLogging()).isFalse(); + assertThat(request.getPullPolicy()).isEqualTo(PullPolicy.ALWAYS); } @Test @@ -105,6 +107,14 @@ class ImageTests { assertThat(request.isVerboseLogging()).isTrue(); } + @Test + void getBuildRequestWhenHasPullPolicyUsesPullPolicy() { + Image image = new Image(); + image.setPullPolicy(PullPolicy.NEVER); + BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); + assertThat(request.getPullPolicy()).isEqualTo(PullPolicy.NEVER); + } + private Artifact createArtifact() { return new DefaultArtifact("com.example", "my-app", VersionRange.createFromVersion("0.0.1-SNAPSHOT"), "compile", "jar", null, new DefaultArtifactHandler()); From 6b15822cb13ae9d31bb3b1d0fc8c68c28bb64c71 Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Tue, 11 Aug 2020 18:17:36 -0500 Subject: [PATCH 2/2] Polish "Add pullPolicy option for image building" See gh-22736 --- .../platform/build/AbstractBuildLog.java | 5 ++++ .../buildpack/platform/build/BuildLog.java | 15 ++++++++-- .../buildpack/platform/build/Builder.java | 28 ++++++++----------- .../buildpack/platform/build/ImageType.java | 5 ++-- .../buildpack/platform/build/PullPolicy.java | 6 ++-- .../build/PrintStreamBuildLogTests.java | 9 +++--- .../docs/asciidoc/packaging-oci-image.adoc | 6 ++++ .../BootBuildImageIntegrationTests.java | 25 +++++++++++++++++ .../tasks/bundling/BootBuildImageTests.java | 5 ++-- .../docs/asciidoc/packaging-oci-image.adoc | 8 +++++- 10 files changed, 81 insertions(+), 31 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/AbstractBuildLog.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/AbstractBuildLog.java index e668917a70..863f82e308 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/AbstractBuildLog.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/AbstractBuildLog.java @@ -30,6 +30,7 @@ import org.springframework.boot.buildpack.platform.docker.type.VolumeName; * * @author Phillip Webb * @author Scott Frederick + * @author Andrey Shlykov * @since 2.3.0 */ public abstract class AbstractBuildLog implements BuildLog { @@ -41,21 +42,25 @@ public abstract class AbstractBuildLog implements BuildLog { } @Override + @Deprecated public Consumer pullingBuilder(BuildRequest request, ImageReference imageReference) { return pullingImage(imageReference, ImageType.BUILDER); } @Override + @Deprecated public void pulledBuilder(BuildRequest request, Image image) { pulledImage(image, ImageType.BUILDER); } @Override + @Deprecated public Consumer pullingRunImage(BuildRequest request, ImageReference imageReference) { return pullingImage(imageReference, ImageType.RUNNER); } @Override + @Deprecated public void pulledRunImage(BuildRequest request, Image image) { pulledImage(image, ImageType.RUNNER); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildLog.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildLog.java index da13f1eb22..cee0eafc8f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildLog.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildLog.java @@ -30,6 +30,7 @@ import org.springframework.boot.buildpack.platform.docker.type.VolumeName; * * @author Phillip Webb * @author Scott Frederick + * @author Andrey Shlykov * @since 2.3.0 * @see #toSystemOut() */ @@ -46,14 +47,19 @@ public interface BuildLog { * @param request the build request * @param imageReference the builder image reference * @return a consumer for progress update events + * @deprecated since 2.4.0 in favor of + * {@link #pullingImage(ImageReference, ImageType)} */ + @Deprecated Consumer pullingBuilder(BuildRequest request, ImageReference imageReference); /** * Log that the builder image has been pulled. * @param request the build request * @param image the builder image that was pulled + * @deprecated since 2.4.0 in favor of {@link #pulledImage(Image, ImageType)} */ + @Deprecated void pulledBuilder(BuildRequest request, Image image); /** @@ -61,18 +67,23 @@ public interface BuildLog { * @param request the build request * @param imageReference the run image reference * @return a consumer for progress update events + * @deprecated since 2.4.0 in favor of + * {@link #pullingImage(ImageReference, ImageType)} */ + @Deprecated Consumer pullingRunImage(BuildRequest request, ImageReference imageReference); /** * Log that a run image has been pulled. * @param request the build request * @param image the run image that was pulled + * @deprecated since 2.4.0 in favor of {@link #pulledImage(Image, ImageType)} */ + @Deprecated void pulledRunImage(BuildRequest request, Image image); /** - * Log that the image is being pulled. + * Log that an image is being pulled. * @param imageReference the image reference * @param imageType the image type * @return a consumer for progress update events @@ -80,7 +91,7 @@ public interface BuildLog { Consumer pullingImage(ImageReference imageReference, ImageType imageType); /** - * Log that the image has been pulled. + * Log that an image has been pulled. * @param image the builder image that was pulled * @param imageType the image type that was pulled */ 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 3737d2e885..de06b36523 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 @@ -96,25 +96,21 @@ public class Builder { private Image getImage(BuildRequest request, ImageType imageType) throws IOException { ImageReference imageReference = (imageType == ImageType.BUILDER) ? request.getBuilder() : request.getRunImage(); - Image image; - if (request.getPullPolicy() != PullPolicy.ALWAYS) { - try { - image = this.docker.image().inspect(imageReference); + if (request.getPullPolicy() == PullPolicy.ALWAYS) { + return pullImage(imageReference, imageType); + } + + try { + return this.docker.image().inspect(imageReference); + } + catch (DockerEngineException exception) { + if (request.getPullPolicy() == PullPolicy.IF_NOT_PRESENT && exception.getStatusCode() == 404) { + return pullImage(imageReference, imageType); } - catch (DockerEngineException exception) { - if (request.getPullPolicy() == PullPolicy.IF_NOT_PRESENT && exception.getStatusCode() == 404) { - image = pullImage(imageReference, imageType); - } - else { - throw exception; - } + else { + throw exception; } } - else { - image = pullImage(imageReference, imageType); - } - - return image; } private Image pullImage(ImageReference reference, ImageType imageType) throws IOException { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ImageType.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ImageType.java index 68713962b8..28bd79dc70 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ImageType.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/ImageType.java @@ -20,9 +20,8 @@ package org.springframework.boot.buildpack.platform.build; * Image types. * * @author Andrey Shlykov - * @since 2.4.0 */ -public enum ImageType { +enum ImageType { /** * Builder image. @@ -40,7 +39,7 @@ public enum ImageType { this.description = description; } - public String getDescription() { + String getDescription() { return this.description; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/PullPolicy.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/PullPolicy.java index 6969687b4b..4cbbac8694 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/PullPolicy.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/PullPolicy.java @@ -25,17 +25,17 @@ package org.springframework.boot.buildpack.platform.build; public enum PullPolicy { /** - * Always pull the image. + * Always pull the image from the registry. */ ALWAYS, /** - * Never pull the image. + * Never pull the image from the registry. */ NEVER, /** - * Pull the image if it does not already exist in registry. + * Pull the image from the registry only if it does not exist locally. */ IF_NOT_PRESENT diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PrintStreamBuildLogTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PrintStreamBuildLogTests.java index 00bb5d0145..ec0ec4f3f7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PrintStreamBuildLogTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PrintStreamBuildLogTests.java @@ -57,12 +57,13 @@ class PrintStreamBuildLogTests { given(runImage.getDigests()).willReturn(Collections.singletonList("00000002")); given(request.getName()).willReturn(name); log.start(request); - Consumer pullBuildImageConsumer = log.pullingBuilder(request, builderImageReference); + Consumer pullBuildImageConsumer = log.pullingImage(builderImageReference, + ImageType.BUILDER); pullBuildImageConsumer.accept(new TotalProgressEvent(100)); - log.pulledBuilder(request, builderImage); - Consumer pullRunImageConsumer = log.pullingRunImage(request, runImageReference); + log.pulledImage(builderImage, ImageType.BUILDER); + Consumer pullRunImageConsumer = log.pullingImage(runImageReference, ImageType.RUNNER); pullRunImageConsumer.accept(new TotalProgressEvent(100)); - log.pulledRunImage(request, runImage); + log.pulledImage(runImage, ImageType.RUNNER); log.executingLifecycle(request, LifecycleVersion.parse("0.5"), VolumeName.of("pack-abc.cache")); Consumer phase1Consumer = log.runningPhase(request, "alphabet"); phase1Consumer.accept(mockLogEvent("one")); 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 7a5dfb6016..f55d3aab3c 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 @@ -61,6 +61,12 @@ The following table summarizes the available properties and their default values | {spring-boot-api}/buildpack/platform/docker/type/ImageReference.html#of-java.lang.String-[Image name] for the generated image. | `docker.io/library/${project.artifactId}:${project.version}` +| `pullPolicy` +| `--pullPolicy` +| {spring-boot-api}/buildpack/platform/build/PullPolicy.html[Policy] used to determine when to pull the builder and run images from the registry. +Acceptable values are `ALWAYS`, `NEVER`, and `IF_NOT_PRESENT`. +| `ALWAYS` + | `environment` | | Environment variables that should be passed to the builder. 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 b24beebaa4..8ea9671408 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 @@ -126,6 +126,31 @@ class BootBuildImageIntegrationTests { } } + @TestTemplate + void buildsImageWithPullPolicy() throws IOException { + writeMainClass(); + writeLongNameResource(); + String projectName = this.gradleBuild.getProjectDir().getName(); + ImageReference imageReference = ImageReference.of(ImageName.of(projectName)); + + BuildResult result = this.gradleBuild.build("bootBuildImage", "--pullPolicy=ALWAYS"); + assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).contains("Pulled builder image").contains("Pulled run image"); + try (GenericContainer container = new GenericContainer<>(imageReference.toString())) { + container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start(); + } + + result = this.gradleBuild.build("bootBuildImage", "--pullPolicy=IF_NOT_PRESENT"); + assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).doesNotContain("Pulled builder image").doesNotContain("Pulled run image"); + try (GenericContainer container = new GenericContainer<>(imageReference.toString())) { + container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start(); + } + finally { + new DockerApi().image().remove(imageReference, false); + } + } + @TestTemplate void failsWithLaunchScript() { writeMainClass(); 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 6ec40ca0b5..fad0ead78d 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 @@ -36,6 +36,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Andy Wilkinson * @author Scott Frederick + * @author Andrey Shlykov */ class BootBuildImageTests { @@ -196,12 +197,12 @@ class BootBuildImageTests { } @Test - void whenUsingDefaultConfigurationThenRequestHasNoPullDisabled() { + void whenUsingDefaultConfigurationThenRequestHasAlwaysPullPolicy() { assertThat(this.buildImage.createRequest().getPullPolicy()).isEqualTo(PullPolicy.ALWAYS); } @Test - void whenNoPullIsEnabledThenRequestHasNoPullEnabled() { + void whenPullPolicyIsConfiguredThenRequestHasPullPolicy() { this.buildImage.setPullPolicy(PullPolicy.NEVER); assertThat(this.buildImage.createRequest().getPullPolicy()).isEqualTo(PullPolicy.NEVER); } 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 744d6592ef..2d8d1e5354 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 @@ -85,6 +85,12 @@ The following table summarizes the available parameters and their default values | `spring-boot.build-image.imageName` | `docker.io/library/${project.artifactId}:${project.version}` +| `pullPolicy` +| {spring-boot-api}/buildpack/platform/build/PullPolicy.html[Policy] used to determine when to pull the builder and run images from the registry. +Acceptable values are `ALWAYS`, `NEVER`, and `IF_NOT_PRESENT`. +| `spring-boot.build-image.pullPolicy` +| `ALWAYS` + | `env` | Environment variables that should be passed to the builder. | @@ -101,7 +107,7 @@ The following table summarizes the available parameters and their default values | `false` |=== -For more details, see <> and <>. +For more details, see <>. include::goals/build-image.adoc[leveloffset=+1]