pkg/gitops/insights/diffobjects.go
Rama main · réplica de lectura del repositorio original.
package insights
import "sort"
// DiffObjects walks two whole manifests (desired vs live) and returns the
// field-level differences, reusing the same diffValues walker that backs the
// last-applied drift path.
//
// Two scoping rules keep the output to real, user-actionable drift:
// - `status` is skipped entirely — it is controller-owned observed state, not
// declared intent.
// - under `metadata`, only `labels` and `annotations` are compared. The rest
// of metadata (resourceVersion, uid, generation, managedFields,
// creationTimestamp, …) is server-assigned and is never drift.
//
// The inputs are Argo CD's normalized/predicted states, so Argo's own
// normalizations and the Application's spec.ignoreDifferences are already
// applied upstream. This makes DiffObjects a field-level *summary* only — the
// canonical, authoritative view is the desired/live YAML pair the caller
// renders alongside these entries. Output is sorted by path and capped at
// driftEntryCap. The slice is never nil, so an empty diff serializes as []
// rather than null. DiffObjectsTruncated also reports whether the cap cut
// entries off.
func DiffObjects(desired, live map[string]any) []DriftEntry {
entries, _ := DiffObjectsTruncated(desired, live)
return entries
}
// DiffObjectsTruncated is DiffObjects plus whether driftEntryCap cut entries
// off.
func DiffObjectsTruncated(desired, live map[string]any) (entries []DriftEntry, truncated bool) {
entries = []DriftEntry{}
for _, key := range unionKeys(desired, live) {
switch key {
case "status":
continue
case "metadata":
entries = diffMetadataScoped(desired[key], live[key], entries)
default:
entries = diffValues(key, desired[key], live[key], entries)
}
}
return sortAndCapDriftEntries(entries)
}
// diffMetadataScoped diffs only the labels and annotations sub-maps of two
// metadata blocks, ignoring server-assigned identity/versioning fields.
func diffMetadataScoped(desired, live any, out []DriftEntry) []DriftEntry {
desiredMeta, _ := desired.(map[string]any)
liveMeta, _ := live.(map[string]any)
for _, field := range []string{"labels", "annotations"} {
out = diffValues("metadata."+field, desiredMeta[field], liveMeta[field], out)
}
return out
}
// sortAndCapDriftEntries orders entries by path and truncates to driftEntryCap,
// reporting whether truncation occurred. Shared by DiffObjects and the
// last-applied drift path so the two can't drift on ordering or the cap.
func sortAndCapDriftEntries(entries []DriftEntry) ([]DriftEntry, bool) {
sort.SliceStable(entries, func(i, j int) bool { return entries[i].Path < entries[j].Path })
if len(entries) > driftEntryCap {
return entries[:driftEntryCap], true
}
return entries, false
}
// unionKeys returns the deduplicated set of top-level keys across two maps.
// Order is unspecified; callers that need determinism sort the resulting
// entries (DiffObjects does, via sortAndCapDriftEntries).
func unionKeys(a, b map[string]any) []string {
set := make(map[string]struct{}, len(a)+len(b))
for k := range a {
set[k] = struct{}{}
}
for k := range b {
set[k] = struct{}{}
}
out := make([]string, 0, len(set))
for k := range set {
out = append(out, k)
}
return out
}