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"
],