Skip to content

Commit 53bda71

Browse files
committed
Use platform when exporting buildpack images
Update `DockerApi` and `Builder` to support export of images with a specified platform. This prevents 'NotFound: content digest sha256:' errors when building an amd64 image on an arm64 machine. Fixes gh-46665
1 parent f3b0d5a commit 53bda71

File tree

3 files changed

+95
-17
lines changed
  • spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src

3 files changed

+95
-17
lines changed

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ public Image fetchImage(ImageReference reference, ImageType imageType) throws IO
358358
@Override
359359
public void exportImageLayers(ImageReference reference, IOBiConsumer<String, TarArchive> exports)
360360
throws IOException {
361-
Builder.this.docker.image().exportLayers(reference, exports);
361+
Builder.this.docker.image().exportLayers(reference, this.platform, exports);
362362
}
363363

364364
}

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ private JsonStream jsonStream() {
122122
return this.jsonStream;
123123
}
124124

125+
URI buildPlatformJsonUrl(Feature feature, ImagePlatform platform, String path) {
126+
if (platform != null && getApiVersion().supports(feature.minimumVersion())) {
127+
return buildUrl(feature, path, "platform", platform.toJson());
128+
}
129+
return buildUrl(path);
130+
}
131+
125132
private URI buildUrl(String path, Collection<?> params) {
126133
return buildUrl(Feature.BASELINE, path, (params != null) ? params.toArray() : null);
127134
}
@@ -227,9 +234,8 @@ public Image pull(ImageReference reference, ImagePlatform platform,
227234
UpdateListener<PullImageUpdateEvent> listener, String registryAuth) throws IOException {
228235
Assert.notNull(reference, "Reference must not be null");
229236
Assert.notNull(listener, "Listener must not be null");
230-
URI createUri = (platform != null)
231-
? buildUrl(Feature.PLATFORM, "/images/create", "fromImage", reference, "platform", platform)
232-
: buildUrl("/images/create", "fromImage", reference);
237+
URI createUri = (platform != null) ? buildUrl(Feature.PLATFORM_IMAGE_PULL, "/images/create", "fromImage",
238+
reference, "platform", platform) : buildUrl("/images/create", "fromImage", reference);
233239
DigestCaptureUpdateListener digestCapture = new DigestCaptureUpdateListener();
234240
listener.onStart();
235241
try {
@@ -336,9 +342,24 @@ public void exportLayerFiles(ImageReference reference, IOBiConsumer<String, Path
336342
*/
337343
public void exportLayers(ImageReference reference, IOBiConsumer<String, TarArchive> exports)
338344
throws IOException {
345+
exportLayers(reference, null, exports);
346+
}
347+
348+
/**
349+
* Export the layers of an image as {@link TarArchive TarArchives}.
350+
* @param reference the reference to export
351+
* @param platform the platform (os/architecture/variant) of the image to export.
352+
* Ignored on older versions of Docker.
353+
* @param exports a consumer to receive the layers (contents can only be accessed
354+
* during the callback)
355+
* @throws IOException on IO error
356+
* @since 3.4.12
357+
*/
358+
public void exportLayers(ImageReference reference, ImagePlatform platform,
359+
IOBiConsumer<String, TarArchive> exports) throws IOException {
339360
Assert.notNull(reference, "Reference must not be null");
340361
Assert.notNull(exports, "Exports must not be null");
341-
URI uri = buildUrl("/images/" + reference + "/get");
362+
URI uri = buildPlatformJsonUrl(Feature.PLATFORM_IMAGE_EXPORT, platform, "/images/" + reference + "/get");
342363
try (Response response = http().get(uri)) {
343364
try (ExportedImageTar exportedImageTar = new ExportedImageTar(reference, response.getContent())) {
344365
exportedImageTar.exportLayers(exports);
@@ -382,20 +403,13 @@ public Image inspect(ImageReference reference, ImagePlatform platform) throws IO
382403
// The Docker documentation is incomplete but platform parameters
383404
// are supported since 1.49 (see https://github.com/moby/moby/pull/49586)
384405
Assert.notNull(reference, "Reference must not be null");
385-
URI inspectUrl = inspectUrl(reference, platform);
406+
URI inspectUrl = buildPlatformJsonUrl(Feature.PLATFORM_IMAGE_INSPECT, platform,
407+
"/images/" + reference + "/json");
386408
try (Response response = http().get(inspectUrl)) {
387409
return Image.of(response.getContent());
388410
}
389411
}
390412

391-
private URI inspectUrl(ImageReference reference, ImagePlatform platform) {
392-
String path = "/images/" + reference + "/json";
393-
if (platform != null && getApiVersion().supports(Feature.PLATFORM_INSPECT.minimumVersion())) {
394-
return buildUrl(Feature.PLATFORM_INSPECT, path, "platform", platform.toJson());
395-
}
396-
return buildUrl(path);
397-
}
398-
399413
public void tag(ImageReference sourceReference, ImageReference targetReference) throws IOException {
400414
Assert.notNull(sourceReference, "SourceReference must not be null");
401415
Assert.notNull(targetReference, "TargetReference must not be null");
@@ -437,7 +451,8 @@ public ContainerReference create(ContainerConfig config, ImagePlatform platform,
437451
}
438452

439453
private ContainerReference createContainer(ContainerConfig config, ImagePlatform platform) throws IOException {
440-
URI createUri = (platform != null) ? buildUrl(Feature.PLATFORM, "/containers/create", "platform", platform)
454+
URI createUri = (platform != null)
455+
? buildUrl(Feature.PLATFORM_CONTAINER_CREATE, "/containers/create", "platform", platform)
441456
: buildUrl("/containers/create");
442457
try (Response response = http().post(createUri, "application/json", config::writeTo)) {
443458
return ContainerReference
@@ -639,9 +654,13 @@ enum Feature {
639654

640655
BASELINE(ApiVersion.of(1, 24)),
641656

642-
PLATFORM(ApiVersion.of(1, 41)),
657+
PLATFORM_IMAGE_PULL(ApiVersion.of(1, 41)),
658+
659+
PLATFORM_CONTAINER_CREATE(ApiVersion.of(1, 41)),
660+
661+
PLATFORM_IMAGE_INSPECT(ApiVersion.of(1, 49)),
643662

644-
PLATFORM_INSPECT(ApiVersion.of(1, 49));
663+
PLATFORM_IMAGE_EXPORT(ApiVersion.of(1, 48));
645664

646665
private final ApiVersion minimumVersion;
647666

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,65 @@ void exportLayersExportsLayerTars() throws Exception {
505505
.containsExactly("/cnb/stack.toml");
506506
}
507507

508+
@Test
509+
void exportLayersExportsLayerTarsWithPlatformWhenSupportedVersion() throws Exception {
510+
setVersion("1.48");
511+
ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base");
512+
URI exportUri = new URI("/v1.48/images/docker.io/paketobuildpacks/builder:base/get?platform="
513+
+ ENCODED_LINUX_ARM64_PLATFORM_JSON);
514+
given(DockerApiTests.this.http.get(exportUri)).willReturn(responseOf("export.tar"));
515+
MultiValueMap<String, String> contents = new LinkedMultiValueMap<>();
516+
this.api.exportLayers(reference, LINUX_ARM64_PLATFORM, (name, archive) -> {
517+
ByteArrayOutputStream out = new ByteArrayOutputStream();
518+
archive.writeTo(out);
519+
try (TarArchiveInputStream in = new TarArchiveInputStream(
520+
new ByteArrayInputStream(out.toByteArray()))) {
521+
TarArchiveEntry entry = in.getNextEntry();
522+
while (entry != null) {
523+
contents.add(name, entry.getName());
524+
entry = in.getNextEntry();
525+
}
526+
}
527+
});
528+
assertThat(contents).hasSize(3)
529+
.containsKeys("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar",
530+
"74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar",
531+
"a69532b5b92bb891fbd9fa1a6b3af9087ea7050255f59ba61a796f8555ecd783/layer.tar");
532+
assertThat(contents.get("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar"))
533+
.containsExactly("/cnb/order.toml");
534+
assertThat(contents.get("74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar"))
535+
.containsExactly("/cnb/stack.toml");
536+
}
537+
538+
@Test
539+
void exportLayersExportsLayerTarsWithPlatformWhenOldVersionInspectImage() throws Exception {
540+
setVersion("1.47");
541+
ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base");
542+
URI exportUri = new URI("/v1.47/images/docker.io/paketobuildpacks/builder:base/get");
543+
given(DockerApiTests.this.http.get(exportUri)).willReturn(responseOf("export.tar"));
544+
MultiValueMap<String, String> contents = new LinkedMultiValueMap<>();
545+
this.api.exportLayers(reference, LINUX_ARM64_PLATFORM, (name, archive) -> {
546+
ByteArrayOutputStream out = new ByteArrayOutputStream();
547+
archive.writeTo(out);
548+
try (TarArchiveInputStream in = new TarArchiveInputStream(
549+
new ByteArrayInputStream(out.toByteArray()))) {
550+
TarArchiveEntry entry = in.getNextEntry();
551+
while (entry != null) {
552+
contents.add(name, entry.getName());
553+
entry = in.getNextEntry();
554+
}
555+
}
556+
});
557+
assertThat(contents).hasSize(3)
558+
.containsKeys("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar",
559+
"74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar",
560+
"a69532b5b92bb891fbd9fa1a6b3af9087ea7050255f59ba61a796f8555ecd783/layer.tar");
561+
assertThat(contents.get("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar"))
562+
.containsExactly("/cnb/order.toml");
563+
assertThat(contents.get("74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar"))
564+
.containsExactly("/cnb/stack.toml");
565+
}
566+
508567
@Test
509568
void exportLayersWithSymlinksExportsLayerTars() throws Exception {
510569
ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base");

0 commit comments

Comments
 (0)