Add support for build workspace option when building images

Closes gh-37478
This commit is contained in:
Scott Frederick 2023-09-20 13:30:20 -05:00
parent d8576f319e
commit 4433fcd1f2
19 changed files with 239 additions and 58 deletions

View File

@ -77,6 +77,8 @@ public class BuildRequest {
private final List<ImageReference> tags;
private final Cache buildWorkspace;
private final Cache buildCache;
private final Cache launchCache;
@ -102,6 +104,7 @@ public class BuildRequest {
this.bindings = Collections.emptyList();
this.network = null;
this.tags = Collections.emptyList();
this.buildWorkspace = null;
this.buildCache = null;
this.launchCache = null;
this.createdDate = null;
@ -111,8 +114,8 @@ public class BuildRequest {
BuildRequest(ImageReference name, Function<Owner, TarArchive> applicationContent, ImageReference builder,
ImageReference runImage, Creator creator, Map<String, String> env, boolean cleanCache,
boolean verboseLogging, PullPolicy pullPolicy, boolean publish, List<BuildpackReference> buildpacks,
List<Binding> bindings, String network, List<ImageReference> tags, Cache buildCache, Cache launchCache,
Instant createdDate, String applicationDirectory) {
List<Binding> bindings, String network, List<ImageReference> tags, Cache buildWorkspace, Cache buildCache,
Cache launchCache, Instant createdDate, String applicationDirectory) {
this.name = name;
this.applicationContent = applicationContent;
this.builder = builder;
@ -127,6 +130,7 @@ public class BuildRequest {
this.bindings = bindings;
this.network = network;
this.tags = tags;
this.buildWorkspace = buildWorkspace;
this.buildCache = buildCache;
this.launchCache = launchCache;
this.createdDate = createdDate;
@ -142,8 +146,8 @@ public class BuildRequest {
Assert.notNull(builder, "Builder must not be null");
return new BuildRequest(this.name, this.applicationContent, builder.inTaggedOrDigestForm(), this.runImage,
this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish,
this.buildpacks, this.bindings, this.network, this.tags, this.buildCache, this.launchCache,
this.createdDate, this.applicationDirectory);
this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache,
this.launchCache, this.createdDate, this.applicationDirectory);
}
/**
@ -154,8 +158,8 @@ public class BuildRequest {
public BuildRequest withRunImage(ImageReference runImageName) {
return new BuildRequest(this.name, this.applicationContent, this.builder, runImageName.inTaggedOrDigestForm(),
this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish,
this.buildpacks, this.bindings, this.network, this.tags, this.buildCache, this.launchCache,
this.createdDate, this.applicationDirectory);
this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache,
this.launchCache, this.createdDate, this.applicationDirectory);
}
/**
@ -167,7 +171,7 @@ public class BuildRequest {
Assert.notNull(creator, "Creator must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate,
this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
@ -184,8 +188,8 @@ public class BuildRequest {
env.put(name, value);
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator,
Collections.unmodifiableMap(env), this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish,
this.buildpacks, this.bindings, this.network, this.tags, this.buildCache, this.launchCache,
this.createdDate, this.applicationDirectory);
this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache,
this.launchCache, this.createdDate, this.applicationDirectory);
}
/**
@ -199,8 +203,8 @@ public class BuildRequest {
updatedEnv.putAll(env);
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator,
Collections.unmodifiableMap(updatedEnv), this.cleanCache, this.verboseLogging, this.pullPolicy,
this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildCache,
this.launchCache, this.createdDate, this.applicationDirectory);
this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace,
this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory);
}
/**
@ -211,7 +215,7 @@ public class BuildRequest {
public BuildRequest withCleanCache(boolean cleanCache) {
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate,
this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
@ -223,7 +227,7 @@ public class BuildRequest {
public BuildRequest withVerboseLogging(boolean verboseLogging) {
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate,
this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
@ -235,7 +239,7 @@ public class BuildRequest {
public BuildRequest withPullPolicy(PullPolicy pullPolicy) {
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate,
this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
@ -247,7 +251,7 @@ public class BuildRequest {
public BuildRequest withPublish(boolean publish) {
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate,
this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
@ -272,7 +276,7 @@ public class BuildRequest {
Assert.notNull(buildpacks, "Buildpacks must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, buildpacks, this.bindings,
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate,
this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
@ -297,7 +301,7 @@ public class BuildRequest {
Assert.notNull(bindings, "Bindings must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, bindings,
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate,
this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
@ -310,7 +314,8 @@ public class BuildRequest {
public BuildRequest withNetwork(String network) {
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
network, this.tags, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory);
network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
/**
@ -332,7 +337,21 @@ public class BuildRequest {
Assert.notNull(tags, "Tags must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, tags, this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory);
this.network, tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
/**
* Return a new {@link BuildRequest} with an updated build workspace.
* @param buildWorkspace the build workspace
* @return an updated build request
*/
public BuildRequest withBuildWorkspace(Cache buildWorkspace) {
Assert.notNull(buildWorkspace, "BuildWorkspace must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
/**
@ -344,7 +363,8 @@ public class BuildRequest {
Assert.notNull(buildCache, "BuildCache must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, buildCache, this.launchCache, this.createdDate, this.applicationDirectory);
this.network, this.tags, this.buildWorkspace, buildCache, this.launchCache, this.createdDate,
this.applicationDirectory);
}
/**
@ -356,7 +376,8 @@ public class BuildRequest {
Assert.notNull(launchCache, "LaunchCache must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildCache, launchCache, this.createdDate, this.applicationDirectory);
this.network, this.tags, this.buildWorkspace, this.buildCache, launchCache, this.createdDate,
this.applicationDirectory);
}
/**
@ -368,8 +389,8 @@ public class BuildRequest {
Assert.notNull(createdDate, "CreatedDate must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildCache, this.launchCache, parseCreatedDate(createdDate),
this.applicationDirectory);
this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache,
parseCreatedDate(createdDate), this.applicationDirectory);
}
private Instant parseCreatedDate(String createdDate) {
@ -393,7 +414,8 @@ public class BuildRequest {
Assert.notNull(applicationDirectory, "ApplicationDirectory must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildCache, this.launchCache, this.createdDate, applicationDirectory);
this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
applicationDirectory);
}
/**
@ -513,6 +535,10 @@ public class BuildRequest {
return this.tags;
}
public Cache getBuildWorkspace() {
return this.buildWorkspace;
}
/**
* Return the custom build cache that should be used by the lifecycle.
* @return the build cache

View File

@ -18,7 +18,9 @@ package org.springframework.boot.buildpack.platform.build;
import java.io.Closeable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Consumer;
import com.sun.jna.Platform;
@ -70,9 +72,9 @@ class Lifecycle implements Closeable {
private final ApiVersion platformVersion;
private final VolumeName layersVolume;
private final Cache layers;
private final VolumeName applicationVolume;
private final Cache application;
private final Cache buildCache;
@ -101,17 +103,13 @@ class Lifecycle implements Closeable {
this.builder = builder;
this.lifecycleVersion = LifecycleVersion.parse(builder.getBuilderMetadata().getLifecycle().getVersion());
this.platformVersion = getPlatformVersion(builder.getBuilderMetadata().getLifecycle());
this.layersVolume = createRandomVolumeName("pack-layers-");
this.applicationVolume = createRandomVolumeName("pack-app-");
this.layers = getLayersBindingSource(request);
this.application = getApplicationBindingSource(request);
this.buildCache = getBuildCache(request);
this.launchCache = getLaunchCache(request);
this.applicationDirectory = getApplicationDirectory(request);
}
protected VolumeName createRandomVolumeName(String prefix) {
return VolumeName.random(prefix);
}
private Cache getBuildCache(BuildRequest request) {
if (request.getBuildCache() != null) {
return request.getBuildCache();
@ -130,11 +128,6 @@ class Lifecycle implements Closeable {
return (request.getApplicationDirectory() != null) ? request.getApplicationDirectory() : Directory.APPLICATION;
}
private Cache createVolumeCache(BuildRequest request, String suffix) {
return Cache.volume(
VolumeName.basedOn(request.getName(), ImageReference::toLegacyString, "pack-cache-", "." + suffix, 6));
}
private ApiVersion getPlatformVersion(BuilderMetadata.Lifecycle lifecycle) {
if (lifecycle.getApis().getPlatform() != null) {
String[] supportedVersions = lifecycle.getApis().getPlatform();
@ -153,12 +146,7 @@ class Lifecycle implements Closeable {
this.executed = true;
this.log.executingLifecycle(this.request, this.lifecycleVersion, this.buildCache);
if (this.request.isCleanCache()) {
if (this.buildCache.getVolume() != null) {
deleteVolume(this.buildCache.getVolume().getVolumeName());
}
if (this.buildCache.getBind() != null) {
deleteBind(this.buildCache.getBind().getSource());
}
deleteCache(this.buildCache);
}
run(createPhase());
this.log.executedLifecycle(this.request);
@ -183,8 +171,8 @@ class Lifecycle implements Closeable {
phase.withArgs("-process-type=web");
}
phase.withArgs(this.request.getName());
phase.withBinding(Binding.from(this.layersVolume, Directory.LAYERS));
phase.withBinding(Binding.from(this.applicationVolume, this.applicationDirectory));
phase.withBinding(Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS));
phase.withBinding(Binding.from(getCacheBindingSource(this.application), this.applicationDirectory));
phase.withBinding(Binding.from(getCacheBindingSource(this.buildCache), Directory.CACHE));
phase.withBinding(Binding.from(getCacheBindingSource(this.launchCache), Directory.LAUNCH_CACHE));
if (this.request.getBindings() != null) {
@ -200,10 +188,42 @@ class Lifecycle implements Closeable {
return phase;
}
private Cache getLayersBindingSource(BuildRequest request) {
if (request.getBuildWorkspace() != null) {
return getBuildWorkspaceBindingSource(request.getBuildWorkspace(), "layers");
}
return createVolumeCache("pack-layers-");
}
private Cache getApplicationBindingSource(BuildRequest request) {
if (request.getBuildWorkspace() != null) {
return getBuildWorkspaceBindingSource(request.getBuildWorkspace(), "app");
}
return createVolumeCache("pack-app-");
}
private Cache getBuildWorkspaceBindingSource(Cache buildWorkspace, String suffix) {
return (buildWorkspace.getVolume() != null) ? Cache.volume(buildWorkspace.getVolume().getName() + "-" + suffix)
: Cache.bind(buildWorkspace.getBind().getSource() + "-" + suffix);
}
private String getCacheBindingSource(Cache cache) {
return (cache.getVolume() != null) ? cache.getVolume().getName() : cache.getBind().getSource();
}
private Cache createVolumeCache(String prefix) {
return Cache.volume(createRandomVolumeName(prefix));
}
private Cache createVolumeCache(BuildRequest request, String suffix) {
return Cache.volume(
VolumeName.basedOn(request.getName(), ImageReference::toLegacyString, "pack-cache-", "." + suffix, 6));
}
protected VolumeName createRandomVolumeName(String prefix) {
return VolumeName.random(prefix);
}
private void configureDaemonAccess(Phase phase) {
if (this.dockerHost != null) {
if (this.dockerHost.isRemote()) {
@ -255,6 +275,9 @@ class Lifecycle implements Closeable {
return this.docker.container().create(config);
}
try {
if (this.application.getBind() != null) {
Files.createDirectories(Path.of(this.application.getBind().getSource()));
}
TarArchive applicationContent = this.request.getApplicationContent(this.builder.getBuildOwner());
return this.docker.container()
.create(config, ContainerContent.of(applicationContent, this.applicationDirectory));
@ -266,8 +289,17 @@ class Lifecycle implements Closeable {
@Override
public void close() throws IOException {
deleteVolume(this.layersVolume);
deleteVolume(this.applicationVolume);
deleteCache(this.layers);
deleteCache(this.application);
}
private void deleteCache(Cache cache) throws IOException {
if (cache.getVolume() != null) {
deleteVolume(cache.getVolume().getVolumeName());
}
if (cache.getBind() != null) {
deleteBind(cache.getBind().getSource());
}
}
private void deleteVolume(VolumeName name) throws IOException {

View File

@ -233,6 +233,22 @@ class BuildRequestTests {
.withMessage("Tags must not be null");
}
@Test
void withBuildWorkspaceVolumeAddsWorkspace() throws IOException {
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"));
BuildRequest withWorkspace = request.withBuildWorkspace(Cache.volume("build-workspace"));
assertThat(request.getBuildWorkspace()).isNull();
assertThat(withWorkspace.getBuildWorkspace()).isEqualTo(Cache.volume("build-workspace"));
}
@Test
void withBuildWorkspaceBindAddsWorkspace() throws IOException {
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"));
BuildRequest withWorkspace = request.withBuildWorkspace(Cache.bind("/tmp/build-workspace"));
assertThat(request.getBuildWorkspace()).isNull();
assertThat(withWorkspace.getBuildWorkspace()).isEqualTo(Cache.bind("/tmp/build-workspace"));
}
@Test
void withBuildVolumeCacheAddsCache() throws IOException {
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"));

View File

@ -211,7 +211,8 @@ class LifecycleTests {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
BuildRequest request = getTestRequest().withBuildCache(Cache.volume("build-volume"))
BuildRequest request = getTestRequest().withBuildWorkspace(Cache.volume("work-volume"))
.withBuildCache(Cache.volume("build-volume"))
.withLaunchCache(Cache.volume("launch-volume"));
createLifecycle(request).execute();
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-cache-volumes.json"));
@ -223,7 +224,8 @@ class LifecycleTests {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
BuildRequest request = getTestRequest().withBuildCache(Cache.bind("/tmp/build-cache"))
BuildRequest request = getTestRequest().withBuildWorkspace(Cache.bind("/tmp/work"))
.withBuildCache(Cache.bind("/tmp/build-cache"))
.withLaunchCache(Cache.bind("/tmp/launch-cache"));
createLifecycle(request).execute();
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-cache-bind-mounts.json"));

View File

@ -27,8 +27,8 @@
"HostConfig": {
"Binds": [
"/var/run/docker.sock:/var/run/docker.sock",
"pack-layers-aaaaaaaaaa:/layers",
"pack-app-aaaaaaaaaa:/workspace",
"/tmp/work-layers:/layers",
"/tmp/work-app:/workspace",
"/tmp/build-cache:/cache",
"/tmp/launch-cache:/launch-cache"
],

View File

@ -27,8 +27,8 @@
"HostConfig": {
"Binds": [
"/var/run/docker.sock:/var/run/docker.sock",
"pack-layers-aaaaaaaaaa:/layers",
"pack-app-aaaaaaaaaa:/workspace",
"work-volume-layers:/layers",
"work-volume-app:/workspace",
"build-volume:/cache",
"launch-volume:/launch-cache"
],

View File

@ -193,14 +193,22 @@ The value supplied will be passed unvalidated to Docker when creating the builde
The values provided to the `tags` option should be full image references in the form of `[image name]:[tag]` or `[repository]/[image name]:[tag]`.
|
| `buildWorkspace`
|
| A temporary workspace that will be used by the builder and buildpacks to store files during image building.
The value can be a named volume or a bind mount location.
| A named volume in the Docker daemon, with a name derived from the image name.
| `buildCache`
|
| A cache containing layers created by buildpacks and used by the image building process.
The value can be a named volume or a bind mount location.
| 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.
The value can be a named volume or a bind mount location.
| A named volume in the Docker daemon, with a name derived from the image name.
| `createdDate`
@ -420,7 +428,7 @@ The publish option can be specified on the command line as well, as shown in thi
----
[[build-image.examples.caches]]
=== Builder Cache Configuration
=== Builder Cache and Workspace 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.
@ -440,7 +448,10 @@ 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:
Builders and buildpacks need a location to store temporary files during image building.
By default, this temporary build workspace is stored in a named volume.
The caches and the build workspace 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

View File

@ -9,6 +9,11 @@ tasks.named("bootJar") {
// tag::caches[]
tasks.named("bootBuildImage") {
buildWorkspace {
bind {
source = "/tmp/cache-${rootProject.name}.work"
}
}
buildCache {
bind {
source = "/tmp/cache-${rootProject.name}.build"
@ -24,6 +29,7 @@ tasks.named("bootBuildImage") {
tasks.register("bootBuildImageCaches") {
doFirst {
bootBuildImage.buildWorkspace.asCache().with { print "buildWorkspace=$source" }
bootBuildImage.buildCache.asCache().with { println "buildCache=$source" }
bootBuildImage.launchCache.asCache().with { println "launchCache=$source" }
}

View File

@ -7,6 +7,11 @@ plugins {
// tag::caches[]
tasks.named<BootBuildImage>("bootBuildImage") {
buildWorkspace {
bind {
source.set("/tmp/cache-${rootProject.name}.work")
}
}
buildCache {
bind {
source.set("/tmp/cache-${rootProject.name}.build")
@ -22,6 +27,7 @@ tasks.named<BootBuildImage>("bootBuildImage") {
tasks.register("bootBuildImageCaches") {
doFirst {
println("buildWorkspace=" + tasks.getByName<BootBuildImage>("bootBuildImage").buildWorkspace.asCache().bind.source)
println("buildCache=" + tasks.getByName<BootBuildImage>("bootBuildImage").buildCache.asCache().bind.source)
println("launchCache=" + tasks.getByName<BootBuildImage>("bootBuildImage").launchCache.asCache().bind.source)
}

View File

@ -69,6 +69,8 @@ public abstract class BootBuildImage extends DefaultTask {
private final String projectName;
private final CacheSpec buildWorkspace;
private final CacheSpec buildCache;
private final CacheSpec launchCache;
@ -91,6 +93,7 @@ public abstract class BootBuildImage extends DefaultTask {
getCleanCache().convention(false);
getVerboseLogging().convention(false);
getPublish().convention(false);
this.buildWorkspace = getProject().getObjects().newInstance(CacheSpec.class);
this.buildCache = getProject().getObjects().newInstance(CacheSpec.class);
this.launchCache = getProject().getObjects().newInstance(CacheSpec.class);
this.docker = getProject().getObjects().newInstance(DockerSpec.class);
@ -222,6 +225,25 @@ public abstract class BootBuildImage extends DefaultTask {
@Option(option = "network", description = "Connect detect and build containers to network")
public abstract Property<String> getNetwork();
/**
* Returns the build temporary workspace that will be used when building the image.
* @return the cache
*/
@Nested
@Optional
public CacheSpec getBuildWorkspace() {
return this.buildWorkspace;
}
/**
* Customizes the {@link CacheSpec} for the build temporary workspace using the given
* {@code action}.
* @param action the action
*/
public void buildWorkspace(Action<CacheSpec> action) {
action.execute(this.buildWorkspace);
}
/**
* Returns the build cache that will be used when building the image.
* @return the cache
@ -400,6 +422,9 @@ public abstract class BootBuildImage extends DefaultTask {
}
private BuildRequest customizeCaches(BuildRequest request) {
if (this.buildWorkspace.asCache() != null) {
request = request.withBuildWorkspace((this.buildWorkspace.asCache()));
}
if (this.buildCache.asCache() != null) {
request = request.withBuildCache(this.buildCache.asCache());
}

View File

@ -343,7 +343,8 @@ class PackagingDocumentationTests {
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")
assertThat(result.getOutput()).containsPattern("buildWorkspace=/tmp/cache-gradle-[\\d]+.work")
.containsPattern("buildCache=/tmp/cache-gradle-[\\d]+.build")
.containsPattern("launchCache=/tmp/cache-gradle-[\\d]+.launch");
}

View File

@ -11,6 +11,11 @@ java {
bootBuildImage {
builder = "projects.registry.vmware.com/springboot/spring-boot-cnb-builder:0.0.2"
pullPolicy = "IF_NOT_PRESENT"
buildWorkspace {
bind {
source = System.getProperty('java.io.tmpdir') + "/junit-image-pack-${rootProject.name}-work"
}
}
buildCache {
bind {
source = System.getProperty('java.io.tmpdir') + "/junit-image-cache-${rootProject.name}-build"

View File

@ -11,6 +11,11 @@ java {
bootBuildImage {
builder = "projects.registry.vmware.com/springboot/spring-boot-cnb-builder:0.0.2"
pullPolicy = "IF_NOT_PRESENT"
buildWorkspace {
volume {
name = "pack-${rootProject.name}.work"
}
}
buildCache {
volume {
name = "cache-${rootProject.name}.build"

View File

@ -202,12 +202,19 @@ The value supplied will be passed unvalidated to Docker when creating the builde
The values provided to the `tags` option should be full image references in the form of `[image name]:[tag]` or `[repository]/[image name]:[tag]`.
|
| `buildWorkspace`
| A temporary workspace that will be used by the builder and buildpacks to store files during image building.
The value can be a named volume or a bind mount location.
| A named volume in the Docker daemon, with a name derived from the image name.
| `buildCache`
| A cache containing layers created by buildpacks and used by the image building process.
The value can be a named volume or a bind mount location.
| 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.
The value can be a named volume or a bind mount location.
| A named volume in the Docker daemon, with a name derived from the image name.
| `createdDate` +
@ -403,7 +410,7 @@ include::../maven/packaging-oci-image/docker-pom-authentication-command-line.xml
----
[[build-image.examples.caches]]
=== Builder Cache Configuration
=== Builder Cache and Workspace 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.
@ -416,7 +423,10 @@ 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:
Builders and buildpacks need a location to store temporary files during image building.
By default, this temporary build workspace is stored in a named volume.
The caches and the build workspace 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]
----

View File

@ -8,6 +8,11 @@
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<buildWorkspace>
<bind>
<source>/tmp/cache-${project.artifactId}.work</source>
</bind>
</buildWorkspace>
<buildCache>
<bind>
<source>/tmp/cache-${project.artifactId}.build</source>

View File

@ -24,6 +24,11 @@
<configuration>
<image>
<builder>projects.registry.vmware.com/springboot/spring-boot-cnb-builder:0.0.2</builder>
<buildWorkspace>
<bind>
<source>${java.io.tmpdir}/junit-image-cache-${test-build-id}-work</source>
</bind>
</buildWorkspace>
<buildCache>
<bind>
<source>${java.io.tmpdir}/junit-image-cache-${test-build-id}-build</source>

View File

@ -24,6 +24,11 @@
<configuration>
<image>
<builder>projects.registry.vmware.com/springboot/spring-boot-cnb-builder:0.0.2</builder>
<buildWorkspace>
<volume>
<name>cache-${test-build-id}.work</name>
</volume>
</buildWorkspace>
<buildCache>
<volume>
<name>cache-${test-build-id}.build</name>

View File

@ -69,6 +69,8 @@ public class Image {
List<String> tags;
CacheInfo buildWorkspace;
CacheInfo buildCache;
CacheInfo launchCache;
@ -243,6 +245,9 @@ public class Image {
if (!CollectionUtils.isEmpty(this.tags)) {
request = request.withTags(this.tags.stream().map(ImageReference::of).toList());
}
if (this.buildWorkspace != null) {
request = request.withBuildWorkspace(this.buildWorkspace.asCache());
}
if (this.buildCache != null) {
request = request.withBuildCache(this.buildCache.asCache());
}

View File

@ -170,6 +170,14 @@ class ImageTests {
ImageReference.of("example.com/my-app:0.0.1-SNAPSHOT"), ImageReference.of("example.com/my-app:latest"));
}
@Test
void getBuildRequestWhenHasBuildWorkspaceVolumeUsesWorkspace() {
Image image = new Image();
image.buildWorkspace = CacheInfo.fromVolume(new VolumeCacheInfo("build-work-vol"));
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getBuildWorkspace()).isEqualTo(Cache.volume("build-work-vol"));
}
@Test
void getBuildRequestWhenHasBuildCacheVolumeUsesCache() {
Image image = new Image();
@ -186,6 +194,14 @@ class ImageTests {
assertThat(request.getLaunchCache()).isEqualTo(Cache.volume("launch-cache-vol"));
}
@Test
void getBuildRequestWhenHasBuildWorkspaceBindUsesWorkspace() {
Image image = new Image();
image.buildWorkspace = CacheInfo.fromBind(new BindCacheInfo("build-work-dir"));
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getBuildWorkspace()).isEqualTo(Cache.bind("build-work-dir"));
}
@Test
void getBuildRequestWhenHasBuildCacheBindUsesCache() {
Image image = new Image();