Add support for build workspace option when building images
Closes gh-37478
This commit is contained in:
		
							parent
							
								
									d8576f319e
								
							
						
					
					
						commit
						4433fcd1f2
					
				|  | @ -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 | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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")); | ||||
|  |  | |||
|  | @ -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")); | ||||
|  |  | |||
|  | @ -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" | ||||
|     ], | ||||
|  |  | |||
|  | @ -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" | ||||
|     ], | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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" } | ||||
| 	} | ||||
|  |  | |||
|  | @ -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) | ||||
| 	} | ||||
|  |  | |||
|  | @ -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()); | ||||
| 		} | ||||
|  |  | |||
|  | @ -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"); | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -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" | ||||
|  |  | |||
|  | @ -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" | ||||
|  |  | |||
|  | @ -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] | ||||
| ---- | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -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()); | ||||
| 		} | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue