Skip to content

Commit c90f61c

Browse files
committed
Merge branch '3.4.x' into 3.5.x
Closes gh-48127
2 parents 91cb68b + 53bda71 commit c90f61c

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
@@ -426,7 +426,7 @@ public Image fetchImage(ImageReference reference, ImageType imageType) throws IO
426426
@Override
427427
public void exportImageLayers(ImageReference reference, IOBiConsumer<String, TarArchive> exports)
428428
throws IOException {
429-
Builder.this.docker.image().exportLayers(reference, exports);
429+
Builder.this.docker.image().exportLayers(reference, this.platform, exports);
430430
}
431431

432432
}

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
@@ -138,6 +138,13 @@ private JsonStream jsonStream() {
138138
return this.jsonStream;
139139
}
140140

141+
URI buildPlatformJsonUrl(Feature feature, ImagePlatform platform, String path) {
142+
if (platform != null && getApiVersion().supports(feature.minimumVersion())) {
143+
return buildUrl(feature, path, "platform", platform.toJson());
144+
}
145+
return buildUrl(path);
146+
}
147+
141148
private URI buildUrl(String path, Collection<?> params) {
142149
return buildUrl(Feature.BASELINE, path, (params != null) ? params.toArray() : null);
143150
}
@@ -243,9 +250,8 @@ public Image pull(ImageReference reference, ImagePlatform platform,
243250
UpdateListener<PullImageUpdateEvent> listener, String registryAuth) throws IOException {
244251
Assert.notNull(reference, "'reference' must not be null");
245252
Assert.notNull(listener, "'listener' must not be null");
246-
URI createUri = (platform != null)
247-
? buildUrl(Feature.PLATFORM, "/images/create", "fromImage", reference, "platform", platform)
248-
: buildUrl("/images/create", "fromImage", reference);
253+
URI createUri = (platform != null) ? buildUrl(Feature.PLATFORM_IMAGE_PULL, "/images/create", "fromImage",
254+
reference, "platform", platform) : buildUrl("/images/create", "fromImage", reference);
249255
DigestCaptureUpdateListener digestCapture = new DigestCaptureUpdateListener();
250256
listener.onStart();
251257
try {
@@ -324,9 +330,24 @@ public void load(ImageArchive archive, UpdateListener<LoadImageUpdateEvent> list
324330
*/
325331
public void exportLayers(ImageReference reference, IOBiConsumer<String, TarArchive> exports)
326332
throws IOException {
333+
exportLayers(reference, null, exports);
334+
}
335+
336+
/**
337+
* Export the layers of an image as {@link TarArchive TarArchives}.
338+
* @param reference the reference to export
339+
* @param platform the platform (os/architecture/variant) of the image to export.
340+
* Ignored on older versions of Docker.
341+
* @param exports a consumer to receive the layers (contents can only be accessed
342+
* during the callback)
343+
* @throws IOException on IO error
344+
* @since 3.4.12
345+
*/
346+
public void exportLayers(ImageReference reference, ImagePlatform platform,
347+
IOBiConsumer<String, TarArchive> exports) throws IOException {
327348
Assert.notNull(reference, "'reference' must not be null");
328349
Assert.notNull(exports, "'exports' must not be null");
329-
URI uri = buildUrl("/images/" + reference + "/get");
350+
URI uri = buildPlatformJsonUrl(Feature.PLATFORM_IMAGE_EXPORT, platform, "/images/" + reference + "/get");
330351
try (Response response = http().get(uri)) {
331352
try (ExportedImageTar exportedImageTar = new ExportedImageTar(reference, response.getContent())) {
332353
exportedImageTar.exportLayers(exports);
@@ -370,20 +391,13 @@ public Image inspect(ImageReference reference, ImagePlatform platform) throws IO
370391
// The Docker documentation is incomplete but platform parameters
371392
// are supported since 1.49 (see https://github.com/moby/moby/pull/49586)
372393
Assert.notNull(reference, "'reference' must not be null");
373-
URI inspectUrl = inspectUrl(reference, platform);
394+
URI inspectUrl = buildPlatformJsonUrl(Feature.PLATFORM_IMAGE_INSPECT, platform,
395+
"/images/" + reference + "/json");
374396
try (Response response = http().get(inspectUrl)) {
375397
return Image.of(response.getContent());
376398
}
377399
}
378400

379-
private URI inspectUrl(ImageReference reference, ImagePlatform platform) {
380-
String path = "/images/" + reference + "/json";
381-
if (platform != null && getApiVersion().supports(Feature.PLATFORM_INSPECT.minimumVersion())) {
382-
return buildUrl(Feature.PLATFORM_INSPECT, path, "platform", platform.toJson());
383-
}
384-
return buildUrl(path);
385-
}
386-
387401
public void tag(ImageReference sourceReference, ImageReference targetReference) throws IOException {
388402
Assert.notNull(sourceReference, "'sourceReference' must not be null");
389403
Assert.notNull(targetReference, "'targetReference' must not be null");
@@ -425,7 +439,8 @@ public ContainerReference create(ContainerConfig config, ImagePlatform platform,
425439
}
426440

427441
private ContainerReference createContainer(ContainerConfig config, ImagePlatform platform) throws IOException {
428-
URI createUri = (platform != null) ? buildUrl(Feature.PLATFORM, "/containers/create", "platform", platform)
442+
URI createUri = (platform != null)
443+
? buildUrl(Feature.PLATFORM_CONTAINER_CREATE, "/containers/create", "platform", platform)
429444
: buildUrl("/containers/create");
430445
try (Response response = http().post(createUri, "application/json", config::writeTo)) {
431446
return ContainerReference
@@ -631,9 +646,13 @@ enum Feature {
631646

632647
BASELINE(ApiVersion.of(1, 24)),
633648

634-
PLATFORM(ApiVersion.of(1, 41)),
649+
PLATFORM_IMAGE_PULL(ApiVersion.of(1, 41)),
650+
651+
PLATFORM_CONTAINER_CREATE(ApiVersion.of(1, 41)),
652+
653+
PLATFORM_IMAGE_INSPECT(ApiVersion.of(1, 49)),
635654

636-
PLATFORM_INSPECT(ApiVersion.of(1, 49));
655+
PLATFORM_IMAGE_EXPORT(ApiVersion.of(1, 48));
637656

638657
private final ApiVersion minimumVersion;
639658

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
@@ -489,6 +489,65 @@ void exportLayersExportsLayerTars() throws Exception {
489489
.containsExactly("/cnb/stack.toml");
490490
}
491491

492+
@Test
493+
void exportLayersExportsLayerTarsWithPlatformWhenSupportedVersion() throws Exception {
494+
setVersion("1.48");
495+
ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base");
496+
URI exportUri = new URI("/v1.48/images/docker.io/paketobuildpacks/builder:base/get?platform="
497+
+ ENCODED_LINUX_ARM64_PLATFORM_JSON);
498+
given(DockerApiTests.this.http.get(exportUri)).willReturn(responseOf("export.tar"));
499+
MultiValueMap<String, String> contents = new LinkedMultiValueMap<>();
500+
this.api.exportLayers(reference, LINUX_ARM64_PLATFORM, (name, archive) -> {
501+
ByteArrayOutputStream out = new ByteArrayOutputStream();
502+
archive.writeTo(out);
503+
try (TarArchiveInputStream in = new TarArchiveInputStream(
504+
new ByteArrayInputStream(out.toByteArray()))) {
505+
TarArchiveEntry entry = in.getNextEntry();
506+
while (entry != null) {
507+
contents.add(name, entry.getName());
508+
entry = in.getNextEntry();
509+
}
510+
}
511+
});
512+
assertThat(contents).hasSize(3)
513+
.containsKeys("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar",
514+
"74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar",
515+
"a69532b5b92bb891fbd9fa1a6b3af9087ea7050255f59ba61a796f8555ecd783/layer.tar");
516+
assertThat(contents.get("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar"))
517+
.containsExactly("/cnb/order.toml");
518+
assertThat(contents.get("74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar"))
519+
.containsExactly("/cnb/stack.toml");
520+
}
521+
522+
@Test
523+
void exportLayersExportsLayerTarsWithPlatformWhenOldVersionInspectImage() throws Exception {
524+
setVersion("1.47");
525+
ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base");
526+
URI exportUri = new URI("/v1.47/images/docker.io/paketobuildpacks/builder:base/get");
527+
given(DockerApiTests.this.http.get(exportUri)).willReturn(responseOf("export.tar"));
528+
MultiValueMap<String, String> contents = new LinkedMultiValueMap<>();
529+
this.api.exportLayers(reference, LINUX_ARM64_PLATFORM, (name, archive) -> {
530+
ByteArrayOutputStream out = new ByteArrayOutputStream();
531+
archive.writeTo(out);
532+
try (TarArchiveInputStream in = new TarArchiveInputStream(
533+
new ByteArrayInputStream(out.toByteArray()))) {
534+
TarArchiveEntry entry = in.getNextEntry();
535+
while (entry != null) {
536+
contents.add(name, entry.getName());
537+
entry = in.getNextEntry();
538+
}
539+
}
540+
});
541+
assertThat(contents).hasSize(3)
542+
.containsKeys("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar",
543+
"74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar",
544+
"a69532b5b92bb891fbd9fa1a6b3af9087ea7050255f59ba61a796f8555ecd783/layer.tar");
545+
assertThat(contents.get("70bb7a3115f3d5c01099852112c7e05bf593789e510468edb06b6a9a11fa3b73/layer.tar"))
546+
.containsExactly("/cnb/order.toml");
547+
assertThat(contents.get("74a9a50ece13c025cf10e9110d9ddc86c995079c34e2a22a28d1a3d523222c6e/layer.tar"))
548+
.containsExactly("/cnb/stack.toml");
549+
}
550+
492551
@Test
493552
void exportLayersWithSymlinksExportsLayerTars() throws Exception {
494553
ImageReference reference = ImageReference.of("docker.io/paketobuildpacks/builder:base");

0 commit comments

Comments
 (0)