Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC-0008] Custom Event Metadata from Annotations #848

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apis/event/v1beta1/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 13 additions & 1 deletion runtime/events/recorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import (
"encoding/json"
"errors"
"fmt"
"maps"
"net/http"
"net/url"
"os"
"strings"
"time"

"github.com/go-logr/logr"
Expand Down Expand Up @@ -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{}) {

Expand All @@ -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)
Copy link
Member Author

@matheuscscp matheuscscp Dec 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function was not modifying the metadata before so, now that it does, in order to avoid a side-effect it is safer to make a shallow copy of the map here (which in the case of a map[string]string is the same as a deep copy).

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)

Expand Down
113 changes: 78 additions & 35 deletions runtime/events/recorder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,48 +25,91 @@ import (

"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"

eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
)

func TestEventRecorder_AnnotatedEventf(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.ConfigMap{}
obj.Namespace = "gitops-system"
obj.Name = "webapp"

meta := map[string]string{
"test": "true",
for _, tt := range []struct {
name string
object runtime.Object
expectedMetadata map[string]string
}{
{
name: "event with ConfigMap",
object: &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",
},
},
},
expectedMetadata: map[string]string{
"test": "true",
"event.toolkit.fluxcd.io/deploymentID": "e076e315-5a48-41c3-81c8-8d8bdee7d74d",
"event.toolkit.fluxcd.io/image": "ghcr.io/stefanprodan/podinfo:6.5.0",
},
},
{
name: "event with ObjectReference for ConfigMap (does not panic with runtime.Object without annotations)",
object: &corev1.ObjectReference{
Name: "webapp",
Namespace: "gitops-system",
Kind: "ConfigMap",
},
expectedMetadata: map[string]string{
"test": "true",
},
},
} {
t.Run(tt.name, func(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, "sync", payload.Reason)
require.Equal(t, "sync object", payload.Message)

for k, v := range tt.expectedMetadata {
require.Equal(t, v, payload.Metadata[k])
}
}))
defer ts.Close()

eventRecorder, err := NewRecorder(env, ctrl.Log, ts.URL, "test-controller")
require.NoError(t, err)

obj := tt.object

meta := map[string]string{
"test": "true",
}

const msg = "sync object"

eventRecorder.AnnotatedEventf(obj, meta, corev1.EventTypeNormal, "sync", "%s", msg)
require.Equal(t, 2, requestCount)

// When a trace event is sent, it's dropped, no new request.
eventRecorder.AnnotatedEventf(obj, meta, eventv1.EventTypeTrace, "sync", "%s", msg)
require.Equal(t, 2, requestCount)
})
}

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_Retry(t *testing.T) {
Expand Down
Loading