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
- Variables
- func IsSidecarImage(image string) bool
- func IsSubordinate(role ComponentRole) bool
- func ReconcileLifecycle(manifest *InventoryManifest, activeApps []AppRecord, discoveryComplete bool, ...) bool
- func RenderOverview(result *DiscoveryResult, commitSHA string) string
- func SaveManifest(repoRoot, clusterName string, manifest *InventoryManifest) error
- func ValidateExposureRules(rules config.ExposureRules) []string
- type AppKey
- type AppLifecycle
- type AppManifest
- type AppObserved
- type AppRecord
- type CacheStatus
- type Catalog
- type CatalogApp
- type CatalogMatch
- type CategoryResolver
- type Client
- type ComponentRef
- type ComponentRole
- type DeclaredSource
- type DiscoveryResult
- type DiscoveryStatus
- type EnrichBasis
- type EnrichMeta
- type ExposureLevel
- type ExposureRef
- type ExposureResult
- type GraveyardEntry
- type IdentityCache
- type ImageRef
- type InventoryManifest
- type Status
- type Tier
- type WorkloadIdentity
Constants ¶
const ( SourceRelationDeploys = "deploys" SourceRelationConfigures = "configures" SourceRelationSecures = "secures" SourceRelationDependsOn = "depends_on" )
Source relation constants — prevent typos across discovery/renderer.
Variables ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
LoadCatalog reads and parses a catalog metadata file. Returns an empty catalog if the file does not exist.
func (*Catalog) ApplyOverrides ¶
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 ¶
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.
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.
func ComputeStatus ¶
ComputeStatus derives health from ready vs desired replica counts.
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.