Merge e9b5d376b7
into 5d13311073
This commit is contained in:
commit
dbad01fcb7
6
go.mod
6
go.mod
|
@ -137,3 +137,9 @@ require (
|
||||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||||
tags.cncf.io/container-device-interface/specs-go v1.0.0 // indirect
|
tags.cncf.io/container-device-interface/specs-go v1.0.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace go.podman.io/common => github.com/bitoku/container-libs/common v0.0.0-20251001085230-f74ad300c684
|
||||||
|
|
||||||
|
replace go.podman.io/storage => github.com/bitoku/container-libs/storage v0.0.0-20251001085230-f74ad300c684
|
||||||
|
|
||||||
|
replace go.podman.io/image/v5 => github.com/bitoku/container-libs/image/v5 v5.0.0-20251001085230-f74ad300c684
|
||||||
|
|
12
go.sum
12
go.sum
|
@ -18,6 +18,12 @@ github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6 h1:5L8Mj9Co9sJVgW3TpY
|
||||||
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6/go.mod h1:3HgLJ9d18kXMLQlJvIY3+FszZYMxCz8WfE2MQ7hDY0w=
|
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6/go.mod h1:3HgLJ9d18kXMLQlJvIY3+FszZYMxCz8WfE2MQ7hDY0w=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/bitoku/container-libs/common v0.0.0-20251001085230-f74ad300c684 h1:9nKP5MUjA0tyrqUqvEzeHpYLBGkK+qh+EdVXfHC0kfc=
|
||||||
|
github.com/bitoku/container-libs/common v0.0.0-20251001085230-f74ad300c684/go.mod h1:DyOdwtkwzYA8lE0TueJnxRju4Lmsrx6ZAC/ATAkYYck=
|
||||||
|
github.com/bitoku/container-libs/image/v5 v5.0.0-20251001085230-f74ad300c684 h1:no+ZS52NRYbenjo2Hte4UCdFH9PMV8gIDt7yXfAAF2E=
|
||||||
|
github.com/bitoku/container-libs/image/v5 v5.0.0-20251001085230-f74ad300c684/go.mod h1:cGWb3IyBziJGxhFikTOlt9Ap+zo6s3rz9Qd1rbzqs4s=
|
||||||
|
github.com/bitoku/container-libs/storage v0.0.0-20251001085230-f74ad300c684 h1:ZPtKzmDeEUf4RU/I77Xb1jASYLTYj5h9/325ymQH624=
|
||||||
|
github.com/bitoku/container-libs/storage v0.0.0-20251001085230-f74ad300c684/go.mod h1:AeZXAN8Qu1gTlAEHIc6mVhxk+61oMSM3K3iLx5UAQWE=
|
||||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
|
@ -317,12 +323,6 @@ go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKr
|
||||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||||
go.podman.io/common v0.65.1-0.20250916163606-92222dcd3da4 h1:YjBqTOxz4cqfpifcd71VoBl1FTQL2U2La5NgMqmRRqU=
|
|
||||||
go.podman.io/common v0.65.1-0.20250916163606-92222dcd3da4/go.mod h1:DyOdwtkwzYA8lE0TueJnxRju4Lmsrx6ZAC/ATAkYYck=
|
|
||||||
go.podman.io/image/v5 v5.37.1-0.20250916163606-92222dcd3da4 h1:hfc3lZaxi6KGnWN3IusIaCkcMPR4rTR+vWZzakeD1EA=
|
|
||||||
go.podman.io/image/v5 v5.37.1-0.20250916163606-92222dcd3da4/go.mod h1:cGWb3IyBziJGxhFikTOlt9Ap+zo6s3rz9Qd1rbzqs4s=
|
|
||||||
go.podman.io/storage v1.60.1-0.20250916163606-92222dcd3da4 h1:jo0PSKh6muU7rmhXXqOV9aK+HrA8koqs47KhBsZf6LY=
|
|
||||||
go.podman.io/storage v1.60.1-0.20250916163606-92222dcd3da4/go.mod h1:AeZXAN8Qu1gTlAEHIc6mVhxk+61oMSM3K3iLx5UAQWE=
|
|
||||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||||
|
|
|
@ -707,6 +707,13 @@ type Destination struct {
|
||||||
// Identity file with ssh key, optional
|
// Identity file with ssh key, optional
|
||||||
Identity string `json:",omitempty" toml:"identity,omitempty"`
|
Identity string `json:",omitempty" toml:"identity,omitempty"`
|
||||||
|
|
||||||
|
// Path to TLS client certificate PEM file, optional
|
||||||
|
TLSCert string `json:",omitempty" toml:"tls_cert,omitempty"`
|
||||||
|
// Path to TLS client certificate private key PEM file, optional
|
||||||
|
TLSKey string `json:",omitempty" toml:"tls_key,omitempty"`
|
||||||
|
// Path to TLS certificate authority PEM file, optional
|
||||||
|
TLSCA string `json:",omitempty" toml:"tls_ca,omitempty"`
|
||||||
|
|
||||||
// isMachine describes if the remote destination is a machine.
|
// isMachine describes if the remote destination is a machine.
|
||||||
IsMachine bool `json:",omitempty" toml:"is_machine,omitempty"`
|
IsMachine bool `json:",omitempty" toml:"is_machine,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -779,10 +779,17 @@ default_sysctls = [
|
||||||
# rootful "unix:///run/podman/podman.sock (Default)
|
# rootful "unix:///run/podman/podman.sock (Default)
|
||||||
# remote rootless ssh://engineering.lab.company.com/run/user/1000/podman/podman.sock
|
# remote rootless ssh://engineering.lab.company.com/run/user/1000/podman/podman.sock
|
||||||
# remote rootful ssh://root@10.10.1.136:22/run/podman/podman.sock
|
# remote rootful ssh://root@10.10.1.136:22/run/podman/podman.sock
|
||||||
|
# tcp/tls remote tcp://10.10.1.136:9443
|
||||||
#
|
#
|
||||||
# uri = "ssh://user@production.example.com/run/user/1001/podman/podman.sock"
|
# uri = "ssh://user@production.example.com/run/user/1001/podman/podman.sock"
|
||||||
# Path to file containing ssh identity key
|
# Path to file containing ssh identity key
|
||||||
# identity = "~/.ssh/id_rsa"
|
# identity = "~/.ssh/id_rsa"
|
||||||
|
# Path to PEM file containing TLS client certificate
|
||||||
|
# tls_cert = "/path/to/certs/podman/tls.crt"
|
||||||
|
# Path to PEM file containing TLS client certificate private key
|
||||||
|
# tls_key = "/path/to/certs/podman/tls.key"
|
||||||
|
# Path to PEM file containing TLS certificate authority (CA) bundle
|
||||||
|
# tls_ca = "/path/to/certs/podman/ca.crt"
|
||||||
|
|
||||||
# Directory for temporary files. Must be tmpfs (wiped after reboot)
|
# Directory for temporary files. Must be tmpfs (wiped after reboot)
|
||||||
#
|
#
|
||||||
|
|
|
@ -598,10 +598,17 @@ default_sysctls = [
|
||||||
# rootful "unix:///run/podman/podman.sock (Default)
|
# rootful "unix:///run/podman/podman.sock (Default)
|
||||||
# remote rootless ssh://engineering.lab.company.com/run/user/1000/podman/podman.sock
|
# remote rootless ssh://engineering.lab.company.com/run/user/1000/podman/podman.sock
|
||||||
# remote rootful ssh://root@10.10.1.136:22/run/podman/podman.sock
|
# remote rootful ssh://root@10.10.1.136:22/run/podman/podman.sock
|
||||||
|
# tcp/tls remote tcp://10.10.1.136:9443
|
||||||
#
|
#
|
||||||
# uri = "ssh://user@production.example.com/run/user/1001/podman/podman.sock"
|
# uri = "ssh://user@production.example.com/run/user/1001/podman/podman.sock"
|
||||||
# Path to file containing ssh identity key
|
# Path to file containing ssh identity key
|
||||||
# identity = "~/.ssh/id_rsa"
|
# identity = "~/.ssh/id_rsa"
|
||||||
|
# Path to PEM file containing TLS client certificate
|
||||||
|
# tls_cert = "/path/to/certs/podman/tls.crt"
|
||||||
|
# Path to PEM file containing TLS client certificate private key
|
||||||
|
# tls_key = "/path/to/certs/podman/tls.key"
|
||||||
|
# Path to PEM file containing TLS certificate authority (CA) bundle
|
||||||
|
# tls_ca = "/path/to/certs/podman/ca.crt"
|
||||||
|
|
||||||
# Directory for temporary files. Must be tmpfs (wiped after reboot)
|
# Directory for temporary files. Must be tmpfs (wiped after reboot)
|
||||||
#
|
#
|
||||||
|
|
|
@ -39,7 +39,7 @@ func (stub NoSignaturesInitialize) PutSignaturesWithFormat(ctx context.Context,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportsSignatures implements SupportsSignatures() that returns nil.
|
// AlwaysSupportsSignatures implements SupportsSignatures() that returns nil.
|
||||||
// Note that it might be even more useful to return a value dynamically detected based on
|
// Note that it might be even more useful to return a value dynamically detected based on
|
||||||
type AlwaysSupportsSignatures struct{}
|
type AlwaysSupportsSignatures struct{}
|
||||||
|
|
||||||
|
|
|
@ -37,12 +37,22 @@ func (ref ociReference) DeleteImage(ctx context.Context, sys *types.SystemContex
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signaturesToDelete, err := ref.getObsoleteSignatures(blobsToDelete)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err = ref.deleteBlobs(blobsToDelete)
|
err = ref.deleteBlobs(blobsToDelete)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ref.deleteReferenceFromIndex(descriptorIndex)
|
err = ref.deleteReferenceFromIndex(descriptorIndex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref.deleteSignatures(sys, signaturesToDelete)
|
||||||
}
|
}
|
||||||
|
|
||||||
// countBlobsForDescriptor updates dest with usage counts of blobs required for descriptor, INCLUDING descriptor itself.
|
// countBlobsForDescriptor updates dest with usage counts of blobs required for descriptor, INCLUDING descriptor itself.
|
||||||
|
@ -148,6 +158,7 @@ func deleteBlob(blobPath string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deleteReferencesFromIndex deletes manifest from the root index.
|
||||||
func (ref ociReference) deleteReferenceFromIndex(referenceIndex int) error {
|
func (ref ociReference) deleteReferenceFromIndex(referenceIndex int) error {
|
||||||
index, err := ref.getIndex()
|
index, err := ref.getIndex()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -159,6 +170,25 @@ func (ref ociReference) deleteReferenceFromIndex(referenceIndex int) error {
|
||||||
return saveJSON(ref.indexPath(), index)
|
return saveJSON(ref.indexPath(), index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deleteReferencesFromIndex deletes referenceIndex first, and then remove signatures.
|
||||||
|
func (ref ociReference) deleteSignaturesFromIndex(signatures []imgspecv1.Descriptor) error {
|
||||||
|
index, err := ref.getIndex()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
signaturesSet := set.New[digest.Digest]()
|
||||||
|
for _, sign := range signatures {
|
||||||
|
signaturesSet.Add(sign.Digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
index.Manifests = slices.DeleteFunc(index.Manifests, func(d imgspecv1.Descriptor) bool {
|
||||||
|
return signaturesSet.Contains(d.Digest)
|
||||||
|
})
|
||||||
|
|
||||||
|
return saveJSON(ref.indexPath(), index)
|
||||||
|
}
|
||||||
|
|
||||||
func saveJSON(path string, content any) (retErr error) {
|
func saveJSON(path string, content any) (retErr error) {
|
||||||
// If the file already exists, get its mode to preserve it
|
// If the file already exists, get its mode to preserve it
|
||||||
var mode fs.FileMode
|
var mode fs.FileMode
|
||||||
|
@ -187,3 +217,64 @@ func saveJSON(path string, content any) (retErr error) {
|
||||||
|
|
||||||
return json.NewEncoder(file).Encode(content)
|
return json.NewEncoder(file).Encode(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ref ociReference) getObsoleteSignatures(blobsToDelete *set.Set[digest.Digest]) (signaturesToDelete []imgspecv1.Descriptor, err error) {
|
||||||
|
// create a mapping from sigstore tag to its descriptor
|
||||||
|
signDigestMap := make(map[string]imgspecv1.Descriptor)
|
||||||
|
index, err := ref.getIndex()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, m := range index.Manifests {
|
||||||
|
if isSigstoreTag(m.Annotations[imgspecv1.AnnotationRefName]) {
|
||||||
|
signDigestMap[m.Annotations[imgspecv1.AnnotationRefName]] = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for dgst := range blobsToDelete.All() {
|
||||||
|
sigstoreTag, err := sigstoreAttachmentTag(dgst)
|
||||||
|
if err != nil {
|
||||||
|
// This shouldn't happen because all digests in the root index should be valid.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
signDesc, ok := signDigestMap[sigstoreTag]
|
||||||
|
if !ok {
|
||||||
|
// No signature found for this digest
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
signaturesToDelete = append(signaturesToDelete, signDesc)
|
||||||
|
}
|
||||||
|
return signaturesToDelete, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteSignatures delete sigstore signatures of the given manifest digest.
|
||||||
|
func (ref ociReference) deleteSignatures(sys *types.SystemContext, signaturesToDelete []imgspecv1.Descriptor) error {
|
||||||
|
sharedBlobsDir := ""
|
||||||
|
if sys != nil && sys.OCISharedBlobDirPath != "" {
|
||||||
|
sharedBlobsDir = sys.OCISharedBlobDirPath
|
||||||
|
}
|
||||||
|
|
||||||
|
blobsUsedByImage := make(map[digest.Digest]int)
|
||||||
|
for _, descriptor := range signaturesToDelete {
|
||||||
|
if err := ref.countBlobsForDescriptor(blobsUsedByImage, &descriptor, sharedBlobsDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blobsToDelete, err := ref.getBlobsToDelete(blobsUsedByImage, sharedBlobsDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ref.deleteBlobs(blobsToDelete)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ref.deleteSignaturesFromIndex(signaturesToDelete)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,25 +1,32 @@
|
||||||
package layout
|
package layout
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
digest "github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
imgspec "github.com/opencontainers/image-spec/specs-go"
|
imgspec "github.com/opencontainers/image-spec/specs-go"
|
||||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"go.podman.io/image/v5/internal/imagedestination/impl"
|
"go.podman.io/image/v5/internal/imagedestination/impl"
|
||||||
"go.podman.io/image/v5/internal/imagedestination/stubs"
|
"go.podman.io/image/v5/internal/imagedestination/stubs"
|
||||||
"go.podman.io/image/v5/internal/manifest"
|
"go.podman.io/image/v5/internal/iolimits"
|
||||||
"go.podman.io/image/v5/internal/private"
|
"go.podman.io/image/v5/internal/private"
|
||||||
"go.podman.io/image/v5/internal/putblobdigest"
|
"go.podman.io/image/v5/internal/putblobdigest"
|
||||||
|
"go.podman.io/image/v5/internal/set"
|
||||||
|
"go.podman.io/image/v5/internal/signature"
|
||||||
|
"go.podman.io/image/v5/manifest"
|
||||||
|
"go.podman.io/image/v5/pkg/blobinfocache/none"
|
||||||
"go.podman.io/image/v5/types"
|
"go.podman.io/image/v5/types"
|
||||||
"go.podman.io/storage/pkg/fileutils"
|
"go.podman.io/storage/pkg/fileutils"
|
||||||
)
|
)
|
||||||
|
@ -27,13 +34,17 @@ import (
|
||||||
type ociImageDestination struct {
|
type ociImageDestination struct {
|
||||||
impl.Compat
|
impl.Compat
|
||||||
impl.PropertyMethodsInitialize
|
impl.PropertyMethodsInitialize
|
||||||
|
stubs.AlwaysSupportsSignatures
|
||||||
stubs.IgnoresOriginalOCIConfig
|
stubs.IgnoresOriginalOCIConfig
|
||||||
stubs.NoPutBlobPartialInitialize
|
stubs.NoPutBlobPartialInitialize
|
||||||
stubs.NoSignaturesInitialize
|
|
||||||
|
|
||||||
ref ociReference
|
ref ociReference
|
||||||
index imgspecv1.Index
|
index imgspecv1.Index
|
||||||
sharedBlobDir string
|
sharedBlobDir string
|
||||||
|
manifestDigest digest.Digest // or "" if not yet known.
|
||||||
|
// blobDeleteCandidates is a set of digests which may be deleted _if_ we find no other references to them;
|
||||||
|
// it’s safe to optimistically include entries which may have other references
|
||||||
|
blobDeleteCandidates *set.Set[digest.Digest]
|
||||||
}
|
}
|
||||||
|
|
||||||
// newImageDestination returns an ImageDestination for writing to an existing directory.
|
// newImageDestination returns an ImageDestination for writing to an existing directory.
|
||||||
|
@ -75,10 +86,10 @@ func newImageDestination(sys *types.SystemContext, ref ociReference) (private.Im
|
||||||
HasThreadSafePutBlob: true,
|
HasThreadSafePutBlob: true,
|
||||||
}),
|
}),
|
||||||
NoPutBlobPartialInitialize: stubs.NoPutBlobPartial(ref),
|
NoPutBlobPartialInitialize: stubs.NoPutBlobPartial(ref),
|
||||||
NoSignaturesInitialize: stubs.NoSignatures("Pushing signatures for OCI images is not supported"),
|
|
||||||
|
|
||||||
ref: ref,
|
ref: ref,
|
||||||
index: *index,
|
index: *index,
|
||||||
|
blobDeleteCandidates: set.New[digest.Digest](),
|
||||||
}
|
}
|
||||||
d.Compat = impl.AddCompat(d)
|
d.Compat = impl.AddCompat(d)
|
||||||
if sys != nil {
|
if sys != nil {
|
||||||
|
@ -255,6 +266,9 @@ func (d *ociImageDestination) PutManifest(ctx context.Context, m []byte, instanc
|
||||||
if instanceDigest != nil {
|
if instanceDigest != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// d.manifestDigest is used for a single image (not a manifest list).
|
||||||
|
// This should be placed after checking instanceDigest is nil.
|
||||||
|
d.manifestDigest = digest
|
||||||
|
|
||||||
// If we had platform information, we'd build an imgspecv1.Platform structure here.
|
// If we had platform information, we'd build an imgspecv1.Platform structure here.
|
||||||
|
|
||||||
|
@ -312,6 +326,26 @@ func (d *ociImageDestination) CommitWithOptions(ctx context.Context, options pri
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Delete unreferenced blobs (e.g. old signature manifest and its config)
|
||||||
|
if !d.blobDeleteCandidates.Empty() {
|
||||||
|
blobsUsedInRootIndex := make(map[digest.Digest]int)
|
||||||
|
err = d.ref.countBlobsReferencedByIndex(blobsUsedInRootIndex, &d.index, d.sharedBlobDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error counting blobs to delete: %w", err)
|
||||||
|
}
|
||||||
|
// Don't delete blobs which are referenced
|
||||||
|
actualBlobsToDelete := set.New[digest.Digest]()
|
||||||
|
for dgst := range d.blobDeleteCandidates.All() {
|
||||||
|
if blobsUsedInRootIndex[dgst] == 0 {
|
||||||
|
actualBlobsToDelete.Add(dgst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := d.ref.deleteBlobs(actualBlobsToDelete)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error deleting blobs: %w", err)
|
||||||
|
}
|
||||||
|
d.blobDeleteCandidates = set.New[digest.Digest]()
|
||||||
|
}
|
||||||
if err := os.WriteFile(d.ref.ociLayoutPath(), layoutBytes, 0644); err != nil {
|
if err := os.WriteFile(d.ref.ociLayoutPath(), layoutBytes, 0644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -322,6 +356,164 @@ func (d *ociImageDestination) CommitWithOptions(ctx context.Context, options pri
|
||||||
return os.WriteFile(d.ref.indexPath(), indexJSON, 0644)
|
return os.WriteFile(d.ref.indexPath(), indexJSON, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *ociImageDestination) PutSignaturesWithFormat(ctx context.Context, signatures []signature.Signature, instanceDigest *digest.Digest) error {
|
||||||
|
if instanceDigest == nil {
|
||||||
|
if d.manifestDigest == "" {
|
||||||
|
// This shouldn’t happen, ImageDestination users are required to call PutManifest before PutSignatures
|
||||||
|
return errors.New("unknown manifest digest, can't add signatures")
|
||||||
|
}
|
||||||
|
instanceDigest = &d.manifestDigest
|
||||||
|
}
|
||||||
|
|
||||||
|
var sigstoreSignatures []signature.Sigstore
|
||||||
|
for _, sig := range signatures {
|
||||||
|
if sigstoreSig, ok := sig.(signature.Sigstore); ok {
|
||||||
|
sigstoreSignatures = append(sigstoreSignatures, sigstoreSig)
|
||||||
|
} else {
|
||||||
|
return errors.New("oci: layout only supports sigstore signatures")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := d.putSignaturesToSigstoreAttachment(ctx, sigstoreSignatures, *instanceDigest); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ociImageDestination) putSignaturesToSigstoreAttachment(ctx context.Context, signatures []signature.Sigstore, manifestDigest digest.Digest) error {
|
||||||
|
var signConfig imgspecv1.Image // Most fields empty by default
|
||||||
|
|
||||||
|
signManifest, signDesc, err := d.ref.getSigstoreAttachmentManifest(manifestDigest, &d.index, d.sharedBlobDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if signManifest == nil {
|
||||||
|
signManifest = manifest.OCI1FromComponents(imgspecv1.Descriptor{
|
||||||
|
MediaType: imgspecv1.MediaTypeImageConfig,
|
||||||
|
Digest: "", // We will fill this in later.
|
||||||
|
Size: 0,
|
||||||
|
}, nil)
|
||||||
|
signConfig.RootFS.Type = "layers"
|
||||||
|
} else {
|
||||||
|
logrus.Debugf("Fetching sigstore attachment config %s", signManifest.Config.Digest.String())
|
||||||
|
configBlob, err := d.ref.getOCIDescriptorContents(signManifest.Config.Digest, iolimits.MaxConfigBodySize, d.sharedBlobDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(configBlob, &signConfig); err != nil {
|
||||||
|
return fmt.Errorf("parsing sigstore attachment config %s in %s: %w", signManifest.Config.Digest.String(),
|
||||||
|
d.ref.StringWithinTransport(), err)
|
||||||
|
}
|
||||||
|
// The signature manifest and its config may be updated and unreferenced when a new config is created.
|
||||||
|
d.blobDeleteCandidates.Add(signDesc.Digest)
|
||||||
|
d.blobDeleteCandidates.Add(signManifest.Config.Digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
desc, err := d.getDescriptor(manifestDigest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
signManifest.Subject = desc
|
||||||
|
|
||||||
|
// To make sure we can safely append to the slices of signManifest, without adding a remote dependency on the code that creates it.
|
||||||
|
signManifest.Layers = slices.Clone(signManifest.Layers)
|
||||||
|
for _, sig := range signatures {
|
||||||
|
mimeType := sig.UntrustedMIMEType()
|
||||||
|
payloadBlob := sig.UntrustedPayload()
|
||||||
|
annotations := sig.UntrustedAnnotations()
|
||||||
|
|
||||||
|
// Skip if the signature is already on the registry.
|
||||||
|
if slices.ContainsFunc(signManifest.Layers, func(layer imgspecv1.Descriptor) bool {
|
||||||
|
return layerMatchesSigstoreSignature(layer, mimeType, payloadBlob, annotations)
|
||||||
|
}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
signDesc, err := d.putBlobBytesAsOCI(ctx, payloadBlob, mimeType, private.PutBlobOptions{
|
||||||
|
Cache: none.NoCache,
|
||||||
|
IsConfig: false,
|
||||||
|
EmptyLayer: false,
|
||||||
|
LayerIndex: nil,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
signDesc.Annotations = annotations
|
||||||
|
signManifest.Layers = append(signManifest.Layers, signDesc)
|
||||||
|
signConfig.RootFS.DiffIDs = append(signConfig.RootFS.DiffIDs, signDesc.Digest)
|
||||||
|
logrus.Debugf("Adding new signature, digest %s", signDesc.Digest.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
configBlob, err := json.Marshal(signConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logrus.Debugf("Creating updated sigstore attachment config")
|
||||||
|
configDesc, err := d.putBlobBytesAsOCI(ctx, configBlob, imgspecv1.MediaTypeImageConfig, private.PutBlobOptions{
|
||||||
|
Cache: none.NoCache,
|
||||||
|
IsConfig: true,
|
||||||
|
EmptyLayer: false,
|
||||||
|
LayerIndex: nil,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
signManifest.Config = configDesc
|
||||||
|
signManifestBlob, err := signManifest.Serialize()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logrus.Debugf("Creating sigstore attachment manifest")
|
||||||
|
signDigest := digest.FromBytes(signManifestBlob)
|
||||||
|
if err = d.PutManifest(ctx, signManifestBlob, &signDigest); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
signTag, err := sigstoreAttachmentTag(manifestDigest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.addManifest(&imgspecv1.Descriptor{
|
||||||
|
MediaType: signManifest.MediaType,
|
||||||
|
Digest: signDigest,
|
||||||
|
Size: int64(len(signManifestBlob)),
|
||||||
|
Annotations: map[string]string{
|
||||||
|
imgspecv1.AnnotationRefName: signTag,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ociImageDestination) getDescriptor(digest digest.Digest) (*imgspecv1.Descriptor, error) {
|
||||||
|
for _, desc := range d.index.Manifests {
|
||||||
|
if desc.Digest == digest {
|
||||||
|
return &desc, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("manifest %s not found in index", digest.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// putBlobBytesAsOCI uploads a blob with the specified contents, and returns an appropriate
|
||||||
|
// OCI descriptor.
|
||||||
|
func (d *ociImageDestination) putBlobBytesAsOCI(ctx context.Context, contents []byte, mimeType string, options private.PutBlobOptions) (imgspecv1.Descriptor, error) {
|
||||||
|
blobDigest := digest.FromBytes(contents)
|
||||||
|
info, err := d.PutBlobWithOptions(ctx, bytes.NewReader(contents),
|
||||||
|
types.BlobInfo{
|
||||||
|
Digest: blobDigest,
|
||||||
|
Size: int64(len(contents)),
|
||||||
|
MediaType: mimeType,
|
||||||
|
}, options)
|
||||||
|
if err != nil {
|
||||||
|
return imgspecv1.Descriptor{}, fmt.Errorf("writing blob %s: %w", blobDigest.String(), err)
|
||||||
|
}
|
||||||
|
return imgspecv1.Descriptor{
|
||||||
|
MediaType: mimeType,
|
||||||
|
Digest: info.Digest,
|
||||||
|
Size: info.Size,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// PutBlobFromLocalFileOption is unused but may receive functionality in the future.
|
// PutBlobFromLocalFileOption is unused but may receive functionality in the future.
|
||||||
type PutBlobFromLocalFileOption struct{}
|
type PutBlobFromLocalFileOption struct{}
|
||||||
|
|
||||||
|
@ -412,3 +604,18 @@ func indexExists(ref ociReference) bool {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func layerMatchesSigstoreSignature(layer imgspecv1.Descriptor, mimeType string,
|
||||||
|
payloadBlob []byte, annotations map[string]string) bool {
|
||||||
|
if layer.MediaType != mimeType ||
|
||||||
|
layer.Size != int64(len(payloadBlob)) ||
|
||||||
|
// This is not quite correct, we should use the layer’s digest algorithm.
|
||||||
|
// But right now we don’t want to deal with corner cases like bad digest formats
|
||||||
|
// or unavailable algorithms; in the worst case we end up with duplicate signature
|
||||||
|
// entries.
|
||||||
|
layer.Digest.String() != digest.FromBytes(payloadBlob).String() ||
|
||||||
|
!maps.Equal(layer.Annotations, annotations) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -16,8 +16,10 @@ import (
|
||||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"go.podman.io/image/v5/internal/imagesource/impl"
|
"go.podman.io/image/v5/internal/imagesource/impl"
|
||||||
"go.podman.io/image/v5/internal/imagesource/stubs"
|
"go.podman.io/image/v5/internal/imagesource/stubs"
|
||||||
"go.podman.io/image/v5/internal/manifest"
|
"go.podman.io/image/v5/internal/iolimits"
|
||||||
"go.podman.io/image/v5/internal/private"
|
"go.podman.io/image/v5/internal/private"
|
||||||
|
"go.podman.io/image/v5/internal/signature"
|
||||||
|
"go.podman.io/image/v5/manifest"
|
||||||
"go.podman.io/image/v5/pkg/tlsclientconfig"
|
"go.podman.io/image/v5/pkg/tlsclientconfig"
|
||||||
"go.podman.io/image/v5/types"
|
"go.podman.io/image/v5/types"
|
||||||
"go.podman.io/storage/pkg/fileutils"
|
"go.podman.io/storage/pkg/fileutils"
|
||||||
|
@ -37,7 +39,6 @@ func (e ImageNotFoundError) Error() string {
|
||||||
type ociImageSource struct {
|
type ociImageSource struct {
|
||||||
impl.Compat
|
impl.Compat
|
||||||
impl.PropertyMethodsInitialize
|
impl.PropertyMethodsInitialize
|
||||||
impl.NoSignatures
|
|
||||||
impl.DoesNotAffectLayerInfosForCopy
|
impl.DoesNotAffectLayerInfosForCopy
|
||||||
stubs.NoGetBlobAtInitialize
|
stubs.NoGetBlobAtInitialize
|
||||||
|
|
||||||
|
@ -158,20 +159,7 @@ func (s *ociImageSource) GetBlob(ctx context.Context, info types.BlobInfo, cache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
path, err := s.ref.blobPath(info.Digest, s.sharedBlobDir)
|
return s.ref.getBlob(info.Digest, s.sharedBlobDir)
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
fi, err := r.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
return r, fi.Size(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getExternalBlob returns the reader of the first available blob URL from urls, which must not be empty.
|
// getExternalBlob returns the reader of the first available blob URL from urls, which must not be empty.
|
||||||
|
@ -246,3 +234,30 @@ func GetLocalBlobPath(ctx context.Context, src types.ImageSource, digest digest.
|
||||||
|
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ociImageSource) GetSignaturesWithFormat(ctx context.Context, instanceDigest *digest.Digest) ([]signature.Signature, error) {
|
||||||
|
if instanceDigest == nil {
|
||||||
|
instanceDigest = &s.descriptor.Digest
|
||||||
|
}
|
||||||
|
|
||||||
|
ociManifest, _, err := s.ref.getSigstoreAttachmentManifest(*instanceDigest, s.index, s.sharedBlobDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ociManifest == nil {
|
||||||
|
// No signature found
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
signatures := make([]signature.Signature, 0, len(ociManifest.Layers))
|
||||||
|
for _, layer := range ociManifest.Layers {
|
||||||
|
// Note that this copies all kinds of attachments: attestations, and whatever else is there,
|
||||||
|
// not just signatures. We leave the signature consumers to decide based on the MIME type.
|
||||||
|
payload, err := s.ref.getOCIDescriptorContents(layer.Digest, iolimits.MaxSignatureBodySize, s.sharedBlobDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
signatures = append(signatures, signature.SigstoreFromComponents(layer.MediaType, payload, layer.Annotations))
|
||||||
|
}
|
||||||
|
return signatures, nil
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -14,7 +15,8 @@ import (
|
||||||
"go.podman.io/image/v5/directory/explicitfilepath"
|
"go.podman.io/image/v5/directory/explicitfilepath"
|
||||||
"go.podman.io/image/v5/docker/reference"
|
"go.podman.io/image/v5/docker/reference"
|
||||||
"go.podman.io/image/v5/internal/image"
|
"go.podman.io/image/v5/internal/image"
|
||||||
"go.podman.io/image/v5/internal/manifest"
|
"go.podman.io/image/v5/internal/iolimits"
|
||||||
|
"go.podman.io/image/v5/manifest"
|
||||||
"go.podman.io/image/v5/oci/internal"
|
"go.podman.io/image/v5/oci/internal"
|
||||||
"go.podman.io/image/v5/transports"
|
"go.podman.io/image/v5/transports"
|
||||||
"go.podman.io/image/v5/types"
|
"go.podman.io/image/v5/types"
|
||||||
|
@ -28,6 +30,9 @@ var (
|
||||||
// Transport is an ImageTransport for OCI directories.
|
// Transport is an ImageTransport for OCI directories.
|
||||||
Transport = ociTransport{}
|
Transport = ociTransport{}
|
||||||
|
|
||||||
|
// ErrEmptyIndex is an error returned when the index includes no image.
|
||||||
|
ErrEmptyIndex = errors.New("no image in oci")
|
||||||
|
|
||||||
// ErrMoreThanOneImage is an error returned when the manifest includes
|
// ErrMoreThanOneImage is an error returned when the manifest includes
|
||||||
// more than one image and the user should choose which one to use.
|
// more than one image and the user should choose which one to use.
|
||||||
ErrMoreThanOneImage = errors.New("more than one image in oci, choose an image")
|
ErrMoreThanOneImage = errors.New("more than one image in oci, choose an image")
|
||||||
|
@ -248,11 +253,33 @@ func (ref ociReference) getManifestDescriptor() (imgspecv1.Descriptor, int, erro
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// return manifest if only one image is in the oci directory
|
// return manifest if only one image is in the oci directory
|
||||||
if len(index.Manifests) != 1 {
|
if len(index.Manifests) == 0 {
|
||||||
// ask user to choose image when more than one image in the oci directory
|
return imgspecv1.Descriptor{}, -1, ErrEmptyIndex
|
||||||
|
}
|
||||||
|
// if there's one image return it, even if it is a signature
|
||||||
|
if len(index.Manifests) == 1 {
|
||||||
|
return index.Manifests[0], 0, nil
|
||||||
|
}
|
||||||
|
// when there's more than one image, try to get a non-signature image
|
||||||
|
var desc imgspecv1.Descriptor
|
||||||
|
idx := -1
|
||||||
|
for i, md := range index.Manifests {
|
||||||
|
if isSigstoreTag(md.Annotations[imgspecv1.AnnotationRefName]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// More than one non-signature image was found
|
||||||
|
if idx != -1 {
|
||||||
|
// ask user to choose image when more than one image in the oci directory
|
||||||
|
return imgspecv1.Descriptor{}, -1, ErrMoreThanOneImage
|
||||||
|
}
|
||||||
|
desc = md
|
||||||
|
idx = i
|
||||||
|
}
|
||||||
|
// there's only multiple signature images
|
||||||
|
if idx == -1 {
|
||||||
return imgspecv1.Descriptor{}, -1, ErrMoreThanOneImage
|
return imgspecv1.Descriptor{}, -1, ErrMoreThanOneImage
|
||||||
}
|
}
|
||||||
return index.Manifests[0], 0, nil
|
return desc, idx, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,3 +329,101 @@ func (ref ociReference) blobPath(digest digest.Digest, sharedBlobDir string) (st
|
||||||
}
|
}
|
||||||
return filepath.Join(blobDir, digest.Algorithm().String(), digest.Encoded()), nil
|
return filepath.Join(blobDir, digest.Algorithm().String(), digest.Encoded()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sigstoreAttachmentTag returns a sigstore attachment tag for the specified digest.
|
||||||
|
func sigstoreAttachmentTag(d digest.Digest) (string, error) {
|
||||||
|
if err := d.Validate(); err != nil { // Make sure d.String() doesn’t contain any unexpected characters
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return strings.Replace(d.String(), ":", "-", 1) + ".sig", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ref ociReference) getSigstoreAttachmentManifest(d digest.Digest, idx *imgspecv1.Index, sharedBlobDir string) (*manifest.OCI1, *imgspecv1.Descriptor, error) {
|
||||||
|
signTag, err := sigstoreAttachmentTag(d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
var signDesc *imgspecv1.Descriptor
|
||||||
|
for _, m := range idx.Manifests {
|
||||||
|
if m.Annotations[imgspecv1.AnnotationRefName] == signTag {
|
||||||
|
signDesc = &m
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if signDesc == nil {
|
||||||
|
// No signature found
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
if signDesc.MediaType != imgspecv1.MediaTypeImageManifest {
|
||||||
|
return nil, nil, fmt.Errorf("unexpected MIME type for sigstore attachment manifest %s: %q",
|
||||||
|
signTag, signDesc.MediaType)
|
||||||
|
}
|
||||||
|
blobReader, _, err := ref.getBlob(signDesc.Digest, sharedBlobDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to get Blob %s: %w", signTag, err)
|
||||||
|
}
|
||||||
|
defer blobReader.Close()
|
||||||
|
signBlob, err := iolimits.ReadAtMost(blobReader, iolimits.MaxManifestBodySize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to read blob: %w", err)
|
||||||
|
}
|
||||||
|
res, err := manifest.OCI1FromManifest(signBlob)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("parsing manifest %s: %w", signDesc.Digest, err)
|
||||||
|
}
|
||||||
|
return res, signDesc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ref ociReference) getBlob(d digest.Digest, sharedBlobDir string) (io.ReadCloser, int64, error) {
|
||||||
|
path, err := ref.blobPath(d, sharedBlobDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
fi, err := r.Stat()
|
||||||
|
if err != nil {
|
||||||
|
_ = r.Close() // Avoid leak r.
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return r, fi.Size(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ref ociReference) getOCIDescriptorContents(dgst digest.Digest, maxSize int, sharedBlobDir string) ([]byte, error) {
|
||||||
|
if err := dgst.Validate(); err != nil { // .Algorithm() might panic without this check
|
||||||
|
return nil, fmt.Errorf("invalid digest %q: %w", dgst.String(), err)
|
||||||
|
}
|
||||||
|
digestAlgorithm := dgst.Algorithm()
|
||||||
|
if !digestAlgorithm.Available() {
|
||||||
|
return nil, fmt.Errorf("invalid digest %q: unsupported digest algorithm %q", dgst.String(), digestAlgorithm.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, _, err := ref.getBlob(dgst, sharedBlobDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
payload, err := iolimits.ReadAtMost(reader, maxSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reading blob %s in %s: %w", dgst.String(), ref.image, err)
|
||||||
|
}
|
||||||
|
actualDigest := digestAlgorithm.FromBytes(payload)
|
||||||
|
if actualDigest != dgst {
|
||||||
|
return nil, fmt.Errorf("digest mismatch, expected %q, got %q", dgst.String(), actualDigest.String())
|
||||||
|
}
|
||||||
|
return payload, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSigstoreTag returns true if the tag is sigstore signature tag.
|
||||||
|
func isSigstoreTag(tag string) bool {
|
||||||
|
digestPart, found := strings.CutSuffix(tag, ".sig")
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
digestPart = strings.Replace(digestPart, "-", ":", 1)
|
||||||
|
_, err := digest.Parse(digestPart)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
|
@ -464,7 +464,7 @@ go.opentelemetry.io/otel/trace
|
||||||
go.opentelemetry.io/otel/trace/embedded
|
go.opentelemetry.io/otel/trace/embedded
|
||||||
go.opentelemetry.io/otel/trace/internal/telemetry
|
go.opentelemetry.io/otel/trace/internal/telemetry
|
||||||
go.opentelemetry.io/otel/trace/noop
|
go.opentelemetry.io/otel/trace/noop
|
||||||
# go.podman.io/common v0.65.1-0.20250916163606-92222dcd3da4
|
# go.podman.io/common v0.65.1-0.20250916163606-92222dcd3da4 => github.com/bitoku/container-libs/common v0.0.0-20251001085230-f74ad300c684
|
||||||
## explicit; go 1.24.2
|
## explicit; go 1.24.2
|
||||||
go.podman.io/common/internal
|
go.podman.io/common/internal
|
||||||
go.podman.io/common/internal/attributedstring
|
go.podman.io/common/internal/attributedstring
|
||||||
|
@ -518,7 +518,7 @@ go.podman.io/common/pkg/umask
|
||||||
go.podman.io/common/pkg/util
|
go.podman.io/common/pkg/util
|
||||||
go.podman.io/common/pkg/version
|
go.podman.io/common/pkg/version
|
||||||
go.podman.io/common/version
|
go.podman.io/common/version
|
||||||
# go.podman.io/image/v5 v5.37.1-0.20250916163606-92222dcd3da4
|
# go.podman.io/image/v5 v5.37.1-0.20250916163606-92222dcd3da4 => github.com/bitoku/container-libs/image/v5 v5.0.0-20251001085230-f74ad300c684
|
||||||
## explicit; go 1.24.0
|
## explicit; go 1.24.0
|
||||||
go.podman.io/image/v5/copy
|
go.podman.io/image/v5/copy
|
||||||
go.podman.io/image/v5/directory
|
go.podman.io/image/v5/directory
|
||||||
|
@ -586,7 +586,7 @@ go.podman.io/image/v5/transports
|
||||||
go.podman.io/image/v5/transports/alltransports
|
go.podman.io/image/v5/transports/alltransports
|
||||||
go.podman.io/image/v5/types
|
go.podman.io/image/v5/types
|
||||||
go.podman.io/image/v5/version
|
go.podman.io/image/v5/version
|
||||||
# go.podman.io/storage v1.60.1-0.20250916163606-92222dcd3da4
|
# go.podman.io/storage v1.60.1-0.20250916163606-92222dcd3da4 => github.com/bitoku/container-libs/storage v0.0.0-20251001085230-f74ad300c684
|
||||||
## explicit; go 1.24.0
|
## explicit; go 1.24.0
|
||||||
go.podman.io/storage
|
go.podman.io/storage
|
||||||
go.podman.io/storage/drivers
|
go.podman.io/storage/drivers
|
||||||
|
@ -826,3 +826,6 @@ tags.cncf.io/container-device-interface/pkg/parser
|
||||||
# tags.cncf.io/container-device-interface/specs-go v1.0.0
|
# tags.cncf.io/container-device-interface/specs-go v1.0.0
|
||||||
## explicit; go 1.19
|
## explicit; go 1.19
|
||||||
tags.cncf.io/container-device-interface/specs-go
|
tags.cncf.io/container-device-interface/specs-go
|
||||||
|
# go.podman.io/common => github.com/bitoku/container-libs/common v0.0.0-20251001085230-f74ad300c684
|
||||||
|
# go.podman.io/storage => github.com/bitoku/container-libs/storage v0.0.0-20251001085230-f74ad300c684
|
||||||
|
# go.podman.io/image/v5 => github.com/bitoku/container-libs/image/v5 v5.0.0-20251001085230-f74ad300c684
|
||||||
|
|
Loading…
Reference in New Issue