Skip to content

Commit 84956ad

Browse files
Merge branch '3.2.x'
Closes gh-41091
2 parents 9817ff5 + e228ed3 commit 84956ad

File tree

7 files changed

+236
-18
lines changed

7 files changed

+236
-18
lines changed

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.util.List;
2121
import java.util.function.Consumer;
2222

23-
import org.springframework.boot.buildpack.platform.build.BuilderMetadata.Stack;
2423
import org.springframework.boot.buildpack.platform.docker.DockerApi;
2524
import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent;
2625
import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListener;
@@ -103,7 +102,7 @@ public void build(BuildRequest request) throws DockerEngineException, IOExceptio
103102
ImageFetcher imageFetcher = new ImageFetcher(domain, getBuilderAuthHeader(), pullPolicy);
104103
Image builderImage = imageFetcher.fetchImage(ImageType.BUILDER, request.getBuilder());
105104
BuilderMetadata builderMetadata = BuilderMetadata.fromImage(builderImage);
106-
request = withRunImageIfNeeded(request, builderMetadata.getStack());
105+
request = withRunImageIfNeeded(request, builderMetadata);
107106
Image runImage = imageFetcher.fetchImage(ImageType.RUNNER, request.getRunImage());
108107
assertStackIdsMatch(runImage, builderImage);
109108
BuildOwner buildOwner = BuildOwner.fromEnv(builderImage.getConfig().getEnv());
@@ -124,24 +123,30 @@ public void build(BuildRequest request) throws DockerEngineException, IOExceptio
124123
}
125124
}
126125

