Add support for caching to bind mounts when building images

When building an image using the Maven `spring-boot:build-image` goal or
the Gradle `bootBuildImage` task, the build and launch caches can be
configured to use a bind mount as an alternative to using a named
volume.

Closes gh-28387
This commit is contained in:
Scott Frederick
2023-08-21 15:03:39 -05:00
parent d46a58f0f6
commit c17ecf0f0b
25 changed files with 575 additions and 47 deletions

View File

@@ -420,6 +420,13 @@ The cache volumes can be configured to use alternative names to give more contro
include::../maven/packaging-oci-image/caches-pom.xml[tags=caches]
----
The caches can be configured to use bind mounts instead of named volumes, as shown in the following example:
[source,xml,indent=0,subs="verbatim,attributes",tabsize=4]
----
include::../maven/packaging-oci-image/bind-caches-pom.xml[tags=caches]
----
[[build-image.examples.docker]]
=== Docker Configuration

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- tag::caches[] -->
<project>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<buildCache>
<bind>
<source>/tmp/cache-${project.artifactId}.build</source>
</bind>
</buildCache>
<launchCache>
<bind>
<source>/tmp/cache-${project.artifactId}.launch</source>
</bind>
</launchCache>
</image>
</configuration>
</plugin>
</plugins>
</build>
</project>
<!-- end::caches[] -->

View File

