diff --git a/controllers/classifier_deployer.go b/controllers/classifier_deployer.go index d462cff..ebae8ef 100644 --- a/controllers/classifier_deployer.go +++ b/controllers/classifier_deployer.go @@ -19,6 +19,7 @@ package controllers import ( "context" "crypto/sha256" + "encoding/json" "errors" "fmt" "reflect" @@ -906,39 +907,55 @@ func (r *ClassifierReconciler) canProceed(ctx context.Context, classifierScope * // the kubeconfig to access management cluster func (r *ClassifierReconciler) getCurrentHash(ctx context.Context, classifierScope *scope.ClassifierScope, cpEndpoint string, cluster *corev1.ObjectReference, f feature, logger logr.Logger) ([]byte, error) { + // Get Classifier Spec hash (at this very precise moment) currentHash := f.currentHash(classifierScope.Classifier) - // If sveltos-agent configuration is in a ConfigMap. fetch ConfigMap and use its Data - // section in the hash evaluation. - if sveltosAgentConfigMap := getSveltosAgentConfigMap(); sveltosAgentConfigMap != "" { - configMap, err := collectAgentConfigMap(ctx, sveltosAgentConfigMap) + sveltosAgentPatches, err := getSveltosAgentPatches(ctx, r.Client, cluster.Namespace, cluster.Name, + clusterproxy.GetClusterType(cluster), logger) + if err != nil { + return nil, err + } + if len(sveltosAgentPatches) > 0 { + sortPatches(sveltosAgentPatches) + + patchBytes, err := json.Marshal(sveltosAgentPatches) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to marshal patches for hashing: %w", err) } + + // Use the hash interface to avoid linter-unfriendly appends h := sha256.New() - config := string(currentHash) - config += render.AsCode(configMap.Data) - h.Write([]byte(config)) + h.Write(currentHash) + h.Write(patchBytes) + + // currentHash is now the hash of (classifier + patches) currentHash = h.Sum(nil) } - // If sveltos-applier configuration is in a ConfigMap. fetch ConfigMap and use its Data - // section in the hash evaluation. - if sveltosApplierConfigMap := getSveltosApplierConfigMap(); sveltosApplierConfigMap != "" { - configMap, err := collectAgentConfigMap(ctx, sveltosApplierConfigMap) + sveltosApplierPatches, err := getSveltosApplierPatches(ctx, r.Client, cluster.Namespace, cluster.Name, + clusterproxy.GetClusterType(cluster), logger) + if err != nil { + return nil, err + } + if len(sveltosApplierPatches) > 0 { + sortPatches(sveltosApplierPatches) + + patchBytes, err := json.Marshal(sveltosApplierPatches) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to marshal patches for hashing: %w", err) } + + // Use the hash interface to avoid linter-unfriendly appends h := sha256.New() - config := string(currentHash) - config += render.AsCode(configMap.Data) - h.Write([]byte(config)) + h.Write(currentHash) + h.Write(patchBytes) + + // currentHash is now the hash of (classifier + patches) currentHash = h.Sum(nil) } var kubeconfig []byte - var err error if r.ClassifierReportMode == AgentSendReportsNoGateway { h := sha256.New() config := string(currentHash) diff --git a/controllers/classifier_predicates.go b/controllers/classifier_predicates.go index 3fdc9fc..e8a7913 100644 --- a/controllers/classifier_predicates.go +++ b/controllers/classifier_predicates.go @@ -167,7 +167,7 @@ func ConfigMapPredicates(logger logr.Logger) predicate.Funcs { "configMap", newConfigMap.Name, ) - if newConfigMap.Namespace != projectsveltos || newConfigMap.Name != getSveltosAgentConfigMap() { + if !isConfigMapWithPatches(newConfigMap) { return false } @@ -195,8 +195,8 @@ func ConfigMapPredicates(logger logr.Logger) predicate.Funcs { "configMap", configMap.Name, ) - if configMap.Namespace == projectsveltos && configMap.Name == getSveltosAgentConfigMap() { - log.V(logs.LogVerbose).Info("ConfigMap created. Will attempt to reconcile associated Classifiers.") + if isConfigMapWithPatches(configMap) { + log.V(logs.LogVerbose).Info("Patch ConfigMap created. Will attempt to reconcile associated Classifiers.") return true } @@ -211,8 +211,8 @@ func ConfigMapPredicates(logger logr.Logger) predicate.Funcs { "configMap", configMap.Name, ) - if configMap.Namespace == projectsveltos && configMap.Name == getSveltosAgentConfigMap() { - log.V(logs.LogVerbose).Info("ConfigMap deleted. Will attempt to reconcile associated Classifiers.") + if isConfigMapWithPatches(configMap) { + log.V(logs.LogVerbose).Info("Patch ConfigMap deleted. Will attempt to reconcile associated Classifiers.") return true } @@ -232,6 +232,31 @@ func ConfigMapPredicates(logger logr.Logger) predicate.Funcs { } } +func isConfigMapWithPatches(cm *corev1.ConfigMap) bool { + isPerClusterPatch := false + annotations := cm.Annotations + if annotations != nil { + _, ok := annotations[sveltosAgentOverrideAnnotation] + if ok { + isPerClusterPatch = true + } + _, ok = annotations[sveltosApplierOverrideAnnotation] + if ok { + isPerClusterPatch = true + } + } + + if isPerClusterPatch { + return true + } + + if cm.Namespace == projectsveltos && cm.Name == getSveltosAgentConfigMap() { + return true + } + + return false +} + // ClassifierReportPredicate predicates for ClassifierReport. ClassifierReconciler watches ClassifierReport events // and react to those by reconciling itself based on following predicates func ClassifierReportPredicate(logger logr.Logger) predicate.Funcs { diff --git a/controllers/classifier_transformations.go b/controllers/classifier_transformations.go index a90e583..13445f1 100644 --- a/controllers/classifier_transformations.go +++ b/controllers/classifier_transformations.go @@ -138,7 +138,7 @@ func (r *ClassifierReconciler) requeueClassifierForConfigMap( r.Mux.Lock() defer r.Mux.Unlock() - if configMap.Namespace != projectsveltos || configMap.Name != getSveltosAgentConfigMap() { + if !isConfigMapWithPatches(configMap) { return nil } diff --git a/controllers/management_cluster.go b/controllers/management_cluster.go index 6354e8b..c7c2714 100644 --- a/controllers/management_cluster.go +++ b/controllers/management_cluster.go @@ -17,11 +17,7 @@ limitations under the License. package controllers import ( - "context" - - corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -83,15 +79,3 @@ func GetSveltosAgentRegistry() string { func getAgentInMgmtCluster() bool { return agentInMgmtCluster } - -func collectAgentConfigMap(ctx context.Context, name string) (*corev1.ConfigMap, error) { - c := getManagementClusterClient() - configMap := &corev1.ConfigMap{} - - err := c.Get(ctx, types.NamespacedName{Namespace: projectsveltos, Name: name}, configMap) - if err != nil { - return nil, err - } - - return configMap, nil -} diff --git a/controllers/utils.go b/controllers/utils.go index 1e59b68..ef18f29 100644 --- a/controllers/utils.go +++ b/controllers/utils.go @@ -19,6 +19,7 @@ package controllers import ( "context" "fmt" + "sort" "strings" "time" @@ -220,3 +221,41 @@ func deplAssociatedClusterExist(ctx context.Context, c client.Client, depl *apps func stringPtr(s string) *string { return &s } + +func sortPatches(patches []libsveltosv1beta1.Patch) { + sort.Slice(patches, func(i, j int) bool { + p1 := patches[i].Target + p2 := patches[j].Target + + // Handle cases where Target might be nil + if p1 == nil && p2 == nil { + return false + } + if p1 == nil { + return true // Nil targets first + } + if p2 == nil { + return false + } + + // Comparison chain + if p1.Group != p2.Group { + return p1.Group < p2.Group + } + if p1.Version != p2.Version { + return p1.Version < p2.Version + } + if p1.Kind != p2.Kind { + return p1.Kind < p2.Kind + } + if p1.Namespace != p2.Namespace { + return p1.Namespace < p2.Namespace + } + if p1.Name != p2.Name { + return p1.Name < p2.Name + } + + // Finally, sort by the patch content itself if targets are identical + return patches[i].Patch < patches[j].Patch + }) +}