diff --git a/notifications/bridge.go b/notifications/bridge.go index bc4a90aa..48048063 100644 --- a/notifications/bridge.go +++ b/notifications/bridge.go @@ -116,6 +116,13 @@ func (b *bridge) TagDeleted(repo reference.Named, tag string) error { return b.sink.Write(*event) } +func (b *bridge) RepoDeleted(repo reference.Named) error { + event := b.createEvent(EventActionDelete) + event.Target.Repository = repo.Name() + + return b.sink.Write(*event) +} + func (b *bridge) createManifestEventAndWrite(action string, repo reference.Named, sm distribution.Manifest) error { manifestEvent, err := b.createManifestEvent(action, repo, sm) if err != nil { diff --git a/notifications/bridge_test.go b/notifications/bridge_test.go index 0f4d7736..5c1401aa 100644 --- a/notifications/bridge_test.go +++ b/notifications/bridge_test.go @@ -124,6 +124,18 @@ func TestEventBridgeTagDeleted(t *testing.T) { } } +func TestEventBridgeRepoDeleted(t *testing.T) { + l := createTestEnv(t, testSinkFn(func(events ...Event) error { + checkDeleted(t, EventActionDelete, events...) + return nil + })) + + repoRef, _ := reference.WithName(repo) + if err := l.RepoDeleted(repoRef); err != nil { + t.Fatalf("unexpected error notifying repo deletion: %v", err) + } +} + func createTestEnv(t *testing.T, fn testSinkFn) Listener { pk, err := libtrust.GenerateECP256PrivateKey() if err != nil { diff --git a/notifications/listener.go b/notifications/listener.go index 8cfdb67e..b11f0773 100644 --- a/notifications/listener.go +++ b/notifications/listener.go @@ -26,9 +26,9 @@ type BlobListener interface { BlobDeleted(repo reference.Named, desc digest.Digest) error } -// RepoListener describes a listener that can respond to repository related events. type RepoListener interface { TagDeleted(repo reference.Named, tag string) error + RepoDeleted(repo reference.Named) error } // Listener combines all repository events into a single interface. @@ -43,12 +43,28 @@ type repositoryListener struct { listener Listener } +type removerListener struct { + distribution.RepositoryRemover + listener Listener +} + // Listen dispatches events on the repository to the listener. -func Listen(repo distribution.Repository, listener Listener) distribution.Repository { +func Listen(repo distribution.Repository, remover distribution.RepositoryRemover, listener Listener) (distribution.Repository, distribution.RepositoryRemover) { return &repositoryListener{ - Repository: repo, - listener: listener, + Repository: repo, + listener: listener, + }, &removerListener{ + RepositoryRemover: remover, + listener: listener, + } +} + +func (nl *removerListener) Remove(ctx context.Context, name reference.Named) error { + err := nl.RepositoryRemover.Remove(ctx, name) + if err != nil { + return err } + return nl.listener.RepoDeleted(name) } func (rl *repositoryListener) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) { diff --git a/notifications/listener_test.go b/notifications/listener_test.go index 32a7f6d9..1d8b731e 100644 --- a/notifications/listener_test.go +++ b/notifications/listener_test.go @@ -38,10 +38,15 @@ func TestListener(t *testing.T) { if err != nil { t.Fatalf("unexpected error getting repo: %v", err) } - repository = Listen(repository, tl) + + 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 - checkExerciseRepository(t, repository) + checkExerciseRepository(t, repository, remover) expectedOps := map[string]int{ "manifest:push": 1, @@ -51,6 +56,7 @@ func TestListener(t *testing.T) { "layer:pull": 2, "layer:delete": 2, "tag:delete": 1, + "repo:delete": 1, } if !reflect.DeepEqual(tl.ops, expectedOps) { @@ -102,9 +108,14 @@ func (tl *testListener) TagDeleted(repo reference.Named, tag string) error { return nil } +func (tl *testListener) RepoDeleted(repo reference.Named) error { + tl.ops["repo:delete"]++ + return nil +} + // checkExerciseRegistry takes the registry through all of its operations, // carrying out generic checks. -func checkExerciseRepository(t *testing.T, repository distribution.Repository) { +func checkExerciseRepository(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 @@ -210,4 +221,9 @@ func checkExerciseRepository(t *testing.T, repository distribution.Repository) { if err != nil { t.Fatalf("unexpected error deleting tag: %v", err) } + + err = remover.Remove(ctx, repository.Named()) + if err != nil { + t.Fatalf("unexpected error deleting repo: %v", err) + } } diff --git a/registry.go b/registry.go index a3a80ab8..6c321098 100644 --- a/registry.go +++ b/registry.go @@ -54,6 +54,11 @@ type RepositoryEnumerator interface { Enumerate(ctx context.Context, ingester func(string) error) error } +// RepositoryRemover removes given repository +type RepositoryRemover interface { + Remove(ctx context.Context, name reference.Named) error +} + // ManifestServiceOption is a function argument for Manifest Service methods type ManifestServiceOption interface { Apply(ManifestService) error diff --git a/registry/storage/catalog.go b/registry/storage/catalog.go index 3c1a78de..4db8bd88 100644 --- a/registry/storage/catalog.go +++ b/registry/storage/catalog.go @@ -7,6 +7,7 @@ import ( "path" "strings" + "github.com/docker/distribution/reference" "github.com/docker/distribution/registry/storage/driver" ) @@ -70,6 +71,16 @@ func (reg *registry) Enumerate(ctx context.Context, ingester func(string) error) return err } +// Remove removes a repository from storage +func (r *registry) Remove(ctx context.Context, name reference.Named) error { + root, err := pathFor(repositoriesRootPathSpec{}) + if err != nil { + return err + } + repoDir := path.Join(root, name.Name()) + return r.driver.Delete(ctx, repoDir) +} + // lessPath returns true if one path a is less than path b. // // A component-wise comparison is done, rather than the lexical comparison of diff --git a/registry/storage/registry.go b/registry/storage/registry.go index 46b96853..70d5b8d2 100644 --- a/registry/storage/registry.go +++ b/registry/storage/registry.go @@ -23,6 +23,7 @@ type registry struct { schema1SigningKey libtrust.PrivateKey blobDescriptorServiceFactory distribution.BlobDescriptorServiceFactory manifestURLs manifestURLs + driver storagedriver.StorageDriver } // manifestURLs holds regular expressions for controlling manifest URL whitelisting @@ -133,6 +134,7 @@ func NewRegistry(ctx context.Context, driver storagedriver.StorageDriver, option }, statter: statter, resumableDigestEnabled: true, + driver: driver, } for _, option := range options {