Propogate tag as a functional argument into the notification system to attach

tags to manifest push and pull event notifications.

Signed-off-by: Richard Scothern <richard.scothern@gmail.com>
This commit is contained in:
Richard Scothern 2016-03-18 15:30:47 -07:00
parent ec6ac0c05e
commit afe2bdd1c5
9 changed files with 100 additions and 37 deletions

View file

@ -53,12 +53,34 @@ func NewRequestRecord(id string, r *http.Request) RequestRecord {
} }
} }
func (b *bridge) ManifestPushed(repo reference.Named, sm distribution.Manifest) error { func (b *bridge) ManifestPushed(repo reference.Named, sm distribution.Manifest, options ...distribution.ManifestServiceOption) error {
return b.createManifestEventAndWrite(EventActionPush, repo, sm) manifestEvent, err := b.createManifestEvent(EventActionPush, repo, sm)
if err != nil {
return err
}
for _, option := range options {
if opt, ok := option.(distribution.WithTagOption); ok {
manifestEvent.Target.Tag = opt.Tag
break
}
}
return b.sink.Write(*manifestEvent)
} }
func (b *bridge) ManifestPulled(repo reference.Named, sm distribution.Manifest) error { func (b *bridge) ManifestPulled(repo reference.Named, sm distribution.Manifest, options ...distribution.ManifestServiceOption) error {
return b.createManifestEventAndWrite(EventActionPull, repo, sm) manifestEvent, err := b.createManifestEvent(EventActionPull, repo, sm)
if err != nil {
return err
}
for _, option := range options {
if opt, ok := option.(distribution.WithTagOption); ok {
manifestEvent.Target.Tag = opt.Tag
break
}
}
return b.sink.Write(*manifestEvent)
} }
func (b *bridge) ManifestDeleted(repo reference.Named, dgst digest.Digest) error { func (b *bridge) ManifestDeleted(repo reference.Named, dgst digest.Digest) error {

View file

@ -3,6 +3,7 @@ package notifications
import ( import (
"testing" "testing"
"github.com/docker/distribution"
"github.com/docker/distribution/digest" "github.com/docker/distribution/digest"
"github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
@ -61,6 +62,38 @@ func TestEventBridgeManifestPushed(t *testing.T) {
} }
} }
func TestEventBridgeManifestPushedWithTag(t *testing.T) {
l := createTestEnv(t, testSinkFn(func(events ...Event) error {
checkCommonManifest(t, EventActionPush, events...)
if events[0].Target.Tag != "latest" {
t.Fatalf("missing or unexpected tag: %#v", events[0].Target)
}
return nil
}))
repoRef, _ := reference.ParseNamed(repo)
if err := l.ManifestPushed(repoRef, sm, distribution.WithTag(m.Tag)); err != nil {
t.Fatalf("unexpected error notifying manifest pull: %v", err)
}
}
func TestEventBridgeManifestPulledWithTag(t *testing.T) {
l := createTestEnv(t, testSinkFn(func(events ...Event) error {
checkCommonManifest(t, EventActionPull, events...)
if events[0].Target.Tag != "latest" {
t.Fatalf("missing or unexpected tag: %#v", events[0].Target)
}
return nil
}))
repoRef, _ := reference.ParseNamed(repo)
if err := l.ManifestPulled(repoRef, sm, distribution.WithTag(m.Tag)); err != nil {
t.Fatalf("unexpected error notifying manifest pull: %v", err)
}
}
func TestEventBridgeManifestDeleted(t *testing.T) { func TestEventBridgeManifestDeleted(t *testing.T) {
l := createTestEnv(t, testSinkFn(func(events ...Event) error { l := createTestEnv(t, testSinkFn(func(events ...Event) error {
checkDeleted(t, EventActionDelete, events...) checkDeleted(t, EventActionDelete, events...)

View file

@ -68,6 +68,9 @@ type Event struct {
// URL provides a direct link to the content. // URL provides a direct link to the content.
URL string `json:"url,omitempty"` URL string `json:"url,omitempty"`
// Tag provides the tag
Tag string `json:"tag,omitempty"`
} `json:"target,omitempty"` } `json:"target,omitempty"`
// Request covers the request that generated the event. // Request covers the request that generated the event.

View file

@ -12,8 +12,8 @@ import (
// ManifestListener describes a set of methods for listening to events related to manifests. // ManifestListener describes a set of methods for listening to events related to manifests.
type ManifestListener interface { type ManifestListener interface {
ManifestPushed(repo reference.Named, sm distribution.Manifest) error ManifestPushed(repo reference.Named, sm distribution.Manifest, options ...distribution.ManifestServiceOption) error
ManifestPulled(repo reference.Named, sm distribution.Manifest) error ManifestPulled(repo reference.Named, sm distribution.Manifest, options ...distribution.ManifestServiceOption) error
ManifestDeleted(repo reference.Named, dgst digest.Digest) error ManifestDeleted(repo reference.Named, dgst digest.Digest) error
} }
@ -81,7 +81,7 @@ func (msl *manifestServiceListener) Delete(ctx context.Context, dgst digest.Dige
func (msl *manifestServiceListener) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) { func (msl *manifestServiceListener) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) {
sm, err := msl.ManifestService.Get(ctx, dgst) sm, err := msl.ManifestService.Get(ctx, dgst)
if err == nil { if err == nil {
if err := msl.parent.listener.ManifestPulled(msl.parent.Repository.Named(), sm); err != nil { if err := msl.parent.listener.ManifestPulled(msl.parent.Repository.Named(), sm, options...); err != nil {
logrus.Errorf("error dispatching manifest pull to listener: %v", err) logrus.Errorf("error dispatching manifest pull to listener: %v", err)
} }
} }
@ -93,7 +93,7 @@ func (msl *manifestServiceListener) Put(ctx context.Context, sm distribution.Man
dgst, err := msl.ManifestService.Put(ctx, sm, options...) dgst, err := msl.ManifestService.Put(ctx, sm, options...)
if err == nil { if err == nil {
if err := msl.parent.listener.ManifestPushed(msl.parent.Repository.Named(), sm); err != nil { if err := msl.parent.listener.ManifestPushed(msl.parent.Repository.Named(), sm, options...); err != nil {
logrus.Errorf("error dispatching manifest push to listener: %v", err) logrus.Errorf("error dispatching manifest push to listener: %v", err)
} }
} }

View file

@ -44,7 +44,7 @@ func TestListener(t *testing.T) {
"manifest:delete": 1, "manifest:delete": 1,
"layer:push": 2, "layer:push": 2,
"layer:pull": 2, "layer:pull": 2,
"layer:delete": 2, // deletes not supported for now "layer:delete": 2,
} }
if !reflect.DeepEqual(tl.ops, expectedOps) { if !reflect.DeepEqual(tl.ops, expectedOps) {
@ -57,13 +57,13 @@ type testListener struct {
ops map[string]int ops map[string]int
} }
func (tl *testListener) ManifestPushed(repo reference.Named, m distribution.Manifest) error { func (tl *testListener) ManifestPushed(repo reference.Named, m distribution.Manifest, options ...distribution.ManifestServiceOption) error {
tl.ops["manifest:push"]++ tl.ops["manifest:push"]++
return nil return nil
} }
func (tl *testListener) ManifestPulled(repo reference.Named, m distribution.Manifest) error { func (tl *testListener) ManifestPulled(repo reference.Named, m distribution.Manifest, options ...distribution.ManifestServiceOption) error {
tl.ops["manifest:pull"]++ tl.ops["manifest:pull"]++
return nil return nil
} }

View file

@ -58,6 +58,18 @@ type ManifestServiceOption interface {
Apply(ManifestService) error Apply(ManifestService) error
} }
// WithTag allows a tag to be passed into Put
func WithTag(tag string) ManifestServiceOption {
return WithTagOption{tag}
}
type WithTagOption struct{ Tag string }
func (o WithTagOption) Apply(m ManifestService) error {
// no implementation
return nil
}
// Repository is a named collection of manifests and layers. // Repository is a named collection of manifests and layers.
type Repository interface { type Repository interface {
// Named returns the name of the repository. // Named returns the name of the repository.

View file

@ -402,9 +402,9 @@ func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...dis
) )
for _, option := range options { for _, option := range options {
if opt, ok := option.(withTagOption); ok { if opt, ok := option.(distribution.WithTagOption); ok {
digestOrTag = opt.tag digestOrTag = opt.Tag
ref, err = reference.WithTag(ms.name, opt.tag) ref, err = reference.WithTag(ms.name, opt.Tag)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -465,21 +465,6 @@ func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...dis
return nil, HandleErrorResponse(resp) return nil, HandleErrorResponse(resp)
} }
// WithTag allows a tag to be passed into Put which enables the client
// to build a correct URL.
func WithTag(tag string) distribution.ManifestServiceOption {
return withTagOption{tag}
}
type withTagOption struct{ tag string }
func (o withTagOption) Apply(m distribution.ManifestService) error {
if _, ok := m.(*manifests); ok {
return nil
}
return fmt.Errorf("withTagOption is a client-only option")
}
// Put puts a manifest. A tag can be specified using an options parameter which uses some shared state to hold the // Put puts a manifest. A tag can be specified using an options parameter which uses some shared state to hold the
// tag name in order to build the correct upload URL. // tag name in order to build the correct upload URL.
func (ms *manifests) Put(ctx context.Context, m distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) { func (ms *manifests) Put(ctx context.Context, m distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) {
@ -487,9 +472,9 @@ func (ms *manifests) Put(ctx context.Context, m distribution.Manifest, options .
var tagged bool var tagged bool
for _, option := range options { for _, option := range options {
if opt, ok := option.(withTagOption); ok { if opt, ok := option.(distribution.WithTagOption); ok {
var err error var err error
ref, err = reference.WithTag(ref, opt.tag) ref, err = reference.WithTag(ref, opt.Tag)
if err != nil { if err != nil {
return "", err return "", err
} }

View file

@ -710,7 +710,7 @@ func TestV1ManifestFetch(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
manifest, err = ms.Get(ctx, dgst, WithTag("latest")) manifest, err = ms.Get(ctx, dgst, distribution.WithTag("latest"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -723,7 +723,7 @@ func TestV1ManifestFetch(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
manifest, err = ms.Get(ctx, dgst, WithTag("badcontenttype")) manifest, err = ms.Get(ctx, dgst, distribution.WithTag("badcontenttype"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -761,7 +761,7 @@ func TestManifestFetchWithEtag(t *testing.T) {
if !ok { if !ok {
panic("wrong type for client manifest service") panic("wrong type for client manifest service")
} }
_, err = clientManifestService.Get(ctx, d1, WithTag("latest"), AddEtagToTag("latest", d1.String())) _, err = clientManifestService.Get(ctx, d1, distribution.WithTag("latest"), AddEtagToTag("latest", d1.String()))
if err != distribution.ErrManifestNotModified { if err != distribution.ErrManifestNotModified {
t.Fatal(err) t.Fatal(err)
} }
@ -861,7 +861,7 @@ func TestManifestPut(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if _, err := ms.Put(ctx, m1, WithTag(m1.Tag)); err != nil { if _, err := ms.Put(ctx, m1, distribution.WithTag(m1.Tag)); err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -86,7 +86,11 @@ func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http
return return
} }
manifest, err = manifests.Get(imh, imh.Digest) var options []distribution.ManifestServiceOption
if imh.Tag != "" {
options = append(options, distribution.WithTag(imh.Tag))
}
manifest, err = manifests.Get(imh, imh.Digest, options...)
if err != nil { if err != nil {
imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err)) imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err))
return return
@ -245,7 +249,11 @@ func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http
return return
} }
_, err = manifests.Put(imh, manifest) var options []distribution.ManifestServiceOption
if imh.Tag != "" {
options = append(options, distribution.WithTag(imh.Tag))
}
_, err = manifests.Put(imh, manifest, options...)
if err != nil { if err != nil {
// TODO(stevvooe): These error handling switches really need to be // TODO(stevvooe): These error handling switches really need to be
// handled by an app global mapper. // handled by an app global mapper.