inventory

package
v1.0.0-alpha.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 30, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Overview

Package inventory is the runtime-neutral, shared inventory contract for OPM.

"Inventory" answers one question: which Kubernetes objects does a rendered release own, and which of those are now stale relative to a fresh render. Historically this logic existed twice — once in the operator (opm-operator/internal/inventory) and once in the CLI (cli/pkg/inventory) — and the two copies drifted, computing different prune sets for the same release. This package is the single canonical implementation both frontends consume so that entry identity, content digests, and the stale/prune set are computed by the same code, which is the correctness precondition for the enhancement 0006 CLI↔operator handoff.

This is kernel core, NOT opt-in helper code: it lives under opm/ (not opm/helper/) because it defines a contract every OPM frontend MUST compute identically, not a convenience a frontend MAY skip.

Neutrality constraints (enhancement 0006 D13):

  • The package and its types MUST NOT import sigs.k8s.io/controller-runtime, any github.com/fluxcd/* package, or k8s.io/apiextensions-apiserver. Importing the operator's CRD InventoryEntry would transitively drag that whole stack into every consumer; this package's InventoryEntry is a plain value type with none of it.
  • Its only non-standard-library dependency is k8s.io/apimachinery, used for unstructured access and object-identity primitives.
  • All functions are pure: no cluster I/O, no logging. Fetching live cluster state and reacting to verdicts are caller (CLI/operator) responsibilities, per the kernel-neutrality constitution (I/O at the edges, no direct logging).

Consumers map their own entry shapes to and from InventoryEntry at their boundary: the operator maps api/v1alpha1.InventoryEntry, the CLI maps its own struct. The field set is identical across all three, so the mapping is mechanical and total.

Index

Constants

View Source
const (
	// LabelManagedBy is the standard Kubernetes label key indicating the manager.
	LabelManagedBy = "app.kubernetes.io/managed-by"
	// ManagedByCLI is the attribution value for the CLI actor.
	ManagedByCLI = "opm-cli"
	// ManagedByController is the attribution value for the controller actor.
	ManagedByController = "opm-controller"
	// ManagedByLegacy is the attribution value used before runtime-owned labels
	// were introduced. Recognized for backward compatibility.
	ManagedByLegacy = "open-platform-model"
)

Managed-by attribution values identifying OPM runtime actors. They mirror the constants in the operator and CLI core packages and are duplicated here to keep this package free of any runtime-specific dependency.

View Source
const LabelComponentName = "component.opmodel.dev/name"

LabelComponentName is the label injected by the CUE catalog onto every rendered application resource to record which component produced it. Its value is the component name, used by inventory to track provenance for the component-rename safety check (see ApplyComponentRenameSafetyCheck).

This mirrors the constant of the same name in the operator and CLI core packages; it is duplicated here rather than imported to keep this package free of any runtime-specific dependency.

Variables

This section is empty.

Functions

func CollidesOnApply

func CollidesOnApply(entry InventoryEntry, observed ObservedState) bool

CollidesOnApply reports whether applying the candidate entry would collide with the live cluster object whose already-observed state is supplied. A collision means the apply cannot proceed safely without operator intervention.

The decision, mirroring the field usage of the CLI's pre-apply existence check, is:

  • no live object exists → no collision
  • the live object is terminating (being deleted) → collision
  • the live object is foreign-owned → collision
  • the live object is OPM-owned and not deleting → no collision

The verdict currently derives entirely from observed; the candidate entry is part of the signature so the contract reads as "does applying THIS entry collide" at every call site and so identity-specific rules (e.g. never flagging a collision for a particular Kind) can be added later without a signature break. It is deliberately not consumed yet.

The function is pure: it reads only its arguments and performs no cluster read, apply, delete, or logging. Fetching the live state into ObservedState and reacting to the verdict are caller responsibilities.

func ComputeDigest

func ComputeDigest(entries []InventoryEntry) string

ComputeDigest returns a deterministic SHA-256 digest of an inventory entry set, formatted as "sha256:<hex>". The digest is order-independent: entries are sorted by their identity tuple (Group, Kind, Namespace, Name, Component, Version) before a canonical JSON encoding is hashed. Two actors computing a digest over the same rendered inventory therefore obtain the same value, regardless of the order their entries were produced in.

func IdentityEqual

func IdentityEqual(a, b InventoryEntry) bool

IdentityEqual reports whether two entries identify the same owned resource with full, component-aware identity. It compares Group, Kind, Namespace, Name, and Component. Version is excluded to prevent false orphans during Kubernetes API version migrations.

This is NOT the comparator used by ComputeStaleSet — that uses K8sIdentityEqual so that a component rename (same GVK + namespace + name, different component label) does not produce stale entries for live objects that an SSA apply patches in place. It is deterministic and side-effect free.

func IsOPMManagedBy

func IsOPMManagedBy(value string) bool

IsOPMManagedBy reports whether a managed-by attribution value identifies any OPM runtime actor (CLI, controller, or the legacy value). An object carrying any of these is considered owned by OPM and safe for a release to take over; any other value (including the empty string) is foreign.

func K8sIdentityEqual

func K8sIdentityEqual(a, b InventoryEntry) bool

K8sIdentityEqual reports whether two entries identify the same Kubernetes object as the apiserver sees it: one live object per Group + Kind + Namespace + Name. It ignores Version and Component, and is deterministic and side-effect free. This is the canonical base relation for stale-set computation.

Types

type InventoryEntry

type InventoryEntry struct {
	// Group is the API group of the object (empty for the core group).
	Group string `json:"group"`
	// Kind is the object Kind (e.g. "Deployment").
	Kind string `json:"kind"`
	// Namespace is the object namespace (empty for cluster-scoped objects).
	Namespace string `json:"namespace"`
	// Name is the object name.
	Name string `json:"name"`
	// Version is the API version (e.g. "v1"). It is recorded for reference but
	// is deliberately excluded from identity comparison so that Kubernetes API
	// version migrations (e.g. v1beta1 → v1) do not produce false orphans.
	//
	// The JSON key is the abbreviated "v" rather than "version" deliberately:
	// ComputeDigest hashes this struct's JSON encoding, and both the operator's
	// CRD InventoryEntry and the CLI's struct already use `json:"v,omitempty"`.
	// Keeping the key identical preserves digest continuity with inventories
	// those actors stored before adopting this package. Do NOT rename it.
	Version string `json:"v,omitempty"`
	// Component is the name of the OPM component that produced the object. It is
	// excluded from Kubernetes object identity (K8sIdentityEqual) but included in
	// full identity (IdentityEqual); a component move is handled explicitly by
	// ApplyComponentRenameSafetyCheck rather than by treating the object as stale.
	Component string `json:"component,omitempty"`
}

InventoryEntry is the runtime-neutral, in-memory representation of a single Kubernetes object owned by a rendered release. It carries no Kubernetes framework baggage, so importing it never drags controller-runtime or Flux into a consumer. It is distinct from any CRD serialization type (e.g. the operator's api/v1alpha1.InventoryEntry); consumers map their own shapes to and from this type at their boundary.

func ApplyComponentRenameSafetyCheck

func ApplyComponentRenameSafetyCheck(stale, current []InventoryEntry) []InventoryEntry

ApplyComponentRenameSafetyCheck returns the stale set with any entry removed when that entry shares Kubernetes object identity (K8sIdentityEqual) with a current entry under a different Component. The effect is that a resource which moves between components is neither pruned nor recreated; it is left in place to be re-owned by its new component.

This is a safety net for stale sets produced by a component-aware comparison (a migrated inventory, or a caller that diffed on full identity). For a stale set produced by ComputeStaleSet — whose base relation already ignores Component — it is a no-op, since a moved entry never enters that set.

The function is pure: no I/O, no logging. Callers that wish to observe a detected rename do so by diffing the input and output sets at their edge.

func ComputeStaleSet

func ComputeStaleSet(previous, current []InventoryEntry) []InventoryEntry

ComputeStaleSet returns the entries present in previous but absent from current, where presence is determined by K8sIdentityEqual — Kubernetes object identity (Group, Kind, Namespace, Name), component-agnostic. This is the single canonical stale-set base for every OPM frontend.

Component moves are deliberately NOT treated as stale here: an entry whose Kubernetes identity matches a current entry is retained even if its Component differs, because the live object would be patched in place by an SSA apply rather than deleted and recreated.

Because this base relation already ignores Component, a component move never reaches the returned set, so chaining ApplyComponentRenameSafetyCheck onto this function's output is a no-op for moves. The safety check exists for callers whose stale set was instead derived from a full, component-aware comparison (e.g. a migrated or externally computed inventory); see its doc.

func NewEntryFromResource

func NewEntryFromResource(r *unstructured.Unstructured) InventoryEntry

NewEntryFromResource builds an InventoryEntry from a rendered Kubernetes object, reading its GroupVersionKind, namespace, name, and the OPM component label. It performs no I/O and reads only the supplied object.

type ObservedState

type ObservedState struct {
	// Exists reports whether a live object was found at the candidate identity.
	Exists bool
	// BeingDeleted reports whether the live object is terminating (its
	// deletionTimestamp is set).
	BeingDeleted bool
	// ManagedBy is the value of the live object's app.kubernetes.io/managed-by
	// label, or "" if the label is absent.
	ManagedBy string
}

ObservedState is the neutral, already-fetched state of the live cluster object at a candidate entry's identity. The caller populates it from its own cluster read; this package never fetches it. Its fields are exactly those the pre-apply collision decision inspects — no speculative additions.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL