pkg/prune/prune.go
Rama main · réplica de lectura del repositorio original.
// Package prune subtracts heavy or noisy subtrees from unstructured Kubernetes
// objects according to declarative per-kind profiles. It is the shared
// mechanism under consumer-specific policies: the resources API's
// include=summary keep-lists (internal/server/resource_summary.go) and
// pkg/ai/context's verbosity pruning. Policies (WHICH paths drop) stay with
// their consumers; only the tree surgery lives here.
package prune
import (
"fmt"
"sort"
"strings"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// TailTrim reduces a slice at Path to only its LAST element, itself reduced
// to only KeepField (e.g. an Argo Application's status.history → the last
// entry's deployedAt). A missing or empty slice is left untouched; a tail
// element missing KeepField yields an empty object; a non-map tail leaves
// the slice untouched (fail open — rewriting an unexpected shape would be a
// transformation, which profiles must never do).
type TailTrim struct {
Path []string
KeepField string
}
// ElementDrop deletes Keys from every map element of the slice at Path.
// Non-map elements are skipped; a missing or non-slice Path is left
// untouched (fail open).
type ElementDrop struct {
Path []string
Keys []string
}
// Profile declares the subtractions for one object shape. Ops execute in
// order: Drop → ElementDrops → TailTrims. Empty paths are skipped, and
// NestedSlice errors (a non-map intermediate on the path) are treated as
// not-found — every malformed shape fails open, leaving the tree untouched.
type Profile struct {
Drop [][]string
ElementDrops []ElementDrop
TailTrims []TailTrim
}
// FromDropTables builds a Profile whose Drop paths are the cross product of
// each table's dot-separated subtree and its keys. It panics on an empty
// subtree, empty path segment, or empty leaf key: the tables are hardcoded
// compile-adjacent data evaluated at init, and a typo'd empty segment would
// otherwise silently no-op forever.
func FromDropTables(tables map[string]map[string]bool) Profile {
var p Profile
for subtree, keys := range tables {
if subtree == "" {
panic("prune.FromDropTables: empty subtree")
}
base := strings.Split(subtree, ".")
for _, seg := range base {
if seg == "" {
panic(fmt.Sprintf("prune.FromDropTables: empty path segment in subtree %q", subtree))
}
}
for key := range keys {
if key == "" {
panic(fmt.Sprintf("prune.FromDropTables: empty leaf key under subtree %q", subtree))
}
path := append(append([]string(nil), base...), key)
p.Drop = append(p.Drop, path)
}
}
// Map iteration order would otherwise make the profile nondeterministic;
// harmless for correctness but noisy when diffing or debugging profiles.
sort.Slice(p.Drop, func(i, j int) bool {
return strings.Join(p.Drop[i], ".") < strings.Join(p.Drop[j], ".")
})
return p
}
// Apply returns a deep copy of obj with the profile's subtractions applied.
// The input is NEVER mutated — callers routinely hold informer-cache objects,
// where in-place edits would corrupt every other consumer.
func Apply(obj *unstructured.Unstructured, p Profile) *unstructured.Unstructured {
if obj == nil {
return nil
}
copied := obj.DeepCopy()
ApplyInPlace(copied.Object, p)
return copied
}
// ApplyInPlace executes the profile directly on m — for callers that already
// own a copy (pkg/ai/context deep-copies before minifying) and shouldn't pay
// for a second one. Never hand this an informer-cache object.
func ApplyInPlace(m map[string]any, p Profile) {
if m == nil {
return
}
for _, path := range p.Drop {
// RemoveNestedField panics on an empty path (fields[:len-1]).
if len(path) == 0 {
continue
}
unstructured.RemoveNestedField(m, path...)
}
for _, d := range p.ElementDrops {
if len(d.Path) == 0 {
continue
}
applyElementDrop(m, d)
}
for _, t := range p.TailTrims {
if len(t.Path) == 0 {
continue
}
applyTailTrimMap(m, t)
}
}
func applyElementDrop(m map[string]any, d ElementDrop) {
v, found, err := unstructured.NestedFieldNoCopy(m, d.Path...)
if !found || err != nil {
return
}
items, ok := v.([]any)
if !ok {
return
}
for _, item := range items {
elem, ok := item.(map[string]any)
if !ok {
continue
}
for _, key := range d.Keys {
delete(elem, key)
}
}
}
func applyTailTrimMap(m map[string]any, t TailTrim) {
// NestedFieldNoCopy, not NestedSlice: the latter deep-copies via
// DeepCopyJSONValue, which PANICS on any non-JSON scalar in the slice
// (a hand-built int, a []string, time.Time) — the one op that could
// crash instead of failing open like Drop/ElementDrop. No-copy read +
// in-place rewrite keeps this op consistent with the rest of the package.
v, found, err := unstructured.NestedFieldNoCopy(m, t.Path...)
if !found || err != nil {
return
}
items, ok := v.([]any)
if !ok || len(items) == 0 {
return
}
last, ok := items[len(items)-1].(map[string]any)
if !ok {
return
}
trimmed := map[string]any{}
if val, exists := last[t.KeepField]; exists {
trimmed[t.KeepField] = val
}
// Assign a FRESH one-element slice (not items[:1], which would keep the
// original backing array — and every pruned entry — live until the
// response is released). No SetNestedSlice, so no deep-copy/panic path.
if base, ok := parentMap(m, t.Path); ok {
base[t.Path[len(t.Path)-1]] = []any{trimmed}
}
}
// parentMap walks to the map holding the final path segment, returning it so
// a caller can rewrite that key. Returns ok=false if any intermediate is
// absent or not a map.
func parentMap(m map[string]any, path []string) (map[string]any, bool) {
cur := m
for _, seg := range path[:len(path)-1] {
next, ok := cur[seg].(map[string]any)
if !ok {
return nil, false
}
cur = next
}
return cur, true
}