From ea5785abdc74b335ed4348714719fae089f487e7 Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Tue, 4 Aug 2026 14:49:36 -0500 Subject: [PATCH] Warn when builder and run image stack IDs do not match Spring Boot's Buildpacks integration inspects the stack IDs contained in labels on the builder image and run image. Prior to this change, it was a failure condition when the stack IDs did not match. Stack IDs have been deprecated in the CNB spec, and it is no longer required that the builder and run images match. The validation of stack IDs was changed from an error to a warning to match the `pack` reference implementation. Signed-off-by: Scott Frederick --- .../buildpack/platform/build/AbstractBuildLog.java | 7 +++++++ .../boot/buildpack/platform/build/BuildLog.java | 10 ++++++++++ .../boot/buildpack/platform/build/Builder.java | 9 ++++----- .../boot/buildpack/platform/build/BuilderTests.java | 10 ++++++---- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/AbstractBuildLog.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/AbstractBuildLog.java index 2d3c49898eb4..9cf1de13e3db 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/AbstractBuildLog.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/AbstractBuildLog.java @@ -128,6 +128,13 @@ public void sensitiveTargetBindingDetected(Binding binding) { log(); } + @Override + public void stackIdsDoNotMatch(String runImageStackId, String builderImageStackId) { + log("Warning: Run image stack '%s' does not match builder stack '%s'. Stack IDs are deprecated, but the images may not be compatible." + .formatted(runImageStackId, builderImageStackId)); + log(); + } + private String getDigest(Image image) { List digests = image.getDigests(); return (digests.isEmpty() ? "" : digests.get(0)); diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildLog.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildLog.java index ad7fbd61d5a8..01597ea4f2c3 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildLog.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildLog.java @@ -135,6 +135,16 @@ Consumer pullingImage(ImageReference imageReference, @Nullab */ void sensitiveTargetBindingDetected(Binding binding); + /** + * Log that the stack ID of the run image does not match the stack ID of the builder + * image. + * @param runImageStackId the stack ID of the run image + * @param builderImageStackId the stack ID of the builder image + * @since 4.0.8 + */ + default void stackIdsDoNotMatch(String runImageStackId, String builderImageStackId) { + } + /** * Factory method that returns a {@link BuildLog} the outputs to {@link System#out}. * @return a build log instance that logs to system out diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java index 0657b314db05..3532cfc24725 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java @@ -120,7 +120,7 @@ public void build(BuildRequest request) throws DockerEngineException, IOExceptio request = request.withRunImage(request.getRunImage().withDigest(runImage.getPrimaryDigest())); runImage = imageFetcher.fetchImage(ImageType.RUNNER, request.getRunImage(), platform); } - assertStackIdsMatch(runImage, builderImage); + warnIfStackIdsDoNotMatch(runImage, builderImage); BuildOwner buildOwner = BuildOwner.fromEnv(builderImage.getConfig().getEnv()); BuildpackLayersMetadata buildpackLayersMetadata = BuildpackLayersMetadata.fromImage(builderImage); Buildpacks buildpacks = getBuildpacks(request, imageFetcher, platform, builderMetadata, @@ -159,12 +159,11 @@ private ImageReference getRunImageReference(BuilderMetadata metadata) { return ImageReference.of(runImageName).inTaggedOrDigestForm(); } - private void assertStackIdsMatch(Image runImage, Image builderImage) { + private void warnIfStackIdsDoNotMatch(Image runImage, Image builderImage) { StackId runImageStackId = StackId.fromImage(runImage); StackId builderImageStackId = StackId.fromImage(builderImage); - if (runImageStackId.hasId() && builderImageStackId.hasId()) { - Assert.state(runImageStackId.equals(builderImageStackId), () -> "Run image stack '" + runImageStackId - + "' does not match builder stack '" + builderImageStackId + "'"); + if (runImageStackId.hasId() && builderImageStackId.hasId() && !runImageStackId.equals(builderImageStackId)) { + this.log.stackIdsDoNotMatch(runImageStackId.toString(), builderImageStackId.toString()); } } diff --git a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java index 56ea417fd9e9..3c38c55cf62c 100644 --- a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java +++ b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java @@ -447,7 +447,7 @@ void buildInvokesBuilderWithPlatform() throws Exception { } @Test - void buildWhenStackIdDoesNotMatchThrowsException() throws Exception { + void buildWhenStackIdDoesNotMatchLogsWarning() throws Exception { TestPrintStream out = new TestPrintStream(); DockerApi docker = mockDockerApi(); Image builderImage = loadImage("image.json"); @@ -458,9 +458,11 @@ void buildWhenStackIdDoesNotMatchThrowsException() throws Exception { .willAnswer(withPulledImage(runImage)); Builder builder = new Builder(BuildLog.to(out), docker, null); BuildRequest request = getTestRequest(); - assertThatIllegalStateException().isThrownBy(() -> builder.build(request)) - .withMessage( - "Run image stack 'org.cloudfoundry.stacks.cfwindowsfs3' does not match builder stack 'io.buildpacks.stacks.bionic'"); + builder.build(request); + assertThat(out.toString()).contains( + "Warning: Run image stack 'org.cloudfoundry.stacks.cfwindowsfs3' does not match builder stack 'io.buildpacks.stacks.bionic'"); + assertThat(out.toString()).contains("Running creator"); + assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); } @Test