From 0d455849072f986099aee64939f6881532625b3f Mon Sep 17 00:00:00 2001 From: kabicin <37311900+kabicin@users.noreply.github.com> Date: Thu, 10 Jul 2025 09:50:20 -0400 Subject: [PATCH 1/2] Add annotation tracking into the .status field --- api/v1/runtimecomponent_types.go | 24 +++++ api/v1/zz_generated.deepcopy.go | 16 ++++ api/v1beta2/runtimecomponent_types.go | 14 +++ common/common.go | 95 +++++++++++++++++++ common/types.go | 17 ++++ .../rc.app.stacks_runtimecomponents.yaml | 6 ++ .../controller/runtimecomponent_controller.go | 3 +- utils/reconciler.go | 9 ++ utils/utils.go | 37 ++++++++ 9 files changed, 220 insertions(+), 1 deletion(-) diff --git a/api/v1/runtimecomponent_types.go b/api/v1/runtimecomponent_types.go index 2e694dfcd..ae9e526ff 100644 --- a/api/v1/runtimecomponent_types.go +++ b/api/v1/runtimecomponent_types.go @@ -450,6 +450,8 @@ type RuntimeComponentStatus struct { // The reconciliation interval in seconds. ReconcileInterval *int32 `json:"reconcileInterval,omitempty"` + + TrackedAnnotations map[common.StatusTrackedAnnotationType][]string `json:"trackedAnnotations,omitempty"` } // Defines possible status conditions. @@ -801,6 +803,28 @@ func (s *RuntimeComponentStatus) SetBinding(r *corev1.LocalObjectReference) { s.Binding = r } +func (s *RuntimeComponentStatus) GetTrackedAnnotations() common.StatusTrackedAnnotations { + return s.TrackedAnnotations +} + +func (s *RuntimeComponentStatus) SetTrackedAnnotations(trackedAnnotations common.StatusTrackedAnnotations) { + s.TrackedAnnotations = trackedAnnotations +} + +func (s *RuntimeComponentStatus) GetTrackedAnnotation(annotationType common.StatusTrackedAnnotationType) []string { + if s.TrackedAnnotations == nil { + s.TrackedAnnotations = make(common.StatusTrackedAnnotations) + } + return s.TrackedAnnotations[annotationType] +} + +func (s *RuntimeComponentStatus) SetTrackedAnnotation(annotationType common.StatusTrackedAnnotationType, annotationKeys []string) { + if s.TrackedAnnotations == nil { + s.TrackedAnnotations = make(common.StatusTrackedAnnotations) + } + s.TrackedAnnotations[annotationType] = annotationKeys +} + // GetMinReplicas returns minimum replicas func (a *RuntimeComponentAutoScaling) GetMinReplicas() *int32 { return a.MinReplicas diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 921a97e33..7e4ae2d50 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -735,6 +735,22 @@ func (in *RuntimeComponentStatus) DeepCopyInto(out *RuntimeComponentStatus) { *out = new(int32) **out = **in } + if in.TrackedAnnotations != nil { + in, out := &in.TrackedAnnotations, &out.TrackedAnnotations + *out = make(map[common.StatusTrackedAnnotationType][]string, len(*in)) + for key, val := range *in { + var outVal []string + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = make([]string, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuntimeComponentStatus. diff --git a/api/v1beta2/runtimecomponent_types.go b/api/v1beta2/runtimecomponent_types.go index 273532c22..1521a6229 100644 --- a/api/v1beta2/runtimecomponent_types.go +++ b/api/v1beta2/runtimecomponent_types.go @@ -670,6 +670,20 @@ func (s *RuntimeComponentStatus) SetBinding(r *corev1.LocalObjectReference) { s.Binding = r } +func (s *RuntimeComponentStatus) GetTrackedAnnotations() common.StatusTrackedAnnotations { + return nil +} + +func (s *RuntimeComponentStatus) SetTrackedAnnotations(trackedAnnotations common.StatusTrackedAnnotations) { +} + +func (s *RuntimeComponentStatus) GetTrackedAnnotation(annotationType common.StatusTrackedAnnotationType) []string { + return nil +} + +func (s *RuntimeComponentStatus) SetTrackedAnnotation(annotationType common.StatusTrackedAnnotationType, annotationKeys []string) { +} + // GetMinReplicas returns minimum replicas func (a *RuntimeComponentAutoScaling) GetMinReplicas() *int32 { return a.MinReplicas diff --git a/common/common.go b/common/common.go index a8f750e37..e1dbdb6c8 100644 --- a/common/common.go +++ b/common/common.go @@ -1,6 +1,8 @@ package common import ( + "slices" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/intstr" ) @@ -59,3 +61,96 @@ func GetDefaultMicroProfileLivenessProbe(ba BaseComponent) *corev1.Probe { func GetComponentNameLabel(ba BaseComponent) string { return ba.GetGroupName() + "/name" } + +// GetComponentAnnotationsGetter returns the getter for annotationType annotations configured in the BaseComponent instance. +func GetComponentAnnotationsGetter(ba BaseComponent, annotationType StatusTrackedAnnotationType) func() map[string]string { + var annotationGetter func() map[string]string + annotationGetter = nil + switch annotationType { + case StatusTrackedAnnotationTypeGlobal: + annotationGetter = ba.GetAnnotations + case StatusTrackedAnnotationTypeDeployment: + if ba.GetDeployment() != nil { + annotationGetter = ba.GetDeployment().GetAnnotations + } + case StatusTrackedAnnotationTypeStatefulSet: + if ba.GetStatefulSet() != nil { + annotationGetter = ba.GetStatefulSet().GetAnnotations + } + case StatusTrackedAnnotationTypeService: + if ba.GetService() != nil { + annotationGetter = ba.GetService().GetAnnotations + } + case StatusTrackedAnnotationTypeRoute: + if ba.GetRoute() != nil { + annotationGetter = ba.GetRoute().GetAnnotations + } + } + return annotationGetter +} + +// SaveTrackedAnnotations stores current annotations configured in the BaseComponent into the BaseComponentStatus. +func SaveTrackedAnnotations(ba BaseComponent, annotationTypes ...StatusTrackedAnnotationType) { + for _, annotationType := range annotationTypes { + if annotationGetter := GetComponentAnnotationsGetter(ba, annotationType); annotationGetter != nil { + currentAnnotationKeys := []string{} + for key := range annotationGetter() { + currentAnnotationKeys = append(currentAnnotationKeys, key) + } + if len(currentAnnotationKeys) > 0 { + slices.Sort(currentAnnotationKeys) + ba.GetStatus().SetTrackedAnnotation(annotationType, currentAnnotationKeys) + } else { + ba.GetStatus().SetTrackedAnnotation(annotationType, nil) + } + } + } +} + +// DeleteMissingTrackedAnnotations returns the map of currentAnnotations after removing annotationType annotations +// that are no longer configured in BaseComponent instance but still found within the tracked annotations in BaseComponentStatus. +func DeleteMissingTrackedAnnotations(currentAnnotations map[string]string, ba BaseComponent, annotationTypes ...StatusTrackedAnnotationType) map[string]string { + missingTrackedAnnotations := FilterMissingTrackedAnnotations(ba, StatusTrackedAnnotationTypeGlobal, ba.GetAnnotations()) + for _, missingTrackedAnnotation := range missingTrackedAnnotations { + delete(currentAnnotations, missingTrackedAnnotation) + } + for _, annotationType := range annotationTypes { + if annotationType != StatusTrackedAnnotationTypeGlobal { + if annotationGetter := GetComponentAnnotationsGetter(ba, annotationType); annotationGetter != nil { + missingTrackedAnnotations := FilterMissingTrackedAnnotations(ba, annotationType, annotationGetter()) + for _, missingTrackedAnnotation := range missingTrackedAnnotations { + delete(currentAnnotations, missingTrackedAnnotation) + } + } + } + } + return currentAnnotations +} + +// FilterMissingTrackedAnnotations returns an array of tracked annotations that are no longer configured in the BaseComponent instance +// but still exist in the tracked annotations of BaseComponentStatus. +func FilterMissingTrackedAnnotations(ba BaseComponent, annotationType StatusTrackedAnnotationType, annotations map[string]string) []string { + if ba.GetStatus() == nil { + return []string{} + } + trackedAnnotations := ba.GetStatus().GetTrackedAnnotation(annotationType) + if len(trackedAnnotations) == 0 || annotations == nil { + return []string{} + } + for annotationKey := range annotations { + if slices.Contains(trackedAnnotations, annotationKey) { + // remove annotationKey from trackedAnnotations + deleteIndex := -1 + for i := range trackedAnnotations { + if trackedAnnotations[i] == annotationKey { + deleteIndex = i + break + } + } + if deleteIndex != -1 { + trackedAnnotations = append(trackedAnnotations[:deleteIndex], trackedAnnotations[deleteIndex+1:]...) + } + } + } + return trackedAnnotations +} diff --git a/common/types.go b/common/types.go index 42c54ff27..b610e96b1 100644 --- a/common/types.go +++ b/common/types.go @@ -18,6 +18,10 @@ type StatusEndpointScope string type StatusReferences map[string]string +type StatusTrackedAnnotationType string + +type StatusTrackedAnnotations map[StatusTrackedAnnotationType][]string + const ( StatusReferenceCertSecretName = "svcCertSecretName" StatusReferencePullSecretName = "saPullSecretName" @@ -90,6 +94,11 @@ type BaseComponentStatus interface { UnsetReconcileInterval() GetLatestTransitionTime() *metav1.Time + + GetTrackedAnnotations() StatusTrackedAnnotations + SetTrackedAnnotations(StatusTrackedAnnotations) + GetTrackedAnnotation(StatusTrackedAnnotationType) []string + SetTrackedAnnotation(StatusTrackedAnnotationType, []string) } const ( @@ -105,6 +114,14 @@ const ( // Status Endpoint Scopes StatusEndpointScopeExternal StatusEndpointScope = "External" StatusEndpointScopeInternal StatusEndpointScope = "Internal" + + // Status Tracked Annotation Types + StatusTrackedAnnotationTypeGlobal StatusTrackedAnnotationType = "global" + StatusTrackedAnnotationTypeDeployment StatusTrackedAnnotationType = "deployment" + StatusTrackedAnnotationTypeStatefulSet StatusTrackedAnnotationType = "statefulset" + StatusTrackedAnnotationTypeService StatusTrackedAnnotationType = "service" + StatusTrackedAnnotationTypeRoute StatusTrackedAnnotationType = "route" + StatusTrackedAnnotationTypeNetworkPolicy StatusTrackedAnnotationType = "networkpolicy" ) // BaseComponentAutoscaling represents basic HPA configuration diff --git a/config/crd/bases/rc.app.stacks_runtimecomponents.yaml b/config/crd/bases/rc.app.stacks_runtimecomponents.yaml index 3eb6cf951..081c0c566 100644 --- a/config/crd/bases/rc.app.stacks_runtimecomponents.yaml +++ b/config/crd/bases/rc.app.stacks_runtimecomponents.yaml @@ -8865,6 +8865,12 @@ spec: additionalProperties: type: string type: object + trackedAnnotations: + additionalProperties: + items: + type: string + type: array + type: object versions: properties: reconciled: diff --git a/internal/controller/runtimecomponent_controller.go b/internal/controller/runtimecomponent_controller.go index 59c0546a3..f979187fb 100644 --- a/internal/controller/runtimecomponent_controller.go +++ b/internal/controller/runtimecomponent_controller.go @@ -592,7 +592,8 @@ func (r *RuntimeComponentReconciler) SetupWithManager(mgr ctrl.Manager) error { pred := predicate.Funcs{ UpdateFunc: func(e event.UpdateEvent) bool { // Ignore updates to CR status in which case metadata.Generation does not change - return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() && (isClusterWide || watchNamespacesMap[e.ObjectNew.GetNamespace()]) + return (e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() && (isClusterWide || watchNamespacesMap[e.ObjectNew.GetNamespace()])) || + (e.ObjectOld.GetGeneration() == e.ObjectNew.GetGeneration() && !appstacksutils.AnnotationsEqual(e.ObjectOld.GetAnnotations(), e.ObjectNew.GetAnnotations())) }, CreateFunc: func(e event.CreateEvent) bool { return isClusterWide || watchNamespacesMap[e.Object.GetNamespace()] diff --git a/utils/reconciler.go b/utils/reconciler.go index 6cc11fdf9..b5e48887f 100644 --- a/utils/reconciler.go +++ b/utils/reconciler.go @@ -384,6 +384,15 @@ func (r *ReconcilerBase) ManageSuccess(conditionType common.StatusConditionType, } } + // Track annotations that were defined in the CR into status + common.SaveTrackedAnnotations(ba, + common.StatusTrackedAnnotationTypeGlobal, + common.StatusTrackedAnnotationTypeDeployment, + common.StatusTrackedAnnotationTypeStatefulSet, + common.StatusTrackedAnnotationTypeService, + common.StatusTrackedAnnotationTypeRoute, + common.StatusTrackedAnnotationTypeNetworkPolicy) + err := r.UpdateStatus(ba.(client.Object)) if err != nil { log.Error(err, "Unable to update status") diff --git a/utils/utils.go b/utils/utils.go index 5005717ee..89f28232b 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "os" + "slices" "sort" "strconv" "strings" @@ -45,10 +46,41 @@ const RCOOperandVersion = "1.4.4" var APIVersionNotFoundError = errors.New("APIVersion is not available") +func AnnotationsEqual(a1, a2 map[string]string) bool { + if a1 == nil && a2 == nil { + return true + } + if a1 == nil { + return false + } + if a2 == nil { + return false + } + if len(a1) != len(a2) { + return false + } + for k1, v1 := range a1 { + if a2[k1] != v1 { + return false + } + } + return true +} + +func FilterMapByKeys(values map[string]string, keys []string) map[string]string { + for k := range values { + if !slices.Contains(keys, k) { + delete(values, k) + } + } + return values +} + // CustomizeDeployment ... func CustomizeDeployment(deploy *appsv1.Deployment, ba common.BaseComponent) { obj := ba.(metav1.Object) deploy.Labels = ba.GetLabels() + deploy.Annotations = common.DeleteMissingTrackedAnnotations(deploy.Annotations, ba, common.StatusTrackedAnnotationTypeDeployment) deploy.Annotations = MergeMaps(deploy.Annotations, ba.GetAnnotations()) if ba.GetAutoscaling() == nil { @@ -79,6 +111,7 @@ func CustomizeDeployment(deploy *appsv1.Deployment, ba common.BaseComponent) { func CustomizeStatefulSet(statefulSet *appsv1.StatefulSet, ba common.BaseComponent) { obj := ba.(metav1.Object) statefulSet.Labels = ba.GetLabels() + statefulSet.Annotations = common.DeleteMissingTrackedAnnotations(statefulSet.Annotations, ba, common.StatusTrackedAnnotationTypeStatefulSet) statefulSet.Annotations = MergeMaps(statefulSet.Annotations, ba.GetAnnotations()) if ba.GetAutoscaling() == nil { @@ -110,6 +143,7 @@ func CustomizeStatefulSet(statefulSet *appsv1.StatefulSet, ba common.BaseCompone func CustomizeRoute(route *routev1.Route, ba common.BaseComponent, key string, crt string, ca string, destCACert string) { obj := ba.(metav1.Object) route.Labels = ba.GetLabels() + route.Annotations = common.DeleteMissingTrackedAnnotations(route.Annotations, ba, common.StatusTrackedAnnotationTypeRoute) route.Annotations = MergeMaps(route.Annotations, ba.GetAnnotations()) if ba.GetRoute() != nil { @@ -198,6 +232,7 @@ func ErrorIsNoMatchesForKind(err error, kind string, version string) bool { func CustomizeService(svc *corev1.Service, ba common.BaseComponent) { obj := ba.(metav1.Object) svc.Labels = ba.GetLabels() + svc.Annotations = common.DeleteMissingTrackedAnnotations(svc.Annotations, ba, common.StatusTrackedAnnotationTypeService) CustomizeServiceAnnotations(svc) svc.Annotations = MergeMaps(svc.Annotations, ba.GetAnnotations()) @@ -344,6 +379,7 @@ func customizeProbeDefaults(config *corev1.Probe, defaultProbe *corev1.Probe) *c func CustomizeNetworkPolicy(networkPolicy *networkingv1.NetworkPolicy, isOpenShift bool, ba common.BaseComponent) { obj := ba.(metav1.Object) networkPolicy.Labels = ba.GetLabels() + networkPolicy.Annotations = common.DeleteMissingTrackedAnnotations(networkPolicy.Annotations, ba, common.StatusTrackedAnnotationTypeNetworkPolicy) networkPolicy.Annotations = MergeMaps(networkPolicy.Annotations, ba.GetAnnotations()) networkPolicy.Spec.PolicyTypes = []networkingv1.PolicyType{networkingv1.PolicyTypeIngress} @@ -649,6 +685,7 @@ func customizeAffinityArchitectures(affinity *corev1.Affinity, affinityConfig co func CustomizePodSpec(pts *corev1.PodTemplateSpec, ba common.BaseComponent) { obj := ba.(metav1.Object) pts.Labels = ba.GetLabels() + pts.Annotations = common.DeleteMissingTrackedAnnotations(pts.Annotations, ba, common.StatusTrackedAnnotationTypeDeployment, common.StatusTrackedAnnotationTypeStatefulSet) pts.Annotations = MergeMaps(pts.Annotations, ba.GetAnnotations()) // If they exist, add annotations from the StatefulSet or Deployment to the pods From c2ae759efcedef826bf4e6139e81cf3e1bdc5c50 Mon Sep 17 00:00:00 2001 From: kabicin <37311900+kabicin@users.noreply.github.com> Date: Thu, 10 Jul 2025 09:56:21 -0400 Subject: [PATCH 2/2] Remove network policy annotation type --- common/types.go | 11 +++++------ utils/reconciler.go | 3 +-- utils/utils.go | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/common/types.go b/common/types.go index b610e96b1..d87e59515 100644 --- a/common/types.go +++ b/common/types.go @@ -116,12 +116,11 @@ const ( StatusEndpointScopeInternal StatusEndpointScope = "Internal" // Status Tracked Annotation Types - StatusTrackedAnnotationTypeGlobal StatusTrackedAnnotationType = "global" - StatusTrackedAnnotationTypeDeployment StatusTrackedAnnotationType = "deployment" - StatusTrackedAnnotationTypeStatefulSet StatusTrackedAnnotationType = "statefulset" - StatusTrackedAnnotationTypeService StatusTrackedAnnotationType = "service" - StatusTrackedAnnotationTypeRoute StatusTrackedAnnotationType = "route" - StatusTrackedAnnotationTypeNetworkPolicy StatusTrackedAnnotationType = "networkpolicy" + StatusTrackedAnnotationTypeGlobal StatusTrackedAnnotationType = "global" + StatusTrackedAnnotationTypeDeployment StatusTrackedAnnotationType = "deployment" + StatusTrackedAnnotationTypeStatefulSet StatusTrackedAnnotationType = "statefulset" + StatusTrackedAnnotationTypeService StatusTrackedAnnotationType = "service" + StatusTrackedAnnotationTypeRoute StatusTrackedAnnotationType = "route" ) // BaseComponentAutoscaling represents basic HPA configuration diff --git a/utils/reconciler.go b/utils/reconciler.go index b5e48887f..d8beb3254 100644 --- a/utils/reconciler.go +++ b/utils/reconciler.go @@ -390,8 +390,7 @@ func (r *ReconcilerBase) ManageSuccess(conditionType common.StatusConditionType, common.StatusTrackedAnnotationTypeDeployment, common.StatusTrackedAnnotationTypeStatefulSet, common.StatusTrackedAnnotationTypeService, - common.StatusTrackedAnnotationTypeRoute, - common.StatusTrackedAnnotationTypeNetworkPolicy) + common.StatusTrackedAnnotationTypeRoute) err := r.UpdateStatus(ba.(client.Object)) if err != nil { diff --git a/utils/utils.go b/utils/utils.go index 89f28232b..66bb68797 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -379,7 +379,7 @@ func customizeProbeDefaults(config *corev1.Probe, defaultProbe *corev1.Probe) *c func CustomizeNetworkPolicy(networkPolicy *networkingv1.NetworkPolicy, isOpenShift bool, ba common.BaseComponent) { obj := ba.(metav1.Object) networkPolicy.Labels = ba.GetLabels() - networkPolicy.Annotations = common.DeleteMissingTrackedAnnotations(networkPolicy.Annotations, ba, common.StatusTrackedAnnotationTypeNetworkPolicy) + networkPolicy.Annotations = common.DeleteMissingTrackedAnnotations(networkPolicy.Annotations, ba, common.StatusTrackedAnnotationTypeGlobal) networkPolicy.Annotations = MergeMaps(networkPolicy.Annotations, ba.GetAnnotations()) networkPolicy.Spec.PolicyTypes = []networkingv1.PolicyType{networkingv1.PolicyTypeIngress}