Polish "Add option to customize cache volume names when building an image"

See gh-28292
This commit is contained in:
Scott Frederick
2021-10-14 14:55:04 -05:00
parent dc36346285
commit 871468931f
27 changed files with 937 additions and 157 deletions

View File

@@ -187,10 +187,18 @@ The value supplied will be passed unvalidated to Docker when creating the builde
| One or more additional tags to apply to the generated image.
|
| `cacheVolumeNames`
| `caches`
| Cache volume names that should be used by the builder instead of generating random names.
|
| `buildCache`
| A cache containing layers created by buildpacks and used by the image building process.
| A named volume in the Docker daemon, with a name derived from the image name.
| `launchCache`
| A cache containing layers created by buildpacks and used by the image launching process.
| A named volume in the Docker daemon, with a name derived from the image name.
|===
NOTE: The plugin detects the target Java compatibility of the project using the compiler's plugin configuration or the `maven.compiler.target` property.
@@ -344,10 +352,23 @@ The `publish` option can be specified on the command line as well, as shown in t
$ mvn spring-boot:build-image -Dspring-boot.build-image.imageName=docker.example.com/library/my-app:v1 -Dspring-boot.build-image.publish=true
----
[[build-image.examples.caches]]
=== Builder Cache Configuration
The CNB builder caches layers that are used when building and launching an image.
By default, these caches are stored as named volumes in the Docker daemon with names that are derived from the full name of the target image.
If the image name changes frequently, for example when the project version is used as a tag in the image name, then the caches can be invalidated frequently.
The cache volumes can be configured to use alternative names to give more control over cache lifecycle as shown in the following example:
[source,xml,indent=0,subs="verbatim,attributes",tabsize=4]
----
include::../maven/packaging-oci-image/caches-pom.xml[tags=caches]
----
[[build-image.examples.docker]]
=== Docker Configuration
If you need the plugin to communicate with the Docker daemon using a remote connection instead of the default local connection, the connection details can be provided using `docker` parameters as shown in the following example:
[source,xml,indent=0,subs="verbatim,attributes",tabsize=4]

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>
<volume>
<name>cache-${project.artifactId}.build</name>
</volume>
</buildCache>
<launchCache>
<volume>
<name>cache-${project.artifactId}.launch</name>
</volume>
</launchCache>
</image>
</configuration>
</plugin>
</plugins>
</build>
</project>
<!-- end::caches[] -->

View File

@@ -27,8 +27,10 @@ import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.buildpack.platform.docker.DockerApi;
import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi;
import org.springframework.boot.buildpack.platform.docker.type.ImageName;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.docker.type.VolumeName;
import org.springframework.boot.testsupport.testcontainers.DisabledIfDockerUnavailable;
import static org.assertj.core.api.Assertions.assertThat;
@@ -306,6 +308,18 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
});
}
@TestTemplate
void whenBuildImageIsInvokedWithVolumeCaches(MavenBuild mavenBuild) {
mavenBuild.project("build-image-caches").goals("package")
.systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT").execute((project) -> {
assertThat(buildLog(project)).contains("Building image")
.contains("docker.io/library/build-image-caches:0.0.1.BUILD-SNAPSHOT")
.contains("Successfully built image");
removeImage("build-image-caches", "0.0.1.BUILD-SNAPSHOT");
deleteVolumes("build-cache-volume", "launch-cache-volume");
});
}
@TestTemplate
void failsWhenBuildImageIsInvokedOnMultiModuleProjectWithBuildImageGoal(MavenBuild mavenBuild) {
mavenBuild.project("build-image-multi-module").goals("spring-boot:build-image")
@@ -343,6 +357,13 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
.contains("is required for building an image"));
}
@TestTemplate
void failsWhenCachesAreConfiguredTwice(MavenBuild mavenBuild) {
mavenBuild.project("build-image-caches-multiple").goals("package")
.executeAndFail((project) -> assertThat(buildLog(project))
.contains("Each image building cache can be configured only once"));
}
private void writeLongNameResource(File project) {
StringBuilder name = new StringBuilder();
new Random().ints('a', 'z' + 1).limit(128).forEach((i) -> name.append((char) i));
@@ -366,4 +387,11 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
}
}
private void deleteVolumes(String... names) throws IOException {
VolumeApi volumeApi = new DockerApi().volume();
for (String name : names) {
volumeApi.delete(VolumeName.of(name), false);
}
}
}

