Merge branch '3.4.x'

Closes gh-45257
This commit is contained in:
Phillip Webb 2025-04-22 19:13:29 -07:00
commit 89a89a11a7
4 changed files with 74 additions and 38 deletions

View File

@ -31,6 +31,7 @@ import org.springframework.boot.buildpack.platform.docker.configuration.Resolved
import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException;
import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageArchive;
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.io.IOBiConsumer;
@ -115,16 +116,10 @@ public class Builder {
Buildpacks buildpacks = getBuildpacks(request, imageFetcher, builderMetadata, buildpackLayersMetadata);
EphemeralBuilder ephemeralBuilder = new EphemeralBuilder(buildOwner, builderImage, request.getName(),
builderMetadata, request.getCreator(), request.getEnv(), buildpacks);
this.docker.image().load(ephemeralBuilder.getArchive(), UpdateListener.none());
try {
executeLifecycle(request, ephemeralBuilder);
tagImage(request.getName(), request.getTags());
if (request.isPublish()) {
pushImages(request.getName(), request.getTags());
}
}
finally {
this.docker.image().remove(ephemeralBuilder.getName(), true);
executeLifecycle(request, ephemeralBuilder);
tagImage(request.getName(), request.getTags());
if (request.isPublish()) {
pushImages(request.getName(), request.getTags());
}
}
@ -170,13 +165,25 @@ public class Builder {
}
private void executeLifecycle(BuildRequest request, EphemeralBuilder builder) throws IOException {
ResolvedDockerHost dockerHost = null;
if (this.dockerConfiguration != null && this.dockerConfiguration.isBindHostToBuilder()) {
dockerHost = ResolvedDockerHost.from(this.dockerConfiguration.getHost());
try (Lifecycle lifecycle = new Lifecycle(this.log, this.docker, getDockerHost(), request, builder)) {
executeLifecycle(builder, lifecycle);
}
try (Lifecycle lifecycle = new Lifecycle(this.log, this.docker, dockerHost, request, builder)) {
}
private void executeLifecycle(EphemeralBuilder builder, Lifecycle lifecycle) throws IOException {
ImageArchive archive = builder.getArchive(lifecycle.getApplicationDirectory());
this.docker.image().load(archive, UpdateListener.none());
try {
lifecycle.execute();
}
finally {
this.docker.image().remove(builder.getName(), true);
}
}
private ResolvedDockerHost getDockerHost() {
boolean bindHostToBuilder = this.dockerConfiguration != null && this.dockerConfiguration.isBindHostToBuilder();
return (bindHostToBuilder) ? ResolvedDockerHost.from(this.dockerConfiguration.getHost()) : null;
}
private void tagImage(ImageReference sourceReference, List<ImageReference> tags) throws IOException {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 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,11 +21,14 @@ import java.util.Map;
import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageArchive;
import org.springframework.boot.buildpack.platform.docker.type.ImageArchive.Update;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.docker.type.Layer;
import org.springframework.boot.buildpack.platform.io.Content;
import org.springframework.boot.buildpack.platform.io.IOConsumer;
import org.springframework.boot.buildpack.platform.io.Owner;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* A short-lived builder that is created for each {@link Lifecycle} run.
@ -37,13 +40,17 @@ class EphemeralBuilder {
static final String BUILDER_FOR_LABEL_NAME = "org.springframework.boot.builderFor";
private ImageReference name;
private final BuildOwner buildOwner;
private final Creator creator;
private final BuilderMetadata builderMetadata;
private final ImageArchive archive;
private final Image builderImage;
private final Creator creator;
private final IOConsumer<Update> archiveUpdate;
/**
* Create a new {@link EphemeralBuilder} instance.
@ -54,26 +61,25 @@ class EphemeralBuilder {
* @param creator the builder creator
* @param env the builder env
* @param buildpacks an optional set of buildpacks to apply
* @throws IOException on IO error
*/
EphemeralBuilder(BuildOwner buildOwner, Image builderImage, ImageReference targetImage,
BuilderMetadata builderMetadata, Creator creator, Map<String, String> env, Buildpacks buildpacks)
throws IOException {
ImageReference name = ImageReference.random("pack.local/builder/").inTaggedForm();
BuilderMetadata builderMetadata, Creator creator, Map<String, String> env, Buildpacks buildpacks) {
this.name = ImageReference.random("pack.local/builder/").inTaggedForm();
this.buildOwner = buildOwner;
this.creator = creator;
this.builderMetadata = builderMetadata.copy(this::updateMetadata);
this.archive = ImageArchive.from(builderImage, (update) -> {
this.builderImage = builderImage;
this.archiveUpdate = (update) -> {
update.withUpdatedConfig(this.builderMetadata::attachTo);
update.withUpdatedConfig((config) -> config.withLabel(BUILDER_FOR_LABEL_NAME, targetImage.toString()));
update.withTag(name);
update.withTag(this.name);
if (!CollectionUtils.isEmpty(env)) {
update.withNewLayer(getEnvLayer(env));
}
if (buildpacks != null) {
buildpacks.apply(update::withNewLayer);
}
});
};
}
private void updateMetadata(BuilderMetadata.Update update) {
@ -95,7 +101,7 @@ class EphemeralBuilder {
* @return the ephemeral builder name
*/
ImageReference getName() {
return this.archive.getTag();
return this.name;
}
/**
@ -116,15 +122,26 @@ class EphemeralBuilder {
/**
* Return the contents of ephemeral builder for passing to Docker.
* @param applicationDirectory the application directory
* @return the ephemeral builder archive
* @throws IOException on IO error
*/
ImageArchive getArchive() {
return this.archive;
ImageArchive getArchive(String applicationDirectory) throws IOException {
return ImageArchive.from(this.builderImage, (update) -> {
this.archiveUpdate.accept(update);
if (StringUtils.hasLength(applicationDirectory)) {
update.withNewLayer(applicationDirectoryLayer(applicationDirectory));
}
});
}
private Layer applicationDirectoryLayer(String applicationDirectory) throws IOException {
return Layer.of((layout) -> layout.directory(applicationDirectory, this.buildOwner));
}
@Override
public String toString() {
return this.archive.getTag().toString();
return this.name.toString();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 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.
@ -117,6 +117,10 @@ class Lifecycle implements Closeable {
this.securityOptions = getSecurityOptions(request);
}
String getApplicationDirectory() {
return this.applicationDirectory;
}
private Cache getBuildCache(BuildRequest request) {
if (request.getBuildCache() != null) {
return request.getBuildCache();

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@ -88,7 +88,7 @@ class EphemeralBuilderTests extends AbstractJsonTests {
}
@Test
void getNameHasRandomName() throws Exception {
void getNameHasRandomName() {
EphemeralBuilder b1 = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata,
this.creator, this.env, this.buildpacks);
EphemeralBuilder b2 = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata,
@ -101,7 +101,7 @@ class EphemeralBuilderTests extends AbstractJsonTests {
void getArchiveHasCreatedByConfig() throws Exception {
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata,
this.creator, this.env, this.buildpacks);
ImageConfig config = builder.getArchive().getImageConfig();
ImageConfig config = builder.getArchive(null).getImageConfig();
BuilderMetadata ephemeralMetadata = BuilderMetadata.fromImageConfig(config);
assertThat(ephemeralMetadata.getCreatedBy().getName()).isEqualTo("Spring Boot");
assertThat(ephemeralMetadata.getCreatedBy().getVersion()).isEqualTo("dev");
@ -111,7 +111,7 @@ class EphemeralBuilderTests extends AbstractJsonTests {
void getArchiveHasTag() throws Exception {
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata,
this.creator, this.env, this.buildpacks);
ImageReference tag = builder.getArchive().getTag();
ImageReference tag = builder.getArchive(null).getTag();
assertThat(tag.toString()).startsWith("pack.local/builder/").endsWith(":latest");
}
@ -119,7 +119,7 @@ class EphemeralBuilderTests extends AbstractJsonTests {
void getArchiveHasFixedCreatedDate() throws Exception {
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata,
this.creator, this.env, this.buildpacks);
Instant createInstant = builder.getArchive().getCreateDate();
Instant createInstant = builder.getArchive(null).getCreateDate();
OffsetDateTime createDateTime = OffsetDateTime.ofInstant(createInstant, ZoneId.of("UTC"));
assertThat(createDateTime.getYear()).isEqualTo(1980);
assertThat(createDateTime.getMonthValue()).isOne();
@ -133,7 +133,7 @@ class EphemeralBuilderTests extends AbstractJsonTests {
void getArchiveContainsEnvLayer() throws Exception {
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata,
this.creator, this.env, this.buildpacks);
File directory = unpack(getLayer(builder.getArchive(), EXISTING_IMAGE_LAYER_COUNT), "env");
File directory = unpack(getLayer(builder.getArchive(null), EXISTING_IMAGE_LAYER_COUNT), "env");
assertThat(new File(directory, "platform/env/spring")).usingCharset(StandardCharsets.UTF_8).hasContent("boot");
assertThat(new File(directory, "platform/env/empty")).usingCharset(StandardCharsets.UTF_8).hasContent("");
}
@ -142,7 +142,7 @@ class EphemeralBuilderTests extends AbstractJsonTests {
void getArchiveHasBuilderForLabel() throws Exception {
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata,
this.creator, this.env, this.buildpacks);
ImageConfig config = builder.getArchive().getImageConfig();
ImageConfig config = builder.getArchive(null).getImageConfig();
assertThat(config.getLabels())
.contains(entry(EphemeralBuilder.BUILDER_FOR_LABEL_NAME, this.targetImage.toString()));
}
@ -162,13 +162,21 @@ class EphemeralBuilderTests extends AbstractJsonTests {
"/cnb/buildpacks/example_buildpack2/0.0.2/buildpack.toml");
assertBuildpackLayerContent(builder, EXISTING_IMAGE_LAYER_COUNT + 2,
"/cnb/buildpacks/example_buildpack3/0.0.3/buildpack.toml");
File orderDirectory = unpack(getLayer(builder.getArchive(), EXISTING_IMAGE_LAYER_COUNT + 3), "order");
File orderDirectory = unpack(getLayer(builder.getArchive(null), EXISTING_IMAGE_LAYER_COUNT + 3), "order");
assertThat(new File(orderDirectory, "cnb/order.toml")).usingCharset(StandardCharsets.UTF_8)
.hasContent(content("order.toml"));
}
@Test
void getArchiveHasApplicationDirectoryLayer() throws Exception {
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.targetImage, this.metadata,
this.creator, this.env, this.buildpacks);
File directory = unpack(getLayer(builder.getArchive("/myapp"), EXISTING_IMAGE_LAYER_COUNT + 1), "appdir");
assertThat(new File(directory, "myapp")).isDirectory();
}
private void assertBuildpackLayerContent(EphemeralBuilder builder, int index, String s) throws Exception {
File buildpackDirectory = unpack(getLayer(builder.getArchive(), index), "buildpack");
File buildpackDirectory = unpack(getLayer(builder.getArchive(null), index), "buildpack");
assertThat(new File(buildpackDirectory, s)).usingCharset(StandardCharsets.UTF_8).hasContent("[test]");
}