@@ -103,23 +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- ImagePlatform requestedPlatform = request .getImagePlatform ();
107- ImageFetcher imageFetcher = new ImageFetcher (domain , getBuilderAuthHeader (), pullPolicy , requestedPlatform );
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- ImageReference imageReference = request .getRunImage ();
112- Image runImage = imageFetcher .fetchImage (ImageType .RUNNER , imageReference );
113- String digest = this .docker .image ().resolveManifestDigest (imageReference , requestedPlatform );
114- if (StringUtils .hasText (digest )) {
115- imageReference = imageReference .withDigest (digest );
116- runImage = imageFetcher .fetchImage (ImageType .RUNNER , imageReference );
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 );
117117 }
118- request = request .withRunImage (imageReference );
119118 assertStackIdsMatch (runImage , builderImage );
120119 BuildOwner buildOwner = BuildOwner .fromEnv (builderImage .getConfig ().getEnv ());
121120 BuildpackLayersMetadata buildpackLayersMetadata = BuildpackLayersMetadata .fromImage (builderImage );
122- Buildpacks buildpacks = getBuildpacks (request , imageFetcher , builderMetadata , buildpackLayersMetadata );
121+ Buildpacks buildpacks = getBuildpacks (request , imageFetcher , platform , builderMetadata ,
122+ buildpackLayersMetadata );
123123 EphemeralBuilder ephemeralBuilder = new EphemeralBuilder (buildOwner , builderImage , request .getName (),
124124 builderMetadata , request .getCreator (), request .getEnv (), buildpacks );
125125 executeLifecycle (request , ephemeralBuilder );
@@ -163,9 +163,9 @@ private void assertStackIdsMatch(Image runImage, Image builderImage) {
163163 }
164164 }
165165
166- private Buildpacks getBuildpacks (BuildRequest request , ImageFetcher imageFetcher , BuilderMetadata builderMetadata ,
167- BuildpackLayersMetadata buildpackLayersMetadata ) {
168- 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 ,
169169 buildpackLayersMetadata );
170170 return BuildpackResolvers .resolveAll (resolverContext , request .getBuildpacks ());
171171 }
@@ -234,51 +234,74 @@ private class ImageFetcher {
234234
235235 private final PullPolicy pullPolicy ;
236236
237- private ImagePlatform defaultPlatform ;
238-
239- ImageFetcher (String domain , String authHeader , PullPolicy pullPolicy , ImagePlatform platform ) {
237+ ImageFetcher (String domain , String authHeader , PullPolicy pullPolicy ) {
240238 this .domain = domain ;
241239 this .authHeader = authHeader ;
242240 this .pullPolicy = pullPolicy ;
243- this .defaultPlatform = platform ;
244241 }
245242
246- Image fetchImage (ImageType type , ImageReference reference ) throws IOException {
243+ Image fetchImage (ImageType type , ImageReference reference , ImagePlatform platform ) throws IOException {
247244 Assert .notNull (type , "Type must not be null" );
248245 Assert .notNull (reference , "Reference must not be null" );
249246 Assert .state (this .authHeader == null || reference .getDomain ().equals (this .domain ),
250247 () -> String .format ("%s '%s' must be pulled from the '%s' authenticated registry" ,
251248 StringUtils .capitalize (type .getDescription ()), reference , this .domain ));
252249 if (this .pullPolicy == PullPolicy .ALWAYS ) {
253- return checkPlatformMismatch ( pullImage ( reference , type ) , reference );
250+ return pullImageAndCheckForPlatformMismatch ( type , reference , platform );
254251 }
255252 try {
256- 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 );
257255 }
258256 catch (DockerEngineException ex ) {
259257 if (this .pullPolicy == PullPolicy .IF_NOT_PRESENT && ex .getStatusCode () == 404 ) {
260- return checkPlatformMismatch ( pullImage ( reference , type ) , reference );
258+ return pullImageAndCheckForPlatformMismatch ( type , reference , platform );
261259 }
262260 throw ex ;
263261 }
264262 }
265263
266- private Image pullImage (ImageReference reference , ImageType imageType ) throws IOException {
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 );
276+ }
277+ throw ex ;
278+ }
279+ }
280+
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 {
267293 TotalProgressPullListener listener = new TotalProgressPullListener (
268- Builder .this .log .pullingImage (reference , this . defaultPlatform , imageType ));
269- 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 );
270296 Builder .this .log .pulledImage (image , imageType );
271- if (this .defaultPlatform == null ) {
272- this .defaultPlatform = ImagePlatform .from (image );
273- }
274297 return image ;
275298 }
276299
277- private Image checkPlatformMismatch (Image image , ImageReference imageReference ) {
278- if (this . defaultPlatform != null ) {
279- ImagePlatform imagePlatform = ImagePlatform .from (image );
280- if (!imagePlatform .equals (this . defaultPlatform )) {
281- 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 );
282305 }
283306 }
284307 return image ;
@@ -289,9 +312,9 @@ private Image checkPlatformMismatch(Image image, ImageReference imageReference)
289312 private static final class PlatformMismatchException extends RuntimeException {
290313
291314 private PlatformMismatchException (ImageReference imageReference , ImagePlatform requestedPlatform ,
292- ImagePlatform actualPlatform ) {
315+ ImagePlatform actualPlatform , Throwable cause ) {
293316 super ("Image platform mismatch detected. The configured platform '%s' is not supported by the image '%s'. Requested platform '%s' but got '%s'"
294- .formatted (requestedPlatform , imageReference , requestedPlatform , actualPlatform ));
317+ .formatted (requestedPlatform , imageReference , requestedPlatform , actualPlatform ), cause );
295318 }
296319
297320 }
@@ -303,13 +326,16 @@ private class BuilderResolverContext implements BuildpackResolverContext {
303326
304327 private final ImageFetcher imageFetcher ;
305328
329+ private final ImagePlatform platform ;
330+
306331 private final BuilderMetadata builderMetadata ;
307332
308333 private final BuildpackLayersMetadata buildpackLayersMetadata ;
309334
310- BuilderResolverContext (ImageFetcher imageFetcher , BuilderMetadata builderMetadata ,
335+ BuilderResolverContext (ImageFetcher imageFetcher , ImagePlatform platform , BuilderMetadata builderMetadata ,
311336 BuildpackLayersMetadata buildpackLayersMetadata ) {
312337 this .imageFetcher = imageFetcher ;
338+ this .platform = platform ;
313339 this .builderMetadata = builderMetadata ;
314340 this .buildpackLayersMetadata = buildpackLayersMetadata ;
315341 }
@@ -326,21 +352,13 @@ public BuildpackLayersMetadata getBuildpackLayersMetadata() {
326352
327353 @ Override
328354 public Image fetchImage (ImageReference reference , ImageType imageType ) throws IOException {
329- return this .imageFetcher .fetchImage (imageType , reference );
355+ return this .imageFetcher .fetchImage (imageType , reference , this . platform );
330356 }
331357
332358 @ Override
333359 public void exportImageLayers (ImageReference reference , IOBiConsumer <String , TarArchive > exports )
334360 throws IOException {
335- String digest = Builder .this .docker .image ()
336- .resolveManifestDigest (reference , this .imageFetcher .defaultPlatform );
337- if (StringUtils .hasText (digest )) {
338- ImageReference pinned = reference .withDigest (digest );
339- Builder .this .docker .image ().exportLayers (pinned , null , exports );
340- }
341- else {
342- Builder .this .docker .image ().exportLayers (reference , this .imageFetcher .defaultPlatform , exports );
343- }
361+ Builder .this .docker .image ().exportLayers (reference , exports );
344362 }
345363
346364 }
0 commit comments