View File

@@ -0,0 +1,42 @@
<?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-caches-multiple</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</goal>
</goals>
<configuration>
<image>
<builder>projects.registry.vmware.com/springboot/spring-boot-cnb-builder:0.0.1</builder>
<buildCache>
<volume>
<name>build-cache-volume1</name>
</volume>
<volume>
<name>build-cache-volume2</name>
</volume>
</buildCache>
</image>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2012-2021 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

@@ -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-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</goal>
</goals>
<configuration>
<image>
<builder>projects.registry.vmware.com/springboot/spring-boot-cnb-builder:0.0.1</builder>
<buildCache>
<volume>
<name>build-cache-volume</name>
</volume>
</buildCache>
<launchCache>
<volume>
<name>launch-cache-volume</name>
</volume>
</launchCache>
</image>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2012-2021 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

@@ -0,0 +1,72 @@
/*
* Copyright 2012-2021 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.springframework.boot.buildpack.platform.build.Cache;
import org.springframework.util.Assert;
/**
* Encapsulates configuration of an image building cache.
*
* @author Scott Frederick
* @since 2.6.0
*/
public class CacheInfo {
private Cache cache;
public CacheInfo() {
}
CacheInfo(VolumeCacheInfo volumeCacheInfo) {
this.cache = Cache.volume(volumeCacheInfo.getName());
}
public void setVolume(VolumeCacheInfo info) {
Assert.state(this.cache == null, "Each image building cache can be configured only once");
this.cache = Cache.volume(info.getName());
}
Cache asCache() {
return this.cache;
}
/**
* Encapsulates configuration of an image building cache stored in a volume.
*/
public static class VolumeCacheInfo {
private String name;
public VolumeCacheInfo() {
}
VolumeCacheInfo(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
void setName(String name) {
this.name = name;
}
}
}

View File

@@ -70,7 +70,9 @@ public class Image {
List<String> tags;
Map<String, String> cacheVolumeNames;
CacheInfo buildCache;
CacheInfo launchCache;
/**
* The name of the created image.
@@ -215,8 +217,11 @@ public class Image {
if (!CollectionUtils.isEmpty(this.tags)) {
request = request.withTags(this.tags.stream().map(ImageReference::of).collect(Collectors.toList()));
}
if (this.cacheVolumeNames != null && !this.cacheVolumeNames.isEmpty()) {
request = request.withCacheVolumeNames(this.cacheVolumeNames);
if (this.buildCache != null) {
request = request.withBuildCache(this.buildCache.asCache());
}
if (this.launchCache != null) {
request = request.withLaunchCache(this.launchCache.asCache());
}
return request;
}

View File

@@ -28,11 +28,13 @@ import org.junit.jupiter.api.Test;
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.PullPolicy;
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.VolumeCacheInfo;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
@@ -167,6 +169,22 @@ class ImageTests {
ImageReference.of("example.com/my-app:0.0.1-SNAPSHOT"), ImageReference.of("example.com/my-app:latest"));
}
@Test
void getBuildRequestWhenHasBuildVolumeCacheUsesCache() {
Image image = new Image();
image.buildCache = new CacheInfo(new VolumeCacheInfo("build-cache-vol"));
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getBuildCache()).isEqualTo(Cache.volume("build-cache-vol"));
}
@Test
void getBuildRequestWhenHasLaunchVolumeCacheUsesCache() {
Image image = new Image();
image.launchCache = new CacheInfo(new VolumeCacheInfo("launch-cache-vol"));
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getLaunchCache()).isEqualTo(Cache.volume("launch-cache-vol"));
}
private Artifact createArtifact() {
return new DefaultArtifact("com.example", "my-app", VersionRange.createFromVersion("0.0.1-SNAPSHOT"), "compile",
"jar", null, new DefaultArtifactHandler());