@@ -41,7 +41,7 @@ import (
4141// pointing to the new target repository. This will allow subsequent pushes
4242// to perform cross-repo mounts of the shared content when pushing to a different
4343// repository on the same registry.
44- func (i * ImageService ) PushImage (ctx context.Context , sourceRef reference.Named , metaHeaders map [string ][]string , authConfig * registry.AuthConfig , outStream io.Writer ) (retErr error ) {
44+ func (i * ImageService ) PushImage (ctx context.Context , sourceRef reference.Named , platform * ocispec. Platform , metaHeaders map [string ][]string , authConfig * registry.AuthConfig , outStream io.Writer ) (retErr error ) {
4545 start := time .Now ()
4646 defer func () {
4747 if retErr == nil {
@@ -76,7 +76,7 @@ func (i *ImageService) PushImage(ctx context.Context, sourceRef reference.Named,
7676 continue
7777 }
7878
79- if err := i .pushRef (ctx , named , metaHeaders , authConfig , out ); err != nil {
79+ if err := i .pushRef (ctx , named , platform , metaHeaders , authConfig , out ); err != nil {
8080 return err
8181 }
8282 }
@@ -85,10 +85,10 @@ func (i *ImageService) PushImage(ctx context.Context, sourceRef reference.Named,
8585 }
8686 }
8787
88- return i .pushRef (ctx , sourceRef , metaHeaders , authConfig , out )
88+ return i .pushRef (ctx , sourceRef , platform , metaHeaders , authConfig , out )
8989}
9090
91- func (i * ImageService ) pushRef (ctx context.Context , targetRef reference.Named , metaHeaders map [string ][]string , authConfig * registry.AuthConfig , out progress.Output ) (retErr error ) {
91+ func (i * ImageService ) pushRef (ctx context.Context , targetRef reference.Named , platform * ocispec. Platform , metaHeaders map [string ][]string , authConfig * registry.AuthConfig , out progress.Output ) (retErr error ) {
9292 leasedCtx , release , err := i .client .WithLease (ctx )
9393 if err != nil {
9494 return err
@@ -104,12 +104,18 @@ func (i *ImageService) pushRef(ctx context.Context, targetRef reference.Named, m
104104 if cerrdefs .IsNotFound (err ) {
105105 return errdefs .NotFound (fmt .Errorf ("tag does not exist: %s" , reference .FamiliarString (targetRef )))
106106 }
107- return errdefs .NotFound (err )
107+ return errdefs .System (err )
108108 }
109109
110110 target := img .Target
111- store := i .content
111+ if platform != nil {
112+ target , err = i .getPushDescriptor (ctx , img , platform )
113+ if err != nil {
114+ return err
115+ }
116+ }
112117
118+ store := i .content
113119 resolver , tracker := i .newResolverFromAuthConfig (ctx , authConfig , targetRef )
114120 pp := pushProgress {Tracker : tracker }
115121 jobsQueue := newJobs ()
@@ -121,7 +127,7 @@ func (i *ImageService) pushRef(ctx context.Context, targetRef reference.Named, m
121127 finishProgress ()
122128 if retErr == nil {
123129 if tagged , ok := targetRef .(reference.Tagged ); ok {
124- progress .Messagef (out , "" , "%s: digest: %s size: %d" , tagged .Tag (), target .Digest , img . Target .Size )
130+ progress .Messagef (out , "" , "%s: digest: %s size: %d" , tagged .Tag (), target .Digest , target .Size )
125131 }
126132 }
127133 }()
@@ -164,16 +170,44 @@ func (i *ImageService) pushRef(ctx context.Context, targetRef reference.Named, m
164170
165171 err = remotes .PushContent (ctx , pusher , target , store , limiter , platforms .All , handlerWrapper )
166172 if err != nil {
167- if containerdimages .IsIndexType (target .MediaType ) && cerrdefs .IsNotFound (err ) {
173+ // If push failed because of a missing content, no specific platform was requested
174+ // and the target is an index, select a platform-specific manifest to push instead.
175+ if cerrdefs .IsNotFound (err ) && containerdimages .IsIndexType (target .MediaType ) && platform == nil {
176+ var newTarget ocispec.Descriptor
177+ newTarget , err = i .getPushDescriptor (ctx , img , nil )
178+ if err != nil {
179+ return err
180+ }
181+
182+ // Retry only if the new push candidate is different from the previous one.
183+ if newTarget .Digest != target .Digest {
184+ target = newTarget
185+ pp .TurnNotStartedIntoUnavailable ()
186+ err = remotes .PushContent (ctx , pusher , target , store , limiter , platforms .All , handlerWrapper )
187+
188+ if err == nil {
189+ progress .Aux (out , map [string ]string {
190+ "Stripped" : "true" ,
191+ "Index" : img .Target .Digest .String (),
192+ "Manifest" : target .Digest .String (),
193+ })
194+ }
195+ }
196+ }
197+
198+ if err != nil {
199+ if ! cerrdefs .IsNotFound (err ) {
200+ return errdefs .System (err )
201+ }
168202 return errdefs .NotFound (fmt .Errorf (
169203 "missing content: %w\n " +
170204 "Note: You're trying to push a manifest list/index which " +
171205 "references multiple platform specific manifests, but not all of them are available locally " +
172- "or available to the remote repository.\n " +
173- "Make sure you have all the referenced content and try again." ,
206+ "or available to the remote repository.\n \n " +
207+ "Make sure you have all the referenced content and try again.\n " +
208+ "You can also push only a single platform specific manifest directly by specifying the platform you want to push." ,
174209 err ))
175210 }
176- return err
177211 }
178212
179213 appendDistributionSourceLabel (ctx , realStore , targetRef , target )
@@ -183,6 +217,97 @@ func (i *ImageService) pushRef(ctx context.Context, targetRef reference.Named, m
183217 return nil
184218}
185219
220+ func (i * ImageService ) getPushDescriptor (ctx context.Context , img containerdimages.Image , platform * ocispec.Platform ) (ocispec.Descriptor , error ) {
221+ // Allow to override the host platform for testing purposes.
222+ hostPlatform := i .defaultPlatformOverride
223+ if hostPlatform == nil {
224+ hostPlatform = platforms .Default ()
225+ }
226+
227+ pm := matchAllWithPreference (hostPlatform )
228+ if platform != nil {
229+ pm = platforms .OnlyStrict (* platform )
230+ }
231+
232+ anyMissing := false
233+
234+ var bestMatchPlatform ocispec.Platform
235+ var bestMatch * ImageManifest
236+ var presentMatchingManifests []* ImageManifest
237+ err := i .walkReachableImageManifests (ctx , img , func (im * ImageManifest ) error {
238+ available , err := im .CheckContentAvailable (ctx )
239+ if err != nil {
240+ return fmt .Errorf ("failed to determine availability of image manifest %s: %w" , im .Target ().Digest , err )
241+ }
242+
243+ if ! available {
244+ anyMissing = true
245+ return nil
246+ }
247+
248+ if im .IsAttestation () {
249+ return nil
250+ }
251+
252+ imgPlatform , err := im .ImagePlatform (ctx )
253+ if err != nil {
254+ return fmt .Errorf ("failed to determine platform of image %s: %w" , img .Name , err )
255+ }
256+
257+ if ! pm .Match (imgPlatform ) {
258+ return nil
259+ }
260+
261+ presentMatchingManifests = append (presentMatchingManifests , im )
262+ if bestMatch == nil || pm .Less (imgPlatform , bestMatchPlatform ) {
263+ bestMatchPlatform = imgPlatform
264+ bestMatch = im
265+ }
266+
267+ return nil
268+ })
269+ if err != nil {
270+ return ocispec.Descriptor {}, err
271+ }
272+
273+ switch len (presentMatchingManifests ) {
274+ case 0 :
275+ return ocispec.Descriptor {}, errdefs .NotFound (fmt .Errorf ("no suitable image manifest found for platform %s" , * platform ))
276+ case 1 :
277+ // Only one manifest is available AND matching the requested platform.
278+
279+ if platform != nil {
280+ // Explicit platform was requested
281+ return presentMatchingManifests [0 ].Target (), nil
282+ }
283+
284+ // No specific platform was requested, but only one manifest is available.
285+ if anyMissing {
286+ return presentMatchingManifests [0 ].Target (), nil
287+ }
288+
289+ // Index has only one manifest anyway, select the full index.
290+ return img .Target , nil
291+ default :
292+ if platform == nil {
293+ if ! anyMissing {
294+ // No specific platform requested, and all manifests are available, select the full index.
295+ return img .Target , nil
296+ }
297+
298+ // No specific platform requested and not all manifests are available.
299+ // Select the manifest that matches the host platform the best.
300+ if bestMatch != nil && hostPlatform .Match (bestMatchPlatform ) {
301+ return bestMatch .Target (), nil
302+ }
303+
304+ return ocispec.Descriptor {}, errdefs .Conflict (errors .Errorf ("multiple matching manifests found but no specific platform requested" ))
305+ }
306+
307+ return ocispec.Descriptor {}, errdefs .Conflict (errors .Errorf ("multiple manifests found for platform %s" , * platform ))
308+ }
309+ }
310+
186311func appendDistributionSourceLabel (ctx context.Context , realStore content.Store , targetRef reference.Named , target ocispec.Descriptor ) {
187312 appendSource , err := docker .AppendDistributionSourceLabel (realStore , targetRef .String ())
188313 if err != nil {
0 commit comments