Merge pull request #51390 from scottfrederick

Closes gh-51390

* gh-51166-buildpack-stack-id:
  Polish "Warn when builder and run image stack IDs do not match"
  Warn when builder and run image stack IDs do not match
This commit is contained in:
Stéphane Nicoll
2026-08-21 10:50:58 +02:00
4 changed files with 27 additions and 9 deletions
@@ -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<String> digests = image.getDigests();
return (digests.isEmpty() ? "" : digests.get(0));
@@ -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.9
*/
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
@@ -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());
}
}
@@ -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