@@ -196,47 +196,81 @@ func copyMultiplePlatforms(ctx context.Context, statusHandler status.CopyHandler
196196 return fmt .Errorf ("source reference %s is not an index or manifest list" , opts .From .Reference )
197197 }
198198
199+ index , filteredManifests , err := filterManifestByPlatform (ctx , src , root , opts )
200+ if err != nil {
201+ return err
202+ }
203+
204+ // If all platforms are specified, we can just copy the root descriptor
205+ if len (index .Manifests ) == len (filteredManifests ) {
206+ opts .Platform .Platform = nil
207+ return copySinglePlatformOrRecursive (ctx , statusHandler , metadataHandler , src , dst , opts )
208+ }
209+
210+ // Perform multiple copies
211+ return doMultipleCopy (ctx , statusHandler , metadataHandler , src , dst , opts , index , filteredManifests )
212+ }
213+
214+ func filterManifestByPlatform (ctx context.Context , src oras.ReadOnlyGraphTarget , root ocispec.Descriptor , opts * copyOptions ) (ocispec.Index , []ocispec.Descriptor , error ) {
199215 // For indexes/lists, fetch the index content
200216 indexContent , err := content .FetchAll (ctx , src , root )
201217 if err != nil {
202- return fmt .Errorf ("failed to fetch index: %w" , err )
218+ return ocispec. Index {}, nil , fmt .Errorf ("failed to fetch index: %w" , err )
203219 }
204220
205221 var index ocispec.Index
206- if err : = json .Unmarshal (indexContent , & index ); err != nil {
207- return fmt .Errorf ("failed to parse index: %w" , err )
222+ if err = json .Unmarshal (indexContent , & index ); err != nil {
223+ return ocispec. Index {}, nil , fmt .Errorf ("failed to parse index: %w" , err )
208224 }
209225
210- var availablePlatforms []string
211226 // Filter manifests based on the specified platforms
227+ var availablePlatforms []string
212228 var filteredManifests []ocispec.Descriptor
213- matchedPlatforms := make (map [string ]bool )
214229 for _ , manifest := range index .Manifests {
215230 if manifest .Platform == nil {
216231 continue
217232 }
218- availablePlatforms = append (availablePlatforms , fmt .Sprintf ("%s/%s" , manifest .Platform .OS , manifest .Platform .Architecture ))
233+ var platformStr string
234+ if manifest .Platform .Variant != "" {
235+ platformStr = fmt .Sprintf ("%s/%s/%s" , manifest .Platform .OS , manifest .Platform .Architecture , manifest .Platform .Variant )
236+ } else {
237+ platformStr = fmt .Sprintf ("%s/%s" , manifest .Platform .OS , manifest .Platform .Architecture )
238+ }
239+ availablePlatforms = append (availablePlatforms , platformStr )
219240 if matchesAnyPlatform (manifest .Platform , opts .Platform .Platforms ) {
220241 filteredManifests = append (filteredManifests , manifest )
221- matchedPlatforms [fmt .Sprintf ("%s/%s" , manifest .Platform .OS , manifest .Platform .Architecture )] = true
222242 }
223243 }
224244
225- if len (filteredManifests ) != len (opts .Platform .Platforms ) {
226-
227- var unmatchedPlatforms []string
228- for _ , platform := range opts .Platform .Platforms {
229- platformStr := fmt .Sprintf ("%s/%s" , platform .OS , platform .Architecture )
230- if ! matchedPlatforms [platformStr ] {
231- unmatchedPlatforms = append (unmatchedPlatforms , platformStr )
232- }
245+ // Check if any platforms were unmatched
246+ var unmatchedPlatforms []string
247+ for _ , platform := range opts .Platform .Platforms {
248+ var platformStr string
249+ var contains bool
250+ if platform .Variant != "" {
251+ platformStr = fmt .Sprintf ("%s/%s/%s" , platform .OS , platform .Architecture , platform .Variant )
252+ contains = slices .ContainsFunc (filteredManifests , func (manifest ocispec.Descriptor ) bool {
253+ return manifest .Platform .OS == platform .OS && manifest .Platform .Architecture == platform .Architecture && manifest .Platform .Variant == platform .Variant
254+ })
255+ } else {
256+ platformStr = fmt .Sprintf ("%s/%s" , platform .OS , platform .Architecture )
257+ contains = slices .ContainsFunc (filteredManifests , func (manifest ocispec.Descriptor ) bool {
258+ return manifest .Platform .OS == platform .OS && manifest .Platform .Architecture == platform .Architecture
259+ })
233260 }
234-
235- // Return error with details about unmatched platforms
236- return fmt .Errorf ("only %d of %d requested platforms were matched: unmatched platforms: [%s]; available platforms in index: [%s]" ,
237- len (filteredManifests ), len (opts .Platform .Platforms ), strings .Join (unmatchedPlatforms , ", " ), strings .Join (availablePlatforms , ", " ))
261+ if ! contains {
262+ unmatchedPlatforms = append (unmatchedPlatforms , platformStr )
263+ }
264+ }
265+ // Return error with details about unmatched platforms
266+ if len (unmatchedPlatforms ) > 0 {
267+ return ocispec.Index {}, nil , fmt .Errorf ("some requested platforms were not matched; unmatched platforms: [%s]; available platforms in index: [%s]" ,
268+ strings .Join (unmatchedPlatforms , ", " ), strings .Join (availablePlatforms , ", " ))
238269 }
270+ return index , filteredManifests , nil
271+ }
239272
273+ func doMultipleCopy (ctx context.Context , statusHandler status.CopyHandler , metadataHandler metadata.CopyHandler , src oras.ReadOnlyGraphTarget , dst oras.GraphTarget , opts * copyOptions , index ocispec.Index , filteredManifests []ocispec.Descriptor ) error {
240274 // Create a new index with only the filtered manifests
241275 newIndex := index
242276 newIndex .Manifests = filteredManifests
@@ -261,7 +295,6 @@ func copyMultiplePlatforms(ctx context.Context, statusHandler status.CopyHandler
261295 extendedCopyGraphOptions .FindPredecessors = func (ctx context.Context , src content.ReadOnlyGraphStorage , desc ocispec.Descriptor ) ([]ocispec.Descriptor , error ) {
262296 return registry .Referrers (ctx , src , desc , "" )
263297 }
264-
265298 if mountRepo , canMount := getMountPoint (src , dst , opts ); canMount {
266299 extendedCopyGraphOptions .MountFrom = func (ctx context.Context , desc ocispec.Descriptor ) ([]string , error ) {
267300 return []string {mountRepo }, nil
@@ -282,19 +315,19 @@ func copyMultiplePlatforms(ctx context.Context, statusHandler status.CopyHandler
282315 // Copy all matching manifests and their content
283316 for _ , manifestDesc := range filteredManifests {
284317 // Copy the manifest itself
285- if err : = oras .CopyGraph (ctx , src , dst , manifestDesc , extendedCopyGraphOptions .CopyGraphOptions ); err != nil {
318+ if err = oras .CopyGraph (ctx , src , dst , manifestDesc , extendedCopyGraphOptions .CopyGraphOptions ); err != nil {
286319 return fmt .Errorf ("failed to copy manifest %s: %w" , manifestDesc .Digest , err )
287320 }
288321 }
289322
290323 // Push the new index to the destination
291- if err : = dst .Push (ctx , newIndexDesc , strings .NewReader (string (newIndexContent ))); err != nil {
324+ if err = dst .Push (ctx , newIndexDesc , strings .NewReader (string (newIndexContent ))); err != nil {
292325 return fmt .Errorf ("failed to push new index: %w" , err )
293326 }
294327
295328 // Tag the new index if needed
296329 if opts .To .Reference != "" {
297- if err : = dst .Tag (ctx , newIndexDesc , opts .To .Reference ); err != nil {
330+ if err = dst .Tag (ctx , newIndexDesc , opts .To .Reference ); err != nil {
298331 return fmt .Errorf ("failed to tag new index: %w" , err )
299332 }
300333 }
@@ -313,37 +346,38 @@ func copyMultiplePlatforms(ctx context.Context, statusHandler status.CopyHandler
313346 if from , err := digest .Parse (opts .From .Reference ); err == nil && from != newIndexDesc .Digest {
314347 opts .From .RawReference = fmt .Sprintf ("%s@%s" , opts .From .Path , newIndexDesc .Digest .String ())
315348 }
316-
317- if err := metadataHandler .OnCopied (& opts .BinaryTarget , newIndexDesc ); err != nil {
349+ if err = metadataHandler .OnCopied (& opts .BinaryTarget , newIndexDesc ); err != nil {
318350 return err
319351 }
320-
321352 return metadataHandler .Render ()
322353}
323354
324355// matchesAnyPlatform checks if a manifest platform matches any of the specified platforms
325356func matchesAnyPlatform (manifestPlatform * ocispec.Platform , platforms []* ocispec.Platform ) bool {
326- for _ , platform := range platforms {
327- if platformMatches (manifestPlatform , platform ) {
328- return true
329- }
330- }
331- return false
357+ return slices .ContainsFunc (platforms , func (platform * ocispec.Platform ) bool {
358+ return platformMatches (manifestPlatform , platform )
359+ })
332360}
333361
334362// platformMatches checks if two platforms match
335- func platformMatches (a , b * ocispec.Platform ) bool {
336- if a .OS != b .OS || a .Architecture != b .Architecture {
363+ func platformMatches (manifestPlatform , targetPlatform * ocispec.Platform ) bool {
364+ if manifestPlatform .OS != targetPlatform .OS || manifestPlatform .Architecture != targetPlatform .Architecture {
337365 return false
338366 }
339367
340- // Variant is optional; only treat it as a mismatch if both variants are non-empty and different.
341- if a .Variant != "" && b .Variant != "" && a .Variant != b .Variant {
368+ // Variant: optional; if specified, must match exactly, otherwise it is ignored
369+ //if targetPlatform.Variant == "" && manifestPlatform.Variant != "" {
370+ // return false
371+ //}
372+ if targetPlatform .Variant != "" && manifestPlatform .Variant == "" {
373+ return false
374+ }
375+ if targetPlatform .Variant != "" && manifestPlatform .Variant != "" && targetPlatform .Variant != manifestPlatform .Variant {
342376 return false
343377 }
344378
345379 // OSVersion is optional; only treat it as a mismatch if both OSVersions are non-empty and different.
346- if a .OSVersion != "" && b .OSVersion != "" && a .OSVersion != b .OSVersion {
380+ if manifestPlatform .OSVersion != "" && targetPlatform .OSVersion != "" && manifestPlatform .OSVersion != targetPlatform .OSVersion {
347381 return false
348382 }
349383
0 commit comments