@@ -103,16 +103,23 @@ public void build(BuildRequest request) throws DockerEngineException, IOExceptio
103103 validateBindings (request .getBindings ());
104104 String domain = request .getBuilder ().getDomain ();
105105 PullPolicy pullPolicy = request .getPullPolicy ();
106- ImageFetcher imageFetcher = new ImageFetcher (domain , getBuilderAuthHeader (), pullPolicy ,
107- request .getImagePlatform ());
108- Image builderImage = imageFetcher .fetchImage (ImageType .BUILDER , request .getBuilder ());
106+ ImagePlatform platform = request .getImagePlatform ();
107+ boolean specifiedPlatform = request .getImagePlatform () != null ;
108+ ImageFetcher imageFetcher = new ImageFetcher (domain , getBuilderAuthHeader (), pullPolicy );
109+ Image builderImage = imageFetcher .fetchImage (ImageType .BUILDER , request .getBuilder (), platform );
109110 BuilderMetadata builderMetadata = BuilderMetadata .fromImage (builderImage );
110111 request = withRunImageIfNeeded (request , builderMetadata );
111- Image runImage = imageFetcher .fetchImage (ImageType .RUNNER , request .getRunImage ());
112+ platform = (platform != null ) ? platform : ImagePlatform .from (builderImage );
113+ Image runImage = imageFetcher .fetchImage (ImageType .RUNNER , request .getRunImage (), platform );
114+ if (specifiedPlatform && runImage .getPrimaryDigest () != null ) {
115+ request = request .withRunImage (request .getRunImage ().withDigest (runImage .getPrimaryDigest ()));
116+ runImage = imageFetcher .fetchImage (ImageType .RUNNER , request .getRunImage (), platform );
117+ }
112118 assertStackIdsMatch (runImage , builderImage );
113119 BuildOwner buildOwner = BuildOwner .fromEnv (builderImage .getConfig ().getEnv ());
114120 BuildpackLayersMetadata buildpackLayersMetadata = BuildpackLayersMetadata .fromImage (builderImage );
115- Buildpacks buildpacks = getBuildpacks (request , imageFetcher , builderMetadata , buildpackLayersMetadata );
121+ Buildpacks buildpacks = getBuildpacks (request , imageFetcher , platform , builderMetadata ,
122+ buildpackLayersMetadata );
116123 EphemeralBuilder ephemeralBuilder = new EphemeralBuilder (buildOwner , builderImage , request .getName (),
117124 builderMetadata , request .getCreator (), request .getEnv (), buildpacks );
118125 executeLifecycle (request , ephemeralBuilder );
@@ -156,9 +163,9 @@ private void assertStackIdsMatch(Image runImage, Image builderImage) {
156163 }
157164 }
158165
159- private Buildpacks getBuildpacks (BuildRequest request , ImageFetcher imageFetcher , BuilderMetadata builderMetadata ,
160- BuildpackLayersMetadata buildpackLayersMetadata ) {
161- BuildpackResolverContext resolverContext = new BuilderResolverContext (imageFetcher , builderMetadata ,
166+ private Buildpacks getBuildpacks (BuildRequest request , ImageFetcher imageFetcher , ImagePlatform platform ,
167+ BuilderMetadata builderMetadata , BuildpackLayersMetadata buildpackLayersMetadata ) {
168+ BuildpackResolverContext resolverContext = new BuilderResolverContext (imageFetcher , platform , builderMetadata ,
162169 buildpackLayersMetadata );
163170 return BuildpackResolvers .resolveAll (resolverContext , request .getBuildpacks ());
164171 }
@@ -227,51 +234,74 @@ private class ImageFetcher {
227234
228235 private final PullPolicy pullPolicy ;
229236
230- private ImagePlatform defaultPlatform ;
231-
232- ImageFetcher (String domain , String authHeader , PullPolicy pullPolicy , ImagePlatform platform ) {
237+ ImageFetcher (String domain , String authHeader , PullPolicy pullPolicy ) {
233238 this .domain = domain ;
234239 this .authHeader = authHeader ;
235240 this .pullPolicy = pullPolicy ;
236- this .defaultPlatform = platform ;
237241 }
238242
239- Image fetchImage (ImageType type , ImageReference reference ) throws IOException {
243+ Image fetchImage (ImageType type , ImageReference reference , ImagePlatform platform ) throws IOException {
240244 Assert .notNull (type , "Type must not be null" );
241245 Assert .notNull (reference , "Reference must not be null" );
242246 Assert .state (this .authHeader == null || reference .getDomain ().equals (this .domain ),
243247 () -> String .format ("%s '%s' must be pulled from the '%s' authenticated registry" ,
244248 StringUtils .capitalize (type .getDescription ()), reference , this .domain ));
245249 if (this .pullPolicy == PullPolicy .ALWAYS ) {
246- return checkPlatformMismatch ( pullImage ( reference , type ) , reference );
250+ return pullImageAndCheckForPlatformMismatch ( type , reference , platform );
247251 }
248252 try {
249- return checkPlatformMismatch (Builder .this .docker .image ().inspect (reference ), reference );
253+ Image image = Builder .this .docker .image ().inspect (reference , platform );
254+ return checkPlatformMismatch (image , reference , platform );
250255 }
251256 catch (DockerEngineException ex ) {
252257 if (this .pullPolicy == PullPolicy .IF_NOT_PRESENT && ex .getStatusCode () == 404 ) {
253- return checkPlatformMismatch (pullImage (reference , type ), reference );
258+ return pullImageAndCheckForPlatformMismatch (type , reference , platform );
259+ }
260+ throw ex ;
261+ }
262+ }
263+
264+ private Image pullImageAndCheckForPlatformMismatch (ImageType type , ImageReference reference ,
265+ ImagePlatform platform ) throws IOException {
266+ try {
267+ Image image = pullImage (reference , type , platform );
268+ return checkPlatformMismatch (image , reference , platform );
269+ }
270+ catch (DockerEngineException ex ) {
271+ // Try to throw our own exception for consistent log output. Matching
272+ // on the message is a little brittle, but it doesn't matter too much
273+ // if it fails as the original exception is still enough to stop the build
274+ if (platform != null && ex .getMessage ().contains ("does not provide the specified platform" )) {
275+ throwAsPlatformMismatchException (type , reference , platform , ex );
254276 }
255277 throw ex ;
256278 }
257279 }
258280
259- private Image pullImage (ImageReference reference , ImageType imageType ) throws IOException {
281+ private void throwAsPlatformMismatchException (ImageType type , ImageReference reference , ImagePlatform platform ,
282+ Throwable cause ) throws IOException {
283+ try {
284+ Image image = pullImage (reference , type , null );
285+ throw new PlatformMismatchException (reference , platform , ImagePlatform .from (image ), cause );
286+ }
287+ catch (DockerEngineException ex ) {
288+ }
289+ }
290+
291+ private Image pullImage (ImageReference reference , ImageType imageType , ImagePlatform platform )
292+ throws IOException {
260293 TotalProgressPullListener listener = new TotalProgressPullListener (
261- Builder .this .log .pullingImage (reference , this . defaultPlatform , imageType ));
262- Image image = Builder .this .docker .image ().pull (reference , this . defaultPlatform , listener , this .authHeader );
294+ Builder .this .log .pullingImage (reference , platform , imageType ));
295+ Image image = Builder .this .docker .image ().pull (reference , platform , listener , this .authHeader );
263296 Builder .this .log .pulledImage (image , imageType );
264- if (this .defaultPlatform == null ) {
265- this .defaultPlatform = ImagePlatform .from (image );
266- }
267297 return image ;
268298 }
269299
270- private Image checkPlatformMismatch (Image image , ImageReference imageReference ) {
271- if (this . defaultPlatform != null ) {
272- ImagePlatform imagePlatform = ImagePlatform .from (image );
273- if (!imagePlatform .equals (this . defaultPlatform )) {
274- throw new PlatformMismatchException (imageReference , this . defaultPlatform , imagePlatform );
300+ private Image checkPlatformMismatch (Image image , ImageReference reference , ImagePlatform requestedPlatform ) {
301+ if (requestedPlatform != null ) {
302+ ImagePlatform actualPlatform = ImagePlatform .from (image );
303+ if (!actualPlatform .equals (requestedPlatform )) {
304+ throw new PlatformMismatchException (reference , requestedPlatform , actualPlatform , null );
275305 }
276306 }
277307 return image ;
@@ -282,9 +312,9 @@ private Image checkPlatformMismatch(Image image, ImageReference imageReference)
282312 private static final class PlatformMismatchException extends RuntimeException {
283313
284314 private PlatformMismatchException (ImageReference imageReference , ImagePlatform requestedPlatform ,
285- ImagePlatform actualPlatform ) {
315+ ImagePlatform actualPlatform , Throwable cause ) {
286316 super ("Image platform mismatch detected. The configured platform '%s' is not supported by the image '%s'. Requested platform '%s' but got '%s'"
287- .formatted (requestedPlatform , imageReference , requestedPlatform , actualPlatform ));
317+ .formatted (requestedPlatform , imageReference , requestedPlatform , actualPlatform ), cause );
288318 }
289319
290320 }
@@ -296,13 +326,16 @@ private class BuilderResolverContext implements BuildpackResolverContext {
296326
297327 private final ImageFetcher imageFetcher ;
298328
329+ private final ImagePlatform platform ;
330+
299331 private final BuilderMetadata builderMetadata ;
300332
301333 private final BuildpackLayersMetadata buildpackLayersMetadata ;
302334
303- BuilderResolverContext (ImageFetcher imageFetcher , BuilderMetadata builderMetadata ,
335+ BuilderResolverContext (ImageFetcher imageFetcher , ImagePlatform platform , BuilderMetadata builderMetadata ,
304336 BuildpackLayersMetadata buildpackLayersMetadata ) {
305337 this .imageFetcher = imageFetcher ;
338+ this .platform = platform ;
306339 this .builderMetadata = builderMetadata ;
307340 this .buildpackLayersMetadata = buildpackLayersMetadata ;
308341 }
@@ -319,7 +352,7 @@ public BuildpackLayersMetadata getBuildpackLayersMetadata() {
319352
320353 @ Override
321354 public Image fetchImage (ImageReference reference , ImageType imageType ) throws IOException {
322- return this .imageFetcher .fetchImage (imageType , reference );
355+ return this .imageFetcher .fetchImage (imageType , reference , this . platform );
323356 }
324357
325358 @ Override
0 commit comments