Skip to content

Commit 59b6ed0

Browse files
author
cleverhu
committed
support copy multi arch instance
Signed-off-by: cleverhu <[email protected]>
1 parent 94e0c4f commit 59b6ed0

File tree

5 files changed

+107
-9
lines changed

5 files changed

+107
-9
lines changed

copy/copy.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,9 @@ type Options struct {
9292
PreserveDigests bool
9393
// manifest MIME type of image set by user. "" is default and means use the autodetection to the manifest MIME type
9494
ForceManifestMIMEType string
95-
ImageListSelection ImageListSelection // set to either CopySystemImage (the default), CopyAllImages, or CopySpecificImages to control which instances we copy when the source reference is a list; ignored if the source reference is not a list
96-
Instances []digest.Digest // if ImageListSelection is CopySpecificImages, copy only these instances and the list itself
95+
ImageListSelection ImageListSelection // set to either CopySystemImage (the default), CopyAllImages, or CopySpecificImages to control which instances we copy when the source reference is a list; ignored if the source reference is not a list
96+
ImageListPlatforms []manifest.Schema2PlatformSpec // if ImageListSelection is CopySpecificImages, copy only these target platforms
97+
Instances []digest.Digest // if ImageListSelection is CopySpecificImages, copy only these instances and the list itself, this is auto generated by ImageListPlatforms
9798
// Give priority to pulling gzip images if multiple images are present when configured to OptionalBoolTrue,
9899
// prefers the best compression if this is configured as OptionalBoolFalse. Choose automatically (and the choice may change over time)
99100
// if this is set to OptionalBoolUndefined (which is the default behavior, and recommended for most callers).
@@ -325,6 +326,7 @@ func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef,
325326
if !supportsMultipleImages(c.dest) {
326327
return nil, fmt.Errorf("copying multiple images: destination transport %q does not support copying multiple images as a group", destRef.Transport().Name())
327328
}
329+
328330
// Copy some or all of the images.
329331
switch c.options.ImageListSelection {
330332
case CopyAllImages:

copy/multiple.go

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,9 @@ func platformV1ToPlatformComparable(platform *imgspecv1.Platform) platformCompar
6060
}
6161
osFeatures := slices.Clone(platform.OSFeatures)
6262
sort.Strings(osFeatures)
63-
return platformComparable{architecture: platform.Architecture,
64-
os: platform.OS,
63+
return platformComparable{
64+
architecture: platform.Architecture,
65+
os: platform.OS,
6566
// This is strictly speaking ambiguous, fields of OSFeatures can contain a ','. Probably good enough for now.
6667
osFeatures: strings.Join(osFeatures, ","),
6768
osVersion: platform.OSVersion,
@@ -98,8 +99,65 @@ func validateCompressionVariantExists(input []OptionCompressionVariant) error {
9899
return nil
99100
}
100101

102+
func isPlatformSupported(list internalManifest.List, platform manifest.Schema2PlatformSpec) (digest.Digest, bool) {
103+
for _, instanceDigest := range list.Instances() {
104+
instance, err := list.Instance(instanceDigest)
105+
if err != nil {
106+
return "", false
107+
}
108+
109+
if instance.ReadOnly.Platform == nil {
110+
continue
111+
}
112+
113+
if instance.ReadOnly.Platform.OS == platform.OS &&
114+
instance.ReadOnly.Platform.Architecture == platform.Architecture {
115+
return instanceDigest, true
116+
}
117+
}
118+
119+
return "", false
120+
}
121+
122+
func filterInstancesByPlatforms(list internalManifest.List, platforms []manifest.Schema2PlatformSpec) ([]digest.Digest, error) {
123+
missingPlatforms := []manifest.Schema2PlatformSpec{}
124+
supportedInstance := []digest.Digest{}
125+
// Check each requested platform
126+
for _, platform := range platforms {
127+
if digest, ok := isPlatformSupported(list, platform); !ok {
128+
missingPlatforms = append(missingPlatforms, platform)
129+
} else {
130+
supportedInstance = append(supportedInstance, digest)
131+
}
132+
}
133+
134+
if len(missingPlatforms) > 0 {
135+
var platformStrings []string
136+
for _, p := range missingPlatforms {
137+
platformStr := fmt.Sprintf("%s/%s", p.OS, p.Architecture)
138+
if p.Variant != "" {
139+
platformStr += "/" + p.Variant
140+
}
141+
platformStrings = append(platformStrings, platformStr)
142+
}
143+
return nil, fmt.Errorf("requested platforms not found in image: %s", strings.Join(platformStrings, ", "))
144+
}
145+
146+
if len(platforms) == 0 {
147+
supportedInstance = list.Instances()
148+
}
149+
150+
return supportedInstance, nil
151+
}
152+
101153
// prepareInstanceCopies prepares a list of instances which needs to copied to the manifest list.
102154
func prepareInstanceCopies(list internalManifest.List, instanceDigests []digest.Digest, options *Options) ([]instanceCopy, error) {
155+
filteredInstanceDigests, err := filterInstancesByPlatforms(list, options.ImageListPlatforms)
156+
if err != nil {
157+
return nil, err
158+
}
159+
options.Instances = filteredInstanceDigests
160+
103161
res := []instanceCopy{}
104162
if options.ImageListSelection == CopySpecificImages && len(options.EnsureCompressionVariantsExist) > 0 {
105163
// List can already contain compressed instance for a compression selected in `EnsureCompressionVariantsExist`
@@ -109,7 +167,8 @@ func prepareInstanceCopies(list internalManifest.List, instanceDigests []digest.
109167
// We might define the semantics and implement this in the future.
110168
return res, fmt.Errorf("EnsureCompressionVariantsExist is not implemented for CopySpecificImages")
111169
}
112-
err := validateCompressionVariantExists(options.EnsureCompressionVariantsExist)
170+
171+
err = validateCompressionVariantExists(options.EnsureCompressionVariantsExist)
113172
if err != nil {
114173
return res, err
115174
}
@@ -252,15 +311,17 @@ func (c *copier) copyMultipleImages(ctx context.Context) (copiedManifest []byte,
252311
UpdateDigest: updated.manifestDigest,
253312
UpdateSize: int64(len(updated.manifest)),
254313
UpdateCompressionAlgorithms: updated.compressionAlgorithms,
255-
UpdateMediaType: updated.manifestMIMEType})
314+
UpdateMediaType: updated.manifestMIMEType,
315+
})
256316
case instanceCopyClone:
257317
logrus.Debugf("Replicating instance %s (%d/%d)", instance.sourceDigest, i+1, len(instanceCopyList))
258318
c.Printf("Replicating image %s (%d/%d)\n", instance.sourceDigest, i+1, len(instanceCopyList))
259319
unparsedInstance := image.UnparsedInstance(c.rawSource, &instanceCopyList[i].sourceDigest)
260320
updated, err := c.copySingleImage(ctx, unparsedInstance, &instanceCopyList[i].sourceDigest, copySingleImageOptions{
261321
requireCompressionFormatMatch: true,
262322
compressionFormat: &instance.cloneCompressionVariant.Algorithm,
263-
compressionLevel: instance.cloneCompressionVariant.Level})
323+
compressionLevel: instance.cloneCompressionVariant.Level,
324+
})
264325
if err != nil {
265326
return nil, fmt.Errorf("replicating image %d/%d from manifest list: %w", i+1, len(instanceCopyList), err)
266327
}
@@ -285,6 +346,21 @@ func (c *copier) copyMultipleImages(ctx context.Context) (copiedManifest []byte,
285346
return nil, fmt.Errorf("updating manifest list: %w", err)
286347
}
287348

