Skip to content

Commit f788e3f

Browse files
authored
Merge pull request #495 from mtrmac/zstd-annotation
Fix copying of non-Buildah Zstd images with --preserve-digests
2 parents 0ed219f + f869fc7 commit f788e3f

File tree

6 files changed

+74
-23
lines changed

6 files changed

+74
-23
lines changed

image/copy/multiple.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ func (c *copier) copyMultipleImages(ctx context.Context) (copiedManifest []byte,
216216
case imgspecv1.MediaTypeImageManifest:
217217
forceListMIMEType = imgspecv1.MediaTypeImageIndex
218218
}
219+
// FIXME: This does not take into account cannotModifyManifestListReason.
219220
selectedListType, otherManifestMIMETypeCandidates, err := c.determineListConversion(manifestType, c.dest.SupportedManifestMIMETypes(), forceListMIMEType)
220221
if err != nil {
221222
return nil, fmt.Errorf("determining manifest list type to write to destination: %w", err)
@@ -284,7 +285,7 @@ func (c *copier) copyMultipleImages(ctx context.Context) (copiedManifest []byte,
284285
}
285286

286287
// Now reset the digest/size/types of the manifests in the list to account for any conversions that we made.
287-
if err = updatedList.EditInstances(instanceEdits); err != nil {
288+
if err = updatedList.EditInstances(instanceEdits, cannotModifyManifestListReason != ""); err != nil {
288289
return nil, fmt.Errorf("updating manifest list: %w", err)
289290
}
290291

@@ -318,7 +319,7 @@ func (c *copier) copyMultipleImages(ctx context.Context) (copiedManifest []byte,
318319
// If we can't just use the original value, but we have to change it, flag an error.
319320
if !bytes.Equal(attemptedManifestList, originalManifestList) {
320321
if cannotModifyManifestListReason != "" {
321-
return nil, fmt.Errorf("Manifest list must be converted to type %q to be written to destination, but we cannot modify it: %q", thisListType, cannotModifyManifestListReason)
322+
return nil, fmt.Errorf("Manifest list was edited, but we cannot modify it: %q", cannotModifyManifestListReason)
322323
}
323324
logrus.Debugf("Manifest list has been updated")
324325
} else {

image/internal/manifest/docker_schema2_list.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,13 @@ func (list *Schema2ListPublic) UpdateInstances(updates []ListUpdate) error {
8585
ListOperation: ListOpUpdate,
8686
})
8787
}
88-
return list.editInstances(editInstances)
88+
return list.editInstances(editInstances, false)
8989
}
9090

91-
func (list *Schema2ListPublic) editInstances(editInstances []ListEdit) error {
91+
// editInstances edits information about the list's instances, based on instructions in editInstances.
92+
// If cannotModifyManifest, avoidable updates should be skipped.
93+
func (list *Schema2ListPublic) editInstances(editInstances []ListEdit, cannotModifyManifest bool) error {
94+
_ = cannotModifyManifest // None of the edits we make are avoidable.
9295
addedEntries := []Schema2ManifestDescriptor{}
9396
for i, editInstance := range editInstances {
9497
switch editInstance.ListOperation {
@@ -141,8 +144,10 @@ func (list *Schema2ListPublic) editInstances(editInstances []ListEdit) error {
141144
return nil
142145
}
143146

144-
func (list *Schema2List) EditInstances(editInstances []ListEdit) error {
145-
return list.editInstances(editInstances)
147+
// EditInstances edits information about the list's instances, based on instructions in editInstances.
148+
// If cannotModifyManifest, avoidable updates should be skipped.
149+
func (list *Schema2List) EditInstances(editInstances []ListEdit, cannotModifyManifest bool) error {
150+
return list.editInstances(editInstances, cannotModifyManifest)
146151
}
147152

148153
func (list *Schema2ListPublic) ChooseInstanceByCompression(ctx *types.SystemContext, preferGzip types.OptionalBool) (digest.Digest, error) {

image/internal/manifest/docker_schema2_list_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func TestSchema2ListEditInstances(t *testing.T) {
4747
UpdateMediaType: "something",
4848
ListOperation: ListOpUpdate,
4949
})
50-
err = list.EditInstances(editInstances)
50+
err = list.EditInstances(editInstances, false)
5151
require.NoError(t, err)
5252

5353
expectedDigests[0] = editInstances[0].UpdateDigest
@@ -82,7 +82,7 @@ func TestSchema2ListEditInstances(t *testing.T) {
8282
AddPlatform: &imgspecv1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"sse4"}},
8383
ListOperation: ListOpAdd,
8484
})
85-
err = list.EditInstances(editInstances)
85+
err = list.EditInstances(editInstances, false)
8686
require.NoError(t, err)
8787

8888
// Verify new elements are added to the end of old list

image/internal/manifest/list.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,9 @@ type List interface {
5656
// SystemContext ( or for the current platform if the SystemContext doesn't specify any detail ) and preferGzip for compression which
5757
// when configured to OptionalBoolTrue and chooses best available compression when it is OptionalBoolFalse or left OptionalBoolUndefined.
5858
ChooseInstanceByCompression(ctx *types.SystemContext, preferGzip types.OptionalBool) (digest.Digest, error)
59-
// Edit information about the list's instances. Contains Slice of ListEdit where each element
60-
// is responsible for either Modifying or Adding a new instance to the Manifest. Operation is
61-
// selected on the basis of configured ListOperation field.
62-
EditInstances([]ListEdit) error
59+
// EditInstances edits information about the list's instances, based on instructions in editInstances.
60+
// If cannotModifyManifest, avoidable updates should be skipped.
61+
EditInstances(editInstances []ListEdit, cannotModifyManifest bool) error
6362
}
6463

6564
// ListUpdate includes the fields which a List's UpdateInstances() method will modify.

image/internal/manifest/oci_index.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func (index *OCI1IndexPublic) UpdateInstances(updates []ListUpdate) error {
8282
ListOperation: ListOpUpdate,
8383
})
8484
}
85-
return index.editInstances(editInstances)
85+
return index.editInstances(editInstances, false)
8686
}
8787

