Help us shape the future of Go! Take the 2026 Go Developer Survey

k8s

package
v0.14.0 Latest Latest
Warning

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

Go to latest
Published: Oct 14, 2026 License: AGPL-3.0, AGPL-3.0-only Imports: 26 Imported by: 0

Documentation

Overview

Package k8s provides Kubernetes cluster discovery for the k8s-inventory scribe k8s-inventory module. Discovers workloads, groups by app identity, augments with routes/services, and classifies into tiers.

Three layers (never collapse):

  • Discovery: authority (what the cluster has)
  • Catalog: meaning (human descriptions, overrides, graveyard)
  • Renderer: presentation (stable markdown output)

Index

Constants

View Source
const (
	SourceRelationDeploys    = "deploys"
	SourceRelationConfigures = "configures"
	SourceRelationSecures    = "secures"
	SourceRelationDependsOn  = "depends_on"
)

Source relation constants — prevent typos across discovery/renderer.

View Source
const GraveyardAfter = 24 * time.Hour

GraveyardAfter is how long an app must stay unseen before it is retired. Until this elapses it sits in "missing": absent from the last sweep, but still believed to exist.

It also covers a DEGRADED sweep. A discovery that silently missed workloads sends them to "missing", and the next healthy sweep restores them long before the window closes, so a bad run can no longer bury the estate — which is what the (currently inert) discoveryComplete guard was reaching for.

View Source
const Uncategorized = "Uncategorized"

Uncategorized is the section for namespaces the cluster declares no category for.

Variables

View Source
var SidecarImages = []string{
	"istio-proxy",
	"linkerd-proxy",
	"cilium",
	"envoy",
	"kube-rbac-proxy",
	"vault-agent",
}

SidecarImages is the set of known sidecar/infrastructure container image substrings that should be excluded from version detection.

Functions

func IsSidecarImage

func IsSidecarImage(image string) bool

IsSidecarImage returns true if the image string matches a known sidecar pattern.

func IsSubordinate

func IsSubordinate(role ComponentRole) bool

IsSubordinate returns true if the role should fold into a parent app.

func Products added in v0.14.0

func Products(images []string) []string

Products names the software an app is: its image repositories without registry or tag, minus backing services (databases, caches, queues, ...) that run alongside it. An app made only of backing services is those services.

func ReconcileLifecycle

func ReconcileLifecycle(manifest *InventoryManifest, activeApps []AppRecord, discoveryComplete bool, now time.Time) bool

ReconcileLifecycle updates lifecycle state from current discovery. Discovery is authoritative for active. Manifest is memory. HARD RULE: discovery_status.complete must be true to mutate lifecycle.

func RenderOverview

func RenderOverview(result *DiscoveryResult) string

RenderOverview produces stable, deterministic markdown from a DiscoveryResult. Two-layer UX: minimal summary table + expandable details per app. Docs are scanned, not read — instant orientation in <3 seconds. Content-identical cluster state renders byte-identical markdown, so the scribe write-if-changed gate commits only real inventory changes; refresh time and lineage are the auto-commit's own git metadata.

func SaveManifest

func SaveManifest(repoRoot, clusterName string, manifest *InventoryManifest) error

SaveManifest writes the manifest with stable formatting. Sorted keys, indented JSON, no-op if unchanged.

Types

type AppKey

type AppKey struct {
	Namespace string
	Identity  string
}

AppKey uniquely identifies an application within the cluster. Scoped to namespace + resolved identity.

type AppLifecycle

type AppLifecycle struct {
	State        string     `json:"state"` // active | graveyard
	LastSeen     time.Time  `json:"last_seen"`
	MissingSince *time.Time `json:"missing_since,omitempty"`
}

AppLifecycle tracks active/graveyard state transitions.

type AppManifest

type AppManifest struct {
	Lifecycle           AppLifecycle          `json:"lifecycle"`
	Observed            AppObserved           `json:"observed"`
	IdentityCache       IdentityCache         `json:"identity_cache"`
	IdentityCacheStatus CacheStatus           `json:"identity_cache_status"`
	EnrichmentMeta      map[string]EnrichMeta `json:"enrichment_meta,omitempty"`
}

AppManifest is the per-app section in the inventory manifest. Three clean planes: lifecycle (memory), observed (snapshot), identity_cache (enrichment).

type AppObserved

type AppObserved struct {
	Namespace    string   `json:"namespace"`
	Name         string   `json:"name"`
	Images       []string `json:"images"`
	ImageDigests []string `json:"image_digests"`
}

AppObserved holds current discovery snapshot. Rewritten each successful run.

type AppRecord

