Skip to content

Commit 89a89a1

Browse files
committed
Merge branch '3.4.x'
Closes gh-45257
2 parents 82a2cbb + f526666 commit 89a89a1

File tree

4 files changed

+74
-38
lines changed

4 files changed

+74
-38
lines changed

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

+21-14
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException;
3232
import org.springframework.boot.buildpack.platform.docker.type.Binding;
3333
import org.springframework.boot.buildpack.platform.docker.type.Image;
34+
import org.springframework.boot.buildpack.platform.docker.type.ImageArchive;
3435
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
3536
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
3637
import org.springframework.boot.buildpack.platform.io.IOBiConsumer;
@@ -115,16 +116,10 @@ public void build(BuildRequest request) throws DockerEngineException, IOExceptio
115116
Buildpacks buildpacks = getBuildpacks(request, imageFetcher, builderMetadata, buildpackLayersMetadata);
116117
EphemeralBuilder ephemeralBuilder = new EphemeralBuilder(buildOwner, builderImage, request.getName(),
117118
builderMetadata, request.getCreator(), request.getEnv(), buildpacks);
118-
this.docker.image().load(ephemeralBuilder.getArchive(), UpdateListener.none());
119-
try {
120-
executeLifecycle(request, ephemeralBuilder);
121-
tagImage(request.getName(), request.getTags());
122-
if (request.isPublish()) {
123-
pushImages(request.getName(), request.getTags());
124-
}
125-
}
126-
finally {
127-
this.docker.image().remove(ephemeralBuilder.getName(), true);
119+
executeLifecycle(request, ephemeralBuilder);
120+
tagImage(request.getName(), request.getTags());
121+
if (request.isPublish()) {
122+
pushImages(request.getName(), request.getTags());
128123
}
129124
}
130125

@@ -170,13 +165,25 @@ private Buildpacks getBuildpacks(BuildRequest request, ImageFetcher imageFetcher
170165
}
171166

