From 829fd8bb293a1b61849b033d7430a0659347ca97 Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Tue, 4 Aug 2026 14:49:36 -0500 Subject: [PATCH 1/2] 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. See gh-51390 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 2d3c49898eb..9cf1de13e3d 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 abstract class AbstractBuildLog implements BuildLog { 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 ad7fbd61d5a..01597ea4f2c 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 @@ public interface BuildLog { */ 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 0657b314db0..3532cfc2472 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 class Builder { 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 @@ public class Builder { 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 56ea417fd9e..3c38c55cf62 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 @@ class BuilderTests { } @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 @@ class BuilderTests { .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 From 0b0a6c88dc59b29f732bfaf8222cbeb43d95011e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Thu, 20 Aug 2026 09:16:10 +0200 Subject: [PATCH 2/2] Polish "Warn when builder and run image stack IDs do not match" See gh-51390 --- .../springframework/boot/buildpack/platform/build/BuildLog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 01597ea4f2c..b78285e34e4 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 @@ -140,7 +140,7 @@ public interface BuildLog { * image. * @param runImageStackId the stack ID of the run image * @param builderImageStackId the stack ID of the builder image - * @since 4.0.8 + * @since 4.0.9 */ default void stackIdsDoNotMatch(String runImageStackId, String builderImageStackId) { }