Add bindings option for image building

This commit adds configuration to the Maven and Gradle plugins to
allow a list of volume mount bindings to be provided to the image
building goal and task. This enables service bindings to be mounted
in the builder image that are recognized by buildpacks to support
custom certificates, build tool configuration, APM integration, and
other buildpack features.

Fixes gh-23518
This commit is contained in:
Scott Frederick
2021-02-22 13:10:54 -06:00
parent 8cb24a426d
commit 89555a8745
24 changed files with 546 additions and 66 deletions

View File

@@ -157,13 +157,29 @@ a|Buildpacks that the builder should use when building the image.
Only the specified buildpacks will be used, overriding the default buildpacks included in the builder.
Buildpack references must be in one of the following forms:
* Buildpack in the builder - [urn:cnb:builder:]<buildpack id>[@<version>]
* Buildpack in a directory on the file system - [file://]<path>
* Buildpack in a gzipped tar (.tgz) file on the file system - [file://]<path>/<file name>
* Buildpack in an OCI image - [docker://]<host>/<repo>[:<tag>][@<digest>]
* Buildpack in the builder - `[urn:cnb:builder:]<buildpack ID>[@<version>]`
* Buildpack in a directory on the file system - `[file://]<path>`
* Buildpack in a gzipped tar (.tgz) file on the file system - `[file://]<path>/<file name>`
* Buildpack in an OCI image - `[docker://]<host>/<repo>[:<tag>][@<digest>]`
|
| None, indicating the builder should use the buildpacks included in it.
| `bindings`
a|https://docs.docker.com/storage/bind-mounts/[Volume bind mounts] that should be mounted to the builder container when building the image.
The bindings will be passed unparsed and unvalidated to Docker when creating the builder container.
Bindings must be in one of the following forms:
* `<host source path>:<container destination path>[:<options>]`
* `<host volume name>:<container destination path>[:<options>]`
Where `<options>` can contain:
* `ro` to mount the volume as read-only in the container
* `rw` to mount the volume as readable and writable in the container
* `volume-opt=key=value` to specify key-value pairs consisting of an option name and its value
|
|
| `cleanCache`
| Whether to clean the cache before building.
| `spring-boot.build-image.cleanCache`

View File

@@ -168,6 +168,15 @@ public class BuildImageTests extends AbstractArchiveIntegrationTests {
});
}
@TestTemplate
void failsWithBindingContainingInvalidCertificate(MavenBuild mavenBuild) {
mavenBuild.project("build-image-bindings").goals("package")
.systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT")
.executeAndFail((project) -> assertThat(buildLog(project)).contains("Building image")
.contains("failed to decode certificate")
.contains("/platform/bindings/ca-certificates/test.crt"));
}
@TestTemplate
void failsWhenPublishWithoutPublishRegistryConfigured(MavenBuild mavenBuild) {
mavenBuild.project("build-image").goals("package").systemProperty("spring-boot.build-image.publish", "true")

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>build-image-bindings</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>@java.version@</maven.compiler.source>
<maven.compiler.target>@java.version@</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>build-image</goal>
</goals>
<configuration>
<image>
<bindings>
<binding>${basedir}/bindings/ca-certificates:/platform/bindings/ca-certificates</binding>
</bindings>
</image>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.test;
public class SampleApplication {
public static void main(String[] args) throws Exception {
System.out.println("Launched");
synchronized(args) {
args.wait(); // Prevent exit
}
}
}

View File

@@ -26,6 +26,7 @@ import org.apache.maven.artifact.Artifact;
import org.springframework.boot.buildpack.platform.build.BuildRequest;
import org.springframework.boot.buildpack.platform.build.BuildpackReference;
import org.springframework.boot.buildpack.platform.build.PullPolicy;
import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.ImageName;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.io.Owner;
@@ -59,6 +60,8 @@ public class Image {
List<String> buildpacks;
List<String> bindings;
/**
* The name of the created image.
* @return the image name
@@ -183,6 +186,9 @@ public class Image {
request = request
.withBuildpacks(this.buildpacks.stream().map(BuildpackReference::of).collect(Collectors.toList()));
}
if (this.bindings != null && !this.bindings.isEmpty()) {
request = request.withBindings(this.bindings.stream().map(Binding::of).collect(Collectors.toList()));
}
return request;
}

View File

@@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.buildpack.platform.build.BuildRequest;
import org.springframework.boot.buildpack.platform.build.BuildpackReference;
import org.springframework.boot.buildpack.platform.build.PullPolicy;
import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.io.Owner;
import org.springframework.boot.buildpack.platform.io.TarArchive;
@@ -68,6 +69,7 @@ class ImageTests {
assertThat(request.isVerboseLogging()).isFalse();
assertThat(request.getPullPolicy()).isEqualTo(PullPolicy.ALWAYS);
assertThat(request.getBuildpacks()).isEmpty();
assertThat(request.getBindings()).isEmpty();
}
@Test
@@ -135,6 +137,15 @@ class ImageTests {
BuildpackReference.of("example/buildpack2@0.0.2"));
}
@Test
void getBuildRequestWhenHasBindingsUsesBindings() {
Image image = new Image();
image.bindings = Arrays.asList("host-src:container-dest:ro", "volume-name:container-dest:rw");
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getBindings()).containsExactly(Binding.of("host-src:container-dest:ro"),
Binding.of("volume-name:container-dest:rw"));
}
private Artifact createArtifact() {
return new DefaultArtifact("com.example", "my-app", VersionRange.createFromVersion("0.0.1-SNAPSHOT"), "compile",
"jar", null, new DefaultArtifactHandler());