package image // import "github.com/docker/docker/image" import ( "crypto/rand" "crypto/sha256" "encoding/hex" "errors" "os" "path/filepath" "testing" "github.com/opencontainers/go-digest" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) func defaultFSStoreBackend(t *testing.T) (StoreBackend, func()) { tmpdir, err := os.MkdirTemp("", "images-fs-store") assert.Check(t, err) fsBackend, err := NewFSStoreBackend(tmpdir) assert.Check(t, err) return fsBackend, func() { os.RemoveAll(tmpdir) } } func TestFSGetInvalidData(t *testing.T) { store, cleanup := defaultFSStoreBackend(t) defer cleanup() dgst, err := store.Set([]byte("foobar")) assert.Check(t, err) err = os.WriteFile(filepath.Join(store.(*fs).root, contentDirName, string(dgst.Algorithm()), dgst.Encoded()), []byte("foobar2"), 0o600) assert.Check(t, err) _, err = store.Get(dgst) assert.Check(t, is.ErrorContains(err, "failed to verify")) } func TestFSInvalidSet(t *testing.T) { store, cleanup := defaultFSStoreBackend(t) defer cleanup() id := digest.FromBytes([]byte("foobar")) err := os.Mkdir(filepath.Join(store.(*fs).root, contentDirName, string(id.Algorithm()), id.Encoded()), 0o700) assert.Check(t, err) _, err = store.Set([]byte("foobar")) assert.Check(t, is.ErrorContains(err, "failed to write digest data")) } func TestFSInvalidRoot(t *testing.T) { tmpdir, err := os.MkdirTemp("", "images-fs-store") assert.Check(t, err) defer os.RemoveAll(tmpdir) tcases := []struct { root, invalidFile string }{ {"root", "root"}, {"root", "root/content"}, {"root", "root/metadata"}, } for _, tc := range tcases { root := filepath.Join(tmpdir, tc.root) filePath := filepath.Join(tmpdir, tc.invalidFile) err := os.MkdirAll(filepath.Dir(filePath), 0700) assert.Check(t, err) f, err := os.Create(filePath) assert.Check(t, err) f.Close() _, err = NewFSStoreBackend(root) assert.Check(t, is.ErrorContains(err, "failed to create storage backend")) os.RemoveAll(root) } } func TestFSMetadataGetSet(t *testing.T) { store, cleanup := defaultFSStoreBackend(t) defer cleanup() id, err := store.Set([]byte("foo")) assert.Check(t, err) id2, err := store.Set([]byte("bar")) assert.Check(t, err) tcases := []struct { id digest.Digest key string value []byte }{ {id, "tkey", []byte("tval1")}, {id, "tkey2", []byte("tval2")}, {id2, "tkey", []byte("tval3")}, } for _, tc := range tcases { err = store.SetMetadata(tc.id, tc.key, tc.value) assert.Check(t, err) actual, err := store.GetMetadata(tc.id, tc.key) assert.Check(t, err) assert.Check(t, is.DeepEqual(tc.value, actual)) } _, err = store.GetMetadata(id2, "tkey2") assert.Check(t, is.ErrorContains(err, "failed to read metadata")) id3 := digest.FromBytes([]byte("baz")) err = store.SetMetadata(id3, "tkey", []byte("tval")) assert.Check(t, is.ErrorContains(err, "failed to get digest")) _, err = store.GetMetadata(id3, "tkey") assert.Check(t, is.ErrorContains(err, "failed to get digest")) } func TestFSInvalidWalker(t *testing.T) { store, cleanup := defaultFSStoreBackend(t) defer cleanup() fooID, err := store.Set([]byte("foo")) assert.Check(t, err) err = os.WriteFile(filepath.Join(store.(*fs).root, contentDirName, "sha256/foobar"), []byte("foobar"), 0600) assert.Check(t, err) n := 0 err = store.Walk(func(id digest.Digest) error { assert.Check(t, is.Equal(fooID, id)) n++ return nil }) assert.Check(t, err) assert.Check(t, is.Equal(1, n)) } func TestFSGetSet(t *testing.T) { store, cleanup := defaultFSStoreBackend(t) defer cleanup() type tcase struct { input []byte expected digest.Digest } tcases := []tcase{ {[]byte("foobar"), digest.Digest("sha256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2")}, } randomInput := make([]byte, 8*1024) _, err := rand.Read(randomInput) assert.Check(t, err) // skipping use of digest pkg because it is used by the implementation h := sha256.New() _, err = h.Write(randomInput) assert.Check(t, err) tcases = append(tcases, tcase{ input: randomInput, expected: digest.Digest("sha256:" + hex.EncodeToString(h.Sum(nil))), }) for _, tc := range tcases { id, err := store.Set(tc.input) assert.Check(t, err) assert.Check(t, is.Equal(tc.expected, id)) } for _, tc := range tcases { data, err := store.Get(tc.expected) assert.Check(t, err) assert.Check(t, is.DeepEqual(tc.input, data)) } } func TestFSGetUnsetKey(t *testing.T) { store, cleanup := defaultFSStoreBackend(t) defer cleanup() for _, key := range []digest.Digest{"foobar:abc", "sha256:abc", "sha256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2a"} { _, err := store.Get(key) assert.Check(t, is.ErrorContains(err, "failed to get digest")) } } func TestFSGetEmptyData(t *testing.T) { store, cleanup := defaultFSStoreBackend(t) defer cleanup() for _, emptyData := range [][]byte{nil, {}} { _, err := store.Set(emptyData) assert.Check(t, is.ErrorContains(err, "invalid empty data")) } } func TestFSDelete(t *testing.T) { store, cleanup := defaultFSStoreBackend(t) defer cleanup() id, err := store.Set([]byte("foo")) assert.Check(t, err) id2, err := store.Set([]byte("bar")) assert.Check(t, err) err = store.Delete(id) assert.Check(t, err) _, err = store.Get(id) assert.Check(t, is.ErrorContains(err, "failed to get digest")) _, err = store.Get(id2) assert.Check(t, err) err = store.Delete(id2) assert.Check(t, err) _, err = store.Get(id2) assert.Check(t, is.ErrorContains(err, "failed to get digest")) } func TestFSWalker(t *testing.T) { store, cleanup := defaultFSStoreBackend(t) defer cleanup() id, err := store.Set([]byte("foo")) assert.Check(t, err) id2, err := store.Set([]byte("bar")) assert.Check(t, err) tcases := make(map[digest.Digest]struct{}) tcases[id] = struct{}{} tcases[id2] = struct{}{} n := 0 err = store.Walk(func(id digest.Digest) error { delete(tcases, id) n++ return nil }) assert.Check(t, err) assert.Check(t, is.Equal(2, n)) assert.Check(t, is.Len(tcases, 0)) } func TestFSWalkerStopOnError(t *testing.T) { store, cleanup := defaultFSStoreBackend(t) defer cleanup() id, err := store.Set([]byte("foo")) assert.Check(t, err) tcases := make(map[digest.Digest]struct{}) tcases[id] = struct{}{} err = store.Walk(func(id digest.Digest) error { return errors.New("what") }) assert.Check(t, is.ErrorContains(err, "what")) }