Ctrl K
ComunidadGuías…

pkg/ownership/deletion.go

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

Archivo original ↗Preguntar a KiBot
package ownership

import (
	"context"
	"slices"

	"github.com/skyhook-io/radar/pkg/resourceid"
)

// DeletionAnswer projects garbage collection after the subject is removed.
// It is an observation, not a server dry-run or a guarantee that controllers
// will finish. Finalizers, admission and controllers may delay or change it.
type DeletionAnswer struct {
	Subject              Subject
	Dependents, Possible []Observation
	Gaps                 []Gap
	Narrowed, Truncated  bool
}

// Deletion follows every owner reference, including non-controller references.
// A live owner outside the deletion prevents collection; an unknown owner makes
// collection possible. Cycles without a proven path to collection stay possible.
// limit bounds the objects retained and expanded, including survivors. ValidateCaches must
// still pass before the answer is used.
func (v *View) Deletion(ctx context.Context, ref resourceid.Ref, limit int) DeletionAnswer {
	a := DeletionAnswer{Subject: v.subject(ref)}
	if !a.Subject.Present() {
		return a
	}
	objects := map[resourceid.Ref]Observation{ref: a.Subject.Read.Object}
	order := []resourceid.Ref{ref}
	for i := 0; i < len(order); i++ {
		if ctx.Err() != nil {
			a.Truncated = true
			break
		}
		current := order[i]
		children := v.Children(current)
		a.Gaps = append(a.Gaps, children.Gaps...)
		a.Narrowed = a.Narrowed || children.Narrowed
		candidates := children.Children
		if found, searched := v.searches[current]; searched {
			readable, _, _ := v.candidates(found)
			for _, c := range readable {
				for _, owner := range c.OwnerReferences {
					if owner.UID != objects[current].UID {
						continue
					}
					resolution := v.resolve(c, owner)
					if resolution.State == OwnerUnknown && mayResolveTo(resolution, owner.Name, current) {
						candidates = append(candidates, c)
						break
					}
				}
			}
		}
		for _, child := range candidates {
			if _, seen := objects[child.Ref]; seen {
				continue
			}
			if len(objects) >= limit {
				a.Truncated = true
				continue
			}
			objects[child.Ref] = child
			order = append(order, child.Ref)
		}
	}
	const (
		survives = iota
		possible
		collected
	)
	fates := map[resourceid.Ref]int{ref: collected}
	ownersByRef := make(map[resourceid.Ref][]OwnerResolution, len(objects))
	for _, r := range order {
		if r != ref {
			fates[r] = possible
			if ctx.Err() == nil {
				_, ownersByRef[r] = v.References(r)
			}
		}
	}
	// Propagate certainty in either direction until shared-owner paths settle.
	for changed := true; changed; {
		changed = false
		for _, r := range order {
			if r == ref {
				continue
			}
			if ctx.Err() != nil {
				a.Truncated = true
				break
			}
			fate := collected
			for _, owner := range ownersByRef[r] {
				switch owner.State {
				case OwnerFound:
					switch fates[owner.Ref] {
					case survives:
						if _, visited := objects[owner.Ref]; !visited && a.Truncated {
							if fate != survives {
								fate = possible
							}
						} else {
							fate = survives
						}
					case possible:
						if fate != survives {
							fate = possible
						}
					}
				case OwnerGone, OwnerReplaced:
				case OwnerUnresolvable:
					fate = survives
				default:
					if fate != survives {
						fate = possible
					}
				}
			}
			if fates[r] != fate {
				fates[r] = fate
				changed = true
			}
		}
		if ctx.Err() != nil {
			break
		}
	}
	if ctx.Err() != nil {
		for r := range objects {
			if r != ref && fates[r] == collected {
				fates[r] = possible
			}
		}
	}
	for r, o := range objects {
		if r == ref {
			continue
		}
		switch fates[r] {
		case collected:
			a.Dependents = append(a.Dependents, o)
		case possible:
			a.Possible = append(a.Possible, o)
		}
	}
	sort := func(a, b Observation) int { return compareRefs(a.Ref, b.Ref) }
	slices.SortFunc(a.Dependents, sort)
	slices.SortFunc(a.Possible, sort)
	slices.SortFunc(a.Gaps, compareGaps)
	a.Gaps = slices.Compact(a.Gaps)
	return a
}