172167
private void executeLifecycle(BuildRequest request, EphemeralBuilder builder) throws IOException {
173-
ResolvedDockerHost dockerHost = null;
174-
if (this.dockerConfiguration != null && this.dockerConfiguration.isBindHostToBuilder()) {
175-
dockerHost = ResolvedDockerHost.from(this.dockerConfiguration.getHost());
168+
try (Lifecycle lifecycle = new Lifecycle(this.log, this.docker, getDockerHost(), request, builder)) {
169+
executeLifecycle(builder, lifecycle);
176170
}
177-
try (Lifecycle lifecycle = new Lifecycle(this.log, this.docker, dockerHost, request, builder)) {
171+
}
172+
173+
private void executeLifecycle(EphemeralBuilder builder, Lifecycle lifecycle) throws IOException {
174+
ImageArchive archive = builder.getArchive(lifecycle.getApplicationDirectory());
175+
this.docker.image().load(archive, UpdateListener.none());
176+
try {
178177
lifecycle.execute();
179178
}
179+
finally {
180+
this.docker.image().remove(builder.getName(), true);
181+
}
182+
}
183+
184+
private ResolvedDockerHost getDockerHost() {
185+
boolean bindHostToBuilder = this.dockerConfiguration != null && this.dockerConfiguration.isBindHostToBuilder();
186+
return (bindHostToBuilder) ? ResolvedDockerHost.from(this.dockerConfiguration.getHost()) : null;
180187
}
181188

182189
private void tagImage(ImageReference sourceReference, List<ImageReference> tags) throws IOException {

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

+31-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 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,11 +21,14 @@
2121

2222
import org.springframework.boot.buildpack.platform.docker.type.Image;
2323
import org.springframework.boot.buildpack.platform.docker.type.ImageArchive;
24+
import org.springframework.boot.buildpack.platform.docker.type.ImageArchive.Update;
2425
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
2526
import org.springframework.boot.buildpack.platform.docker.type.Layer;
2627
import org.springframework.boot.buildpack.platform.io.Content;
28+
import org.springframework.boot.buildpack.platform.io.IOConsumer;
2729
import org.springframework.boot.buildpack.platform.io.Owner;
2830
import org.springframework.util.CollectionUtils;
31+
import org.springframework.util.StringUtils;
2932

3033
/**
3134
* A short-lived builder that is created for each {@link Lifecycle} run.
@@ -37,13 +40,17 @@ class EphemeralBuilder {
3740

3841
static final String BUILDER_FOR_LABEL_NAME = "org.springframework.boot.builderFor";
3942

43+
private ImageReference name;
44+
4045
private final BuildOwner buildOwner;
4146

47+
private final Creator creator;
48+
4249
private final BuilderMetadata builderMetadata;
4350

44-
private final ImageArchive archive;
51+
private final Image builderImage;
4552

46-
private final Creator creator;
53+
private final IOConsumer<Update> archiveUpdate;
4754

4855
/**
4956
* Create a new {@link EphemeralBuilder} instance.
@@ -54,26 +61,25 @@ class EphemeralBuilder {
5461
* @param creator the builder creator
5562
* @param env the builder env
5663
* @param buildpacks an optional set of buildpacks to apply
57-
* @throws IOException on IO error
5864
*/
5965
EphemeralBuilder(BuildOwner buildOwner, Image builderImage, ImageReference targetImage,
60-
BuilderMetadata builderMetadata, Creator creator, Map<String, String> env, Buildpacks buildpacks)
61-
throws IOException {
62-
ImageReference name = ImageReference.random("pack.local/builder/").inTaggedForm();
66+
BuilderMetadata builderMetadata, Creator creator, Map<String, String> env, Buildpacks buildpacks) {
67+
this.name = ImageReference.random("pack.local/builder/").inTaggedForm();
6368
this.buildOwner = buildOwner;
6469
this.creator = creator;
6570
this.builderMetadata = builderMetadata.copy(this::updateMetadata);
66-
this.archive = ImageArchive.from(builderImage, (update) -> {
71+
this.builderImage = builderImage;
72+
this.archiveUpdate = (update) -> {
6773
update.withUpdatedConfig(this.builderMetadata::attachTo);
6874
update.withUpdatedConfig((config) -> config.withLabel(BUILDER_FOR_LABEL_NAME, targetImage.toString()));
69-
update.withTag(name);
75+
update.withTag(this.name);
7076
if (!CollectionUtils.isEmpty(env)) {
7177
update.withNewLayer(getEnvLayer(env));
7278
}
7379
if (buildpacks != null) {
7480
buildpacks.apply(update::withNewLayer);
7581
}
76-
});
82+
};
7783
}
7884

7985
private void updateMetadata(BuilderMetadata.Update update) {
@@ -95,7 +101,7 @@ private Layer getEnvLayer(Map<String, String> env) throws IOException {
95101
* @return the ephemeral builder name
96102
*/
97103
ImageReference getName() {
98-
return this.archive.getTag();
104+
return this.name;
99105
}
100106

101107
/**
@@ -116,15 +122,26 @@ BuilderMetadata getBuilderMetadata() {
116122

117123
/**
118124
* Return the contents of ephemeral builder for passing to Docker.
125+
* @param applicationDirectory the application directory
119126
* @return the ephemeral builder archive
127+
* @throws IOException on IO error
120128
*/
121-
ImageArchive getArchive() {
122-
return this.archive;
129+
ImageArchive getArchive(String applicationDirectory) throws IOException {
130+
return ImageArchive.from(this.builderImage, (update) -> {
131+
this.archiveUpdate.accept(update);
132+
if (StringUtils.hasLength(applicationDirectory)) {
133+
update.withNewLayer(applicationDirectoryLayer(applicationDirectory));
134+
}
135+
});
136+
}
137+
138+
private Layer applicationDirectoryLayer(String applicationDirectory) throws IOException {
139+
return Layer.of((layout) -> layout.directory(applicationDirectory, this.buildOwner));
123140
}
124141

125142
@Override
126143
public String toString() {
127-
return this.archive.getTag().toString();
144+
return this.name.toString();
128145
}
129146

130147
}

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 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.
@@ -117,6 +117,10 @@ class Lifecycle implements Closeable {
117117
this.securityOptions = getSecurityOptions(request);
118118
}
119119

120+
String getApplicationDirectory() {
121+
return this.applicationDirectory;
122+
}
123+
120124
private Cache getBuildCache(BuildRequest request) {
121125
if (request.getBuildCache() != null) {
122126
return request.getBuildCache();

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

+17-9
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-2025 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.
@@ -88,7 +88,7 @@ void setup() throws Exception {
8888
}
8989

9090
@Test
91-
void getNameHasRandomName() throws Exception {
91+
void getNameHasRandomName() {
9292
EphemeralBuilder b1 = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata,
9393
this.creator, this.env, this.buildpacks);
9494
EphemeralBuilder b2 = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata,
@@ -101,7 +101,7 @@ void getNameHasRandomName() throws Exception {
101101
void getArchiveHasCreatedByConfig() throws Exception {
102102
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata,
103103
this.creator, this.env, this.buildpacks);
104-
ImageConfig config = builder.getArchive().getImageConfig();
104+
ImageConfig config = builder.getArchive(null).getImageConfig();
105105
BuilderMetadata ephemeralMetadata = BuilderMetadata.fromImageConfig(config);
106106
assertThat(ephemeralMetadata.getCreatedBy().getName()).isEqualTo("Spring Boot");
107107
assertThat(ephemeralMetadata.getCreatedBy().getVersion()).isEqualTo("dev");
@@ -111,15 +111,15 @@ void getArchiveHasCreatedByConfig() throws Exception {
111111
void getArchiveHasTag() throws Exception {
112112
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata,
113113
this.creator, this.env, this.buildpacks);
114-
ImageReference tag = builder.getArchive().getTag();
114+
ImageReference tag = builder.getArchive(null).getTag();
115115
assertThat(tag.toString()).startsWith("pack.local/builder/").endsWith(":latest");
116116
}
117117

118118
@Test
119119
void getArchiveHasFixedCreatedDate() throws Exception {
120120
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata,
121121
this.creator, this.env, this.buildpacks);
122-
Instant createInstant = builder.getArchive().getCreateDate();
122+
Instant createInstant = builder.getArchive(null).getCreateDate();
123123
OffsetDateTime createDateTime = OffsetDateTime.ofInstant(createInstant, ZoneId.of("UTC"));
124124
assertThat(createDateTime.getYear()).isEqualTo(1980);
125125
assertThat(createDateTime.getMonthValue()).isOne();
@@ -133,7 +133,7 @@ void getArchiveHasFixedCreatedDate() throws Exception {
133133
void getArchiveContainsEnvLayer() throws Exception {
134134
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata,
135135
this.creator, this.env, this.buildpacks);
136-
File directory = unpack(getLayer(builder.getArchive(), EXISTING_IMAGE_LAYER_COUNT), "env");
136+
File directory = unpack(getLayer(builder.getArchive(null), EXISTING_IMAGE_LAYER_COUNT), "env");
137137
assertThat(new File(directory, "platform/env/spring")).usingCharset(StandardCharsets.UTF_8).hasContent("boot");
138138
assertThat(new File(directory, "platform/env/empty")).usingCharset(StandardCharsets.UTF_8).hasContent("");
139139
}
@@ -142,7 +142,7 @@ void getArchiveContainsEnvLayer() throws Exception {
142142
void getArchiveHasBuilderForLabel() throws Exception {
143143
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata,
144144
this.creator, this.env, this.buildpacks);
145-
ImageConfig config = builder.getArchive().getImageConfig();
145+
ImageConfig config = builder.getArchive(null).getImageConfig();
146146
assertThat(config.getLabels())
147147
.contains(entry(EphemeralBuilder.BUILDER_FOR_LABEL_NAME, this.targetImage.toString()));
148148
}
@@ -162,13 +162,21 @@ void getArchiveContainsBuildpackLayers() throws Exception {
162162
"/cnb/buildpacks/example_buildpack2/0.0.2/buildpack.toml");
163163
assertBuildpackLayerContent(builder, EXISTING_IMAGE_LAYER_COUNT + 2,
164164
"/cnb/buildpacks/example_buildpack3/0.0.3/buildpack.toml");
165-
File orderDirectory = unpack(getLayer(builder.getArchive(), EXISTING_IMAGE_LAYER_COUNT + 3), "order");
165+
File orderDirectory = unpack(getLayer(builder.getArchive(null), EXISTING_IMAGE_LAYER_COUNT + 3), "order");
166166
assertThat(new File(orderDirectory, "cnb/order.toml")).usingCharset(StandardCharsets.UTF_8)
167167
.hasContent(content("order.toml"));
168168
}
169169

170+
@Test
171+
void getArchiveHasApplicationDirectoryLayer() throws Exception {
172+
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata,
173+
this.creator, this.env, this.buildpacks);
174+
File directory = unpack(getLayer(builder.getArchive("/myapp"), EXISTING_IMAGE_LAYER_COUNT + 1), "appdir");
175+
assertThat(new File(directory, "myapp")).isDirectory();
176+
}
177+
170178
private void assertBuildpackLayerContent(EphemeralBuilder builder, int index, String s) throws Exception {
171-
File buildpackDirectory = unpack(getLayer(builder.getArchive(), index), "buildpack");
179+
File buildpackDirectory = unpack(getLayer(builder.getArchive(null), index), "buildpack");
172180
assertThat(new File(buildpackDirectory, s)).usingCharset(StandardCharsets.UTF_8).hasContent("[test]");
173181
}
174182

0 commit comments

Comments
 (0)