Skip to content

Commit

Permalink
Merge pull request #1992 from flouthoc/list-add-annotations
Browse files Browse the repository at this point in the history
list,oci_index: automatically add inbuilt annotations on add
  • Loading branch information
mtrmac committed Jun 27, 2023
2 parents a3043f9 + 4cf082a commit 35341f8
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 12 deletions.
23 changes: 14 additions & 9 deletions internal/manifest/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package manifest
import (
"fmt"

compression "github.com/containers/image/v5/pkg/compression/types"
"github.com/containers/image/v5/types"
digest "github.com/opencontainers/go-digest"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
Expand Down Expand Up @@ -82,17 +83,21 @@ type ListEdit struct {
ListOperation ListOp

// if Op == ListEditUpdate (basically the previous UpdateInstances). All fields must be set.
UpdateOldDigest digest.Digest
UpdateDigest digest.Digest
UpdateSize int64
UpdateMediaType string
UpdateOldDigest digest.Digest
UpdateDigest digest.Digest
UpdateSize int64
UpdateMediaType string
UpdateAffectAnnotations bool
UpdateAnnotations map[string]string
UpdateCompressionAlgorithms []compression.Algorithm

// If Op = ListEditAdd. All fields must be set.
AddDigest digest.Digest
AddSize int64
AddMediaType string
AddPlatform *imgspecv1.Platform
AddAnnotations map[string]string
AddDigest digest.Digest
AddSize int64
AddMediaType string
AddPlatform *imgspecv1.Platform
AddAnnotations map[string]string
AddCompressionAlgorithms []compression.Algorithm
}

// ListPublicFromBlob parses a list of manifests.
Expand Down
40 changes: 38 additions & 2 deletions internal/manifest/oci_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"runtime"

platform "github.com/containers/image/v5/internal/pkg/platform"
compression "github.com/containers/image/v5/pkg/compression/types"
"github.com/containers/image/v5/types"
"github.com/opencontainers/go-digest"
imgspec "github.com/opencontainers/image-spec/specs-go"
Expand All @@ -22,7 +23,8 @@ const (
// That also suggests that this instance benefits from
// Zstd compression, so it can be preferred by compatible consumers over instances that
// use gzip, depending on their local policy.
OCI1InstanceAnnotationCompressionZSTD = "io.github.containers.compression.zstd"
OCI1InstanceAnnotationCompressionZSTD = "io.github.containers.compression.zstd"
OCI1InstanceAnnotationCompressionZSTDValue = "true"
)

// OCI1IndexPublic is just an alias for the OCI index type, but one which we can
Expand Down Expand Up @@ -76,8 +78,23 @@ func (index *OCI1IndexPublic) UpdateInstances(updates []ListUpdate) error {
return index.editInstances(editInstances)
}

func addCompressionAnnotations(compressionAlgorithms []compression.Algorithm, annotationsMap map[string]string) {
// TODO: This should also delete the algorithm if map already contains an algorithm and compressionAlgorithm
// list has a different algorithm. To do that, we would need to modify the callers to always provide a reliable
// and full compressionAlghorithms list.
for _, algo := range compressionAlgorithms {
switch algo.Name() {
case compression.ZstdAlgorithmName:
annotationsMap[OCI1InstanceAnnotationCompressionZSTD] = OCI1InstanceAnnotationCompressionZSTDValue
default:
continue
}
}
}

func (index *OCI1IndexPublic) editInstances(editInstances []ListEdit) error {
addedEntries := []imgspecv1.Descriptor{}
updatedAnnotations := false
for i, editInstance := range editInstances {
switch editInstance.ListOperation {
case ListOpUpdate:
Expand All @@ -102,19 +119,38 @@ func (index *OCI1IndexPublic) editInstances(editInstances []ListEdit) error {
return fmt.Errorf("update %d of %d passed to OCI1Index.UpdateInstances had no media type (was %q)", i+1, len(editInstances), index.Manifests[i].MediaType)
}
index.Manifests[targetIndex].MediaType = editInstance.UpdateMediaType
if editInstance.UpdateAnnotations != nil {
updatedAnnotations = true
if editInstance.UpdateAffectAnnotations {
index.Manifests[targetIndex].Annotations = maps.Clone(editInstance.UpdateAnnotations)
} else {
if index.Manifests[targetIndex].Annotations == nil {
index.Manifests[targetIndex].Annotations = map[string]string{}
}
maps.Copy(index.Manifests[targetIndex].Annotations, editInstance.UpdateAnnotations)
}
}
addCompressionAnnotations(editInstance.UpdateCompressionAlgorithms, index.Manifests[targetIndex].Annotations)
case ListOpAdd:
annotations := map[string]string{}
if editInstance.AddAnnotations != nil {
annotations = maps.Clone(editInstance.AddAnnotations)
}
addCompressionAnnotations(editInstance.AddCompressionAlgorithms, annotations)
addedEntries = append(addedEntries, imgspecv1.Descriptor{
MediaType: editInstance.AddMediaType,
Size: editInstance.AddSize,
Digest: editInstance.AddDigest,
Platform: editInstance.AddPlatform,
Annotations: editInstance.AddAnnotations})
Annotations: annotations})
default:
return fmt.Errorf("internal error: invalid operation: %d", editInstance.ListOperation)
}
}
if len(addedEntries) != 0 {
index.Manifests = append(index.Manifests, addedEntries...)
}
if len(addedEntries) != 0 || updatedAnnotations {
slices.SortStableFunc(index.Manifests, func(a, b imgspecv1.Descriptor) bool {
return !instanceIsZstd(a) && instanceIsZstd(b)
})
Expand Down
36 changes: 35 additions & 1 deletion internal/manifest/oci_index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"path/filepath"
"testing"

"github.com/containers/image/v5/pkg/compression"
"github.com/containers/image/v5/types"
"github.com/opencontainers/go-digest"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
Expand Down Expand Up @@ -99,6 +100,23 @@ func TestOCI1EditInstances(t *testing.T) {
AddPlatform: &imgspecv1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"sse4"}},
AddAnnotations: annotations,
ListOperation: ListOpAdd})
// with zstd but with compression, annotation must be added automatically
editInstances = append(editInstances, ListEdit{
AddDigest: "sha256:hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh",
AddSize: 32,
AddMediaType: "application/vnd.oci.image.manifest.v1+json",
AddPlatform: &imgspecv1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"sse4"}},
AddCompressionAlgorithms: []compression.Algorithm{compression.Zstd},
AddAnnotations: map[string]string{},
ListOperation: ListOpAdd})
// with zstd but with compression, annotation must be added automatically and AddAnnotations is unset
editInstances = append(editInstances, ListEdit{
AddDigest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
AddSize: 32,
AddMediaType: "application/vnd.oci.image.manifest.v1+json",
AddPlatform: &imgspecv1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"sse4"}},
AddCompressionAlgorithms: []compression.Algorithm{compression.Zstd},
ListOperation: ListOpAdd})
// without zstd
editInstances = append(editInstances, ListEdit{
AddDigest: "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
Expand All @@ -110,7 +128,23 @@ func TestOCI1EditInstances(t *testing.T) {
require.NoError(t, err)

// Zstd should be kept on lowest priority as compared to the default gzip ones and order of prior elements must be preserved.
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:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee")})
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:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"), digest.Digest("sha256:hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh"), digest.Digest("sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")})

// Update list and remove zstd annotation from existing instance, and verify if resorting works
editInstances = []ListEdit{}
editInstances = append(editInstances, ListEdit{
UpdateOldDigest: digest.Digest("sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
UpdateDigest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
UpdateSize: 32,
UpdateMediaType: "application/vnd.oci.image.manifest.v1+json",
UpdateAffectAnnotations: true,
UpdateAnnotations: map[string]string{},
ListOperation: ListOpUpdate})
err = list.EditInstances(editInstances)
require.NoError(t, err)
// Digest `ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff` should be re-ordered on update.
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")})

}

func TestOCI1IndexChooseInstanceByCompression(t *testing.T) {
Expand Down

0 comments on commit 35341f8

Please sign in to comment.