diff --git a/notifications/bridge.go b/notifications/bridge.go index 9954e7c7..21d2105d 100644 --- a/notifications/bridge.go +++ b/notifications/bridge.go @@ -65,37 +65,38 @@ func (b *bridge) ManifestDeleted(repo distribution.Repository, sm *manifest.Sign } func (b *bridge) LayerPushed(repo distribution.Repository, layer distribution.Layer) error { - return b.createLayerEventAndWrite(EventActionPush, repo, layer.Digest()) + return b.createLayerEventAndWrite(EventActionPush, repo, layer) } func (b *bridge) LayerPulled(repo distribution.Repository, layer distribution.Layer) error { - return b.createLayerEventAndWrite(EventActionPull, repo, layer.Digest()) + return b.createLayerEventAndWrite(EventActionPull, repo, layer) } func (b *bridge) LayerDeleted(repo distribution.Repository, layer distribution.Layer) error { - return b.createLayerEventAndWrite(EventActionDelete, repo, layer.Digest()) + return b.createLayerEventAndWrite(EventActionDelete, repo, layer) } func (b *bridge) createManifestEventAndWrite(action string, repo distribution.Repository, sm *manifest.SignedManifest) error { - event, err := b.createManifestEvent(action, repo, sm) + manifestEvent, err := b.createManifestEvent(action, repo, sm) if err != nil { return err } - return b.sink.Write(*event) + return b.sink.Write(*manifestEvent) } func (b *bridge) createManifestEvent(action string, repo distribution.Repository, sm *manifest.SignedManifest) (*Event, error) { event := b.createEvent(action) - event.Target.Type = EventTargetTypeManifest - event.Target.Name = repo.Name() - event.Target.Tag = sm.Tag + event.Target.MediaType = manifest.ManifestMediaType + event.Target.Repository = repo.Name() p, err := sm.Payload() if err != nil { return nil, err } + event.Target.Length = int64(len(p)) + event.Target.Digest, err = digest.FromBytes(p) if err != nil { return nil, err @@ -111,8 +112,8 @@ func (b *bridge) createManifestEvent(action string, repo distribution.Repository return event, nil } -func (b *bridge) createLayerEventAndWrite(action string, repo distribution.Repository, dgst digest.Digest) error { - event, err := b.createLayerEvent(action, repo, dgst) +func (b *bridge) createLayerEventAndWrite(action string, repo distribution.Repository, layer distribution.Layer) error { + event, err := b.createLayerEvent(action, repo, layer) if err != nil { return err } @@ -120,10 +121,14 @@ func (b *bridge) createLayerEventAndWrite(action string, repo distribution.Repos return b.sink.Write(*event) } -func (b *bridge) createLayerEvent(action string, repo distribution.Repository, dgst digest.Digest) (*Event, error) { +func (b *bridge) createLayerEvent(action string, repo distribution.Repository, layer distribution.Layer) (*Event, error) { event := b.createEvent(action) - event.Target.Type = EventTargetTypeBlob - event.Target.Name = repo.Name() + event.Target.MediaType = layerMediaType + event.Target.Repository = repo.Name() + + event.Target.Length = layer.Length() + + dgst := layer.Digest() event.Target.Digest = dgst var err error diff --git a/notifications/event.go b/notifications/event.go index c23766fa..a898021b 100644 --- a/notifications/event.go +++ b/notifications/event.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/docker/distribution/digest" + "github.com/docker/distribution" ) // EventAction constants used in action field of Event. @@ -14,17 +14,16 @@ const ( EventActionDelete = "delete" ) -// EventTargetType constants used in Target section of Event. const ( - EventTargetTypeManifest = "manifest" - EventTargetTypeBlob = "blob" + // EventsMediaType is the mediatype for the json event envelope. If the + // Event, ActorRecord, SourceRecord or Envelope structs change, the version + // number should be incremented. + EventsMediaType = "application/vnd.docker.distribution.events.v1+json" + // LayerMediaType is the media type for image rootfs diffs (aka "layers") + // used by Docker. We don't expect this to change for quite a while. + layerMediaType = "application/vnd.docker.container.image.rootfs.diff+x-gtar" ) -// EventsMediaType is the mediatype for the json event envelope. If the Event, -// ActorRecord, SourceRecord or Envelope structs change, the version number -// should be incremented. -const EventsMediaType = "application/vnd.docker.distribution.events.v1+json" - // Envelope defines the fields of a json event envelope message that can hold // one or more events. type Envelope struct { @@ -51,19 +50,14 @@ type Event struct { // Target uniquely describes the target of the event. Target struct { - // Type should be "manifest" or "blob" - Type string `json:"type,omitempty"` + // TODO(stevvooe): Use http.DetectContentType for layers, maybe. - // Name identifies the named repository. - Name string `json:"name,omitempty"` + distribution.Descriptor - // Digest should identify the object in the repository. - Digest digest.Digest `json:"digest,omitempty"` + // Repository identifies the named repository. + Repository string `json:"repository,omitempty"` - // Tag is present if the operation involved a tagged manifest. - Tag string `json:"tag,omitempty"` - - // URL provides a link to the content on the relevant repository instance. + // URL provides a direct link to the content. URL string `json:"url,omitempty"` } `json:"target,omitempty"` diff --git a/notifications/event_test.go b/notifications/event_test.go index cc2180ac..c413ce79 100644 --- a/notifications/event_test.go +++ b/notifications/event_test.go @@ -5,6 +5,8 @@ import ( "strings" "testing" "time" + + "github.com/docker/distribution/manifest" ) // TestEventJSONFormat provides silly test to detect if the event format or @@ -19,10 +21,10 @@ func TestEventEnvelopeJSONFormat(t *testing.T) { "timestamp": "2006-01-02T15:04:05Z", "action": "push", "target": { - "type": "manifest", - "name": "library/test", + "mediaType": "application/vnd.docker.distribution.manifest.v1+json", + "length": 1, "digest": "sha256:0123456789abcdef0", - "tag": "latest", + "repository": "library/test", "url": "http://example.com/v2/library/test/manifests/latest" }, "request": { @@ -44,9 +46,10 @@ func TestEventEnvelopeJSONFormat(t *testing.T) { "timestamp": "2006-01-02T15:04:05Z", "action": "push", "target": { - "type": "blob", - "name": "library/test", + "mediaType": "application/vnd.docker.container.image.rootfs.diff+x-gtar", + "length": 2, "digest": "tarsum.v2+sha256:0123456789abcdef1", + "repository": "library/test", "url": "http://example.com/v2/library/test/manifests/latest" }, "request": { @@ -68,9 +71,10 @@ func TestEventEnvelopeJSONFormat(t *testing.T) { "timestamp": "2006-01-02T15:04:05Z", "action": "push", "target": { - "type": "blob", - "name": "library/test", + "mediaType": "application/vnd.docker.container.image.rootfs.diff+x-gtar", + "length": 3, "digest": "tarsum.v2+sha256:0123456789abcdef2", + "repository": "library/test", "url": "http://example.com/v2/library/test/manifests/latest" }, "request": { @@ -97,7 +101,7 @@ func TestEventEnvelopeJSONFormat(t *testing.T) { } var prototype Event - prototype.Action = "push" + prototype.Action = EventActionPush prototype.Timestamp = tm prototype.Actor.Name = "test-actor" prototype.Request.ID = "asdfasdf" @@ -111,25 +115,27 @@ func TestEventEnvelopeJSONFormat(t *testing.T) { manifestPush = prototype manifestPush.ID = "asdf-asdf-asdf-asdf-0" manifestPush.Target.Digest = "sha256:0123456789abcdef0" - manifestPush.Target.Type = EventTargetTypeManifest - manifestPush.Target.Name = "library/test" - manifestPush.Target.Tag = "latest" + manifestPush.Target.Length = int64(1) + manifestPush.Target.MediaType = manifest.ManifestMediaType + manifestPush.Target.Repository = "library/test" manifestPush.Target.URL = "http://example.com/v2/library/test/manifests/latest" var layerPush0 Event layerPush0 = prototype layerPush0.ID = "asdf-asdf-asdf-asdf-1" layerPush0.Target.Digest = "tarsum.v2+sha256:0123456789abcdef1" - layerPush0.Target.Type = EventTargetTypeBlob - layerPush0.Target.Name = "library/test" + layerPush0.Target.Length = 2 + layerPush0.Target.MediaType = layerMediaType + layerPush0.Target.Repository = "library/test" layerPush0.Target.URL = "http://example.com/v2/library/test/manifests/latest" var layerPush1 Event layerPush1 = prototype layerPush1.ID = "asdf-asdf-asdf-asdf-2" layerPush1.Target.Digest = "tarsum.v2+sha256:0123456789abcdef2" - layerPush1.Target.Type = EventTargetTypeBlob - layerPush1.Target.Name = "library/test" + layerPush1.Target.Length = 3 + layerPush1.Target.MediaType = layerMediaType + layerPush1.Target.Repository = "library/test" layerPush1.Target.URL = "http://example.com/v2/library/test/manifests/latest" var envelope Envelope diff --git a/notifications/http_test.go b/notifications/http_test.go index c2cfbc02..e0276ccd 100644 --- a/notifications/http_test.go +++ b/notifications/http_test.go @@ -9,6 +9,8 @@ import ( "reflect" "strconv" "testing" + + "github.com/docker/distribution/manifest" ) // TestHTTPSink mocks out an http endpoint and notifies it under a couple of @@ -73,14 +75,14 @@ func TestHTTPSink(t *testing.T) { { statusCode: http.StatusOK, events: []Event{ - createTestEvent("push", "library/test", "manifest")}, + createTestEvent("push", "library/test", manifest.ManifestMediaType)}, }, { statusCode: http.StatusOK, events: []Event{ - createTestEvent("push", "library/test", "manifest"), - createTestEvent("push", "library/test", "layer"), - createTestEvent("push", "library/test", "layer"), + createTestEvent("push", "library/test", manifest.ManifestMediaType), + createTestEvent("push", "library/test", layerMediaType), + createTestEvent("push", "library/test", layerMediaType), }, }, { @@ -148,8 +150,8 @@ func TestHTTPSink(t *testing.T) { func createTestEvent(action, repo, typ string) Event { event := createEvent(action) - event.Target.Type = typ - event.Target.Name = repo + event.Target.MediaType = typ + event.Target.Repository = repo return *event } diff --git a/registry.go b/registry.go index ef63a24a..e4eebe5f 100644 --- a/registry.go +++ b/registry.go @@ -95,10 +95,12 @@ type Layer interface { io.ReadSeeker io.Closer - // Digest returns the unique digest of the blob, which is the tarsum for - // layers. + // Digest returns the unique digest of the blob. Digest() digest.Digest + // Length returns the length in bytes of the blob. + Length() int64 + // CreatedAt returns the time this layer was created. CreatedAt() time.Time } @@ -134,3 +136,24 @@ type SignatureService interface { // Put stores the signature for the provided digest. Put(dgst digest.Digest, signatures ...[]byte) error } + +// Descriptor describes targeted content. Used in conjunction with a blob +// store, a descriptor can be used to fetch, store and target any kind of +// blob. The struct also describes the wire protocol format. Fields should +// only be added but never changed. +type Descriptor struct { + // MediaType describe the type of the content. All text based formats are + // encoded as utf-8. + MediaType string `json:"mediaType,omitempty"` + + // Length in bytes of content. + Length int64 `json:"length,omitempty"` + + // Digest uniquely identifies the content. A byte stream can be verified + // against against this digest. + Digest digest.Digest `json:"digest,omitempty"` + + // NOTE: Before adding a field here, please ensure that all + // other options have been exhausted. Much of the type relationships + // depend on the simplicity of this type. +} diff --git a/registry/storage/layerreader.go b/registry/storage/layerreader.go index 2d8e588d..1de98e50 100644 --- a/registry/storage/layerreader.go +++ b/registry/storage/layerreader.go @@ -21,6 +21,10 @@ func (lrs *layerReader) Digest() digest.Digest { return lrs.digest } +func (lrs *layerReader) Length() int64 { + return lrs.size +} + func (lrs *layerReader) CreatedAt() time.Time { return lrs.modtime }