Merge branch '3.2.x'

Closes gh-41091
This commit is contained in:
Scott Frederick 2024-06-12 15:16:26 -05:00
commit 84956ad56b
7 changed files with 236 additions and 18 deletions

View File

@ -20,7 +20,6 @@ import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.boot.buildpack.platform.build.BuilderMetadata.Stack;
import org.springframework.boot.buildpack.platform.docker.DockerApi;
import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent;
import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListener;
@ -103,7 +102,7 @@ public class Builder {
ImageFetcher imageFetcher = new ImageFetcher(domain, getBuilderAuthHeader(), pullPolicy);
Image builderImage = imageFetcher.fetchImage(ImageType.BUILDER, request.getBuilder());
BuilderMetadata builderMetadata = BuilderMetadata.fromImage(builderImage);
request = withRunImageIfNeeded(request, builderMetadata.getStack());
request = withRunImageIfNeeded(request, builderMetadata);
Image runImage = imageFetcher.fetchImage(ImageType.RUNNER, request.getRunImage());
assertStackIdsMatch(runImage, builderImage);
BuildOwner buildOwner = BuildOwner.fromEnv(builderImage.getConfig().getEnv());
@ -124,24 +123,30 @@ public class Builder {
}
}
private BuildRequest withRunImageIfNeeded(BuildRequest request, Stack builderStack) {
private BuildRequest withRunImageIfNeeded(BuildRequest request, BuilderMetadata metadata) {
if (request.getRunImage() != null) {
return request;
}
return request.withRunImage(getRunImageReferenceForStack(builderStack));
return request.withRunImage(getRunImageReference(metadata));
}
private ImageReference getRunImageReferenceForStack(Stack stack) {
String name = stack.getRunImage().getImage();
Assert.state(StringUtils.hasText(name), "Run image must be specified in the builder image stack");
return ImageReference.of(name).inTaggedOrDigestForm();
private ImageReference getRunImageReference(BuilderMetadata metadata) {
if (metadata.getRunImages() != null && !metadata.getRunImages().isEmpty()) {
String runImageName = metadata.getRunImages().get(0).getImage();
return ImageReference.of(runImageName).inTaggedOrDigestForm();
}
String runImageName = metadata.getStack().getRunImage().getImage();
Assert.state(StringUtils.hasText(runImageName), "Run image must be specified in the builder image metadata");
return ImageReference.of(runImageName).inTaggedOrDigestForm();
}
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 + "'");
if (runImageStackId.hasId() && builderImageStackId.hasId()) {
Assert.state(runImageStackId.equals(builderImageStackId), () -> "Run image stack '" + runImageStackId
+ "' does not match builder stack '" + builderImageStackId + "'");
}
}
private Buildpacks getBuildpacks(BuildRequest request, ImageFetcher imageFetcher, BuilderMetadata builderMetadata,

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2024 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.
@ -49,6 +49,8 @@ class BuilderMetadata extends MappedObject {
private final Stack stack;
private final List<RunImage> runImages;
private final Lifecycle lifecycle;
private final CreatedBy createdBy;
@ -58,6 +60,7 @@ class BuilderMetadata extends MappedObject {
BuilderMetadata(JsonNode node) {
super(node, MethodHandles.lookup());
this.stack = valueAt("/stack", Stack.class);
this.runImages = childrenAt("/images", RunImage::new);
this.lifecycle = valueAt("/lifecycle", Lifecycle.class);
this.createdBy = valueAt("/createdBy", CreatedBy.class);
this.buildpacks = extractBuildpacks(getNode().at("/buildpacks"));
@ -80,6 +83,14 @@ class BuilderMetadata extends MappedObject {
return this.stack;
}
/**
* Return run images metadata.
* @return the run images metadata
*/
List<RunImage> getRunImages() {
return this.runImages;
}
/**
* Return lifecycle metadata.
* @return the lifecycle metadata
@ -196,6 +207,32 @@ class BuilderMetadata extends MappedObject {
}
static class RunImage extends MappedObject {
private final String image;
private final List<String> mirrors;
/**
* Create a new {@link MappedObject} instance.
* @param node the source node
*/
RunImage(JsonNode node) {
super(node, MethodHandles.lookup());
this.image = valueAt("/image", String.class);
this.mirrors = childrenAt("/mirrors", JsonNode::asText);
}
String getImage() {
return this.image;
}
List<String> getMirrors() {
return this.mirrors;
}
}
/**
* Lifecycle metadata.
*/

View File

@ -19,7 +19,6 @@ package org.springframework.boot.buildpack.platform.build;
import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageConfig;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A Stack ID.
@ -48,6 +47,10 @@ class StackId {
return this.value.equals(((StackId) obj).value);
}
boolean hasId() {
return this.value != null;
}
@Override
public int hashCode() {
return this.value.hashCode();
@ -75,7 +78,6 @@ class StackId {
*/
private static StackId fromImageConfig(ImageConfig imageConfig) {
String value = imageConfig.getLabels().get(LABEL_NAME);
Assert.state(StringUtils.hasText(value), () -> "Missing '" + LABEL_NAME + "' stack label");
return new StackId(value);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -21,6 +21,7 @@ import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.springframework.boot.buildpack.platform.build.BuilderMetadata.RunImage;
import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageConfig;
import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
@ -46,6 +47,29 @@ class BuilderMetadataTests extends AbstractJsonTests {
BuilderMetadata metadata = BuilderMetadata.fromImage(image);
assertThat(metadata.getStack().getRunImage().getImage()).isEqualTo("cloudfoundry/run:base-cnb");
assertThat(metadata.getStack().getRunImage().getMirrors()).isEmpty();
assertThat(metadata.getRunImages()).isEmpty();
assertThat(metadata.getLifecycle().getVersion()).isEqualTo("0.7.2");
assertThat(metadata.getLifecycle().getApi().getBuildpack()).isEqualTo("0.2");
assertThat(metadata.getLifecycle().getApi().getPlatform()).isEqualTo("0.3");
assertThat(metadata.getCreatedBy().getName()).isEqualTo("Pack CLI");
assertThat(metadata.getCreatedBy().getVersion())
.isEqualTo("v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)");
assertThat(metadata.getBuildpacks()).extracting(BuildpackMetadata::getId, BuildpackMetadata::getVersion)
.contains(tuple("paketo-buildpacks/java", "4.10.0"))
.contains(tuple("paketo-buildpacks/spring-boot", "3.5.0"))
.contains(tuple("paketo-buildpacks/executable-jar", "3.1.3"))
.contains(tuple("paketo-buildpacks/graalvm", "4.1.0"))
.contains(tuple("paketo-buildpacks/java-native-image", "4.7.0"))
.contains(tuple("paketo-buildpacks/spring-boot-native-image", "2.0.1"))
.contains(tuple("paketo-buildpacks/bellsoft-liberica", "6.2.0"));
}
@Test
void fromImageWithoutStackLoadsMetadata() throws IOException {
Image image = Image.of(getContent("image-with-empty-stack.json"));
BuilderMetadata metadata = BuilderMetadata.fromImage(image);
assertThat(metadata.getRunImages()).extracting(RunImage::getImage, RunImage::getMirrors)
.contains(tuple("cloudfoundry/run:base-cnb", Collections.emptyList()));
assertThat(metadata.getLifecycle().getVersion()).isEqualTo("0.7.2");
assertThat(metadata.getLifecycle().getApi().getBuildpack()).isEqualTo("0.2");
assertThat(metadata.getLifecycle().getApi().getPlatform()).isEqualTo("0.3");

View File

@ -185,6 +185,26 @@ class BuilderTests {
then(docker.image()).should().remove(archive.getValue().getTag(), true);
}
@Test
void buildInvokesBuilderWithNoStack() throws Exception {
TestPrintStream out = new TestPrintStream();
DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image-with-empty-stack.json");
Image runImage = loadImage("run-image.json");
given(docker.image().pull(eq(ImageReference.of("gcr.io/paketo-buildpacks/builder:latest")), any(), isNull()))
.willAnswer(withPulledImage(builderImage));
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
.willAnswer(withPulledImage(runImage));
Builder builder = new Builder(BuildLog.to(out), docker, null);
BuildRequest request = getTestRequest().withBuilder(ImageReference.of("gcr.io/paketo-buildpacks/builder"));
builder.build(request);
assertThat(out.toString()).contains("Running creator");
assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
then(docker.image()).should().load(archive.capture(), any());
then(docker.image()).should().remove(archive.getValue().getTag(), true);
}
@Test
void buildInvokesBuilderWithRunImageFromRequest() throws Exception {
TestPrintStream out = new TestPrintStream();

View File

@ -25,7 +25,6 @@ import org.springframework.boot.buildpack.platform.docker.type.ImageConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
@ -43,12 +42,12 @@ class StackIdTests {
}
@Test
void fromImageWhenLabelIsMissingThrowsException() {
void fromImageWhenLabelIsMissingHasNoId() {
Image image = mock(Image.class);
ImageConfig imageConfig = mock(ImageConfig.class);
given(image.getConfig()).willReturn(imageConfig);
assertThatIllegalStateException().isThrownBy(() -> StackId.fromImage(image))
.withMessage("Missing 'io.buildpacks.stack.id' stack label");
StackId stackId = StackId.fromImage(image);
assertThat(stackId.hasId()).isFalse();
}
@Test
@ -59,6 +58,7 @@ class StackIdTests {
given(imageConfig.getLabels()).willReturn(Collections.singletonMap("io.buildpacks.stack.id", "test"));
StackId stackId = StackId.fromImage(image);
assertThat(stackId).hasToString("test");
assertThat(stackId.hasId()).isTrue();
}
@Test