Ctrl K
ComunidadGuías…

pkg/subject/overlay.go

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

Archivo original ↗Preguntar a KiBot
package subject

import (
	"sort"
	"strings"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// ============================= TIER 2: APP OVERLAY =============================

// GitOps / Helm signal keys. Tiers 1-5 consolidate the signal keys that
// pkg/topology/managedby.go reads.
const (
	argoTrackingIDAnnotation = "argocd.argoproj.io/tracking-id"
	argoInstanceLabel        = "argocd.argoproj.io/instance"
	fluxKustomizeNameLabel   = "kustomize.toolkit.fluxcd.io/name"
	fluxKustomizeNSLabel     = "kustomize.toolkit.fluxcd.io/namespace"
	fluxHelmNameLabel        = "helm.toolkit.fluxcd.io/name"
	fluxHelmNSLabel          = "helm.toolkit.fluxcd.io/namespace"
	helmReleaseNameAnno      = "meta.helm.sh/release-name"
	helmReleaseNSAnno        = "meta.helm.sh/release-namespace"

	appInstanceLabel = "app.kubernetes.io/instance"
	partOfLabel      = "app.kubernetes.io/part-of"
	appNameLabel     = "app.kubernetes.io/name"
	bareAppLabel     = "app"
)

// API groups for the synthesized manager refs (tiers 1-5). The
// {HelmRelease,Group:""} native-Helm sentinel that summary.go:sourceForOwner
// keys "helm" off MUST NOT change.
const (
	argoApplicationGroup = "argoproj.io"
	fluxKustomizeGroup   = "kustomize.toolkit.fluxcd.io"
	fluxHelmGroup        = "helm.toolkit.fluxcd.io"
)

// Tier is the 9-tier precedence rank of the winning app signal. Tiers 1-5 are
// the CONSOLIDATED managedby.go precedence, in its native order (argo-instance
// #4, Helm-release #5 — matching managedby.go). The locked rule is "Helm ranks
// above the app.kubernetes.io label tiers (6-9)", NOT above Argo. Tiers 6-9 are
// NET-NEW.
type Tier int

const (
	TierNone            Tier = 0
	TierFluxHelmRelease Tier = 1 // helm.toolkit.fluxcd.io/{name,namespace}
	TierFluxKustomize   Tier = 2 // kustomize.toolkit.fluxcd.io/{name,namespace}
	TierArgoTrackingID  Tier = 3 // argocd.argoproj.io/tracking-id
	TierArgoInstance    Tier = 4 // argocd.argoproj.io/instance (legacy Argo app label)
	TierHelmRelease     Tier = 5 // meta.helm.sh/release-name — above the label tiers (6-9)
	// TierInstance is Argo CD's DEFAULT resource-tracking label and Helm's
	// standard installation-identity label. It disambiguates installations the
	// way the semantic name/part-of labels cannot (two releases of one chart
	// share app.kubernetes.io/name but differ on /instance), so it ranks above
	// them — but below the explicit Helm-release annotation. NET-NEW.
	TierInstance Tier = 6 // app.kubernetes.io/instance  NET-NEW
	TierPartOf   Tier = 7 // app.kubernetes.io/part-of   NET-NEW
	TierAppName  Tier = 8 // app.kubernetes.io/name      NET-NEW
	TierBareApp  Tier = 9 // bare `app` / name heuristic NET-NEW
)

// Confidence is the rendered trust tier.
type Confidence string

const (
	ConfidenceHigh   Confidence = "high"   // tiers 1-4 (Flux / Argo) — core GitOps
	ConfidenceMedium Confidence = "medium" // tiers 5-8 (Helm-release / instance / part-of / name) — badge
	ConfidenceLow    Confidence = "low"    // tier 9 (bare app) — opt-in, never silent
)

func confidenceForTier(t Tier) Confidence {
	switch {
	case t >= TierFluxHelmRelease && t <= TierArgoInstance:
		return ConfidenceHigh
	case t >= TierHelmRelease && t <= TierAppName:
		return ConfidenceMedium
	default:
		return ConfidenceLow
	}
}

// Signal is one matched app-grouping signal — winner OR retained runner-up.
// Ref is the implied app/manager (Flux/Argo/Helm CR ref for tiers 1-5; a
// synthetic {kind:"app"} ref for tiers 6-8). Retaining these instead of
// returning on first hit is the conflicts[] net-new logic.
type Signal struct {
	Tier       Tier
	Key        string // e.g. "<ns>/HelmRelease/<name>" or "<ns>/app/<name>"
	Ref        Ref    // hand-set Group for tiers 1-5 (Flux groups / "" native Helm); empty for 6-8
	Confidence Confidence
}

// AppOverlay is the OPTIONAL Tier-2 key ABOVE the Subject. Absent (nil) when no
// signal at/above tier 8 (TierAppName) exists -> raw-always: surface degrades to
// Subject-only.
type AppOverlay struct {
	Winner    Signal   // highest-precedence signal (provenance: which tier won)
	Conflicts []Signal // retained runner-ups (NET-NEW: collection + retention)
}

// ResolveOverlay is the Tier-2 entrypoint. It COLLECTS ALL matching signals from
// obj's labels/annotations, sorts by Tier, returns the winner + retained
// conflicts, or nil when nothing reaches TierAppName (TierBareApp alone is opt-in via
// allowBareApp; default off => never silent). It covers topology.ManagedByFromMeta's
// signals (tiers 1-5) but keeps every match, not only the first. The native-Helm
// sentinel {Kind:"HelmRelease",Group:""} the Source classifier keys on is set
// here; enrichRef MUST NOT be applied to tier 1-5 refs (Group is hand-set) —
// pkg/subject does no dp enrichment at all.
func ResolveOverlay(obj metav1.Object, allowBareApp bool) *AppOverlay {
	if obj == nil {
		return nil
	}
	labels := obj.GetLabels()
	annos := obj.GetAnnotations()
	ns := obj.GetNamespace()

	var signals []Signal

	if name, n := labels[fluxHelmNameLabel], labels[fluxHelmNSLabel]; name != "" && n != "" {
		signals = append(signals, Signal{
			Tier: TierFluxHelmRelease,
			Key:  n + "/HelmRelease/" + name,
			Ref:  Ref{Kind: "HelmRelease", Group: fluxHelmGroup, Namespace: n, Name: name},
		})
	}
	if name, n := labels[fluxKustomizeNameLabel], labels[fluxKustomizeNSLabel]; name != "" && n != "" {
		signals = append(signals, Signal{
			Tier: TierFluxKustomize,
			Key:  n + "/Kustomization/" + name,
			Ref:  Ref{Kind: "Kustomization", Group: fluxKustomizeGroup, Namespace: n, Name: name},
		})
	}
	if id := annos[argoTrackingIDAnnotation]; id != "" {
		if argoNS, argoName, ok := parseArgoTrackingID(id); ok {
			signals = append(signals, Signal{
				Tier: TierArgoTrackingID,
				Key:  argoNS + "/Application/" + argoName,
				Ref:  Ref{Kind: "Application", Group: argoApplicationGroup, Namespace: argoNS, Name: argoName},
			})
		}
	}
	if n := annos[helmReleaseNameAnno]; n != "" {
		// Native Helm: Group deliberately empty to distinguish from Flux's
		// HelmRelease CR — the sentinel sourceForOwner classifies "helm" off.
		signals = append(signals, Signal{
			Tier: TierHelmRelease,
			Key:  annos[helmReleaseNSAnno] + "/HelmRelease/" + n,
			Ref:  Ref{Kind: "HelmRelease", Group: "", Namespace: annos[helmReleaseNSAnno], Name: n},
		})
	}
	if n := labels[argoInstanceLabel]; n != "" {
		signals = append(signals, Signal{
			Tier: TierArgoInstance,
			Key:  "/Application/" + n,
			Ref:  Ref{Kind: "Application", Group: argoApplicationGroup, Namespace: "", Name: n},
		})
	}
	if n := labels[appInstanceLabel]; n != "" {
		// Same "/app/" key shape as part-of/name: an instance value unifies with
		// matching name/part-of values (same app). Argo's default tracking writes
		// the app name here; Helm writes the release name — both are the grouping
		// we want. Env-specific by nature, so this is excluded as a CROSS-cluster
		// join key in the Hub (single-cluster overlay only).
		signals = append(signals, Signal{
			Tier: TierInstance,
			Key:  ns + "/app/" + n,
			Ref:  Ref{Kind: "app", Name: n, Namespace: ns},
		})
	}
	if n := labels[partOfLabel]; n != "" {
		signals = append(signals, Signal{
			Tier: TierPartOf,
			Key:  ns + "/app/" + n,
			Ref:  Ref{Kind: "app", Name: n, Namespace: ns},
		})
	}
	if n := labels[appNameLabel]; n != "" {
		signals = append(signals, Signal{
			Tier: TierAppName,
			Key:  ns + "/app/" + n,
			Ref:  Ref{Kind: "app", Name: n, Namespace: ns},
		})
	}
	if n := labels[bareAppLabel]; n != "" {
		signals = append(signals, Signal{
			Tier: TierBareApp,
			Key:  ns + "/app/" + n,
			Ref:  Ref{Kind: "app", Name: n, Namespace: ns},
		})
	}

	if len(signals) == 0 {
		return nil
	}

	sort.SliceStable(signals, func(i, j int) bool { return signals[i].Tier < signals[j].Tier })
	for i := range signals {
		signals[i].Confidence = confidenceForTier(signals[i].Tier)
	}

	winner := signals[0]
	// Raw-always: a bare-app-only overlay is opt-in. Without allowBareApp,
	// TierBareApp alone means no overlay — never silent.
	if winner.Tier > TierAppName && !allowBareApp {
		return nil
	}

	return &AppOverlay{
		Winner:    winner,
		Conflicts: signals[1:],
	}
}

// parseArgoTrackingID parses ArgoCD's tracking-id annotation in either format:
//
//	"<appName>:<group>/<kind>:<resourceNs>/<resourceName>"               (default)
//	"<appNamespace>_<appName>:<group>/<kind>:<resourceNs>/<resourceName>" (namespaced)
//
// Returns (namespace, name, ok). The legacy single-name form yields an empty
// namespace.
func parseArgoTrackingID(value string) (namespace, name string, ok bool) {
	firstColon := strings.Index(value, ":")
	if firstColon < 0 {
		return "", "", false
	}
	head := value[:firstColon]
	sep := strings.Index(head, "_")
	if sep < 0 {
		if head == "" {
			return "", "", false
		}
		return "", head, true
	}
	ns := head[:sep]
	n := head[sep+1:]
	if n == "" {
		return "", "", false
	}
	return ns, n, true
}