diff --git a/go.mod b/go.mod index f0b6f061c3..3b0dcc50aa 100644 --- a/go.mod +++ b/go.mod @@ -14,14 +14,14 @@ require ( github.com/checkpoint-restore/go-criu/v7 v7.2.0 github.com/containernetworking/plugins v1.5.1 github.com/containers/buildah v1.38.1-0.20250125114111-92015b7f4301 - github.com/containers/common v0.61.1-0.20250124131345-fa339b6b6eda + github.com/containers/common v0.62.0 github.com/containers/conmon v2.0.20+incompatible github.com/containers/gvisor-tap-vsock v0.8.2 - github.com/containers/image/v5 v5.33.2-0.20250122233652-b5c6aff95ca7 + github.com/containers/image/v5 v5.34.0 github.com/containers/libhvee v0.9.0 github.com/containers/ocicrypt v1.2.1 github.com/containers/psgo v1.9.0 - github.com/containers/storage v1.56.2-0.20250123125217-80d3c0e77d29 + github.com/containers/storage v1.57.1 github.com/containers/winquit v1.1.0 github.com/coreos/go-systemd/v22 v22.5.1-0.20231103132048-7d375ecc2b09 github.com/crc-org/crc/v2 v2.45.0 diff --git a/go.sum b/go.sum index 1a3cfcbdf1..f87b32633f 100644 --- a/go.sum +++ b/go.sum @@ -78,14 +78,14 @@ github.com/containernetworking/plugins v1.5.1 h1:T5ji+LPYjjgW0QM+KyrigZbLsZ8jaX+ github.com/containernetworking/plugins v1.5.1/go.mod h1:MIQfgMayGuHYs0XdNudf31cLLAC+i242hNm6KuDGqCM= github.com/containers/buildah v1.38.1-0.20250125114111-92015b7f4301 h1:eqHczpfbWOjyvQAkuCmzsZPds657cPXxXniXLcKFHdQ= github.com/containers/buildah v1.38.1-0.20250125114111-92015b7f4301/go.mod h1:UOhJzUS2A0uyZR/TygObcg2Og+nJ02pwMfVhIQBRIN8= -github.com/containers/common v0.61.1-0.20250124131345-fa339b6b6eda h1:ltztaW234A8UvR3xz0hY4eD8ABo3B6gd3kGKVHPqGuE= -github.com/containers/common v0.61.1-0.20250124131345-fa339b6b6eda/go.mod h1:mWhwkYaWR5bXeOwq3ruzdmH9gaT2pex00C6pd4VXuvM= +github.com/containers/common v0.62.0 h1:Sl9WE5h7Y/F3bejrMAA4teP1EcY9ygqJmW4iwSloZ10= +github.com/containers/common v0.62.0/go.mod h1:Yec+z8mrSq4rydHofrnDCBqAcNA/BGrSg1kfFUL6F6s= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/gvisor-tap-vsock v0.8.2 h1:uQMBCCHlIIj62fPjbvgm6AL5EzsP6TP5eviByOJEsOg= github.com/containers/gvisor-tap-vsock v0.8.2/go.mod h1:EMRe2o63ddq2zxcP0hTysmxCf/5JlaNEg8/gpzP0ox4= -github.com/containers/image/v5 v5.33.2-0.20250122233652-b5c6aff95ca7 h1:dTKluTTlijEdQmTZPK1wTX39qXb4F93/GPHhq+vlC3U= -github.com/containers/image/v5 v5.33.2-0.20250122233652-b5c6aff95ca7/go.mod h1:NyH4/AlLW8cvPgtwbCMzz71dr9SOh8/WP9ua5c9Akc8= +github.com/containers/image/v5 v5.34.0 h1:HPqQaDUsox/3mC1pbOyLAIQEp0JhQqiUZ+6JiFIZLDI= +github.com/containers/image/v5 v5.34.0/go.mod h1:/WnvUSEfdqC/ahMRd4YJDBLrpYWkGl018rB77iB3FDo= github.com/containers/libhvee v0.9.0 h1:5UxJMka1lDfxTeITA25Pd8QVVttJAG43eQS1Getw1tc= github.com/containers/libhvee v0.9.0/go.mod h1:p44VJd8jMIx3SRN1eM6PxfCEwXQE0lJ0dQppCAlzjPQ= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= @@ -96,8 +96,8 @@ github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpV github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ= github.com/containers/psgo v1.9.0 h1:eJ74jzSaCHnWt26OlKZROSyUyRcGDf+gYBdXnxrMW4g= github.com/containers/psgo v1.9.0/go.mod h1:0YoluUm43Mz2UnBIh1P+6V6NWcbpTL5uRtXyOcH0B5A= -github.com/containers/storage v1.56.2-0.20250123125217-80d3c0e77d29 h1:3L1QCfh72xytLizQeswDaKcB5ajq54DuFRcAy/C3P3c= -github.com/containers/storage v1.56.2-0.20250123125217-80d3c0e77d29/go.mod h1:i/Hb4lu7YgFr9G0K6BMjqW0BLJO1sFsnWQwj2UoWCUM= +github.com/containers/storage v1.57.1 h1:hKPoFsuBcB3qTzBxa4IFpZMRzUuL5Xhv/BE44W0XHx8= +github.com/containers/storage v1.57.1/go.mod h1:i/Hb4lu7YgFr9G0K6BMjqW0BLJO1sFsnWQwj2UoWCUM= github.com/containers/winquit v1.1.0 h1:jArun04BNDQvt2W0Y78kh9TazN2EIEMG5Im6/JY7+pE= github.com/containers/winquit v1.1.0/go.mod h1:PsPeZlnbkmGGIToMPHF1zhWjBUkd8aHjMOr/vFcPxw8= github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo= diff --git a/vendor/github.com/containers/common/libnetwork/cni/config.go b/vendor/github.com/containers/common/libnetwork/cni/config.go index 43f3bfef46..9b45c1589c 100644 --- a/vendor/github.com/containers/common/libnetwork/cni/config.go +++ b/vendor/github.com/containers/common/libnetwork/cni/config.go @@ -86,7 +86,7 @@ func (n *cniNetwork) networkCreate(newNetwork *types.Network, defaultNet bool) ( switch newNetwork.Driver { case types.BridgeNetworkDriver: internalutil.MapDockerBridgeDriverOptions(newNetwork) - err = internalutil.CreateBridge(n, newNetwork, usedNetworks, n.defaultsubnetPools) + err = internalutil.CreateBridge(n, newNetwork, usedNetworks, n.defaultsubnetPools, true) if err != nil { return nil, err } diff --git a/vendor/github.com/containers/common/libnetwork/internal/rootlessnetns/netns_linux.go b/vendor/github.com/containers/common/libnetwork/internal/rootlessnetns/netns_linux.go index 7fac465a62..2655587654 100644 --- a/vendor/github.com/containers/common/libnetwork/internal/rootlessnetns/netns_linux.go +++ b/vendor/github.com/containers/common/libnetwork/internal/rootlessnetns/netns_linux.go @@ -135,6 +135,15 @@ func (n *Netns) getOrCreateNetns() (ns.NetNS, bool, error) { } // In case of errors continue and setup the network cmd again. } else { + // Special case, the file might exist already but is not a valid netns. + // One reason could be that a previous setup was killed between creating + // the file and mounting it. Or if the file is not on tmpfs (deleted on boot) + // you might run into it as well: https://github.com/containers/podman/issues/25144 + // We have to do this because NewNSAtPath fails with EEXIST otherwise + if errors.As(err, &ns.NSPathNotNSErr{}) { + // We don't care if this fails, NewNSAtPath() should return the real error. + _ = os.Remove(nsPath) + } logrus.Debugf("Creating rootless network namespace at %q", nsPath) // We have to create the netns dir again here because it is possible // that cleanup() removed it. diff --git a/vendor/github.com/containers/common/libnetwork/internal/util/bridge.go b/vendor/github.com/containers/common/libnetwork/internal/util/bridge.go index b75e9a57f4..44cd555c17 100644 --- a/vendor/github.com/containers/common/libnetwork/internal/util/bridge.go +++ b/vendor/github.com/containers/common/libnetwork/internal/util/bridge.go @@ -10,11 +10,13 @@ import ( "github.com/containers/common/pkg/config" ) -func CreateBridge(n NetUtil, network *types.Network, usedNetworks []*net.IPNet, subnetPools []config.SubnetPool) error { +func CreateBridge(n NetUtil, network *types.Network, usedNetworks []*net.IPNet, subnetPools []config.SubnetPool, checkBridgeConflict bool) error { if network.NetworkInterface != "" { - bridges := GetBridgeInterfaceNames(n) - if slices.Contains(bridges, network.NetworkInterface) { - return fmt.Errorf("bridge name %s already in use", network.NetworkInterface) + if checkBridgeConflict { + bridges := GetBridgeInterfaceNames(n) + if slices.Contains(bridges, network.NetworkInterface) { + return fmt.Errorf("bridge name %s already in use", network.NetworkInterface) + } } if !types.NameRegex.MatchString(network.NetworkInterface) { return fmt.Errorf("bridge name %s invalid: %w", network.NetworkInterface, types.RegexError) diff --git a/vendor/github.com/containers/common/libnetwork/netavark/config.go b/vendor/github.com/containers/common/libnetwork/netavark/config.go index 3305258b6c..4916f725e1 100644 --- a/vendor/github.com/containers/common/libnetwork/netavark/config.go +++ b/vendor/github.com/containers/common/libnetwork/netavark/config.go @@ -169,11 +169,9 @@ func (n *netavarkNetwork) networkCreate(newNetwork *types.Network, defaultNet bo switch newNetwork.Driver { case types.BridgeNetworkDriver: internalutil.MapDockerBridgeDriverOptions(newNetwork) - err = internalutil.CreateBridge(n, newNetwork, usedNetworks, n.defaultsubnetPools) - if err != nil { - return nil, err - } - // validate the given options, we do not need them but just check to make sure they are valid + + var vlan int + // validate the given options, for key, value := range newNetwork.Options { switch key { case types.MTUOption: @@ -183,7 +181,7 @@ func (n *netavarkNetwork) networkCreate(newNetwork *types.Network, defaultNet bo } case types.VLANOption: - _, err = internalutil.ParseVlan(value) + vlan, err = internalutil.ParseVlan(value) if err != nil { return nil, err } @@ -218,6 +216,17 @@ func (n *netavarkNetwork) networkCreate(newNetwork *types.Network, defaultNet bo return nil, fmt.Errorf("unsupported bridge network option %s", key) } } + + // If there is no vlan there should be no other config with the same bridge. + // However with vlan we want to allow that so that you can have different + // configs on the same bridge but different vlans + // https://github.com/containers/common/issues/2095 + checkBridgeConflict := vlan == 0 + err = internalutil.CreateBridge(n, newNetwork, usedNetworks, n.defaultsubnetPools, checkBridgeConflict) + if err != nil { + return nil, err + } + case types.MacVLANNetworkDriver, types.IPVLANNetworkDriver: err = createIpvlanOrMacvlan(newNetwork) if err != nil { diff --git a/vendor/github.com/containers/common/version/version.go b/vendor/github.com/containers/common/version/version.go index 2cdbff5b29..f676ffa999 100644 --- a/vendor/github.com/containers/common/version/version.go +++ b/vendor/github.com/containers/common/version/version.go @@ -1,4 +1,4 @@ package version // Version is the version of the build. -const Version = "0.62.0-dev" +const Version = "0.62.0" diff --git a/vendor/github.com/containers/image/v5/docker/registries_d.go b/vendor/github.com/containers/image/v5/docker/registries_d.go index 3619c3baef..89d48cc4fe 100644 --- a/vendor/github.com/containers/image/v5/docker/registries_d.go +++ b/vendor/github.com/containers/image/v5/docker/registries_d.go @@ -3,6 +3,7 @@ package docker import ( "errors" "fmt" + "io/fs" "net/url" "os" "path" @@ -129,6 +130,11 @@ func loadAndMergeConfig(dirPath string) (*registryConfiguration, error) { configPath := filepath.Join(dirPath, configName) configBytes, err := os.ReadFile(configPath) if err != nil { + if errors.Is(err, fs.ErrNotExist) { + // file must have been removed between the directory listing + // and the open call, ignore that as it is a expected race + continue + } return nil, err } diff --git a/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go b/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go index 1b161474da..9ac050512a 100644 --- a/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go +++ b/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go @@ -1,6 +1,7 @@ package sysregistriesv2 import ( + "errors" "fmt" "io/fs" "os" @@ -744,6 +745,11 @@ func tryUpdatingCache(ctx *types.SystemContext, wrapper configWrapper) (*parsedC // Enforce v2 format for drop-in-configs. dropIn, err := loadConfigFile(path, true) if err != nil { + if errors.Is(err, fs.ErrNotExist) { + // file must have been removed between the directory listing + // and the open call, ignore that as it is a expected race + continue + } return nil, fmt.Errorf("loading drop-in registries configuration %q: %w", path, err) } config.updateWithConfigurationFrom(dropIn) diff --git a/vendor/github.com/containers/image/v5/pkg/tlsclientconfig/tlsclientconfig.go b/vendor/github.com/containers/image/v5/pkg/tlsclientconfig/tlsclientconfig.go index f6c0576e07..4e0ee57e91 100644 --- a/vendor/github.com/containers/image/v5/pkg/tlsclientconfig/tlsclientconfig.go +++ b/vendor/github.com/containers/image/v5/pkg/tlsclientconfig/tlsclientconfig.go @@ -3,6 +3,7 @@ package tlsclientconfig import ( "crypto/tls" "crypto/x509" + "errors" "fmt" "net" "net/http" @@ -36,12 +37,9 @@ func SetupCertificates(dir string, tlsc *tls.Config) error { logrus.Debugf(" crt: %s", fullPath) data, err := os.ReadFile(fullPath) if err != nil { - if os.IsNotExist(err) { - // Dangling symbolic link? - // Race with someone who deleted the - // file after we read the directory's - // list of contents? - logrus.Warnf("error reading certificate %q: %v", fullPath, err) + if errors.Is(err, os.ErrNotExist) { + // file must have been removed between the directory listing + // and the open call, ignore that as it is a expected race continue } return err diff --git a/vendor/github.com/containers/image/v5/version/version.go b/vendor/github.com/containers/image/v5/version/version.go index 701e87f352..0322755ba1 100644 --- a/vendor/github.com/containers/image/v5/version/version.go +++ b/vendor/github.com/containers/image/v5/version/version.go @@ -8,10 +8,10 @@ const ( // VersionMinor is for functionality in a backwards-compatible manner VersionMinor = 34 // VersionPatch is for backwards-compatible bug fixes - VersionPatch = 1 + VersionPatch = 0 // VersionDev indicates development branch. Releases will be empty string. - VersionDev = "-dev" + VersionDev = "" ) // Version is the specification version that the package types support. diff --git a/vendor/github.com/containers/storage/VERSION b/vendor/github.com/containers/storage/VERSION index a38254646f..b4cf7c0db5 100644 --- a/vendor/github.com/containers/storage/VERSION +++ b/vendor/github.com/containers/storage/VERSION @@ -1 +1 @@ -1.57.0-dev +1.57.1 diff --git a/vendor/github.com/containers/storage/pkg/chunked/compression_linux.go b/vendor/github.com/containers/storage/pkg/chunked/compression_linux.go index 229ce366e4..67cc6cf082 100644 --- a/vendor/github.com/containers/storage/pkg/chunked/compression_linux.go +++ b/vendor/github.com/containers/storage/pkg/chunked/compression_linux.go @@ -23,7 +23,7 @@ import ( const ( // maxTocSize is the maximum size of a blob that we will attempt to process. // It is used to prevent DoS attacks from layers that embed a very large TOC file. - maxTocSize = (1 << 20) * 50 + maxTocSize = (1 << 20) * 150 ) var typesToTar = map[string]byte{ @@ -44,6 +44,8 @@ func typeToTarType(t string) (byte, error) { return r, nil } +// readEstargzChunkedManifest reads the estargz manifest from the seekable stream blobStream. +// It may return an error matching ErrFallbackToOrdinaryLayerDownload / errFallbackCanConvert. func readEstargzChunkedManifest(blobStream ImageSourceSeekable, blobSize int64, tocDigest digest.Digest) ([]byte, int64, error) { // information on the format here https://github.com/containerd/stargz-snapshotter/blob/main/docs/stargz-estargz.md footerSize := int64(51) @@ -54,6 +56,10 @@ func readEstargzChunkedManifest(blobStream ImageSourceSeekable, blobSize int64, footer := make([]byte, footerSize) streamsOrErrors, err := getBlobAt(blobStream, ImageSourceChunk{Offset: uint64(blobSize - footerSize), Length: uint64(footerSize)}) if err != nil { + var badRequestErr ErrBadRequest + if errors.As(err, &badRequestErr) { + err = errFallbackCanConvert{newErrFallbackToOrdinaryLayerDownload(err)} + } return nil, 0, err } @@ -84,11 +90,16 @@ func readEstargzChunkedManifest(blobStream ImageSourceSeekable, blobSize int64, size := int64(blobSize - footerSize - tocOffset) // set a reasonable limit if size > maxTocSize { - return nil, 0, errors.New("manifest too big") + // Not errFallbackCanConvert: we would still use too much memory. + return nil, 0, newErrFallbackToOrdinaryLayerDownload(fmt.Errorf("estargz manifest too big to process in memory (%d bytes)", size)) } streamsOrErrors, err = getBlobAt(blobStream, ImageSourceChunk{Offset: uint64(tocOffset), Length: uint64(size)}) if err != nil { + var badRequestErr ErrBadRequest + if errors.As(err, &badRequestErr) { + err = errFallbackCanConvert{newErrFallbackToOrdinaryLayerDownload(err)} + } return nil, 0, err } @@ -148,6 +159,7 @@ func readEstargzChunkedManifest(blobStream ImageSourceSeekable, blobSize int64, // readZstdChunkedManifest reads the zstd:chunked manifest from the seekable stream blobStream. // Returns (manifest blob, parsed manifest, tar-split blob or nil, manifest offset). +// It may return an error matching ErrFallbackToOrdinaryLayerDownload / errFallbackCanConvert. func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Digest, annotations map[string]string) (_ []byte, _ *minimal.TOC, _ []byte, _ int64, retErr error) { offsetMetadata := annotations[minimal.ManifestInfoKey] if offsetMetadata == "" { @@ -173,10 +185,12 @@ func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Di // set a reasonable limit if manifestChunk.Length > maxTocSize { - return nil, nil, nil, 0, errors.New("manifest too big") + // Not errFallbackCanConvert: we would still use too much memory. + return nil, nil, nil, 0, newErrFallbackToOrdinaryLayerDownload(fmt.Errorf("zstd:chunked manifest too big to process in memory (%d bytes compressed)", manifestChunk.Length)) } if manifestLengthUncompressed > maxTocSize { - return nil, nil, nil, 0, errors.New("manifest too big") + // Not errFallbackCanConvert: we would still use too much memory. + return nil, nil, nil, 0, newErrFallbackToOrdinaryLayerDownload(fmt.Errorf("zstd:chunked manifest too big to process in memory (%d bytes uncompressed)", manifestLengthUncompressed)) } chunks := []ImageSourceChunk{manifestChunk} @@ -186,6 +200,10 @@ func readZstdChunkedManifest(blobStream ImageSourceSeekable, tocDigest digest.Di streamsOrErrors, err := getBlobAt(blobStream, chunks...) if err != nil { + var badRequestErr ErrBadRequest + if errors.As(err, &badRequestErr) { + err = errFallbackCanConvert{newErrFallbackToOrdinaryLayerDownload(err)} + } return nil, nil, nil, 0, err } diff --git a/vendor/github.com/containers/storage/pkg/chunked/storage_linux.go b/vendor/github.com/containers/storage/pkg/chunked/storage_linux.go index cbc96b39cb..8f46798512 100644 --- a/vendor/github.com/containers/storage/pkg/chunked/storage_linux.go +++ b/vendor/github.com/containers/storage/pkg/chunked/storage_linux.go @@ -62,46 +62,53 @@ const ( type compressedFileType int type chunkedDiffer struct { + // Initial parameters, used throughout and never modified + // ========== + pullOptions pullOptions stream ImageSourceSeekable - manifest []byte - toc *minimal.TOC // The parsed contents of manifest, or nil if not yet available - tarSplit []byte - layersCache *layersCache - tocOffset int64 - fileType compressedFileType - - copyBuffer []byte - - gzipReader *pgzip.Reader - zstdReader *zstd.Decoder - rawReader io.Reader - - // tocDigest is the digest of the TOC document when the layer - // is partially pulled. - tocDigest digest.Digest + // blobDigest is the digest of the whole compressed layer. It is used if + // convertToZstdChunked to validate a layer when it is converted since there + // is no TOC referenced by the manifest. + blobDigest digest.Digest + blobSize int64 + // Input format + // ========== + fileType compressedFileType // convertedToZstdChunked is set to true if the layer needs to // be converted to the zstd:chunked format before it can be // handled. convertToZstdChunked bool + // Chunked metadata + // This is usually set in GetDiffer, but if convertToZstdChunked, it is only computed in chunkedDiffer.ApplyDiff + // ========== + // tocDigest is the digest of the TOC document when the layer + // is partially pulled, or "" if not relevant to consumers. + tocDigest digest.Digest + tocOffset int64 + manifest []byte + toc *minimal.TOC // The parsed contents of manifest, or nil if not yet available + tarSplit []byte + uncompressedTarSize int64 // -1 if unknown // skipValidation is set to true if the individual files in // the layer are trusted and should not be validated. skipValidation bool - // blobDigest is the digest of the whole compressed layer. It is used if - // convertToZstdChunked to validate a layer when it is converted since there - // is no TOC referenced by the manifest. - blobDigest digest.Digest - - blobSize int64 - uncompressedTarSize int64 // -1 if unknown - - pullOptions pullOptions - - useFsVerity graphdriver.DifferFsVerity + // Long-term caches + // This is set in GetDiffer, when the caller must not hold any storage locks, and later consumed in .ApplyDiff() + // ========== + layersCache *layersCache + copyBuffer []byte + fsVerityMutex sync.Mutex // protects fsVerityDigests fsVerityDigests map[string]string - fsVerityMutex sync.Mutex + + // Private state of .ApplyDiff + // ========== + gzipReader *pgzip.Reader + zstdReader *zstd.Decoder + rawReader io.Reader + useFsVerity graphdriver.DifferFsVerity } var xattrsToIgnore = map[string]interface{}{ @@ -185,7 +192,7 @@ func (c *chunkedDiffer) convertTarToZstdChunked(destDirectory string, payload *o } // GetDiffer returns a differ than can be used with ApplyDiffWithDiffer. -// If it returns an error that implements IsErrFallbackToOrdinaryLayerDownload, the caller can +// If it returns an error that matches ErrFallbackToOrdinaryLayerDownload, the caller can // retry the operation with a different method. func GetDiffer(ctx context.Context, store storage.Store, blobDigest digest.Digest, blobSize int64, annotations map[string]string, iss ImageSourceSeekable) (graphdriver.Differ, error) { pullOptions := parsePullOptions(store) @@ -208,65 +215,94 @@ func GetDiffer(ctx context.Context, store storage.Store, blobDigest digest.Diges return nil, newErrFallbackToOrdinaryLayerDownload(fmt.Errorf("graph driver %s does not support partial pull", graphDriver.String())) } - differ, canFallback, err := getProperDiffer(store, blobDigest, blobSize, annotations, iss, pullOptions) + differ, err := getProperDiffer(store, blobDigest, blobSize, annotations, iss, pullOptions) if err != nil { - if !canFallback { + var fallbackErr ErrFallbackToOrdinaryLayerDownload + if !errors.As(err, &fallbackErr) { return nil, err } // If convert_images is enabled, always attempt to convert it instead of returning an error or falling back to a different method. - if pullOptions.convertImages { - logrus.Debugf("Created differ to convert blob %q", blobDigest) - return makeConvertFromRawDiffer(store, blobDigest, blobSize, iss, pullOptions) + if !pullOptions.convertImages { + return nil, err } - return nil, newErrFallbackToOrdinaryLayerDownload(err) + var canConvertErr errFallbackCanConvert + if !errors.As(err, &canConvertErr) { + // We are supposed to use makeConvertFromRawDiffer, but that would not work. + // Fail, and make sure the error does _not_ match ErrFallbackToOrdinaryLayerDownload: use only the error text, + // discard all type information. + return nil, fmt.Errorf("neither a partial pull nor convert_images is possible: %s", err.Error()) + } + logrus.Debugf("Created differ to convert blob %q", blobDigest) + return makeConvertFromRawDiffer(store, blobDigest, blobSize, iss, pullOptions) } return differ, nil } +// errFallbackCanConvert is an an error type _accompanying_ ErrFallbackToOrdinaryLayerDownload +// within getProperDiffer, to mark that using makeConvertFromRawDiffer makes sense. +// This is used to distinguish between cases where the environment does not support partial pulls +// (e.g. a registry does not support range requests) and convert_images is still possible, +// from cases where the image content is unacceptable for partial pulls (e.g. exceeds memory limits) +// and convert_images would not help. +type errFallbackCanConvert struct { + err error +} + +func (e errFallbackCanConvert) Error() string { + return e.err.Error() +} + +func (e errFallbackCanConvert) Unwrap() error { + return e.err +} + // getProperDiffer is an implementation detail of GetDiffer. // It returns a “proper” differ (not a convert_images one) if possible. -// On error, the second return value is true if a fallback to an alternative (either the makeConverToRaw differ, or a non-partial pull) -// is permissible. -func getProperDiffer(store storage.Store, blobDigest digest.Digest, blobSize int64, annotations map[string]string, iss ImageSourceSeekable, pullOptions pullOptions) (graphdriver.Differ, bool, error) { +// May return an error matching ErrFallbackToOrdinaryLayerDownload if a fallback to an alternative +// (either makeConvertFromRawDiffer, or a non-partial pull) is permissible. +func getProperDiffer(store storage.Store, blobDigest digest.Digest, blobSize int64, annotations map[string]string, iss ImageSourceSeekable, pullOptions pullOptions) (graphdriver.Differ, error) { zstdChunkedTOCDigestString, hasZstdChunkedTOC := annotations[minimal.ManifestChecksumKey] estargzTOCDigestString, hasEstargzTOC := annotations[estargz.TOCJSONDigestAnnotation] switch { case hasZstdChunkedTOC && hasEstargzTOC: - return nil, false, errors.New("both zstd:chunked and eStargz TOC found") + return nil, errors.New("both zstd:chunked and eStargz TOC found") case hasZstdChunkedTOC: zstdChunkedTOCDigest, err := digest.Parse(zstdChunkedTOCDigestString) if err != nil { - return nil, false, err + return nil, err } - differ, canFallback, err := makeZstdChunkedDiffer(store, blobSize, zstdChunkedTOCDigest, annotations, iss, pullOptions) + differ, err := makeZstdChunkedDiffer(store, blobSize, zstdChunkedTOCDigest, annotations, iss, pullOptions) if err != nil { logrus.Debugf("Could not create zstd:chunked differ for blob %q: %v", blobDigest, err) - return nil, canFallback, err + return nil, err } logrus.Debugf("Created zstd:chunked differ for blob %q", blobDigest) - return differ, false, nil + return differ, nil case hasEstargzTOC: estargzTOCDigest, err := digest.Parse(estargzTOCDigestString) if err != nil { - return nil, false, err + return nil, err } - differ, canFallback, err := makeEstargzChunkedDiffer(store, blobSize, estargzTOCDigest, iss, pullOptions) + differ, err := makeEstargzChunkedDiffer(store, blobSize, estargzTOCDigest, iss, pullOptions) if err != nil { logrus.Debugf("Could not create estargz differ for blob %q: %v", blobDigest, err) - return nil, canFallback, err + return nil, err } logrus.Debugf("Created eStargz differ for blob %q", blobDigest) - return differ, false, nil + return differ, nil default: // no TOC + message := "no TOC found" if !pullOptions.convertImages { - return nil, true, errors.New("no TOC found and convert_images is not configured") + message = "no TOC found and convert_images is not configured" + } + return nil, errFallbackCanConvert{ + newErrFallbackToOrdinaryLayerDownload(errors.New(message)), } - return nil, true, errors.New("no TOC found") } } @@ -277,95 +313,100 @@ func makeConvertFromRawDiffer(store storage.Store, blobDigest digest.Digest, blo } return &chunkedDiffer{ - fsVerityDigests: make(map[string]string), - blobDigest: blobDigest, - blobSize: blobSize, - uncompressedTarSize: -1, // Will be computed later + pullOptions: pullOptions, + stream: iss, + blobDigest: blobDigest, + blobSize: blobSize, + convertToZstdChunked: true, - copyBuffer: makeCopyBuffer(), - layersCache: layersCache, - pullOptions: pullOptions, - stream: iss, + + uncompressedTarSize: -1, // Will be computed later + + layersCache: layersCache, + copyBuffer: makeCopyBuffer(), + fsVerityDigests: make(map[string]string), }, nil } // makeZstdChunkedDiffer sets up a chunkedDiffer for a zstd:chunked layer. -// -// On error, the second return value is true if a fallback to an alternative (either the makeConverToRaw differ, or a non-partial pull) -// is permissible. -func makeZstdChunkedDiffer(store storage.Store, blobSize int64, tocDigest digest.Digest, annotations map[string]string, iss ImageSourceSeekable, pullOptions pullOptions) (*chunkedDiffer, bool, error) { +// It may return an error matching ErrFallbackToOrdinaryLayerDownload / errFallbackCanConvert. +func makeZstdChunkedDiffer(store storage.Store, blobSize int64, tocDigest digest.Digest, annotations map[string]string, iss ImageSourceSeekable, pullOptions pullOptions) (*chunkedDiffer, error) { manifest, toc, tarSplit, tocOffset, err := readZstdChunkedManifest(iss, tocDigest, annotations) - if err != nil { - // If the error is a bad request to the server, then signal to the caller that it can try a different method. - var badRequestErr ErrBadRequest - return nil, errors.As(err, &badRequestErr), fmt.Errorf("read zstd:chunked manifest: %w", err) + if err != nil { // May be ErrFallbackToOrdinaryLayerDownload / errFallbackCanConvert + return nil, fmt.Errorf("read zstd:chunked manifest: %w", err) } var uncompressedTarSize int64 = -1 if tarSplit != nil { uncompressedTarSize, err = tarSizeFromTarSplit(tarSplit) if err != nil { - return nil, false, fmt.Errorf("computing size from tar-split: %w", err) + return nil, fmt.Errorf("computing size from tar-split: %w", err) } } else if !pullOptions.insecureAllowUnpredictableImageContents { // With no tar-split, we can't compute the traditional UncompressedDigest. - return nil, true, fmt.Errorf("zstd:chunked layers without tar-split data don't support partial pulls with guaranteed consistency with non-partial pulls") + return nil, errFallbackCanConvert{ + newErrFallbackToOrdinaryLayerDownload(fmt.Errorf("zstd:chunked layers without tar-split data don't support partial pulls with guaranteed consistency with non-partial pulls")), + } } layersCache, err := getLayersCache(store) if err != nil { - return nil, false, err + return nil, err } return &chunkedDiffer{ - fsVerityDigests: make(map[string]string), - blobSize: blobSize, - uncompressedTarSize: uncompressedTarSize, + pullOptions: pullOptions, + stream: iss, + blobSize: blobSize, + + fileType: fileTypeZstdChunked, + tocDigest: tocDigest, - copyBuffer: makeCopyBuffer(), - fileType: fileTypeZstdChunked, - layersCache: layersCache, + tocOffset: tocOffset, manifest: manifest, toc: toc, - pullOptions: pullOptions, - stream: iss, tarSplit: tarSplit, - tocOffset: tocOffset, - }, false, nil + uncompressedTarSize: uncompressedTarSize, + + layersCache: layersCache, + copyBuffer: makeCopyBuffer(), + fsVerityDigests: make(map[string]string), + }, nil } -// makeZstdChunkedDiffer sets up a chunkedDiffer for an estargz layer. -// -// On error, the second return value is true if a fallback to an alternative (either the makeConverToRaw differ, or a non-partial pull) -// is permissible. -func makeEstargzChunkedDiffer(store storage.Store, blobSize int64, tocDigest digest.Digest, iss ImageSourceSeekable, pullOptions pullOptions) (*chunkedDiffer, bool, error) { +// makeEstargzChunkedDiffer sets up a chunkedDiffer for an estargz layer. +// It may return an error matching ErrFallbackToOrdinaryLayerDownload / errFallbackCanConvert. +func makeEstargzChunkedDiffer(store storage.Store, blobSize int64, tocDigest digest.Digest, iss ImageSourceSeekable, pullOptions pullOptions) (*chunkedDiffer, error) { if !pullOptions.insecureAllowUnpredictableImageContents { // With no tar-split, we can't compute the traditional UncompressedDigest. - return nil, true, fmt.Errorf("estargz layers don't support partial pulls with guaranteed consistency with non-partial pulls") + return nil, errFallbackCanConvert{ + newErrFallbackToOrdinaryLayerDownload(fmt.Errorf("estargz layers don't support partial pulls with guaranteed consistency with non-partial pulls")), + } } manifest, tocOffset, err := readEstargzChunkedManifest(iss, blobSize, tocDigest) - if err != nil { - // If the error is a bad request to the server, then signal to the caller that it can try a different method. - var badRequestErr ErrBadRequest - return nil, errors.As(err, &badRequestErr), fmt.Errorf("read zstd:chunked manifest: %w", err) + if err != nil { // May be ErrFallbackToOrdinaryLayerDownload / errFallbackCanConvert + return nil, fmt.Errorf("read zstd:chunked manifest: %w", err) } layersCache, err := getLayersCache(store) if err != nil { - return nil, false, err + return nil, err } return &chunkedDiffer{ - fsVerityDigests: make(map[string]string), - blobSize: blobSize, - uncompressedTarSize: -1, // We would have to read and decompress the whole layer + pullOptions: pullOptions, + stream: iss, + blobSize: blobSize, + + fileType: fileTypeEstargz, + tocDigest: tocDigest, - copyBuffer: makeCopyBuffer(), - fileType: fileTypeEstargz, - layersCache: layersCache, - manifest: manifest, - pullOptions: pullOptions, - stream: iss, tocOffset: tocOffset, - }, false, nil + manifest: manifest, + uncompressedTarSize: -1, // We would have to read and decompress the whole layer + + layersCache: layersCache, + copyBuffer: makeCopyBuffer(), + fsVerityDigests: make(map[string]string), + }, nil } func makeCopyBuffer() []byte { diff --git a/vendor/github.com/containers/storage/pkg/chunked/storage_unsupported.go b/vendor/github.com/containers/storage/pkg/chunked/storage_unsupported.go index 7be0adeb8a..fe3d36c76b 100644 --- a/vendor/github.com/containers/storage/pkg/chunked/storage_unsupported.go +++ b/vendor/github.com/containers/storage/pkg/chunked/storage_unsupported.go @@ -13,5 +13,5 @@ import ( // GetDiffer returns a differ than can be used with ApplyDiffWithDiffer. func GetDiffer(ctx context.Context, store storage.Store, blobDigest digest.Digest, blobSize int64, annotations map[string]string, iss ImageSourceSeekable) (graphdriver.Differ, error) { - return nil, errors.New("format not supported on this system") + return nil, newErrFallbackToOrdinaryLayerDownload(errors.New("format not supported on this system")) } diff --git a/vendor/modules.txt b/vendor/modules.txt index f0909a815f..359eae7e08 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -179,7 +179,7 @@ github.com/containers/buildah/pkg/sshagent github.com/containers/buildah/pkg/util github.com/containers/buildah/pkg/volumes github.com/containers/buildah/util -# github.com/containers/common v0.61.1-0.20250124131345-fa339b6b6eda +# github.com/containers/common v0.62.0 ## explicit; go 1.22.8 github.com/containers/common/internal github.com/containers/common/internal/attributedstring @@ -252,7 +252,7 @@ github.com/containers/conmon/runner/config # github.com/containers/gvisor-tap-vsock v0.8.2 ## explicit; go 1.22.0 github.com/containers/gvisor-tap-vsock/pkg/types -# github.com/containers/image/v5 v5.33.2-0.20250122233652-b5c6aff95ca7 +# github.com/containers/image/v5 v5.34.0 ## explicit; go 1.22.8 github.com/containers/image/v5/copy github.com/containers/image/v5/directory @@ -364,7 +364,7 @@ github.com/containers/psgo/internal/dev github.com/containers/psgo/internal/host github.com/containers/psgo/internal/proc github.com/containers/psgo/internal/process -# github.com/containers/storage v1.56.2-0.20250123125217-80d3c0e77d29 +# github.com/containers/storage v1.57.1 ## explicit; go 1.22.0 github.com/containers/storage github.com/containers/storage/drivers