k8s

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Apr 4, 2026 License: AGPL-3.0, AGPL-3.0-only Imports: 22 Imported by: 0

Documentation

Overview

Package k8s provides Kubernetes cluster discovery for the k8s-inventory narrator 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.

Variables

View Source
var CategoryOrder = []string{
	"Administrative",
	"DNS & NTP",
	"Routing Infrastructure",
	"Storage Services",
	"Backup Services",
	"Monitoring",
	"Security & IDS",
	"Bot Protection",
	"Internal Trust",
	"Business Gateway",
	"Personal Gateway",
	"Internal Gateway",
	"Archival & Media",
	"Discovery & Dashboards",
	"Downloads & Arr",
	"Game Servers",
	"Mail Services",
	"Remote Management",
	"Tools & Utilities",
	"Privileged Services",
	"Platform",
	"Uncategorized",
}

CategoryOrder defines the stable rendering order for categories. Categories not in this list sort alphabetically after the listed ones.

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 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, commitSHA string) 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.

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.

func ValidateExposureRules

func ValidateExposureRules(rules config.ExposureRules) []string

ValidateExposureRules checks rules for valid schema at load time.

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 maps namespaces to human-readable category names.

func NewCategoryResolver

func NewCategoryResolver(overrides map[string]string) *CategoryResolver

NewCategoryResolver creates a resolver with built-in defaults and optional overrides.

func (*CategoryResolver) Resolve

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

Resolve returns the category for a namespace. Precedence: overrides → defaults → "Uncategorized".

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
	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, exposureRules config.ExposureRules) (*DiscoveryResult, error)

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

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 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 // parentRef gateway name for exposure classification
}

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, lbIP string, 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.

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/manifests/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"
)

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