@@ -106,17 +106,25 @@ public void build(BuildRequest request) throws DockerEngineException, IOExceptio
106106 this .log .start (request );
107107 validateBindings (request .getBindings ());
108108 PullPolicy pullPolicy = request .getPullPolicy ();
109- ImageFetcher imageFetcher = new ImageFetcher (this .dockerConfiguration .builderRegistryAuthentication (),
110- pullPolicy , request .getImagePlatform ());
111- Image builderImage = imageFetcher .fetchImage (ImageType .BUILDER , request .getBuilder ());
109+ ImagePlatform platform = request .getImagePlatform ();
110+ boolean specifiedPlatform = request .getImagePlatform () != null ;
111+ DockerRegistryAuthentication registryAuthentication = this .dockerConfiguration .builderRegistryAuthentication ();
112+ ImageFetcher imageFetcher = new ImageFetcher (registryAuthentication , pullPolicy );
113+ Image builderImage = imageFetcher .fetchImage (ImageType .BUILDER , request .getBuilder (), platform );
112114 BuilderMetadata builderMetadata = BuilderMetadata .fromImage (builderImage );
113115 request = withRunImageIfNeeded (request , builderMetadata );
114116 Assert .state (request .getRunImage () != null , "'request.getRunImage()' must not be null" );
115- Image runImage = imageFetcher .fetchImage (ImageType .RUNNER , request .getRunImage ());
117+ platform = (platform != null ) ? platform : ImagePlatform .from (builderImage );
118+ Image runImage = imageFetcher .fetchImage (ImageType .RUNNER , request .getRunImage (), platform );
119+ if (specifiedPlatform && runImage .getPrimaryDigest () != null ) {
120+ request = request .withRunImage (request .getRunImage ().withDigest (runImage .getPrimaryDigest ()));
121+ runImage = imageFetcher .fetchImage (ImageType .RUNNER , request .getRunImage (), platform );
122+ }
116123 assertStackIdsMatch (runImage , builderImage );
117124 BuildOwner buildOwner = BuildOwner .fromEnv (builderImage .getConfig ().getEnv ());
118125 BuildpackLayersMetadata buildpackLayersMetadata = BuildpackLayersMetadata .fromImage (builderImage );
119- Buildpacks buildpacks = getBuildpacks (request , imageFetcher , builderMetadata , buildpackLayersMetadata );
126+ Buildpacks buildpacks = getBuildpacks (request , imageFetcher , platform , builderMetadata ,
127+ buildpackLayersMetadata );
120128 EphemeralBuilder ephemeralBuilder = new EphemeralBuilder (buildOwner , builderImage , request .getName (),
121129 builderMetadata , request .getCreator (), request .getEnv (), buildpacks );
122130 executeLifecycle (request , ephemeralBuilder );
@@ -160,9 +168,9 @@ private void assertStackIdsMatch(Image runImage, Image builderImage) {
160168 }
161169 }
162170
163- private Buildpacks getBuildpacks (BuildRequest request , ImageFetcher imageFetcher , BuilderMetadata builderMetadata ,
164- BuildpackLayersMetadata buildpackLayersMetadata ) {
165- BuildpackResolverContext resolverContext = new BuilderResolverContext (imageFetcher , builderMetadata ,
171+ private Buildpacks getBuildpacks (BuildRequest request , ImageFetcher imageFetcher , ImagePlatform platform ,
172+ BuilderMetadata builderMetadata , BuildpackLayersMetadata buildpackLayersMetadata ) {
173+ BuildpackResolverContext resolverContext = new BuilderResolverContext (imageFetcher , platform , builderMetadata ,
166174 buildpackLayersMetadata );
167175 return BuildpackResolvers .resolveAll (resolverContext , request .getBuildpacks ());
168176 }
@@ -225,49 +233,74 @@ private class ImageFetcher {
225233
226234 private final PullPolicy pullPolicy ;
227235
228- private @ Nullable ImagePlatform defaultPlatform ;
229-
230- ImageFetcher (@ Nullable DockerRegistryAuthentication registryAuthentication , PullPolicy pullPolicy ,
231- @ Nullable ImagePlatform platform ) {
236+ ImageFetcher (@ Nullable DockerRegistryAuthentication registryAuthentication , PullPolicy pullPolicy ) {
232237 this .registryAuthentication = registryAuthentication ;
233238 this .pullPolicy = pullPolicy ;
234- this .defaultPlatform = platform ;
235239 }
236240
237- Image fetchImage (ImageType type , ImageReference reference ) throws IOException {
241+ Image fetchImage (ImageType type , ImageReference reference , @ Nullable ImagePlatform platform )
242+ throws IOException {
238243 Assert .notNull (type , "'type' must not be null" );
239244 Assert .notNull (reference , "'reference' must not be null" );
240245 if (this .pullPolicy == PullPolicy .ALWAYS ) {
241- return checkPlatformMismatch ( pullImage ( reference , type ) , reference );
246+ return pullImageAndCheckForPlatformMismatch ( type , reference , platform );
242247 }
243248 try {
244- return checkPlatformMismatch (Builder .this .docker .image ().inspect (reference ), reference );
249+ Image image = Builder .this .docker .image ().inspect (reference , platform );
250+ return checkPlatformMismatch (image , reference , platform );
245251 }
246252 catch (DockerEngineException ex ) {
247253 if (this .pullPolicy == PullPolicy .IF_NOT_PRESENT && ex .getStatusCode () == 404 ) {
248- return checkPlatformMismatch ( pullImage ( reference , type ) , reference );
254+ return pullImageAndCheckForPlatformMismatch ( type , reference , platform );
249255 }
250256 throw ex ;
251257 }
252258 }
253259
254- private Image pullImage (ImageReference reference , ImageType imageType ) throws IOException {
260+ private Image pullImageAndCheckForPlatformMismatch (ImageType type , ImageReference reference ,
261+ @ Nullable ImagePlatform platform ) throws IOException {
262+ try {
263+ Image image = pullImage (reference , type , platform );
264+ return checkPlatformMismatch (image , reference , platform );
265+ }
266+ catch (DockerEngineException ex ) {
267+ // Try to throw our own exception for consistent log output. Matching
268+ // on the message is a little brittle, but it doesn't matter too much
269+ // if it fails as the original exception is still enough to stop the build
270+ if (platform != null && ex .getMessage () != null
271+ && ex .getMessage ().contains ("does not provide the specified platform" )) {
272+ throwAsPlatformMismatchException (type , reference , platform , ex );
273+ }
274+ throw ex ;
275+ }
276+ }
277+
278+ private void throwAsPlatformMismatchException (ImageType type , ImageReference reference , ImagePlatform platform ,
279+ @ Nullable Throwable cause ) throws IOException {
280+ try {
281+ Image image = pullImage (reference , type , null );
282+ throw new PlatformMismatchException (reference , platform , ImagePlatform .from (image ), cause );
283+ }
284+ catch (DockerEngineException ex ) {
285+ }
286+ }
287+
288+ private Image pullImage (ImageReference reference , ImageType imageType , @ Nullable ImagePlatform platform )
289+ throws IOException {
255290 TotalProgressPullListener listener = new TotalProgressPullListener (
256- Builder .this .log .pullingImage (reference , this . defaultPlatform , imageType ));
291+ Builder .this .log .pullingImage (reference , platform , imageType ));
257292 String authHeader = authHeader (this .registryAuthentication , reference );
258- Image image = Builder .this .docker .image ().pull (reference , this . defaultPlatform , listener , authHeader );
293+ Image image = Builder .this .docker .image ().pull (reference , platform , listener , authHeader );
259294 Builder .this .log .pulledImage (image , imageType );
260- if (this .defaultPlatform == null ) {
261- this .defaultPlatform = ImagePlatform .from (image );
262- }
263295 return image ;
264296 }
265297
266- private Image checkPlatformMismatch (Image image , ImageReference imageReference ) {
267- if (this .defaultPlatform != null ) {
268- ImagePlatform imagePlatform = ImagePlatform .from (image );
269- if (!imagePlatform .equals (this .defaultPlatform )) {
270- throw new PlatformMismatchException (imageReference , this .defaultPlatform , imagePlatform );
298+ private Image checkPlatformMismatch (Image image , ImageReference reference ,
299+ @ Nullable ImagePlatform requestedPlatform ) {
300+ if (requestedPlatform != null ) {
301+ ImagePlatform actualPlatform = ImagePlatform .from (image );
302+ if (!actualPlatform .equals (requestedPlatform )) {
303+ throw new PlatformMismatchException (reference , requestedPlatform , actualPlatform , null );
271304 }
272305 }
273306 return image ;
@@ -278,9 +311,9 @@ private Image checkPlatformMismatch(Image image, ImageReference imageReference)
278311 private static final class PlatformMismatchException extends RuntimeException {
279312
280313 private PlatformMismatchException (ImageReference imageReference , ImagePlatform requestedPlatform ,
281- ImagePlatform actualPlatform ) {
314+ ImagePlatform actualPlatform , @ Nullable Throwable cause ) {
282315 super ("Image platform mismatch detected. The configured platform '%s' is not supported by the image '%s'. Requested platform '%s' but got '%s'"
283- .formatted (requestedPlatform , imageReference , requestedPlatform , actualPlatform ));
316+ .formatted (requestedPlatform , imageReference , requestedPlatform , actualPlatform ), cause );
284317 }
285318
286319 }
@@ -326,13 +359,16 @@ private class BuilderResolverContext implements BuildpackResolverContext {
326359
327360 private final ImageFetcher imageFetcher ;
328361
362+ private final ImagePlatform platform ;
363+
329364 private final BuilderMetadata builderMetadata ;
330365
331366 private final BuildpackLayersMetadata buildpackLayersMetadata ;
332367
333- BuilderResolverContext (ImageFetcher imageFetcher , BuilderMetadata builderMetadata ,
368+ BuilderResolverContext (ImageFetcher imageFetcher , ImagePlatform platform , BuilderMetadata builderMetadata ,
334369 BuildpackLayersMetadata buildpackLayersMetadata ) {
335370 this .imageFetcher = imageFetcher ;
371+ this .platform = platform ;
336372 this .builderMetadata = builderMetadata ;
337373 this .buildpackLayersMetadata = buildpackLayersMetadata ;
338374 }
@@ -349,7 +385,7 @@ public BuildpackLayersMetadata getBuildpackLayersMetadata() {
349385
350386 @ Override
351387 public Image fetchImage (ImageReference reference , ImageType imageType ) throws IOException {
352- return this .imageFetcher .fetchImage (imageType , reference );
388+ return this .imageFetcher .fetchImage (imageType , reference , this . platform );
353389 }
354390
355391 @ Override
0 commit comments