Add option to customize cache volume names when building an image

This commit adds configuration to the Maven and Gradle plugins to
allow specifying the names of build and launch cache volumes provided
to the CNB builder.

See gh-28292
This commit is contained in:
Julian Liebig 2021-10-11 17:59:45 +02:00 committed by Scott Frederick
parent 9d6a0cfd24
commit dc36346285
8 changed files with 171 additions and 17 deletions

View File

@ -38,6 +38,7 @@ import org.springframework.util.Assert;
* @author Andrey Shlykov
* @author Jeroen Meijer
* @author Rafael Ceccone
* @author Julian Liebig
* @since 2.3.0
*/
public class BuildRequest {
@ -74,6 +75,8 @@ public class BuildRequest {
private final List<ImageReference> tags;
private final Map<String, String> cacheVolumeNames;
BuildRequest(ImageReference name, Function<Owner, TarArchive> applicationContent) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(applicationContent, "ApplicationContent must not be null");
@ -91,12 +94,13 @@ public class BuildRequest {
this.bindings = Collections.emptyList();
this.network = null;
this.tags = Collections.emptyList();
this.cacheVolumeNames = Collections.emptyMap();
}
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) {
List<Binding> bindings, String network, List<ImageReference> tags, Map<String, String> cacheVolumeNames) {
this.name = name;
this.applicationContent = applicationContent;
this.builder = builder;
@ -111,6 +115,7 @@ public class BuildRequest {
this.bindings = bindings;
this.network = network;
this.tags = tags;
this.cacheVolumeNames = cacheVolumeNames;
}
/**
@ -122,7 +127,7 @@ 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.buildpacks, this.bindings, this.network, this.tags, this.cacheVolumeNames);
}
/**
@ -133,7 +138,7 @@ 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.buildpacks, this.bindings, this.network, this.tags, this.cacheVolumeNames);
}
/**
@ -145,7 +150,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.network, this.tags, this.cacheVolumeNames);
}
/**
@ -161,7 +166,7 @@ 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.buildpacks, this.bindings, this.network, this.tags, this.cacheVolumeNames);
}
/**
@ -175,7 +180,7 @@ 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.publish, this.buildpacks, this.bindings, this.network, this.tags, this.cacheVolumeNames);
}
/**
@ -186,7 +191,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.network, this.tags, this.cacheVolumeNames);
}
/**
@ -197,7 +202,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.network, this.tags, this.cacheVolumeNames);
}
/**
@ -208,7 +213,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.network, this.tags, this.cacheVolumeNames);
}
/**
@ -219,7 +224,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.network, this.tags, this.cacheVolumeNames);
}
/**
@ -243,7 +248,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.network, this.tags, this.cacheVolumeNames);
}
/**
@ -267,7 +272,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.network, this.tags, this.cacheVolumeNames);
}
/**
@ -279,7 +284,7 @@ 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);
network, this.tags, this.cacheVolumeNames);
}
/**
@ -301,7 +306,39 @@ 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.network, tags, this.cacheVolumeNames);
}
/**
* Return a new {@link BuildRequest} with an additional cache volume name.
* @param type the cache volume type
* @param name the cache volume name
* @return an updated build request
*/
public BuildRequest withCacheVolumeName(String type, String name) {
Assert.hasText(type, "Type must not be empty");
Assert.state((type.equals("build") || type.equals("launch")), "Type must be either 'build' or 'launch'");
Assert.hasText(name, "Name must not be empty");
Map<String, String> cacheVolumeNames = new LinkedHashMap<>(this.cacheVolumeNames);
cacheVolumeNames.put(type, name);
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, Collections.unmodifiableMap(cacheVolumeNames));
}
/**
* Return a new {@link BuildRequest} with additional cache volume names.
* @param entries the additional cache volume names
* @return an updated build request
*/
public BuildRequest withCacheVolumeNames(Map<String, String> entries) {
Assert.notNull(entries, "Entries must not be null");
Assert.state(!entries.isEmpty(), "Entries must not be empty");
BuildRequest request = null;
for (Map.Entry<String, String> entry : entries.entrySet()) {
request = withCacheVolumeName(entry.getKey(), entry.getValue());
}
return request;
}
/**
@ -421,6 +458,14 @@ public class BuildRequest {
return this.tags;
}
/**
* Return the custom cache volume names that should be used by the lifecycle.
* @return the cache volume names
*/
public Map<String, String> getCacheVolumeNames() {
return this.cacheVolumeNames;
}
/**
* Factory method to create a new {@link BuildRequest} from a JAR file.
* @param jarFile the source jar file

View File

@ -39,6 +39,7 @@ import org.springframework.util.Assert;
* @author Phillip Webb
* @author Scott Frederick
* @author Jeroen Meijer
* @author Julian Liebig
*/
class Lifecycle implements Closeable {
@ -86,8 +87,8 @@ class Lifecycle implements Closeable {
this.platformVersion = getPlatformVersion(builder.getBuilderMetadata().getLifecycle());
this.layersVolume = createRandomVolumeName("pack-layers-");
this.applicationVolume = createRandomVolumeName("pack-app-");
this.buildCacheVolume = createCacheVolumeName(request, ".build");
this.launchCacheVolume = createCacheVolumeName(request, ".launch");
this.buildCacheVolume = createCacheVolumeName(request, "build");
this.launchCacheVolume = createCacheVolumeName(request, "launch");
}
protected VolumeName createRandomVolumeName(String prefix) {
@ -95,7 +96,10 @@ class Lifecycle implements Closeable {
}
private VolumeName createCacheVolumeName(BuildRequest request, String suffix) {
return VolumeName.basedOn(request.getName(), ImageReference::toLegacyString, "pack-cache-", suffix, 6);
if (!request.getCacheVolumeNames().isEmpty() && request.getCacheVolumeNames().containsKey(suffix)) {
return VolumeName.of(request.getCacheVolumeNames().get(suffix));
}
return VolumeName.basedOn(request.getName(), ImageReference::toLegacyString, "pack-cache-", "." + suffix, 6);
}
private ApiVersion getPlatformVersion(BuilderMetadata.Lifecycle lifecycle) {

View File

@ -181,6 +181,11 @@ The value supplied will be passed unvalidated to Docker when creating the builde
| A list of one or more additional tags to apply to the generated image.
|
| `cacheVolumeNames`
|
| Cache volume names that should be used by the builder instead of generating random names.
|
|===
NOTE: The plugin detects the target Java compatibility of the project using the JavaPlugin's `targetCompatibility` property.

View File

@ -0,0 +1,23 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{gradle-project-version}'
}
bootJar {
mainClass = 'com.example.ExampleApplication'
}
// tag::cacheVolumeNames[]
bootBuildImage {
cacheVolumeNames = [
"build": "example-build-cachevol",
"launch": "example-launch-cachevol"
]
}
// end::cacheVolumeNames[]
task bootBuildImageCacheVolumeNames {
doFirst {
bootBuildImage.cacheVolumeNames.each { type, name -> println "$type=$name" }
}
}

View File

@ -0,0 +1,21 @@
import org.springframework.boot.gradle.tasks.bundling.BootBuildImage
plugins {
java
id("org.springframework.boot") version "{gradle-project-version}"
}
// tag::cacheVolumeNames[]
tasks.getByName<BootBuildImage>("bootBuildImage") {
cacheVolumeNames = mapOf("build" to "example-build-cachevol",
"launch" to "example-launch-cachevol")
}
// end::cacheVolumeNames[]
tasks.register("bootBuildImageEnvironment") {
doFirst {
for((type, name) in tasks.getByName<BootBuildImage>("bootBuildImage").cacheVolumeNames) {
print(type + "=" + name)
}
}
}

View File

@ -60,6 +60,7 @@ import org.springframework.util.StringUtils;
* @author Scott Frederick
* @author Rafael Ceccone
* @author Jeroen Meijer
* @author Julian Liebig
* @since 2.3.0
*/
public class BootBuildImage extends DefaultTask {
@ -98,6 +99,8 @@ public class BootBuildImage extends DefaultTask {
private final ListProperty<String> tags;
private Map<String, String> cacheVolumeNames = new HashMap<>();
private final DockerSpec docker = new DockerSpec();
public BootBuildImage() {
@ -417,6 +420,41 @@ public class BootBuildImage extends DefaultTask {
this.tags.addAll(tags);
}
/**
* Returns the cache volume names that will be used when building the image.
* @return the cache volume names
*/
@Input
@Optional
public Map<String, String> getCacheVolumeNames() {
return this.cacheVolumeNames;
}
/**
* Sets the cache volume names that will be used when building the image.
* @param cacheVolumeNames the cache volume names
*/
public void setCacheVolumeNames(Map<String, String> cacheVolumeNames) {
this.cacheVolumeNames = cacheVolumeNames;
}
/**
* Add an entry to cache volume names that will be used when building the image.
* @param type the type of the entry
* @param name the name of the entry
*/
public void cacheVolumeName(String type, String name) {
this.cacheVolumeNames.put(type, name);
}
/**
* Adds entries to cache volume names that will be used when building the image.
* @param entries the entries to add to cache volume names
*/
public void cacheVolumeNames(Map<String, String> entries) {
this.cacheVolumeNames.putAll(entries);
}
/**
* Returns the network the build container will connect to.
* @return the network
@ -499,6 +537,7 @@ public class BootBuildImage extends DefaultTask {
request = customizeBuildpacks(request);
request = customizeBindings(request);
request = customizeTags(request);
request = customizeCacheVolumeNames(request);
request = request.withNetwork(this.network);
return request;
}
@ -576,6 +615,13 @@ public class BootBuildImage extends DefaultTask {
return request;
}
private BuildRequest customizeCacheVolumeNames(BuildRequest request) {
if (this.cacheVolumeNames != null && !this.cacheVolumeNames.isEmpty()) {
request = request.withCacheVolumeNames(this.cacheVolumeNames);
}
return request;
}
private String translateTargetJavaVersion() {
return this.targetJavaVersion.get().getMajorVersion() + ".*";
}

View File

@ -187,6 +187,10 @@ The value supplied will be passed unvalidated to Docker when creating the builde
| One or more additional tags to apply to the generated image.
|
| `cacheVolumeNames`
| Cache volume names that should be used by the builder instead of generating random names.
|
|===
NOTE: The plugin detects the target Java compatibility of the project using the compiler's plugin configuration or the `maven.compiler.target` property.

View File

@ -41,6 +41,7 @@ import org.springframework.util.StringUtils;
* @author Scott Frederick
* @author Jeroen Meijer
* @author Rafael Ceccone
* @author Julian Liebig
* @since 2.3.0
*/
public class Image {
@ -69,6 +70,8 @@ public class Image {
List<String> tags;
Map<String, String> cacheVolumeNames;
/**
* The name of the created image.
* @return the image name
@ -212,6 +215,9 @@ public class Image {
if (!CollectionUtils.isEmpty(this.tags)) {
request = request.withTags(this.tags.stream().map(ImageReference::of).collect(Collectors.toList()));
}
if (this.cacheVolumeNames != null && !this.cacheVolumeNames.isEmpty()) {
request = request.withCacheVolumeNames(this.cacheVolumeNames);
}
return request;
}