From 3d9bcc8dc9c51c34d390f607518ecad16d93d55a Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Thu, 19 Dec 2024 17:33:50 +0200 Subject: [PATCH 1/3] Allow notification-controller to receive `FluxInstance` events Signed-off-by: Stefan Prodan --- internal/builder/build.go | 2 +- internal/builder/images.go | 2 +- internal/builder/profiles.go | 47 +++++++++++++++++++ internal/builder/templates.go | 2 +- .../controller/fluxinstance_controller.go | 4 ++ .../fluxinstance_controller_test.go | 11 +++++ 6 files changed, 65 insertions(+), 3 deletions(-) diff --git a/internal/builder/build.go b/internal/builder/build.go index 6ac0add..3448f44 100644 --- a/internal/builder/build.go +++ b/internal/builder/build.go @@ -58,7 +58,7 @@ func Build(srcDir, tmpDir string, options Options) (*Result, error) { } func generate(base string, options Options) error { - if containsItemString(options.Components, options.NotificationController) { + if ContainElementString(options.Components, options.NotificationController) { options.EventsAddr = fmt.Sprintf("http://%s.%s.svc.%s./", options.NotificationController, options.Namespace, options.ClusterDomain) } diff --git a/internal/builder/images.go b/internal/builder/images.go index 5d7b924..fd43726 100644 --- a/internal/builder/images.go +++ b/internal/builder/images.go @@ -95,7 +95,7 @@ func ExtractComponentImagesWithDigest(srcDir string, opts Options) (images []Com for _, img := range kc.Images { name := img.Name component := name[strings.LastIndex(name, "/")+1:] - if containsItemString(opts.Components, component) { + if ContainElementString(opts.Components, component) { images = append(images, ComponentImage{ Name: component, Repository: fmt.Sprintf("%s/%s", registry, component), diff --git a/internal/builder/profiles.go b/internal/builder/profiles.go index 45a72ff..3b8dcd7 100644 --- a/internal/builder/profiles.go +++ b/internal/builder/profiles.go @@ -61,3 +61,50 @@ func GetMultitenantProfile(defaultSA string) string { return fmt.Sprintf(profileMultitenant, defaultSA) } + +const tmpNotificationPatch = ` +- target: + kind: CustomResourceDefinition + name: alerts.notification.toolkit.fluxcd.io + patch: |- + - op: add + path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/eventSources/items/properties/kind/enum/- + value: FluxInstance + - op: add + path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/eventSources/items/properties/kind/enum/- + value: FluxInstance + - op: add + path: /spec/versions/2/schema/openAPIV3Schema/properties/spec/properties/eventSources/items/properties/kind/enum/- + value: FluxInstance +- target: + kind: CustomResourceDefinition + name: receivers.notification.toolkit.fluxcd.io + patch: |- + - op: add + path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/resources/items/properties/kind/enum/- + value: FluxInstance + - op: add + path: /spec/versions/1/schema/openAPIV3Schema/properties/spec/properties/resources/items/properties/kind/enum/- + value: FluxInstance + - op: add + path: /spec/versions/2/schema/openAPIV3Schema/properties/spec/properties/resources/items/properties/kind/enum/- + value: FluxInstance +- target: + kind: ClusterRole + name: crd-controller-%s + patch: |- + - op: add + path: /rules/- + value: + apiGroups: [ 'fluxcd.controlplane.io' ] + resources: [ '*' ] + verbs: [ '*' ] +` + +func GetNotificationPatch(namespace string) string { + if namespace == "" { + namespace = "flux-system" + } + + return fmt.Sprintf(tmpNotificationPatch, namespace) +} diff --git a/internal/builder/templates.go b/internal/builder/templates.go index 3e1fd5f..1a09282 100644 --- a/internal/builder/templates.go +++ b/internal/builder/templates.go @@ -383,7 +383,7 @@ func execTemplate(obj interface{}, tmpl, filename string) (err error) { return file.Sync() } -func containsItemString(s []string, e string) bool { +func ContainElementString(s []string, e string) bool { for _, a := range s { if a == e { return true diff --git a/internal/controller/fluxinstance_controller.go b/internal/controller/fluxinstance_controller.go index fc11904..4b04bb4 100644 --- a/internal/controller/fluxinstance_controller.go +++ b/internal/controller/fluxinstance_controller.go @@ -307,6 +307,10 @@ func (r *FluxInstanceReconciler) build(ctx context.Context, options.Patches += builder.GetMultitenantProfile(obj.GetCluster().TenantDefaultServiceAccount) } + if builder.ContainElementString(options.Components, options.NotificationController) { + options.Patches += builder.GetNotificationPatch(options.Namespace) + } + if obj.Spec.Sharding != nil { options.ShardingKey = obj.Spec.Sharding.Key options.Shards = obj.Spec.Sharding.Shards diff --git a/internal/controller/fluxinstance_controller_test.go b/internal/controller/fluxinstance_controller_test.go index d029434..617d274 100644 --- a/internal/controller/fluxinstance_controller_test.go +++ b/internal/controller/fluxinstance_controller_test.go @@ -528,6 +528,7 @@ func TestFluxInstanceReconciler_Profiles(t *testing.T) { g := NewWithT(t) reconciler := getFluxInstanceReconciler() spec := getDefaultFluxSpec() + spec.Distribution.Version = "v2.4.x" spec.Cluster = &fluxcdv1.Cluster{ Type: "openshift", Multitenant: true, @@ -606,6 +607,16 @@ func TestFluxInstanceReconciler_Profiles(t *testing.T) { )) } + // Check if the notification CRD was patched. + crd := &unstructured.Unstructured{} + crd.SetAPIVersion("apiextensions.k8s.io/v1") + crd.SetKind("CustomResourceDefinition") + err = testClient.Get(ctx, types.NamespacedName{Name: "alerts.notification.toolkit.fluxcd.io"}, crd) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect( + crd.Object["spec"].(map[string]interface{})["versions"].([]interface{})[2].(map[string]interface{})["schema"].(map[string]interface{})["openAPIV3Schema"].(map[string]interface{})["properties"].(map[string]interface{})["spec"].(map[string]interface{})["properties"].(map[string]interface{})["eventSources"].(map[string]interface{})["items"].(map[string]interface{})["properties"].(map[string]interface{})["kind"].(map[string]interface{})["enum"]). + To(ContainElement("FluxInstance")) + // Uninstall the instance. err = testClient.Delete(ctx, obj) g.Expect(err).ToNot(HaveOccurred()) From b0cee382e56cd6a81fdac0e8496d8add9b482692 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Sat, 21 Dec 2024 11:44:52 +0200 Subject: [PATCH 2/3] Issue alerts for install, upgrade and reconcile errors Signed-off-by: Stefan Prodan --- go.mod | 5 +- go.sum | 18 +++++++- .../controller/fluxinstance_controller.go | 46 +++++++++++++++++-- .../fluxinstance_controller_test.go | 35 +++++++------- 4 files changed, 81 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index eaff7e2..266da59 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/fluxcd/pkg/apis/kustomize v1.8.0 github.com/fluxcd/pkg/apis/meta v1.9.0 github.com/fluxcd/pkg/kustomize v1.15.0 - github.com/fluxcd/pkg/runtime v0.51.0 + github.com/fluxcd/pkg/runtime v0.51.1 github.com/fluxcd/pkg/ssa v0.43.0 github.com/fluxcd/pkg/tar v0.10.0 github.com/golang-jwt/jwt/v4 v4.5.1 @@ -59,6 +59,7 @@ require ( github.com/emicklei/go-restful/v3 v3.12.0 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect + github.com/fluxcd/pkg/apis/event v0.12.0 // indirect github.com/fluxcd/pkg/envsubst v1.3.0 // indirect github.com/fluxcd/pkg/sourceignore v0.10.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect @@ -83,6 +84,8 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/josharian/intern v1.0.0 // indirect diff --git a/go.sum b/go.sum index c03954d..574bd51 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,12 @@ github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0 github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fluxcd/cli-utils v0.36.0-flux.11 h1:W0y2uvCVkcE8bgV9jgoGSjzWbLFiNq1AjrWtuxllek8= github.com/fluxcd/cli-utils v0.36.0-flux.11/go.mod h1:WZ7xUpZbK+O6HBxA5UWqzWTLSSltdmj4wS1LstS5Dqs= +github.com/fluxcd/pkg/apis/event v0.12.0 h1:+zQVefTG3+THYRS48dtZkoA1rdbZZNx3t6wnbzprFIE= +github.com/fluxcd/pkg/apis/event v0.12.0/go.mod h1:aRK2AONnjjSNW61B6Iy3SW4YHozACntnJeGm3fFqDqA= github.com/fluxcd/pkg/apis/kustomize v1.8.0 h1:HH6YRa3SMS72KK4cUyb9m5sK/dZH+Eti1qhjWDCgwKg= github.com/fluxcd/pkg/apis/kustomize v1.8.0/go.mod h1:QCKIFj1ocdndaWSkrLs5JKvdGNYyTzQX1ZB3lYTwma0= github.com/fluxcd/pkg/apis/meta v1.9.0 h1:wPgm7bWNJZ/ImS5GqikOxt362IgLPFBG73dZ27uWRiQ= @@ -81,8 +85,8 @@ github.com/fluxcd/pkg/envsubst v1.3.0 h1:84Ain+8EBvyzu6y0FsKRwNsvaSiKuqhTqeh/4yo github.com/fluxcd/pkg/envsubst v1.3.0/go.mod h1:lz6HvqDnxbX0sIqjr1fxw0oTGYACLVFcOE/srKS0VQQ= github.com/fluxcd/pkg/kustomize v1.15.0 h1:lII4FW9EJl0rI20dk+Glg5C2JZhP343FBov7HwW+SQo= github.com/fluxcd/pkg/kustomize v1.15.0/go.mod h1:e2SGi7cl28c9cnBVZ8YV8HAS4VBgUsiM6HMqv/AHJWQ= -github.com/fluxcd/pkg/runtime v0.51.0 h1:F4gKLUBUdvUdtg2lBsg72KUPqlOnaf9ChEL8bmP7CvQ= -github.com/fluxcd/pkg/runtime v0.51.0/go.mod h1:uMJ+s81+TyNGVjcnn+PIXUGGYs9VA3AK8nDmQWXAnis= +github.com/fluxcd/pkg/runtime v0.51.1 h1:68C6V/P2l/IwivqzvkgcR6Aa7zKds5ihsvoo0NcLarA= +github.com/fluxcd/pkg/runtime v0.51.1/go.mod h1:uMJ+s81+TyNGVjcnn+PIXUGGYs9VA3AK8nDmQWXAnis= github.com/fluxcd/pkg/sourceignore v0.10.0 h1:z5Bhh0G990uLbwjKNj7SzYqbGkicpGcXxF/Z4ZSVB64= github.com/fluxcd/pkg/sourceignore v0.10.0/go.mod h1:d1d9hcFxf+grda6JL3k+mC09nVTtBb9kJVzQn6J77B0= github.com/fluxcd/pkg/ssa v0.43.0 h1:XmADD3C0erYZayKfGI0WTsMlW9TtS4bp5gy4Axo1dcA= @@ -139,6 +143,12 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -161,6 +171,10 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= diff --git a/internal/controller/fluxinstance_controller.go b/internal/controller/fluxinstance_controller.go index 4b04bb4..b4afeca 100644 --- a/internal/controller/fluxinstance_controller.go +++ b/internal/controller/fluxinstance_controller.go @@ -15,6 +15,7 @@ import ( "github.com/fluxcd/cli-utils/pkg/kstatus/polling" "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/runtime/conditions" + "github.com/fluxcd/pkg/runtime/events" "github.com/fluxcd/pkg/runtime/patch" "github.com/fluxcd/pkg/ssa" "github.com/fluxcd/pkg/ssa/normalize" @@ -170,7 +171,7 @@ func (r *FluxInstanceReconciler) reconcile(ctx context.Context, meta.BuildFailedReason, "%s", msg) log.Error(err, msg) - r.EventRecorder.Event(obj, corev1.EventTypeWarning, meta.BuildFailedReason, msg) + r.notify(ctx, obj, meta.BuildFailedReason, corev1.EventTypeWarning, msg) return ctrl.Result{}, nil } @@ -195,8 +196,7 @@ func (r *FluxInstanceReconciler) reconcile(ctx context.Context, meta.ReadyCondition, meta.ReconciliationFailedReason, "%s", msg) - r.EventRecorder.Event(obj, corev1.EventTypeWarning, meta.ReconciliationFailedReason, msg) - + r.notify(ctx, obj, meta.ReconciliationFailedReason, corev1.EventTypeWarning, msg) return ctrl.Result{}, err } @@ -213,7 +213,7 @@ func (r *FluxInstanceReconciler) reconcile(ctx context.Context, map[string]string{fluxcdv1.RevisionAnnotation: obj.Status.LastAppliedRevision}, corev1.EventTypeNormal, meta.ReconciliationSucceededReason, - msg) + "%s", msg) return requeueAfter(obj), nil } @@ -520,6 +520,18 @@ func (r *FluxInstanceReconciler) apply(ctx context.Context, } } + // Send event to notification-controller only if the server-side apply resulted in changes. + applyLog := strings.TrimSuffix(changeSetLog.String(), "\n") + if applyLog != "" { + action := "updated" + if len(oldInventory.Entries) == 0 { + action = "installed" + } + + msg := fmt.Sprintf("Flux %s revision %s\n%s", action, buildResult.Revision, applyLog) + r.notify(ctx, obj, meta.ReconciliationSucceededReason, corev1.EventTypeNormal, msg) + } + return nil } @@ -626,3 +638,29 @@ func (r *FluxInstanceReconciler) recordMetrics(obj *fluxcdv1.FluxInstance) error reporter.RecordMetrics(unstructured.Unstructured{Object: rawMap}) return nil } + +func (r *FluxInstanceReconciler) notify(ctx context.Context, obj *fluxcdv1.FluxInstance, reason, eventType, msg string) { + notificationAddress := "" + if builder.ContainElementString(obj.GetComponents(), builder.MakeDefaultOptions().NotificationController) { + notificationAddress = fmt.Sprintf("http://%s.%s.svc.%s/", + builder.MakeDefaultOptions().NotificationController, + obj.GetNamespace(), + obj.GetCluster().Domain, + ) + } + + log := ctrl.LoggerFrom(ctx) + + eventRecorder, err := events.NewRecorderForScheme( + r.Scheme, + r.EventRecorder, + log, + notificationAddress, + r.StatusManager) + if err != nil { + log.Error(err, "failed to create event recorder") + return + } + + go eventRecorder.Eventf(obj, eventType, reason, "%s", msg) +} diff --git a/internal/controller/fluxinstance_controller_test.go b/internal/controller/fluxinstance_controller_test.go index 617d274..880c722 100644 --- a/internal/controller/fluxinstance_controller_test.go +++ b/internal/controller/fluxinstance_controller_test.go @@ -15,6 +15,7 @@ import ( "github.com/fluxcd/pkg/runtime/conditions" . "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -190,17 +191,22 @@ func TestFluxInstanceReconciler_LifeCycle(t *testing.T) { // Check if events were recorded for each step. events := getEvents(result.Name) - g.Expect(events).To(HaveLen(5)) - g.Expect(events[0].Reason).To(Equal(fluxcdv1.OutdatedReason)) - g.Expect(events[1].Reason).To(Equal(meta.ProgressingReason)) - g.Expect(events[1].Message).To(HavePrefix("Installing")) - g.Expect(events[2].Reason).To(Equal(meta.ReconciliationSucceededReason)) - g.Expect(events[2].Message).To(HavePrefix("Reconciliation finished")) - g.Expect(events[3].Reason).To(Equal(meta.ProgressingReason)) - g.Expect(events[3].Message).To(HavePrefix("Upgrading")) - g.Expect(events[4].Reason).To(Equal(meta.ReconciliationSucceededReason)) - g.Expect(events[4].Annotations).To(HaveKeyWithValue(fluxcdv1.RevisionAnnotation, resultFinal.Status.LastAppliedRevision)) + for _, event := range events { + t.Log(event.Message) + } + messages := []string{ + "is outdated", + "Installing revision", + "Flux installed revision", + "Upgrading to revision", + "Flux updated revision", + "Reconciliation finished", + } + for _, message := range messages { + g.Expect(events).Should(ContainElement(WithTransform(func(e corev1.Event) string { return e.Message }, ContainSubstring(message)))) + } + // Uninstall the instance. err = testClient.Delete(ctx, obj) g.Expect(err).ToNot(HaveOccurred()) @@ -486,12 +492,9 @@ func TestFluxInstanceReconciler_Disabled(t *testing.T) { err = testClient.Get(ctx, client.ObjectKeyFromObject(obj), resultFinal) g.Expect(err).ToNot(HaveOccurred()) - // Check if events were recorded for each step. + // Check if the ReconciliationDisabled event was recorded. events := getEvents(result.Name) - g.Expect(events).To(HaveLen(4)) - g.Expect(events[1].Reason).To(Equal(meta.ProgressingReason)) - g.Expect(events[2].Reason).To(Equal(meta.ReconciliationSucceededReason)) - g.Expect(events[3].Reason).To(Equal("ReconciliationDisabled")) + g.Expect(events[len(events)-1].Reason).To(Equal("ReconciliationDisabled")) // Check that resources were not deleted. kc := &appsv1.Deployment{} @@ -672,7 +675,7 @@ func TestFluxInstanceReconciler_NewVersion(t *testing.T) { // Check if events were recorded for each step. events := getEvents(obj.Name) - g.Expect(events).To(HaveLen(3)) + g.Expect(events).To(HaveLen(4)) g.Expect(events[0].Reason).To(Equal("OutdatedVersion")) err = testClient.Delete(ctx, obj) From ba5594706dc042fb93913748f58971e0879b724e Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Sun, 22 Dec 2024 13:23:23 +0200 Subject: [PATCH 3/3] Add the revision to `FluxInstance` events metadata Signed-off-by: Stefan Prodan --- .../fluxinstance_artifact_controller_test.go | 2 +- .../controller/fluxinstance_controller.go | 15 ++++++--- .../fluxinstance_controller_test.go | 31 ++++++++++++------- .../controller/fluxreport_controller_test.go | 4 +-- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/internal/controller/fluxinstance_artifact_controller_test.go b/internal/controller/fluxinstance_artifact_controller_test.go index 129ce83..61ed728 100644 --- a/internal/controller/fluxinstance_artifact_controller_test.go +++ b/internal/controller/fluxinstance_artifact_controller_test.go @@ -125,7 +125,7 @@ func TestFluxInstanceArtifactReconciler(t *testing.T) { Namespace: ns.Name, Annotations: tt.annotations, }, - Spec: getDefaultFluxSpec(), + Spec: getDefaultFluxSpec(t), } obj.Spec.Distribution.Artifact = tt.manifestsURL diff --git a/internal/controller/fluxinstance_controller.go b/internal/controller/fluxinstance_controller.go index b4afeca..c918ba0 100644 --- a/internal/controller/fluxinstance_controller.go +++ b/internal/controller/fluxinstance_controller.go @@ -154,7 +154,7 @@ func (r *FluxInstanceReconciler) reconcile(ctx context.Context, meta.ReadyCondition, meta.ArtifactFailedReason, "%s", msg) - r.EventRecorder.Event(obj, corev1.EventTypeWarning, meta.ArtifactFailedReason, msg) + r.notify(ctx, obj, meta.ArtifactFailedReason, corev1.EventTypeWarning, msg) return ctrl.Result{}, err } @@ -528,7 +528,9 @@ func (r *FluxInstanceReconciler) apply(ctx context.Context, action = "installed" } - msg := fmt.Sprintf("Flux %s revision %s\n%s", action, buildResult.Revision, applyLog) + ver := strings.Split(buildResult.Revision, "@")[0] + + msg := fmt.Sprintf("Flux %s %s\n%s", ver, action, applyLog) r.notify(ctx, obj, meta.ReconciliationSucceededReason, corev1.EventTypeNormal, msg) } @@ -641,7 +643,7 @@ func (r *FluxInstanceReconciler) recordMetrics(obj *fluxcdv1.FluxInstance) error func (r *FluxInstanceReconciler) notify(ctx context.Context, obj *fluxcdv1.FluxInstance, reason, eventType, msg string) { notificationAddress := "" - if builder.ContainElementString(obj.GetComponents(), builder.MakeDefaultOptions().NotificationController) { + if os.Getenv("NOTIFICATIONS_DISABLED") == "" && builder.ContainElementString(obj.GetComponents(), builder.MakeDefaultOptions().NotificationController) { notificationAddress = fmt.Sprintf("http://%s.%s.svc.%s/", builder.MakeDefaultOptions().NotificationController, obj.GetNamespace(), @@ -662,5 +664,10 @@ func (r *FluxInstanceReconciler) notify(ctx context.Context, obj *fluxcdv1.FluxI return } - go eventRecorder.Eventf(obj, eventType, reason, "%s", msg) + annotations := map[string]string{} + if obj.Status.LastAttemptedRevision != "" { + annotations[fluxcdv1.RevisionAnnotation] = obj.Status.LastAttemptedRevision + } + + eventRecorder.AnnotatedEventf(obj, annotations, eventType, reason, "%s", msg) } diff --git a/internal/controller/fluxinstance_controller_test.go b/internal/controller/fluxinstance_controller_test.go index 880c722..2993018 100644 --- a/internal/controller/fluxinstance_controller_test.go +++ b/internal/controller/fluxinstance_controller_test.go @@ -42,7 +42,7 @@ func TestFluxInstanceReconciler_LifeCycle(t *testing.T) { Name: ns.Name, Namespace: ns.Name, }, - Spec: getDefaultFluxSpec(), + Spec: getDefaultFluxSpec(t), } obj.Spec.Distribution.Artifact = manifestsURL @@ -196,16 +196,19 @@ func TestFluxInstanceReconciler_LifeCycle(t *testing.T) { } messages := []string{ "is outdated", - "Installing revision", - "Flux installed revision", - "Upgrading to revision", - "Flux updated revision", + "Installing", + "installed", + "Upgrading", + "updated", "Reconciliation finished", } for _, message := range messages { g.Expect(events).Should(ContainElement(WithTransform(func(e corev1.Event) string { return e.Message }, ContainSubstring(message)))) } + // Check if events contain the revision metadata. + g.Expect(events[len(events)-1].Annotations).To(HaveKeyWithValue(fluxcdv1.RevisionAnnotation, resultFinal.Status.LastAppliedRevision)) + // Uninstall the instance. err = testClient.Delete(ctx, obj) g.Expect(err).ToNot(HaveOccurred()) @@ -238,7 +241,7 @@ func TestFluxInstanceReconciler_FetchFail(t *testing.T) { Name: ns.Name, Namespace: ns.Name, }, - Spec: getDefaultFluxSpec(), + Spec: getDefaultFluxSpec(t), } obj.Spec.Distribution.Artifact = manifestsURL @@ -300,7 +303,7 @@ func TestFluxInstanceReconciler_BuildFail(t *testing.T) { Name: ns.Name, Namespace: ns.Name, }, - Spec: getDefaultFluxSpec(), + Spec: getDefaultFluxSpec(t), } err = testClient.Create(ctx, obj) @@ -355,7 +358,7 @@ func TestFluxInstanceReconciler_BuildFail(t *testing.T) { func TestFluxInstanceReconciler_Downgrade(t *testing.T) { g := NewWithT(t) reconciler := getFluxInstanceReconciler() - spec := getDefaultFluxSpec() + spec := getDefaultFluxSpec(t) ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -433,7 +436,7 @@ func TestFluxInstanceReconciler_Downgrade(t *testing.T) { func TestFluxInstanceReconciler_Disabled(t *testing.T) { g := NewWithT(t) reconciler := getFluxInstanceReconciler() - spec := getDefaultFluxSpec() + spec := getDefaultFluxSpec(t) ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -530,7 +533,7 @@ func TestFluxInstanceReconciler_Disabled(t *testing.T) { func TestFluxInstanceReconciler_Profiles(t *testing.T) { g := NewWithT(t) reconciler := getFluxInstanceReconciler() - spec := getDefaultFluxSpec() + spec := getDefaultFluxSpec(t) spec.Distribution.Version = "v2.4.x" spec.Cluster = &fluxcdv1.Cluster{ Type: "openshift", @@ -640,7 +643,7 @@ func TestFluxInstanceReconciler_Profiles(t *testing.T) { func TestFluxInstanceReconciler_NewVersion(t *testing.T) { g := NewWithT(t) reconciler := getFluxInstanceReconciler() - spec := getDefaultFluxSpec() + spec := getDefaultFluxSpec(t) spec.Distribution.Version = "v2.2.x" ctx, cancel := context.WithTimeout(context.Background(), timeout) @@ -689,7 +692,11 @@ func TestFluxInstanceReconciler_NewVersion(t *testing.T) { } -func getDefaultFluxSpec() fluxcdv1.FluxInstanceSpec { +func getDefaultFluxSpec(t *testing.T) fluxcdv1.FluxInstanceSpec { + // Disable notifications for the tests as no pod is running. + // This is required to avoid the 30s retry loop performed by the HTTP client. + t.Setenv("NOTIFICATIONS_DISABLED", "yes") + return fluxcdv1.FluxInstanceSpec{ Wait: ptr.To(false), MigrateResources: ptr.To(true), diff --git a/internal/controller/fluxreport_controller_test.go b/internal/controller/fluxreport_controller_test.go index 1b65d92..47dc400 100644 --- a/internal/controller/fluxreport_controller_test.go +++ b/internal/controller/fluxreport_controller_test.go @@ -45,7 +45,7 @@ func TestFluxReportReconciler_Reconcile(t *testing.T) { Name: ns.Name, Namespace: ns.Name, }, - Spec: getDefaultFluxSpec(), + Spec: getDefaultFluxSpec(t), } err = testEnv.Create(ctx, instance) g.Expect(err).ToNot(HaveOccurred()) @@ -161,7 +161,7 @@ func TestFluxReportReconciler_CustomSyncName(t *testing.T) { Name: ns.Name, Namespace: ns.Name, }, - Spec: getDefaultFluxSpec(), + Spec: getDefaultFluxSpec(t), } // Set custom sync name.