8888
func annotationsToCompressionAlgorithmNames(annotations map[string]string) []string {
@@ -97,7 +97,12 @@ func annotationsToCompressionAlgorithmNames(annotations map[string]string) []str
9797
return result
9898
}
9999

100-
func addCompressionAnnotations(compressionAlgorithms []compression.Algorithm, annotationsMap *map[string]string) {
100+
// addCompressionAnnotations updates annotationsMap for a manifest, based on compressionAlgorithms known to be used in that instance.
101+
// If cannotModifyManifest, avoidable updates should be skipped (i.e. this does nothing, because the algorithm annotation is “only” used for prioritization).
102+
func addCompressionAnnotations(compressionAlgorithms []compression.Algorithm, annotationsMap *map[string]string, cannotModifyManifest bool) {
103+
if cannotModifyManifest {
104+
return
105+
}
101106
// TODO: This should also delete the algorithm if map already contains an algorithm and compressionAlgorithm
102107
// list has a different algorithm. To do that, we would need to modify the callers to always provide a reliable
103108
// and full compressionAlghorithms list.
@@ -114,7 +119,9 @@ func addCompressionAnnotations(compressionAlgorithms []compression.Algorithm, an
114119
}
115120
}
116121

117-
func (index *OCI1IndexPublic) editInstances(editInstances []ListEdit) error {
122+
// editInstances edits information about the list's instances, based on instructions in editInstances.
123+
// If cannotModifyManifest, avoidable updates should be skipped.
124+
func (index *OCI1IndexPublic) editInstances(editInstances []ListEdit, cannotModifyManifest bool) error {
118125
addedEntries := []imgspecv1.Descriptor{}
119126
updatedAnnotations := false
120127
for i, editInstance := range editInstances {
@@ -152,13 +159,15 @@ func (index *OCI1IndexPublic) editInstances(editInstances []ListEdit) error {
152159
maps.Copy(index.Manifests[targetIndex].Annotations, editInstance.UpdateAnnotations)
153160
}
154161
}
155-
addCompressionAnnotations(editInstance.UpdateCompressionAlgorithms, &index.Manifests[targetIndex].Annotations)
162+
// FIXME: This does not set updatedAnnotations, so we don’t re-sort images with Zstd instances if some other
163+
// tool created them without the OCI1InstanceAnnotationCompressionZSTD annotation.
164+
addCompressionAnnotations(editInstance.UpdateCompressionAlgorithms, &index.Manifests[targetIndex].Annotations, cannotModifyManifest)
156165
case ListOpAdd:
157166
annotations := map[string]string{}
158167
if editInstance.AddAnnotations != nil {
159168
annotations = maps.Clone(editInstance.AddAnnotations)
160169
}
161-
addCompressionAnnotations(editInstance.AddCompressionAlgorithms, &annotations)
170+
addCompressionAnnotations(editInstance.AddCompressionAlgorithms, &annotations, false) // We are adding a full new entry, so skipping the annotation would still not allow preserving the original manifest.
162171
addedEntries = append(addedEntries, imgspecv1.Descriptor{
163172
MediaType: editInstance.AddMediaType,
164173
ArtifactType: editInstance.AddArtifactType,
@@ -195,8 +204,10 @@ func (index *OCI1IndexPublic) editInstances(editInstances []ListEdit) error {
195204
return nil
196205
}
197206

198-
func (index *OCI1Index) EditInstances(editInstances []ListEdit) error {
199-
return index.editInstances(editInstances)
207+
// EditInstances edits information about the list's instances, based on instructions in editInstances.
208+
// If cannotModifyManifest, avoidable updates should be skipped.
209+
func (index *OCI1Index) EditInstances(editInstances []ListEdit, cannotModifyManifest bool) error {
210+
return index.editInstances(editInstances, cannotModifyManifest)
200211
}
201212

202213
// instanceIsZstd returns true if instance is a zstd instance otherwise false.

image/internal/manifest/oci_index_test.go

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func TestOCI1EditInstances(t *testing.T) {
6868
UpdateMediaType: "something",
6969
ListOperation: ListOpUpdate,
7070
})
71-
err = list.EditInstances(editInstances)
71+
err = list.EditInstances(editInstances, false)
7272
require.NoError(t, err)
7373

7474
expectedDigests[0] = editInstances[0].UpdateDigest
@@ -135,7 +135,7 @@ func TestOCI1EditInstances(t *testing.T) {
135135
AddPlatform: &imgspecv1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"sse4"}},
136136
ListOperation: ListOpAdd,
137137
})
138-
err = list.EditInstances(editInstances)
138+
err = list.EditInstances(editInstances, false)
139139
require.NoError(t, err)
140140

141141
// Zstd should be kept on lowest priority as compared to the default gzip ones and order of prior elements must be preserved.
@@ -162,7 +162,7 @@ func TestOCI1EditInstances(t *testing.T) {
162162
UpdateAnnotations: map[string]string{},
163163
ListOperation: ListOpUpdate,
164164
})
165-
err = list.EditInstances(editInstances)
165+
err = list.EditInstances(editInstances, false)
166166
require.NoError(t, err)
167167
// Digest `ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff` should be re-ordered on update.
168168
assert.Equal(t, list.Instances(), []digest.Digest{digest.Digest("sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f"), digest.Digest("sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"), digest.Digest("sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), digest.Digest("sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"), digest.Digest("sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), digest.Digest("sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"), digest.Digest("sha256:hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")})
@@ -187,12 +187,47 @@ func TestOCI1EditInstances(t *testing.T) {
187187
UpdateAffectAnnotations: true,
188188
UpdateAnnotations: map[string]string{},
189189
}}
190-
err = list.EditInstances(editInstances)
190+
err = list.EditInstances(editInstances, false)
191191
require.NoError(t, err)
192192
// Verify that the artifactType wasn't lost.
193193
instance, err = list.Instance(digest.Digest("sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))
194194
require.NoError(t, err)
195195
assert.Equal(t, "application/x-tar", instance.ReadOnly.ArtifactType)
196+
197+
// Updating the zstd annotation for an existing instance works
198+
for _, cannotModifyManifest := range []bool{false, true} {
199+
list, err = ListFromBlob(validManifest, GuessMIMEType(validManifest))
200+
require.NoError(t, err)
201+
originalInstance, err := list.Instance(list.Instances()[0])
202+
require.NoError(t, err)
203+
editInstances = []ListEdit{{
204+
ListOperation: ListOpUpdate,
205+
UpdateOldDigest: originalInstance.Digest,
206+
UpdateDigest: originalInstance.Digest,
207+
UpdateSize: originalInstance.Size,
208+
UpdateMediaType: originalInstance.MediaType,
209+
UpdateAffectAnnotations: false,
210+
UpdateAnnotations: nil,
211+
UpdateCompressionAlgorithms: []compression.Algorithm{compression.Zstd},
212+
}}
213+
err = list.EditInstances(editInstances, cannotModifyManifest)
214+
require.NoError(t, err)
215+
instance, err = list.Instance(list.Instances()[0])
216+
require.NoError(t, err)
217+
218+
if cannotModifyManifest {
219+
assert.Equal(t, originalInstance, instance) // No changes
220+
} else {
221+
assert.Equal(t, "true", instance.ReadOnly.Annotations["io.github.containers.compression.zstd"])
222+
assert.Equal(t, []string{compressionTypes.ZstdAlgorithmName}, instance.ReadOnly.CompressionAlgorithmNames)
223+
// These are the only changes:
224+
delete(instance.ReadOnly.Annotations, "io.github.containers.compression.zstd")
225+
require.Empty(t, instance.ReadOnly.Annotations)
226+
instance.ReadOnly.Annotations = nil
227+
instance.ReadOnly.CompressionAlgorithmNames = []string{compressionTypes.GzipAlgorithmName}
228+
assert.Equal(t, originalInstance, instance)
229+
}
230+
}
196231
}
197232

198233
func TestOCI1IndexChooseInstanceByCompression(t *testing.T) {

0 commit comments

Comments
 (0)