Ctrl K
ComunidadGuías…

pkg/gitops/insights/diffobjects.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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
}