distribution/notifications/listener_test.go

233 lines
6.1 KiB
Go

package notifications
import (
"bytes"
"reflect"
"testing"
"github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/internal/dcontext"
"github.com/distribution/distribution/v3/manifest/schema2"
"github.com/distribution/distribution/v3/registry/storage"
"github.com/distribution/distribution/v3/registry/storage/cache/memory"
"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
"github.com/distribution/distribution/v3/testutil"
"github.com/distribution/reference"
"github.com/opencontainers/go-digest"
)
func TestListener(t *testing.T) {
ctx := dcontext.Background()
registry, err := storage.NewRegistry(ctx, inmemory.New(),
storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider(memory.UnlimitedSize)),
storage.EnableDelete, storage.EnableRedirect)
if err != nil {
t.Fatalf("error creating registry: %v", err)
}
tl := &testListener{
ops: make(map[string]int),
}
repoRef, _ := reference.WithName("foo/bar")
repository, err := registry.Repository(ctx, repoRef)
if err != nil {
t.Fatalf("unexpected error getting repo: %v", err)
}
remover, ok := registry.(distribution.RepositoryRemover)
if !ok {
t.Fatal("registry does not implement RepositoryRemover")
}
repository, remover = Listen(repository, remover, tl)
// Now take the registry through a number of operations
checkTestRepository(t, repository, remover)
expectedOps := map[string]int{
"manifest:push": 1,
"manifest:pull": 1,
"manifest:delete": 1,
"layer:push": 3,
"layer:pull": 3,
"layer:delete": 3,
"tag:delete": 1,
"repo:delete": 1,
}
if !reflect.DeepEqual(tl.ops, expectedOps) {
t.Fatalf("counts do not match:\n%v\n !=\n%v", tl.ops, expectedOps)
}
}
type testListener struct {
ops map[string]int
}
func (tl *testListener) ManifestPushed(repo reference.Named, m distribution.Manifest, options ...distribution.ManifestServiceOption) error {
tl.ops["manifest:push"]++
return nil
}
func (tl *testListener) ManifestPulled(repo reference.Named, m distribution.Manifest, options ...distribution.ManifestServiceOption) error {
tl.ops["manifest:pull"]++
return nil
}
func (tl *testListener) ManifestDeleted(repo reference.Named, d digest.Digest) error {
tl.ops["manifest:delete"]++
return nil
}
func (tl *testListener) BlobPushed(repo reference.Named, desc distribution.Descriptor) error {
tl.ops["layer:push"]++
return nil
}
func (tl *testListener) BlobPulled(repo reference.Named, desc distribution.Descriptor) error {
tl.ops["layer:pull"]++
return nil
}
func (tl *testListener) BlobMounted(repo reference.Named, desc distribution.Descriptor, fromRepo reference.Named) error {
tl.ops["layer:mount"]++
return nil
}
func (tl *testListener) BlobDeleted(repo reference.Named, d digest.Digest) error {
tl.ops["layer:delete"]++
return nil
}
func (tl *testListener) TagDeleted(repo reference.Named, tag string) error {
tl.ops["tag:delete"]++
return nil
}
func (tl *testListener) RepoDeleted(repo reference.Named) error {
tl.ops["repo:delete"]++
return nil
}
// checkTestRepository takes the registry through all of its operations,
// carrying out generic checks.
func checkTestRepository(t *testing.T, repository distribution.Repository, remover distribution.RepositoryRemover) {
// TODO(stevvooe): This would be a nice testutil function. Basically, it
// takes the registry through a common set of operations. This could be
// used to make cross-cutting updates by changing internals that affect
// update counts. Basically, it would make writing tests a lot easier.
// TODO: change this to use Builder
ctx := dcontext.Background()
tag := "thetag"
config := []byte(`{"name": "foo"}`)
configDgst := digest.FromBytes(config)
configReader := bytes.NewReader(config)
var blobDigests []digest.Digest
blobDigests = append(blobDigests, configDgst)
blobs := repository.Blobs(ctx)
// push config blob
if err := testutil.PushBlob(ctx, repository, configReader, configDgst); err != nil {
t.Fatal(err)
}
// Then fetch the config blob
if rc, err := blobs.Open(ctx, configDgst); err != nil {
t.Fatalf("error fetching config: %v", err)
} else {
defer rc.Close()
}
m := schema2.Manifest{
Versioned: schema2.SchemaVersion,
Config: distribution.Descriptor{
MediaType: "foo/bar",
Digest: configDgst,
},
}
for i := 0; i < 2; i++ {
rs, dgst, err := testutil.CreateRandomTarFile()
if err != nil {
t.Fatalf("error creating test layer: %v", err)
}
blobDigests = append(blobDigests, dgst)
if err := testutil.PushBlob(ctx, repository, rs, dgst); err != nil {
t.Fatal(err)
}
m.Layers = append(m.Layers, distribution.Descriptor{
MediaType: "application/octet-stream",
Digest: dgst,
})
// Then fetch the blobs
if rc, err := blobs.Open(ctx, dgst); err != nil {
t.Fatalf("error fetching layer: %v", err)
} else {
defer rc.Close()
}
}
sm, err := schema2.FromStruct(m)
if err != nil {
t.Fatal(err.Error())
}
manifests, err := repository.Manifests(ctx)
if err != nil {
t.Fatal(err.Error())
}
var digestPut digest.Digest
if digestPut, err = manifests.Put(ctx, sm); err != nil {
t.Fatalf("unexpected error putting the manifest: %v", err)
}
_, canonical, err := sm.Payload()
if err != nil {
t.Fatal(err.Error())
}
dgst := digest.FromBytes(canonical)
if dgst != digestPut {
t.Fatalf("mismatching digest from payload and put")
}
if err := repository.Tags(ctx).Tag(ctx, tag, distribution.Descriptor{Digest: dgst}); err != nil {
t.Fatalf("unexpected error tagging manifest: %v", err)
}
_, err = manifests.Get(ctx, dgst)
if err != nil {
t.Fatalf("unexpected error fetching manifest: %v", err)
}
err = repository.Tags(ctx).Untag(ctx, tag)
if err != nil {
t.Fatalf("unexpected error deleting tag: %v", err)
}
err = manifests.Delete(ctx, dgst)
if err != nil {
t.Fatalf("unexpected error deleting blob: %v", err)
}
for _, d := range blobDigests {
err = blobs.Delete(ctx, d)
if err != nil {
t.Fatalf("unexpected error deleting blob: %v", err)
}
}
err = remover.Remove(ctx, repository.Named())
if err != nil {
t.Fatalf("unexpected error deleting repo: %v", err)
}
}