Repository: k0rdent/kcm
Pull request #1417: feat: Emit events for services
Author: login: wahabmk
Labels: name: enhancement
description: Small feature, request or improvement suggestion
name: ksm
description: Issue relates to ksm (K0rdent State Mgmt)
Created: 2025-04-22T20:55:07Z | Merged: 2025-04-24T17:24:13Z

# Description

This PR introduces the following events:
```
1. ServicesReferencesValidationSucceeded
2. ServicesReferencesValidationFailed
3. SveltosProfileReady
4. SveltosProfileNotReady
5. SveltosClusterProfileReady
6. SveltosClusterProfileNotReady
7. FetchServicesStatusSuccess
8. FetchServicesStatusFailed
9. SveltosHelmReleaseReady
10. SveltosHelmReleaseNotReady
11. SveltosFeatureReady
12. SveltosFeatureNotReady
```

# Testing

The following output shows that the status `ClusterDeployment` is being reported correctly:
```sh
➜  ~ kubectl -n kcm-system get clusterdeployment wali-dev-1 -o yaml    
apiVersion: k0rdent.mirantis.com/v1alpha1
kind: ClusterDeployment
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"k0rdent.mirantis.com/v1alpha1","kind":"ClusterDeployment","metadata":{"annotations":{},"name":"wali-dev-1","namespace":"kcm-system"},"spec":{"config":{"clusterIdentity":{"name":"aws-cluster-identity","namespace":"kcm-system"},"controlPlane":{"amiID":"ami-0eb9fdcf0d07bd5ef","instanceProfile":"control-plane.cluster-api-provider-aws.sigs.k8s.io","instanceType":"t3.small"},"controlPlaneNumber":1,"publicIP":true,"region":"ca-central-1","worker":{"amiID":"ami-0eb9fdcf0d07bd5ef","instanceProfile":"nodes.cluster-api-provider-aws.sigs.k8s.io","instanceType":"t3.small"},"workersNumber":1},"credential":"aws-cluster-identity-cred","serviceSpec":{"services":[{"name":"ingress-nginx","namespace":"ingress-nginx-4-11-3","template":"ingress-nginx-4-11-3"},{"name":"kyverno","namespace":"kyverno","template":"kyverno-3-2-6"}],"stopOnConflict":false,"syncMode":"Continuous"},"template":"aws-standalone-cp-0-2-0"}}
  creationTimestamp: "2025-04-23T01:20:06Z"
  finalizers:
  - k0rdent.mirantis.com/cluster-deployment
  generation: 2
  labels:
    k0rdent.mirantis.com/component: kcm
  name: wali-dev-1
  namespace: kcm-system
  resourceVersion: "34110"
  uid: 60941995-5bdb-4d47-b099-1ec6799cbf6a
spec:
  config:
    clusterIdentity:
      name: aws-cluster-identity
      namespace: kcm-system
    controlPlane:
      amiID: ami-0eb9fdcf0d07bd5ef
      instanceProfile: control-plane.cluster-api-provider-aws.sigs.k8s.io
      instanceType: t3.small
    controlPlaneNumber: 1
    publicIP: true
    region: ca-central-1
    worker:
      amiID: ami-0eb9fdcf0d07bd5ef
      instanceProfile: nodes.cluster-api-provider-aws.sigs.k8s.io
      instanceType: t3.small
    workersNumber: 1
  credential: aws-cluster-identity-cred
  propagateCredentials: true
  serviceSpec:
    continueOnError: false
    priority: 100
    services:
    - name: ingress-nginx
      namespace: ingress-nginx-4-11-3
      template: ingress-nginx-4-11-3
    - name: kyverno
      namespace: kyverno
      template: kyverno-3-2-6
    stopOnConflict: false
    syncMode: Continuous
  template: aws-standalone-cp-0-2-0
status:
  conditions:
  - lastTransitionTime: "2025-04-23T02:09:54Z"
    message: ""
    observedGeneration: 2
    reason: Succeeded
    status: "True"
    type: TemplateReady
  - lastTransitionTime: "2025-04-23T02:09:54Z"
    message: ""
    observedGeneration: 2
    reason: Succeeded
    status: "True"
    type: HelmChartReady
  - lastTransitionTime: "2025-04-23T02:09:54Z"
    message: Helm install succeeded for release kcm-system/wali-dev-1.v1 with chart
      aws-standalone-cp@0.2.0
    reason: InstallSucceeded
    status: "True"
    type: HelmReleaseReady
  - lastTransitionTime: "2025-04-23T01:42:13Z"
    message: Object is ready
    reason: Succeeded
    status: "True"
    type: Ready
  - lastTransitionTime: "2025-04-23T02:09:54Z"
    message: ""
    observedGeneration: 2
    reason: Succeeded
    status: "True"
    type: CredentialReady
  - lastTransitionTime: "2025-04-23T02:09:54Z"
    message: ""
    observedGeneration: 2
    reason: Succeeded
    status: "True"
    type: SveltosProfileReady
  - lastTransitionTime: "2025-04-23T02:09:54Z"
    message: ""
    observedGeneration: 2
    reason: Succeeded
    status: "True"
    type: FetchServicesStatusSuccess
  - lastTransitionTime: "2025-04-23T02:09:54Z"
    message: ""
    observedGeneration: 2
    reason: Succeeded
    status: "True"
    type: ServicesReferencesValidation
  - lastTransitionTime: "2025-04-23T01:42:13Z"
    message: 2/2
    reason: Succeeded
    status: "True"
    type: ServicesInReadyState
  - lastTransitionTime: "2025-04-23T01:26:42Z"
    message: ""
    observedGeneration: 2
    reason: InfoReported
    status: "True"
    type: CAPIClusterSummary
  observedGeneration: 2
  services:
  - clusterName: wali-dev-1
    clusterNamespace: kcm-system
    conditions:
    - lastTransitionTime: "2025-04-23T02:09:54Z"
      message: ""
      reason: Provisioned
      status: "True"
      type: Resources
    - lastTransitionTime: "2025-04-23T02:09:54Z"
      message: ""
      reason: Provisioned
      status: "True"
      type: Helm
    - lastTransitionTime: "2025-04-23T02:09:54Z"
      message: Release ingress-nginx-4-11-3/ingress-nginx
      reason: Managing
      status: "True"
      type: ingress-nginx-4-11-3.ingress-nginx/SveltosHelmReleaseReady
    - lastTransitionTime: "2025-04-23T02:09:54Z"
      message: Release kyverno/kyverno
      reason: Managing
      status: "True"
      type: kyverno.kyverno/SveltosHelmReleaseReady
```