type AppRecord struct {
	Key           AppKey
	FriendlyName  string // from catalog, or derived from identity
	Category      string // from CategoryResolver
	Tier          Tier
	Description   string // from catalog
	Components    []ComponentRef
	WorkloadKinds []string // deduped: ["Deployment"], ["StatefulSet", "Deployment"] → ["Mixed"]
	Images        []ImageRef
	Version       string         // resolved via strict precedence
	Hosts         []string       // deduplicated, sorted hostnames from routes
	Exposure      ExposureResult // classified from exposure rules
	Replicas      string         // "ready/desired" format
	Status        Status
	Collision     bool             // true if identity was disambiguated via #shortUID
	Sources       []DeclaredSource // authoritative repo paths from Flux graph
	HomepageURL   string
	DocsURL       string
	SourceURL     string
}

AppRecord is the fully resolved, grouped representation of an application. One AppRecord per app identity per namespace — multiple workloads merge.

func FoldSubordinates

func FoldSubordinates(records []AppRecord) []AppRecord

FoldSubordinates merges subordinate-only app records into their parent records. A record is subordinate-only if ALL its components have subordinate roles. Parent matching: same namespace, subordinate identity starts with parent identity + "-". Longest matching parent identity wins (most specific). Unmatched subordinates stay as top-level rows (surfaces misconfiguration).

type CacheStatus

type CacheStatus struct {
	State           string     `json:"state"`            // fresh | stale
	Reason          string     `json:"reason,omitempty"` // why stale
	LastAttemptedAt *time.Time `json:"last_attempted_at,omitempty"`
}

CacheStatus tracks freshness of the identity cache.

type Catalog

type Catalog struct {
	Apps      []CatalogApp     `yaml:"apps"`
	Graveyard []GraveyardEntry `yaml:"graveyard"`
}

Catalog holds human-curated metadata that augments cluster discovery. Cluster tells you what exists; catalog tells you how to describe it. Catalog is for exceptions only — exposure rules live on gitops.cluster.

func LoadCatalog

func LoadCatalog(path string) (*Catalog, error)

LoadCatalog reads and parses a catalog metadata file. Returns an empty catalog if the file does not exist.

func (*Catalog) ApplyOverrides

func (c *Catalog) ApplyOverrides(rec *AppRecord)

ApplyOverrides merges catalog metadata onto an AppRecord. Catalog values take precedence over discovery for description, friendly name, tier, and URLs.

func (*Catalog) LookupApp

func (c *Catalog) LookupApp(key AppKey) *CatalogApp

LookupApp finds catalog metadata for a given app key. Returns nil if no match found.

type CatalogApp

type CatalogApp struct {
	Match        CatalogMatch `yaml:"match"`
	FriendlyName string       `yaml:"friendly_name"`
	Description  string       `yaml:"description"`
	Tier         string       `yaml:"tier"`   // "app", "platform", "hidden"
	Ignore       bool         `yaml:"ignore"` // suppress from output entirely
	HomepageURL  string       `yaml:"homepage_url"`
	DocsURL      string       `yaml:"docs_url"`
	SourceURL    string       `yaml:"source_url"`
}

CatalogApp defines metadata overrides for a discovered application.

type CatalogMatch

type CatalogMatch struct {
	Namespace string `yaml:"namespace"`
	Identity  string `yaml:"identity"`
}

CatalogMatch identifies which discovered app this entry applies to.

type CategoryResolver

type CategoryResolver struct {
	// contains filtered or unexported fields
}

CategoryResolver answers what the cluster declares about a namespace: its inventory section, that section's position, and the tier of everything in it.

func NewCategoryResolver

func NewCategoryResolver(categories []config.InventoryCategory) *CategoryResolver

func (*CategoryResolver) Declared added in v0.14.0

func (r *CategoryResolver) Declared(namespace string) bool

Declared reports whether the cluster places the namespace in a category.

func (*CategoryResolver) Resolve

func (r *CategoryResolver) Resolve(namespace string) string

Resolve returns the namespace's declared category, or Uncategorized.

func (*CategoryResolver) Tier added in v0.14.0

func (r *CategoryResolver) Tier(namespace string) Tier

Tier returns the namespace's declared tier, or "" when the cluster declares none.

type Client

type Client struct {
	Config        *rest.Config
	Clientset     kubernetes.Interface
	GatewayClient gatewayclient.Interface
	ClusterName   string // resolved from actual auth path, not blindly from input
	// contains filtered or unexported fields
}

