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; }