From b3fa67bdc072caac3fbd638b912e6b3df6bf08f6 Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 23 Dec 2025 11:48:13 +0800 Subject: [PATCH 1/2] feat: support nydus image format Signed-off-by: fatelei --- go.work.sum | 4 +++- image/internal/image/oci.go | 2 ++ image/internal/manifest/manifest.go | 4 ++++ image/manifest/oci.go | 19 +++++++++++++++---- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/go.work.sum b/go.work.sum index 86e9f2da2e..aa9bd66874 100644 --- a/go.work.sum +++ b/go.work.sum @@ -44,7 +44,7 @@ github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4p github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/chainguard-dev/clog v1.7.0/go.mod h1:4+WFhRMsGH79etYXY3plYdp+tCz/KCkU8fAr0HoaPvs= -github.com/checkpoint-restore/go-criu/v8 v8.0.0/go.mod h1:GtCbuv5kNUGKXLEhHwURnHwhHf7VApBd4k0+5cqsRmA= +github.com/checkpoint-restore/go-criu/v7 v7.2.0/go.mod h1:u0LCWLg0w4yqqu14aXhiB4YD3a1qd8EcCEg7vda5dwo= github.com/cilium/ebpf v0.17.3/go.mod h1:G5EDHij8yiLzaqn0WjyfJHvRa+3aDlReIaLVRMvOyJk= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= @@ -153,6 +153,7 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= goa.design/goa/v3 v3.22.6/go.mod h1:rhssEXxox3+sKnYp18hPNFCz65I4hLWHEtJKewoNJWk= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= +golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= google.golang.org/api v0.254.0/go.mod h1:5BkSURm3D9kAqjGvBNgf0EcbX6Rnrf6UArKkwBzAyqQ= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= @@ -160,3 +161,4 @@ sigs.k8s.io/knftables v0.0.18/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojG sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/release-utils v0.12.2/go.mod h1:Ab9Lb/FpGUw4lUXj1QYbUcF2TRzll+GS7Md54W1G7sA= tags.cncf.io/container-device-interface/specs-go v1.0.0/go.mod h1:u86hoFWqnh3hWz3esofRFKbI261bUlvUfLKGrDhJkgQ= +tags.cncf.io/container-device-interface/specs-go v1.1.0/go.mod h1:u86hoFWqnh3hWz3esofRFKbI261bUlvUfLKGrDhJkgQ= diff --git a/image/internal/image/oci.go b/image/internal/image/oci.go index 8ddb2875e0..1074a94656 100644 --- a/image/internal/image/oci.go +++ b/image/internal/image/oci.go @@ -290,6 +290,8 @@ func (m *manifestOCI1) convertToManifestSchema2(_ context.Context, options *type case ociencspec.MediaTypeLayerEnc, ociencspec.MediaTypeLayerGzipEnc, ociencspec.MediaTypeLayerZstdEnc, ociencspec.MediaTypeLayerNonDistributableEnc, ociencspec.MediaTypeLayerNonDistributableGzipEnc, ociencspec.MediaTypeLayerNonDistributableZstdEnc: return nil, fmt.Errorf("during manifest conversion: encrypted layers (%q) are not supported in docker images", layers[idx].MediaType) + case internalManifest.NydusBootstrapLayerMediaType, internalManifest.NydusBlobLayerMediaType: + layers[idx].MediaType = manifest.DockerV2Schema2ForeignLayerMediaType default: return nil, fmt.Errorf("Unknown media type during manifest conversion: %q", layers[idx].MediaType) } diff --git a/image/internal/manifest/manifest.go b/image/internal/manifest/manifest.go index 46e1e4df17..e7d6886cf2 100644 --- a/image/internal/manifest/manifest.go +++ b/image/internal/manifest/manifest.go @@ -34,6 +34,10 @@ const ( DockerV2Schema2ForeignLayerMediaType = "application/vnd.docker.image.rootfs.foreign.diff.tar" // DockerV2Schema2ForeignLayerMediaType is the MIME type used for gzipped schema 2 foreign layers. DockerV2Schema2ForeignLayerMediaTypeGzip = "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip" + // NydusBootstrapLayerMediaType is the MIME type used for Nydus bootstrap layers. + NydusBootstrapLayerMediaType = "application/vnd.containers.image.nydus.bootstrap.v1+json" + // NydusBlobLayerMediaType is the MIME type used for Nydus data blob layers. + NydusBlobLayerMediaType = "application/vnd.containers.image.nydus.blob.v1" ) // GuessMIMEType guesses MIME type of a manifest and returns it _if it is recognized_, or "" if unknown or unrecognized. diff --git a/image/manifest/oci.go b/image/manifest/oci.go index 286d58c423..7a5d6b8aa9 100644 --- a/image/manifest/oci.go +++ b/image/manifest/oci.go @@ -47,7 +47,8 @@ func SupportedOCI1MediaType(m string) error { imgspecv1.MediaTypeImageLayerNonDistributable, imgspecv1.MediaTypeImageLayerNonDistributableGzip, imgspecv1.MediaTypeImageLayerNonDistributableZstd, //nolint:staticcheck // NonDistributable layers are deprecated, but we want to continue to support manipulating pre-existing images. imgspecv1.MediaTypeImageManifest, imgspecv1.MediaTypeLayoutHeader, - ociencspec.MediaTypeLayerEnc, ociencspec.MediaTypeLayerGzipEnc: + ociencspec.MediaTypeLayerEnc, ociencspec.MediaTypeLayerGzipEnc, + manifest.NydusBootstrapLayerMediaType, manifest.NydusBlobLayerMediaType: return nil default: return fmt.Errorf("unsupported OCIv1 media type: %q", m) @@ -142,10 +143,20 @@ func (m *OCI1) UpdateLayerInfos(layerInfos []types.BlobInfo) error { } mimeType = decMimeType } - mimeType, err := updatedMIMEType(oci1CompressionMIMETypeSets, mimeType, info) - if err != nil { - return fmt.Errorf("preparing updated manifest, layer %q: %w", info.Digest, err) + // Nydus layer types don't support compression/decompression operations + // They should only be preserved as-is + if mimeType == manifest.NydusBootstrapLayerMediaType || mimeType == manifest.NydusBlobLayerMediaType { + if info.CompressionOperation != types.PreserveOriginal { + return fmt.Errorf("preparing updated manifest, layer %q: Nydus layer types (%q) do not support compression or decompression operations", info.Digest, mimeType) + } + } else { + var err error + mimeType, err = updatedMIMEType(oci1CompressionMIMETypeSets, mimeType, info) + if err != nil { + return fmt.Errorf("preparing updated manifest, layer %q: %w", info.Digest, err) + } } + if info.CryptoOperation == types.Encrypt { encMediaType, err := getEncryptedMediaType(mimeType) if err != nil { From 3d8b55abd6e53a0a1fd60a1e2a62f41fb74ef1e9 Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 10 Feb 2026 22:21:09 +0800 Subject: [PATCH 2/2] chore: resolve comment issue Signed-off-by: fatelei --- go.work.sum | 4 +--- image/internal/image/oci.go | 2 +- image/internal/image/oci_test.go | 4 +++- image/manifest/oci.go | 17 +++++------------ image/manifest/oci_test.go | 7 ++++++- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/go.work.sum b/go.work.sum index aa9bd66874..86e9f2da2e 100644 --- a/go.work.sum +++ b/go.work.sum @@ -44,7 +44,7 @@ github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4p github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/chainguard-dev/clog v1.7.0/go.mod h1:4+WFhRMsGH79etYXY3plYdp+tCz/KCkU8fAr0HoaPvs= -github.com/checkpoint-restore/go-criu/v7 v7.2.0/go.mod h1:u0LCWLg0w4yqqu14aXhiB4YD3a1qd8EcCEg7vda5dwo= +github.com/checkpoint-restore/go-criu/v8 v8.0.0/go.mod h1:GtCbuv5kNUGKXLEhHwURnHwhHf7VApBd4k0+5cqsRmA= github.com/cilium/ebpf v0.17.3/go.mod h1:G5EDHij8yiLzaqn0WjyfJHvRa+3aDlReIaLVRMvOyJk= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= @@ -153,7 +153,6 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= goa.design/goa/v3 v3.22.6/go.mod h1:rhssEXxox3+sKnYp18hPNFCz65I4hLWHEtJKewoNJWk= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= google.golang.org/api v0.254.0/go.mod h1:5BkSURm3D9kAqjGvBNgf0EcbX6Rnrf6UArKkwBzAyqQ= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= @@ -161,4 +160,3 @@ sigs.k8s.io/knftables v0.0.18/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojG sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/release-utils v0.12.2/go.mod h1:Ab9Lb/FpGUw4lUXj1QYbUcF2TRzll+GS7Md54W1G7sA= tags.cncf.io/container-device-interface/specs-go v1.0.0/go.mod h1:u86hoFWqnh3hWz3esofRFKbI261bUlvUfLKGrDhJkgQ= -tags.cncf.io/container-device-interface/specs-go v1.1.0/go.mod h1:u86hoFWqnh3hWz3esofRFKbI261bUlvUfLKGrDhJkgQ= diff --git a/image/internal/image/oci.go b/image/internal/image/oci.go index 1074a94656..b95ccdc836 100644 --- a/image/internal/image/oci.go +++ b/image/internal/image/oci.go @@ -291,7 +291,7 @@ func (m *manifestOCI1) convertToManifestSchema2(_ context.Context, options *type ociencspec.MediaTypeLayerNonDistributableEnc, ociencspec.MediaTypeLayerNonDistributableGzipEnc, ociencspec.MediaTypeLayerNonDistributableZstdEnc: return nil, fmt.Errorf("during manifest conversion: encrypted layers (%q) are not supported in docker images", layers[idx].MediaType) case internalManifest.NydusBootstrapLayerMediaType, internalManifest.NydusBlobLayerMediaType: - layers[idx].MediaType = manifest.DockerV2Schema2ForeignLayerMediaType + return nil, fmt.Errorf("during manifest conversion: Nydus layers (%q) are not supported in docker images", layers[idx].MediaType) default: return nil, fmt.Errorf("Unknown media type during manifest conversion: %q", layers[idx].MediaType) } diff --git a/image/internal/image/oci_test.go b/image/internal/image/oci_test.go index af6ee7349f..b943060fda 100644 --- a/image/internal/image/oci_test.go +++ b/image/internal/image/oci_test.go @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.podman.io/image/v5/docker/reference" + internalManifest "go.podman.io/image/v5/internal/manifest" "go.podman.io/image/v5/internal/testing/mocks" "go.podman.io/image/v5/manifest" "go.podman.io/image/v5/pkg/compression" @@ -884,8 +885,9 @@ func TestManifestOCI1CanChangeLayerCompression(t *testing.T) { manifestOCI1FromComponentsLikeFixture(nil), } { assert.True(t, m.CanChangeLayerCompression(imgspecv1.MediaTypeImageLayerGzip)) - // Some projects like to use squashfs and other unspecified formats for layers; don’t touch those. assert.False(t, m.CanChangeLayerCompression("a completely unknown and quite possibly invalid MIME type")) + assert.False(t, m.CanChangeLayerCompression(internalManifest.NydusBootstrapLayerMediaType)) + assert.False(t, m.CanChangeLayerCompression(internalManifest.NydusBlobLayerMediaType)) } artifact := manifestOCI1FromFixture(t, mocks.ForbiddenImageSource{}, "oci1-artifact.json") diff --git a/image/manifest/oci.go b/image/manifest/oci.go index 7a5d6b8aa9..395b33d565 100644 --- a/image/manifest/oci.go +++ b/image/manifest/oci.go @@ -143,18 +143,11 @@ func (m *OCI1) UpdateLayerInfos(layerInfos []types.BlobInfo) error { } mimeType = decMimeType } - // Nydus layer types don't support compression/decompression operations - // They should only be preserved as-is - if mimeType == manifest.NydusBootstrapLayerMediaType || mimeType == manifest.NydusBlobLayerMediaType { - if info.CompressionOperation != types.PreserveOriginal { - return fmt.Errorf("preparing updated manifest, layer %q: Nydus layer types (%q) do not support compression or decompression operations", info.Digest, mimeType) - } - } else { - var err error - mimeType, err = updatedMIMEType(oci1CompressionMIMETypeSets, mimeType, info) - if err != nil { - return fmt.Errorf("preparing updated manifest, layer %q: %w", info.Digest, err) - } + + var err error + mimeType, err = updatedMIMEType(oci1CompressionMIMETypeSets, mimeType, info) + if err != nil { + return fmt.Errorf("preparing updated manifest, layer %q: %w", info.Digest, err) } if info.CryptoOperation == types.Encrypt { diff --git a/image/manifest/oci_test.go b/image/manifest/oci_test.go index 50d2b463e0..f5811e3ab0 100644 --- a/image/manifest/oci_test.go +++ b/image/manifest/oci_test.go @@ -9,6 +9,7 @@ import ( imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + internalManifest "go.podman.io/image/v5/internal/manifest" "go.podman.io/image/v5/pkg/compression" "go.podman.io/image/v5/types" ) @@ -405,9 +406,13 @@ func TestOCI1CanChangeLayerCompression(t *testing.T) { m := manifestOCI1FromFixture(t, "ociv1.manifest.json") assert.True(t, m.CanChangeLayerCompression(imgspecv1.MediaTypeImageLayerGzip)) - // Some projects like to use squashfs and other unspecified formats for layers; don’t touch those. + // Some projects like to use squashfs and other unspecified formats for layers; don't touch those. assert.False(t, m.CanChangeLayerCompression("a completely unknown and quite possibly invalid MIME type")) artifact := manifestOCI1FromFixture(t, "ociv1.artifact.json") assert.False(t, artifact.CanChangeLayerCompression(imgspecv1.MediaTypeImageLayerGzip)) + + // Nydus layer types don't support compression changes + assert.False(t, m.CanChangeLayerCompression(internalManifest.NydusBootstrapLayerMediaType)) + assert.False(t, m.CanChangeLayerCompression(internalManifest.NydusBlobLayerMediaType)) }