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:
@@ -136,12 +136,28 @@ 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`
|
||||
| `--cleanCache`
|
||||
| Whether to clean the cache before building.
|
||||
@@ -295,7 +311,7 @@ A buildpack located in a CNB Builder (version may be omitted if there is only on
|
||||
A path to a directory containing buildpack content (not supported on Windows):
|
||||
|
||||
* `\file:///path/to/buildpack/`
|
||||
* `/path/to/buildpack/`BootBuildImageIntegrationTests
|
||||
* `/path/to/buildpack/`
|
||||
|
||||
A path to a gzipped tar file containing buildpack content:
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.springframework.boot.buildpack.platform.build.BuildpackReference;
|
||||
import org.springframework.boot.buildpack.platform.build.Creator;
|
||||
import org.springframework.boot.buildpack.platform.build.PullPolicy;
|
||||
import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException;
|
||||
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.ZipFileTarArchive;
|
||||
@@ -87,9 +88,11 @@ public class BootBuildImage extends DefaultTask {
|
||||
|
||||
private boolean publish;
|
||||
|
||||
private ListProperty<String> buildpacks;
|
||||
private final ListProperty<String> buildpacks;
|
||||
|
||||
private DockerSpec docker = new DockerSpec();
|
||||
private final ListProperty<String> bindings;
|
||||
|
||||
private final DockerSpec docker = new DockerSpec();
|
||||
|
||||
public BootBuildImage() {
|
||||
this.jar = getProject().getObjects().fileProperty();
|
||||
@@ -99,6 +102,7 @@ public class BootBuildImage extends DefaultTask {
|
||||
Project project = getProject();
|
||||
this.projectVersion.set(getProject().provider(() -> project.getVersion().toString()));
|
||||
this.buildpacks = getProject().getObjects().listProperty(String.class);
|
||||
this.bindings = getProject().getObjects().listProperty(String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,7 +296,7 @@ public class BootBuildImage extends DefaultTask {
|
||||
|
||||
/**
|
||||
* Returns the buildpacks that will be used when building the image.
|
||||
* @return the buildpacks
|
||||
* @return the buildpack references
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
@@ -302,7 +306,7 @@ public class BootBuildImage extends DefaultTask {
|
||||
|
||||
/**
|
||||
* Sets the buildpacks that will be used when building the image.
|
||||
* @param buildpacks the buildpacks
|
||||
* @param buildpacks the buildpack references
|
||||
*/
|
||||
public void setBuildpacks(List<String> buildpacks) {
|
||||
this.buildpacks.set(buildpacks);
|
||||
@@ -317,13 +321,51 @@ public class BootBuildImage extends DefaultTask {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds entries to the environment that will be used when building the image.
|
||||
* Adds entries to the buildpacks that will be used when building the image.
|
||||
* @param buildpacks the buildpack references
|
||||
*/
|
||||
public void buildpacks(List<String> buildpacks) {
|
||||
this.buildpacks.addAll(buildpacks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the volume bindings that will be mounted to the container when building the
|
||||
* image.
|
||||
* @return the bindings
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public List<String> getBindings() {
|
||||
return this.bindings.getOrNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the volume bindings that will be mounted to the container when building the
|
||||
* image.
|
||||
* @param bindings the bindings
|
||||
*/
|
||||
public void setBindings(List<String> bindings) {
|
||||
this.bindings.set(bindings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an entry to the volume bindings that will be mounted to the container when
|
||||
* building the image.
|
||||
* @param binding the binding
|
||||
*/
|
||||
public void binding(String binding) {
|
||||
this.bindings.add(binding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add entries to the volume bindings that will be mounted to the container when
|
||||
* building the image.
|
||||
* @param bindings the bindings
|
||||
*/
|
||||
public void bindings(List<String> bindings) {
|
||||
this.bindings.addAll(bindings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Docker configuration the builder will use.
|
||||
* @return docker configuration.
|
||||
@@ -388,6 +430,7 @@ public class BootBuildImage extends DefaultTask {
|
||||
request = customizePullPolicy(request);
|
||||
request = customizePublish(request);
|
||||
request = customizeBuildpacks(request);
|
||||
request = customizeBindings(request);
|
||||
return request;
|
||||
}
|
||||
|
||||
@@ -448,6 +491,14 @@ public class BootBuildImage extends DefaultTask {
|
||||
return request;
|
||||
}
|
||||
|
||||
private BuildRequest customizeBindings(BuildRequest request) {
|
||||
List<String> bindings = this.bindings.getOrNull();
|
||||
if (bindings != null && !bindings.isEmpty()) {
|
||||
return request.withBindings(bindings.stream().map(Binding::of).collect(Collectors.toList()));
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
private String translateTargetJavaVersion() {
|
||||
return this.targetJavaVersion.get().getMajorVersion() + ".*";
|
||||
}
|
||||
|
||||
@@ -219,6 +219,17 @@ class BootBuildImageIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void failsWithBindingContainingInvalidCertificate() throws IOException {
|
||||
writeMainClass();
|
||||
writeLongNameResource();
|
||||
writeCertificateBindingFiles();
|
||||
BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage", "--pullPolicy=IF_NOT_PRESENT");
|
||||
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.FAILED);
|
||||
assertThat(result.getOutput()).contains("failed to decode certificate")
|
||||
.contains("/platform/bindings/certificates/test.crt");
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void failsWithLaunchScript() throws IOException {
|
||||
writeMainClass();
|
||||
@@ -380,4 +391,17 @@ class BootBuildImageIntegrationTests {
|
||||
tar.closeArchiveEntry();
|
||||
}
|
||||
|
||||
private void writeCertificateBindingFiles() throws IOException {
|
||||
File bindingDir = new File(this.gradleBuild.getProjectDir(), "bindings/ca-certificates");
|
||||
bindingDir.mkdirs();
|
||||
File type = new File(bindingDir, "type");
|
||||
try (PrintWriter writer = new PrintWriter(new FileWriter(type))) {
|
||||
writer.print("ca-certificates");
|
||||
}
|
||||
File cert = new File(bindingDir, "test.crt");
|
||||
try (PrintWriter writer = new PrintWriter(new FileWriter(cert))) {
|
||||
writer.println("not a valid certificate");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.junit.jupiter.api.io.TempDir;
|
||||
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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -250,4 +251,31 @@ class BootBuildImageTests {
|
||||
BuildpackReference.of("example/buildpack1"), BuildpackReference.of("example/buildpack2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoBindingsAreConfiguredThenRequestHasNoBindings() {
|
||||
assertThat(this.buildImage.createRequest().getBindings()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenBindingsAreConfiguredThenRequestHasBindings() {
|
||||
this.buildImage.setBindings(Arrays.asList("host-src:container-dest:ro", "volume-name:container-dest:rw"));
|
||||
assertThat(this.buildImage.createRequest().getBindings())
|
||||
.containsExactly(Binding.of("host-src:container-dest:ro"), Binding.of("volume-name:container-dest:rw"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenEntriesAreAddedToBindingsThenRequestHasBindings() {
|
||||
this.buildImage.bindings(Arrays.asList("host-src:container-dest:ro", "volume-name:container-dest:rw"));
|
||||
assertThat(this.buildImage.createRequest().getBindings())
|
||||
.containsExactly(Binding.of("host-src:container-dest:ro"), Binding.of("volume-name:container-dest:rw"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenIndividualEntriesAreAddedToBindingsThenRequestHasBindings() {
|
||||
this.buildImage.binding("host-src:container-dest:ro");
|
||||
this.buildImage.binding("volume-name:container-dest:rw");
|
||||
assertThat(this.buildImage.createRequest().getBindings())
|
||||
.containsExactly(Binding.of("host-src:container-dest:ro"), Binding.of("volume-name:container-dest:rw"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
}
|
||||
|
||||
sourceCompatibility = '1.8'
|
||||
targetCompatibility = '1.8'
|
||||
|
||||
bootBuildImage {
|
||||
bindings = [ "${projectDir}/bindings/ca-certificates:/platform/bindings/certificates" ]
|
||||
}
|
||||
Reference in New Issue
Block a user