From d13fb2de1892c819fbff71dce5166a1926c471d2 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Mon, 23 Dec 2024 15:33:42 +0000 Subject: [PATCH] [RFC-0008] Custom Event Metadata from Annotations Signed-off-by: Matheus Pimenta --- apis/event/v1beta1/event.go | 3 ++ runtime/events/recorder.go | 14 +++++++- runtime/events/recorder_test.go | 57 +++++++++++++++++++++++++++++++-- 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/apis/event/v1beta1/event.go b/apis/event/v1beta1/event.go index b6664c20..61621e73 100644 --- a/apis/event/v1beta1/event.go +++ b/apis/event/v1beta1/event.go @@ -21,6 +21,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// Group is the API Group for the Event API. +const Group = "event.toolkit.fluxcd.io" + // These constants define valid event severity values. const ( // EventSeverityTrace represents a trace event, usually diff --git a/runtime/events/recorder.go b/runtime/events/recorder.go index 36804037..7383b9ae 100644 --- a/runtime/events/recorder.go +++ b/runtime/events/recorder.go @@ -21,9 +21,11 @@ import ( "encoding/json" "errors" "fmt" + "maps" "net/http" "net/url" "os" + "strings" "time" "github.com/go-logr/logr" @@ -145,7 +147,7 @@ func (r *Recorder) Eventf(object runtime.Object, eventtype, reason, messageFmt s // It also logs the event if debug logs are enabled in the logger. func (r *Recorder) AnnotatedEventf( object runtime.Object, - annotations map[string]string, + inputAnnotations map[string]string, eventtype, reason string, messageFmt string, args ...interface{}) { @@ -154,6 +156,16 @@ func (r *Recorder) AnnotatedEventf( r.Log.Error(err, "failed to get object reference") } + // Add object annotations to the annotations. + annotations := maps.Clone(inputAnnotations) + if annotatedObject, ok := object.(interface{ GetAnnotations() map[string]string }); ok { + for k, v := range annotatedObject.GetAnnotations() { + if strings.HasPrefix(k, eventv1.Group+"/") { + annotations[k] = v + } + } + } + // Add object info in the logger. log := r.Log.WithValues("name", ref.Name, "namespace", ref.Namespace, "reconciler kind", ref.Kind) diff --git a/runtime/events/recorder_test.go b/runtime/events/recorder_test.go index 58246298..90150231 100644 --- a/runtime/events/recorder_test.go +++ b/runtime/events/recorder_test.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" @@ -45,6 +46,8 @@ func TestEventRecorder_AnnotatedEventf(t *testing.T) { require.Equal(t, "webapp", payload.InvolvedObject.Name) require.Equal(t, "gitops-system", payload.InvolvedObject.Namespace) require.Equal(t, "true", payload.Metadata["test"]) + require.Equal(t, "e076e315-5a48-41c3-81c8-8d8bdee7d74d", payload.Metadata["event.toolkit.fluxcd.io/deploymentID"]) + require.Equal(t, "ghcr.io/stefanprodan/podinfo:6.5.0", payload.Metadata["event.toolkit.fluxcd.io/image"]) require.Equal(t, "sync", payload.Reason) })) @@ -53,9 +56,57 @@ func TestEventRecorder_AnnotatedEventf(t *testing.T) { eventRecorder, err := NewRecorder(env, ctrl.Log, ts.URL, "test-controller") require.NoError(t, err) - obj := &corev1.ConfigMap{} - obj.Namespace = "gitops-system" - obj.Name = "webapp" + obj := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "webapp", + Namespace: "gitops-system", + Annotations: map[string]string{ + "event.toolkit.fluxcd.io/deploymentID": "e076e315-5a48-41c3-81c8-8d8bdee7d74d", + "event.toolkit.fluxcd.io/image": "ghcr.io/stefanprodan/podinfo:6.5.0", + }, + }, + } + + meta := map[string]string{ + "test": "true", + } + + eventRecorder.AnnotatedEventf(obj, meta, corev1.EventTypeNormal, "sync", "sync %s", obj.Name) + require.Equal(t, 2, requestCount) + + // When a trace event is sent, it's dropped, no new request. + eventRecorder.AnnotatedEventf(obj, meta, eventv1.EventTypeTrace, "sync", "sync %s", obj.Name) + require.Equal(t, 2, requestCount) +} + +func TestEventRecorder_AnnotatedEventf_DoesNotPanicWithObjectWithoutAnnotations(t *testing.T) { + requestCount := 0 + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requestCount++ + b, err := io.ReadAll(r.Body) + require.NoError(t, err) + + var payload eventv1.Event + err = json.Unmarshal(b, &payload) + require.NoError(t, err) + + require.Equal(t, "ConfigMap", payload.InvolvedObject.Kind) + require.Equal(t, "webapp", payload.InvolvedObject.Name) + require.Equal(t, "gitops-system", payload.InvolvedObject.Namespace) + require.Equal(t, "true", payload.Metadata["test"]) + require.Equal(t, "sync", payload.Reason) + + })) + defer ts.Close() + + eventRecorder, err := NewRecorder(env, ctrl.Log, ts.URL, "test-controller") + require.NoError(t, err) + + obj := &corev1.ObjectReference{ + Name: "webapp", + Namespace: "gitops-system", + Kind: "ConfigMap", + } meta := map[string]string{ "test": "true",