From dc3634628507fcf9e2a842c8d4318bdb974f2351 Mon Sep 17 00:00:00 2001 From: Julian Liebig Date: Mon, 11 Oct 2021 17:59:45 +0200 Subject: [PATCH] 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 --- .../platform/build/BuildRequest.java | 73 +++++++++++++++---- .../buildpack/platform/build/Lifecycle.java | 10 ++- .../docs/asciidoc/packaging-oci-image.adoc | 5 ++ .../boot-build-image-cacheVolumeNames.gradle | 23 ++++++ ...ot-build-image-cacheVolumeNames.gradle.kts | 21 ++++++ .../gradle/tasks/bundling/BootBuildImage.java | 46 ++++++++++++ .../docs/asciidoc/packaging-oci-image.adoc | 4 + .../org/springframework/boot/maven/Image.java | 6 ++ 8 files changed, 171 insertions(+), 17 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-cacheVolumeNames.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-cacheVolumeNames.gradle.kts diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java index 0d637a79eb..78c1547a06 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java @@ -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 tags; + private final Map cacheVolumeNames; + BuildRequest(ImageReference name, Function 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 applicationContent, ImageReference builder, ImageReference runImage, Creator creator, Map env, boolean cleanCache, boolean verboseLogging, PullPolicy pullPolicy, boolean publish, List buildpacks, - List bindings, String network, List tags) { + List bindings, String network, List tags, Map 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 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 entries) { + Assert.notNull(entries, "Entries must not be null"); + Assert.state(!entries.isEmpty(), "Entries must not be empty"); + BuildRequest request = null; + for (Map.Entry 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 getCacheVolumeNames() { + return this.cacheVolumeNames; + } + /** * Factory method to create a new {@link BuildRequest} from a JAR file. * @param jarFile the source jar file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java index ae372bc71e..39474fba3c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java @@ -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) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc index baa70ab7da..6e58839a1e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc @@ -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. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-cacheVolumeNames.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-cacheVolumeNames.gradle new file mode 100644 index 0000000000..757bad180d --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-cacheVolumeNames.gradle @@ -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" } + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-cacheVolumeNames.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-cacheVolumeNames.gradle.kts new file mode 100644 index 0000000000..49d0da7d1f --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/packaging/boot-build-image-cacheVolumeNames.gradle.kts @@ -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") { + 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").cacheVolumeNames) { + print(type + "=" + name) + } + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java index b986dd6ff1..f800aa3646 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java @@ -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 tags; + private Map 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 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 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 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() + ".*"; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc index f830694f34..3ab524bffe 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc @@ -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. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Image.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Image.java index b2f7692d40..c140d70d59 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Image.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Image.java @@ -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 tags; + Map 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; }