Client holds resolved Kubernetes API clients and cluster metadata. Created via NewClient — the only way to get authenticated cluster access. All auth resolution happens here. Consumers never build their own config. Exposure rules are NOT part of Client — they are discovery input, not bootstrap state.

func NewClient

func NewClient(cluster config.ClusterConfig) (*Client, error)

NewClient resolves kubeconfig and builds authenticated API clients. Auth chain: default kubeconfig → OIDC BuildKubeconfig → in-cluster. ClusterName comes from the resolved context/path, not input config.

func (*Client) Close

func (c *Client) Close()

Close releases any resources (temp kubeconfig files from OIDC path).

type ComponentRef

type ComponentRef struct {
	Name string        // e.g. "core", "registry", "jobservice"
	Kind string        // e.g. "Deployment", "StatefulSet"
	Role ComponentRole // classified role (database, cache, primary, etc.)
}

ComponentRef identifies a single workload component within a multi-component app.

type ComponentRole

type ComponentRole string

ComponentRole classifies a workload's function within an application.

const (
	RolePrimary   ComponentRole = "primary"
	RoleDatabase  ComponentRole = "database"
	RoleCache     ComponentRole = "cache"
	RoleQueue     ComponentRole = "queue"
	RoleSearch    ComponentRole = "search"
	RoleSentinel  ComponentRole = "sentinel"
	RoleProxy     ComponentRole = "proxy"
	RoleWorker    ComponentRole = "worker"
	RoleSecurity  ComponentRole = "security"
	RoleExtension ComponentRole = "extension"
	RoleUnknown   ComponentRole = "unknown"
)

func ClassifyComponentRole

func ClassifyComponentRole(workloadName string, imageNames []string) ComponentRole

ClassifyComponentRole determines a workload's role from its container images and workload name. Image patterns take priority over name suffixes. Returns RoleUnknown if no signal matches (treated as non-subordinate).

type DeclaredSource

type DeclaredSource struct {
	Kind     string // overlay, helmrelease, kustomization, policy
	RepoPath string // repo-relative authoritative path
	Relation string // deploys, configures, secures, depends_on
	Primary  bool
}

DeclaredSource links an app to its authoritative declaration in the repo. Resolved from Flux objects, not guessed from filenames. Sources are authoritative or empty — never partial guesses.

type DiscoveryResult

type DiscoveryResult struct {
	Apps       []AppRecord
	Platform   []AppRecord
	Unresolved []AppRecord
	Graveyard  []GraveyardEntry
	ObservedAt time.Time
	Cluster    string
}

DiscoveryResult holds the complete output of cluster discovery.

func Discover

func Discover(ctx context.Context, client *Client, catalogPath, repoRoot string, cluster config.ClusterConfig) (*DiscoveryResult, error)

Discover queries the live cluster and returns a complete DiscoveryResult. ObservedAt is captured once at start. Uses client-go directly.

func Sweep added in v0.14.0

func Sweep(ctx context.Context, cluster config.ClusterConfig, catalogPath, repoRoot string) (*DiscoveryResult, error)

Sweep discovers a cluster at most once per process for a repo and catalog. The inventory region and the {inventory.<cluster>.count} badge both read the cluster; sharing one sweep makes them one reading whichever renders first, and runs the lifecycle reconcile once.

func (*DiscoveryResult) Listed added in v0.14.0

func (r *DiscoveryResult) Listed() int

Listed is the number of running workloads the inventory page lists.

type DiscoveryStatus

type DiscoveryStatus struct {
	Complete bool   `json:"complete"`
	Source   string `json:"source,omitempty"` // live_cluster
}

DiscoveryStatus records whether discovery completed fully. Lifecycle mutations MUST be blocked unless Complete == true.

type Endpoint added in v0.14.0

type Endpoint struct {
	IP   string
	Port int
}

Endpoint is one load-balanced address an app exposes.

func (Endpoint) String added in v0.14.0

func (e Endpoint) String() string

type EnrichBasis

type EnrichBasis struct {
	ImageDigest string `json:"image_digest,omitempty"`
}

EnrichBasis holds the content basis for cache invalidation.

type EnrichMeta

type EnrichMeta struct {
	Source    string       `json:"source"` // oci_label, helm_chart, annotation
	UpdatedAt string       `json:"updated_at"`
	Basis     *EnrichBasis `json:"basis,omitempty"` // invalidation key
}

EnrichMeta tracks provenance for a single cached field.

type ExposureLevel

type ExposureLevel string

ExposureLevel classifies how an app is exposed.

const (
	ExposureInternet ExposureLevel = "internet"
	ExposureIntranet ExposureLevel = "intranet"
	ExposureCluster  ExposureLevel = "cluster"
	ExposureUnknown  ExposureLevel = "unknown"
)