✅ The following successful events can be seen via kubectl:
```sh
➜  ~ kubectl -n kcm-system get event --field-selector reason=ServicesReferencesValidationSucceeded
LAST SEEN   TYPE     REASON                                  OBJECT                         MESSAGE
57m         Normal   ServicesReferencesValidationSucceeded   clusterdeployment/wali-dev-1   Successfully validated services references
➜  ~ kubectl -n kcm-system get event --field-selector reason=SveltosProfileReady                  
LAST SEEN   TYPE     REASON                OBJECT                         MESSAGE
57m         Normal   SveltosProfileReady   clusterdeployment/wali-dev-1   Successfully reconciled kcm-system/wali-dev-1 Profile
➜  ~ kubectl -n kcm-system get event --field-selector reason=FetchServicesStatusSuccess
LAST SEEN   TYPE     REASON                       OBJECT                         MESSAGE
58m         Normal   FetchServicesStatusSuccess   clusterdeployment/wali-dev-1   Successfully fetched status of services from Sveltos ClusterSummary
```

:no_entry_sign: Whereas, the following events should have been created but are not:
```sh
➜  ~ kubectl -n kcm-system get event --field-selector reason=SveltosHelmReleaseReady   
No resources found in kcm-system namespace.
➜  ~ kubectl -n kcm-system get event --field-selector reason=SveltosHelmReleaseNotReady
No resources found in kcm-system namespace.
➜  ~ kubectl -n kcm-system get event --field-selector reason=SveltosFeatureReady       
No resources found in kcm-system namespace.
```

Even though the controller logs show that these events were created:
```sh
2025-04-23T01:42:34Z	DEBUG	events	Sveltos feature Resources Provisioned on target cluster kcm-system/wali-dev-1	{"type": "Normal", "object": {"kind":"ClusterDeployment","namespace":"kcm-system","name":"wali-dev-1","uid":"60941995-5bdb-4d47-b099-1ec6799cbf6a","apiVersion":"k0rdent.mirantis.com/v1alpha1","resourceVersion":"19535"}, "reason": "SveltosFeatureReady"}
2025-04-23T01:42:34Z	DEBUG	events	Sveltos feature Helm Provisioned on target cluster kcm-system/wali-dev-1	{"type": "Normal", "object": {"kind":"ClusterDeployment","namespace":"kcm-system","name":"wali-dev-1","uid":"60941995-5bdb-4d47-b099-1ec6799cbf6a","apiVersion":"k0rdent.mirantis.com/v1alpha1","resourceVersion":"19535"}, "reason": "SveltosFeatureReady"}
2025-04-23T01:42:34Z	DEBUG	events	Release ingress-nginx-4-11-3/ingress-nginx	{"type": "Normal", "object": {"kind":"ClusterDeployment","namespace":"kcm-system","name":"wali-dev-1","uid":"60941995-5bdb-4d47-b099-1ec6799cbf6a","apiVersion":"k0rdent.mirantis.com/v1alpha1","resourceVersion":"19535"}, "reason": "SveltosHelmReleaseReady"}
2025-04-23T01:42:34Z	DEBUG	events	Release kyverno/kyverno	{"type": "Normal", "object": {"kind":"ClusterDeployment","namespace":"kcm-system","name":"wali-dev-1","uid":"60941995-5bdb-4d47-b099-1ec6799cbf6a","apiVersion":"k0rdent.mirantis.com/v1alpha1","resourceVersion":"19535"}, "reason": "SveltosHelmReleaseReady"}
```



