diff --git a/controller/appcontroller.go b/controller/appcontroller.go index 964bcc1aa0dec..23bd1bd6fa552 100644 --- a/controller/appcontroller.go +++ b/controller/appcontroller.go @@ -1697,13 +1697,53 @@ func (ctrl *ApplicationController) persistAppStatus(orig *appv1.Application, new } delete(newAnnotations, appv1.AnnotationKeyRefresh) } + + // false on first run (Create) but that's ok, original diff logic can handle nulls; avoid nil pointer deref + hasHelmValuesObject := orig.Status.Sync.ComparedTo.Source.Helm != nil && newStatus.Sync.ComparedTo.Source.Helm != nil + + valuesObjectPatch, valuesModified, valuesErr := []byte{}, false, error(nil) + if hasHelmValuesObject { + valuesObjectPatch, valuesModified, valuesErr = createMergePatchValuesObject( + orig.Status.Sync.ComparedTo.Source.Helm.ValuesObject, + newStatus.Sync.ComparedTo.Source.Helm.ValuesObject, + ) + if valuesErr != nil { + logCtx.Errorf("Error constructing status patch in valuesObject: %v", valuesErr) + return + } + } + + var origTmp, newStatusTmp *apiruntime.RawExtension + if hasHelmValuesObject { + // strategic merge patching should ignore valuesObject + origTmp = orig.Status.Sync.ComparedTo.Source.Helm.ValuesObject + orig.Status.Sync.ComparedTo.Source.Helm.ValuesObject = nil + + newStatusTmp = newStatus.Sync.ComparedTo.Source.Helm.ValuesObject + newStatus.Sync.ComparedTo.Source.Helm.ValuesObject = nil + } patch, modified, err := diff.CreateTwoWayMergePatch( &appv1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: orig.GetAnnotations()}, Status: orig.Status}, &appv1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: newAnnotations}, Status: *newStatus}, appv1.Application{}) + + if hasHelmValuesObject { + orig.Status.Sync.ComparedTo.Source.Helm.ValuesObject = origTmp + newStatus.Sync.ComparedTo.Source.Helm.ValuesObject = newStatusTmp + } if err != nil { logCtx.Errorf("Error constructing app status patch: %v", err) return } + + if valuesModified { + modified = true + patch, err = jsonpatch.MergeMergePatches(patch, valuesObjectPatch) + if err != nil { + logCtx.Errorf("error merging operation state patch: %v", err) + return + } + } + if !modified { logCtx.Infof("No status changes. Skipping patch") return @@ -2142,3 +2182,46 @@ func (ctrl *ApplicationController) toAppQualifiedName(appName, appNamespace stri } type ClusterFilterFunction func(c *argov1alpha.Cluster, distributionFunction sharding.DistributionFunction) bool + +// jsonCreateMergePatch is a wrapper func to calculate the diff between two objects +// instead of bytes. +func jsonCreateMergePatch(orig, new interface{}) ([]byte, bool, error) { + origBytes, err := json.Marshal(orig) + if err != nil { + return nil, false, err + } + newBytes, err := json.Marshal(new) + if err != nil { + return nil, false, err + } + patch, err := jsonpatch.CreateMergePatch(origBytes, newBytes) + return patch, string(patch) != "{}", err +} + +// createMergePatchValuesObject compute patch just for the Helm valuesObject using jsonpatch. +func createMergePatchValuesObject(orig, new *apiruntime.RawExtension) ([]byte, bool, error) { + return jsonCreateMergePatch( + &appv1.Application{ObjectMeta: metav1.ObjectMeta{}, Status: appv1.ApplicationStatus{ + Sync: appv1.SyncStatus{ + ComparedTo: appv1.ComparedTo{ + Source: appv1.ApplicationSource{ + Helm: &appv1.ApplicationSourceHelm{ + ValuesObject: orig, + }, + }, + }, + }, + }}, + &appv1.Application{ObjectMeta: metav1.ObjectMeta{}, Status: appv1.ApplicationStatus{ + Sync: appv1.SyncStatus{ + ComparedTo: appv1.ComparedTo{ + Source: appv1.ApplicationSource{ + Helm: &appv1.ApplicationSourceHelm{ + ValuesObject: new, + }, + }, + }, + }, + }}, + ) +}