Skip to content

Commit cb9a242

Browse files
giuseppeclaude
andcommitted
manifest: add zstd:chunked sentinel annotation support and instance selection
Add annotation constants for identifying zstd:chunked sentinel layers in OCI manifests. Extend instance candidate selection to prefer manifests with sentinel annotations when available. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
1 parent fcaf436 commit cb9a242

1 file changed

Lines changed: 37 additions & 8 deletions

File tree

image/internal/manifest/oci_index.go

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/opencontainers/go-digest"
1313
imgspec "github.com/opencontainers/image-spec/specs-go"
1414
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
15+
"github.com/sirupsen/logrus"
1516
platform "go.podman.io/image/v5/internal/pkg/platform"
1617
compression "go.podman.io/image/v5/pkg/compression/types"
1718
"go.podman.io/image/v5/types"
@@ -26,6 +27,12 @@ const (
2627
// use gzip, depending on their local policy.
2728
OCI1InstanceAnnotationCompressionZSTD = "io.github.containers.compression.zstd"
2829
OCI1InstanceAnnotationCompressionZSTDValue = "true"
30+
31+
// OCI1InstanceAnnotationZstdChunkedSentinel is an annotation name that can be placed on a manifest descriptor
32+
// in an OCI index. The value must be "true". It signals that the manifest contains a sentinel layer
33+
// prepended to its layers, indicating that aware clients can skip the zstd:chunked full-digest mitigation.
34+
OCI1InstanceAnnotationZstdChunkedSentinel = "io.github.containers.zstd-chunked.sentinel"
35+
OCI1InstanceAnnotationZstdChunkedSentinelValue = "true"
2936
)
3037

3138
// OCI1IndexPublic is just an alias for the OCI index type, but one which we can
@@ -187,16 +194,14 @@ func (index *OCI1IndexPublic) editInstances(editInstances []ListEdit, cannotModi
187194
}
188195
if len(addedEntries) != 0 || updatedAnnotations {
189196
slices.SortStableFunc(index.Manifests, func(a, b imgspecv1.Descriptor) int {
190-
// FIXME? With Go 1.21 and cmp.Compare available, turn instanceIsZstd into an integer score that can be compared, and generalizes
191-
// into more algorithms?
192-
aZstd := instanceIsZstd(a)
193-
bZstd := instanceIsZstd(b)
197+
aScore := instanceSortScore(a)
198+
bScore := instanceSortScore(b)
194199
switch {
195-
case aZstd == bZstd:
200+
case aScore == bScore:
196201
return 0
197-
case !aZstd: // Implies bZstd
202+
case aScore < bScore:
198203
return -1
199-
default: // aZstd && !bZstd
204+
default:
200205
return 1
201206
}
202207
})
@@ -218,9 +223,28 @@ func instanceIsZstd(manifest imgspecv1.Descriptor) bool {
218223
return false
219224
}
220225

226+
// instanceHasSentinel returns true if instance has the zstd:chunked sentinel annotation.
227+
func instanceHasSentinel(manifest imgspecv1.Descriptor) bool {
228+
return manifest.Annotations[OCI1InstanceAnnotationZstdChunkedSentinel] == OCI1InstanceAnnotationZstdChunkedSentinelValue
229+
}
230+
231+
// instanceSortScore returns a sorting score for manifest index ordering:
232+
// 0 = plain (gzip), 1 = zstd, 2 = zstd:chunked sentinel.
233+
// Lower scores sort first in the index.
234+
func instanceSortScore(d imgspecv1.Descriptor) int {
235+
if instanceHasSentinel(d) {
236+
return 2
237+
}
238+
if instanceIsZstd(d) {
239+
return 1
240+
}
241+
return 0
242+
}
243+
221244
type instanceCandidate struct {
222245
platformIndex int // Index of the candidate in platform.WantedPlatforms: lower numbers are preferred; or math.maxInt if the candidate doesn’t have a platform
223246
isZstd bool // tells if particular instance if zstd instance
247+
hasSentinel bool // tells if particular instance has the zstd:chunked sentinel layer
224248
manifestPosition int // A zero-based index of the instance in the manifest list
225249
digest digest.Digest // Instance digest
226250
}
@@ -235,6 +259,8 @@ func (ic instanceCandidate) isPreferredOver(other *instanceCandidate, preferGzip
235259
} else {
236260
return !ic.isZstd
237261
}
262+
case ic.hasSentinel != other.hasSentinel:
263+
return ic.hasSentinel
238264
case ic.manifestPosition != other.manifestPosition:
239265
return ic.manifestPosition < other.manifestPosition
240266
}
@@ -248,7 +274,7 @@ func (index *OCI1IndexPublic) chooseInstance(ctx *types.SystemContext, preferGzi
248274
var bestMatch *instanceCandidate
249275
bestMatch = nil
250276
for manifestIndex, d := range index.Manifests {
251-
candidate := instanceCandidate{platformIndex: math.MaxInt, manifestPosition: manifestIndex, isZstd: instanceIsZstd(d), digest: d.Digest}
277+
candidate := instanceCandidate{platformIndex: math.MaxInt, manifestPosition: manifestIndex, isZstd: instanceIsZstd(d), hasSentinel: instanceHasSentinel(d), digest: d.Digest}
252278
if d.Platform != nil {
253279
imagePlatform := ociPlatformClone(*d.Platform)
254280
platformIndex := slices.IndexFunc(wantedPlatforms, func(wantedPlatform imgspecv1.Platform) bool {
@@ -264,6 +290,9 @@ func (index *OCI1IndexPublic) chooseInstance(ctx *types.SystemContext, preferGzi
264290
}
265291
}
266292
if bestMatch != nil {
293+
if bestMatch.hasSentinel {
294+
logrus.Debugf("Selected instance %s with zstd:chunked sentinel (position %d in index)", bestMatch.digest, bestMatch.manifestPosition)
295+
}
267296
return bestMatch.digest, nil
268297
}
269298
return "", fmt.Errorf("no image found in image index for architecture %q, variant %q, OS %q", wantedPlatforms[0].Architecture, wantedPlatforms[0].Variant, wantedPlatforms[0].OS)

0 commit comments

Comments
 (0)