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

@@ -440,6 +440,20 @@ include::../gradle/packaging/boot-build-image-caches.gradle[tags=caches]
include::../gradle/packaging/boot-build-image-caches.gradle.kts[tags=caches]
----
The caches can be configured to use bind mounts instead of named volumes, as shown in the following example:
[source,groovy,indent=0,subs="verbatim,attributes",role="primary"]
.Groovy
----
include::../gradle/packaging/boot-build-image-bind-caches.gradle[tags=caches]
----
[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"]
.Kotlin
----
include::../gradle/packaging/boot-build-image-bind-caches.gradle.kts[tags=caches]
----
[[build-image.examples.docker]]
=== Docker Configuration

View File

@@ -0,0 +1,30 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{gradle-project-version}'
}
tasks.named("bootJar") {
mainClass = 'com.example.ExampleApplication'
}
// tag::caches[]
tasks.named("bootBuildImage") {
buildCache {
bind {
source = "/tmp/cache-${rootProject.name}.build"
}
}
launchCache {
bind {
source = "/tmp/cache-${rootProject.name}.launch"
}
}
}
// end::caches[]
tasks.register("bootBuildImageCaches") {
doFirst {
bootBuildImage.buildCache.asCache().with { println "buildCache=$source" }
bootBuildImage.launchCache.asCache().with { println "launchCache=$source" }
}
}

View File

@@ -0,0 +1,28 @@
import org.springframework.boot.gradle.tasks.bundling.BootBuildImage
plugins {
java
id("org.springframework.boot") version "{gradle-project-version}"
}
// tag::caches[]
tasks.named<BootBuildImage>("bootBuildImage") {
buildCache {
bind {
source.set("/tmp/cache-${rootProject.name}.build")
}
}
launchCache {
bind {
source.set("/tmp/cache-${rootProject.name}.launch")
}
}
}
// end::caches[]
tasks.register("bootBuildImageCaches") {
doFirst {
println("buildCache=" + tasks.getByName<BootBuildImage>("bootBuildImage").buildCache.asCache().bind.source)
println("launchCache=" + tasks.getByName<BootBuildImage>("bootBuildImage").launchCache.asCache().bind.source)
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2021-2022 the original author or authors.
* Copyright 2021-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.
@@ -60,6 +60,19 @@ public class CacheSpec {
this.cache = Cache.volume(spec.getName().get());
}
/**
* Configures a bind cache using the given {@code action}.
* @param action the action
*/
public void bind(Action<BindCacheSpec> action) {
if (this.cache != null) {
throw new GradleException("Each image building cache can be configured only once");
}
BindCacheSpec spec = this.objectFactory.newInstance(BindCacheSpec.class);
action.execute(spec);
this.cache = Cache.bind(spec.getSource().get());
}
/**
* Configuration for an image building cache stored in a Docker volume.
*/
@@ -74,4 +87,18 @@ public class CacheSpec {
}
/**
* Configuration for an image building cache stored in a bind mount.
*/
public abstract static class BindCacheSpec {
/**
* Returns the source of the cache.
* @return the cache source
*/
@Input
public abstract Property<String> getSource();
}
}

View File

@@ -339,6 +339,14 @@ class PackagingDocumentationTests {
.containsPattern("launchCache=cache-gradle-[\\d]+.launch");
}
@TestTemplate
void bootBuildImageWithBindCaches() {
BuildResult result = this.gradleBuild.script("src/docs/gradle/packaging/boot-build-image-bind-caches")
.build("bootBuildImageCaches");
assertThat(result.getOutput()).containsPattern("buildCache=/tmp/cache-gradle-[\\d]+.build")
.containsPattern("launchCache=/tmp/cache-gradle-[\\d]+.launch");
}
protected void jarFile(File file) throws IOException {
try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(file))) {
jar.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF"));

View File

@@ -50,6 +50,7 @@ import org.springframework.boot.gradle.junit.GradleCompatibility;
import org.springframework.boot.testsupport.gradle.testkit.GradleBuild;
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;
@@ -297,6 +298,26 @@ class BootBuildImageIntegrationTests {
deleteVolumes("cache-" + projectName + ".build", "cache-" + projectName + ".launch");
}
@TestTemplate
void buildsImageWithBindCaches() throws IOException {
writeMainClass();
writeLongNameResource();
BuildResult result = this.gradleBuild.build("bootBuildImage");
String projectName = this.gradleBuild.getProjectDir().getName();
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("docker.io/library/" + projectName);
assertThat(result.getOutput()).contains("---> Test Info buildpack building");
assertThat(result.getOutput()).contains("---> Test Info buildpack done");
removeImages(projectName);
String tempDir = System.getProperty("java.io.tmpdir");
Path buildCachePath = Paths.get(tempDir, "junit-image-cache-" + projectName + "-build");
Path launchCachePath = Paths.get(tempDir, "junit-image-cache-" + projectName + "-launch");
assertThat(buildCachePath).exists().isDirectory();
assertThat(launchCachePath).exists().isDirectory();
FileSystemUtils.deleteRecursively(buildCachePath);
FileSystemUtils.deleteRecursively(launchCachePath);
}
@TestTemplate
void buildsImageWithCreatedDate() throws IOException {
writeMainClass();

View File

@@ -0,0 +1,24 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
java {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
bootBuildImage {
builder = "projects.registry.vmware.com/springboot/spring-boot-cnb-builder:0.0.2"
pullPolicy = "IF_NOT_PRESENT"
buildCache {
bind {
source = System.getProperty('java.io.tmpdir') + "/junit-image-cache-${rootProject.name}-build"
}
}
launchCache {
bind {
source = System.getProperty('java.io.tmpdir') + "/junit-image-cache-${rootProject.name}-launch"
}
}
}

View File

@@ -12,10 +12,10 @@ bootBuildImage {
builder = "projects.registry.vmware.com/springboot/spring-boot-cnb-builder:0.0.2"
buildCache {
volume {
name = "build-cache-volume1"
name = "build-cache-volume"
}
volume {
name = "build-cache-volum2"
bind {
name = "/tmp/build-cache-bind"
}
}
}