mirror of https://github.com/containers/image.git
329 lines
17 KiB
Go
329 lines
17 KiB
Go
package manifest
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/containers/image/v5/pkg/compression"
|
|
compressiontypes "github.com/containers/image/v5/pkg/compression/types"
|
|
"github.com/containers/image/v5/types"
|
|
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// Test that parser() rejects all of the provided manifest fixtures.
|
|
// Intended to help test manifest parsers' detection of schema mismatches.
|
|
func testManifestFixturesAreRejected(t *testing.T, parser func([]byte) error, fixtures []string) {
|
|
for _, fixture := range fixtures {
|
|
manifest, err := os.ReadFile(filepath.Join("fixtures", fixture))
|
|
require.NoError(t, err, fixture)
|
|
err = parser(manifest)
|
|
assert.Error(t, err, fixture)
|
|
}
|
|
}
|
|
|
|
// Test that parser() rejects validManifest with an added top-level field with any of the provided field names.
|
|
// Intended to help test callers of validateUnambiguousManifestFormat.
|
|
func testValidManifestWithExtraFieldsIsRejected(t *testing.T, parser func([]byte) error,
|
|
validManifest []byte, fields []string) {
|
|
for _, field := range fields {
|
|
// end (the final '}') is not always at len(validManifest)-1 because the manifest can end with
|
|
// white space.
|
|
end := bytes.LastIndexByte(validManifest, '}')
|
|
require.NotEqual(t, end, -1)
|
|
updatedManifest := []byte(string(validManifest[:end]) +
|
|
fmt.Sprintf(`,"%s":[]}`, field))
|
|
err := parser(updatedManifest)
|
|
// Make sure it is the error from validateUnambiguousManifestFormat, not something that
|
|
// went wrong with creating updatedManifest.
|
|
assert.ErrorContains(t, err, "rejecting ambiguous manifest", field)
|
|
}
|
|
}
|
|
|
|
func TestLayerInfosToStrings(t *testing.T) {
|
|
strings := layerInfosToStrings([]LayerInfo{})
|
|
assert.Equal(t, []string{}, strings)
|
|
|
|
strings = layerInfosToStrings([]LayerInfo{
|
|
{
|
|
BlobInfo: types.BlobInfo{
|
|
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
|
Digest: "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4",
|
|
Size: 32,
|
|
},
|
|
EmptyLayer: true,
|
|
},
|
|
{
|
|
BlobInfo: types.BlobInfo{
|
|
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
|
Digest: "sha256:bbd6b22eb11afce63cc76f6bc41042d99f10d6024c96b655dafba930b8d25909",
|
|
Size: 8841833,
|
|
},
|
|
EmptyLayer: false,
|
|
},
|
|
{
|
|
BlobInfo: types.BlobInfo{
|
|
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
|
Digest: "sha256:960e52ecf8200cbd84e70eb2ad8678f4367e50d14357021872c10fa3fc5935fa",
|
|
Size: 291,
|
|
},
|
|
EmptyLayer: false,
|
|
},
|
|
{
|
|
BlobInfo: types.BlobInfo{
|
|
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
|
Digest: "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4",
|
|
Size: 32,
|
|
},
|
|
EmptyLayer: true,
|
|
},
|
|
})
|
|
assert.Equal(t, []string{
|
|
"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4",
|
|
"sha256:bbd6b22eb11afce63cc76f6bc41042d99f10d6024c96b655dafba930b8d25909",
|
|
"sha256:960e52ecf8200cbd84e70eb2ad8678f4367e50d14357021872c10fa3fc5935fa",
|
|
"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4",
|
|
}, strings)
|
|
}
|
|
|
|
func TestCompressionVariantMIMEType(t *testing.T) {
|
|
sets := []compressionMIMETypeSet{
|
|
{mtsUncompressed: "AU", compressiontypes.GzipAlgorithmName: "AG" /* No zstd variant */},
|
|
{mtsUncompressed: "BU", compressiontypes.GzipAlgorithmName: "BG", compressiontypes.ZstdAlgorithmName: mtsUnsupportedMIMEType},
|
|
{ /* No uncompressed variant */ compressiontypes.GzipAlgorithmName: "CG", compressiontypes.ZstdAlgorithmName: "CZ"},
|
|
{mtsUncompressed: "", compressiontypes.GzipAlgorithmName: "DG"},
|
|
}
|
|
|
|
for _, c := range []struct {
|
|
input string
|
|
algo *compressiontypes.Algorithm
|
|
expected string
|
|
}{
|
|
{"AU", nil, "AU"}, {"AU", &compression.Gzip, "AG"}, {"AU", &compression.Zstd, ""},
|
|
{"AG", nil, "AU"}, {"AG", &compression.Gzip, "AG"}, {"AG", &compression.Zstd, ""},
|
|
{"BU", &compression.Zstd, ""},
|
|
{"BG", &compression.Zstd, ""},
|
|
{"CG", nil, ""}, {"CG", &compression.Zstd, "CZ"},
|
|
{"CZ", nil, ""}, {"CZ", &compression.Gzip, "CG"},
|
|
{"DG", nil, ""},
|
|
{"unknown", nil, ""}, {"unknown", &compression.Gzip, ""},
|
|
{"", nil, ""}, {"", &compression.Gzip, ""},
|
|
} {
|
|
res, err := compressionVariantMIMEType(sets, c.input, c.algo)
|
|
if c.expected == "" {
|
|
assert.Error(t, err, c.input)
|
|
} else {
|
|
require.NoError(t, err, c.input)
|
|
assert.Equal(t, c.expected, res, c.input)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUpdatedMIMEType(t *testing.T) {
|
|
// all known types, PreserveOriginal
|
|
preserve := []struct {
|
|
compression []compressionMIMETypeSet
|
|
mimeType string
|
|
}{
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema1MediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema1SignedMediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2MediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2ConfigMediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2LayerMediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2SchemaLayerMediaTypeUncompressed},
|
|
{schema2CompressionMIMETypeSets, DockerV2ListMediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2ForeignLayerMediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2ForeignLayerMediaTypeGzip},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeDescriptor},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeLayoutHeader},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageManifest},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageIndex},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayer},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerGzip},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerZstd},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerNonDistributable}, //nolint:staticcheck // NonDistributable layers are deprecated, but we want to continue to support manipulating pre-existing images.
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerNonDistributableGzip}, //nolint:staticcheck // NonDistributable layers are deprecated, but we want to continue to support manipulating pre-existing images.
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerNonDistributableZstd}, //nolint:staticcheck // NonDistributable layers are deprecated, but we want to continue to support manipulating pre-existing images.
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageConfig},
|
|
}
|
|
for i, c := range preserve {
|
|
update := types.BlobInfo{
|
|
MediaType: c.mimeType,
|
|
CompressionOperation: types.PreserveOriginal,
|
|
}
|
|
updatedType, err := updatedMIMEType(c.compression, c.mimeType, update)
|
|
require.NoErrorf(t, err, "%d: updatedMIMEType(%q, %+v) failed unexpectedly", i, c.mimeType, update)
|
|
assert.Equalf(t, c.mimeType, updatedType, "%d: updatedMIMEType(%q, %+v)", i, c.mimeType, update)
|
|
}
|
|
|
|
// known types where Decompress is expected to succeed
|
|
decompressSuccess := []struct {
|
|
compression []compressionMIMETypeSet
|
|
mimeType string
|
|
updatedType string
|
|
}{
|
|
{schema2CompressionMIMETypeSets, DockerV2SchemaLayerMediaTypeUncompressed, DockerV2SchemaLayerMediaTypeUncompressed},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2ForeignLayerMediaTypeGzip, DockerV2Schema2ForeignLayerMediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2LayerMediaType, DockerV2SchemaLayerMediaTypeUncompressed},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2ForeignLayerMediaType, DockerV2Schema2ForeignLayerMediaType},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayer, imgspecv1.MediaTypeImageLayer},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerGzip, imgspecv1.MediaTypeImageLayer},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerZstd, imgspecv1.MediaTypeImageLayer},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerNonDistributable, imgspecv1.MediaTypeImageLayerNonDistributable}, //nolint:staticcheck // NonDistributable layers are deprecated, but we want to continue to support manipulating pre-existing images.
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerNonDistributableGzip, imgspecv1.MediaTypeImageLayerNonDistributable}, //nolint:staticcheck // NonDistributable layers are deprecated, but we want to continue to support manipulating pre-existing images.
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerNonDistributableZstd, imgspecv1.MediaTypeImageLayerNonDistributable}, //nolint:staticcheck // NonDistributable layers are deprecated, but we want to continue to support manipulating pre-existing images.
|
|
}
|
|
for i, c := range decompressSuccess {
|
|
update := types.BlobInfo{
|
|
MediaType: c.mimeType,
|
|
CompressionOperation: types.Decompress,
|
|
}
|
|
updatedType, err := updatedMIMEType(c.compression, c.mimeType, update)
|
|
require.NoErrorf(t, err, "%d: updatedMIMEType(%q, %+v) failed unexpectedly", i, c.mimeType, update)
|
|
assert.Equalf(t, c.updatedType, updatedType, "%d: updatedMIMEType(%q, %+v)", i, c.mimeType, update)
|
|
}
|
|
|
|
// known types where Decompress is expected to fail
|
|
decompressFailure := []struct {
|
|
compression []compressionMIMETypeSet
|
|
mimeType string
|
|
}{
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema1MediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema1SignedMediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2MediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2ConfigMediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2ListMediaType},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeDescriptor},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeLayoutHeader},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageManifest},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageIndex},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageConfig},
|
|
}
|
|
for i, c := range decompressFailure {
|
|
update := types.BlobInfo{
|
|
MediaType: c.mimeType,
|
|
CompressionOperation: types.Decompress,
|
|
}
|
|
_, err := updatedMIMEType(c.compression, c.mimeType, update)
|
|
require.Errorf(t, err, "%d: updatedMIMEType(%q, %+v) should have failed", i, c.mimeType, update)
|
|
}
|
|
|
|
require.Equalf(t, len(preserve), len(decompressSuccess)+len(decompressFailure), "missing some decompression tests")
|
|
|
|
// all known types where Compress with gzip should succeed
|
|
compressGzipSuccess := []struct {
|
|
compression []compressionMIMETypeSet
|
|
mimeType string
|
|
updatedType string
|
|
}{
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2LayerMediaType, DockerV2Schema2LayerMediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2SchemaLayerMediaTypeUncompressed, DockerV2Schema2LayerMediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2ForeignLayerMediaType, DockerV2Schema2ForeignLayerMediaTypeGzip},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2ForeignLayerMediaTypeGzip, DockerV2Schema2ForeignLayerMediaTypeGzip},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayer, imgspecv1.MediaTypeImageLayerGzip},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerGzip, imgspecv1.MediaTypeImageLayerGzip},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerZstd, imgspecv1.MediaTypeImageLayerGzip},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerNonDistributable, imgspecv1.MediaTypeImageLayerNonDistributableGzip}, //nolint:staticcheck // NonDistributable layers are deprecated, but we want to continue to support manipulating pre-existing images.
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerNonDistributableGzip, imgspecv1.MediaTypeImageLayerNonDistributableGzip}, //nolint:staticcheck // NonDistributable layers are deprecated, but we want to continue to support manipulating pre-existing images.
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerNonDistributableZstd, imgspecv1.MediaTypeImageLayerNonDistributableGzip}, //nolint:staticcheck // NonDistributable layers are deprecated, but we want to continue to support manipulating pre-existing images.
|
|
}
|
|
for i, c := range compressGzipSuccess {
|
|
update := types.BlobInfo{
|
|
MediaType: c.mimeType,
|
|
CompressionOperation: types.Compress,
|
|
CompressionAlgorithm: &compression.Gzip,
|
|
}
|
|
updatedType, err := updatedMIMEType(c.compression, c.mimeType, update)
|
|
require.NoErrorf(t, err, "%d: updatedMIMEType(%q, %+v) failed unexpectedly", i, c.mimeType, update)
|
|
assert.Equalf(t, c.updatedType, updatedType, "%d: updatedMIMEType(%q, %+v)", i, c.mimeType, update)
|
|
}
|
|
|
|
// known types where Compress with gzip is expected to fail
|
|
compressGzipFailure := []struct {
|
|
compression []compressionMIMETypeSet
|
|
mimeType string
|
|
}{
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema1MediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema1SignedMediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2MediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2ConfigMediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2ListMediaType},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeDescriptor},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeLayoutHeader},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageManifest},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageIndex},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageConfig},
|
|
}
|
|
for i, c := range compressGzipFailure {
|
|
update := types.BlobInfo{
|
|
MediaType: c.mimeType,
|
|
CompressionOperation: types.Compress,
|
|
CompressionAlgorithm: &compression.Gzip,
|
|
}
|
|
_, err := updatedMIMEType(c.compression, c.mimeType, update)
|
|
require.Errorf(t, err, "%d: updatedMIMEType(%q, %+v) should have failed", i, c.mimeType, update)
|
|
}
|
|
|
|
require.Equalf(t, len(preserve), len(compressGzipSuccess)+len(compressGzipFailure), "missing some gzip compression tests")
|
|
|
|
// known types where Compress with zstd is expected to succeed
|
|
compressZstdSuccess := []struct {
|
|
compression []compressionMIMETypeSet
|
|
mimeType string
|
|
updatedType string
|
|
}{
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayer, imgspecv1.MediaTypeImageLayerZstd},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerGzip, imgspecv1.MediaTypeImageLayerZstd},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerZstd, imgspecv1.MediaTypeImageLayerZstd},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerNonDistributable, imgspecv1.MediaTypeImageLayerNonDistributableZstd}, //nolint:staticcheck // NonDistributable layers are deprecated, but we want to continue to support manipulating pre-existing images.
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerNonDistributableGzip, imgspecv1.MediaTypeImageLayerNonDistributableZstd}, //nolint:staticcheck // NonDistributable layers are deprecated, but we want to continue to support manipulating pre-existing images.
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageLayerNonDistributableZstd, imgspecv1.MediaTypeImageLayerNonDistributableZstd}, //nolint:staticcheck // NonDistributable layers are deprecated, but we want to continue to support manipulating pre-existing images.
|
|
}
|
|
for i, c := range compressZstdSuccess {
|
|
update := types.BlobInfo{
|
|
MediaType: c.mimeType,
|
|
CompressionOperation: types.Compress,
|
|
CompressionAlgorithm: &compression.Zstd,
|
|
}
|
|
updatedType, err := updatedMIMEType(c.compression, c.mimeType, update)
|
|
require.NoErrorf(t, err, "%d: updatedMIMEType(%q, %+v) failed unexpectedly", i, c.mimeType, update)
|
|
assert.Equalf(t, c.updatedType, updatedType, "%d: updatedMIMEType(%q, %+v)", i, c.mimeType, update)
|
|
}
|
|
|
|
// known types where Compress with zstd is expected to fail
|
|
compressZstdFailure := []struct {
|
|
compression []compressionMIMETypeSet
|
|
mimeType string
|
|
}{
|
|
{schema2CompressionMIMETypeSets, DockerV2SchemaLayerMediaTypeUncompressed},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2LayerMediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2ForeignLayerMediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2ForeignLayerMediaTypeGzip},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema1MediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema1SignedMediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2MediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2Schema2ConfigMediaType},
|
|
{schema2CompressionMIMETypeSets, DockerV2ListMediaType},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeDescriptor},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeLayoutHeader},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageManifest},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageIndex},
|
|
{oci1CompressionMIMETypeSets, imgspecv1.MediaTypeImageConfig},
|
|
}
|
|
for i, c := range compressZstdFailure {
|
|
update := types.BlobInfo{
|
|
MediaType: c.mimeType,
|
|
CompressionOperation: types.Compress,
|
|
CompressionAlgorithm: &compression.Zstd,
|
|
}
|
|
_, err := updatedMIMEType(c.compression, c.mimeType, update)
|
|
require.Errorf(t, err, "%d: updatedMIMEType(%q, %+v) should have failed", i, c.mimeType, update)
|
|
}
|
|
|
|
require.Equalf(t, len(preserve), len(compressZstdSuccess)+len(compressZstdFailure), "missing some zstd compression tests")
|
|
}
|