type ExposureRef

type ExposureRef struct {
	Kind    string // "HTTPRoute", "Ingress"
	Host    string
	Name    string
	Gateway string // HTTPRoute: parentRef gateway name, for exposure classification
	Class   string // Ingress: ingress class, for exposure classification
	Group   string // the app group the route declares, when a group source is configured
}

ExposureRef represents a discovered route/ingress attachment. Interface-based: HTTPRoute today, Ingress extensible later.

type ExposureResult

type ExposureResult struct {
	Declared   ExposureLevel `json:"declared"`
	Observed   ExposureLevel `json:"observed,omitempty"` // future: from external probe
	Gateway    string        `json:"gateway,omitempty"`
	Endpoint   string        `json:"endpoint,omitempty"`
	Confidence string        `json:"confidence"` // exact | inferred | fallback | unknown
	RuleIndex  int           `json:"rule_index"` // which rule matched (-1 if none)
}

ExposureResult is the classified exposure of an app. Declared is from rule matching. Observed is future (external probe). Confidence is derived from match type, never user-set.

func ClassifyExposure

func ClassifyExposure(routes []ExposureRef, serviceType string, endpoints []Endpoint, rules config.ExposureRules) ExposureResult

ClassifyExposure evaluates exposure rules against an app's routes and services. Rules are grouped by precedence: endpoint > gateway > CIDR > service type. Conflicting equal-precedence rules → unknown. Never first-match-wins. Confidence is derived from match type, never user-set. endpoints are the app's load-balanced addresses: every ip:port its LoadBalancer Services expose.

type GraveyardEntry

type GraveyardEntry struct {
	Name      string `yaml:"name"`
	Namespace string `yaml:"namespace"`
	Category  string `yaml:"category"`
	Reason    string `yaml:"reason"`
	DocsURL   string `yaml:"docs_url"`
}

GraveyardEntry represents a retired application (catalog-only, never inferred).

func GraveyardFromManifest

func GraveyardFromManifest(manifest *InventoryManifest) []GraveyardEntry

GraveyardFromManifest returns graveyard entries for rendering.

type IdentityCache

type IdentityCache struct {
	DisplayName string `json:"display_name,omitempty"`
	Description string `json:"description,omitempty"`
	Homepage    string `json:"homepage,omitempty"`
	Version     string `json:"version,omitempty"`
}

IdentityCache holds enriched presentation fields. Cached, replaceable.

type ImageRef

type ImageRef struct {
	Repository string // e.g. "docker.io/library/redis"
	Tag        string // e.g. "7.4.2", "latest"
	Digest     string // e.g. "sha256:abc..." (if available from pod spec)
}

ImageRef holds a parsed container image reference.

type InventoryManifest

type InventoryManifest struct {
	SchemaVersion   int                    `json:"schema_version"`
	Cluster         string                 `json:"cluster"`
	GeneratedAt     time.Time              `json:"generated_at"`
	DiscoveryStatus DiscoveryStatus        `json:"discovery_status"`
	Apps            map[string]AppManifest `json:"apps"` // key: namespace/identity
}

InventoryManifest is the scoped k8s inventory manifest. Lives at .stagefreight/inventory/k8s-inventory-<cluster>.json Manifest accelerates truth — it never becomes truth.

func LoadManifest

func LoadManifest(repoRoot, clusterName string) (*InventoryManifest, error)

LoadManifest reads the generated inventory manifest for a cluster.

type Status

type Status string

Status represents the health state of an application.

const (
	StatusHealthy  Status = "Healthy"
	StatusDegraded Status = "Degraded"
	StatusDown     Status = "Down"
	StatusUnknown  Status = "Unknown"
)

func ComputeStatus

func ComputeStatus(ready, desired int32) Status

ComputeStatus derives health from ready vs desired replica counts.

type Tier

type Tier string

Tier classifies an app's visibility in the overview.

const (
	TierApp      Tier = "app"
	TierPlatform Tier = "platform"
	TierHidden   Tier = "hidden"

	// TierUnresolved is a workload neither declared nor confidently inferred. It is
	// listed on its own rather than guessed into apps or platform.
	TierUnresolved Tier = "unresolved"
)

type WorkloadIdentity

type WorkloadIdentity struct {
	Key     AppKey
	Source  string // "label/instance", "label/name", "helm", "ownerRef", "name"
	RootUID string // UID of the root owner (for collision detection)
}

WorkloadIdentity records how an app's identity was resolved. Frozen at discovery time — never recomputed during augmentation.

Jump to

Keyboard shortcuts

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