@@ -146,16 +146,24 @@ public void build(BuildRequest request) throws DockerEngineException, IOExceptio
146146 this .log .start (request );
147147 validateBindings (request .getBindings ());
148148 PullPolicy pullPolicy = request .getPullPolicy ();
149- ImageFetcher imageFetcher = new ImageFetcher (this .dockerConfiguration .builderRegistryAuthentication (),
150- pullPolicy , request .getImagePlatform ());
151- Image builderImage = imageFetcher .fetchImage (ImageType .BUILDER , request .getBuilder ());
149+ ImagePlatform platform = request .getImagePlatform ();
150+ boolean specifiedPlatform = request .getImagePlatform () != null ;
151+ DockerRegistryAuthentication registryAuthentication = this .dockerConfiguration .builderRegistryAuthentication ();
152+ ImageFetcher imageFetcher = new ImageFetcher (registryAuthentication , pullPolicy );
153+ Image builderImage = imageFetcher .fetchImage (ImageType .BUILDER , request .getBuilder (), platform );
152154 BuilderMetadata builderMetadata = BuilderMetadata .fromImage (builderImage );
153155 request = withRunImageIfNeeded (request , builderMetadata );
154- Image runImage = imageFetcher .fetchImage (ImageType .RUNNER , request .getRunImage ());
156+ platform = (platform != null ) ? platform : ImagePlatform .from (builderImage );
157+ Image runImage = imageFetcher .fetchImage (ImageType .RUNNER , request .getRunImage (), platform );
158+ if (specifiedPlatform && runImage .getPrimaryDigest () != null ) {
159+ request = request .withRunImage (request .getRunImage ().withDigest (runImage .getPrimaryDigest ()));
160+ runImage = imageFetcher .fetchImage (ImageType .RUNNER , request .getRunImage (), platform );
161+ }
155162 assertStackIdsMatch (runImage , builderImage );
156163 BuildOwner buildOwner = BuildOwner .fromEnv (builderImage .getConfig ().getEnv ());
157164 BuildpackLayersMetadata buildpackLayersMetadata = BuildpackLayersMetadata .fromImage (builderImage );
158- Buildpacks buildpacks = getBuildpacks (request , imageFetcher , builderMetadata , buildpackLayersMetadata );
165+ Buildpacks buildpacks = getBuildpacks (request , imageFetcher , platform , builderMetadata ,
166+ buildpackLayersMetadata );
159167 EphemeralBuilder ephemeralBuilder = new EphemeralBuilder (buildOwner , builderImage , request .getName (),
160168 builderMetadata , request .getCreator (), request .getEnv (), buildpacks );
161169 executeLifecycle (request , ephemeralBuilder );
@@ -199,9 +207,9 @@ private void assertStackIdsMatch(Image runImage, Image builderImage) {
199207 }
200208 }
201209
202- private Buildpacks getBuildpacks (BuildRequest request , ImageFetcher imageFetcher , BuilderMetadata builderMetadata ,
203- BuildpackLayersMetadata buildpackLayersMetadata ) {
204- BuildpackResolverContext resolverContext = new BuilderResolverContext (imageFetcher , builderMetadata ,
210+ private Buildpacks getBuildpacks (BuildRequest request , ImageFetcher imageFetcher , ImagePlatform platform ,
211+ BuilderMetadata builderMetadata , BuildpackLayersMetadata buildpackLayersMetadata ) {
212+ BuildpackResolverContext resolverContext = new BuilderResolverContext (imageFetcher , platform , builderMetadata ,
205213 buildpackLayersMetadata );
206214 return BuildpackResolvers .resolveAll (resolverContext , request .getBuildpacks ());
207215 }
@@ -263,49 +271,71 @@ private class ImageFetcher {
263271
264272 private final PullPolicy pullPolicy ;
265273
266- private ImagePlatform defaultPlatform ;
267-
268- ImageFetcher (DockerRegistryAuthentication registryAuthentication , PullPolicy pullPolicy ,
269- ImagePlatform platform ) {
274+ ImageFetcher (DockerRegistryAuthentication registryAuthentication , PullPolicy pullPolicy ) {
270275 this .registryAuthentication = registryAuthentication ;
271276 this .pullPolicy = pullPolicy ;
272- this .defaultPlatform = platform ;
273277 }
274278
275- Image fetchImage (ImageType type , ImageReference reference ) throws IOException {
279+ Image fetchImage (ImageType type , ImageReference reference , ImagePlatform platform ) throws IOException {
276280 Assert .notNull (type , "'type' must not be null" );
277281 Assert .notNull (reference , "'reference' must not be null" );
278282 if (this .pullPolicy == PullPolicy .ALWAYS ) {
279- return checkPlatformMismatch ( pullImage ( reference , type ) , reference );
283+ return pullImageAndCheckForPlatformMismatch ( type , reference , platform );
280284 }
281285 try {
282- return checkPlatformMismatch (Builder .this .docker .image ().inspect (reference ), reference );
286+ Image image = Builder .this .docker .image ().inspect (reference , platform );
287+ return checkPlatformMismatch (image , reference , platform );
283288 }
284289 catch (DockerEngineException ex ) {
285290 if (this .pullPolicy == PullPolicy .IF_NOT_PRESENT && ex .getStatusCode () == 404 ) {
286- return checkPlatformMismatch (pullImage (reference , type ), reference );
291+ return pullImageAndCheckForPlatformMismatch (type , reference , platform );
292+ }
293+ throw ex ;
294+ }
295+ }
296+
297+ private Image pullImageAndCheckForPlatformMismatch (ImageType type , ImageReference reference ,
298+ ImagePlatform platform ) throws IOException {
299+ try {
300+ Image image = pullImage (reference , type , platform );
301+ return checkPlatformMismatch (image , reference , platform );
302+ }
303+ catch (DockerEngineException ex ) {
304+ // Try to throw our own exception for consistent log output. Matching
305+ // on the message is a little brittle, but it doesn't matter too much
306+ // if it fails as the original exception is still enough to stop the build
307+ if (platform != null && ex .getMessage ().contains ("does not provide the specified platform" )) {
308+ throwAsPlatformMismatchException (type , reference , platform , ex );
287309 }
288310 throw ex ;
289311 }
290312 }
291313
292- private Image pullImage (ImageReference reference , ImageType imageType ) throws IOException {
314+ private void throwAsPlatformMismatchException (ImageType type , ImageReference reference , ImagePlatform platform ,
315+ Throwable cause ) throws IOException {
316+ try {
317+ Image image = pullImage (reference , type , null );
318+ throw new PlatformMismatchException (reference , platform , ImagePlatform .from (image ), cause );
319+ }
320+ catch (DockerEngineException ex ) {
321+ }
322+ }
323+
324+ private Image pullImage (ImageReference reference , ImageType imageType , ImagePlatform platform )
325+ throws IOException {
293326 TotalProgressPullListener listener = new TotalProgressPullListener (
294- Builder .this .log .pullingImage (reference , this . defaultPlatform , imageType ));
327+ Builder .this .log .pullingImage (reference , platform , imageType ));
295328 String authHeader = authHeader (this .registryAuthentication , reference );
296- Image image = Builder .this .docker .image ().pull (reference , this . defaultPlatform , listener , authHeader );
329+ Image image = Builder .this .docker .image ().pull (reference , platform , listener , authHeader );
297330 Builder .this .log .pulledImage (image , imageType );
298- if (this .defaultPlatform == null ) {
299- this .defaultPlatform = ImagePlatform .from (image );
300- }
301331 return image ;
302332 }
303333
304- private Image checkPlatformMismatch (Image image , ImageReference imageReference ) {
305- if (this . defaultPlatform != null ) {
306- ImagePlatform imagePlatform = ImagePlatform .from (image );
307- if (!imagePlatform .equals (this . defaultPlatform )) {
308- throw new PlatformMismatchException (imageReference , this . defaultPlatform , imagePlatform );
334+ private Image checkPlatformMismatch (Image image , ImageReference reference , ImagePlatform requestedPlatform ) {
335+ if (requestedPlatform != null ) {
336+ ImagePlatform actualPlatform = ImagePlatform .from (image );
337+ if (!actualPlatform .equals (requestedPlatform )) {
338+ throw new PlatformMismatchException (reference , requestedPlatform , actualPlatform , null );
309339 }
310340 }
311341 return image ;
@@ -316,9 +346,9 @@ private Image checkPlatformMismatch(Image image, ImageReference imageReference)
316346 private static final class PlatformMismatchException extends RuntimeException {
317347
318348 private PlatformMismatchException (ImageReference imageReference , ImagePlatform requestedPlatform ,
319- ImagePlatform actualPlatform ) {
349+ ImagePlatform actualPlatform , Throwable cause ) {
320350 super ("Image platform mismatch detected. The configured platform '%s' is not supported by the image '%s'. Requested platform '%s' but got '%s'"
321- .formatted (requestedPlatform , imageReference , requestedPlatform , actualPlatform ));
351+ .formatted (requestedPlatform , imageReference , requestedPlatform , actualPlatform ), cause );
322352 }
323353
324354 }
@@ -364,13 +394,16 @@ private class BuilderResolverContext implements BuildpackResolverContext {
364394
365395 private final ImageFetcher imageFetcher ;
366396
397+ private final ImagePlatform platform ;
398+
367399 private final BuilderMetadata builderMetadata ;
368400
369401 private final BuildpackLayersMetadata buildpackLayersMetadata ;
370402
371- BuilderResolverContext (ImageFetcher imageFetcher , BuilderMetadata builderMetadata ,
403+ BuilderResolverContext (ImageFetcher imageFetcher , ImagePlatform platform , BuilderMetadata builderMetadata ,
372404 BuildpackLayersMetadata buildpackLayersMetadata ) {
373405 this .imageFetcher = imageFetcher ;
406+ this .platform = platform ;
374407 this .builderMetadata = builderMetadata ;
375408 this .buildpackLayersMetadata = buildpackLayersMetadata ;
376409 }
@@ -387,7 +420,7 @@ public BuildpackLayersMetadata getBuildpackLayersMetadata() {
387420
388421 @ Override
389422 public Image fetchImage (ImageReference reference , ImageType imageType ) throws IOException {
390- return this .imageFetcher .fetchImage (imageType , reference );
423+ return this .imageFetcher .fetchImage (imageType , reference , this . platform );
391424 }
392425
393426 @ Override
0 commit comments