ImageDockerApiTests Class — spring-boot Architecture
Architecture documentation for the ImageDockerApiTests class in DockerApiTests.java from the spring-boot codebase.
Entity Profile
Relationship Graph
Source Code
buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java lines 219–627
@Nested
class ImageDockerApiTests {
private ImageApi api;
@Mock
@SuppressWarnings("NullAway.Init")
private UpdateListener<PullImageUpdateEvent> pullListener;
@Mock
@SuppressWarnings("NullAway.Init")
private UpdateListener<PushImageUpdateEvent> pushListener;
@Mock
@SuppressWarnings("NullAway.Init")
private UpdateListener<LoadImageUpdateEvent> loadListener;
@Captor
@SuppressWarnings("NullAway.Init")
private ArgumentCaptor<IOConsumer<OutputStream>> writer;
@BeforeEach
void setup() {
this.api = DockerApiTests.this.dockerApi.image();
}
@Test
@SuppressWarnings("NullAway") // Test null check
void pullWhenReferenceIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.api.pull(null, null, this.pullListener))
.withMessage("'reference' must not be null");
}
@Test
@SuppressWarnings("NullAway") // Test null check
void pullWhenListenerIsNullThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.api.pull(ImageReference.of("ubuntu"), null, null))
.withMessage("'listener' must not be null");
}
@Test
void pullPullsImageAndProducesEvents() throws Exception {
ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base");
URI createUri = new URI(IMAGES_URL + "/create?fromImage=docker.io%2Fpaketobuildpacks%2Fbuilder%3Abase");
URI imageUri = new URI(IMAGES_URL + "/docker.io/paketobuildpacks/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, null, 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 pullWithRegistryAuthPullsImageAndProducesEvents() throws Exception {
ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base");
URI createUri = new URI(IMAGES_URL + "/create?fromImage=docker.io%2Fpaketobuildpacks%2Fbuilder%3Abase");
URI imageUri = new URI(IMAGES_URL + "/docker.io/paketobuildpacks/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, null, this.pullListener, "auth token");
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 pullWithPlatformPullsImageAndProducesEvents() throws Exception {
ImageReference reference = ImageReference.of("gcr.io/paketo-buildpacks/builder:base");
URI createUri = new URI(
"/v1.49/images/create?fromImage=gcr.io%2Fpaketo-buildpacks%2Fbuilder%3Abase&platform=linux%2Farm64%2Fv1");
URI imageUri = new URI("/v1.49/images/gcr.io/paketo-buildpacks/builder:base/json?platform="
+ ENCODED_LINUX_ARM64_PLATFORM_JSON);
setVersion("1.49");
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, LINUX_ARM64_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");
setVersion("1.24");
assertThatIllegalStateException().isThrownBy(() -> this.api.pull(reference, platform, this.pullListener))
.withMessageContaining("must be at least 1.41")
.withMessageContaining("current API version is 1.24");
}
@Test
@SuppressWarnings("NullAway") // Test null check
void pushWhenReferenceIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.api.push(null, this.pushListener, null))
.withMessage("'reference' must not be null");
}
@Test
@SuppressWarnings("NullAway") // Test null check
void pushWhenListenerIsNullThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.api.push(ImageReference.of("ubuntu"), null, null))
.withMessage("'listener' must not be null");
}
@Test
void pushPushesImageAndProducesEvents() throws Exception {
ImageReference reference = ImageReference.of("localhost:5000/ubuntu");
URI pushUri = new URI(IMAGES_URL + "/localhost:5000/ubuntu/push");
given(http().post(pushUri, "auth token")).willReturn(responseOf("push-stream.json"));
this.api.push(reference, this.pushListener, "auth token");
InOrder ordered = inOrder(this.pushListener);
ordered.verify(this.pushListener).onStart();
ordered.verify(this.pushListener, times(44)).onUpdate(any());
ordered.verify(this.pushListener).onFinish();
}
@Test
void pushWithErrorInStreamThrowsException() throws Exception {
ImageReference reference = ImageReference.of("localhost:5000/ubuntu");
URI pushUri = new URI(IMAGES_URL + "/localhost:5000/ubuntu/push");
given(http().post(pushUri, "auth token")).willReturn(responseOf("push-stream-with-error.json"));
assertThatIllegalStateException()
.isThrownBy(() -> this.api.push(reference, this.pushListener, "auth token"))
.withMessageContaining("test message");
}
@Test
@SuppressWarnings("NullAway") // Test null check
void loadWhenArchiveIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.api.load(null, UpdateListener.none()))
.withMessage("'archive' must not be null");
}
@Test
@SuppressWarnings("NullAway") // Test null check
void loadWhenListenerIsNullThrowsException() {
ImageArchive archive = mock(ImageArchive.class);
assertThatIllegalArgumentException().isThrownBy(() -> this.api.load(archive, null))
.withMessage("'listener' must not be null");
}
@Test // gh-23130
void loadWithEmptyResponseThrowsException() throws Exception {
Image image = Image.of(getClass().getResourceAsStream("type/image.json"));
ImageArchive archive = ImageArchive.from(image);
URI loadUri = new URI(IMAGES_URL + "/load");
given(http().post(eq(loadUri), eq("application/x-tar"), any())).willReturn(emptyResponse());
assertThatIllegalStateException().isThrownBy(() -> this.api.load(archive, this.loadListener))
.withMessageContaining("Invalid response received");
}
@Test // gh-31243
void loadWithErrorResponseThrowsException() throws Exception {
Image image = Image.of(getClass().getResourceAsStream("type/image.json"));
ImageArchive archive = ImageArchive.from(image);
URI loadUri = new URI(IMAGES_URL + "/load");
given(http().post(eq(loadUri), eq("application/x-tar"), any())).willReturn(responseOf("load-error.json"));
assertThatIllegalStateException().isThrownBy(() -> this.api.load(archive, this.loadListener))
.withMessageContaining("Error response received");
}
@Test
void loadLoadsImage() throws Exception {
Image image = Image.of(getClass().getResourceAsStream("type/image.json"));
ImageArchive archive = ImageArchive.from(image);
URI loadUri = new URI(IMAGES_URL + "/load");
given(http().post(eq(loadUri), eq("application/x-tar"), any())).willReturn(responseOf("load-stream.json"));
this.api.load(archive, this.loadListener);
InOrder ordered = inOrder(this.loadListener);
ordered.verify(this.loadListener).onStart();
ordered.verify(this.loadListener).onUpdate(any());
ordered.verify(this.loadListener).onFinish();
then(http()).should().post(any(), any(), this.writer.capture());
ByteArrayOutputStream out = new ByteArrayOutputStream();
this.writer.getValue().accept(out);
assertThat(out.toByteArray()).hasSizeGreaterThan(21000);
}
@Test
@SuppressWarnings("NullAway") // Test null check
void removeWhenReferenceIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.api.remove(null, true))
.withMessage("'reference' must not be null");
}
@Test
void removeRemovesContainer() throws Exception {
ImageReference reference = ImageReference
.of("ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d");
URI removeUri = new URI(IMAGES_URL
+ "/docker.io/library/ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d");
given(http().delete(removeUri)).willReturn(emptyResponse());
this.api.remove(reference, false);
then(http()).should().delete(removeUri);
}
@Test
void removeWhenForceIsTrueRemovesContainer() throws Exception {
ImageReference reference = ImageReference
.of("ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d");
URI removeUri = new URI(IMAGES_URL
+ "/docker.io/library/ubuntu@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d?force=1");
given(http().delete(removeUri)).willReturn(emptyResponse());
this.api.remove(reference, true);
then(http()).should().delete(removeUri);
}
@Test
@SuppressWarnings("NullAway") // Test null check
void inspectWhenReferenceIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.api.inspect(null))
.withMessage("'reference' must not be null");
}
@Test
void inspectInspectImage() throws Exception {
ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base");
URI imageUri = new URI(IMAGES_URL + "/docker.io/paketobuildpacks/builder:base/json");
given(http().get(imageUri)).willReturn(responseOf("type/image.json"));
Image image = this.api.inspect(reference);
assertThat(image.getArchitecture()).isEqualTo("amd64");
assertThat(image.getLayers()).hasSize(46);
}
@Test
void inspectWithPlatformWhenSupportedVersionInspectImage() throws Exception {
ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base");
URI imageUri = new URI("/v1.49/images/docker.io/paketobuildpacks/builder:base/json?platform="
+ ENCODED_LINUX_ARM64_PLATFORM_JSON);
setVersion("1.49");
given(http().get(imageUri)).willReturn(responseOf("type/image-platform.json"));
Image image = this.api.inspect(reference, LINUX_ARM64_PLATFORM);
assertThat(image.getArchitecture()).isEqualTo("arm64");
assertThat(image.getLayers()).hasSize(2);
}
@Test
void inspectWithPlatformWhenOldVersionInspectImage() throws Exception {
ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base");
URI imageUri = new URI("/v1.48/images/docker.io/paketobuildpacks/builder:base/json");
setVersion("1.48");
given(http().get(imageUri)).willReturn(responseOf("type/image.json"));
Image image = this.api.inspect(reference, LINUX_ARM64_PLATFORM);
assertThat(image.getArchitecture()).isEqualTo("amd64");
assertThat(image.getLayers()).hasSize(46);
}
@Test
void exportLayersExportsLayerTars() throws Exception {
ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base");
URI exportUri = new URI(IMAGES_URL + "/docker.io/paketobuildpacks/builder:base/get");
given(DockerApiTests.this.http.get(exportUri)).willReturn(responseOf("export.tar"));
MultiValueMap<String, String> contents = new LinkedMultiValueMap<>();
this.api.exportLayers(reference, (name, archive) -> {
ByteArrayOutputStream out = new ByteArrayOutputStream();
archive.writeTo(out);
try (TarArchiveInputStream in = new TarArchiveInputStream(
new ByteArrayInputStream(out.toByteArray()))) {
TarArchiveEntry entry = in.getNextEntry();
while (entry != null) {
contents.add(name, entry.getName());
entry = in.getNextEntry();
}
}
});
assertThat(contents).hasSize(3)
.containsKeys("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar",
"74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar",
"a69532b5b92bb891fbd9fa1a6b3af9087ea7050255f59ba61a796f8555ecd783/layer.tar");
assertThat(contents.get("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar"))
.containsExactly("/cnb/order.toml");
assertThat(contents.get("74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar"))
.containsExactly("/cnb/stack.toml");
}
@Test
void exportLayersExportsLayerTarsWithPlatformWhenSupportedVersion() throws Exception {
setVersion("1.48");
ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base");
URI exportUri = new URI("/v1.48/images/docker.io/paketobuildpacks/builder:base/get?platform="
+ ENCODED_LINUX_ARM64_PLATFORM_JSON);
given(DockerApiTests.this.http.get(exportUri)).willReturn(responseOf("export.tar"));
MultiValueMap<String, String> contents = new LinkedMultiValueMap<>();
this.api.exportLayers(reference, LINUX_ARM64_PLATFORM, (name, archive) -> {
ByteArrayOutputStream out = new ByteArrayOutputStream();
archive.writeTo(out);
try (TarArchiveInputStream in = new TarArchiveInputStream(
new ByteArrayInputStream(out.toByteArray()))) {
TarArchiveEntry entry = in.getNextEntry();
while (entry != null) {
contents.add(name, entry.getName());
entry = in.getNextEntry();
}
}
});
assertThat(contents).hasSize(3)
.containsKeys("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar",
"74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar",
"a69532b5b92bb891fbd9fa1a6b3af9087ea7050255f59ba61a796f8555ecd783/layer.tar");
assertThat(contents.get("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar"))
.containsExactly("/cnb/order.toml");
assertThat(contents.get("74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar"))
.containsExactly("/cnb/stack.toml");
}
@Test
void exportLayersExportsLayerTarsWithPlatformWhenOldVersionInspectImage() throws Exception {
setVersion("1.47");
ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base");
URI exportUri = new URI("/v1.47/images/docker.io/paketobuildpacks/builder:base/get");
given(DockerApiTests.this.http.get(exportUri)).willReturn(responseOf("export.tar"));
MultiValueMap<String, String> contents = new LinkedMultiValueMap<>();
this.api.exportLayers(reference, LINUX_ARM64_PLATFORM, (name, archive) -> {
ByteArrayOutputStream out = new ByteArrayOutputStream();
archive.writeTo(out);
try (TarArchiveInputStream in = new TarArchiveInputStream(
new ByteArrayInputStream(out.toByteArray()))) {
TarArchiveEntry entry = in.getNextEntry();
while (entry != null) {
contents.add(name, entry.getName());
entry = in.getNextEntry();
}
}
});
assertThat(contents).hasSize(3)
.containsKeys("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar",
"74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar",
"a69532b5b92bb891fbd9fa1a6b3af9087ea7050255f59ba61a796f8555ecd783/layer.tar");
assertThat(contents.get("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar"))
.containsExactly("/cnb/order.toml");
assertThat(contents.get("74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar"))
.containsExactly("/cnb/stack.toml");
}
@Test
void exportLayersWithSymlinksExportsLayerTars() throws Exception {
ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base");
URI exportUri = new URI(IMAGES_URL + "/docker.io/paketobuildpacks/builder:base/get");
given(DockerApiTests.this.http.get(exportUri)).willReturn(responseOf("export-symlinks.tar"));
MultiValueMap<String, String> contents = new LinkedMultiValueMap<>();
this.api.exportLayers(reference, (name, archive) -> {
ByteArrayOutputStream out = new ByteArrayOutputStream();
archive.writeTo(out);
try (TarArchiveInputStream in = new TarArchiveInputStream(
new ByteArrayInputStream(out.toByteArray()))) {
TarArchiveEntry entry = in.getNextEntry();
while (entry != null) {
contents.add(name, entry.getName());
entry = in.getNextEntry();
}
}
});
assertThat(contents).hasSize(3)
.containsKeys("6aa3691a73805f608e5fce69fb6bc89aec8362f58a6b4be2682515e9cfa3cc1a.tar",
"762e198f655bc2580ef3e56b538810fd2b9981bd707f8a44c70344b58f9aee68.tar",
"d3cc975ad97fdfbb73d9daf157e7f658d6117249fd9c237e3856ad173c87e1d2.tar");
assertThat(contents.get("d3cc975ad97fdfbb73d9daf157e7f658d6117249fd9c237e3856ad173c87e1d2.tar"))
.containsExactly("/cnb/order.toml");
assertThat(contents.get("762e198f655bc2580ef3e56b538810fd2b9981bd707f8a44c70344b58f9aee68.tar"))
.containsExactly("/cnb/stack.toml");
}
@Test
@SuppressWarnings("NullAway") // Test null check
void tagWhenReferenceIsNullThrowsException() {
ImageReference tag = ImageReference.of("localhost:5000/ubuntu");
assertThatIllegalArgumentException().isThrownBy(() -> this.api.tag(null, tag))
.withMessage("'sourceReference' must not be null");
}
@Test
@SuppressWarnings("NullAway") // Test null check
void tagWhenTargetIsNullThrowsException() {
ImageReference reference = ImageReference.of("localhost:5000/ubuntu");
assertThatIllegalArgumentException().isThrownBy(() -> this.api.tag(reference, null))
.withMessage("'targetReference' must not be null");
}
@Test
void tagTagsImage() throws Exception {
ImageReference sourceReference = ImageReference.of("localhost:5000/ubuntu");
ImageReference targetReference = ImageReference.of("localhost:5000/ubuntu:tagged");
URI tagURI = new URI(IMAGES_URL + "/localhost:5000/ubuntu/tag?repo=localhost%3A5000%2Fubuntu&tag=tagged");
given(http().post(tagURI)).willReturn(emptyResponse());
this.api.tag(sourceReference, targetReference);
then(http()).should().post(tagURI);
}
@Test
void tagRenamesImage() throws Exception {
ImageReference sourceReference = ImageReference.of("localhost:5000/ubuntu");
ImageReference targetReference = ImageReference.of("localhost:5000/ubuntu-2");
URI tagURI = new URI(IMAGES_URL + "/localhost:5000/ubuntu/tag?repo=localhost%3A5000%2Fubuntu-2");
given(http().post(tagURI)).willReturn(emptyResponse());
this.api.tag(sourceReference, targetReference);
then(http()).should().post(tagURI);
}
}
Domain
Source
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free