mirror of https://github.com/containers/image.git
211 lines
6.7 KiB
Go
211 lines
6.7 KiB
Go
package directory
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"errors"
|
||
"io"
|
||
"os"
|
||
"testing"
|
||
|
||
"github.com/containers/image/v5/internal/private"
|
||
"github.com/containers/image/v5/manifest"
|
||
"github.com/containers/image/v5/pkg/blobinfocache/memory"
|
||
"github.com/containers/image/v5/types"
|
||
"github.com/opencontainers/go-digest"
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
var _ private.ImageSource = (*dirImageSource)(nil)
|
||
var _ private.ImageDestination = (*dirImageDestination)(nil)
|
||
|
||
func TestDestinationReference(t *testing.T) {
|
||
ref, tmpDir := refToTempDir(t)
|
||
|
||
dest, err := ref.NewImageDestination(context.Background(), nil)
|
||
require.NoError(t, err)
|
||
defer dest.Close()
|
||
ref2 := dest.Reference()
|
||
assert.Equal(t, tmpDir, ref2.StringWithinTransport())
|
||
}
|
||
|
||
func TestGetPutManifest(t *testing.T) {
|
||
ref, _ := refToTempDir(t)
|
||
|
||
man := []byte("test-manifest")
|
||
list := []byte("test-manifest-list")
|
||
md, err := manifest.Digest(man)
|
||
require.NoError(t, err)
|
||
dest, err := ref.NewImageDestination(context.Background(), nil)
|
||
require.NoError(t, err)
|
||
defer dest.Close()
|
||
err = dest.PutManifest(context.Background(), man, &md)
|
||
assert.NoError(t, err)
|
||
err = dest.PutManifest(context.Background(), list, nil)
|
||
assert.NoError(t, err)
|
||
err = dest.Commit(context.Background(), nil) // nil unparsedToplevel is invalid, we don’t currently use the value
|
||
assert.NoError(t, err)
|
||
|
||
src, err := ref.NewImageSource(context.Background(), nil)
|
||
require.NoError(t, err)
|
||
defer src.Close()
|
||
m, mt, err := src.GetManifest(context.Background(), nil)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, list, m)
|
||
assert.Equal(t, "", mt)
|
||
|
||
m, mt, err = src.GetManifest(context.Background(), &md)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, man, m)
|
||
assert.Equal(t, "", mt)
|
||
}
|
||
|
||
func TestGetPutBlob(t *testing.T) {
|
||
computedBlob := []byte("test-blob")
|
||
providedBlob := []byte("provided-blob")
|
||
providedDigest := digest.Digest("sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
||
|
||
ref, _ := refToTempDir(t)
|
||
cache := memory.New()
|
||
|
||
dest, err := ref.NewImageDestination(context.Background(), nil)
|
||
require.NoError(t, err)
|
||
defer dest.Close()
|
||
assert.Equal(t, types.PreserveOriginal, dest.DesiredLayerCompression())
|
||
// PutBlob with caller-provided data
|
||
providedInfo, err := dest.PutBlob(context.Background(), bytes.NewReader(providedBlob), types.BlobInfo{Digest: providedDigest, Size: int64(len(providedBlob))}, cache, false)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, int64(len(providedBlob)), providedInfo.Size)
|
||
assert.Equal(t, providedDigest, providedInfo.Digest)
|
||
// PutBlob with unknown data
|
||
computedInfo, err := dest.PutBlob(context.Background(), bytes.NewReader(computedBlob), types.BlobInfo{Digest: "", Size: int64(-1)}, cache, false)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, int64(len(computedBlob)), computedInfo.Size)
|
||
assert.Equal(t, digest.FromBytes(computedBlob), computedInfo.Digest)
|
||
err = dest.Commit(context.Background(), nil) // nil unparsedToplevel is invalid, we don’t currently use the value
|
||
assert.NoError(t, err)
|
||
|
||
src, err := ref.NewImageSource(context.Background(), nil)
|
||
require.NoError(t, err)
|
||
defer src.Close()
|
||
for digest, expectedBlob := range map[digest.Digest][]byte{
|
||
providedInfo.Digest: providedBlob,
|
||
computedInfo.Digest: computedBlob,
|
||
} {
|
||
rc, size, err := src.GetBlob(context.Background(), types.BlobInfo{Digest: digest, Size: int64(len(expectedBlob))}, cache)
|
||
assert.NoError(t, err)
|
||
defer rc.Close()
|
||
b, err := io.ReadAll(rc)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, expectedBlob, b)
|
||
assert.Equal(t, int64(len(expectedBlob)), size)
|
||
}
|
||
}
|
||
|
||
// readerFromFunc allows implementing Reader by any function, e.g. a closure.
|
||
type readerFromFunc func([]byte) (int, error)
|
||
|
||
func (fn readerFromFunc) Read(p []byte) (int, error) {
|
||
return fn(p)
|
||
}
|
||
|
||
// TestPutBlobDigestFailure simulates behavior on digest verification failure.
|
||
func TestPutBlobDigestFailure(t *testing.T) {
|
||
const digestErrorString = "Simulated digest error"
|
||
const blobDigest = digest.Digest("sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
||
|
||
ref, _ := refToTempDir(t)
|
||
dirRef, ok := ref.(dirReference)
|
||
require.True(t, ok)
|
||
blobPath, err := dirRef.layerPath(blobDigest)
|
||
require.NoError(t, err)
|
||
cache := memory.New()
|
||
|
||
firstRead := true
|
||
reader := readerFromFunc(func(p []byte) (int, error) {
|
||
_, err := os.Lstat(blobPath)
|
||
require.Error(t, err)
|
||
require.True(t, os.IsNotExist(err))
|
||
if firstRead {
|
||
if len(p) > 0 {
|
||
firstRead = false
|
||
}
|
||
for i := 0; i < len(p); i++ {
|
||
p[i] = 0xAA
|
||
}
|
||
return len(p), nil
|
||
}
|
||
return 0, errors.New(digestErrorString)
|
||
})
|
||
|
||
dest, err := ref.NewImageDestination(context.Background(), nil)
|
||
require.NoError(t, err)
|
||
defer dest.Close()
|
||
_, err = dest.PutBlob(context.Background(), reader, types.BlobInfo{Digest: blobDigest, Size: -1}, cache, false)
|
||
assert.ErrorContains(t, err, digestErrorString)
|
||
err = dest.Commit(context.Background(), nil) // nil unparsedToplevel is invalid, we don’t currently use the value
|
||
assert.NoError(t, err)
|
||
|
||
_, err = os.Lstat(blobPath)
|
||
require.Error(t, err)
|
||
require.True(t, os.IsNotExist(err))
|
||
}
|
||
|
||
func TestGetPutSignatures(t *testing.T) {
|
||
ref, _ := refToTempDir(t)
|
||
|
||
man := []byte("test-manifest")
|
||
list := []byte("test-manifest-list")
|
||
md, err := manifest.Digest(man)
|
||
require.NoError(t, err)
|
||
// These signatures are completely invalid; start with 0xA3 just to be minimally plausible to signature.FromBlob.
|
||
signatures := [][]byte{
|
||
[]byte("\xA3sig1"),
|
||
[]byte("\xA3sig2"),
|
||
}
|
||
listSignatures := [][]byte{
|
||
[]byte("\xA3sig3"),
|
||
[]byte("\xA3sig4"),
|
||
}
|
||
|
||
dest, err := ref.NewImageDestination(context.Background(), nil)
|
||
require.NoError(t, err)
|
||
defer dest.Close()
|
||
err = dest.SupportsSignatures(context.Background())
|
||
assert.NoError(t, err)
|
||
|
||
err = dest.PutManifest(context.Background(), man, &md)
|
||
require.NoError(t, err)
|
||
err = dest.PutManifest(context.Background(), list, nil)
|
||
require.NoError(t, err)
|
||
|
||
err = dest.PutSignatures(context.Background(), signatures, &md)
|
||
assert.NoError(t, err)
|
||
err = dest.PutSignatures(context.Background(), listSignatures, nil)
|
||
assert.NoError(t, err)
|
||
err = dest.Commit(context.Background(), nil) // nil unparsedToplevel is invalid, we don’t currently use the value
|
||
assert.NoError(t, err)
|
||
|
||
src, err := ref.NewImageSource(context.Background(), nil)
|
||
require.NoError(t, err)
|
||
defer src.Close()
|
||
sigs, err := src.GetSignatures(context.Background(), nil)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, listSignatures, sigs)
|
||
|
||
sigs, err = src.GetSignatures(context.Background(), &md)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, signatures, sigs)
|
||
}
|
||
|
||
func TestSourceReference(t *testing.T) {
|
||
ref, tmpDir := refToTempDir(t)
|
||
|
||
src, err := ref.NewImageSource(context.Background(), nil)
|
||
require.NoError(t, err)
|
||
defer src.Close()
|
||
ref2 := src.Reference()
|
||
assert.Equal(t, tmpDir, ref2.StringWithinTransport())
|
||
}
|