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:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* 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.
|
||||
@@ -20,6 +20,8 @@ import java.util.stream.IntStream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ApiVersion;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
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.ImagePlatform;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||
import org.springframework.boot.buildpack.platform.io.Owner;
|
||||
import org.springframework.boot.buildpack.platform.io.TarArchive;
|
||||
@@ -392,6 +393,13 @@ class BuildRequestTests {
|
||||
assertThat(withAppDir.getSecurityOptions()).containsExactly("label=user:USER", "label=role:ROLE");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withPlatformSetsPlatform() throws Exception {
|
||||
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"));
|
||||
BuildRequest withAppDir = request.withImagePlatform("linux/arm64");
|
||||
assertThat(withAppDir.getImagePlatform()).isEqualTo(ImagePlatform.of("linux/arm64"));
|
||||
}
|
||||
|
||||
private void hasExpectedJarContent(TarArchive archive) {
|
||||
try {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.springframework.boot.buildpack.platform.docker.type.ContainerReferenc
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ContainerStatus;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.Image;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImageArchive;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||
import org.springframework.boot.buildpack.platform.io.TarArchive;
|
||||
|
||||
@@ -86,9 +87,12 @@ class BuilderTests {
|
||||
DockerApi docker = mockDockerApi();
|
||||
Image builderImage = loadImage("image.json");
|
||||
Image runImage = loadImage("run-image.json");
|
||||
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
|
||||
any(), isNull()))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, null);
|
||||
BuildRequest request = getTestRequest();
|
||||
@@ -97,9 +101,10 @@ class BuilderTests {
|
||||
assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
|
||||
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
|
||||
then(docker.image()).should()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull());
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull());
|
||||
then(docker.image()).should()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull());
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
|
||||
any(), isNull());
|
||||
then(docker.image()).should().load(archive.capture(), any());
|
||||
then(docker.image()).should().remove(archive.getValue().getTag(), true);
|
||||
then(docker.image()).shouldHaveNoMoreInteractions();
|
||||
@@ -115,12 +120,12 @@ class BuilderTests {
|
||||
.withBuilderRegistryTokenAuthentication("builder token")
|
||||
.withPublishRegistryTokenAuthentication("publish token");
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(),
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(),
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(),
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
|
||||
any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration);
|
||||
BuildRequest request = getTestRequest().withPublish(true);
|
||||
@@ -129,11 +134,11 @@ class BuilderTests {
|
||||
assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
|
||||
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
|
||||
then(docker.image()).should()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(),
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(),
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()));
|
||||
then(docker.image()).should()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(),
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()));
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
|
||||
any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()));
|
||||
then(docker.image()).should()
|
||||
.push(eq(request.getName()), any(),
|
||||
eq(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()));
|
||||
@@ -148,9 +153,12 @@ class BuilderTests {
|
||||
DockerApi docker = mockDockerApi();
|
||||
Image builderImage = loadImage("image-with-no-run-image-tag.json");
|
||||
Image runImage = loadImage("run-image.json");
|
||||
given(docker.image().pull(eq(ImageReference.of("gcr.io/paketo-buildpacks/builder:latest")), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of("gcr.io/paketo-buildpacks/builder:latest")), isNull(), any(), isNull()))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:latest")), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:latest")), eq(ImagePlatform.from(builderImage)),
|
||||
any(), isNull()))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, null);
|
||||
BuildRequest request = getTestRequest().withBuilder(ImageReference.of("gcr.io/paketo-buildpacks/builder"));
|
||||
@@ -168,12 +176,13 @@ class BuilderTests {
|
||||
DockerApi docker = mockDockerApi();
|
||||
Image builderImage = loadImage("image-with-run-image-digest.json");
|
||||
Image runImage = loadImage("run-image.json");
|
||||
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference
|
||||
.of("docker.io/cloudfoundry/run@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d")),
|
||||
any(), isNull()))
|
||||
eq(ImagePlatform.from(builderImage)), any(), isNull()))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, null);
|
||||
BuildRequest request = getTestRequest();
|
||||
@@ -191,9 +200,12 @@ class BuilderTests {
|
||||
DockerApi docker = mockDockerApi();
|
||||
Image builderImage = loadImage("image-with-empty-stack.json");
|
||||
Image runImage = loadImage("run-image.json");
|
||||
given(docker.image().pull(eq(ImageReference.of("gcr.io/paketo-buildpacks/builder:latest")), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of("gcr.io/paketo-buildpacks/builder:latest")), isNull(), any(), isNull()))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
|
||||
any(), isNull()))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, null);
|
||||
BuildRequest request = getTestRequest().withBuilder(ImageReference.of("gcr.io/paketo-buildpacks/builder"));
|
||||
@@ -211,9 +223,12 @@ class BuilderTests {
|
||||
DockerApi docker = mockDockerApi();
|
||||
Image builderImage = loadImage("image.json");
|
||||
Image runImage = loadImage("run-image.json");
|
||||
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
given(docker.image().pull(eq(ImageReference.of("example.com/custom/run:latest")), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of("example.com/custom/run:latest")), eq(ImagePlatform.from(builderImage)), any(),
|
||||
isNull()))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, null);
|
||||
BuildRequest request = getTestRequest().withRunImage(ImageReference.of("example.com/custom/run:latest"));
|
||||
@@ -231,9 +246,12 @@ class BuilderTests {
|
||||
DockerApi docker = mockDockerApi();
|
||||
Image builderImage = loadImage("image.json");
|
||||
Image runImage = loadImage("run-image.json");
|
||||
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
|
||||
any(), isNull()))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF))))
|
||||
.willReturn(builderImage);
|
||||
@@ -247,7 +265,7 @@ class BuilderTests {
|
||||
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
|
||||
then(docker.image()).should().load(archive.capture(), any());
|
||||
then(docker.image()).should().remove(archive.getValue().getTag(), true);
|
||||
then(docker.image()).should(never()).pull(any(), any());
|
||||
then(docker.image()).should(never()).pull(any(), any(), any());
|
||||
then(docker.image()).should(times(2)).inspect(any());
|
||||
}
|
||||
|
||||
@@ -257,9 +275,12 @@ class BuilderTests {
|
||||
DockerApi docker = mockDockerApi();
|
||||
Image builderImage = loadImage("image.json");
|
||||
Image runImage = loadImage("run-image.json");
|
||||
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
|
||||
any(), isNull()))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF))))
|
||||
.willReturn(builderImage);
|
||||
@@ -273,7 +294,7 @@ class BuilderTests {
|
||||
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
|
||||
then(docker.image()).should().load(archive.capture(), any());
|
||||
then(docker.image()).should().remove(archive.getValue().getTag(), true);
|
||||
then(docker.image()).should(times(2)).pull(any(), any(), isNull());
|
||||
then(docker.image()).should(times(2)).pull(any(), any(), any(), isNull());
|
||||
then(docker.image()).should(never()).inspect(any());
|
||||
}
|
||||
|
||||
@@ -283,9 +304,12 @@ class BuilderTests {
|
||||
DockerApi docker = mockDockerApi();
|
||||
Image builderImage = loadImage("image.json");
|
||||
Image runImage = loadImage("run-image.json");
|
||||
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
|
||||
any(), isNull()))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF))))
|
||||
.willThrow(
|
||||
@@ -304,7 +328,7 @@ class BuilderTests {
|
||||
then(docker.image()).should().load(archive.capture(), any());
|
||||
then(docker.image()).should().remove(archive.getValue().getTag(), true);
|
||||
then(docker.image()).should(times(2)).inspect(any());
|
||||
then(docker.image()).should(times(2)).pull(any(), any(), isNull());
|
||||
then(docker.image()).should(times(2)).pull(any(), any(), any(), isNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -313,9 +337,12 @@ class BuilderTests {
|
||||
DockerApi docker = mockDockerApi();
|
||||
Image builderImage = loadImage("image.json");
|
||||
Image runImage = loadImage("run-image.json");
|
||||
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
|
||||
any(), isNull()))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, null);
|
||||
BuildRequest request = getTestRequest().withTags(ImageReference.of("my-application:1.2.3"));
|
||||
@@ -339,12 +366,12 @@ class BuilderTests {
|
||||
.withBuilderRegistryTokenAuthentication("builder token")
|
||||
.withPublishRegistryTokenAuthentication("publish token");
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(),
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(),
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(),
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
|
||||
any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration);
|
||||
BuildRequest request = getTestRequest().withPublish(true).withTags(ImageReference.of("my-application:1.2.3"));
|
||||
@@ -354,11 +381,11 @@ class BuilderTests {
|
||||
assertThat(out.toString()).contains("Successfully created image tag 'docker.io/library/my-application:1.2.3'");
|
||||
|
||||
then(docker.image()).should()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(),
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(),
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()));
|
||||
then(docker.image()).should()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(),
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()));
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
|
||||
any(), eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()));
|
||||
then(docker.image()).should()
|
||||
.push(eq(request.getName()), any(),
|
||||
eq(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()));
|
||||
@@ -372,15 +399,46 @@ class BuilderTests {
|
||||
then(docker.image()).shouldHaveNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildInvokesBuilderWithPlatform() throws Exception {
|
||||
TestPrintStream out = new TestPrintStream();
|
||||
ImagePlatform platform = ImagePlatform.of("linux/arm64/v1");
|
||||
DockerApi docker = mockDockerApi(platform);
|
||||
Image builderImage = loadImage("image-with-platform.json");
|
||||
Image runImage = loadImage("run-image-with-platform.json");
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), eq(platform), any(), isNull()))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(platform), any(), isNull()))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, null);
|
||||
BuildRequest request = getTestRequest().withImagePlatform("linux/arm64/v1");
|
||||
builder.build(request);
|
||||
assertThat(out.toString()).contains("Running creator");
|
||||
assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
|
||||
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
|
||||
then(docker.image()).should()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), eq(platform), any(), isNull());
|
||||
then(docker.image()).should()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(platform), any(), isNull());
|
||||
then(docker.image()).should().load(archive.capture(), any());
|
||||
then(docker.image()).should().remove(archive.getValue().getTag(), true);
|
||||
then(docker.image()).shouldHaveNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildWhenStackIdDoesNotMatchThrowsException() throws Exception {
|
||||
TestPrintStream out = new TestPrintStream();
|
||||
DockerApi docker = mockDockerApi();
|
||||
Image builderImage = loadImage("image.json");
|
||||
Image runImage = loadImage("run-image-with-bad-stack.json");
|
||||
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
|
||||
any(), isNull()))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, null);
|
||||
BuildRequest request = getTestRequest();
|
||||
@@ -395,9 +453,12 @@ class BuilderTests {
|
||||
DockerApi docker = mockDockerApiLifecycleError();
|
||||
Image builderImage = loadImage("image.json");
|
||||
Image runImage = loadImage("run-image.json");
|
||||
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), isNull(), any(), isNull()))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), eq(ImagePlatform.from(builderImage)),
|
||||
any(), isNull()))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, null);
|
||||
BuildRequest request = getTestRequest();
|
||||
@@ -413,7 +474,7 @@ class BuilderTests {
|
||||
DockerConfiguration dockerConfiguration = new DockerConfiguration()
|
||||
.withBuilderRegistryTokenAuthentication("builder token");
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(),
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), any(),
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration);
|
||||
@@ -431,7 +492,7 @@ class BuilderTests {
|
||||
DockerConfiguration dockerConfiguration = new DockerConfiguration()
|
||||
.withBuilderRegistryTokenAuthentication("builder token");
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(),
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), any(),
|
||||
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration);
|
||||
@@ -447,9 +508,10 @@ class BuilderTests {
|
||||
DockerApi docker = mockDockerApiLifecycleError();
|
||||
Image builderImage = loadImage("image.json");
|
||||
Image runImage = loadImage("run-image.json");
|
||||
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
|
||||
given(docker.image()
|
||||
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), any(), isNull()))
|
||||
.willAnswer(withPulledImage(builderImage));
|
||||
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
|
||||
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), any(), isNull()))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, null);
|
||||
BuildpackReference reference = BuildpackReference.of("urn:cnb:builder:example/buildpack@1.2.3");
|
||||
@@ -460,9 +522,13 @@ class BuilderTests {
|
||||
}
|
||||
|
||||
private DockerApi mockDockerApi() throws IOException {
|
||||
return mockDockerApi(null);
|
||||
}
|
||||
|
||||
private DockerApi mockDockerApi(ImagePlatform platform) throws IOException {
|
||||
ContainerApi containerApi = mock(ContainerApi.class);
|
||||
ContainerReference reference = ContainerReference.of("container-ref");
|
||||
given(containerApi.create(any(), any())).willReturn(reference);
|
||||
given(containerApi.create(any(), eq(platform), any())).willReturn(reference);
|
||||
given(containerApi.wait(eq(reference))).willReturn(ContainerStatus.of(0, null));
|
||||
ImageApi imageApi = mock(ImageApi.class);
|
||||
VolumeApi volumeApi = mock(VolumeApi.class);
|
||||
@@ -476,7 +542,7 @@ class BuilderTests {
|
||||
private DockerApi mockDockerApiLifecycleError() throws IOException {
|
||||
ContainerApi containerApi = mock(ContainerApi.class);
|
||||
ContainerReference reference = ContainerReference.of("container-ref");
|
||||
given(containerApi.create(any(), any())).willReturn(reference);
|
||||
given(containerApi.create(any(), isNull(), any())).willReturn(reference);
|
||||
given(containerApi.wait(eq(reference))).willReturn(ContainerStatus.of(9, null));
|
||||
ImageApi imageApi = mock(ImageApi.class);
|
||||
VolumeApi volumeApi = mock(VolumeApi.class);
|
||||
@@ -499,7 +565,7 @@ class BuilderTests {
|
||||
|
||||
private Answer<Image> withPulledImage(Image image) {
|
||||
return (invocation) -> {
|
||||
TotalProgressPullListener listener = invocation.getArgument(1, TotalProgressPullListener.class);
|
||||
TotalProgressPullListener listener = invocation.getArgument(2, TotalProgressPullListener.class);
|
||||
listener.onStart();
|
||||
listener.onFinish();
|
||||
return image;
|
||||
|
||||
@@ -49,6 +49,7 @@ import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ContainerContent;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ContainerReference;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ContainerStatus;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.VolumeName;
|
||||
import org.springframework.boot.buildpack.platform.io.IOConsumer;
|
||||
@@ -62,6 +63,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -92,8 +94,8 @@ class LifecycleTests {
|
||||
@ParameterizedTest
|
||||
@BooleanValueSource
|
||||
void executeExecutesPhases(boolean trustBuilder) throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
createLifecycle(trustBuilder).execute();
|
||||
if (trustBuilder) {
|
||||
@@ -111,8 +113,8 @@ class LifecycleTests {
|
||||
|
||||
@Test
|
||||
void executeWithBindingsExecutesPhases() throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
BuildRequest request = getTestRequest(true).withBindings(Binding.of("/host/src/path:/container/dest/path:ro"),
|
||||
Binding.of("volume-name:/container/volume/path:rw"));
|
||||
@@ -123,8 +125,8 @@ class LifecycleTests {
|
||||
|
||||
@Test
|
||||
void executeExecutesPhasesWithPlatformApi03() throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
createLifecycle(true, "builder-metadata-platform-api-0.3.json").execute();
|
||||
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-platform-api-0.3.json"));
|
||||
@@ -134,8 +136,8 @@ class LifecycleTests {
|
||||
@ParameterizedTest
|
||||
@BooleanValueSource
|
||||
void executeOnlyUploadsContentOnce(boolean trustBuilder) throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
createLifecycle(trustBuilder).execute();
|
||||
assertThat(this.content).hasSize(1);
|
||||
@@ -144,8 +146,8 @@ class LifecycleTests {
|
||||
@ParameterizedTest
|
||||
@BooleanValueSource
|
||||
void executeWhenAlreadyRunThrowsException(boolean trustBuilder) throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
Lifecycle lifecycle = createLifecycle(trustBuilder);
|
||||
lifecycle.execute();
|
||||
@@ -156,8 +158,8 @@ class LifecycleTests {
|
||||
@ParameterizedTest
|
||||
@BooleanValueSource
|
||||
void executeWhenBuilderReturnsErrorThrowsException(boolean trustBuilder) throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(9, null));
|
||||
assertThatExceptionOfType(BuilderException.class).isThrownBy(() -> createLifecycle(trustBuilder).execute())
|
||||
.withMessage(
|
||||
@@ -167,8 +169,8 @@ class LifecycleTests {
|
||||
@ParameterizedTest
|
||||
@BooleanValueSource
|
||||
void executeWhenCleanCacheClearsCache(boolean trustBuilder) throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
BuildRequest request = getTestRequest(trustBuilder).withCleanCache(true);
|
||||
createLifecycle(request).execute();
|
||||
@@ -188,8 +190,8 @@ class LifecycleTests {
|
||||
|
||||
@Test
|
||||
void executeWhenPlatformApiNotSupportedThrowsException() throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> createLifecycle(true, "builder-metadata-unsupported-api.json").execute())
|
||||
@@ -198,8 +200,8 @@ class LifecycleTests {
|
||||
|
||||
@Test
|
||||
void executeWhenMultiplePlatformApisNotSupportedThrowsException() throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> createLifecycle(true, "builder-metadata-unsupported-apis.json").execute())
|
||||
@@ -209,8 +211,8 @@ class LifecycleTests {
|
||||
@ParameterizedTest
|
||||
@BooleanValueSource
|
||||
void executeWhenMultiplePlatformApisSupportedExecutesPhase(boolean trustBuilder) throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
createLifecycle(trustBuilder, "builder-metadata-supported-apis.json").execute();
|
||||
if (trustBuilder) {
|
||||
@@ -234,8 +236,8 @@ class LifecycleTests {
|
||||
|
||||
@Test
|
||||
void executeWithNetworkExecutesPhases() throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
BuildRequest request = getTestRequest(true).withNetwork("test");
|
||||
createLifecycle(request).execute();
|
||||
@@ -246,8 +248,8 @@ class LifecycleTests {
|
||||
@ParameterizedTest
|
||||
@BooleanValueSource
|
||||
void executeWithCacheVolumeNamesExecutesPhases(boolean trustBuilder) throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
BuildRequest request = getTestRequest(trustBuilder).withBuildWorkspace(Cache.volume("work-volume"))
|
||||
.withBuildCache(Cache.volume("build-volume"))
|
||||
@@ -269,8 +271,8 @@ class LifecycleTests {
|
||||
@ParameterizedTest
|
||||
@BooleanValueSource
|
||||
void executeWithCacheBindMountsExecutesPhases(boolean trustBuilder) throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
BuildRequest request = getTestRequest(trustBuilder).withBuildWorkspace(Cache.bind("/tmp/work"))
|
||||
.withBuildCache(Cache.bind("/tmp/build-cache"))
|
||||
@@ -292,8 +294,8 @@ class LifecycleTests {
|
||||
@ParameterizedTest
|
||||
@BooleanValueSource
|
||||
void executeWithCreatedDateExecutesPhases(boolean trustBuilder) throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
BuildRequest request = getTestRequest(trustBuilder).withCreatedDate("2020-07-01T12:34:56Z");
|
||||
createLifecycle(request).execute();
|
||||
@@ -313,8 +315,8 @@ class LifecycleTests {
|
||||
@ParameterizedTest
|
||||
@BooleanValueSource
|
||||
void executeWithApplicationDirectoryExecutesPhases(boolean trustBuilder) throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
BuildRequest request = getTestRequest(trustBuilder).withApplicationDirectory("/application");
|
||||
createLifecycle(request).execute();
|
||||
@@ -334,8 +336,8 @@ class LifecycleTests {
|
||||
@ParameterizedTest
|
||||
@BooleanValueSource
|
||||
void executeWithSecurityOptionsExecutesPhases(boolean trustBuilder) throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
BuildRequest request = getTestRequest(trustBuilder)
|
||||
.withSecurityOptions(List.of("label=user:USER", "label=role:ROLE"));
|
||||
@@ -356,8 +358,8 @@ class LifecycleTests {
|
||||
@ParameterizedTest
|
||||
@BooleanValueSource
|
||||
void executeWithDockerHostAndRemoteAddressExecutesPhases(boolean trustBuilder) throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
BuildRequest request = getTestRequest(trustBuilder);
|
||||
createLifecycle(request, ResolvedDockerHost.from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376")))
|
||||
@@ -378,8 +380,8 @@ class LifecycleTests {
|
||||
@ParameterizedTest
|
||||
@BooleanValueSource
|
||||
void executeWithDockerHostAndLocalAddressExecutesPhases(boolean trustBuilder) throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), isNull(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
BuildRequest request = getTestRequest(trustBuilder);
|
||||
createLifecycle(request, ResolvedDockerHost.from(DockerHostConfiguration.forAddress("/var/alt.sock")))
|
||||
@@ -397,6 +399,29 @@ class LifecycleTests {
|
||||
assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@BooleanValueSource
|
||||
void executeWithImagePlatformExecutesPhases(boolean trustBuilder) throws Exception {
|
||||
given(this.docker.container().create(any(), eq(ImagePlatform.of("linux/arm64"))))
|
||||
.willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), eq(ImagePlatform.of("linux/arm64")), any()))
|
||||
.willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
BuildRequest request = getTestRequest(trustBuilder).withImagePlatform("linux/arm64");
|
||||
createLifecycle(request).execute();
|
||||
if (trustBuilder) {
|
||||
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator.json"));
|
||||
}
|
||||
else {
|
||||
assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json"));
|
||||
assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json"));
|
||||
assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer.json"));
|
||||
assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json"));
|
||||
assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter.json"));
|
||||
}
|
||||
assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
|
||||
}
|
||||
|
||||
private DockerApi mockDockerApi() {
|
||||
DockerApi docker = mock(DockerApi.class);
|
||||
ImageApi imageApi = mock(ImageApi.class);
|
||||
@@ -458,8 +483,8 @@ class LifecycleTests {
|
||||
ArrayNode command = getCommand(config);
|
||||
String name = command.get(0).asText().substring(1).replaceAll("/", "-");
|
||||
this.configs.put(name, config);
|
||||
if (invocation.getArguments().length > 1) {
|
||||
this.content.put(name, invocation.getArgument(1, ContainerContent.class));
|
||||
if (invocation.getArguments().length > 2) {
|
||||
this.content.put(name, invocation.getArgument(2, ContainerContent.class));
|
||||
}
|
||||
return ContainerReference.of(name);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* 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.
|
||||
@@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent;
|
||||
import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.Image;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.VolumeName;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
@@ -51,6 +52,7 @@ class PrintStreamBuildLogTests {
|
||||
BuildRequest request = mock(BuildRequest.class);
|
||||
ImageReference name = ImageReference.of("my-app:latest");
|
||||
ImageReference builderImageReference = ImageReference.of("cnb/builder");
|
||||
ImagePlatform platform = ImagePlatform.of("linux/arm64/v1");
|
||||
Image builderImage = mock(Image.class);
|
||||
given(builderImage.getDigests()).willReturn(Collections.singletonList("00000001"));
|
||||
ImageReference runImageReference = ImageReference.of("cnb/runner");
|
||||
@@ -60,11 +62,12 @@ class PrintStreamBuildLogTests {
|
||||
ImageReference tag = ImageReference.of("my-app:1.0");
|
||||
given(request.getTags()).willReturn(Collections.singletonList(tag));
|
||||
log.start(request);
|
||||
Consumer<TotalProgressEvent> pullBuildImageConsumer = log.pullingImage(builderImageReference,
|
||||
Consumer<TotalProgressEvent> pullBuildImageConsumer = log.pullingImage(builderImageReference, null,
|
||||
ImageType.BUILDER);
|
||||
pullBuildImageConsumer.accept(new TotalProgressEvent(100));
|
||||
log.pulledImage(builderImage, ImageType.BUILDER);
|
||||
Consumer<TotalProgressEvent> pullRunImageConsumer = log.pullingImage(runImageReference, ImageType.RUNNER);
|
||||
Consumer<TotalProgressEvent> pullRunImageConsumer = log.pullingImage(runImageReference, platform,
|
||||
ImageType.RUNNER);
|
||||
pullRunImageConsumer.accept(new TotalProgressEvent(100));
|
||||
log.pulledImage(runImage, ImageType.RUNNER);
|
||||
log.executingLifecycle(request, LifecycleVersion.parse("0.5"), Cache.volume(VolumeName.of("pack-abc.cache")));
|
||||
|
||||
@@ -18,15 +18,19 @@ package org.springframework.boot.buildpack.platform.docker;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.message.BasicHeader;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -39,15 +43,18 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.docker.DockerApi.ContainerApi;
|
||||
import org.springframework.boot.buildpack.platform.docker.DockerApi.ImageApi;
|
||||
import org.springframework.boot.buildpack.platform.docker.DockerApi.SystemApi;
|
||||
import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi;
|
||||
import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport;
|
||||
import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport.Response;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ApiVersion;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ContainerContent;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ContainerReference;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ContainerStatus;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.Image;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImageArchive;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.VolumeName;
|
||||
import org.springframework.boot.buildpack.platform.io.Content;
|
||||
@@ -80,12 +87,18 @@ import static org.mockito.Mockito.times;
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DockerApiTests {
|
||||
|
||||
private static final String API_URL = "/" + DockerApi.API_VERSION;
|
||||
private static final String API_URL = "/v" + DockerApi.MINIMUM_API_VERSION;
|
||||
|
||||
public static final String PING_URL = "/_ping";
|
||||
|
||||
private static final String IMAGES_URL = API_URL + "/images";
|
||||
|
||||
private static final String IMAGES_1_41_URL = "/v" + ApiVersion.of(1, 41) + "/images";
|
||||
|
||||
private static final String CONTAINERS_URL = API_URL + "/containers";
|
||||
|
||||
private static final String CONTAINERS_1_41_URL = "/v" + ApiVersion.of(1, 41) + "/containers";
|
||||
|
||||
private static final String VOLUMES_URL = API_URL + "/volumes";
|
||||
|
||||
@Mock
|
||||
@@ -124,6 +137,29 @@ class DockerApiTests {
|
||||
};
|
||||
}
|
||||
|
||||
private Response responseWithHeaders(Header... headers) {
|
||||
return new Response() {
|
||||
|
||||
@Override
|
||||
public InputStream getContent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Header getHeader(String name) {
|
||||
return Arrays.stream(headers)
|
||||
.filter((header) -> header.getName().equals(name))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
void createDockerApi() {
|
||||
DockerApi api = new DockerApi();
|
||||
@@ -154,13 +190,14 @@ class DockerApiTests {
|
||||
|
||||
@Test
|
||||
void pullWhenReferenceIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.api.pull(null, this.pullListener))
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.api.pull(null, null, this.pullListener))
|
||||
.withMessage("Reference must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pullWhenListenerIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.api.pull(ImageReference.of("ubuntu"), null))
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.api.pull(ImageReference.of("ubuntu"), null, null))
|
||||
.withMessage("Listener must not be null");
|
||||
}
|
||||
|
||||
@@ -171,7 +208,7 @@ class DockerApiTests {
|
||||
URI imageUri = new URI(IMAGES_URL + "/gcr.io/paketo-buildpacks/builder:base/json");
|
||||
given(http().post(eq(createUri), isNull())).willReturn(responseOf("pull-stream.json"));
|
||||
given(http().get(imageUri)).willReturn(responseOf("type/image.json"));
|
||||
Image image = this.api.pull(reference, this.pullListener);
|
||||
Image image = this.api.pull(reference, null, this.pullListener);
|
||||
assertThat(image.getLayers()).hasSize(46);
|
||||
InOrder ordered = inOrder(this.pullListener);
|
||||
ordered.verify(this.pullListener).onStart();
|
||||
@@ -186,7 +223,7 @@ class DockerApiTests {
|
||||
URI imageUri = new URI(IMAGES_URL + "/gcr.io/paketo-buildpacks/builder:base/json");
|
||||
given(http().post(eq(createUri), eq("auth token"))).willReturn(responseOf("pull-stream.json"));
|
||||
given(http().get(imageUri)).willReturn(responseOf("type/image.json"));
|
||||
Image image = this.api.pull(reference, this.pullListener, "auth token");
|
||||
Image image = this.api.pull(reference, null, this.pullListener, "auth token");
|
||||
assertThat(image.getLayers()).hasSize(46);
|
||||
InOrder ordered = inOrder(this.pullListener);
|
||||
ordered.verify(this.pullListener).onStart();
|
||||
@@ -194,6 +231,36 @@ class DockerApiTests {
|
||||
ordered.verify(this.pullListener).onFinish();
|
||||
}
|
||||
|
||||
@Test
|
||||
void pullWithPlatformPullsImageAndProducesEvents() throws Exception {
|
||||
ImageReference reference = ImageReference.of("gcr.io/paketo-buildpacks/builder:base");
|
||||
ImagePlatform platform = ImagePlatform.of("linux/arm64/v1");
|
||||
URI createUri = new URI(IMAGES_1_41_URL
|
||||
+ "/create?fromImage=gcr.io%2Fpaketo-buildpacks%2Fbuilder%3Abase&platform=linux%2Farm64%2Fv1");
|
||||
URI imageUri = new URI(IMAGES_1_41_URL + "/gcr.io/paketo-buildpacks/builder:base/json");
|
||||
given(http().head(eq(new URI(PING_URL))))
|
||||
.willReturn(responseWithHeaders(new BasicHeader(DockerApi.API_VERSION_HEADER_NAME, "1.41")));
|
||||
given(http().post(eq(createUri), isNull())).willReturn(responseOf("pull-stream.json"));
|
||||
given(http().get(imageUri)).willReturn(responseOf("type/image.json"));
|
||||
Image image = this.api.pull(reference, platform, this.pullListener);
|
||||
assertThat(image.getLayers()).hasSize(46);
|
||||
InOrder ordered = inOrder(this.pullListener);
|
||||
ordered.verify(this.pullListener).onStart();
|
||||
ordered.verify(this.pullListener, times(595)).onUpdate(any());
|
||||
ordered.verify(this.pullListener).onFinish();
|
||||
}
|
||||
|
||||
@Test
|
||||
void pullWithPlatformAndInsufficientApiVersionThrowsException() throws Exception {
|
||||
ImageReference reference = ImageReference.of("gcr.io/paketo-buildpacks/builder:base");
|
||||
ImagePlatform platform = ImagePlatform.of("linux/arm64/v1");
|
||||
given(http().head(eq(new URI(PING_URL)))).willReturn(responseWithHeaders(
|
||||
new BasicHeader(DockerApi.API_VERSION_HEADER_NAME, DockerApi.MINIMUM_API_VERSION)));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.api.pull(reference, platform, this.pullListener))
|
||||
.withMessageContaining("must be at least 1.41")
|
||||
.withMessageContaining("current API version is 1.24");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pushWhenReferenceIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.api.push(null, this.pushListener, null))
|
||||
@@ -460,7 +527,7 @@ class DockerApiTests {
|
||||
|
||||
@Test
|
||||
void createWhenConfigIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.api.create(null))
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.api.create(null, null))
|
||||
.withMessage("Config must not be null");
|
||||
}
|
||||
|
||||
@@ -471,7 +538,7 @@ class DockerApiTests {
|
||||
URI createUri = new URI(CONTAINERS_URL + "/create");
|
||||
given(http().post(eq(createUri), eq("application/json"), any()))
|
||||
.willReturn(responseOf("create-container-response.json"));
|
||||
ContainerReference containerReference = this.api.create(config);
|
||||
ContainerReference containerReference = this.api.create(config, null);
|
||||
assertThat(containerReference).hasToString("e90e34656806");
|
||||
then(http()).should().post(any(), any(), this.writer.capture());
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
@@ -493,7 +560,7 @@ class DockerApiTests {
|
||||
.willReturn(responseOf("create-container-response.json"));
|
||||
URI uploadUri = new URI(CONTAINERS_URL + "/e90e34656806/archive?path=%2F");
|
||||
given(http().put(eq(uploadUri), eq("application/x-tar"), any())).willReturn(emptyResponse());
|
||||
ContainerReference containerReference = this.api.create(config, content);
|
||||
ContainerReference containerReference = this.api.create(config, null, content);
|
||||
assertThat(containerReference).hasToString("e90e34656806");
|
||||
then(http()).should().post(any(), any(), this.writer.capture());
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
@@ -504,6 +571,34 @@ class DockerApiTests {
|
||||
assertThat(out.toByteArray()).hasSizeGreaterThan(2000);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithPlatformCreatesContainer() throws Exception {
|
||||
ImageReference imageReference = ImageReference.of("ubuntu:bionic");
|
||||
ContainerConfig config = ContainerConfig.of(imageReference, (update) -> update.withCommand("/bin/bash"));
|
||||
ImagePlatform platform = ImagePlatform.of("linux/arm64/v1");
|
||||
given(http().head(eq(new URI(PING_URL))))
|
||||
.willReturn(responseWithHeaders(new BasicHeader(DockerApi.API_VERSION_HEADER_NAME, "1.41")));
|
||||
URI createUri = new URI(CONTAINERS_1_41_URL + "/create?platform=linux%2Farm64%2Fv1");
|
||||
given(http().post(eq(createUri), eq("application/json"), any()))
|
||||
.willReturn(responseOf("create-container-response.json"));
|
||||
ContainerReference containerReference = this.api.create(config, platform);
|
||||
assertThat(containerReference).hasToString("e90e34656806");
|
||||
then(http()).should().post(any(), any(), this.writer.capture());
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
this.writer.getValue().accept(out);
|
||||
assertThat(out.toByteArray()).hasSize(config.toString().length());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithPlatformAndInsufficientApiVersionThrowsException() throws Exception {
|
||||
ImageReference imageReference = ImageReference.of("ubuntu:bionic");
|
||||
ContainerConfig config = ContainerConfig.of(imageReference, (update) -> update.withCommand("/bin/bash"));
|
||||
ImagePlatform platform = ImagePlatform.of("linux/arm64/v1");
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.api.create(config, platform))
|
||||
.withMessageContaining("must be at least 1.41")
|
||||
.withMessageContaining("current API version is 1.24");
|
||||
}
|
||||
|
||||
@Test
|
||||
void startWhenReferenceIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.api.start(null))
|
||||
@@ -621,4 +716,42 @@ class DockerApiTests {
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class SystemDockerApiTests {
|
||||
|
||||
private SystemApi api;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.api = DockerApiTests.this.dockerApi.system();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getApiVersionWithVersionHeaderReturnsVersion() throws Exception {
|
||||
given(http().head(eq(new URI(PING_URL))))
|
||||
.willReturn(responseWithHeaders(new BasicHeader(DockerApi.API_VERSION_HEADER_NAME, "1.44")));
|
||||
assertThat(this.api.getApiVersion()).isEqualTo(ApiVersion.of(1, 44));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getApiVersionWithEmptyVersionHeaderReturnsDefaultVersion() throws Exception {
|
||||
given(http().head(eq(new URI(PING_URL))))
|
||||
.willReturn(responseWithHeaders(new BasicHeader(DockerApi.API_VERSION_HEADER_NAME, "")));
|
||||
assertThat(this.api.getApiVersion()).isEqualTo(DockerApi.MINIMUM_API_VERSION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getApiVersionWithNoVersionHeaderReturnsDefaultVersion() throws Exception {
|
||||
given(http().head(eq(new URI(PING_URL)))).willReturn(emptyResponse());
|
||||
assertThat(this.api.getApiVersion()).isEqualTo(DockerApi.MINIMUM_API_VERSION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getApiVersionWithExceptionReturnsDefaultVersion() throws Exception {
|
||||
given(http().head(eq(new URI(PING_URL)))).willThrow(new IOException("simulated error"));
|
||||
assertThat(this.api.getApiVersion()).isEqualTo(DockerApi.MINIMUM_API_VERSION);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.buildpack.platform.build;
|
||||
package org.springframework.boot.buildpack.platform.docker.type;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -22,7 +22,6 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link ApiVersion}.
|
||||
@@ -57,18 +56,6 @@ class ApiVersionTests {
|
||||
assertThat(version.getMinor()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void assertSupportsWhenSupports() {
|
||||
ApiVersion.parse("1.2").assertSupports(ApiVersion.parse("1.0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void assertSupportsWhenDoesNotSupportThrowsException() {
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> ApiVersion.parse("1.2").assertSupports(ApiVersion.parse("1.3")))
|
||||
.withMessage("Detected platform API version '1.3' does not match supported version '1.2'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportsWhenSame() {
|
||||
assertThat(supports("0.0", "0.0")).isTrue();
|
||||
@@ -78,7 +78,7 @@ class ImageArchiveTests extends AbstractJsonTests {
|
||||
}
|
||||
|
||||
private void assertExpectedConfig(TarArchiveEntry entry, byte[] content) throws Exception {
|
||||
assertThat(entry.getName()).isEqualTo("682f8d24b9d9c313d1190a0e955dcb5e65ec9beea40420999839c6f0cbb38382.json");
|
||||
assertThat(entry.getName()).isEqualTo("416c76dc7f691f91e80516ff039e056f32f996b59af4b1cb8114e6ae8171a374.json");
|
||||
String actualJson = new String(content, StandardCharsets.UTF_8);
|
||||
String expectedJson = StreamUtils.copyToString(getContent("image-archive-config.json"), StandardCharsets.UTF_8);
|
||||
JSONAssert.assertEquals(expectedJson, actualJson, false);
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.springframework.boot.buildpack.platform.docker.type;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
class ImagePlatformTests extends AbstractJsonTests {
|
||||
|
||||
@Test
|
||||
void ofWithOsParses() {
|
||||
ImagePlatform platform = ImagePlatform.of("linux");
|
||||
assertThat(platform.toString()).isEqualTo("linux");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWithOsAndArchitectureParses() {
|
||||
ImagePlatform platform = ImagePlatform.of("linux/amd64");
|
||||
assertThat(platform.toString()).isEqualTo("linux/amd64");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWithOsAndArchitectureAndVariantParses() {
|
||||
ImagePlatform platform = ImagePlatform.of("linux/amd64/v1");
|
||||
assertThat(platform.toString()).isEqualTo("linux/amd64/v1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWithEmptyValueFails() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> ImagePlatform.of(""))
|
||||
.withMessageContaining("Value must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWithTooManySegmentsFails() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> ImagePlatform.of("linux/amd64/v1/extra"))
|
||||
.withMessageContaining("value 'linux/amd64/v1/extra'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromImageMatchesImage() throws IOException {
|
||||
ImagePlatform platform = ImagePlatform.from(getImage());
|
||||
assertThat(platform.toString()).isEqualTo("linux/amd64/v1");
|
||||
}
|
||||
|
||||
private Image getImage() throws IOException {
|
||||
return Image.of(getContent("image.json"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* 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.
|
||||
@@ -67,6 +67,18 @@ class ImageTests extends AbstractJsonTests {
|
||||
assertThat(image.getOs()).isEqualTo("linux");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getArchitectureReturnsArchitecture() throws Exception {
|
||||
Image image = getImage();
|
||||
assertThat(image.getArchitecture()).isEqualTo("amd64");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getVariantReturnsVariant() throws Exception {
|
||||
Image image = getImage();
|
||||
assertThat(image.getVariant()).isEqualTo("v1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCreatedReturnsDate() throws Exception {
|
||||
Image image = getImage();
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@ Building image 'docker.io/library/my-app:latest'
|
||||
|
||||
> Pulling builder image 'docker.io/cnb/builder' ..................................................
|
||||
> Pulled builder image '00000001'
|
||||
> Pulling run image 'docker.io/cnb/runner' ..................................................
|
||||
> Pulling run image 'docker.io/cnb/runner' for platform 'linux/arm64/v1' ..................................................
|
||||
> Pulled run image '00000002'
|
||||
> Executing lifecycle version v0.5.0
|
||||
> Using build cache volume 'pack-abc.cache'
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"Id": "sha256:1332879bc8e38793a45ebe5a750f2a1c35df07ec2aa9c18f694644a9de77359b",
|
||||
"RepoTags": [
|
||||
"cloudfoundry/run:base-cnb"
|
||||
],
|
||||
"RepoDigests": [
|
||||
"cloudfoundry/run@sha256:fb5ecb90a42b2067a859aab23fc1f5e9d9c2589d07ba285608879e7baa415aad"
|
||||
],
|
||||
"Parent": "",
|
||||
"Comment": "",
|
||||
"Created": "2020-03-20T20:18:18.117972538Z",
|
||||
"Container": "91d1af87c3bb6163cd9c7cb21e6891cd25f5fa3c7417779047776e288c0bc234",
|
||||
"ContainerConfig": {
|
||||
"Hostname": "91d1af87c3bb",
|
||||
"Domainname": "",
|
||||
"User": "1000:1000",
|
||||
"AttachStdin": false,
|
||||
"AttachStdout": false,
|
||||
"AttachStderr": false,
|
||||
"Tty": false,
|
||||
"OpenStdin": false,
|
||||
"StdinOnce": false,
|
||||
"Env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
],
|
||||
"Cmd": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"#(nop) ",
|
||||
"LABEL io.buildpacks.stack.id=io.buildpacks.stacks.bionic"
|
||||
],
|
||||
"ArgsEscaped": true,
|
||||
"Image": "sha256:fbe314bcb23f15a2a09603b6620acd67c332fd08fbf2a7bc3db8fb2f5078d994",
|
||||
"Volumes": null,
|
||||
"WorkingDir": "",
|
||||
"Entrypoint": null,
|
||||
"OnBuild": null,
|
||||
"Labels": {
|
||||
"io.buildpacks.stack.id": "io.buildpacks.stacks.bionic"
|
||||
}
|
||||
},
|
||||
"DockerVersion": "18.09.6",
|
||||
"Author": "",
|
||||
"Config": {
|
||||
"Hostname": "",
|
||||
"Domainname": "",
|
||||
"User": "1000:1000",
|
||||
"AttachStdin": false,
|
||||
"AttachStdout": false,
|
||||
"AttachStderr": false,
|
||||
"Tty": false,
|
||||
"OpenStdin": false,
|
||||
"StdinOnce": false,
|
||||
"Env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
],
|
||||
"Cmd": [
|
||||
"/bin/bash"
|
||||
],
|
||||
"ArgsEscaped": true,
|
||||
"Image": "sha256:fbe314bcb23f15a2a09603b6620acd67c332fd08fbf2a7bc3db8fb2f5078d994",
|
||||
"Volumes": null,
|
||||
"WorkingDir": "",
|
||||
"Entrypoint": null,
|
||||
"OnBuild": null,
|
||||
"Labels": {
|
||||
"io.buildpacks.stack.id": "io.buildpacks.stacks.bionic"
|
||||
}
|
||||
},
|
||||
"Architecture": "arm64",
|
||||
"Os": "linux",
|
||||
"Variant": "v1",
|
||||
"Size": 71248531,
|
||||
"VirtualSize": 71248531,
|
||||
"GraphDriver": {
|
||||
"Data": {
|
||||
"LowerDir": "/var/lib/docker/overlay2/17f0a4530fbc3e2982f9dc8feb8c8ddc124473bdd50130dae20856ac597d82dd/diff:/var/lib/docker/overlay2/73dfd4e2075fccb239b3d5e9b33b32b8e410bdc3cd5a620b41346f44cc5c51f7/diff:/var/lib/docker/overlay2/b3924ed7c91730f6714d33c455db888604b59ab093033b3f59ac16ecdd777987/diff:/var/lib/docker/overlay2/e36a32cd0ab20b216a8db1a8a166b17464399e4d587d22504088a7a6ef0a68a4/diff:/var/lib/docker/overlay2/3334e94fe191333b65f571912c0fcfbbf31aeb090a2fb9b4cfdbc32a37c0fe5f/diff",
|
||||
"MergedDir": "/var/lib/docker/overlay2/8d3f9e3c00bc5072f8051ec7884500ca394f2331d8bcc9452f68d04531f50f82/merged",
|
||||
"UpperDir": "/var/lib/docker/overlay2/8d3f9e3c00bc5072f8051ec7884500ca394f2331d8bcc9452f68d04531f50f82/diff",
|
||||
"WorkDir": "/var/lib/docker/overlay2/8d3f9e3c00bc5072f8051ec7884500ca394f2331d8bcc9452f68d04531f50f82/work"
|
||||
},
|
||||
"Name": "overlay2"
|
||||
},
|
||||
"RootFS": {
|
||||
"Type": "layers",
|
||||
"Layers": [
|
||||
"sha256:c8be1b8f4d60d99c281fc2db75e0f56df42a83ad2f0b091621ce19357e19d853",
|
||||
"sha256:977183d4e9995d9cd5ffdfc0f29e911ec9de777bcb0f507895daa1068477f76f",
|
||||
"sha256:6597da2e2e52f4d438ad49a14ca79324f130a9ea08745505aa174a8db51cb79d",
|
||||
"sha256:16542a8fc3be1bfaff6ed1daa7922e7c3b47b6c3a8d98b7fca58b9517bb99b75",
|
||||
"sha256:c1daeb79beb276c7441d9a1d7281433e9a7edb9f652b8996ecc62b51e88a47b2",
|
||||
"sha256:eb195d29dc1aa6e4239f00e7868deebc5ac12bebe76104e0b774c1ef29ca78e3"
|
||||
]
|
||||
},
|
||||
"Metadata": {
|
||||
"LastTagTime": "0001-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"config": {
|
||||
"Config": {
|
||||
"Hostname": "",
|
||||
"Domainname": "",
|
||||
"User": "vcap",
|
||||
@@ -25,8 +25,8 @@
|
||||
"io.buildpacks.stack.id": "org.cloudfoundry.stacks.cflinuxfs3"
|
||||
}
|
||||
},
|
||||
"created": "1980-01-01T00:00:01Z",
|
||||
"history": [
|
||||
"Created": "1980-01-01T00:00:01Z",
|
||||
"History": [
|
||||
{
|
||||
|
||||
},
|
||||
@@ -169,8 +169,10 @@
|
||||
|
||||
}
|
||||
],
|
||||
"os": "linux",
|
||||
"rootfs": {
|
||||
"Architecture": "amd64",
|
||||
"Os": "linux",
|
||||
"Variant": "v1",
|
||||
"RootFS": {
|
||||
"diff_ids": [
|
||||
"sha256:733a8e5ce32984099ef675fce04730f6e2a6dcfdf5bd292fea01a8f936265342",
|
||||
"sha256:7755b972f0b4f49de73ef5114fb3ba9c69d80f217e80da99f56f0d0a5dcb3d70",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"Config": "682f8d24b9d9c313d1190a0e955dcb5e65ec9beea40420999839c6f0cbb38382.json",
|
||||
"Config": "416c76dc7f691f91e80516ff039e056f32f996b59af4b1cb8114e6ae8171a374.json",
|
||||
"Layers": [
|
||||
"blank_0",
|
||||
"blank_1",
|
||||
|
||||
@@ -72,9 +72,10 @@
|
||||
"io.buildpacks.stack.id": "org.cloudfoundry.stacks.cflinuxfs3"
|
||||
}
|
||||
},
|
||||
"Architecture": "amd64",
|
||||
"Os": "linux",
|
||||
"Size": 1559461360,
|
||||
"Architecture": "amd64",
|
||||
"Variant": "v1",
|
||||
"Size": 1559461360,
|
||||
"VirtualSize": 1559461360,
|
||||
"GraphDriver": {
|
||||
"Data": {
|
||||
|
||||
Reference in New Issue
Block a user