Diff:
diff --git a/api/v1alpha1/multiclusterservice_types.go b/api/v1alpha1/multiclusterservice_types.go
index 1532599b3..635b6d696 100644
--- a/api/v1alpha1/multiclusterservice_types.go
+++ b/api/v1alpha1/multiclusterservice_types.go
@@ -25,7 +25,9 @@ const (
 	MultiClusterServiceFinalizer = "k0rdent.mirantis.com/multicluster-service"
 	// MultiClusterServiceKind is the string representation of a MultiClusterServiceKind.
 	MultiClusterServiceKind = "MultiClusterService"
+)
 
+const (
 	// SveltosProfileReadyCondition indicates if the Sveltos Profile is ready.
 	SveltosProfileReadyCondition = "SveltosProfileReady"
 	// SveltosClusterProfileReadyCondition indicates if the Sveltos ClusterProfile is ready.
@@ -52,6 +54,26 @@ const (
 	ServicesReferencesValidationCondition = "ServicesReferencesValidation"
 )
 
+// Reasons are provided as utility, and not part of the declarative API.
+const (
+	// ServicesReferencesValidationFailedReason signals that the validation for references related to services (KSM) failed.
+	ServicesReferencesValidationFailedReason = "ServicesReferencesValidationFailed"
+	// ServicesReferencesValidationSucceededReason signals that the validation for references related to services (KSM) succeeded.
+	ServicesReferencesValidationSucceededReason = "ServicesReferencesValidationSucceeded"
+	// SveltosProfileNotReadyReason signals that the Sveltos Profile object is not yet ready.
+	SveltosProfileNotReadyReason = "SveltosProfileNotReady"
+	// SveltosClusterProfileNotReadyReason signals that the Sveltos ClusterProfile object is not yet ready.
+	SveltosClusterProfileNotReadyReason = "SveltosClusterProfileNotReady"
+	// FetchServicesStatusFailedReason signals that fetching the status of services from Sveltos objects failed.
+	FetchServicesStatusFailedReason = "FetchServicesStatusFailed"
+	// SveltosHelmReleaseNotReadyReason signals that the helm release managed by Sveltos on target cluster is not yet ready.
+	SveltosHelmReleaseNotReadyReason = "SveltosHelmReleaseNotReady"
+	// SveltosFeatureReadyReason signals that the feature managed by Sveltos on target cluster is ready.
+	SveltosFeatureReadyReason = "SveltosFeatureReady"
+	// SveltosFeatureNotReadyReason signals that the feature managed by Sveltos on target cluster is not yet ready.
+	SveltosFeatureNotReadyReason = "SveltosFeatureNotReady"
+)
+
 // Service represents a Service to be deployed.
 type Service struct {
 	// Values is the helm values to be passed to the chart used by the template.
diff --git a/internal/controller/clusterdeployment_controller.go b/internal/controller/clusterdeployment_controller.go
index 97af4924e..1ff72aadc 100644
--- a/internal/controller/clusterdeployment_controller.go
+++ b/internal/controller/clusterdeployment_controller.go
@@ -18,6 +18,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"slices"
 	"strings"
 	"time"
 
@@ -571,12 +572,20 @@ func (r *ClusterDeploymentReconciler) updateServices(ctx context.Context, cd *kc
 		if r.IsDisabledValidationWH {
 			merr = errors.Join(merr, validation.ServicesHaveValidTemplates(ctx, r.Client, cd.Spec.ServiceSpec.Services, cd.Namespace))
 		}
-		r.setCondition(cd, kcm.ServicesReferencesValidationCondition, merr)
+
 		if merr != nil {
 			// at this point 2/3 conditions will have unknown status, 1/3 (services validation) cond will have failed cond
+			if r.setCondition(cd, kcm.ServicesReferencesValidationCondition, merr) {
+				r.warnf(cd, kcm.ServicesReferencesValidationFailedReason, merr.Error())
+			}
+
 			l.Error(merr, "failed to validate services, will not retrigger this error")
 			return ctrl.Result{}, nil // no reason to reconcile further
 		}
+
+		if r.setCondition(cd, kcm.ServicesReferencesValidationCondition, nil) {
+			r.eventf(cd, kcm.ServicesReferencesValidationSucceededReason, "Successfully validated services references")
+		}
 	}
 
 	// servicesErr is handled separately from err because we do not want
@@ -586,8 +595,22 @@ func (r *ClusterDeploymentReconciler) updateServices(ctx context.Context, cd *kc
 
 	// TODO: should be refactored, unmaintainable; requires to refactor the whole conditions-approach (e.g. init on each event); requires to refactor controllers to be moved to dedicated pkgs
 	defer func() {
-		r.setCondition(cd, kcm.SveltosProfileReadyCondition, err)
-		r.setCondition(cd, kcm.FetchServicesStatusSuccessCondition, servicesErr)
+		if r.setCondition(cd, kcm.SveltosProfileReadyCondition, err) {
+			if err != nil {
+				r.warnf(cd, kcm.SveltosProfileNotReadyReason, err.Error())
+			} else {
+				r.eventf(cd, kcm.SveltosProfileReadyCondition, "Successfully reconciled %s/%s Profile", cd.Namespace, cd.Name)
+			}
+		}
+
+		if r.setCondition(cd, kcm.FetchServicesStatusSuccessCondition, servicesErr) {
+			if servicesErr != nil {
+				r.warnf(cd, kcm.FetchServicesStatusFailedReason, servicesErr.Error())
+			} else {
+				r.eventf(cd, kcm.FetchServicesStatusSuccessCondition, "Successfully fetched status of services from Sveltos ClusterSummary")
+			}
+		}
+
 		if r.IsDisabledValidationWH && apierrors.IsNotFound(err) {
 			// non-services NotFound errors relate only to ServiceTemplate
 			// if they are gone then nothing to do
@@ -665,16 +688,37 @@ func (r *ClusterDeploymentReconciler) updateServices(ctx context.Context, cd *kc
 
 	if len(cd.Spec.ServiceSpec.Services) == 0 {
 		cd.Status.Services = nil
-	} else {
-		var servicesStatus []kcm.ServiceStatus
-		servicesStatus, servicesErr = updateServicesStatus(ctx, r.Client, profileRef, profile.Status.MatchingClusterRefs, cd.Status.Services)
-		if servicesErr != nil {
-			return ctrl.Result{}, nil
+		return ctrl.Result{}, nil
+	}
+
+	servicesStatus, servicesErr := getServicesStatus(ctx, r.Client, profileRef, profile.Status.MatchingClusterRefs)
+	if servicesErr != nil {
+		return ctrl.Result{}, nil
+	}
+
+	// Running this loop for the sole purpose of creating
+	// a kubernetes event for each change in conditions.
+	for _, svc := range servicesStatus {
+		idx := slices.IndexFunc(cd.Status.Services, func(o kcm.ServiceStatus) bool {
+			return svc.ClusterNamespace == o.ClusterNamespace && svc.ClusterName == o.ClusterName
+		})
+
+		for _, cond := range svc.Conditions {
+			if idx > -1 && apimeta.SetStatusCondition(&cd.Status.Services[idx].Conditions, cond) {
+				sveltos.CreateEventFromCondition(cd, cd.Generation, client.ObjectKey{Namespace: svc.ClusterNamespace, Name: svc.ClusterName}, &cond)
+			}
+			// If idx == -1, then a new service was added to the mcs spec so we should create an event in this case.
+			sveltos.CreateEventFromCondition(cd, cd.Generation, client.ObjectKey{Namespace: svc.ClusterNamespace, Name: svc.ClusterName}, &cond)
 		}
-		cd.Status.Services = servicesStatus
-		l.Info("Successfully updated status of services")
 	}
 
+	// We are overwriting conditions so as to be in-sync with the custom status
+	// implemented by Sveltos ClusterSummary object. E.g. If a service has been
+	// removed, the ClusterSummary status will not show that service, therefore
+	// we also want the entry for that service to be removed from conditions.
+	cd.Status.Services = servicesStatus
+	l.Info("Successfully updated status of services")
+
 	return ctrl.Result{}, nil
 }
 
diff --git a/internal/controller/multiclusterservice_controller.go b/internal/controller/multiclusterservice_controller.go
index bb80ce613..781335464 100644
--- a/internal/controller/multiclusterservice_controller.go
+++ b/internal/controller/multiclusterservice_controller.go
@@ -40,6 +40,7 @@ import (
 
 	kcm "github.com/K0rdent/kcm/api/v1alpha1"
 	"github.com/K0rdent/kcm/internal/metrics"
+	"github.com/K0rdent/kcm/internal/record"
 	"github.com/K0rdent/kcm/internal/sveltos"
 	"github.com/K0rdent/kcm/internal/utils"
 	"github.com/K0rdent/kcm/internal/utils/ratelimit"
@@ -106,7 +107,7 @@ func (*MultiClusterServiceReconciler) initServicesConditions(mcs *kcm.MultiClust
 	return changed
 }
 
-func (*MultiClusterServiceReconciler) setCondition(mcs *kcm.MultiClusterService, typ string, err error) (changed bool) { //nolint:unparam // readability
+func (*MultiClusterServiceReconciler) setCondition(mcs *kcm.MultiClusterService, typ string, err error) (changed bool) {
 	reason, cstatus, msg := kcm.SucceededReason, metav1.ConditionTrue, ""
 	if err != nil {
 		reason, cstatus, msg = kcm.FailedReason, metav1.ConditionFalse, err.Error()
@@ -144,12 +145,18 @@ func (r *MultiClusterServiceReconciler) reconcileUpdate(ctx context.Context, mcs
 
 	if r.IsDisabledValidationWH {
 		if err := validation.ServicesHaveValidTemplates(ctx, r.Client, mcs.Spec.ServiceSpec.Services, r.SystemNamespace); err != nil {
-			r.setCondition(mcs, kcm.ServicesReferencesValidationCondition, err)
+			if r.setCondition(mcs, kcm.ServicesReferencesValidationCondition, err) {
+				r.warnf(mcs, kcm.ServicesReferencesValidationFailedReason, err.Error())
+			}
+
 			l.Error(err, "failed to validate services reference valid ServiceTemplates, will not retrigger this error")
 			return ctrl.Result{}, r.updateStatus(ctx, mcs) // no reason to reconcile further
 		}
 	}
-	r.setCondition(mcs, kcm.ServicesReferencesValidationCondition, nil) // if wh is enabled just set it ok, otherwise it succeeded
+
+	if r.setCondition(mcs, kcm.ServicesReferencesValidationCondition, nil) { // if wh is enabled just set it ok, otherwise it succeeded
+		r.eventf(mcs, kcm.ServicesReferencesValidationSucceededReason, "Successfully validated services references")
+	}
 
 	// servicesErr is handled separately from err because we do not want
 	// to set the condition of SveltosClusterProfileReady type to "False"
@@ -158,8 +165,22 @@ func (r *MultiClusterServiceReconciler) reconcileUpdate(ctx context.Context, mcs
 
 	// TODO: should be refactored, unmaintainable; requires to refactor the whole conditions-approach (e.g. init on each event); requires to refactor controllers to be moved to dedicated pkgs
 	defer func() {
-		r.setCondition(mcs, kcm.SveltosClusterProfileReadyCondition, err)
-		r.setCondition(mcs, kcm.FetchServicesStatusSuccessCondition, servicesErr)
+		if r.setCondition(mcs, kcm.SveltosClusterProfileReadyCondition, err) {
+			if err != nil {
+				r.warnf(mcs, kcm.SveltosClusterProfileNotReadyReason, err.Error())
+			} else {
+				r.eventf(mcs, kcm.SveltosClusterProfileReadyCondition, "Successfully reconciled %s ClusterProfile", mcs.Name)
+			}
+		}
+
+		if r.setCondition(mcs, kcm.FetchServicesStatusSuccessCondition, servicesErr) {
+			if servicesErr != nil {
+				r.warnf(mcs, kcm.FetchServicesStatusFailedReason, servicesErr.Error())
+			} else {
+				r.eventf(mcs, kcm.FetchServicesStatusSuccessCondition, "Successfully fetched status of services from Sveltos ClusterSummaries")
+			}
+		}
+
 		if r.IsDisabledValidationWH && apierrors.IsNotFound(err) {
 			// non-services NotFound errors relate only to ServiceTemplate
 			// if they are gone then nothing to do
@@ -225,14 +246,36 @@ func (r *MultiClusterServiceReconciler) reconcileUpdate(ctx context.Context, mcs
 
 	if len(mcs.Spec.ServiceSpec.Services) == 0 {
 		mcs.Status.Services = nil
-	} else {
-		var servicesStatus []kcm.ServiceStatus
-		servicesStatus, servicesErr = updateServicesStatus(ctx, r.Client, profileRef, profile.Status.MatchingClusterRefs, mcs.Status.Services)
-		if servicesErr != nil {
-			return ctrl.Result{}, nil
+		return ctrl.Result{}, nil
+	}
+
+	servicesStatus, servicesErr := getServicesStatus(ctx, r.Client, profileRef, profile.Status.MatchingClusterRefs)
+	if servicesErr != nil {
+		return ctrl.Result{}, nil
+	}
+
+	// Running this loop for the sole purpose of creating
+	// a kubernetes event for each change in conditions.
+	for _, svc := range servicesStatus {
+		idx := slices.IndexFunc(mcs.Status.Services, func(o kcm.ServiceStatus) bool {
+			return svc.ClusterNamespace == o.ClusterNamespace && svc.ClusterName == o.ClusterName
+		})
+
+		for _, cond := range svc.Conditions {
+			if idx > -1 && apimeta.SetStatusCondition(&mcs.Status.Services[idx].Conditions, cond) {
+				sveltos.CreateEventFromCondition(mcs, mcs.Generation, client.ObjectKey{Namespace: svc.ClusterNamespace, Name: svc.ClusterName}, &cond)
+			}
+			// If idx == -1, then a new service was added to the mcs spec so we should create an event in this case.
+			sveltos.CreateEventFromCondition(mcs, mcs.Generation, client.ObjectKey{Namespace: svc.ClusterNamespace, Name: svc.ClusterName}, &cond)
 		}
-		mcs.Status.Services = servicesStatus
 	}
+
+	// We are overwriting conditions so as to be in-sync with the custom status
+	// implemented by Sveltos ClusterSummary object. E.g. If a service has been
+	// removed, the ClusterSummary status will not show that service, therefore
+	// we also want the entry for that service to be removed from conditions.
+	mcs.Status.Services = servicesStatus
+
 	return ctrl.Result{}, nil
 }
 
@@ -395,14 +438,16 @@ func updateStatusConditions(conditions []metav1.Condition) []metav1.Condition {
 	return conditions
 }
 
-// updateServicesStatus updates the services deployment status.
-func updateServicesStatus(ctx context.Context, c client.Client, profileRef client.ObjectKey, profileStatusMatchingClusterRefs []corev1.ObjectReference, servicesStatus []kcm.ServiceStatus) ([]kcm.ServiceStatus, error) {
+// getServicesStatus gets the services deployment status.
+func getServicesStatus(ctx context.Context, c client.Client, profileRef client.ObjectKey, profileStatusMatchingClusterRefs []corev1.ObjectReference) ([]kcm.ServiceStatus, error) {
 	profileKind := sveltosv1beta1.ProfileKind
 	if profileRef.Namespace == "" {
 		profileKind = sveltosv1beta1.ClusterProfileKind
 	}
 
-	for _, obj := range profileStatusMatchingClusterRefs {
+	servicesStatus := make([]kcm.ServiceStatus, len(profileStatusMatchingClusterRefs))
+
+	for i, obj := range profileStatusMatchingClusterRefs {
 		isSveltosCluster := obj.APIVersion == libsveltosv1beta1.GroupVersion.String()
 		summaryName := sveltoscontrollers.GetClusterSummaryName(profileKind, profileRef.Name, obj.Name, isSveltosCluster)
 
@@ -412,16 +457,9 @@ func updateServicesStatus(ctx context.Context, c client.Client, profileRef clien
 			return nil, fmt.Errorf("failed to get ClusterSummary %s to fetch status: %w", summaryRef.String(), err)
 		}
 
-		idx := slices.IndexFunc(servicesStatus, func(o kcm.ServiceStatus) bool {
-			return obj.Name == o.ClusterName && obj.Namespace == o.ClusterNamespace
-		})
-
-		if idx < 0 {
-			servicesStatus = append(servicesStatus, kcm.ServiceStatus{
-				ClusterName:      obj.Name,
-				ClusterNamespace: obj.Namespace,
-			})
-			idx = len(servicesStatus) - 1
+		status := kcm.ServiceStatus{
+			ClusterName:      obj.Name,
+			ClusterNamespace: obj.Namespace,
 		}
 
 		conditions, err := sveltos.GetStatusConditions(&summary)
@@ -429,11 +467,8 @@ func updateServicesStatus(ctx context.Context, c client.Client, profileRef clien
 			return nil, err
 		}
 
-		// We are overwriting conditions so as to be in-sync with the custom status
-		// implemented by Sveltos ClusterSummary object. E.g. If a service has been
-		// removed, the ClusterSummary status will not show that service, therefore
-		// we also want the entry for that service to be removed from conditions.
-		servicesStatus[idx].Conditions = conditions
+		status.Conditions = conditions
+		servicesStatus[i] = status
 	}
 
 	return servicesStatus, nil
@@ -544,3 +579,11 @@ func (r *MultiClusterServiceReconciler) SetupWithManager(mgr ctrl.Manager) error
 
 	return managedController.Complete(r)
 }
+
+func (*MultiClusterServiceReconciler) eventf(mcs *kcm.MultiClusterService, reason, message string, args ...any) {
+	record.Eventf(mcs, mcs.Generation, reason, message, args...)
+}
+
+func (*MultiClusterServiceReconciler) warnf(mcs *kcm.MultiClusterService, reason, message string, args ...any) {
+	record.Warnf(mcs, mcs.Generation, reason, message, args...)
+}
diff --git a/internal/sveltos/status.go b/internal/sveltos/status.go
index 9b1c787f0..387b6d814 100644
--- a/internal/sveltos/status.go
+++ b/internal/sveltos/status.go
@@ -17,22 +17,24 @@ package sveltos
 import (
 	"errors"
 	"fmt"
+	"strings"
 
 	sveltosv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1"
 	apimeta "k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	kcm "github.com/K0rdent/kcm/api/v1alpha1"
+	"github.com/K0rdent/kcm/internal/record"
 )
 
 // GetStatusConditions returns a list of conditions from provided ClusterSummary.
-func GetStatusConditions(summary *sveltosv1beta1.ClusterSummary) ([]metav1.Condition, error) {
+func GetStatusConditions(summary *sveltosv1beta1.ClusterSummary) (conditions []metav1.Condition, err error) {
 	if summary == nil {
 		return nil, errors.New("error getting status from ClusterSummary: nil summary provided")
 	}
 
-	conditions := make([]metav1.Condition, 0, len(summary.Status.FeatureSummaries)+len(summary.Status.HelmReleaseSummaries))
-
 	for _, x := range summary.Status.FeatureSummaries {
 		msg := ""
 		status := metav1.ConditionTrue
@@ -66,6 +68,30 @@ func GetStatusConditions(summary *sveltosv1beta1.ClusterSummary) ([]metav1.Condi
 	return conditions, nil
 }
 
+// CreateEventFromCondition creates sveltos related kubernetes event from the provided condition.
+func CreateEventFromCondition(object runtime.Object, generation int64, targetCluster client.ObjectKey, condition *metav1.Condition) {
+	if isHelmReleaseReadyConditionType(condition) {
+		if condition.Status == metav1.ConditionTrue {
+			record.Eventf(object, generation, kcm.SveltosHelmReleaseReadyCondition, condition.Message)
+		} else {
+			record.Warnf(object, generation, kcm.SveltosHelmReleaseNotReadyReason, condition.Message)
+		}
+		return
+	}
+
+	// If here then the condition is related to one of the Sveltos Features (Resources, Helm, Kustomize).
+
+	// This msg will render as:
+	// 1. Sveltos feature Resources Provisioned on target cluster cluster-namespace/cluster-name
+	// 2. Sveltos feature Helm FailedNonRetriable on target cluster cluster-namespace/cluster-name
+	msg := fmt.Sprintf("Sveltos feature %s %s on target cluster %s", condition.Type, condition.Reason, targetCluster.String())
+	if condition.Status == metav1.ConditionTrue {
+		record.Eventf(object, generation, kcm.SveltosFeatureReadyReason, msg)
+	} else {
+		record.Eventf(object, generation, kcm.SveltosFeatureNotReadyReason, msg)
+	}
+}
+
 // helmReleaseReadyConditionType returns a SveltosHelmReleaseReady
 // type per service to be used in status conditions.
 func helmReleaseReadyConditionType(releaseNamespace, releaseName string) string {
@@ -77,6 +103,10 @@ func helmReleaseReadyConditionType(releaseNamespace, releaseName string) string
 	)
 }
 
+func isHelmReleaseReadyConditionType(condition *metav1.Condition) bool {
+	return strings.HasSuffix(condition.Type, kcm.SveltosHelmReleaseReadyCondition)
+}
+
 func helmReleaseConditionMessage(releaseNamespace, releaseName, conflictMsg string) string {
 	msg := "Release " + releaseNamespace + "/" + releaseName
 	if conflictMsg != "" {


Reviews:
user: login: wahabmk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T09:53:42Z
user: login: wahabmk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T10:01:07Z
user: login: eromanova
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-24T11:07:12Z
user: login: eromanova
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-24T16:35:51Z
user: login: eromanova
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-24T17:23:01Z
user: login: eromanova
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-24T17:24:02Z

Review comments:
diff_hunk: @@ -52,6 +54,18 @@ const (
 	ServicesReferencesValidationCondition = "ServicesReferencesValidation"
 )
 
+// Reasons are provided as utility, and not part of the declarative API.
path: api/v1alpha1/multiclusterservice_types.go
user: login: wahabmk
body: Inspired from:
https://github.com/fluxcd/source-controller/blob/034bc4962d977b27b420686e322249ead9485425/api/v1/condition_types.go#L65
created_at: 2025-04-23T09:53:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -66,6 +68,30 @@ func GetStatusConditions(summary *sveltosv1beta1.ClusterSummary) ([]metav1.Condi
 	return conditions, nil
 }
 
+// CreateEventFromCondition creates sveltos related kubernetes event from the provided condition.
+func CreateEventFromCondition(object runtime.Object, generation int64, targetCluster client.ObjectKey, condition *metav1.Condition) {
path: internal/sveltos/status.go
user: login: wahabmk
body: The events created with this function are the ones that show up in the controller logs but not with kubectl, as explained in the PR description.
created_at: 2025-04-23T10:01:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -571,12 +572,20 @@ func (r *ClusterDeploymentReconciler) updateServices(ctx context.Context, cd *kc
 		if r.IsDisabledValidationWH {
 			merr = errors.Join(merr, validation.ServicesHaveValidTemplates(ctx, r.Client, cd.Spec.ServiceSpec.Services, cd.Namespace))
 		}
-		r.setCondition(cd, kcm.ServicesReferencesValidationCondition, merr)
+
 		if merr != nil {
 			// at this point 2/3 conditions will have unknown status, 1/3 (services validation) cond will have failed cond
+			if r.setCondition(cd, kcm.ServicesReferencesValidationCondition, merr) {
+				r.warnf(cd, kcm.ServicesReferencesValidationFailedReason, err.Error())
path: internal/controller/clusterdeployment_controller.go
user: login: eromanova
body: Shouldn't it be `merr` instead?

```suggestion
				r.warnf(cd, kcm.ServicesReferencesValidationFailedReason, merr.Error())
```
created_at: 2025-04-23T11:15:56Z
author_association: COLLABORATOR
diff_hunk: @@ -52,6 +54,18 @@ const (
 	ServicesReferencesValidationCondition = "ServicesReferencesValidation"
 )
 
+// Reasons are provided as utility, and not part of the declarative API.
path: api/v1alpha1/multiclusterservice_types.go
user: login: eromanova
body: Resolving this comment to proceed with merging.
created_at: 2025-04-24T17:23:01Z
author_association: COLLABORATOR
diff_hunk: @@ -66,6 +68,30 @@ func GetStatusConditions(summary *sveltosv1beta1.ClusterSummary) ([]metav1.Condi
 	return conditions, nil
 }
 
+// CreateEventFromCondition creates sveltos related kubernetes event from the provided condition.
+func CreateEventFromCondition(object runtime.Object, generation int64, targetCluster client.ObjectKey, condition *metav1.Condition) {
path: internal/sveltos/status.go
user: login: eromanova
body: Resolving this comment to proceed with merging. This problem relates to all of the events and can be addressed separately.
created_at: 2025-04-24T17:24:02Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-22T21:04:09Z
body: There is some code review from AI. See the results [here](https://github.com/k0rdent/kcm/actions/runs/14646816713/job/41102993844#step:8:1)
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-22T21:05:50Z
body: There is some code review from AI. See the results [here](https://github.com/k0rdent/kcm/actions/runs/14604475907/job/40970278712#step:8:1)
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-23T02:11:55Z
body: There is some code review from AI. See the results [here](https://github.com/k0rdent/kcm/actions/runs/14608329451/job/40981586450#step:8:1)
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-23T02:14:35Z
body: There is some code review from AI. See the results [here](https://github.com/k0rdent/kcm/actions/runs/14608368312/job/40981705055#step:8:1)
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-23T02:41:30Z
body: There is some code review from AI. See the results [here](https://github.com/k0rdent/kcm/actions/runs/14608669877/job/40982500486#step:8:1)
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-23T02:48:56Z
body: There is some code review from AI. See the results [here](https://github.com/k0rdent/kcm/actions/runs/14608757747/job/40982740110#step:8:1)
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-23T02:58:38Z
body: There is some code review from AI. See the results [here](https://github.com/k0rdent/kcm/actions/runs/14608868110/job/40983034609#step:8:1)
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-23T09:31:12Z
body: There is some code review from AI. See the results [here](https://github.com/k0rdent/kcm/actions/runs/14614617146/job/40999920915#step:8:1)
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-23T10:27:26Z
body: There is some code review from AI. See the results [here](https://github.com/k0rdent/kcm/actions/runs/14615743073/job/41003486791#step:8:1)
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-24T12:04:41Z
body: There is some code review from AI. See the results [here](https://github.com/k0rdent/kcm/actions/runs/14640988093/job/41083113050#step:8:1)
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-24T12:05:37Z
body: There is some code review from AI. See the results [here](https://github.com/k0rdent/kcm/actions/runs/14640971113/job/41083052201#step:8:1)
author_association: NONE