Ctrl K
ComunidadGuías…

pkg/packages/addons.go

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

Archivo original ↗Preguntar a KiBot
package packages

import "strings"

// The add-on catalog — the single Go-side source of truth for "is this 3rd-party
// platform machinery?". It is derived from two parts:
//
//  1. crdGroupToChart's canonical chart names — so any operator we already
//     recognize by its CRD group also classifies as an add-on, with zero extra
//     maintenance: add a CRD group, its chart joins the catalog automatically.
//  2. addonChartsExtra — platform/security charts that ship NO CRD group of
//     their own (ingress controllers, DNS, metrics, logging, secrets managers),
//     so (1) can't catch them.
//
// The Applications and Add-ons surfaces share this classifier rather than each
// carrying a private allowlist. The frontend's interim KNOWN_ADDON_CHARTS
// (radar-hub-web packagesModel.ts) mirrors this list until the hub forwards the
// classification on the wire; keep the two in sync until then.

// addonChartsExtra lists add-on charts not keyed by a crdGroupToChart entry.
// Kept in lockstep with radar-hub-web's KNOWN_ADDON_CHARTS extras.
var addonChartsExtra = []string{
	// Argo family beyond the argoproj.io→argo-cd catalog entry
	"argocd", "argo-rollouts", "argo-workflows", "argo-events", "flux2", "capi",
	// observability / logging / cost
	"prometheus", "prometheus-operator", "grafana", "loki", "tempo", "mimir",
	"victoria-metrics", "caretta", "alloy", "pyroscope", "promtail", "thanos",
	"jaeger", "kiali",
	"fluent-bit", "fluentd", "vector", "opencost", "kubecost",
	"kube-state-metrics", "node-problem-detector", "datadog", "newrelic-bundle",
	// service mesh / ingress / dns
	"istiod", "istio-base", "ingress-nginx", "nginx-ingress", "external-dns",
	"linkerd", "kong", "contour", "cilium",
	// gateway-helm is Envoy Gateway's actual chart name
	"envoy-gateway", "gateway-helm",
	// secrets / policy / security
	"sealed-secrets", "vault", "vault-secrets-operator", "gatekeeper",
	"falco", "trivy-operator",
	// autoscaling / scheduling / ops
	"metrics-server", "cluster-autoscaler", "vertical-pod-autoscaler", "vpa",
	"reloader", "descheduler", "aws-load-balancer-controller", "keda",
	"kured", "kube-fledged", "kubernetes-dashboard",
	// platform / storage / networking / serverless
	"cnpg", "cloudnative-pg", "opentelemetry-collector", "opentelemetry-operator",
	"crossplane", "crossplane-rbac-manager", "coredns", "calico", "longhorn",
	"metallb", "knative-serving", "knative-eventing", "knative-operator",
	"cluster-api",
}

// knownAddonCharts is the resolved catalog: every canonical chart name
// crdGroupToChart maps to, unioned with addonChartsExtra. Built once at init so
// the catalog stays a derivative of crdGroupToChart, not a parallel copy.
var knownAddonCharts = buildAddonCatalog()

// genericAddonNames are real add-ons, but common enough words that name-only
// evidence can misclassify a user app literally named "vector" or "vault".
// Chart/addon-manager/image evidence still classifies them.
var genericAddonNames = map[string]bool{
	"alloy": true, "contour": true, "kong": true, "vault": true, "vector": true,
}

var addonImageRepos = map[string][]string{
	"alloy":   {"grafana/alloy"},
	"contour": {"projectcontour/contour"},
	"kong":    {"kong/kubernetes-ingress-controller", "kong/kong-gateway", "kong/kong"},
	"vault":   {"hashicorp/vault", "hashicorp/vault-k8s", "hashicorp/vault-csi-provider"},
	"vector":  {"timberio/vector", "vectordotdev/vector"},
}

