Polish "Add support for image-based build caches"

See gh-50899
This commit is contained in:
Stéphane Nicoll
2026-07-27 18:22:16 +02:00
parent a36d078632
commit 5bd3e6435c
15 changed files with 519 additions and 359 deletions
@@ -19,4 +19,4 @@
</plugins>
</build>
</project>
<!-- end::caches[] -->
<!-- end::caches[] -->
@@ -19,6 +19,8 @@ package org.springframework.boot.maven;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.build.Cache;
import org.springframework.boot.maven.LocalCacheInfo.BindCacheInfo;
import org.springframework.boot.maven.LocalCacheInfo.VolumeCacheInfo;
import org.springframework.util.Assert;
/**
@@ -81,54 +83,6 @@ public class CacheInfo {
return new CacheInfo(Cache.image(name));
}
/**
* Encapsulates configuration of an image building cache stored in a volume.
*/
public static class VolumeCacheInfo {
private @Nullable String name;
public VolumeCacheInfo() {
}
VolumeCacheInfo(String name) {
this.name = name;
}
public @Nullable String getName() {
return this.name;
}
void setName(@Nullable String name) {
this.name = name;
}
}
/**
* Encapsulates configuration of an image building cache stored in a bind mount.
*/
public static class BindCacheInfo {
private @Nullable String source;
public BindCacheInfo() {
}
BindCacheInfo(String name) {
this.source = name;
}
public @Nullable String getSource() {
return this.source;
}
void setSource(@Nullable String source) {
this.source = source;
}
}
/**
* Encapsulates configuration of an image building cache stored in an image.
*/
@@ -26,6 +26,7 @@ import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.build.BuildRequest;
import org.springframework.boot.buildpack.platform.build.BuildpackReference;
import org.springframework.boot.buildpack.platform.build.Cache;
import org.springframework.boot.buildpack.platform.build.LocalCache;
import org.springframework.boot.buildpack.platform.build.PullPolicy;
import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.ImageName;
@@ -74,11 +75,11 @@ public class Image {
@Nullable List<String> tags;
@Nullable CacheInfo buildWorkspace;
@Nullable LocalCacheInfo buildWorkspace;
@Nullable CacheInfo buildCache;
@Nullable CacheInfo launchCache;
@Nullable LocalCacheInfo launchCache;
@Nullable String createdDate;
@@ -284,7 +285,7 @@ public class Image {
request = request.withTags(this.tags.stream().map(ImageReference::of).toList());
}
if (this.buildWorkspace != null) {
Cache cache = this.buildWorkspace.asCache();
LocalCache cache = this.buildWorkspace.asCache();
Assert.state(cache != null, "'cache' must not be null");
request = request.withBuildWorkspace(cache);
}
@@ -294,7 +295,7 @@ public class Image {
request = request.withBuildCache(cache);
}
if (this.launchCache != null) {
Cache cache = this.launchCache.asCache();
LocalCache cache = this.launchCache.asCache();
Assert.state(cache != null, "'cache' must not be null");
request = request.withLaunchCache(cache);
}
@@ -0,0 +1,121 @@
/*
* Copyright 2012-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.maven;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.build.Cache;
import org.springframework.boot.buildpack.platform.build.LocalCache;
import org.springframework.util.Assert;
/**
* Encapsulates configuration of a local image building cache.
*
* @author Scott Frederick
* @author Stephane Nicoll
* @since 4.2.0
*/
public class LocalCacheInfo {
private @Nullable LocalCache cache;
public LocalCacheInfo() {
}
LocalCacheInfo(LocalCache cache) {
this.cache = cache;
}
public void setVolume(VolumeCacheInfo info) {
Assert.state(this.cache == null, "Each image building cache can be configured only once");
String name = info.getName();
Assert.state(name != null, "'name' must not be null");
this.cache = Cache.volume(name);
}
public void setBind(BindCacheInfo info) {
Assert.state(this.cache == null, "Each image building cache can be configured only once");
String source = info.getSource();
Assert.state(source != null, "'source' must not be null");
this.cache = Cache.bind(source);
}
@Nullable LocalCache asCache() {
return this.cache;
}
static LocalCacheInfo fromVolume(VolumeCacheInfo cacheInfo) {
String name = cacheInfo.getName();
Assert.state(name != null, "'name' must not be null");
return new LocalCacheInfo(Cache.volume(name));
}
static LocalCacheInfo fromBind(BindCacheInfo cacheInfo) {
String source = cacheInfo.getSource();
Assert.state(source != null, "'source' must not be null");
return new LocalCacheInfo(Cache.bind(source));
}
/**
* Encapsulates configuration of an image building cache stored in a volume.
*/
public static class VolumeCacheInfo {
private @Nullable String name;
public VolumeCacheInfo() {
}
VolumeCacheInfo(String name) {
this.name = name;
}
public @Nullable String getName() {
return this.name;
}
void setName(@Nullable String name) {
this.name = name;
}
}
/**
* Encapsulates configuration of an image building cache stored in a bind mount.
*/
public static class BindCacheInfo {
private @Nullable String source;
public BindCacheInfo() {
}
BindCacheInfo(String name) {
this.source = name;
}
public @Nullable String getSource() {
return this.source;
}
void setSource(@Nullable String source) {
this.source = source;
}
}
}
@@ -36,12 +36,11 @@ import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.io.Owner;
import org.springframework.boot.buildpack.platform.io.TarArchive;
import org.springframework.boot.maven.CacheInfo.BindCacheInfo;
import org.springframework.boot.maven.CacheInfo.ImageCacheInfo;
import org.springframework.boot.maven.CacheInfo.VolumeCacheInfo;
import org.springframework.boot.maven.LocalCacheInfo.BindCacheInfo;
import org.springframework.boot.maven.LocalCacheInfo.VolumeCacheInfo;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.Mockito.mock;
@@ -202,7 +201,7 @@ class ImageTests {
@Test
void getBuildRequestWhenHasBuildWorkspaceVolumeUsesWorkspace() {
Image image = new Image();
image.buildWorkspace = CacheInfo.fromVolume(new VolumeCacheInfo("build-work-vol"));
image.buildWorkspace = LocalCacheInfo.fromVolume(new VolumeCacheInfo("build-work-vol"));
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getBuildWorkspace()).isEqualTo(Cache.volume("build-work-vol"));
}
@@ -218,7 +217,7 @@ class ImageTests {
@Test
void getBuildRequestWhenHasLaunchCacheVolumeUsesCache() {
Image image = new Image();
image.launchCache = CacheInfo.fromVolume(new VolumeCacheInfo("launch-cache-vol"));
image.launchCache = LocalCacheInfo.fromVolume(new VolumeCacheInfo("launch-cache-vol"));
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getLaunchCache()).isEqualTo(Cache.volume("launch-cache-vol"));
}
@@ -226,7 +225,7 @@ class ImageTests {
@Test
void getBuildRequestWhenHasBuildWorkspaceBindUsesWorkspace() {
Image image = new Image();
image.buildWorkspace = CacheInfo.fromBind(new BindCacheInfo("build-work-dir"));
image.buildWorkspace = LocalCacheInfo.fromBind(new BindCacheInfo("build-work-dir"));
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getBuildWorkspace()).isEqualTo(Cache.bind("build-work-dir"));
}
@@ -242,7 +241,7 @@ class ImageTests {
@Test
void getBuildRequestWhenHasLaunchCacheBindUsesCache() {
Image image = new Image();
image.launchCache = CacheInfo.fromBind(new BindCacheInfo("launch-cache-dir"));
image.launchCache = LocalCacheInfo.fromBind(new BindCacheInfo("launch-cache-dir"));
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getLaunchCache()).isEqualTo(Cache.bind("launch-cache-dir"));
}
@@ -255,15 +254,6 @@ class ImageTests {
assertThat(request.getBuildCache()).isEqualTo(Cache.image("build-cache-image"));
}
@Test
void getBuildRequestWhenHasLaunchCacheImageThrowsException() {
Image image = new Image();
image.launchCache = CacheInfo.fromImage(new ImageCacheInfo("launch-cache-image"));
assertThatIllegalArgumentException()
.isThrownBy(() -> image.getBuildRequest(createArtifact(), mockApplicationContent()))
.withMessage("Launch cache must not be an image cache");
}
@Test
void getBuildRequestWhenHasCreatedDateUsesCreatedDate() {
Image image = new Image();