@@ -37,6 +37,7 @@ import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.docker.type.VolumeName;
import org.springframework.boot.testsupport.junit.DisabledOnOs;
import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable;
import org.springframework.util.FileSystemUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -385,19 +386,41 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
@TestTemplate
void whenBuildImageIsInvokedWithVolumeCaches(MavenBuild mavenBuild) {
String testBuildId = randomString();
mavenBuild.project("build-image-caches")
mavenBuild.project("build-image-volume-caches")
.goals("package")
.systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT")
.systemProperty("test-build-id", testBuildId)
.execute((project) -> {
assertThat(buildLog(project)).contains("Building image")
.contains("docker.io/library/build-image-caches:0.0.1.BUILD-SNAPSHOT")
.contains("docker.io/library/build-image-volume-caches:0.0.1.BUILD-SNAPSHOT")
.contains("Successfully built image");
removeImage("build-image-caches", "0.0.1.BUILD-SNAPSHOT");
removeImage("build-image-volume-caches", "0.0.1.BUILD-SNAPSHOT");
deleteVolumes("cache-" + testBuildId + ".build", "cache-" + testBuildId + ".launch");
});
}
@TestTemplate
void whenBuildImageIsInvokedWithBindCaches(MavenBuild mavenBuild) {
String testBuildId = randomString();
mavenBuild.project("build-image-bind-caches")
.goals("package")
.systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT")
.systemProperty("test-build-id", testBuildId)
.execute((project) -> {
assertThat(buildLog(project)).contains("Building image")
.contains("docker.io/library/build-image-bind-caches:0.0.1.BUILD-SNAPSHOT")
.contains("Successfully built image");
removeImage("build-image-bind-caches", "0.0.1.BUILD-SNAPSHOT");
String tempDir = System.getProperty("java.io.tmpdir");
Path buildCachePath = Paths.get(tempDir, "junit-image-cache-" + testBuildId + "-build");
Path launchCachePath = Paths.get(tempDir, "junit-image-cache-" + testBuildId + "-launch");
assertThat(buildCachePath).exists().isDirectory();
assertThat(launchCachePath).exists().isDirectory();
FileSystemUtils.deleteRecursively(buildCachePath);
FileSystemUtils.deleteRecursively(launchCachePath);
});
}
@TestTemplate
void whenBuildImageIsInvokedWithCreatedDate(MavenBuild mavenBuild) {
String testBuildId = randomString();

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>build-image-bind-caches</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>@java.version@</maven.compiler.source>
<maven.compiler.target>@java.version@</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>build-image-no-fork</goal>
</goals>
<configuration>
<image>
<builder>projects.registry.vmware.com/springboot/spring-boot-cnb-builder:0.0.2</builder>
<buildCache>
<bind>
<source>${java.io.tmpdir}/junit-image-cache-${test-build-id}-build</source>
</bind>
</buildCache>
<launchCache>
<bind>
<source>${java.io.tmpdir}/junit-image-cache-${test-build-id}-launch</source>
</bind>
</launchCache>
</image>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2023 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.

View File

@@ -3,7 +3,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>build-image-caches</artifactId>
<artifactId>build-image-volume-caches</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2012-2023 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.test;
public class SampleApplication {
public static void main(String[] args) throws Exception {
System.out.println("Launched");
synchronized(args) {
args.wait(); // Prevent exit
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2023 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.
@@ -32,8 +32,8 @@ public class CacheInfo {
public CacheInfo() {
}
CacheInfo(VolumeCacheInfo volumeCacheInfo) {
this.cache = Cache.volume(volumeCacheInfo.getName());
private CacheInfo(Cache cache) {
this.cache = cache;
}
public void setVolume(VolumeCacheInfo info) {
@@ -41,10 +41,23 @@ public class CacheInfo {
this.cache = Cache.volume(info.getName());
}
public void setBind(BindCacheInfo info) {
Assert.state(this.cache == null, "Each image building cache can be configured only once");
this.cache = Cache.bind(info.getSource());
}
Cache asCache() {
return this.cache;
}
static CacheInfo fromVolume(VolumeCacheInfo cacheInfo) {
return new CacheInfo(Cache.volume(cacheInfo.getName()));
}
static CacheInfo fromBind(BindCacheInfo cacheInfo) {
return new CacheInfo(Cache.bind(cacheInfo.getSource()));
}
/**
* Encapsulates configuration of an image building cache stored in a volume.
*/
@@ -69,4 +82,28 @@ public class CacheInfo {
}
/**
* Encapsulates configuration of an image building cache stored in a bind mount.
*/
public static class BindCacheInfo {
private String source;
public BindCacheInfo() {
}
BindCacheInfo(String name) {
this.source = name;
}
public String getSource() {
return this.source;
}
void setSource(String source) {
this.source = source;
}
}
}

View File

@@ -34,6 +34,7 @@ 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.VolumeCacheInfo;
import static org.assertj.core.api.Assertions.assertThat;
@@ -170,21 +171,37 @@ class ImageTests {
}
@Test
void getBuildRequestWhenHasBuildVolumeCacheUsesCache() {
void getBuildRequestWhenHasBuildCacheVolumeUsesCache() {
Image image = new Image();
image.buildCache = new CacheInfo(new VolumeCacheInfo("build-cache-vol"));
image.buildCache = CacheInfo.fromVolume(new VolumeCacheInfo("build-cache-vol"));
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getBuildCache()).isEqualTo(Cache.volume("build-cache-vol"));
}
@Test
void getBuildRequestWhenHasLaunchVolumeCacheUsesCache() {
void getBuildRequestWhenHasLaunchCacheVolumeUsesCache() {
Image image = new Image();
image.launchCache = new CacheInfo(new VolumeCacheInfo("launch-cache-vol"));
image.launchCache = CacheInfo.fromVolume(new VolumeCacheInfo("launch-cache-vol"));
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getLaunchCache()).isEqualTo(Cache.volume("launch-cache-vol"));
}
@Test
void getBuildRequestWhenHasBuildCacheBindUsesCache() {
Image image = new Image();
image.buildCache = CacheInfo.fromBind(new BindCacheInfo("build-cache-dir"));
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getBuildCache()).isEqualTo(Cache.bind("build-cache-dir"));
}
@Test
void getBuildRequestWhenHasLaunchCacheBindUsesCache() {
Image image = new Image();
image.launchCache = CacheInfo.fromBind(new BindCacheInfo("launch-cache-dir"));
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getLaunchCache()).isEqualTo(Cache.bind("launch-cache-dir"));
}
@Test
void getBuildRequestWhenHasCreatedDateUsesCreatedDate() {
Image image = new Image();