func buildAddonCatalog() map[string]bool {
	m := make(map[string]bool, len(crdGroupToChart)+len(addonChartsExtra))
	for _, chart := range crdGroupToChart {
		m[chart] = true
	}
	for _, chart := range addonChartsExtra {
		m[chart] = true
	}
	return m
}

// matchAddonChart reports the catalog entry a value matches — exact, or
// hyphen-prefixed so a versioned/sub-named value ("kube-prometheus-stack-45",
// "cert-manager-webhook") still hits its base. Returns ("", false) on no match.
func matchAddonChart(value string) (base string, ok bool) {
	v := strings.ToLower(strings.TrimSpace(value))
	if v == "" {
		return "", false
	}
	if knownAddonCharts[v] {
		return v, true
	}
	best := ""
	for known := range knownAddonCharts {
		if strings.HasPrefix(v, known+"-") {
			if len(known) > len(best) || (len(known) == len(best) && known < best) {
				best = known
			}
		}
	}
	if best != "" {
		return best, true
	}
	return "", false
}

func matchAddonImage(image string) (base string, ok bool) {
	repo := imageRepo(image)
	if repo == "" {
		return "", false
	}
	best := ""
	for addon, suffixes := range addonImageRepos {
		for _, suffix := range suffixes {
			if repo == suffix || strings.HasSuffix(repo, "/"+suffix) {
				if len(suffix) > len(best) || (len(suffix) == len(best) && suffix < best) {
					best = suffix
					base = addon
				}
			}
		}
	}
	return base, base != ""
}

func imageRepo(image string) string {
	v := strings.ToLower(strings.TrimSpace(image))
	if v == "" {
		return ""
	}
	if at := strings.Index(v, "@"); at >= 0 {
		v = v[:at]
	}
	slash := strings.LastIndex(v, "/")
	colon := strings.LastIndex(v, ":")
	if colon > slash {
		v = v[:colon]
	}
	return v
}

// ClassifyAddon reports whether a workload belongs to 3rd-party platform
// machinery, matching its helm.sh/chart base, primary image repo,
// app.kubernetes.io/{name,part-of}, or workload name against the shared add-on catalog. Returns the evidence
// ("field=value", with the matched catalog base in parens on a prefix hit) when
// classified.
//
// This is a CLASSIFIER, never a drop filter: callers keep the row visible
// (raw-always) and tag it, so the UI can fold add-ons away while still
// explaining WHY each was tagged.
func ClassifyAddon(chart, appName, partOf, name, addonManagerMode, image string) (addon bool, why string) {
	// The upstream addon-manager label is the platform itself declaring
	// "managed addon" (GKE stamps it on managed components, upstream
	// addon-manager on its manifests) — stronger than any catalog match, and
	// no user app carries it.
	if v := strings.TrimSpace(addonManagerMode); v != "" {
		return true, "addonmanager.kubernetes.io/mode=" + v
	}
	chartBase, _ := SplitChart(chart)
	if addon, why := classifyAddonValue("chart", chartBase, true); addon {
		return addon, why
	}
	if base, ok := matchAddonImage(image); ok {
		return true, "image=" + imageRepo(image) + " (" + base + ")"
	}
	candidates := []struct{ field, value string }{
		{"app.kubernetes.io/name", appName},
		{"app.kubernetes.io/part-of", partOf},
		{"name", name},
	}
	for _, c := range candidates {
		if addon, why := classifyAddonValue(c.field, c.value, false); addon {
			return addon, why
		}
	}
	return false, ""
}

func classifyAddonValue(field, value string, strong bool) (bool, string) {
	v := strings.ToLower(strings.TrimSpace(value))
	if v == "" {
		return false, ""
	}
	base, ok := matchAddonChart(v)
	if !ok || (!strong && genericAddonNames[base]) {
		return false, ""
	}
	if base == v {
		return true, field + "=" + v
	}
	return true, field + "=" + v + " (" + base + ")"
}