Add imagePlatform option for image building

An `imagePlatform` option for the Maven and Gradle image-building
goal/task can be used to specify the os/architecture of any
builder, run, and buildpack images that are pulled during image
building.

Closes gh-40944
This commit is contained in:
Scott Frederick
2024-07-30 12:54:14 -05:00
parent 67bae524b2
commit dfab18c965
45 changed files with 1373 additions and 275 deletions

View File

@@ -71,7 +71,7 @@ class BuildImageRegistryIntegrationTests extends AbstractArchiveIntegrationTests
.contains("Pushed image '" + imageName + ":latest" + "'");
ImageReference imageReference = ImageReference.of(imageName);
DockerApi.ImageApi imageApi = new DockerApi().image();
Image pulledImage = imageApi.pull(imageReference, UpdateListener.none());
Image pulledImage = imageApi.pull(imageReference, null, UpdateListener.none());
assertThat(pulledImage).isNotNull();
imageApi.remove(imageReference, false);
});

View File

@@ -31,13 +31,14 @@ import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.buildpack.platform.docker.DockerApi;
import org.springframework.boot.buildpack.platform.docker.DockerApi.ImageApi;
import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi;
import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException;
import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageName;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.docker.type.VolumeName;
import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable;
import org.springframework.boot.testsupport.junit.DisabledOnOs;
import org.springframework.util.FileSystemUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -51,8 +52,6 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
@ExtendWith(MavenBuildExtension.class)
@DisabledIfDockerUnavailable
@DisabledOnOs(os = { OS.LINUX, OS.MAC }, architecture = "aarch64",
disabledReason = "The builder image has no ARM support")
class BuildImageTests extends AbstractArchiveIntegrationTests {
@TestTemplate
@@ -460,7 +459,6 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
mavenBuild.project("dockerTest", "build-image-created-date")
.goals("package")
.systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT")
.systemProperty("test-build-id", testBuildId)
.execute((project) -> {
assertThat(buildLog(project)).contains("Building image")
.contains("docker.io/library/build-image-created-date:0.0.1.BUILD-SNAPSHOT")
@@ -474,11 +472,9 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
@TestTemplate
void whenBuildImageIsInvokedWithCurrentCreatedDate(MavenBuild mavenBuild) {
String testBuildId = randomString();
mavenBuild.project("dockerTest", "build-image-current-created-date")
.goals("package")
.systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT")
.systemProperty("test-build-id", testBuildId)
.execute((project) -> {
assertThat(buildLog(project)).contains("Building image")
.contains("docker.io/library/build-image-current-created-date:0.0.1.BUILD-SNAPSHOT")
@@ -497,11 +493,9 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
@TestTemplate
void whenBuildImageIsInvokedWithApplicationDirectory(MavenBuild mavenBuild) {
String testBuildId = randomString();
mavenBuild.project("dockerTest", "build-image-app-dir")
.goals("package")
.systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT")
.systemProperty("test-build-id", testBuildId)
.execute((project) -> {
assertThat(buildLog(project)).contains("Building image")
.contains("docker.io/library/build-image-app-dir:0.0.1.BUILD-SNAPSHOT")
@@ -512,11 +506,9 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
@TestTemplate
void whenBuildImageIsInvokedWithEmptySecurityOptions(MavenBuild mavenBuild) {
String testBuildId = randomString();
mavenBuild.project("dockerTest", "build-image-security-opts")
.goals("package")
.systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT")
.systemProperty("test-build-id", testBuildId)
.execute((project) -> {
assertThat(buildLog(project)).contains("Building image")
.contains("docker.io/library/build-image-security-opts:0.0.1.BUILD-SNAPSHOT")
@@ -525,6 +517,49 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
});
}
@TestTemplate
@EnabledOnOs(value = { OS.LINUX, OS.MAC }, architectures = "aarch64",
disabledReason = "Lifecycle will only run on ARM architecture")
void whenBuildImageIsInvokedOnLinuxArmWithImagePlatformLinuxArm(MavenBuild mavenBuild) throws IOException {
String builderImage = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1";
String runImage = "docker.io/paketobuildpacks/run-jammy-tiny:latest";
String buildpackImage = "ghcr.io/spring-io/spring-boot-test-info:0.0.1";
removeImages(builderImage, runImage, buildpackImage);
mavenBuild.project("dockerTest", "build-image-platform-linux-arm").goals("package").execute((project) -> {
File jar = new File(project, "target/build-image-platform-linux-arm-0.0.1.BUILD-SNAPSHOT.jar");
assertThat(jar).isFile();
assertThat(buildLog(project)).contains("Building image")
.contains("docker.io/library/build-image-platform-linux-arm:0.0.1.BUILD-SNAPSHOT")
.contains("Pulling builder image '" + builderImage + "' for platform 'linux/arm64'")
.contains("Pulling run image '" + runImage + "' for platform 'linux/arm64'")
.contains("Pulling buildpack image '" + buildpackImage + "' for platform 'linux/arm64'")
.contains("---> Test Info buildpack building")
.contains("---> Test Info buildpack done")
.contains("Successfully built image");
removeImage("docker.io/library/build-image-platform-linux-arm", "0.0.1.BUILD-SNAPSHOT");
});
removeImages(builderImage, runImage, buildpackImage);
}
@TestTemplate
@EnabledOnOs(value = { OS.LINUX, OS.MAC }, architectures = "amd64",
disabledReason = "The expected failure condition will not fail on ARM architectures")
void failsWhenBuildImageIsInvokedOnLinuxAmdWithImagePlatformLinuxArm(MavenBuild mavenBuild) throws IOException {
String builderImage = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1";
String runImage = "docker.io/paketobuildpacks/run-jammy-tiny:latest";
String buildpackImage = "ghcr.io/spring-io/spring-boot-test-info:0.0.1";
removeImages(buildpackImage, runImage, buildpackImage);
mavenBuild.project("dockerTest", "build-image-platform-linux-arm")
.goals("package")
.executeAndFail((project) -> assertThat(buildLog(project)).contains("Building image")
.contains("docker.io/library/build-image-platform-linux-arm:0.0.1.BUILD-SNAPSHOT")
.contains("Pulling builder image '" + builderImage + "' for platform 'linux/arm64'")
.contains("Pulling run image '" + runImage + "' for platform 'linux/arm64'")
.contains("Pulling buildpack image '" + buildpackImage + "' for platform 'linux/arm64'")
.contains("exec format error"));
removeImages(builderImage, runImage, buildpackImage);
}
@TestTemplate
void failsWhenBuildImageIsInvokedOnMultiModuleProjectWithBuildImageGoal(MavenBuild mavenBuild) {
mavenBuild.project("dockerTest", "build-image-multi-module")
@@ -582,6 +617,18 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
}
}
private void removeImages(String... names) throws IOException {
ImageApi imageApi = new DockerApi().image();
for (String name : names) {
try {
imageApi.remove(ImageReference.of(name), false);
}
catch (DockerEngineException ex) {
// ignore image remove failures
}
}
}
private void removeImage(String name, String version) {
ImageReference imageReference = ImageReference.of(ImageName.of(name), version);
try {

View File

@@ -0,0 +1,39 @@
<?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-platform-linux-arm</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-no-fork</goal>
</goals>
<configuration>
<image>
<builder>ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1</builder>
<runImage>paketobuildpacks/run-jammy-tiny</runImage>
<buildpacks>
<buildpack>ghcr.io/spring-io/spring-boot-test-info:0.0.1</buildpack>
</buildpacks>
<imagePlatform>linux/arm64</imagePlatform>
</image>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2012-2024 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
}
}
}