diff --git a/registry/app.go b/registry/app.go index e7c96b74..53759a1e 100644 --- a/registry/app.go +++ b/registry/app.go @@ -336,13 +336,11 @@ func (app *App) eventBridge(ctx *Context, r *http.Request) notifications.Listene // auth system. Would prefer to do this during logging refactor and // addition of user and google context type. actor := notifications.ActorRecord{ - Name: "--todo--", - Addr: r.RemoteAddr, - Host: r.Host, - RequestID: ctx.RequestID, + Name: "--todo--", } + request := notifications.NewRequestRecord(ctx.RequestID, r) - return notifications.NewBridge(ctx.urlBuilder, app.events.source, actor, app.events.sink) + return notifications.NewBridge(ctx.urlBuilder, app.events.source, actor, request, app.events.sink) } // apiBase implements a simple yes-man for doing overall checks against the diff --git a/storage/notifications/bridge.go b/storage/notifications/bridge.go index 28326cce..d6f41ba0 100644 --- a/storage/notifications/bridge.go +++ b/storage/notifications/bridge.go @@ -1,6 +1,7 @@ package notifications import ( + "net/http" "time" "github.com/docker/distribution/manifest" @@ -11,10 +12,11 @@ import ( ) type bridge struct { - ub URLBuilder - actor ActorRecord - source SourceRecord - sink Sink + ub URLBuilder + actor ActorRecord + source SourceRecord + request RequestRecord + sink Sink } var _ Listener = &bridge{} @@ -28,12 +30,26 @@ type URLBuilder interface { // NewBridge returns a notification listener that writes records to sink, // using the actor and source. Any urls populated in the events created by // this bridge will be created using the URLBuilder. -func NewBridge(ub URLBuilder, source SourceRecord, actor ActorRecord, sink Sink) Listener { +// TODO(stevvooe): Update this to simply take a context.Context object. +func NewBridge(ub URLBuilder, source SourceRecord, actor ActorRecord, request RequestRecord, sink Sink) Listener { return &bridge{ - ub: ub, - actor: actor, - source: source, - sink: sink, + ub: ub, + actor: actor, + source: source, + request: request, + sink: sink, + } +} + +// NewRequestRecord builds a RequestRecord for use in NewBridge from an +// http.Request, associating it with a request id. +func NewRequestRecord(id string, r *http.Request) RequestRecord { + return RequestRecord{ + ID: id, + Addr: r.RemoteAddr, + Host: r.Host, + Method: r.Method, + UserAgent: r.UserAgent(), } } @@ -125,6 +141,7 @@ func (b *bridge) createEvent(action string) *Event { event := createEvent(action) event.Source = b.source event.Actor = b.actor + event.Request = b.request return event } diff --git a/storage/notifications/event.go b/storage/notifications/event.go index fb2094d7..c23766fa 100644 --- a/storage/notifications/event.go +++ b/storage/notifications/event.go @@ -67,6 +67,9 @@ type Event struct { URL string `json:"url,omitempty"` } `json:"target,omitempty"` + // Request covers the request that generated the event. + Request RequestRecord `json:"request,omitempty"` + // Actor specifies the agent that initiated the event. For most // situations, this could be from the authorizaton context of the request. Actor ActorRecord `json:"actor,omitempty"` @@ -86,18 +89,6 @@ type ActorRecord struct { // request context that generated the event. Name string `json:"name,omitempty"` - // Addr contains the ip or hostname and possibly port of the client - // connection that initiated the event. - Addr string `json:"addr,omitempty"` - - // Host is the externally accessible host name of the registry instance, - // as specified by the http host header on incoming requests. - Host string `json:"host,omitempty"` - - // RequestID uniquely identifies the registry request that generated the - // event. - RequestID string `json:"requestID,omitempty"` - // TODO(stevvooe): Look into setting a session cookie to get this // without docker daemon. // SessionID @@ -107,6 +98,27 @@ type ActorRecord struct { // Command } +// RequestRecord covers the request that generated the event. +type RequestRecord struct { + // ID uniquely identifies the request that initiated the event. + ID string `json:"id"` + + // Addr contains the ip or hostname and possibly port of the client + // connection that initiated the event. This is the RemoteAddr from + // the standard http request. + Addr string `json:"addr,omitempty"` + + // Host is the externally accessible host name of the registry instance, + // as specified by the http host header on incoming requests. + Host string `json:"host,omitempty"` + + // Method has the request method that generated the event. + Method string `json:"method"` + + // UserAgent contains the user agent header of the request. + UserAgent string `json:"useragent"` +} + // SourceRecord identifies the registry node that generated the event. Put // differently, while the actor "initiates" the event, the source "generates" // it. diff --git a/storage/notifications/event_test.go b/storage/notifications/event_test.go index 7bb9fa01..cc2180ac 100644 --- a/storage/notifications/event_test.go +++ b/storage/notifications/event_test.go @@ -25,11 +25,15 @@ func TestEventEnvelopeJSONFormat(t *testing.T) { "tag": "latest", "url": "http://example.com/v2/library/test/manifests/latest" }, - "actor": { - "name": "test-actor", + "request": { + "id": "asdfasdf", "addr": "client.local", "host": "registrycluster.local", - "requestID": "asdfasdf" + "method": "PUT", + "useragent": "test/0.1" + }, + "actor": { + "name": "test-actor" }, "source": { "addr": "hostname.local:port" @@ -45,11 +49,15 @@ func TestEventEnvelopeJSONFormat(t *testing.T) { "digest": "tarsum.v2+sha256:0123456789abcdef1", "url": "http://example.com/v2/library/test/manifests/latest" }, - "actor": { - "name": "test-actor", + "request": { + "id": "asdfasdf", "addr": "client.local", "host": "registrycluster.local", - "requestID": "asdfasdf" + "method": "PUT", + "useragent": "test/0.1" + }, + "actor": { + "name": "test-actor" }, "source": { "addr": "hostname.local:port" @@ -65,11 +73,15 @@ func TestEventEnvelopeJSONFormat(t *testing.T) { "digest": "tarsum.v2+sha256:0123456789abcdef2", "url": "http://example.com/v2/library/test/manifests/latest" }, - "actor": { - "name": "test-actor", + "request": { + "id": "asdfasdf", "addr": "client.local", "host": "registrycluster.local", - "requestID": "asdfasdf" + "method": "PUT", + "useragent": "test/0.1" + }, + "actor": { + "name": "test-actor" }, "source": { "addr": "hostname.local:port" @@ -87,10 +99,12 @@ func TestEventEnvelopeJSONFormat(t *testing.T) { var prototype Event prototype.Action = "push" prototype.Timestamp = tm - prototype.Actor.Addr = "client.local" prototype.Actor.Name = "test-actor" - prototype.Actor.RequestID = "asdfasdf" - prototype.Actor.Host = "registrycluster.local" + prototype.Request.ID = "asdfasdf" + prototype.Request.Addr = "client.local" + prototype.Request.Host = "registrycluster.local" + prototype.Request.Method = "PUT" + prototype.Request.UserAgent = "test/0.1" prototype.Source.Addr = "hostname.local:port" var manifestPush Event