Skip to content

Commit 1e81b7d

Browse files
committed
feat(events): add actor and request metadata to webhook events
Signed-off-by: cainydev <wajo432@gmail.com>
1 parent 8bec9b3 commit 1e81b7d

28 files changed

Lines changed: 1131 additions & 412 deletions

pkg/api/routes.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
"zotregistry.dev/zot/v2/pkg/debug/pprof"
4343
debug "zotregistry.dev/zot/v2/pkg/debug/swagger"
4444
ext "zotregistry.dev/zot/v2/pkg/extensions"
45+
"zotregistry.dev/zot/v2/pkg/extensions/events"
4546
"zotregistry.dev/zot/v2/pkg/log"
4647
"zotregistry.dev/zot/v2/pkg/meta"
4748
mTypes "zotregistry.dev/zot/v2/pkg/meta/types"
@@ -778,7 +779,9 @@ func (rh *RouteHandler) UpdateManifest(response http.ResponseWriter, request *ht
778779
return
779780
}
780781

781-
digest, subjectDigest, err := imgStore.PutImageManifest(name, reference, mediaType, body, digestQueryTags)
782+
ctx := events.WithEventContext(request.Context(), eventContextFromRequest(request))
783+
784+
digest, subjectDigest, err := imgStore.PutImageManifest(ctx, name, reference, mediaType, body, digestQueryTags)
782785
if err != nil {
783786
details := zerr.GetDetails(err)
784787
if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
@@ -805,7 +808,7 @@ func (rh *RouteHandler) UpdateManifest(response http.ResponseWriter, request *ht
805808
// could be syscall.EMFILE (Err:0x18 too many opened files), etc
806809
rh.c.Log.Error().Err(err).Msg("unexpected error, performing cleanup")
807810

808-
if err = imgStore.DeleteImageManifest(name, reference, false); err != nil {
811+
if err = imgStore.DeleteImageManifest(ctx, name, reference, false); err != nil {
809812
// deletion of image manifest is important, but not critical for image repo consistency
810813
// in the worst scenario a partial manifest file written to disk will not affect the repo because
811814
// the new manifest was not added to "index.json" file (it is possible that GC will take care of it)
@@ -950,7 +953,9 @@ func (rh *RouteHandler) DeleteManifest(response http.ResponseWriter, request *ht
950953
return
951954
}
952955

953-
err = imgStore.DeleteImageManifest(name, reference, detectCollision)
956+
ctx := events.WithEventContext(request.Context(), eventContextFromRequest(request))
957+
958+
err = imgStore.DeleteImageManifest(ctx, name, reference, detectCollision)
954959
if err != nil { //nolint: dupl
955960
details := zerr.GetDetails(err)
956961
if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
@@ -2543,3 +2548,23 @@ func isSyncOnDemandEnabled(ctlr *Controller) bool {
25432548

25442549
return false
25452550
}
2551+
2552+
func eventContextFromRequest(r *http.Request) *events.EventContext {
2553+
ectx := &events.EventContext{
2554+
Request: &events.RequestInfo{
2555+
Addr: r.RemoteAddr,
2556+
Method: r.Method,
2557+
UserAgent: r.UserAgent(),
2558+
},
2559+
}
2560+
2561+
userAc, err := reqCtx.UserAcFromContext(r.Context())
2562+
if err == nil && userAc != nil {
2563+
username := userAc.GetUsername()
2564+
if username != "" {
2565+
ectx.Actor = &events.ActorInfo{Name: username}
2566+
}
2567+
}
2568+
2569+
return ectx
2570+
}

pkg/api/routes_test.go

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -320,9 +320,9 @@ func TestRoutes(t *testing.T) {
320320
"reference": "reference",
321321
},
322322
&mocks.MockedImageStore{
323-
PutImageManifestFn: func(repo, reference, mediaType string, body []byte, _ []string) (godigest.Digest,
324-
godigest.Digest, error,
325-
) {
323+
PutImageManifestFn: func(ctx context.Context, repo, reference, mediaType string,
324+
body []byte, extraTags []string,
325+
) (godigest.Digest, godigest.Digest, error) {
326326
return "", "", zerr.ErrRepoNotFound
327327
},
328328
})
@@ -335,9 +335,9 @@ func TestRoutes(t *testing.T) {
335335
},
336336

337337
&mocks.MockedImageStore{
338-
PutImageManifestFn: func(repo, reference, mediaType string, body []byte, _ []string) (godigest.Digest,
339-
godigest.Digest, error,
340-
) {
338+
PutImageManifestFn: func(ctx context.Context, repo, reference, mediaType string,
339+
body []byte, extraTags []string,
340+
) (godigest.Digest, godigest.Digest, error) {
341341
return "", "", zerr.ErrManifestNotFound
342342
},
343343
})
@@ -349,9 +349,9 @@ func TestRoutes(t *testing.T) {
349349
"reference": "reference",
350350
},
351351
&mocks.MockedImageStore{
352-
PutImageManifestFn: func(repo, reference, mediaType string, body []byte, _ []string) (godigest.Digest,
353-
godigest.Digest, error,
354-
) {
352+
PutImageManifestFn: func(ctx context.Context, repo, reference, mediaType string,
353+
body []byte, extraTags []string,
354+
) (godigest.Digest, godigest.Digest, error) {
355355
return "", "", zerr.ErrBadManifest
356356
},
357357
})
@@ -363,9 +363,9 @@ func TestRoutes(t *testing.T) {
363363
"reference": "reference",
364364
},
365365
&mocks.MockedImageStore{
366-
PutImageManifestFn: func(repo, reference, mediaType string, body []byte, _ []string) (godigest.Digest,
367-
godigest.Digest, error,
368-
) {
366+
PutImageManifestFn: func(ctx context.Context, repo, reference, mediaType string,
367+
body []byte, extraTags []string,
368+
) (godigest.Digest, godigest.Digest, error) {
369369
return "", "", zerr.ErrBlobNotFound
370370
},
371371
})
@@ -378,9 +378,9 @@ func TestRoutes(t *testing.T) {
378378
"reference": "reference",
379379
},
380380
&mocks.MockedImageStore{
381-
PutImageManifestFn: func(repo, reference, mediaType string, body []byte, _ []string) (godigest.Digest,
382-
godigest.Digest, error,
383-
) {
381+
PutImageManifestFn: func(ctx context.Context, repo, reference, mediaType string,
382+
body []byte, extraTags []string,
383+
) (godigest.Digest, godigest.Digest, error) {
384384
return "", "", zerr.ErrRepoBadVersion
385385
},
386386
})
@@ -412,9 +412,9 @@ func TestRoutes(t *testing.T) {
412412
digestRef := manifestDigest.String()
413413

414414
ism := &mocks.MockedImageStore{
415-
PutImageManifestFn: func(repo, reference, mediaType string, body []byte, extraTags []string) (
416-
godigest.Digest, godigest.Digest, error,
417-
) {
415+
PutImageManifestFn: func(ctx context.Context, repo, reference, mediaType string,
416+
body []byte, extraTags []string,
417+
) (godigest.Digest, godigest.Digest, error) {
418418
So(extraTags, ShouldResemble, []string{"meta-a", "meta-b"})
419419
So(string(body), ShouldEqual, string(mcontent))
420420

@@ -509,7 +509,7 @@ func TestRoutes(t *testing.T) {
509509
"reference": "reference",
510510
},
511511
&mocks.MockedImageStore{
512-
DeleteImageManifestFn: func(repo, reference string, detectCollision bool) error {
512+
DeleteImageManifestFn: func(ctx context.Context, repo, reference string, detectCollision bool) error {
513513
return zerr.ErrRepoNotFound
514514
},
515515
},
@@ -524,7 +524,7 @@ func TestRoutes(t *testing.T) {
524524
"reference": "reference",
525525
},
526526
&mocks.MockedImageStore{
527-
DeleteImageManifestFn: func(repo, reference string, detectCollision bool) error {
527+
DeleteImageManifestFn: func(ctx context.Context, repo, reference string, detectCollision bool) error {
528528
return zerr.ErrManifestNotFound
529529
},
530530
},
@@ -539,7 +539,7 @@ func TestRoutes(t *testing.T) {
539539
"reference": "reference",
540540
},
541541
&mocks.MockedImageStore{
542-
DeleteImageManifestFn: func(repo, reference string, detectCollision bool) error {
542+
DeleteImageManifestFn: func(ctx context.Context, repo, reference string, detectCollision bool) error {
543543
return ErrUnexpectedError
544544
},
545545
},
@@ -554,7 +554,7 @@ func TestRoutes(t *testing.T) {
554554
"reference": "reference",
555555
},
556556
&mocks.MockedImageStore{
557-
DeleteImageManifestFn: func(repo, reference string, detectCollision bool) error {
557+
DeleteImageManifestFn: func(ctx context.Context, repo, reference string, detectCollision bool) error {
558558
return zerr.ErrBadManifest
559559
},
560560
},

pkg/extensions/events/builder.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,22 @@ func (b *eventBuilder) WithEventType(eventType EventType) *eventBuilder {
3434
return b
3535
}
3636

37+
func (b *eventBuilder) WithEventContext(ectx *EventContext) *eventBuilder {
38+
if ectx == nil {
39+
return b
40+
}
41+
42+
if ectx.Actor != nil {
43+
b.data["actor"] = ectx.Actor
44+
}
45+
46+
if ectx.Request != nil {
47+
b.data["request"] = ectx.Request
48+
}
49+
50+
return b
51+
}
52+
3753
func (b *eventBuilder) Build() (*cloudevents.Event, error) {
3854
if b.eventType == "" {
3955
return nil, zerr.ErrEventTypeEmpty

pkg/extensions/events/common.go

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package events
22

33
import (
4+
"context"
45
"time"
56
)
67

@@ -22,11 +23,43 @@ func (e EventType) String() string {
2223
return string(e)
2324
}
2425

26+
// ActorInfo describes who triggered an event.
27+
type ActorInfo struct {
28+
Name string `json:"name"`
29+
}
30+
31+
// RequestInfo describes the HTTP request that triggered an event.
32+
type RequestInfo struct {
33+
Addr string `json:"addr"`
34+
Method string `json:"method"`
35+
UserAgent string `json:"useragent"`
36+
}
37+
38+
// EventContext carries actor and request metadata for events.
39+
type EventContext struct {
40+
Actor *ActorInfo `json:"actor,omitempty"`
41+
Request *RequestInfo `json:"request,omitempty"`
42+
}
43+
44+
type eventContextKey struct{}
45+
46+
// WithEventContext attaches an EventContext to a context.Context.
47+
func WithEventContext(ctx context.Context, ec *EventContext) context.Context {
48+
return context.WithValue(ctx, eventContextKey{}, ec)
49+
}
50+
51+
// EventContextFromContext retrieves the EventContext from a context.Context.
52+
func EventContextFromContext(ctx context.Context) *EventContext {
53+
ec, _ := ctx.Value(eventContextKey{}).(*EventContext)
54+
55+
return ec
56+
}
57+
2558
type Recorder interface {
2659
Close()
2760

28-
RepositoryCreated(name string)
29-
ImageUpdated(name, reference, digest, mediaType, manifest string)
30-
ImageDeleted(name, reference, digest, mediaType string)
31-
ImageLintFailed(name, reference, digest, mediaType, manifest string)
61+
RepositoryCreated(name string, ectx *EventContext)
62+
ImageUpdated(name, reference, digest, mediaType, manifest string, ectx *EventContext)
63+
ImageDeleted(name, reference, digest, mediaType string, ectx *EventContext)
64+
ImageLintFailed(name, reference, digest, mediaType, manifest string, ectx *EventContext)
3265
}

pkg/extensions/events/events.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,11 @@ func (r eventRecorder) publish(event *cloudevents.Event) {
5252
}()
5353
}
5454

55-
func (r eventRecorder) RepositoryCreated(name string) {
55+
func (r eventRecorder) RepositoryCreated(name string, ectx *EventContext) {
5656
event, err := newEventBuilder().
5757
WithEventType(RepositoryCreatedEventType).
5858
WithDataField("name", name).
59+
WithEventContext(ectx).
5960
Build()
6061
if err != nil {
6162
r.log.Warn().Err(err).Msg("failed to create event")
@@ -66,14 +67,15 @@ func (r eventRecorder) RepositoryCreated(name string) {
6667
r.publish(event)
6768
}
6869

69-
func (r eventRecorder) ImageUpdated(name, reference, digest, mediaType, manifest string) {
70+
func (r eventRecorder) ImageUpdated(name, reference, digest, mediaType, manifest string, ectx *EventContext) {
7071
event, err := newEventBuilder().
7172
WithEventType(ImageUpdatedEventType).
7273
WithDataField("name", name).
7374
WithDataField("reference", reference).
7475
WithDataField("digest", digest).
7576
WithDataField("mediaType", mediaType).
7677
WithDataField("manifest", manifest).
78+
WithEventContext(ectx).
7779
Build()
7880
if err != nil {
7981
r.log.Warn().Err(err).Msg("failed to create event")
@@ -84,13 +86,14 @@ func (r eventRecorder) ImageUpdated(name, reference, digest, mediaType, manifest
8486
r.publish(event)
8587
}
8688

87-
func (r eventRecorder) ImageDeleted(name, reference, digest, mediaType string) {
89+
func (r eventRecorder) ImageDeleted(name, reference, digest, mediaType string, ectx *EventContext) {
8890
event, err := newEventBuilder().
8991
WithEventType(ImageDeletedEventType).
9092
WithDataField("name", name).
9193
WithDataField("reference", reference).
9294
WithDataField("digest", digest).
9395
WithDataField("mediaType", mediaType).
96+
WithEventContext(ectx).
9497
Build()
9598
if err != nil {
9699
r.log.Warn().Err(err).Msg("failed to create event")
@@ -101,14 +104,15 @@ func (r eventRecorder) ImageDeleted(name, reference, digest, mediaType string) {
101104
r.publish(event)
102105
}
103106

104-
func (r eventRecorder) ImageLintFailed(name, reference, digest, mediaType, manifest string) {
107+
func (r eventRecorder) ImageLintFailed(name, reference, digest, mediaType, manifest string, ectx *EventContext) {
105108
event, err := newEventBuilder().
106109
WithEventType(ImageLintFailedEventType).
107110
WithDataField("name", name).
108111
WithDataField("reference", reference).
109112
WithDataField("digest", digest).
110113
WithDataField("mediaType", mediaType).
111114
WithDataField("manifest", manifest).
115+
WithEventContext(ectx).
112116
Build()
113117
if err != nil {
114118
r.log.Warn().Err(err).Msg("failed to create event")

0 commit comments

Comments
 (0)