349+
if len(c.options.Instances) > 0 {
350+
deletedInstanceDigests := []internalManifest.ListEdit{}
351+
for _, instanceDigest := range updatedList.Instances() {
352+
if !slices.Contains(c.options.Instances, instanceDigest) {
353+
deletedInstanceDigests = append(deletedInstanceDigests, internalManifest.ListEdit{
354+
ListOperation: internalManifest.ListOpRemove,
355+
UpdateOldDigest: instanceDigest,
356+
})
357+
}
358+
}
359+
if err = updatedList.EditInstances(deletedInstanceDigests); err != nil {
360+
return nil, fmt.Errorf("updating manifest list: %w", err)
361+
}
362+
}
363+
288364
// Iterate through supported list types, preferred format first.
289365
c.Printf("Writing manifest list to image destination\n")
290366
var errs []string

internal/manifest/docker_schema2_list.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ func (index *Schema2ListPublic) UpdateInstances(updates []ListUpdate) error {
8282
UpdateDigest: instance.Digest,
8383
UpdateSize: instance.Size,
8484
UpdateMediaType: instance.MediaType,
85-
ListOperation: ListOpUpdate})
85+
ListOperation: ListOpUpdate,
86+
})
8687
}
8788
return index.editInstances(editInstances)
8889
}
@@ -128,6 +129,14 @@ func (index *Schema2ListPublic) editInstances(editInstances []ListEdit) error {
128129
},
129130
schema2PlatformSpecFromOCIPlatform(*editInstance.AddPlatform),
130131
})
132+
case ListOpRemove:
133+
targetIndex := slices.IndexFunc(index.Manifests, func(m Schema2ManifestDescriptor) bool {
134+
return m.Digest == editInstance.UpdateOldDigest
135+
})
136+
if targetIndex == -1 {
137+
return fmt.Errorf("Schema2List.EditInstances: digest %s not found", editInstance.UpdateOldDigest)
138+
}
139+
index.Manifests = append(index.Manifests[:targetIndex], index.Manifests[targetIndex+1:]...)
131140
default:
132141
return fmt.Errorf("internal error: invalid operation: %d", editInstance.ListOperation)
133142
}

internal/manifest/list.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ const (
8383
listOpInvalid ListOp = iota
8484
ListOpAdd
8585
ListOpUpdate
86+
ListOpRemove
8687
)
8788

8889
// ListEdit includes the fields which a List's EditInstances() method will modify.

internal/manifest/oci_index.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ func (index *OCI1IndexPublic) UpdateInstances(updates []ListUpdate) error {
7979
UpdateDigest: instance.Digest,
8080
UpdateSize: instance.Size,
8181
UpdateMediaType: instance.MediaType,
82-
ListOperation: ListOpUpdate})
82+
ListOperation: ListOpUpdate,
83+
})
8384
}
8485
return index.editInstances(editInstances)
8586
}
@@ -166,6 +167,15 @@ func (index *OCI1IndexPublic) editInstances(editInstances []ListEdit) error {
166167
Platform: editInstance.AddPlatform,
167168
Annotations: annotations,
168169
})
170+
case ListOpRemove:
171+
targetIndex := slices.IndexFunc(index.Manifests, func(m imgspecv1.Descriptor) bool {
172+
return m.Digest == editInstance.UpdateOldDigest
173+
})
174+
175+
if targetIndex == -1 {
176+
return fmt.Errorf("OCI1Index.EditInstances: digest %s not found", editInstance.UpdateOldDigest)
177+
}
178+
index.Manifests = append(index.Manifests[:targetIndex], index.Manifests[targetIndex+1:]...)
169179
default:
170180
return fmt.Errorf("internal error: invalid operation: %d", editInstance.ListOperation)
171181
}

0 commit comments

Comments
 (0)