127-
private BuildRequest withRunImageIfNeeded(BuildRequest request, Stack builderStack) {
126+
private BuildRequest withRunImageIfNeeded(BuildRequest request, BuilderMetadata metadata) {
128127
if (request.getRunImage() != null) {
129128
return request;
130129
}
131-
return request.withRunImage(getRunImageReferenceForStack(builderStack));
130+
return request.withRunImage(getRunImageReference(metadata));
132131
}
133132

134-
private ImageReference getRunImageReferenceForStack(Stack stack) {
135-
String name = stack.getRunImage().getImage();
136-
Assert.state(StringUtils.hasText(name), "Run image must be specified in the builder image stack");
137-
return ImageReference.of(name).inTaggedOrDigestForm();
133+
private ImageReference getRunImageReference(BuilderMetadata metadata) {
134+
if (metadata.getRunImages() != null && !metadata.getRunImages().isEmpty()) {
135+
String runImageName = metadata.getRunImages().get(0).getImage();
136+
return ImageReference.of(runImageName).inTaggedOrDigestForm();
137+
}
138+
String runImageName = metadata.getStack().getRunImage().getImage();
139+
Assert.state(StringUtils.hasText(runImageName), "Run image must be specified in the builder image metadata");
140+
return ImageReference.of(runImageName).inTaggedOrDigestForm();
138141
}
139142

140143
private void assertStackIdsMatch(Image runImage, Image builderImage) {
141144
StackId runImageStackId = StackId.fromImage(runImage);
142145
StackId builderImageStackId = StackId.fromImage(builderImage);
143-
Assert.state(runImageStackId.equals(builderImageStackId), () -> "Run image stack '" + runImageStackId
144-
+ "' does not match builder stack '" + builderImageStackId + "'");
146+
if (runImageStackId.hasId() && builderImageStackId.hasId()) {
147+
Assert.state(runImageStackId.equals(builderImageStackId), () -> "Run image stack '" + runImageStackId
148+
+ "' does not match builder stack '" + builderImageStackId + "'");
149+
}
145150
}
146151

147152
private Buildpacks getBuildpacks(BuildRequest request, ImageFetcher imageFetcher, BuilderMetadata builderMetadata,

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderMetadata.java

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -49,6 +49,8 @@ class BuilderMetadata extends MappedObject {
4949

5050
private final Stack stack;
5151

52+
private final List<RunImage> runImages;
53+
5254
private final Lifecycle lifecycle;
5355

5456
private final CreatedBy createdBy;
@@ -58,6 +60,7 @@ class BuilderMetadata extends MappedObject {
5860
BuilderMetadata(JsonNode node) {
5961
super(node, MethodHandles.lookup());
6062
this.stack = valueAt("/stack", Stack.class);
63+
this.runImages = childrenAt("/images", RunImage::new);
6164
this.lifecycle = valueAt("/lifecycle", Lifecycle.class);
6265
this.createdBy = valueAt("/createdBy", CreatedBy.class);
6366
this.buildpacks = extractBuildpacks(getNode().at("/buildpacks"));
@@ -80,6 +83,14 @@ Stack getStack() {
8083
return this.stack;
8184
}
8285

86+
/**
87+
* Return run images metadata.
88+
* @return the run images metadata
89+
*/
90+
List<RunImage> getRunImages() {
91+
return this.runImages;
92+
}
93+
8394
/**
8495
* Return lifecycle metadata.
8596
* @return the lifecycle metadata
@@ -196,6 +207,32 @@ default String[] getMirrors() {
196207

197208
}
198209

210+
static class RunImage extends MappedObject {
211+
212+
private final String image;
213+
214+
private final List<String> mirrors;
215+
216+
/**
217+
* Create a new {@link MappedObject} instance.
218+
* @param node the source node
219+
*/
220+
RunImage(JsonNode node) {
221+
super(node, MethodHandles.lookup());
222+
this.image = valueAt("/image", String.class);
223+
this.mirrors = childrenAt("/mirrors", JsonNode::asText);
224+
}
225+
226+
String getImage() {
227+
return this.image;
228+
}
229+
230+
List<String> getMirrors() {
231+
return this.mirrors;
232+
}
233+
234+
}
235+
199236
/**
200237
* Lifecycle metadata.
201238
*/

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/StackId.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import org.springframework.boot.buildpack.platform.docker.type.Image;
2020
import org.springframework.boot.buildpack.platform.docker.type.ImageConfig;
2121
import org.springframework.util.Assert;
22-
import org.springframework.util.StringUtils;
2322

2423
/**
2524
* A Stack ID.
@@ -48,6 +47,10 @@ public boolean equals(Object obj) {
4847
return this.value.equals(((StackId) obj).value);
4948
}
5049

50+
boolean hasId() {
51+
return this.value != null;
52+
}
53+
5154
@Override
5255
public int hashCode() {
5356
return this.value.hashCode();
@@ -75,7 +78,6 @@ static StackId fromImage(Image image) {
7578
*/
7679
private static StackId fromImageConfig(ImageConfig imageConfig) {
7780
String value = imageConfig.getLabels().get(LABEL_NAME);
78-
Assert.state(StringUtils.hasText(value), () -> "Missing '" + LABEL_NAME + "' stack label");
7981
return new StackId(value);
8082
}
8183

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderMetadataTests.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
2121

2222
import org.junit.jupiter.api.Test;
2323

24+
import org.springframework.boot.buildpack.platform.build.BuilderMetadata.RunImage;
2425
import org.springframework.boot.buildpack.platform.docker.type.Image;
2526
import org.springframework.boot.buildpack.platform.docker.type.ImageConfig;
2627
import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
@@ -46,6 +47,29 @@ void fromImageLoadsMetadata() throws IOException {
4647
BuilderMetadata metadata = BuilderMetadata.fromImage(image);
4748
assertThat(metadata.getStack().getRunImage().getImage()).isEqualTo("cloudfoundry/run:base-cnb");
4849
assertThat(metadata.getStack().getRunImage().getMirrors()).isEmpty();
50+
assertThat(metadata.getRunImages()).isEmpty();
51+
assertThat(metadata.getLifecycle().getVersion()).isEqualTo("0.7.2");
52+
assertThat(metadata.getLifecycle().getApi().getBuildpack()).isEqualTo("0.2");
53+
assertThat(metadata.getLifecycle().getApi().getPlatform()).isEqualTo("0.3");
54+
assertThat(metadata.getCreatedBy().getName()).isEqualTo("Pack CLI");
55+
assertThat(metadata.getCreatedBy().getVersion())
56+
.isEqualTo("v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)");
57+
assertThat(metadata.getBuildpacks()).extracting(BuildpackMetadata::getId, BuildpackMetadata::getVersion)
58+
.contains(tuple("paketo-buildpacks/java", "4.10.0"))
59+
.contains(tuple("paketo-buildpacks/spring-boot", "3.5.0"))
60+
.contains(tuple("paketo-buildpacks/executable-jar", "3.1.3"))
61+
.contains(tuple("paketo-buildpacks/graalvm", "4.1.0"))
62+
.contains(tuple("paketo-buildpacks/java-native-image", "4.7.0"))
63+
.contains(tuple("paketo-buildpacks/spring-boot-native-image", "2.0.1"))
64+
.contains(tuple("paketo-buildpacks/bellsoft-liberica", "6.2.0"));
65+
}
66+
67+
@Test
68+
void fromImageWithoutStackLoadsMetadata() throws IOException {
69+
Image image = Image.of(getContent("image-with-empty-stack.json"));
70+
BuilderMetadata metadata = BuilderMetadata.fromImage(image);
71+
assertThat(metadata.getRunImages()).extracting(RunImage::getImage, RunImage::getMirrors)
72+
.contains(tuple("cloudfoundry/run:base-cnb", Collections.emptyList()));
4973
assertThat(metadata.getLifecycle().getVersion()).isEqualTo("0.7.2");
5074
assertThat(metadata.getLifecycle().getApi().getBuildpack()).isEqualTo("0.2");
5175
assertThat(metadata.getLifecycle().getApi().getPlatform()).isEqualTo("0.3");

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,26 @@ void buildInvokesBuilderWithRunImageInDigestForm() throws Exception {
185185
then(docker.image()).should().remove(archive.getValue().getTag(), true);
186186
}
187187

188+
@Test
189+
void buildInvokesBuilderWithNoStack() throws Exception {
190+
TestPrintStream out = new TestPrintStream();
191+
DockerApi docker = mockDockerApi();
192+
Image builderImage = loadImage("image-with-empty-stack.json");
193+
Image runImage = loadImage("run-image.json");
194+
given(docker.image().pull(eq(ImageReference.of("gcr.io/paketo-buildpacks/builder:latest")), any(), isNull()))
195+
.willAnswer(withPulledImage(builderImage));
196+
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
197+
.willAnswer(withPulledImage(runImage));
198+
Builder builder = new Builder(BuildLog.to(out), docker, null);
199+
BuildRequest request = getTestRequest().withBuilder(ImageReference.of("gcr.io/paketo-buildpacks/builder"));
200+
builder.build(request);
201+
assertThat(out.toString()).contains("Running creator");
202+
assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
203+
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
204+
then(docker.image()).should().load(archive.capture(), any());
205+
then(docker.image()).should().remove(archive.getValue().getTag(), true);
206+
}
207+
188208
@Test
189209
void buildInvokesBuilderWithRunImageFromRequest() throws Exception {
190210
TestPrintStream out = new TestPrintStream();

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/StackIdTests.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
import static org.assertj.core.api.Assertions.assertThat;
2727
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
28-
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
2928
import static org.mockito.BDDMockito.given;
3029
import static org.mockito.Mockito.mock;
3130

@@ -43,12 +42,12 @@ void fromImageWhenImageIsNullThrowsException() {
4342
}
4443

4544
@Test
46-
void fromImageWhenLabelIsMissingThrowsException() {
45+
void fromImageWhenLabelIsMissingHasNoId() {
4746
Image image = mock(Image.class);
4847
ImageConfig imageConfig = mock(ImageConfig.class);
4948
given(image.getConfig()).willReturn(imageConfig);
50-
assertThatIllegalStateException().isThrownBy(() -> StackId.fromImage(image))
51-
.withMessage("Missing 'io.buildpacks.stack.id' stack label");
49+
StackId stackId = StackId.fromImage(image);
50+
assertThat(stackId.hasId()).isFalse();
5251
}
5352

5453
@Test
@@ -59,6 +58,7 @@ void fromImageCreatesStackId() {
5958
given(imageConfig.getLabels()).willReturn(Collections.singletonMap("io.buildpacks.stack.id", "test"));
6059
StackId stackId = StackId.fromImage(image);
6160
assertThat(stackId).hasToString("test");
61+
assertThat(stackId.hasId()).isTrue();
6262
}
6363

6464
@Test

0 commit comments

Comments
 (0)