diff --git a/cmd/main.go b/cmd/main.go index b5296ac..4631016 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -43,6 +43,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/metrics/filters" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" @@ -130,24 +131,8 @@ func main() { controllers.RegisterFeatures(d, setupLog) var eventTriggerController controller.Controller - eventTriggerReconciler := &controllers.EventTriggerReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - ConcurrentReconciles: concurrentReconciles, - ShardKey: shardKey, - Mux: sync.Mutex{}, - Deployer: d, - Logger: ctrl.Log.WithName("eventTriggerReconciler"), - ClusterMap: make(map[corev1.ObjectReference]*libsveltosset.Set), - ToClusterMap: make(map[types.NamespacedName]*libsveltosset.Set), - EventTriggers: make(map[corev1.ObjectReference]libsveltosv1beta1.Selector), - ClusterLabels: make(map[corev1.ObjectReference]map[string]string), - EventSourceMap: make(map[corev1.ObjectReference]*libsveltosset.Set), - ToEventSourceMap: make(map[types.NamespacedName]*libsveltosset.Set), - EventTriggerMap: make(map[types.NamespacedName]*libsveltosset.Set), - ReferenceMap: make(map[corev1.ObjectReference]*libsveltosset.Set), - ClusterSetMap: make(map[corev1.ObjectReference]*libsveltosset.Set), - } + eventTriggerReconciler := getEventTriggerReconciler(mgr) + eventTriggerReconciler.Deployer = d eventTriggerController, err = eventTriggerReconciler.SetupWithManager(mgr) if err != nil { @@ -318,3 +303,23 @@ func getDiagnosticsOptions() metricsserver.Options { FilterProvider: filters.WithAuthenticationAndAuthorization, } } + +func getEventTriggerReconciler(mgr manager.Manager) *controllers.EventTriggerReconciler { + return &controllers.EventTriggerReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + ConcurrentReconciles: concurrentReconciles, + ShardKey: shardKey, + Mux: sync.Mutex{}, + Logger: ctrl.Log.WithName("eventTriggerReconciler"), + ClusterMap: make(map[corev1.ObjectReference]*libsveltosset.Set), + ToClusterMap: make(map[types.NamespacedName]*libsveltosset.Set), + EventTriggers: make(map[corev1.ObjectReference]libsveltosv1beta1.Selector), + ClusterLabels: make(map[corev1.ObjectReference]map[string]string), + EventSourceMap: make(map[corev1.ObjectReference]*libsveltosset.Set), + ToEventSourceMap: make(map[types.NamespacedName]*libsveltosset.Set), + EventTriggerMap: make(map[types.NamespacedName]*libsveltosset.Set), + ReferenceMap: make(map[corev1.ObjectReference]*libsveltosset.Set), + ClusterSetMap: make(map[corev1.ObjectReference]*libsveltosset.Set), + } +} diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 03011a1..692225b 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -9,23 +9,13 @@ rules: resources: - configmaps verbs: - - create - - delete - - get - - list - - update - - watch + - '*' - apiGroups: - "" resources: - secrets verbs: - - create - - delete - - get - - list - - update - - watch + - '*' - apiGroups: - apiextensions.k8s.io resources: diff --git a/controllers/eventreport_collection.go b/controllers/eventreport_collection.go index 6c781c5..c8e8f0e 100644 --- a/controllers/eventreport_collection.go +++ b/controllers/eventreport_collection.go @@ -29,9 +29,12 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/projectsveltos/event-manager/api/v1beta1" libsveltosv1beta1 "github.com/projectsveltos/libsveltos/api/v1beta1" "github.com/projectsveltos/libsveltos/lib/clusterproxy" logs "github.com/projectsveltos/libsveltos/lib/logsettings" + libsveltosset "github.com/projectsveltos/libsveltos/lib/set" + libsveltostemplate "github.com/projectsveltos/libsveltos/lib/template" ) const ( @@ -98,6 +101,72 @@ func removeEventReportsFromCluster(ctx context.Context, c client.Client, cluster return nil } +// buildEventTriggersForEventSourceMap builds a map: +// key => eventSource name; +// values => slice of the EventTriggers referencing it +// This map is built one per cluster as EventSource can be expressed as a template and instantiated using +// cluster namespace, name and type +func buildEventTriggersForEventSourceMap(cluster *corev1.ObjectReference, eventTriggers *v1beta1.EventTriggerList, +) (map[string][]*v1beta1.EventTrigger, error) { + + clusterType := clusterproxy.GetClusterType(cluster) + + eventSourceMap := map[string][]*v1beta1.EventTrigger{} + + for i := range eventTriggers.Items { + et := &eventTriggers.Items[i] + + eventSourceName, err := libsveltostemplate.GetReferenceResourceName(cluster.Namespace, cluster.Name, + string(clusterType), et.Spec.EventSourceName) + if err != nil { + return nil, err + } + + s := eventSourceMap[eventSourceName] + if s == nil { + s = make([]*v1beta1.EventTrigger, 0) + eventSourceMap[eventSourceName] = s + } + + s = append(s, et) + eventSourceMap[eventSourceName] = s + } + + return eventSourceMap, nil +} + +func addEventTriggerMatchingCluster(et *v1beta1.EventTrigger, + eventTriggerMap map[string]libsveltosset.Set) map[string]libsveltosset.Set { + + matchingClusters := libsveltosset.Set{} + + for i := range et.Status.MatchingClusterRefs { + cluster := &et.Status.MatchingClusterRefs[i] + matchingClusters.Insert(cluster) + } + + eventTriggerMap[et.Name] = matchingClusters + + return eventTriggerMap +} + +// buildClusterForEventTriggerMap builds a map: +// key => eventTrigger name +// values => slice of currently matching clusters +func buildEventTriggersForClusterMap(eventTriggers *v1beta1.EventTriggerList, +) map[string]libsveltosset.Set { + + eventTriggerMap := map[string]libsveltosset.Set{} + + for i := range eventTriggers.Items { + et := &eventTriggers.Items[i] + + eventTriggerMap = addEventTriggerMatchingCluster(et, eventTriggerMap) + } + + return eventTriggerMap +} + // Periodically collects EventReports from each CAPI cluster. func collectEventReports(c client.Client, shardKey string, logger logr.Logger) { interval := 10 * time.Second @@ -108,15 +177,40 @@ func collectEventReports(c client.Client, shardKey string, logger logr.Logger) { ctx := context.TODO() for { - logger.V(logs.LogDebug).Info("collecting EventReports") + logger.V(logs.LogDebug).Info("collecting EventTriggers") + // get all EventTriggers + eventTriggers := &v1beta1.EventTriggerList{} + err := c.List(ctx, eventTriggers) + if err != nil { + logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to get eventTriggers: %v", err)) + time.Sleep(interval) + continue + } + + // build a map eventTrigger: matching clusters + eventTriggerMap := buildEventTriggersForClusterMap(eventTriggers) + + logger.V(logs.LogDebug).Info("collecting managed clusters") clusterList, err := clusterproxy.GetListOfClustersForShardKey(ctx, c, "", shardKey, logger) if err != nil { logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to get clusters: %v", err)) + time.Sleep(interval) + continue } for i := range clusterList { cluster := &clusterList[i] - err = collectAndProcessEventReportsFromCluster(ctx, c, cluster, logger) + + // Build a map of EventTrigger consuming an EventSource. This is built once per cluster + // as EventSourceName in EventTrigger.Spec can be expressed as a template and instantiated + // using cluster namespace, name and type. + eventSourceMap, err := buildEventTriggersForEventSourceMap(cluster, eventTriggers) + if err != nil { + time.Sleep(interval) + continue + } + + err = collectAndProcessEventReportsFromCluster(ctx, c, cluster, eventSourceMap, eventTriggerMap, logger) if err != nil { logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to collect EventReports from cluster: %s/%s %v", cluster.Namespace, cluster.Name, err)) @@ -127,8 +221,9 @@ func collectEventReports(c client.Client, shardKey string, logger logr.Logger) { } } -func collectAndProcessEventReportsFromCluster(ctx context.Context, c client.Client, - cluster *corev1.ObjectReference, logger logr.Logger) error { +func collectAndProcessEventReportsFromCluster(ctx context.Context, c client.Client, cluster *corev1.ObjectReference, + eventSourceMap map[string][]*v1beta1.EventTrigger, eventTriggerMap map[string]libsveltosset.Set, + logger logr.Logger) error { logger = logger.WithValues("cluster", fmt.Sprintf("%s/%s", cluster.Namespace, cluster.Name)) clusterRef := &corev1.ObjectReference{ @@ -155,6 +250,7 @@ func collectAndProcessEventReportsFromCluster(ctx context.Context, c client.Clie } logger.V(logs.LogDebug).Info("collecting EventReports from cluster") + eventReportList := libsveltosv1beta1.EventReportList{} err = remoteClient.List(ctx, &eventReportList) if err != nil { @@ -164,6 +260,11 @@ func collectAndProcessEventReportsFromCluster(ctx context.Context, c client.Clie currentEventReports := make(map[string]bool) for i := range eventReportList.Items { er := &eventReportList.Items[i] + + if shouldIgnore(er) { + continue + } + l := logger.WithValues("eventReport", er.Name) // First update/delete eventReports in managemnent cluster if !er.DeletionTimestamp.IsZero() { @@ -171,12 +272,14 @@ func collectAndProcessEventReportsFromCluster(ctx context.Context, c client.Clie err = deleteEventReport(ctx, c, cluster, er, l) if err != nil { logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to delete EventReport in management cluster. Err: %v", err)) + continue } - } else { + } else if shouldReprocess(er) { logger.V(logs.LogDebug).Info("updating in management cluster") err = updateEventReport(ctx, c, cluster, er, l) if err != nil { logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to update EventReport in management cluster. Err: %v", err)) + continue } // Name in the management cluster is different than name in the managed cluster eventSourceName := er.Labels[libsveltosv1beta1.EventSourceNameLabel] @@ -185,7 +288,9 @@ func collectAndProcessEventReportsFromCluster(ctx context.Context, c client.Clie currentEventReports[eventReportName] = true } - logger.V(logs.LogDebug).Info("updating in managed cluster") + updateAllClusterProfiles(ctx, c, cluster, er, eventSourceMap, eventTriggerMap, logger) + + logger.V(logs.LogDebug).Info("updating EventReport in the managed cluster") // Update EventReport Status in managed cluster phase := libsveltosv1beta1.ReportProcessed er.Status.Phase = &phase @@ -195,8 +300,72 @@ func collectAndProcessEventReportsFromCluster(ctx context.Context, c client.Clie } } - return removeEventReportsFromCluster(ctx, c, cluster.Namespace, cluster.Name, clusterproxy.GetClusterType(cluster), - currentEventReports, logger) + return nil +} + +// EventReports are collected from managed cluster to the management cluster. +// When an EventReport is collected from a managed cluster and created in the +// management cluster, the label eventreport.projectsveltos.io/cluster-name +// is added. All EventReports found in the management cluster with this +// labels should be ignored as collected from other managed clusters. +func shouldIgnore(er *libsveltosv1beta1.EventReport) bool { + if er.Labels == nil { + return false + } + + _, ok := er.Labels[libsveltosv1beta1.EventReportClusterNameLabel] + return ok +} + +// If the EventReport in the managed cluster is marked as Processed, ignore it. +func shouldReprocess(er *libsveltosv1beta1.EventReport) bool { + if er.Status.Phase == nil { + return true + } + + return *er.Status.Phase != libsveltosv1beta1.ReportProcessed +} + +// isEventTriggerMatchingTheCluster returns true if EventTrigger is currently matching +// cluster +func isEventTriggerMatchingTheCluster(et *v1beta1.EventTrigger, cluster *corev1.ObjectReference, + eventTriggerMap map[string]libsveltosset.Set) bool { + + matchingClusters := eventTriggerMap[et.Name] + return matchingClusters.Has(cluster) +} + +func updateAllClusterProfiles(ctx context.Context, mgmtClient client.Client, cluster *corev1.ObjectReference, + er *libsveltosv1beta1.EventReport, eventSourceMap map[string][]*v1beta1.EventTrigger, + eventTriggerMap map[string]libsveltosset.Set, logger logr.Logger) { + + clusterType := clusterproxy.GetClusterType(cluster) + + // Get all EventSource from EventReport + eventSourceName := er.Labels[libsveltosv1beta1.EventSourceNameLabel] + logger.V(logs.LogDebug).Info(fmt.Sprintf("eventSource is %s", eventSourceName)) + + // Get all EventTriggers referencing this EventSource + eventTriggers := eventSourceMap[eventSourceName] + + // For each EventTrigger + for i := range eventTriggers { + l := logger.WithValues("eventTrigger", eventTriggers[i].Name) + + // If EventTrigger is currently not matching this cluster, ignore this EventReports + if !isEventTriggerMatchingTheCluster(eventTriggers[i], cluster, eventTriggerMap) { + l.V(logs.LogDebug).Info("cluster is not a match anymore. Ignore.") + continue + } + + l.V(logs.LogDebug).Info("updating ClusterProfile") + err := updateClusterProfiles(ctx, mgmtClient, cluster.Namespace, cluster.Name, clusterType, + eventTriggers[i], er, logger) + if err != nil { + logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to update ClusterProfile for EventTrigger %s: %v", + eventTriggers[i].GetName(), err)) + } + } } func deleteEventReport(ctx context.Context, c client.Client, cluster *corev1.ObjectReference, @@ -230,13 +399,6 @@ func deleteEventReport(ctx context.Context, c client.Client, cluster *corev1.Obj func updateEventReport(ctx context.Context, c client.Client, cluster *corev1.ObjectReference, eventReport *libsveltosv1beta1.EventReport, logger logr.Logger) error { - if eventReport.Spec.ClusterName != "" { - // if ClusterName is set, this is coming from a - // managed cluster. If management cluster is in turn - // managed by another cluster, do not pull those. - return nil - } - if eventReport.Labels == nil { logger.V(logs.LogInfo).Info(eventReportMalformedLabelError) return errors.New(eventReportMalformedLabelError) diff --git a/controllers/eventreport_collection_test.go b/controllers/eventreport_collection_test.go index 9353aa2..247a132 100644 --- a/controllers/eventreport_collection_test.go +++ b/controllers/eventreport_collection_test.go @@ -18,6 +18,7 @@ package controllers_test import ( "context" + "fmt" "time" "github.com/go-logr/logr" @@ -33,8 +34,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + "github.com/projectsveltos/event-manager/api/v1beta1" "github.com/projectsveltos/event-manager/controllers" libsveltosv1beta1 "github.com/projectsveltos/libsveltos/api/v1beta1" + libsveltosset "github.com/projectsveltos/libsveltos/lib/set" ) var _ = Describe("EventSource Deployer", func() { @@ -167,8 +170,11 @@ var _ = Describe("EventSource Deployer", func() { Expect(waitForObject(context.TODO(), testEnv.Client, eventReport)).To(Succeed()) + eventSourceMap := map[string][]*v1beta1.EventTrigger{} + eventTriggerMap := map[string]libsveltosset.Set{} + Expect(controllers.CollectAndProcessEventReportsFromCluster(context.TODO(), testEnv.Client, getClusterRef(cluster), - logger)).To(Succeed()) + eventSourceMap, eventTriggerMap, logger)).To(Succeed()) clusterType := libsveltosv1beta1.ClusterTypeCapi @@ -176,10 +182,233 @@ var _ = Describe("EventSource Deployer", func() { // Update EventReports and validate again Expect(controllers.CollectAndProcessEventReportsFromCluster(context.TODO(), testEnv.Client, getClusterRef(cluster), - logger)).To(Succeed()) + eventSourceMap, eventTriggerMap, logger)).To(Succeed()) validateEventReports(eventSourceName, cluster, &clusterType) }) + + It("buildEventTriggersForEventSourceMap builds a map of EventTriggers referencing an EventSource", func() { + eventSourceName1 := randomString() + et1 := &v1beta1.EventTrigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + }, + Spec: v1beta1.EventTriggerSpec{ + EventSourceName: eventSourceName1, + }, + } + + cluster := &corev1.ObjectReference{ + Namespace: randomString(), + Name: randomString(), + Kind: libsveltosv1beta1.SveltosClusterKind, + APIVersion: libsveltosv1beta1.GroupVersion.String(), + } + prefix := "test-template" + eventSourceName2 := fmt.Sprintf("%s-{{ .Cluster.metadata.name }}", prefix) + et2 := &v1beta1.EventTrigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + }, + Spec: v1beta1.EventTriggerSpec{ + EventSourceName: eventSourceName2, + }, + } + + initObjects := []client.Object{ + et1, et2, + } + + c := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(initObjects...). + WithObjects(initObjects...).Build() + + eventTriggers := &v1beta1.EventTriggerList{} + Expect(c.List(context.TODO(), eventTriggers)).To(Succeed()) + + eventSourceMap, err := controllers.BuildEventTriggersForEventSourceMap(cluster, eventTriggers) + Expect(err).To(BeNil()) + Expect(eventSourceMap).ToNot(BeNil()) + + v, ok := eventSourceMap[eventSourceName1] + Expect(ok).To(BeTrue()) + Expect(v).To(ContainElement(et1)) + + v, ok = eventSourceMap[fmt.Sprintf("%s-%s", prefix, cluster.Name)] + Expect(ok).To(BeTrue()) + Expect(v).To(ContainElement(et2)) + }) + + It("buildEventTriggersForClusterMap builds a map of clusters matching an eventTrigger", func() { + cluster1 := &corev1.ObjectReference{ + Namespace: randomString(), + Name: randomString(), + Kind: libsveltosv1beta1.SveltosClusterKind, + APIVersion: libsveltosv1beta1.GroupVersion.String(), + } + + cluster2 := &corev1.ObjectReference{ + Namespace: randomString(), + Name: randomString(), + Kind: libsveltosv1beta1.SveltosClusterKind, + APIVersion: libsveltosv1beta1.GroupVersion.String(), + } + + et1 := &v1beta1.EventTrigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + }, + Spec: v1beta1.EventTriggerSpec{ + EventSourceName: randomString(), + }, + Status: v1beta1.EventTriggerStatus{ + MatchingClusterRefs: []corev1.ObjectReference{ + *cluster1, *cluster2, + }, + }, + } + + et2 := &v1beta1.EventTrigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + }, + Spec: v1beta1.EventTriggerSpec{ + EventSourceName: randomString(), + }, + Status: v1beta1.EventTriggerStatus{ + MatchingClusterRefs: []corev1.ObjectReference{ + *cluster1, + }, + }, + } + + initObjects := []client.Object{ + et1, et2, + } + + c := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(initObjects...). + WithObjects(initObjects...).Build() + + eventTriggers := &v1beta1.EventTriggerList{} + Expect(c.List(context.TODO(), eventTriggers)).To(Succeed()) + + eventTriggerMap := controllers.BuildEventTriggersForClusterMap(eventTriggers) + + v, ok := eventTriggerMap[et1.Name] + Expect(ok).To(BeTrue()) + Expect(v.Len()).To(Equal(2)) + Expect(v.Has(cluster1)).To(BeTrue()) + Expect(v.Has(cluster2)).To(BeTrue()) + + v, ok = eventTriggerMap[et2.Name] + Expect(ok).To(BeTrue()) + Expect(v.Len()).To(Equal(1)) + Expect(v.Has(cluster1)).To(BeTrue()) + Expect(v.Has(cluster2)).To(BeFalse()) + }) + + It("shouldIgnore ignore eventReports collected from managed clusters to the management cluster", func() { + eventReport := libsveltosv1beta1.EventReport{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + }, + } + + Expect(controllers.ShouldIgnore(&eventReport)).To(BeFalse()) + + eventReport.Labels = map[string]string{ + libsveltosv1beta1.EventReportClusterNameLabel: randomString(), + } + + Expect(controllers.ShouldIgnore(&eventReport)).To(BeTrue()) + }) + + It("shouldReprocess returns true for EventReport with Status set to processed", func() { + eventReport := libsveltosv1beta1.EventReport{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + Labels: map[string]string{ + libsveltosv1beta1.EventReportClusterNameLabel: randomString(), + }, + }, + } + + Expect(controllers.ShouldReprocess(&eventReport)).To(BeTrue()) + + phase := libsveltosv1beta1.ReportWaitingForDelivery + eventReport.Status.Phase = &phase + Expect(controllers.ShouldReprocess(&eventReport)).To(BeTrue()) + + phase = libsveltosv1beta1.ReportProcessed + eventReport.Status.Phase = &phase + Expect(controllers.ShouldReprocess(&eventReport)).To(BeFalse()) + }) + + It("isEventTriggerMatchingTheCluster returns true if a cluster is a match for an EventTrigger", func() { + cluster1 := &corev1.ObjectReference{ + Namespace: randomString(), + Name: randomString(), + Kind: libsveltosv1beta1.SveltosClusterKind, + APIVersion: libsveltosv1beta1.GroupVersion.String(), + } + + cluster2 := &corev1.ObjectReference{ + Namespace: randomString(), + Name: randomString(), + Kind: libsveltosv1beta1.SveltosClusterKind, + APIVersion: libsveltosv1beta1.GroupVersion.String(), + } + + cluster3 := &corev1.ObjectReference{ + Namespace: randomString(), + Name: randomString(), + Kind: libsveltosv1beta1.SveltosClusterKind, + APIVersion: libsveltosv1beta1.GroupVersion.String(), + } + + et1 := &v1beta1.EventTrigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + }, + Spec: v1beta1.EventTriggerSpec{ + EventSourceName: randomString(), + }, + Status: v1beta1.EventTriggerStatus{ + MatchingClusterRefs: []corev1.ObjectReference{ + *cluster1, *cluster2, + }, + }, + } + + et2 := &v1beta1.EventTrigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + }, + Spec: v1beta1.EventTriggerSpec{ + EventSourceName: randomString(), + }, + Status: v1beta1.EventTriggerStatus{}, + } + + initObjects := []client.Object{ + et1, et2, + } + + c := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(initObjects...). + WithObjects(initObjects...).Build() + + eventTriggers := &v1beta1.EventTriggerList{} + Expect(c.List(context.TODO(), eventTriggers)).To(Succeed()) + + eventTriggerMap := controllers.BuildEventTriggersForClusterMap(eventTriggers) + + Expect(controllers.IsEventTriggerMatchingTheCluster(et1, cluster1, eventTriggerMap)).To(BeTrue()) + Expect(controllers.IsEventTriggerMatchingTheCluster(et1, cluster2, eventTriggerMap)).To(BeTrue()) + Expect(controllers.IsEventTriggerMatchingTheCluster(et1, cluster3, eventTriggerMap)).To(BeFalse()) + + Expect(controllers.IsEventTriggerMatchingTheCluster(et2, cluster1, eventTriggerMap)).To(BeFalse()) + Expect(controllers.IsEventTriggerMatchingTheCluster(et2, cluster2, eventTriggerMap)).To(BeFalse()) + Expect(controllers.IsEventTriggerMatchingTheCluster(et2, cluster3, eventTriggerMap)).To(BeFalse()) + }) }) func validateEventReports(eventSourceName string, cluster *clusterv1.Cluster, clusterType *libsveltosv1beta1.ClusterType) { diff --git a/controllers/eventtrigger_controller.go b/controllers/eventtrigger_controller.go index 85c326d..1091ddd 100644 --- a/controllers/eventtrigger_controller.go +++ b/controllers/eventtrigger_controller.go @@ -22,8 +22,6 @@ import ( "sync" "time" - sourcev1 "github.com/fluxcd/source-controller/api/v1" - sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" "github.com/go-logr/logr" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" @@ -47,7 +45,6 @@ import ( "github.com/projectsveltos/libsveltos/lib/deployer" logs "github.com/projectsveltos/libsveltos/lib/logsettings" libsveltosset "github.com/projectsveltos/libsveltos/lib/set" - libsveltostemplate "github.com/projectsveltos/libsveltos/lib/template" ) type ReportMode int @@ -161,8 +158,8 @@ type EventTriggerReconciler struct { //+kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machines/status,verbs=get;watch;list //+kubebuilder:rbac:groups=lib.projectsveltos.io,resources=sveltosclusters,verbs=get;watch;list //+kubebuilder:rbac:groups=lib.projectsveltos.io,resources=sveltosclusters/status,verbs=get;watch;list -//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;watch;list;create;update;delete -//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;watch;list;create;update;delete +//+kubebuilder:rbac:groups="",resources=secrets,verbs="*" +//+kubebuilder:rbac:groups="",resources=configmaps,verbs="*" func (r *EventTriggerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { logger := ctrl.LoggerFrom(ctx) @@ -227,8 +224,7 @@ func (r *EventTriggerReconciler) reconcileDelete( r.cleanMaps(eventTriggerScope) - f := getHandlersForFeature(v1beta1.FeatureEventTrigger) - err := r.undeployEventTrigger(ctx, eventTriggerScope, f, logger) + err := r.undeployEventTrigger(ctx, eventTriggerScope, eventTriggerScope.EventTrigger.Status.ClusterInfo, logger) if err != nil { logger.V(logs.LogInfo).Error(err, "failed to undeploy") return reconcile.Result{Requeue: true, RequeueAfter: deleteRequeueAfter} @@ -270,6 +266,14 @@ func (r *EventTriggerReconciler) reconcileNormal( matchingCluster = append(matchingCluster, clusterSetClusters...) + // Undeploy EventTrigger from every clusters that used to be a match but it is not a match anymore + err = r.undeployEvenTriggerFromNonMatchingCluster(ctx, eventTriggerScope, removeDuplicates(matchingCluster), + logger) + if err != nil { + logger.V(logs.LogDebug).Info("failed to undeploy EvenTrigger from clusters that are no longer a match: %v", err) + return reconcile.Result{Requeue: true, RequeueAfter: normalRequeueAfter} + } + eventTriggerScope.SetMatchingClusterRefs(removeDuplicates(matchingCluster)) err = r.updateClusterInfo(ctx, eventTriggerScope) @@ -319,24 +323,6 @@ func (r *EventTriggerReconciler) SetupWithManager(mgr ctrl.Manager) (controller. EventReportPredicates(mgr.GetLogger().WithValues("predicate", "eventreportpredicate")), ), ). - Watches(&libsveltosv1beta1.EventSource{}, - handler.EnqueueRequestsFromMapFunc(r.requeueEventTriggerForEventSource), - builder.WithPredicates( - EventSourcePredicates(mgr.GetLogger().WithValues("predicate", "eventsourcepredicate")), - ), - ). - Watches(&corev1.ConfigMap{}, - handler.EnqueueRequestsFromMapFunc(r.requeueEventTriggerForReference), - builder.WithPredicates( - ConfigMapPredicates(mgr.GetLogger().WithValues("predicate", "eventsourcepredicate")), - ), - ). - Watches(&corev1.Secret{}, - handler.EnqueueRequestsFromMapFunc(r.requeueEventTriggerForReference), - builder.WithPredicates( - SecretPredicates(mgr.GetLogger().WithValues("predicate", "eventsourcepredicate")), - ), - ). Build(r) if err != nil { return nil, errors.Wrap(err, "error creating controller") @@ -471,11 +457,6 @@ func (r *EventTriggerReconciler) updateMaps(eventTriggerScope *scope.EventTrigge r.updateEventSourceMaps(eventTriggerScope) - if err := r.updateReferencedResourceMap(eventTriggerScope); err != nil { - logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to update referenced resources map: %v", err)) - return err - } - r.updateClusterSetMap(eventTriggerScope) eventTriggerInfo := getKeyFromObject(r.Scheme, eventTriggerScope.EventTrigger) @@ -595,52 +576,6 @@ func (r *EventTriggerReconciler) updateEventSourceMaps(eventTriggerScope *scope. r.ToEventSourceMap[name] = currentReferences } -func (r *EventTriggerReconciler) updateReferencedResourceMap(eventTriggerScope *scope.EventTriggerScope) error { - // Get list of ConfigMap/Secret currently referenced - currentReferences, err := r.getCurrentReferences(eventTriggerScope) - if err != nil { - return err - } - - r.Mux.Lock() - defer r.Mux.Unlock() - - // Get list of References not referenced anymore by EventTrigger - var toBeRemoved []corev1.ObjectReference - eventTriggerName := types.NamespacedName{Name: eventTriggerScope.Name()} - if v, ok := r.EventTriggerMap[eventTriggerName]; ok { - toBeRemoved = v.Difference(currentReferences) - } - - // For each currently referenced instance, add EventTrigger as consumer - for _, referencedResource := range currentReferences.Items() { - tmpResource := referencedResource - getConsumersForEntry(r.ReferenceMap, &tmpResource).Insert( - &corev1.ObjectReference{ - APIVersion: v1beta1.GroupVersion.String(), - Kind: v1beta1.EventTriggerKind, - Name: eventTriggerScope.Name(), - }, - ) - } - - // For each resource not reference anymore, remove EventTrigger as consumer - for i := range toBeRemoved { - referencedResource := toBeRemoved[i] - getConsumersForEntry(r.ReferenceMap, &referencedResource).Erase( - &corev1.ObjectReference{ - APIVersion: v1beta1.GroupVersion.String(), - Kind: v1beta1.EventTriggerKind, - Name: eventTriggerScope.Name(), - }, - ) - } - - // Update list of ConfigMaps/Secrets currently referenced by EventTrigger - r.EventTriggerMap[eventTriggerName] = currentReferences - return nil -} - func getConsumersForEntry(currentMap map[corev1.ObjectReference]*libsveltosset.Set, entry *corev1.ObjectReference) *libsveltosset.Set { @@ -689,70 +624,6 @@ func (r *EventTriggerReconciler) updateClusterInfo(ctx context.Context, return nil } -func (r *EventTriggerReconciler) getCurrentReferences(eventTriggerScope *scope.EventTriggerScope) (*libsveltosset.Set, error) { - currentReferences := &libsveltosset.Set{} - for i := range eventTriggerScope.EventTrigger.Spec.PolicyRefs { - referencedNamespace := eventTriggerScope.EventTrigger.Spec.PolicyRefs[i].Namespace - - // If referenced resource namespace is empty, at instantiation time the cluster namespace will be used. - // Here to track referenced ConfigMaps/Resource, we use all current matching clusters - for j := range eventTriggerScope.EventTrigger.Status.MatchingClusterRefs { - clusterRef := eventTriggerScope.EventTrigger.Status.MatchingClusterRefs[j] - namespace := libsveltostemplate.GetReferenceResourceNamespace(clusterRef.Namespace, referencedNamespace) - - clusterType := clusterproxy.GetClusterType(&clusterRef) - referencedName, err := libsveltostemplate.GetReferenceResourceName(clusterRef.Namespace, clusterRef.Name, - string(clusterType), eventTriggerScope.EventTrigger.Spec.PolicyRefs[i].Name) - if err != nil { - return nil, err - } - - currentReferences.Insert(&corev1.ObjectReference{ - APIVersion: corev1.SchemeGroupVersion.String(), // the only resources that can be referenced are Secret and ConfigMap - Kind: eventTriggerScope.EventTrigger.Spec.PolicyRefs[i].Kind, - Namespace: namespace, - Name: referencedName, - }) - } - } - - for i := range eventTriggerScope.EventTrigger.Spec.KustomizationRefs { - referencedNamespace := eventTriggerScope.EventTrigger.Spec.KustomizationRefs[i].Namespace - - // If referenced resource namespace is empty, at instantiation time the cluster namespace will be used. - // Here to track referenced ConfigMaps/Resource, we use all current matching clusters - for j := range eventTriggerScope.EventTrigger.Status.MatchingClusterRefs { - clusterRef := eventTriggerScope.EventTrigger.Status.MatchingClusterRefs[j] - namespace := libsveltostemplate.GetReferenceResourceNamespace(clusterRef.Namespace, referencedNamespace) - - clusterType := clusterproxy.GetClusterType(&clusterRef) - referencedName, err := libsveltostemplate.GetReferenceResourceName(clusterRef.Namespace, clusterRef.Name, - string(clusterType), eventTriggerScope.EventTrigger.Spec.KustomizationRefs[i].Name) - if err != nil { - return nil, err - } - - ref := &corev1.ObjectReference{ - Kind: eventTriggerScope.EventTrigger.Spec.KustomizationRefs[i].Kind, - Namespace: namespace, - Name: referencedName, - } - - switch eventTriggerScope.EventTrigger.Spec.KustomizationRefs[i].Kind { - case sourcev1.GitRepositoryKind: - ref.APIVersion = sourcev1.GroupVersion.String() - case sourcev1b2.OCIRepositoryKind: - ref.APIVersion = sourcev1b2.GroupVersion.String() - case sourcev1b2.BucketKind: - ref.APIVersion = sourcev1b2.GroupVersion.String() - } - - currentReferences.Insert(ref) - } - } - return currentReferences, nil -} - func (r *EventTriggerReconciler) getClustersFromClusterSets(ctx context.Context, clusterSetRefs []string, logger logr.Logger) ([]corev1.ObjectReference, error) { @@ -786,3 +657,34 @@ func removeDuplicates(references []corev1.ObjectReference) []corev1.ObjectRefere return set.Items() } + +func (r *EventTriggerReconciler) undeployEvenTriggerFromNonMatchingCluster(ctx context.Context, + eventTriggerScope *scope.EventTriggerScope, currentMatchingClusters []corev1.ObjectReference, + logger logr.Logger) error { + + matchingClusters := libsveltosset.Set{} + for i := range currentMatchingClusters { + matchingClusters.Insert(¤tMatchingClusters[i]) + } + + f := getHandlersForFeature(v1beta1.FeatureEventTrigger) + + // At this point we have not update Status yet, so those are the clusters + // that used to be a match + for i := range eventTriggerScope.EventTrigger.Status.ClusterInfo { + oldMatchingCluster := eventTriggerScope.EventTrigger.Status.MatchingClusterRefs[i] + if !matchingClusters.Has(&oldMatchingCluster) { + logger.V(logs.LogDebug).Info(fmt.Sprintf("undeploy EventTrigger from cluster %s:%s/%s", + oldMatchingCluster.Kind, oldMatchingCluster.Namespace, oldMatchingCluster.Name)) + clusterInfo, err := r.removeEventTrigger(ctx, eventTriggerScope, &oldMatchingCluster, f, logger) + if err != nil { + logger.V(logs.LogInfo).Error(err, "failed to undeploy") + eventTriggerScope.EventTrigger.Status.ClusterInfo[i].Status = clusterInfo.Status + return err + } + eventTriggerScope.EventTrigger.Status.ClusterInfo[i].Status = libsveltosv1beta1.SveltosStatusRemoved + } + } + + return nil +} diff --git a/controllers/eventtrigger_controller_test.go b/controllers/eventtrigger_controller_test.go index a5db2c9..2a69cb5 100644 --- a/controllers/eventtrigger_controller_test.go +++ b/controllers/eventtrigger_controller_test.go @@ -34,7 +34,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - configv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1" "github.com/projectsveltos/event-manager/api/v1beta1" "github.com/projectsveltos/event-manager/controllers" "github.com/projectsveltos/event-manager/pkg/scope" @@ -393,111 +392,6 @@ var _ = Describe("EventTrigger: Reconciler", func() { Expect(controllers.GetConsumersForEntry(reconciler.ClusterSetMap, clusterSetInfo).Len()).To(Equal(1)) }) - It("updateReferencedResourceMap properly update referenced maps", func() { - esName := randomString() - - resource.Spec.EventSourceName = esName - - clusterNamespace := randomString() - clusterName := randomString() - - cmNamespace := randomString() - cmName := randomString() - - secretNamespace := randomString() - secretName := randomString() - - resource.Spec.PolicyRefs = []configv1beta1.PolicyRef{ - { - Namespace: cmNamespace, - Name: cmName, - Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), - }, - { - Namespace: secretNamespace, - Name: secretName, - Kind: string(libsveltosv1beta1.SecretReferencedResourceKind), - }, - } - - resource.Status.MatchingClusterRefs = []corev1.ObjectReference{ - { - Kind: libsveltosv1beta1.SveltosClusterKind, - APIVersion: libsveltosv1beta1.GroupVersion.String(), - Namespace: clusterNamespace, - Name: clusterName, - }, - } - - initObjects := []client.Object{ - resource, - } - - c := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(initObjects...). - WithObjects(initObjects...).Build() - - dep := fakedeployer.GetClient(context.TODO(), logger, c) - Expect(dep.RegisterFeatureID(v1beta1.FeatureEventTrigger)).To(Succeed()) - - reconciler := controllers.EventTriggerReconciler{ - Client: c, - Deployer: dep, - Scheme: c.Scheme(), - Mux: sync.Mutex{}, - ClusterMap: make(map[corev1.ObjectReference]*libsveltosset.Set), - ToClusterMap: make(map[types.NamespacedName]*libsveltosset.Set), - EventTriggers: make(map[corev1.ObjectReference]libsveltosv1beta1.Selector), - EventSourceMap: make(map[corev1.ObjectReference]*libsveltosset.Set), - ToEventSourceMap: make(map[types.NamespacedName]*libsveltosset.Set), - EventTriggerMap: make(map[types.NamespacedName]*libsveltosset.Set), - ReferenceMap: make(map[corev1.ObjectReference]*libsveltosset.Set), - ClusterSetMap: make(map[corev1.ObjectReference]*libsveltosset.Set), - } - resourceScope, err := scope.NewEventTriggerScope(scope.EventTriggerScopeParams{ - Client: c, - Logger: logger, - EventTrigger: resource, - ControllerName: "classifier", - }) - Expect(err).To(BeNil()) - - Expect(controllers.UpdateReferencedResourceMap(&reconciler, resourceScope)).To(Succeed()) - - eventTriggerName := types.NamespacedName{Name: resourceScope.Name()} - v, ok := reconciler.EventTriggerMap[eventTriggerName] - Expect(ok).To(BeTrue()) - Expect(v.Len()).To(Equal(2)) - - Expect(len(reconciler.ReferenceMap)).To(Equal(2)) - index := corev1.ObjectReference{ - Kind: "ConfigMap", - APIVersion: corev1.SchemeGroupVersion.String(), - Namespace: cmNamespace, - Name: cmName, - } - v, ok = reconciler.ReferenceMap[index] - Expect(ok).To(BeTrue()) - Expect(v.Items()).To(ContainElement(corev1.ObjectReference{ - APIVersion: v1beta1.GroupVersion.String(), - Kind: v1beta1.EventTriggerKind, - Name: resourceScope.Name(), - })) - - index = corev1.ObjectReference{ - Kind: "Secret", - APIVersion: corev1.SchemeGroupVersion.String(), - Namespace: secretNamespace, - Name: secretName, - } - v, ok = reconciler.ReferenceMap[index] - Expect(ok).To(BeTrue()) - Expect(v.Items()).To(ContainElement(corev1.ObjectReference{ - APIVersion: v1beta1.GroupVersion.String(), - Kind: v1beta1.EventTriggerKind, - Name: resourceScope.Name(), - })) - }) - It("getClustersFromClusterSets gets cluster selected by referenced clusterSet", func() { clusterSet1 := &libsveltosv1beta1.ClusterSet{ ObjectMeta: metav1.ObjectMeta{ diff --git a/controllers/eventtrigger_deployer.go b/controllers/eventtrigger_deployer.go index b07c952..d090594 100644 --- a/controllers/eventtrigger_deployer.go +++ b/controllers/eventtrigger_deployer.go @@ -62,6 +62,7 @@ const ( // Namespace where reports will be generated ReportNamespace = "projectsveltos" + eventReportNameLabel = "eventtrigger.lib.projectsveltos.io/eventreportname" eventTriggerNameLabel = "eventtrigger.lib.projectsveltos.io/eventtriggername" clusterNamespaceLabel = "eventtrigger.lib.projectsveltos.io/clusterNamespace" clusterNameLabel = "eventtrigger.lib.projectsveltos.io/clustername" @@ -96,7 +97,7 @@ func (r *EventTriggerReconciler) isClusterAShardMatch(ctx context.Context, return sharding.IsShardAMatch(r.ShardKey, cluster), nil } -// deployEventBasedAddon update necessary resources in managed clusters +// deployEventBasedAddon update necessary resources (eventSource) in the managed clusters func (r *EventTriggerReconciler) deployEventTrigger(ctx context.Context, eScope *scope.EventTriggerScope, f feature, logger logr.Logger) error { @@ -163,7 +164,9 @@ func (r *EventTriggerReconciler) deployEventTrigger(ctx context.Context, eScope // undeployEventBasedAddon clean resources in managed clusters func (r *EventTriggerReconciler) undeployEventTrigger(ctx context.Context, eScope *scope.EventTriggerScope, - f feature, logger logr.Logger) error { + clusterInfo []libsveltosv1beta1.ClusterInfo, logger logr.Logger) error { + + f := getHandlersForFeature(v1beta1.FeatureEventTrigger) resource := eScope.EventTrigger @@ -171,28 +174,30 @@ func (r *EventTriggerReconciler) undeployEventTrigger(ctx context.Context, eScop logger.V(logs.LogDebug).Info("request to undeploy") var err error - for i := range resource.Status.ClusterInfo { - shardMatch, tmpErr := r.isClusterAShardMatch(ctx, &resource.Status.ClusterInfo[i]) + for i := range clusterInfo { + shardMatch, tmpErr := r.isClusterAShardMatch(ctx, &clusterInfo[i]) if tmpErr != nil { err = tmpErr continue } - if !shardMatch && resource.Status.ClusterInfo[i].Status != libsveltosv1beta1.SveltosStatusRemoved { + if !shardMatch && clusterInfo[i].Status != libsveltosv1beta1.SveltosStatusRemoved { // If shard is not a match, wait for other controller to remove err = fmt.Errorf("remove pending") continue } - c := &resource.Status.ClusterInfo[i].Cluster + c := &clusterInfo[i].Cluster + logger.V(logs.LogDebug).Info(fmt.Sprintf("undeploy EventTrigger from cluster %s:%s/%s", + c.Kind, c.Namespace, c.Name)) _, tmpErr = r.removeEventTrigger(ctx, eScope, c, f, logger) if tmpErr != nil { err = tmpErr continue } - resource.Status.ClusterInfo[i].Status = libsveltosv1beta1.SveltosStatusRemoved + clusterInfo[i].Status = libsveltosv1beta1.SveltosStatusRemoved } return err @@ -229,14 +234,14 @@ func processEventTriggerForCluster(ctx context.Context, c client.Client, return err } - err = removeStaleEventSources(ctx, c, clusterNamespace, clusterName, clusterType, resource, logger) + err = removeStaleEventSources(ctx, c, clusterNamespace, clusterName, clusterType, resource, false, logger) if err != nil { logger.V(logs.LogDebug).Info("failed to remove stale EventSources") return err } - logger.V(logs.LogDebug).Info("Deployed eventTrigger. Updating ClusterProfiles") - return updateClusterProfiles(ctx, c, clusterNamespace, clusterName, clusterType, resource, logger) + logger.V(logs.LogDebug).Info("Deployed eventTrigger") + return nil } // undeployEventTriggerResourcesFromCluster cleans resources associtated with EventTrigger instance from cluster @@ -259,7 +264,7 @@ func undeployEventTriggerResourcesFromCluster(ctx context.Context, c client.Clie logger = logger.WithValues("cluster", fmt.Sprintf("%s:%s/%s", clusterType, clusterNamespace, clusterName)) logger.V(logs.LogDebug).Info("Undeploy eventTrigger") - err = removeStaleEventSources(ctx, c, clusterNamespace, clusterName, clusterType, resource, logger) + err = removeStaleEventSources(ctx, c, clusterNamespace, clusterName, clusterType, resource, true, logger) if err != nil { logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to remove eventSources: %v", err)) return err @@ -268,7 +273,8 @@ func undeployEventTriggerResourcesFromCluster(ctx context.Context, c client.Clie logger.V(logs.LogDebug).Info("Undeployed eventTrigger.") logger.V(logs.LogDebug).Info("Clearing instantiated ClusterProfile/ConfigMap/Secret instances") - return removeInstantiatedResources(ctx, c, clusterNamespace, clusterName, clusterType, resource, nil, logger) + return removeInstantiatedResources(ctx, c, clusterNamespace, clusterName, clusterType, resource, + nil, nil, logger) } // eventTriggerHash returns the EventTrigger hash @@ -327,6 +333,10 @@ func (r *EventTriggerReconciler) processEventTrigger(ctx context.Context, eScope return nil, nil } + // Remove any queued entry to cleanup + r.Deployer.CleanupEntries(cluster.Namespace, cluster.Name, resource.Name, f.id, + clusterproxy.GetClusterType(cluster), true) + // If undeploying feature is in progress, wait for it to complete. // Otherwise, if we redeploy feature while same feature is still being cleaned up, if two workers process those request in // parallel some resources might end up missing. @@ -443,10 +453,12 @@ func (r *EventTriggerReconciler) removeEventTrigger(ctx context.Context, eScope } if *status == libsveltosv1beta1.SveltosStatusRemoved { + logger.V(logs.LogDebug).Info("status is removed") if err := removeClusterInfoEntry(ctx, r.Client, cluster.Namespace, cluster.Name, clusterproxy.GetClusterType(cluster), resource, logger); err != nil { return nil, err } + clusterInfo.Status = libsveltosv1beta1.SveltosStatusRemoved return clusterInfo, nil } } else { @@ -619,19 +631,13 @@ func deployEventSource(ctx context.Context, c client.Client, clusterNamespace, clusterName string, clusterType libsveltosv1beta1.ClusterType, resource *v1beta1.EventTrigger, logger logr.Logger) error { - eventSourceName, err := libsveltostemplate.GetReferenceResourceName(clusterNamespace, clusterName, - string(clusterType), resource.Spec.EventSourceName) + currentReferenced, err := fetchEventSource(ctx, c, clusterNamespace, clusterName, resource.Spec.EventSourceName, + clusterType, logger) if err != nil { - logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to get EventSource Name %s: %v", + logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to collect EventSource %s: %v", resource.Spec.EventSourceName, err)) return err } - - currentReferenced, err := fetchEventSource(ctx, c, eventSourceName) - if err != nil { - logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to collect EventSource %s: %v", eventSourceName, err)) - return err - } if currentReferenced == nil { logger.V(logs.LogInfo).Info("EventSource not found") return nil @@ -696,7 +702,7 @@ func createOrUpdateEventSource(ctx context.Context, remoteClient client.Client, // An EventSource with zero OwnerReference will be deleted from managed cluster. func removeStaleEventSources(ctx context.Context, c client.Client, clusterNamespace, clusterName string, clusterType libsveltosv1beta1.ClusterType, - resource *v1beta1.EventTrigger, logger logr.Logger) error { + resource *v1beta1.EventTrigger, removeAll bool, logger logr.Logger) error { remoteClient, err := clusterproxy.GetKubernetesClient(ctx, c, clusterNamespace, clusterName, "", "", clusterType, logger) @@ -716,7 +722,9 @@ func removeStaleEventSources(ctx context.Context, c client.Client, es := &eventSources.Items[i] l := logger.WithValues("eventsource", es.Name) - if resource.DeletionTimestamp.IsZero() && + // removeAll indicates all EventSources deployed by this EventTrigger on this cluster + // need to be removed (cluster is no longer a match) + if !removeAll && resource.DeletionTimestamp.IsZero() && es.Name == resource.Spec.EventSourceName { // eventTrigger still exists and eventSource is still referenced continue @@ -776,53 +784,38 @@ type currentObject struct { // - HelmCharts instantiated from EventTrigger.Spec.HelmCharts using values from resources, collected // from the managed cluster, matching the EventSource referenced by EventTrigger func updateClusterProfiles(ctx context.Context, c client.Client, clusterNamespace, clusterName string, - clusterType libsveltosv1beta1.ClusterType, eventTrigger *v1beta1.EventTrigger, + clusterType libsveltosv1beta1.ClusterType, eventTrigger *v1beta1.EventTrigger, er *libsveltosv1beta1.EventReport, logger logr.Logger) error { - // Get EventReport - eventReports, err := fetchEventReports(ctx, c, clusterNamespace, clusterName, eventTrigger.Spec.EventSourceName, - clusterType) - if err != nil { - return err - } - - if len(eventReports.Items) == 0 { - return removeInstantiatedResources(ctx, c, clusterNamespace, clusterName, clusterType, - eventTrigger, nil, logger) - } - - if len(eventReports.Items) > 1 { - msg := "found more than one EventReport for a given EventSource/cluster" - logger.V(logs.LogInfo).Info(msg) - return fmt.Errorf("%s", msg) - } - // If no resource is currently matching, clear all - if len(eventReports.Items[0].Spec.MatchingResources) == 0 { + if !er.DeletionTimestamp.IsZero() || len(er.Spec.MatchingResources) == 0 { return removeInstantiatedResources(ctx, c, clusterNamespace, clusterName, clusterType, - eventTrigger, nil, logger) + eventTrigger, er, nil, logger) } + var err error var clusterProfiles []*configv1beta1.ClusterProfile if eventTrigger.Spec.OneForEvent { clusterProfiles, err = instantiateOneClusterProfilePerResource(ctx, c, clusterNamespace, clusterName, - clusterType, eventTrigger, &eventReports.Items[0], logger) + clusterType, eventTrigger, er, logger) if err != nil { - logger.V(logs.LogInfo).Info("failed to create one clusterProfile instance per matching resource") + logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to create one clusterProfile instance per matching resource: %v", + err)) return err } } else { - clusterProfiles, err = instantiateOneClusterProfilePerAllResource(ctx, c, clusterNamespace, - clusterName, clusterType, eventTrigger, &eventReports.Items[0], logger) + clusterProfiles, err = instantiateOneClusterProfilePerAllResource(ctx, c, clusterNamespace, clusterName, + clusterType, eventTrigger, er, logger) if err != nil { - logger.V(logs.LogInfo).Info("failed to create one clusterProfile instance per matching resource") + logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to create one clusterProfile instance per matching resource: %v", + err)) return err } } // Remove stale ClusterProfiles/ConfigMaps/Secrets, i.e, resources previously created by this EventTrigger // instance for this cluster but currently not needed anymore - return removeInstantiatedResources(ctx, c, clusterNamespace, clusterName, clusterType, eventTrigger, + return removeInstantiatedResources(ctx, c, clusterNamespace, clusterName, clusterType, eventTrigger, er, clusterProfiles, logger) } @@ -846,8 +839,8 @@ func instantiateOneClusterProfilePerResource(ctx context.Context, c client.Clien if len(resources) == 0 { for i := range eventReport.Spec.MatchingResources { var clusterProfile *configv1beta1.ClusterProfile - clusterProfile, err = instantiateClusterProfileForResource(ctx, c, clusterNamespace, clusterName, clusterType, eventTrigger, - &eventReport.Spec.MatchingResources[i], nil, logger) + clusterProfile, err = instantiateClusterProfileForResource(ctx, c, clusterNamespace, clusterName, + clusterType, eventTrigger, eventReport, &eventReport.Spec.MatchingResources[i], nil, logger) if err != nil { return nil, err } @@ -859,9 +852,13 @@ func instantiateOneClusterProfilePerResource(ctx context.Context, c client.Clien for i := range resources { r := &resources[i] var clusterProfile *configv1beta1.ClusterProfile - matchingResource := corev1.ObjectReference{APIVersion: r.GetAPIVersion(), Kind: r.GetKind(), Namespace: r.GetNamespace(), Name: r.GetName()} - clusterProfile, err = instantiateClusterProfileForResource(ctx, c, clusterNamespace, clusterName, clusterType, eventTrigger, - &matchingResource, r, logger) + matchingResource := corev1.ObjectReference{ + APIVersion: r.GetAPIVersion(), Kind: r.GetKind(), + Namespace: r.GetNamespace(), Name: r.GetName(), + } + + clusterProfile, err = instantiateClusterProfileForResource(ctx, c, clusterNamespace, clusterName, + clusterType, eventTrigger, eventReport, &matchingResource, r, logger) if err != nil { return nil, err } @@ -879,24 +876,20 @@ func instantiateOneClusterProfilePerResource(ctx context.Context, c client.Clien // in new ConfigMaps/Secrets and have ClusterProfile.Spec.PolicyRefs reference those; // - labels are added to ClusterProfile to easily fetch all ClusterProfiles created by a given EventTrigger func instantiateClusterProfileForResource(ctx context.Context, c client.Client, clusterNamespace, clusterName string, - clusterType libsveltosv1beta1.ClusterType, eventTrigger *v1beta1.EventTrigger, + clusterType libsveltosv1beta1.ClusterType, eventTrigger *v1beta1.EventTrigger, er *libsveltosv1beta1.EventReport, matchingResource *corev1.ObjectReference, resource *unstructured.Unstructured, logger logr.Logger, ) (*configv1beta1.ClusterProfile, error) { - object := ¤tObject{ - MatchingResource: *matchingResource, - } - if resource != nil { - object.Resource = resource.UnstructuredContent() - } - cluster, err := fecthClusterObjects(ctx, c, clusterNamespace, clusterName, clusterType, logger) + object, err := prepareCurrentObject(ctx, c, clusterNamespace, clusterName, clusterType, resource, + matchingResource, logger) if err != nil { - logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to get cluster %v", err)) + logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to prepare currentObject %v", err)) return nil, err } - object.Cluster = cluster - labels := getInstantiatedObjectLabels(clusterNamespace, clusterName, eventTrigger.Name, clusterType) + labels := getInstantiatedObjectLabels(clusterNamespace, clusterName, eventTrigger.Name, + er, clusterType) + tmpLabels := getInstantiatedObjectLabelsForResource(matchingResource.Namespace, matchingResource.Name) for k, v := range tmpLabels { labels[k] = v @@ -918,25 +911,16 @@ func instantiateClusterProfileForResource(ctx context.Context, c client.Client, } } - clusterProfile := &configv1beta1.ClusterProfile{ - ObjectMeta: metav1.ObjectMeta{ - Name: clusterProfileName, - Labels: labels, - }, - Spec: configv1beta1.Spec{ - StopMatchingBehavior: eventTrigger.Spec.StopMatchingBehavior, - SyncMode: eventTrigger.Spec.SyncMode, - Tier: eventTrigger.Spec.Tier, - ContinueOnConflict: eventTrigger.Spec.ContinueOnConflict, - Reloader: eventTrigger.Spec.Reloader, - MaxUpdate: eventTrigger.Spec.MaxUpdate, - TemplateResourceRefs: eventTrigger.Spec.TemplateResourceRefs, - ValidateHealths: eventTrigger.Spec.ValidateHealths, - Patches: eventTrigger.Spec.Patches, - ExtraLabels: eventTrigger.Spec.ExtraLabels, - ExtraAnnotations: eventTrigger.Spec.ExtraAnnotations, - }, + clusterProfile := getNonInstantiatedClusterProfile(eventTrigger, clusterProfileName, labels) + + templateName := getTemplateName(clusterNamespace, clusterName, eventTrigger.Name) + templateResourceRefs, err := instantiateTemplateResourceRefs(templateName, object.Cluster, object, + eventTrigger.Spec.TemplateResourceRefs) + if err != nil { + logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to instantiate TemplateResourceRefs: %v", err)) + return nil, err } + clusterProfile.Spec.TemplateResourceRefs = templateResourceRefs if reflect.DeepEqual(eventTrigger.Spec.DestinationClusterSelector, libsveltosv1beta1.Selector{}) { clusterProfile.Spec.ClusterRefs = []corev1.ObjectReference{*getClusterRef(clusterNamespace, clusterName, clusterType)} @@ -946,7 +930,6 @@ func instantiateClusterProfileForResource(ctx context.Context, c client.Client, clusterProfile.Spec.ClusterSelector = eventTrigger.Spec.DestinationClusterSelector } - templateName := getTemplateName(clusterNamespace, clusterName, eventTrigger.Name) instantiateHelmChartsWithResource, err := instantiateHelmChartsWithResource(ctx, c, clusterNamespace, templateName, eventTrigger.Spec.HelmCharts, object, labels, logger) if err != nil { @@ -962,14 +945,12 @@ func instantiateClusterProfileForResource(ctx context.Context, c client.Client, clusterProfile.Spec.KustomizationRefs = instantiateKustomizeRefsWithResource clusterRef := getClusterRef(clusterNamespace, clusterName, clusterType) - policyRef, err := instantiateReferencedPolicies(ctx, c, templateName, eventTrigger, clusterRef, object, labels, logger) + localPolicyRef, remotePolicyRef, err := instantiateReferencedPolicies(ctx, c, templateName, + eventTrigger, clusterRef, object, labels, logger) if err != nil { return nil, err } - clusterProfile.Spec.PolicyRefs = getClusterProfilePolicyRefs(policyRef) - - // TODO: is it needed to instantiate kustomize files? - clusterProfile.Spec.KustomizationRefs = eventTrigger.Spec.KustomizationRefs + clusterProfile.Spec.PolicyRefs = getClusterProfilePolicyRefs(localPolicyRef, remotePolicyRef) if createClusterProfile { return clusterProfile, c.Create(ctx, clusterProfile) @@ -995,23 +976,15 @@ func instantiateOneClusterProfilePerAllResource(ctx context.Context, c client.Cl return nil, err } - values := make([]map[string]interface{}, len(resources)) - for i := range resources { - values[i] = resources[i].UnstructuredContent() - } - cluster, err := fecthClusterObjects(ctx, c, clusterNamespace, clusterName, clusterType, logger) + objects, err := prepareCurrentObjects(ctx, c, clusterNamespace, clusterName, clusterType, + eventReport, resources, logger) if err != nil { - logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to get cluster %v", err)) + logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to prepare currentObjects %v", err)) return nil, err } - objects := ¤tObjects{ - MatchingResources: eventReport.Spec.MatchingResources, - Resources: values, - Cluster: cluster, - } - - labels := getInstantiatedObjectLabels(clusterNamespace, clusterName, eventTrigger.Name, clusterType) + labels := getInstantiatedObjectLabels(clusterNamespace, clusterName, eventTrigger.Name, + eventReport, clusterType) clusterProfileName, createClusterProfile, err := getClusterProfileName(ctx, c, labels) if err != nil { @@ -1029,25 +1002,16 @@ func instantiateOneClusterProfilePerAllResource(ctx context.Context, c client.Cl } } - clusterProfile := &configv1beta1.ClusterProfile{ - ObjectMeta: metav1.ObjectMeta{ - Name: clusterProfileName, - Labels: labels, - }, - Spec: configv1beta1.Spec{ - StopMatchingBehavior: eventTrigger.Spec.StopMatchingBehavior, - SyncMode: eventTrigger.Spec.SyncMode, - Tier: eventTrigger.Spec.Tier, - ContinueOnConflict: eventTrigger.Spec.ContinueOnConflict, - Reloader: eventTrigger.Spec.Reloader, - MaxUpdate: eventTrigger.Spec.MaxUpdate, - TemplateResourceRefs: eventTrigger.Spec.TemplateResourceRefs, - ValidateHealths: eventTrigger.Spec.ValidateHealths, - Patches: eventTrigger.Spec.Patches, - ExtraLabels: eventTrigger.Spec.ExtraLabels, - ExtraAnnotations: eventTrigger.Spec.ExtraAnnotations, - }, + clusterProfile := getNonInstantiatedClusterProfile(eventTrigger, clusterProfileName, labels) + + templateName := getTemplateName(clusterNamespace, clusterName, eventTrigger.Name) + templateResourceRefs, err := instantiateTemplateResourceRefs(templateName, objects.Cluster, objects, + eventTrigger.Spec.TemplateResourceRefs) + if err != nil { + logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to instantiate TemplateResourceRefs: %v", err)) + return nil, err } + clusterProfile.Spec.TemplateResourceRefs = templateResourceRefs if reflect.DeepEqual(eventTrigger.Spec.DestinationClusterSelector, libsveltosv1beta1.Selector{}) { clusterProfile.Spec.ClusterRefs = []corev1.ObjectReference{*getClusterRef(clusterNamespace, clusterName, clusterType)} @@ -1057,7 +1021,6 @@ func instantiateOneClusterProfilePerAllResource(ctx context.Context, c client.Cl clusterProfile.Spec.ClusterSelector = eventTrigger.Spec.DestinationClusterSelector } - templateName := getTemplateName(clusterNamespace, clusterName, eventTrigger.Name) instantiateHelmChartsWithResources, err := instantiateHelmChartsWithAllResources(ctx, c, clusterNamespace, templateName, eventTrigger.Spec.HelmCharts, objects, labels, logger) if err != nil { @@ -1073,11 +1036,12 @@ func instantiateOneClusterProfilePerAllResource(ctx context.Context, c client.Cl clusterProfile.Spec.KustomizationRefs = instantiateKustomizeRefsWithResource clusterRef := getClusterRef(clusterNamespace, clusterName, clusterType) - policyRef, err := instantiateReferencedPolicies(ctx, c, templateName, eventTrigger, clusterRef, objects, labels, logger) + localPolicyRef, remotePolicyRef, err := instantiateReferencedPolicies(ctx, c, templateName, + eventTrigger, clusterRef, objects, labels, logger) if err != nil { return nil, err } - clusterProfile.Spec.PolicyRefs = getClusterProfilePolicyRefs(policyRef) + clusterProfile.Spec.PolicyRefs = getClusterProfilePolicyRefs(localPolicyRef, remotePolicyRef) if createClusterProfile { return []*configv1beta1.ClusterProfile{clusterProfile}, c.Create(ctx, clusterProfile) @@ -1086,21 +1050,42 @@ func instantiateOneClusterProfilePerAllResource(ctx context.Context, c client.Cl return []*configv1beta1.ClusterProfile{clusterProfile}, updateClusterProfileSpec(ctx, c, clusterProfile, logger) } -func getClusterProfilePolicyRefs(policyRef *libsveltosset.Set) []configv1beta1.PolicyRef { - result := make([]configv1beta1.PolicyRef, policyRef.Len()) +func getClusterProfilePolicyRefs(localPolicyRef, remotePolicyRef *libsveltosset.Set) []configv1beta1.PolicyRef { + result := make([]configv1beta1.PolicyRef, localPolicyRef.Len()+remotePolicyRef.Len()) + + secret := "Secret" - items := policyRef.Items() + // Add local policyRef + items := localPolicyRef.Items() for i := range items { kind := libsveltosv1beta1.ConfigMapReferencedResourceKind - if items[i].Kind == "Secret" { + if items[i].Kind == secret { kind = libsveltosv1beta1.SecretReferencedResourceKind } result[i] = configv1beta1.PolicyRef{ - Namespace: items[i].Namespace, - Name: items[i].Name, - Kind: string(kind), + DeploymentType: configv1beta1.DeploymentTypeLocal, + Namespace: items[i].Namespace, + Name: items[i].Name, + Kind: string(kind), + } + } + + numOfPolicyItems := localPolicyRef.Len() + // Add remote policyRef + items = remotePolicyRef.Items() + for i := range items { + kind := libsveltosv1beta1.ConfigMapReferencedResourceKind + if items[i].Kind == secret { + kind = libsveltosv1beta1.SecretReferencedResourceKind + } + result[numOfPolicyItems+i] = configv1beta1.PolicyRef{ + DeploymentType: configv1beta1.DeploymentTypeRemote, + Namespace: items[i].Namespace, + Name: items[i].Name, + Kind: string(kind), } } + return result } @@ -1256,14 +1241,16 @@ func instantiateValuesFrom(ctx context.Context, c client.Client, valuesFrom []co return err } + var info *types.NamespacedName if _, ok := resource.GetAnnotations()[libsveltosv1beta1.PolicyTemplateAnnotation]; !ok { // referenced ConfigMap/Secret is not a template. So there is no // need to instantiate a new one. Generated ClusterProfile can directly // reference this one - continue + info = &types.NamespacedName{Namespace: resource.GetNamespace(), Name: resource.GetName()} + } else { + info, err = instantiateReferencedPolicy(ctx, c, resource, templateName, data, labels, logger) } - info, err := instantiateReferencedPolicy(ctx, c, resource, templateName, data, labels, logger) if err != nil { msg := fmt.Sprintf("failed to instantiate content for ValuesFrom: %s/%s", resource.GetNamespace(), resource.GetName()) @@ -1310,6 +1297,37 @@ func instantiateDataSection(templateName string, content map[string]string, data return instantiatedContent, nil } +func instantiateTemplateResourceRefs(templateName string, clusterContent map[string]interface{}, data any, + templateResourceRefs []configv1beta1.TemplateResourceRef) ([]configv1beta1.TemplateResourceRef, error) { + + var uCluster unstructured.Unstructured + uCluster.SetUnstructuredContent(clusterContent) + + instantiated := make([]configv1beta1.TemplateResourceRef, len(templateResourceRefs)) + for i := range templateResourceRefs { + namespace := libsveltostemplate.GetReferenceResourceNamespace(uCluster.GetNamespace(), + templateResourceRefs[i].Resource.Namespace) + + tmpl, err := template.New(templateName).Option("missingkey=error").Funcs( + funcmap.SveltosFuncMap()).Parse(templateResourceRefs[i].Resource.Name) + if err != nil { + return nil, err + } + + var buffer bytes.Buffer + err = tmpl.Execute(&buffer, data) + if err != nil { + return nil, err + } + + instantiated[i] = templateResourceRefs[i] + instantiated[i].Resource.Namespace = namespace + instantiated[i].Resource.Name = buffer.String() + } + + return instantiated, nil +} + // instantiateHelmChartsWithResource instantiate eventTrigger.Spec.HelmCharts using information from passed in object // which represents one of the resource matching referenced EventSource in the managed cluster. func instantiateHelmChartsWithResource(ctx context.Context, c client.Client, clusterNamespace, templateName string, @@ -1350,35 +1368,57 @@ func instantiateKustomizationRefsWithAllResources(ctx context.Context, c client. // which represent all of the resources matching referenced EventSource in the managed cluster. func instantiateReferencedPolicies(ctx context.Context, c client.Client, templateName string, eventTrigger *v1beta1.EventTrigger, cluster *corev1.ObjectReference, objects any, - labels map[string]string, logger logr.Logger) (*libsveltosset.Set, error) { - - result := libsveltosset.Set{} + labels map[string]string, logger logr.Logger) (localSet, remoteSet *libsveltosset.Set, err error) { // fetches all referenced ConfigMaps/Secrets - refs, err := fetchPolicyRefs(ctx, c, eventTrigger, cluster, logger) + var local []client.Object + var remote []client.Object + local, remote, err = fetchPolicyRefs(ctx, c, eventTrigger, cluster, objects, templateName, logger) if err != nil { - return nil, err + return nil, nil, err } + localSet, err = instantiateResources(ctx, c, templateName, local, objects, labels, logger) + if err != nil { + return nil, nil, err + } + remoteSet, err = instantiateResources(ctx, c, templateName, remote, objects, labels, logger) + if err != nil { + return nil, nil, err + } + + return +} + +func instantiateResources(ctx context.Context, c client.Client, templateName string, resources []client.Object, + objects any, labels map[string]string, logger logr.Logger) (*libsveltosset.Set, error) { + + result := libsveltosset.Set{} + // For each referenced resource, instantiate it using objects collected from managed cluster // and create/update corresponding ConfigMap/Secret in managemenent cluster - for i := range refs { - ref := refs[i] + for i := range resources { + ref := resources[i] apiVersion, kind := ref.GetObjectKind().GroupVersionKind().ToAPIVersionAndKind() + l := logger.WithValues("referencedResource", fmt.Sprintf("%s:%s/%s", + ref.GetObjectKind().GroupVersionKind().Kind, ref.GetNamespace(), ref.GetName())) + l.V(logs.LogDebug).Info("process referenced resource") + var info *types.NamespacedName + var err error if _, ok := ref.GetAnnotations()[libsveltosv1beta1.PolicyTemplateAnnotation]; !ok { // referenced ConfigMap/Secret is not a template. So there is no // need to instantiate a new one. Generated ClusterProfile can directly // reference this one - result.Insert(&corev1.ObjectReference{APIVersion: apiVersion, Kind: kind, - Namespace: ref.GetNamespace(), Name: ref.GetName()}) - continue + info = &types.NamespacedName{Namespace: ref.GetNamespace(), Name: ref.GetName()} + } else { + info, err = instantiateReferencedPolicy(ctx, c, ref, templateName, objects, labels, logger) } - info, err := instantiateReferencedPolicy(ctx, c, ref, templateName, objects, labels, logger) if err != nil { return nil, err } + result.Insert(&corev1.ObjectReference{APIVersion: apiVersion, Kind: kind, Namespace: info.Namespace, Name: info.Name}) } @@ -1390,16 +1430,21 @@ func instantiateReferencedPolicy(ctx context.Context, c client.Client, ref clien templateName string, objects any, labels map[string]string, logger logr.Logger, ) (*types.NamespacedName, error) { + l := logger.WithValues("referencedResource", + fmt.Sprintf("%s:%s/%s", ref.GetObjectKind(), ref.GetNamespace(), ref.GetName())) + + content := getDataSection(ref) // If referenced resource is a template, assume it needs to be instantiated using // information from the resources in the managed cluster that generated the event. // Generate then a new ConfigMap/Secret. The autocreated ClusterProfile will reference // this new resource. - content := getDataSection(ref) - instantiatedContent, err := instantiateDataSection(templateName, content, objects, logger) + instantiatedContent, err := instantiateDataSection(templateName, content, objects, l) if err != nil { + l.V(logs.LogInfo).Info(fmt.Sprintf("failed to instantiated referenced resource content: %v", err)) return nil, err } + content = instantiatedContent // Resource name must depend on reference resource name as well. So add those labels. // If an EventTrigger is referencing N configMaps/Secrets, N equivalent referenced @@ -1416,7 +1461,7 @@ func instantiateReferencedPolicy(ctx context.Context, c client.Client, ref clien if create { logger.V(logs.LogDebug).Info(fmt.Sprintf("create resource for %s %s:%s", ref.GetObjectKind().GroupVersionKind().Kind, ref.GetNamespace(), ref.GetName())) - err = createResource(ctx, c, ref, name, labels, instantiatedContent) + err = createResource(ctx, c, ref, name, labels, content) if err != nil { logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to create resource: %v", err)) return nil, err @@ -1424,7 +1469,7 @@ func instantiateReferencedPolicy(ctx context.Context, c client.Client, ref clien } else { logger.V(logs.LogDebug).Info(fmt.Sprintf("update resource for %s %s:%s", ref.GetObjectKind().GroupVersionKind().Kind, ref.GetNamespace(), ref.GetName())) - err = updateResource(ctx, c, ref, name, labels, instantiatedContent) + err = updateResource(ctx, c, ref, name, labels, content) if err != nil { logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to update resource: %v", err)) return nil, err @@ -1691,14 +1736,21 @@ func getInstantiatedObjectName(objects []client.Object) (name string, create boo // getInstantiatedObjectLabels returns the labels to add to a ClusterProfile created // by an EventTrigger for a given cluster func getInstantiatedObjectLabels(clusterNamespace, clusterName, eventTriggerName string, - clusterType libsveltosv1beta1.ClusterType) map[string]string { + er *libsveltosv1beta1.EventReport, clusterType libsveltosv1beta1.ClusterType) map[string]string { - return map[string]string{ + labels := map[string]string{ eventTriggerNameLabel: eventTriggerName, clusterNamespaceLabel: clusterNamespace, clusterNameLabel: clusterName, clusterTypeLabel: string(clusterType), } + + // When deleting all resources created by an EventTrigger, er will be nil + if er != nil { + labels[eventReportNameLabel] = er.Name + } + + return labels } // getInstantiatedObjectLabelsForResource returns the label to add to a ClusterProfile created @@ -1716,10 +1768,10 @@ func getInstantiatedObjectLabelsForResource(resourceNamespace, resourceName stri } func removeInstantiatedResources(ctx context.Context, c client.Client, clusterNamespace, clusterName string, - clusterType libsveltosv1beta1.ClusterType, eventTrigger *v1beta1.EventTrigger, + clusterType libsveltosv1beta1.ClusterType, eventTrigger *v1beta1.EventTrigger, er *libsveltosv1beta1.EventReport, clusterProfiles []*configv1beta1.ClusterProfile, logger logr.Logger) error { - if err := removeClusterProfiles(ctx, c, clusterNamespace, clusterName, clusterType, eventTrigger, + if err := removeClusterProfiles(ctx, c, clusterNamespace, clusterName, clusterType, eventTrigger, er, clusterProfiles, logger); err != nil { logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to remove stale clusterProfiles: %v", err)) return err @@ -1740,13 +1792,13 @@ func removeInstantiatedResources(ctx context.Context, c client.Client, clusterNa } if err := removeConfigMaps(ctx, c, clusterNamespace, clusterName, clusterType, eventTrigger, - policyRefs, logger); err != nil { + er, policyRefs, logger); err != nil { logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to remove stale configMaps: %v", err)) return err } if err := removeSecrets(ctx, c, clusterNamespace, clusterName, clusterType, eventTrigger, - policyRefs, logger); err != nil { + er, policyRefs, logger); err != nil { logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to remove stale secrets: %v", err)) return err } @@ -1792,10 +1844,11 @@ func appendKustomizationRefValuesFrom(policyRefs map[libsveltosv1beta1.PolicyRef // policyRefs arg represents all the ConfigMap the EventTrigger instance is currently managing for the // given cluster func removeConfigMaps(ctx context.Context, c client.Client, clusterNamespace, clusterName string, - clusterType libsveltosv1beta1.ClusterType, eventTrigger *v1beta1.EventTrigger, + clusterType libsveltosv1beta1.ClusterType, eventTrigger *v1beta1.EventTrigger, er *libsveltosv1beta1.EventReport, policyRefs map[libsveltosv1beta1.PolicyRef]bool, logger logr.Logger) error { - labels := getInstantiatedObjectLabels(clusterNamespace, clusterName, eventTrigger.Name, clusterType) + labels := getInstantiatedObjectLabels(clusterNamespace, clusterName, eventTrigger.Name, + er, clusterType) listOptions := []client.ListOption{ client.MatchingLabels(labels), @@ -1828,10 +1881,11 @@ func removeConfigMaps(ctx context.Context, c client.Client, clusterNamespace, cl // policyRefs arg represents all the ConfigMap the EventTrigger instance is currently managing for the // given cluster func removeSecrets(ctx context.Context, c client.Client, clusterNamespace, clusterName string, - clusterType libsveltosv1beta1.ClusterType, eventTrigger *v1beta1.EventTrigger, + clusterType libsveltosv1beta1.ClusterType, eventTrigger *v1beta1.EventTrigger, er *libsveltosv1beta1.EventReport, policyRefs map[libsveltosv1beta1.PolicyRef]bool, logger logr.Logger) error { - labels := getInstantiatedObjectLabels(clusterNamespace, clusterName, eventTrigger.Name, clusterType) + labels := getInstantiatedObjectLabels(clusterNamespace, clusterName, eventTrigger.Name, + er, clusterType) listOptions := []client.ListOption{ client.MatchingLabels(labels), @@ -1864,7 +1918,7 @@ func removeSecrets(ctx context.Context, c client.Client, clusterNamespace, clust // clusterProfiles arg represents all the ClusterProfiles the EventTrigger instance is currently managing for the // given cluster func removeClusterProfiles(ctx context.Context, c client.Client, clusterNamespace, clusterName string, - clusterType libsveltosv1beta1.ClusterType, eventTrigger *v1beta1.EventTrigger, + clusterType libsveltosv1beta1.ClusterType, eventTrigger *v1beta1.EventTrigger, er *libsveltosv1beta1.EventReport, clusterProfiles []*configv1beta1.ClusterProfile, logger logr.Logger) error { // Build a map of current ClusterProfiles for faster indexing @@ -1875,7 +1929,8 @@ func removeClusterProfiles(ctx context.Context, c client.Client, clusterNamespac currentClusterProfiles[clusterProfiles[i].Name] = true } - labels := getInstantiatedObjectLabels(clusterNamespace, clusterName, eventTrigger.Name, clusterType) + labels := getInstantiatedObjectLabels(clusterNamespace, clusterName, eventTrigger.Name, + er, clusterType) listOptions := []client.ListOption{ client.MatchingLabels(labels), @@ -1933,3 +1988,68 @@ func fecthClusterObjects(ctx context.Context, c client.Client, } return runtime.DefaultUnstructuredConverter.ToUnstructured(genericCluster) } + +func getNonInstantiatedClusterProfile(eventTrigger *v1beta1.EventTrigger, + clusterProfileName string, labels map[string]string) *configv1beta1.ClusterProfile { + + return &configv1beta1.ClusterProfile{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterProfileName, + Labels: labels, + }, + Spec: configv1beta1.Spec{ + StopMatchingBehavior: eventTrigger.Spec.StopMatchingBehavior, + SyncMode: eventTrigger.Spec.SyncMode, + Tier: eventTrigger.Spec.Tier, + ContinueOnConflict: eventTrigger.Spec.ContinueOnConflict, + Reloader: eventTrigger.Spec.Reloader, + MaxUpdate: eventTrigger.Spec.MaxUpdate, + TemplateResourceRefs: nil, // this needs to be instantiated + ValidateHealths: eventTrigger.Spec.ValidateHealths, + Patches: eventTrigger.Spec.Patches, + ExtraLabels: eventTrigger.Spec.ExtraLabels, + ExtraAnnotations: eventTrigger.Spec.ExtraAnnotations, + }, + } +} + +func prepareCurrentObjects(ctx context.Context, c client.Client, clusterNamespace, clusterName string, + clusterType libsveltosv1beta1.ClusterType, eventReport *libsveltosv1beta1.EventReport, + resources []unstructured.Unstructured, logger logr.Logger) (*currentObjects, error) { + + values := make([]map[string]interface{}, len(resources)) + for i := range resources { + values[i] = resources[i].UnstructuredContent() + } + cluster, err := fecthClusterObjects(ctx, c, clusterNamespace, clusterName, clusterType, logger) + if err != nil { + logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to get cluster %v", err)) + return nil, err + } + + return ¤tObjects{ + MatchingResources: eventReport.Spec.MatchingResources, + Resources: values, + Cluster: cluster, + }, nil +} + +func prepareCurrentObject(ctx context.Context, c client.Client, clusterNamespace, clusterName string, + clusterType libsveltosv1beta1.ClusterType, resource *unstructured.Unstructured, + matchingResource *corev1.ObjectReference, logger logr.Logger) (*currentObject, error) { + + object := ¤tObject{ + MatchingResource: *matchingResource, + } + if resource != nil { + object.Resource = resource.UnstructuredContent() + } + cluster, err := fecthClusterObjects(ctx, c, clusterNamespace, clusterName, clusterType, logger) + if err != nil { + logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to get cluster %v", err)) + return nil, err + } + object.Cluster = cluster + + return object, nil +} diff --git a/controllers/eventtrigger_deployer_test.go b/controllers/eventtrigger_deployer_test.go index 59c46b9..5a45969 100644 --- a/controllers/eventtrigger_deployer_test.go +++ b/controllers/eventtrigger_deployer_test.go @@ -344,8 +344,7 @@ var _ = Describe("EventTrigger deployer", func() { config += render.AsCode(e.Labels) config += render.AsCode(eventSource.Spec) config += render.AsCode(eventReport.Spec) - config += render.AsCode(configMap.Data) - config += render.AsCode(secret.Data) + // Content of referenced resources in PolicyRef/ValuesFrom is not included h := sha256.New() h.Write([]byte(config)) expectedHash := h.Sum(nil) @@ -443,7 +442,7 @@ var _ = Describe("EventTrigger deployer", func() { // Test has EventTrigger instance reference this EventSource instance. // RemoveStaleEventSources will not remove the EventSource test created. Expect(controllers.RemoveStaleEventSources(context.TODO(), testEnv.Client, clusterNamespace, clusterName, clusterType, - resource, logger)).To(Succeed()) + resource, false, logger)).To(Succeed()) Consistently(func() bool { currentEventSource := &libsveltosv1beta1.EventSource{} @@ -467,7 +466,7 @@ var _ = Describe("EventTrigger deployer", func() { // Test has EventTrigger instance reference a different EventSource. // RemoveStaleEventSources will remove the EventSource test created. Expect(controllers.RemoveStaleEventSources(context.TODO(), testEnv.Client, clusterNamespace, clusterName, clusterType, - currentResource, logger)).To(Succeed()) + currentResource, false, logger)).To(Succeed()) Eventually(func() bool { currentEventSource := &libsveltosv1beta1.EventSource{} @@ -758,7 +757,8 @@ var _ = Describe("EventTrigger deployer", func() { eventTrigger, eventReport, logger) Expect(err).To(BeNil()) - labels := controllers.GetInstantiatedObjectLabels(clusterNamespace, clusterName, eventTrigger.Name, clusterType) + labels := controllers.GetInstantiatedObjectLabels(clusterNamespace, clusterName, eventTrigger.Name, + eventReport, clusterType) listOptions := []client.ListOption{ client.MatchingLabels(labels), @@ -860,7 +860,8 @@ var _ = Describe("EventTrigger deployer", func() { eventTrigger, eventReport, logger) Expect(err).To(BeNil()) - labels := controllers.GetInstantiatedObjectLabels(clusterNamespace, clusterName, eventTrigger.Name, clusterType) + labels := controllers.GetInstantiatedObjectLabels(clusterNamespace, clusterName, eventTrigger.Name, + eventReport, clusterType) listOptions := []client.ListOption{ client.MatchingLabels(labels), @@ -887,24 +888,36 @@ var _ = Describe("EventTrigger deployer", func() { clusterName := randomString() clusterType := libsveltosv1beta1.ClusterTypeSveltos + eventSourceName := randomString() + eventReport := &libsveltosv1beta1.EventReport{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + Labels: map[string]string{ + libsveltosv1beta1.EventSourceNameLabel: eventSourceName, + }, + }, + } + clusterProfile := &configv1beta1.ClusterProfile{ ObjectMeta: metav1.ObjectMeta{ - Name: randomString(), - Labels: controllers.GetInstantiatedObjectLabels(clusterNamespace, clusterName, eventTriggerName, clusterType), + Name: randomString(), + Labels: controllers.GetInstantiatedObjectLabels(clusterNamespace, clusterName, eventTriggerName, + eventReport, clusterType), }, } toBeRemovedClusterProfile := &configv1beta1.ClusterProfile{ ObjectMeta: metav1.ObjectMeta{ - Name: randomString(), - Labels: controllers.GetInstantiatedObjectLabels(clusterNamespace, clusterName, eventTriggerName, clusterType), + Name: randomString(), + Labels: controllers.GetInstantiatedObjectLabels(clusterNamespace, clusterName, eventTriggerName, + eventReport, clusterType), }, } eventTrigger := &v1beta1.EventTrigger{ ObjectMeta: metav1.ObjectMeta{Name: eventTriggerName}, Spec: v1beta1.EventTriggerSpec{ - EventSourceName: randomString(), + EventSourceName: eventSourceName, HelmCharts: []configv1beta1.HelmChart{ { RepositoryName: randomString(), @@ -927,7 +940,7 @@ var _ = Describe("EventTrigger deployer", func() { WithObjects(initObjects...).Build() Expect(controllers.RemoveClusterProfiles(context.TODO(), c, clusterNamespace, clusterName, clusterType, eventTrigger, - []*configv1beta1.ClusterProfile{clusterProfile}, logger)).To(Succeed()) + eventReport, []*configv1beta1.ClusterProfile{clusterProfile}, logger)).To(Succeed()) clusterProfiles := &configv1beta1.ClusterProfileList{} Expect(c.List(context.TODO(), clusterProfiles)).To(Succeed()) @@ -941,9 +954,20 @@ var _ = Describe("EventTrigger deployer", func() { clusterName := randomString() clusterType := libsveltosv1beta1.ClusterTypeSveltos + eventSourceName := randomString() + eventReport := &libsveltosv1beta1.EventReport{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + Labels: map[string]string{ + libsveltosv1beta1.EventSourceNameLabel: eventSourceName, + }, + }, + } + c := fake.NewClientBuilder().WithScheme(scheme).Build() - labels := controllers.GetInstantiatedObjectLabels(clusterNamespace, clusterName, eventTriggerName, clusterType) + labels := controllers.GetInstantiatedObjectLabels(clusterNamespace, clusterName, eventTriggerName, + eventReport, clusterType) name, create, err := controllers.GetClusterProfileName(context.TODO(), c, labels) Expect(err).To(BeNil()) @@ -952,8 +976,9 @@ var _ = Describe("EventTrigger deployer", func() { clusterProfile := &configv1beta1.ClusterProfile{ ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: controllers.GetInstantiatedObjectLabels(clusterNamespace, clusterName, eventTriggerName, clusterType), + Name: name, + Labels: controllers.GetInstantiatedObjectLabels(clusterNamespace, clusterName, eventTriggerName, + eventReport, clusterType), }, } @@ -1002,20 +1027,32 @@ var _ = Describe("EventTrigger deployer", func() { }, } + eventSourceName := randomString() + eventReport := &libsveltosv1beta1.EventReport{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + Labels: map[string]string{ + libsveltosv1beta1.EventSourceNameLabel: eventSourceName, + }, + }, + } + eventTrigger := &v1beta1.EventTrigger{ ObjectMeta: metav1.ObjectMeta{Name: eventTriggerName}, Spec: v1beta1.EventTriggerSpec{ - EventSourceName: randomString(), + EventSourceName: eventSourceName, PolicyRefs: []configv1beta1.PolicyRef{ { - Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), - Name: configMap.Name, - Namespace: configMap.Namespace, + DeploymentType: configv1beta1.DeploymentTypeLocal, + Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), + Name: configMap.Name, + Namespace: configMap.Namespace, }, { - Kind: string(libsveltosv1beta1.SecretReferencedResourceKind), - Name: secret.Name, - Namespace: secret.Namespace, + DeploymentType: configv1beta1.DeploymentTypeRemote, + Kind: string(libsveltosv1beta1.SecretReferencedResourceKind), + Name: secret.Name, + Namespace: secret.Namespace, }, }, }, @@ -1045,13 +1082,15 @@ var _ = Describe("EventTrigger deployer", func() { } labels := controllers.GetInstantiatedObjectLabels(clusterRef.Namespace, clusterRef.Name, eventTrigger.Name, - libsveltosv1beta1.ClusterTypeCapi) + eventReport, libsveltosv1beta1.ClusterTypeCapi) - set, err := controllers.InstantiateReferencedPolicies(context.TODO(), c, randomString(), eventTrigger, + localSet, remoteSet, err := controllers.InstantiateReferencedPolicies(context.TODO(), c, randomString(), eventTrigger, clusterRef, object, labels, logger) Expect(err).To(BeNil()) - Expect(set).ToNot(BeNil()) - Expect(set.Len()).To(Equal(2)) + Expect(localSet).ToNot(BeNil()) + Expect(localSet.Len()).To(Equal(1)) + Expect(remoteSet).ToNot(BeNil()) + Expect(remoteSet.Len()).To(Equal(1)) listOptions := []client.ListOption{ client.InNamespace(controllers.ReportNamespace), @@ -1123,9 +1162,10 @@ var _ = Describe("EventTrigger deployer", func() { OneForEvent: false, PolicyRefs: []configv1beta1.PolicyRef{ { - Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), - Name: configMap.Name, - Namespace: configMap.Namespace, + DeploymentType: configv1beta1.DeploymentTypeRemote, + Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), + Name: configMap.Name, + Namespace: configMap.Namespace, }, }, }, @@ -1138,8 +1178,18 @@ var _ = Describe("EventTrigger deployer", func() { APIVersion: clusterv1.GroupVersion.String(), } + eventSourceName := randomString() + eventReport := &libsveltosv1beta1.EventReport{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + Labels: map[string]string{ + libsveltosv1beta1.EventSourceNameLabel: eventSourceName, + }, + }, + } + initObjects := []client.Object{ - configMap, eventTrigger, + configMap, eventTrigger, eventReport, } c := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(initObjects...). @@ -1194,13 +1244,15 @@ spec: } labels := controllers.GetInstantiatedObjectLabels(clusterRef.Namespace, clusterRef.Name, eventTrigger.Name, - libsveltosv1beta1.ClusterTypeCapi) + eventReport, libsveltosv1beta1.ClusterTypeCapi) - set, err := controllers.InstantiateReferencedPolicies(context.TODO(), c, randomString(), eventTrigger, + localSet, remoteSet, err := controllers.InstantiateReferencedPolicies(context.TODO(), c, randomString(), eventTrigger, clusterRef, objects, labels, logger) Expect(err).To(BeNil()) - Expect(set).ToNot(BeNil()) - Expect(set.Len()).To(Equal(1)) + Expect(localSet).ToNot(BeNil()) + Expect(localSet.Len()).To(Equal(0)) + Expect(remoteSet).ToNot(BeNil()) + Expect(remoteSet.Len()).To(Equal(1)) listOptions := []client.ListOption{ client.InNamespace(controllers.ReportNamespace), @@ -1220,12 +1272,22 @@ spec: clusterType := libsveltosv1beta1.ClusterTypeCapi eventTriggerName := randomString() + eventSourceName := randomString() + eventReport := &libsveltosv1beta1.EventReport{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + Labels: map[string]string{ + libsveltosv1beta1.EventSourceNameLabel: eventSourceName, + }, + }, + } + configMap := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Namespace: controllers.ReportNamespace, Name: randomString(), Labels: controllers.GetInstantiatedObjectLabels(clusterNamespace, - clusterName, eventTriggerName, clusterType), + clusterName, eventTriggerName, eventReport, clusterType), }, Data: map[string]string{ randomString(): randomString(), @@ -1237,7 +1299,7 @@ spec: Namespace: controllers.ReportNamespace, Name: randomString(), Labels: controllers.GetInstantiatedObjectLabels(clusterNamespace, - clusterName, eventTriggerName, clusterType), + clusterName, eventTriggerName, eventReport, clusterType), }, Data: map[string]string{ randomString(): randomString(), @@ -1264,7 +1326,7 @@ spec: policyRefs := map[libsveltosv1beta1.PolicyRef]bool{policyRef: true} Expect(controllers.RemoveConfigMaps(context.TODO(), c, clusterNamespace, clusterName, clusterType, - &eventTrigger, policyRefs, logger)).To(Succeed()) + &eventTrigger, eventReport, policyRefs, logger)).To(Succeed()) listOptions := []client.ListOption{ client.InNamespace(controllers.ReportNamespace), @@ -1281,12 +1343,22 @@ spec: clusterType := libsveltosv1beta1.ClusterTypeCapi eventTriggerName := randomString() + eventSourceName := randomString() + eventReport := &libsveltosv1beta1.EventReport{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + Labels: map[string]string{ + libsveltosv1beta1.EventSourceNameLabel: eventSourceName, + }, + }, + } + secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: controllers.ReportNamespace, Name: randomString(), Labels: controllers.GetInstantiatedObjectLabels(clusterNamespace, - clusterName, eventTriggerName, clusterType), + clusterName, eventTriggerName, eventReport, clusterType), }, Type: libsveltosv1beta1.ClusterProfileSecretType, Data: map[string][]byte{ @@ -1299,7 +1371,7 @@ spec: Namespace: controllers.ReportNamespace, Name: randomString(), Labels: controllers.GetInstantiatedObjectLabels(clusterNamespace, - clusterName, eventTriggerName, clusterType), + clusterName, eventTriggerName, eventReport, clusterType), }, Type: libsveltosv1beta1.ClusterProfileSecretType, Data: map[string][]byte{ @@ -1327,7 +1399,7 @@ spec: policyRefs := map[libsveltosv1beta1.PolicyRef]bool{policyRef: true} Expect(controllers.RemoveSecrets(context.TODO(), c, clusterNamespace, clusterName, clusterType, - &eventTrigger, policyRefs, logger)).To(Succeed()) + &eventTrigger, eventReport, policyRefs, logger)).To(Succeed()) listOptions := []client.ListOption{ client.InNamespace(controllers.ReportNamespace), @@ -1431,7 +1503,7 @@ func validateLabels(labels map[string]string, clusterRef *corev1.ObjectReference Expect(v).To(Equal(referencedResource.GetName())) expectedLabels := controllers.GetInstantiatedObjectLabels(clusterRef.Namespace, - clusterRef.Name, eventTriggerName, clusterproxy.GetClusterType(clusterRef)) + clusterRef.Name, eventTriggerName, nil, clusterproxy.GetClusterType(clusterRef)) for k := range expectedLabels { v = labels[k] diff --git a/controllers/export_test.go b/controllers/export_test.go index 7b1102f..82c1b6a 100644 --- a/controllers/export_test.go +++ b/controllers/export_test.go @@ -32,7 +32,6 @@ var ( UpdateMaps = (*EventTriggerReconciler).updateMaps IsClusterEntryRemoved = (*EventTriggerReconciler).isClusterEntryRemoved ProcessEventTrigger = (*EventTriggerReconciler).processEventTrigger - UpdateReferencedResourceMap = (*EventTriggerReconciler).updateReferencedResourceMap GetClustersFromClusterSets = (*EventTriggerReconciler).getClustersFromClusterSets GetKeyFromObject = getKeyFromObject @@ -63,6 +62,12 @@ var ( RemoveConfigMaps = removeConfigMaps RemoveSecrets = removeSecrets UnstructuredToTyped = unstructuredToTyped + + BuildEventTriggersForEventSourceMap = buildEventTriggersForEventSourceMap + BuildEventTriggersForClusterMap = buildEventTriggersForClusterMap + ShouldIgnore = shouldIgnore + ShouldReprocess = shouldReprocess + IsEventTriggerMatchingTheCluster = isEventTriggerMatchingTheCluster ) const ( diff --git a/controllers/fetcher.go b/controllers/fetcher.go index f25475d..3ea7451 100644 --- a/controllers/fetcher.go +++ b/controllers/fetcher.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + configv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1" v1beta1 "github.com/projectsveltos/event-manager/api/v1beta1" libsveltosv1beta1 "github.com/projectsveltos/libsveltos/api/v1beta1" "github.com/projectsveltos/libsveltos/lib/clusterproxy" @@ -36,7 +37,6 @@ import ( // fetchReferencedResources fetches resources referenced by EventTrigger. // This includes: // - EventSource and corresponding EventReports (from the passed in cluster only); -// - ConfigMaps/Secrets func fetchReferencedResources(ctx context.Context, c client.Client, e *v1beta1.EventTrigger, cluster *corev1.ObjectReference, logger logr.Logger) ([]client.Object, error) { @@ -47,7 +47,8 @@ func fetchReferencedResources(ctx context.Context, c client.Client, } logger.V(logs.LogDebug).Info("fetch EventSource") - resource, err := fetchEventSource(ctx, c, e.Spec.EventSourceName) + resource, err := fetchEventSource(ctx, c, cluster.Namespace, cluster.Name, e.Spec.EventSourceName, + clusterproxy.GetClusterType(cluster), logger) if err != nil { return nil, err } @@ -69,23 +70,31 @@ func fetchReferencedResources(ctx context.Context, c client.Client, result = append(result, &eventReports.Items[i]) } - logger.V(logs.LogDebug).Info("fetch referenced ConfigMaps/Secrets") - var resources []client.Object - resources, err = fetchPolicyRefs(ctx, c, e, cluster, logger) - if err != nil { - return nil, err - } - result = append(result, resources...) + // Resources references in PolicyRefs and/or ValuesFrom are not + // considered. Those resource namespace/name info can be expressed + // as template and so for different clusters/events different resources + // might be used. + // Also, EventTrigger should deploy ClusterProfile based on the state + // in the cluster when the event happened. return result, nil } // fetchEventSource fetches referenced EventSource -func fetchEventSource(ctx context.Context, c client.Client, eventSourceName string, -) (*libsveltosv1beta1.EventSource, error) { +func fetchEventSource(ctx context.Context, c client.Client, + clusterNamespace, clusterName, eventSourceName string, clusterType libsveltosv1beta1.ClusterType, + logger logr.Logger) (*libsveltosv1beta1.EventSource, error) { + + instantiatedEventSourceName, err := libsveltostemplate.GetReferenceResourceName(clusterNamespace, clusterName, + string(clusterType), eventSourceName) + if err != nil { + logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to get EventSource Name %s: %v", + eventSourceName, err)) + return nil, err + } eventSource := &libsveltosv1beta1.EventSource{} - err := c.Get(ctx, types.NamespacedName{Name: eventSourceName}, eventSource) + err = c.Get(ctx, types.NamespacedName{Name: instantiatedEventSourceName}, eventSource) if err != nil { if apierrors.IsNotFound(err) { return nil, nil @@ -115,9 +124,11 @@ func fetchEventReports(ctx context.Context, c client.Client, clusterNamespace, c // fetchPolicyRefs fetches referenced ConfigMaps/Secrets func fetchPolicyRefs(ctx context.Context, c client.Client, e *v1beta1.EventTrigger, - cluster *corev1.ObjectReference, logger logr.Logger) ([]client.Object, error) { + cluster *corev1.ObjectReference, objects any, templateName string, logger logr.Logger, +) (local, remote []client.Object, err error) { - result := make([]client.Object, 0) + local = make([]client.Object, 0) + remote = make([]client.Object, 0) for i := range e.Spec.PolicyRefs { policyRef := &e.Spec.PolicyRefs[i] @@ -126,17 +137,15 @@ func fetchPolicyRefs(ctx context.Context, c client.Client, e *v1beta1.EventTrigg namespace := libsveltostemplate.GetReferenceResourceNamespace(cluster.Namespace, policyRef.Namespace) - clusterType := clusterproxy.GetClusterType(cluster) - referencedName, err := libsveltostemplate.GetReferenceResourceName(cluster.Namespace, cluster.Name, - string(clusterType), policyRef.Name) + referencedName, err := instantiateSection(templateName, []byte(policyRef.Name), objects, logger) if err != nil { - return nil, err + return nil, nil, err } if policyRef.Kind == string(libsveltosv1beta1.ConfigMapReferencedResourceKind) { - object, err = getConfigMap(ctx, c, types.NamespacedName{Namespace: namespace, Name: referencedName}) + object, err = getConfigMap(ctx, c, types.NamespacedName{Namespace: namespace, Name: string(referencedName)}) } else { - object, err = getSecret(ctx, c, types.NamespacedName{Namespace: namespace, Name: referencedName}) + object, err = getSecret(ctx, c, types.NamespacedName{Namespace: namespace, Name: string(referencedName)}) } if err != nil { if apierrors.IsNotFound(err) { @@ -144,12 +153,17 @@ func fetchPolicyRefs(ctx context.Context, c client.Client, e *v1beta1.EventTrigg policyRef.Kind, namespace, referencedName)) continue } - return nil, err + return nil, nil, err + } + + if policyRef.DeploymentType == configv1beta1.DeploymentTypeLocal { + local = append(local, object) + } else { + remote = append(remote, object) } - result = append(result, object) } - return result, nil + return local, remote, nil } // getConfigMap retrieves any ConfigMap from the given name and namespace. diff --git a/controllers/fetcher_test.go b/controllers/fetcher_test.go index 0c98270..a37ceac 100644 --- a/controllers/fetcher_test.go +++ b/controllers/fetcher_test.go @@ -20,11 +20,13 @@ import ( "context" "fmt" + "github.com/go-logr/logr" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2/textlogger" @@ -39,6 +41,12 @@ import ( ) var _ = Describe("Fetcher", func() { + var logger logr.Logger + + BeforeEach(func() { + logger = textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(1))) + }) + It("getConfigMap fetches configMap", func() { configMap := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ @@ -109,14 +117,16 @@ var _ = Describe("Fetcher", func() { Spec: v1beta1.EventTriggerSpec{ PolicyRefs: []configv1beta1.PolicyRef{ { - Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), - Name: configMap.Name, - Namespace: configMap.Namespace, + DeploymentType: configv1beta1.DeploymentTypeLocal, + Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), + Name: configMap.Name, + Namespace: configMap.Namespace, }, { - Kind: string(libsveltosv1beta1.SecretReferencedResourceKind), - Name: secret.Name, - Namespace: "", // leaving it empty to use cluster namespace + DeploymentType: configv1beta1.DeploymentTypeLocal, + Kind: string(libsveltosv1beta1.SecretReferencedResourceKind), + Name: secret.Name, + Namespace: "", // leaving it empty to use cluster namespace }, }, }, @@ -139,10 +149,11 @@ var _ = Describe("Fetcher", func() { } Expect(addTypeInformationToObject(scheme, cluster)).To(Succeed()) - result, err := controllers.FetchPolicyRefs(context.TODO(), c, e, getClusterRef(cluster), - textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(1)))) + local, remote, err := controllers.FetchPolicyRefs(context.TODO(), c, e, getClusterRef(cluster), nil, + randomString(), logger) Expect(err).To(BeNil()) - Expect(len(result)).To(Equal(2)) + Expect(len(local)).To(Equal(2)) + Expect(len(remote)).To(Equal(0)) }) It("fetchPolicyRefs fetches referenced Secrets and ConfigMaps (names are expressed as templates)", func() { @@ -180,14 +191,16 @@ var _ = Describe("Fetcher", func() { Spec: v1beta1.EventTriggerSpec{ PolicyRefs: []configv1beta1.PolicyRef{ { - Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), - Name: namePrefix + "-{{ .Cluster.metadata.name }}", - Namespace: "", // leaving it empty to use cluster namespace + DeploymentType: configv1beta1.DeploymentTypeLocal, + Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), + Name: namePrefix + "-{{ .Cluster.metadata.name }}", + Namespace: "", // leaving it empty to use cluster namespace }, { - Kind: string(libsveltosv1beta1.SecretReferencedResourceKind), - Name: namePrefix + "-{{ .Cluster.metadata.name }}", - Namespace: secret.Namespace, + DeploymentType: configv1beta1.DeploymentTypeRemote, + Kind: string(libsveltosv1beta1.SecretReferencedResourceKind), + Name: namePrefix + "-{{ .Cluster.metadata.name }}", + Namespace: secret.Namespace, }, }, }, @@ -197,15 +210,24 @@ var _ = Describe("Fetcher", func() { secret, configMap, e, + cluster, } c := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(initObjects...). WithObjects(initObjects...).Build() - result, err := controllers.FetchPolicyRefs(context.TODO(), c, e, getClusterRef(cluster), - textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(1)))) + content, err := runtime.DefaultUnstructuredConverter.ToUnstructured(cluster) + Expect(err).To(BeNil()) + + object := &controllers.CurrentObject{ + Cluster: content, + } + + local, remote, err := controllers.FetchPolicyRefs(context.TODO(), c, e, getClusterRef(cluster), + object, randomString(), logger) Expect(err).To(BeNil()) - Expect(len(result)).To(Equal(2)) + Expect(len(local)).To(Equal(1)) + Expect(len(remote)).To(Equal(1)) }) It("fetchEventReports fetch EventReports for a given EventSource/Cluster pair", func() { @@ -265,7 +287,8 @@ var _ = Describe("Fetcher", func() { c := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(initObjects...). WithObjects(initObjects...).Build() - es, err := controllers.FetchEventSource(context.TODO(), c, e.Spec.EventSourceName) + es, err := controllers.FetchEventSource(context.TODO(), c, randomString(), randomString(), + e.Spec.EventSourceName, libsveltosv1beta1.ClusterTypeSveltos, logger) Expect(err).To(BeNil()) Expect(es).ToNot(BeNil()) }) @@ -345,9 +368,8 @@ var _ = Describe("Fetcher", func() { WithObjects(initObjects...).Build() Expect(addTypeInformationToObject(c.Scheme(), cluster)).To(Succeed()) - result, err := controllers.FetchReferencedResources(context.TODO(), c, e, getClusterRef(cluster), - textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(1)))) + result, err := controllers.FetchReferencedResources(context.TODO(), c, e, getClusterRef(cluster), logger) Expect(err).To(BeNil()) - Expect(len(result)).To(Equal(4)) // EventSource + EventReport + ConfigMap + Secret + Expect(len(result)).To(Equal(2)) // EventSource + EventReport (no referenced resources) }) }) diff --git a/controllers/predicates.go b/controllers/predicates.go index b305387..3c3c807 100644 --- a/controllers/predicates.go +++ b/controllers/predicates.go @@ -17,11 +17,9 @@ limitations under the License. package controllers import ( - "fmt" "reflect" "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -318,175 +316,6 @@ func EventReportPredicates(logger logr.Logger) predicate.Funcs { } } -// EventSourcePredicates predicates for EventSource. EventTriggerReconciler watches sveltos -// EventSource events and react to those by reconciling itself based on following predicates -func EventSourcePredicates(logger logr.Logger) predicate.Funcs { - return predicate.Funcs{ - UpdateFunc: func(e event.UpdateEvent) bool { - newES := e.ObjectNew.(*libsveltosv1beta1.EventSource) - oldES := e.ObjectOld.(*libsveltosv1beta1.EventSource) - log := logger.WithValues("predicate", "updateEvent", - "eventSource", newES.Name, - ) - - if oldES == nil { - log.V(logs.LogVerbose).Info("Old EventSource is nil. Reconcile EventTrigger") - return true - } - - // return true if EventSource Spec has changed - if !reflect.DeepEqual(oldES.Spec, newES.Spec) { - log.V(logs.LogVerbose).Info( - "EventSource changed. Will attempt to reconcile associated EventTriggers.") - return true - } - - // otherwise, return false - log.V(logs.LogVerbose).Info( - "EventSource did not match expected conditions. Will not attempt to reconcile associated EventTriggers.") - return false - }, - CreateFunc: func(e event.CreateEvent) bool { - log := logger.WithValues("predicate", "createEvent", - "eventSource", e.Object.GetName(), - ) - - log.V(logs.LogVerbose).Info( - "EventSource did match expected conditions. Will attempt to reconcile associated EventTriggers.") - return true - }, - DeleteFunc: func(e event.DeleteEvent) bool { - log := logger.WithValues("predicate", "deleteEvent", - "eventSource", e.Object.GetName(), - ) - log.V(logs.LogVerbose).Info( - "EventSource deleted. Will attempt to reconcile associated EventTriggers.") - return true - }, - GenericFunc: func(e event.GenericEvent) bool { - log := logger.WithValues("predicate", "genericEvent", - "eventSource", e.Object.GetName(), - ) - log.V(logs.LogVerbose).Info( - "EventSource did not match expected conditions. Will not attempt to reconcile associated EventTriggers.") - return false - }, - } -} - -// ConfigMapPredicates predicates for ConfigMaps. EventTriggerReconciler watches ConfigMap events -// and react to those by reconciling itself based on following predicates -func ConfigMapPredicates(logger logr.Logger) predicate.Funcs { - return predicate.Funcs{ - UpdateFunc: func(e event.UpdateEvent) bool { - newConfigMap := e.ObjectNew.(*corev1.ConfigMap) - oldConfigMap := e.ObjectOld.(*corev1.ConfigMap) - log := logger.WithValues("predicate", "updateEvent", - "configmap", newConfigMap.Name, - ) - - if oldConfigMap == nil { - log.V(logs.LogVerbose).Info("Old ConfigMap is nil. Reconcile EventTriggers.") - return true - } - - if !reflect.DeepEqual(oldConfigMap.Data, newConfigMap.Data) { - log.V(logs.LogVerbose).Info( - "ConfigMap Data changed. Will attempt to reconcile associated EventTriggers.", - ) - return true - } - - // otherwise, return false - log.V(logs.LogVerbose).Info( - "ConfigMap did not match expected conditions. Will not attempt to reconcile associated EventTriggers.") - return false - }, - CreateFunc: func(e event.CreateEvent) bool { - return CreateFuncTrue(e, logger) - }, - DeleteFunc: func(e event.DeleteEvent) bool { - return DeleteFuncTrue(e, logger) - }, - GenericFunc: func(e event.GenericEvent) bool { - return GenericFuncFalse(e, logger) - }, - } -} - -// SecretPredicates predicates for Secrets. EventTriggerReconciler watches Secret events -// and react to those by reconciling itself based on following predicates -func SecretPredicates(logger logr.Logger) predicate.Funcs { - return predicate.Funcs{ - UpdateFunc: func(e event.UpdateEvent) bool { - newSecret := e.ObjectNew.(*corev1.Secret) - oldSecret := e.ObjectOld.(*corev1.Secret) - log := logger.WithValues("predicate", "updateEvent", - "secret", newSecret.Name, - ) - - if oldSecret == nil { - log.V(logs.LogVerbose).Info("Old Secret is nil. Reconcile EventTriggers.") - return true - } - - if !reflect.DeepEqual(oldSecret.Data, newSecret.Data) { - log.V(logs.LogVerbose).Info( - "Secret Data changed. Will attempt to reconcile associated EventTriggers.", - ) - return true - } - - // otherwise, return false - log.V(logs.LogVerbose).Info( - "Secret did not match expected conditions. Will not attempt to reconcile associated EventTriggers.") - return false - }, - CreateFunc: func(e event.CreateEvent) bool { - return CreateFuncTrue(e, logger) - }, - DeleteFunc: func(e event.DeleteEvent) bool { - return DeleteFuncTrue(e, logger) - }, - GenericFunc: func(e event.GenericEvent) bool { - return GenericFuncFalse(e, logger) - }, - } -} - -var ( - CreateFuncTrue = func(e event.CreateEvent, logger logr.Logger) bool { - log := logger.WithValues("predicate", "createEvent", - e.Object.GetObjectKind(), e.Object.GetName(), - ) - - log.V(logs.LogVerbose).Info(fmt.Sprintf( - "%s did match expected conditions. Will attempt to reconcile associated ClusterSummaries.", - e.Object.GetObjectKind())) - return true - } - - DeleteFuncTrue = func(e event.DeleteEvent, logger logr.Logger) bool { - log := logger.WithValues("predicate", "deleteEvent", - e.Object.GetObjectKind(), e.Object.GetName(), - ) - log.V(logs.LogVerbose).Info(fmt.Sprintf( - "%s did match expected conditions. Will attempt to reconcile associated ClusterSummaries.", - e.Object.GetObjectKind())) - return true - } - - GenericFuncFalse = func(e event.GenericEvent, logger logr.Logger) bool { - log := logger.WithValues("predicate", "genericEvent", - e.Object.GetObjectKind(), e.Object.GetName(), - ) - log.V(logs.LogVerbose).Info(fmt.Sprintf( - "%s did not match expected conditions. Will not attempt to reconcile associated ClusterSummaries.", - e.Object.GetObjectKind())) - return false - } -) - // ClusterSetPredicates predicates for ClusterSet. EventTriggerReconciler watches ClusterSet events // and react to those by reconciling itself based on following predicates func ClusterSetPredicates(logger logr.Logger) predicate.Funcs { diff --git a/controllers/predicates_test.go b/controllers/predicates_test.go index d387869..c8453f3 100644 --- a/controllers/predicates_test.go +++ b/controllers/predicates_test.go @@ -17,9 +17,6 @@ limitations under the License. package controllers_test import ( - "encoding/base64" - "fmt" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -490,249 +487,3 @@ var _ = Describe("EventTrigger Predicates: EventReportPredicates", func() { Expect(result).To(BeFalse()) }) }) - -var _ = Describe("EventTrigger Predicates: EventSourcePredicates", func() { - var logger logr.Logger - var eventSource *libsveltosv1beta1.EventSource - - const upstreamClusterNamePrefix = "eventsource-predicates-" - - BeforeEach(func() { - logger = textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(1))) - eventSource = &libsveltosv1beta1.EventSource{ - ObjectMeta: metav1.ObjectMeta{ - Name: upstreamClusterNamePrefix + randomString(), - }, - } - }) - - It("Create will reprocesses", func() { - hcrPredicate := controllers.EventSourcePredicates(logger) - - e := event.CreateEvent{ - Object: eventSource, - } - - result := hcrPredicate.Create(e) - Expect(result).To(BeTrue()) - }) - It("Delete does reprocess ", func() { - hcrPredicate := controllers.EventSourcePredicates(logger) - - e := event.DeleteEvent{ - Object: eventSource, - } - - result := hcrPredicate.Delete(e) - Expect(result).To(BeTrue()) - }) - It("Update reprocesses when EventSource spec changes", func() { - hcrPredicate := controllers.EventSourcePredicates(logger) - - eventSource.Spec = libsveltosv1beta1.EventSourceSpec{ - ResourceSelectors: []libsveltosv1beta1.ResourceSelector{ - { - Group: randomString(), - Version: randomString(), - Kind: randomString(), - Evaluate: randomString(), - }, - }, - } - - oldEventSource := &libsveltosv1beta1.EventSource{ - ObjectMeta: metav1.ObjectMeta{ - Name: eventSource.Name, - }, - } - - e := event.UpdateEvent{ - ObjectNew: eventSource, - ObjectOld: oldEventSource, - } - - result := hcrPredicate.Update(e) - Expect(result).To(BeTrue()) - }) - - It("Update does not reprocesses EventSource spec has not changed", func() { - hcrPredicate := controllers.EventSourcePredicates(logger) - - eventSource.Spec = libsveltosv1beta1.EventSourceSpec{ - ResourceSelectors: []libsveltosv1beta1.ResourceSelector{ - { - Group: randomString(), - Version: randomString(), - Kind: randomString(), - Evaluate: randomString(), - }, - }, - } - - oldEventSource := &libsveltosv1beta1.EventSource{ - ObjectMeta: metav1.ObjectMeta{ - Name: eventSource.Name, - }, - Spec: eventSource.Spec, - } - - e := event.UpdateEvent{ - ObjectNew: eventSource, - ObjectOld: oldEventSource, - } - - result := hcrPredicate.Update(e) - Expect(result).To(BeFalse()) - }) -}) - -var _ = Describe("EventTrigger Predicates: ConfigMapPredicates", func() { - var logger logr.Logger - var configMap *corev1.ConfigMap - - BeforeEach(func() { - logger = textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(1))) - configMap = &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: randomString(), - }, - } - }) - - It("Create returns true", func() { - configMapPredicate := controllers.ConfigMapPredicates(logger) - - e := event.CreateEvent{ - Object: configMap, - } - - result := configMapPredicate.Create(e) - Expect(result).To(BeTrue()) - }) - - It("Delete returns true", func() { - configMapPredicate := controllers.ConfigMapPredicates(logger) - - e := event.DeleteEvent{ - Object: configMap, - } - - result := configMapPredicate.Delete(e) - Expect(result).To(BeTrue()) - }) - - It("Update returns true when data has changed", func() { - configMapPredicate := controllers.ConfigMapPredicates(logger) - configMap.Data = map[string]string{"change": "now"} - - oldConfigMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: configMap.Name, - }, - } - - e := event.UpdateEvent{ - ObjectNew: configMap, - ObjectOld: oldConfigMap, - } - - result := configMapPredicate.Update(e) - Expect(result).To(BeTrue()) - }) - - It("Update returns false when Data has not changed", func() { - configMapPredicate := controllers.ConfigMapPredicates(logger) - configMap = createConfigMapWithPolicy("default", configMap.Name, fmt.Sprintf(viewClusterRole, randomString())) - - oldConfigMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: configMap.Name, - Labels: map[string]string{"env": "testing"}, - }, - Data: configMap.Data, - } - - e := event.UpdateEvent{ - ObjectNew: configMap, - ObjectOld: oldConfigMap, - } - - result := configMapPredicate.Update(e) - Expect(result).To(BeFalse()) - }) -}) - -var _ = Describe("EventTrigger Predicates: SecretPredicates", func() { - var logger logr.Logger - var secret *corev1.Secret - - BeforeEach(func() { - logger = textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(1))) - secret = &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: randomString(), - }, - } - }) - - It("Create returns true", func() { - secretPredicate := controllers.SecretPredicates(logger) - - e := event.CreateEvent{ - Object: secret, - } - - result := secretPredicate.Create(e) - Expect(result).To(BeTrue()) - }) - - It("Delete returns true", func() { - secretPredicate := controllers.SecretPredicates(logger) - - e := event.DeleteEvent{ - Object: secret, - } - - result := secretPredicate.Delete(e) - Expect(result).To(BeTrue()) - }) - - It("Update returns true when data has changed", func() { - secretPredicate := controllers.SecretPredicates(logger) - str := base64.StdEncoding.EncodeToString([]byte("password")) - secret.Data = map[string][]byte{"change": []byte(str)} - - oldSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secret.Name, - }, - } - - e := event.UpdateEvent{ - ObjectNew: secret, - ObjectOld: oldSecret, - } - - result := secretPredicate.Update(e) - Expect(result).To(BeTrue()) - }) - - It("Update returns false when Data has not changed", func() { - secretPredicate := controllers.SecretPredicates(logger) - - oldSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secret.Name, - Labels: map[string]string{"env": "testing"}, - }, - } - - e := event.UpdateEvent{ - ObjectNew: secret, - ObjectOld: oldSecret, - } - - result := secretPredicate.Update(e) - Expect(result).To(BeFalse()) - }) -}) diff --git a/controllers/suite_utils_test.go b/controllers/suite_utils_test.go index b172f0a..a88fd21 100644 --- a/controllers/suite_utils_test.go +++ b/controllers/suite_utils_test.go @@ -21,7 +21,6 @@ import ( "fmt" "sync" "time" - "unicode/utf8" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -49,15 +48,6 @@ import ( const ( sveltosKubeconfigPostfix = "-kubeconfig" - - viewClusterRole = `apiVersion: rbac.authorization.k8s.io/v1 - kind: ClusterRole - metadata: - name: %s - rules: - - apiGroups: [""] # "" indicates the core API group - resources: ["pods"] - verbs: ["get", "watch", "list"]` ) var ( @@ -347,24 +337,3 @@ func createSecretWithKubeconfig(clusterNamespace, clusterName string) { Expect(testEnv.Create(context.TODO(), secret)).To(Succeed()) Expect(waitForObject(context.TODO(), testEnv.Client, secret)).To(Succeed()) } - -// createConfigMapWithPolicy creates a configMap with passed in policies. -func createConfigMapWithPolicy(namespace, configMapName string, policyStrs ...string) *corev1.ConfigMap { - cm := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: configMapName, - }, - Data: map[string]string{}, - } - for i := range policyStrs { - key := fmt.Sprintf("policy%d.yaml", i) - if utf8.Valid([]byte(policyStrs[i])) { - cm.Data[key] = policyStrs[i] - } else { - cm.BinaryData[key] = []byte(policyStrs[i]) - } - } - - return cm -} diff --git a/controllers/transformations.go b/controllers/transformations.go index ec554e6..43d5f7b 100644 --- a/controllers/transformations.go +++ b/controllers/transformations.go @@ -65,38 +65,6 @@ func (r *EventTriggerReconciler) requeueEventTriggerForEventReport( return requests } -func (r *EventTriggerReconciler) requeueEventTriggerForEventSource( - ctx context.Context, o client.Object, -) []reconcile.Request { - - eventSource := o.(*libsveltosv1beta1.EventSource) - logger := r.Logger.WithValues("eventSource", eventSource.GetName()) - - logger.V(logs.LogDebug).Info("reacting to eventSource change") - - r.Mux.Lock() - defer r.Mux.Unlock() - - eventSourceInfo := corev1.ObjectReference{APIVersion: libsveltosv1beta1.GroupVersion.String(), - Kind: libsveltosv1beta1.EventSourceKind, Name: eventSource.Name} - - // Get all EventTriggers referencing this EventSource - requests := make([]ctrl.Request, getConsumersForEntry(r.EventSourceMap, &eventSourceInfo).Len()) - consumers := getConsumersForEntry(r.EventSourceMap, &eventSourceInfo).Items() - - for i := range consumers { - l := logger.WithValues("eventTrigger", consumers[i].Name) - l.V(logs.LogDebug).Info("queuing EventTrigger") - requests[i] = ctrl.Request{ - NamespacedName: client.ObjectKey{ - Name: consumers[i].Name, - }, - } - } - - return requests -} - func (r *EventTriggerReconciler) requeueEventTriggerForSveltosCluster( ctx context.Context, o client.Object, ) []reconcile.Request { @@ -229,62 +197,6 @@ func (r *EventTriggerReconciler) requeueEventTriggerForMachine( return requests } -func (r *EventTriggerReconciler) requeueEventTriggerForReference( - ctx context.Context, o client.Object, -) []reconcile.Request { - - logger := r.Logger.WithValues("reference", fmt.Sprintf("%s:%s/%s", - o.GetObjectKind().GroupVersionKind().Kind, o.GetNamespace(), o.GetName())) - - logger.V(logs.LogDebug).Info("reacting to configMap/secret change") - - r.Mux.Lock() - defer r.Mux.Unlock() - - // Following is needed as o.GetObjectKind().GroupVersionKind().Kind is not set - var key corev1.ObjectReference - switch o.(type) { - case *corev1.ConfigMap: - key = corev1.ObjectReference{ - APIVersion: corev1.SchemeGroupVersion.String(), - Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), - Namespace: o.GetNamespace(), - Name: o.GetName(), - } - case *corev1.Secret: - key = corev1.ObjectReference{ - APIVersion: corev1.SchemeGroupVersion.String(), - Kind: string(libsveltosv1beta1.SecretReferencedResourceKind), - Namespace: o.GetNamespace(), - Name: o.GetName(), - } - default: - key = corev1.ObjectReference{ - APIVersion: o.GetObjectKind().GroupVersionKind().GroupVersion().String(), - Kind: o.GetObjectKind().GroupVersionKind().Kind, - Namespace: o.GetNamespace(), - Name: o.GetName(), - } - } - - logger.V(logs.LogDebug).Info(fmt.Sprintf("referenced key: %s", key)) - - requests := make([]ctrl.Request, getConsumersForEntry(r.ReferenceMap, &key).Len()) - - consumers := getConsumersForEntry(r.ReferenceMap, &key).Items() - for i := range consumers { - logger.V(logs.LogDebug).Info(fmt.Sprintf("requeue consumer: %s", consumers[i])) - requests[i] = ctrl.Request{ - NamespacedName: client.ObjectKey{ - Name: consumers[i].Name, - Namespace: consumers[i].Namespace, - }, - } - } - - return requests -} - func (r *EventTriggerReconciler) requeueEventTriggerForClusterSet( ctx context.Context, o client.Object, ) []reconcile.Request { diff --git a/manifest/manifest.yaml b/manifest/manifest.yaml index f2ed2cd..3bc37b5 100644 --- a/manifest/manifest.yaml +++ b/manifest/manifest.yaml @@ -2111,23 +2111,13 @@ rules: resources: - configmaps verbs: - - create - - delete - - get - - list - - update - - watch + - '*' - apiGroups: - "" resources: - secrets verbs: - - create - - delete - - get - - list - - update - - watch + - '*' - apiGroups: - apiextensions.k8s.io resources: diff --git a/test/fv/stop_matching_test.go b/test/fv/stop_matching_test.go new file mode 100644 index 0000000..98e1c10 --- /dev/null +++ b/test/fv/stop_matching_test.go @@ -0,0 +1,254 @@ +/* +Copyright 2024. projectsveltos.io. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fv_test + +import ( + "context" + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + 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/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + configv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1" + "github.com/projectsveltos/event-manager/api/v1beta1" + libsveltosv1beta1 "github.com/projectsveltos/libsveltos/api/v1beta1" +) + +var ( + configMap = `apiVersion: v1 +kind: ConfigMap +metadata: + namespace: default + name: %s` +) + +var _ = Describe("Deletes ClusterProfile when cluster is not a match anymore", func() { + const ( + namePrefix = "stop-matching" + projectsveltos = "projectsveltos" + ) + + It("Verifies ClusterProfiles is deleted when cluster stops matching EventTrigger", Label("FV"), func() { + nsKey := randomString() + nsValue := randomString() + + Byf("Create a EventSource matching a namespace with label %s=%s", nsKey, nsValue) + eventSource := libsveltosv1beta1.EventSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: randomString(), + }, + Spec: libsveltosv1beta1.EventSourceSpec{ + ResourceSelectors: []libsveltosv1beta1.ResourceSelector{ + { + Group: "", + Version: "v1", + Kind: "Namespace", + LabelFilters: []libsveltosv1beta1.LabelFilter{ + {Key: nsKey, Operation: libsveltosv1beta1.OperationEqual, Value: nsValue}, + }, + }, + }, + CollectResources: true, + }, + } + Expect(k8sClient.Create(context.TODO(), &eventSource)).To(Succeed()) + + cmName := randomString() + By("Creating a ConfigMap containing a ConfigMap") + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: randomString(), + }, + Data: map[string]string{ + "configmap": fmt.Sprintf(configMap, cmName), + }, + } + Expect(k8sClient.Create(context.TODO(), cm)).To(Succeed()) + + policyRef := configv1beta1.PolicyRef{ + Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), + Namespace: cm.Namespace, + Name: cm.Name, + DeploymentType: configv1beta1.DeploymentTypeLocal, + } + + Byf("Create a EventTrigger referencing EventSource %s", eventSource.Name) + eventTrigger := getEventTrigger(namePrefix, eventSource.Name, + map[string]string{key: value}, []configv1beta1.PolicyRef{policyRef}) + eventTrigger.Spec.OneForEvent = false + eventTrigger.Spec.HelmCharts = nil + Expect(k8sClient.Create(context.TODO(), eventTrigger)).To(Succeed()) + + Byf("Getting client to access the workload cluster") + workloadClient, err := getKindWorkloadClusterKubeconfig() + Expect(err).To(BeNil()) + Expect(workloadClient).ToNot(BeNil()) + + Byf("Verifying EventSource %s is present in the managed cluster", eventSource.Name) + Eventually(func() error { + currentEventSource := &libsveltosv1beta1.EventSource{} + return workloadClient.Get(context.TODO(), types.NamespacedName{Name: eventSource.Name}, + currentEventSource) + }, timeout, pollingInterval).Should(BeNil()) + + Byf("Verifying EventReports %s is present in the managed cluster", eventSource.Name) + Eventually(func() error { + currentEventReport := &libsveltosv1beta1.EventReport{} + return workloadClient.Get(context.TODO(), + types.NamespacedName{Namespace: projectsveltos, Name: eventSource.Name}, + currentEventReport) + }, timeout, pollingInterval).Should(BeNil()) + + Byf("Verifying EventReports %s is present in the management cluster", eventSource.Name) + Eventually(func() error { + currentEventReport := &libsveltosv1beta1.EventReport{} + return k8sClient.Get(context.TODO(), + types.NamespacedName{Namespace: kindWorkloadCluster.Namespace, Name: getEventReportName(eventSource.Name)}, + currentEventReport) + }, timeout, pollingInterval).Should(BeNil()) + + namespace := randomString() + Byf("Create namespace %s in the managed cluster", namespace) + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + Labels: map[string]string{ + nsKey: nsValue, + }, + }, + } + Expect(workloadClient.Create(context.TODO(), ns)).To(Succeed()) + + Byf("Verifying EventReports %s is present in the managed cluster with matching resource", eventSource.Name) + Eventually(func() bool { + currentEventReport := &libsveltosv1beta1.EventReport{} + err = workloadClient.Get(context.TODO(), + types.NamespacedName{Namespace: projectsveltos, Name: eventSource.Name}, + currentEventReport) + if err != nil { + return false + } + return len(currentEventReport.Spec.Resources) != 0 + }, timeout, pollingInterval).Should(BeTrue()) + + Byf("Verifying EventReports %s is present in the management cluster with matching resource", getEventReportName(eventSource.Name)) + Eventually(func() bool { + currentEventReport := &libsveltosv1beta1.EventReport{} + err = k8sClient.Get(context.TODO(), + types.NamespacedName{Namespace: kindWorkloadCluster.Namespace, Name: getEventReportName(eventSource.Name)}, + currentEventReport) + if err != nil { + return false + } + + return len(currentEventReport.Spec.Resources) != 0 + }, timeout, pollingInterval).Should(BeTrue()) + + By("Verifying ClusterProfile has been created") + Eventually(func() bool { + listOptions := []client.ListOption{ + client.MatchingLabels(getInstantiatedObjectLabels(eventTrigger.Name)), + } + clusterProfileList := &configv1beta1.ClusterProfileList{} + err = k8sClient.List(context.TODO(), clusterProfileList, listOptions...) + if err != nil { + return false + } + return len(clusterProfileList.Items) == 1 + }, timeout, pollingInterval).Should(BeTrue()) + + listOptions := []client.ListOption{ + client.MatchingLabels(getInstantiatedObjectLabels(eventTrigger.Name)), + } + clusterProfileList := &configv1beta1.ClusterProfileList{} + Expect(k8sClient.List(context.TODO(), clusterProfileList, listOptions...)).To(Succeed()) + clusterProfile := &clusterProfileList.Items[0] + Expect(len(clusterProfile.Spec.PolicyRefs)).To(Equal(1)) + Expect(clusterProfile.Spec.PolicyRefs[0].DeploymentType).To(Equal(configv1beta1.DeploymentTypeLocal)) + _ = verifyClusterSummary(clusterProfile, kindWorkloadCluster.Namespace, kindWorkloadCluster.Name) + + Byf("Change EventTrigger %s SourceClusterSelector", eventTrigger.Name) + currentEventTrigger := &v1beta1.EventTrigger{} + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: eventTrigger.Name}, + currentEventTrigger)).To(Succeed()) + currentEventTrigger.Spec.SourceClusterSelector = libsveltosv1beta1.Selector{ + LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + randomString(): randomString(), + }, + }, + } + Expect(k8sClient.Update(context.TODO(), currentEventTrigger)).To(Succeed()) + + Byf("Verifying EventSource %s is removed from the managed cluster", eventSource.Name) + Eventually(func() bool { + currentEventSource := &libsveltosv1beta1.EventSource{} + err = workloadClient.Get(context.TODO(), types.NamespacedName{Name: eventSource.Name}, + currentEventSource) + return err != nil && apierrors.IsNotFound(err) + }, timeout, pollingInterval).Should(BeTrue()) + + Byf("Verifying EventReports %s is removed from the managed cluster", eventSource.Name) + Eventually(func() bool { + currentEventReport := &libsveltosv1beta1.EventReport{} + err = workloadClient.Get(context.TODO(), + types.NamespacedName{Namespace: projectsveltos, Name: eventSource.Name}, + currentEventReport) + return err != nil && apierrors.IsNotFound(err) + }, timeout, pollingInterval).Should(BeTrue()) + + By("Verifying ClusterProfile is deleted") + Eventually(func() bool { + listOptions := []client.ListOption{ + client.MatchingLabels(getInstantiatedObjectLabels(eventTrigger.Name)), + } + clusterProfileList := &configv1beta1.ClusterProfileList{} + err = k8sClient.List(context.TODO(), clusterProfileList, listOptions...) + if err != nil { + return false + } + return len(clusterProfileList.Items) == 0 + }, timeout, pollingInterval).Should(BeTrue()) + + Byf("Deleting EventTrigger %s", eventTrigger.Name) + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: eventTrigger.Name}, + currentEventTrigger)).To(Succeed()) + Expect(k8sClient.Delete(context.TODO(), currentEventTrigger)).To(Succeed()) + + Byf("Deleting EventSource %s in the managed cluster", eventSource.Name) + currentEventSource := &libsveltosv1beta1.EventSource{} + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: eventSource.Name}, + currentEventSource)).To(Succeed()) + Expect(k8sClient.Delete(context.TODO(), currentEventSource)).To(Succeed()) + + Byf("Verifying EventReports %s is removed from the management cluster", getEventReportName(eventSource.Name)) + Eventually(func() bool { + currentEventReport := &libsveltosv1beta1.EventReport{} + err = k8sClient.Get(context.TODO(), + types.NamespacedName{Namespace: kindWorkloadCluster.Namespace, Name: getEventReportName(eventSource.Name)}, + currentEventReport) + return err != nil && apierrors.IsNotFound(err) + }, timeout, pollingInterval).Should(BeTrue()) + }) +})