Documentation
¶
Overview ¶
Package licenses_core is the vendor-neutral engine for the licenses_exporter family: the license_ metric schema and its constructors, an immutable snapshot store, the collection loop, the Prometheus + OTLP export paths, and the HTTP server with cancelable validated hot reload. Vendor exporters implement Source and call Main.
Index ¶
- Constants
- func Expand(s string) (string, error)
- func LoadDotEnv(cfgPath string)
- func LoadYAML(path string, into any) error
- func Main(app App) error
- func RegisterOTLP(meter metric.Meter, store *SnapshotStore) error
- func ResolveSecret(inline, file string) (string, error)
- func Resource(version, instanceID string) *resource.Resource
- func RunOnce(ctx context.Context, sources []Source, version string, debug bool) error
- type App
- type Base
- type CollectionConfig
- type Collector
- type Health
- type Label
- type OTLPConfig
- type PromCollector
- type Sample
- func BuildInfoSample(version, goVersion string) Sample
- func ExpirationSample(vendor, product, instance string, tsUnix float64) Sample
- func LastSuccessSample(vendor, instance string, tsUnix float64) Sample
- func ScrapeDurationSample(vendor, instance string, seconds float64) Sample
- func SeatSample(name, vendor, product, unit, instance string, v float64) Sample
- func UpSample(vendor, instance string, up bool) Sample
- type Server
- type Snapshot
- type SnapshotStore
- type Source
Constants ¶
const ( MetricSeatsTotal = "license_seats_total" MetricSeatsUsed = "license_seats_used" MetricExpiration = "license_expiration_timestamp_seconds" MetricUp = "license_up" MetricLastSuccess = "license_collector_last_success_timestamp_seconds" MetricScrapeDuration = "license_scrape_duration_seconds" MetricBuildInfo = "license_build_info" )
Metric names. Every metric is prefixed license_ (design spec §4).
Variables ¶
This section is empty.
Functions ¶
func LoadDotEnv ¶
func LoadDotEnv(cfgPath string)
LoadDotEnv loads a .env from the CWD, then from the config file's directory, BEFORE ${ENV} interpolation. godotenv never overrides an already-set variable, so real secret injection always wins. Missing .env files are ignored.
func LoadYAML ¶
LoadYAML reads .env, expands ${ENV} references, and unmarshals the resulting YAML into into. It does not validate; callers run Base.Validate (and any vendor-specific validation) after LoadYAML returns.
func Main ¶
Main runs the whole exporter lifecycle for App.
--once path: one Load, one RunOnce (dumping samples iff Debug), then return — no server is started.
Serve path: Load once, build the process-lifetime server (NewServer), wire OS signals and an optional fsnotify watcher into ReloadLoop's plain reloads/shutdown channels via signalAdapter, then block in ReloadLoop until a shutdown trigger. Only the initial Load and the initial NewServer bind are fatal (startup); once serving, a reload failure is logged and skipped — it never crashes the process. Flag/config-format parsing stays in the CONSUMER: Main takes an already-parsed App.
func RegisterOTLP ¶
func RegisterOTLP(meter metric.Meter, store *SnapshotStore) error
RegisterOTLP registers one observable gauge per metric name. Each callback reads the current snapshot and observes its matching samples at OBSERVATION time (points are not back-dated; data age is carried by license_collector_last_success_timestamp_seconds).
func ResolveSecret ¶
ResolveSecret returns the secret read from file (trimmed of surrounding whitespace) when file is set, otherwise the inline value. Shared by the vendor collectors so inline-vs-file precedence stays consistent across them.
Types ¶
type App ¶
type App struct {
Version string // build version, stamped into license_build_info
Addr string // --web.listen-address, e.g. ":9105"
Once bool // --once: run one collection cycle then exit (no server)
Debug bool // gates the --once sample dump; also sets logrus debug level
Trace bool // logs a generic "SDK tracing intentionally not wired" warning
ConfigPath string // path watched for hot reload; empty => reload via SIGHUP only
Load func() (Base, []Source, error)
}
App is what a vendor main assembles and hands to Main. Load re-parses the vendor's whole config (Base + vendor collector block) and rebuilds the []Source; Main calls it once at startup (--once and serve paths alike) and ReloadLoop calls it again on every SIGHUP / config-file-change reload.
type Base ¶
type Base struct {
Collection CollectionConfig `yaml:"collection"`
OTLP OTLPConfig `yaml:"otlp"`
}
Base is the vendor-neutral configuration shared by every exporter built on this engine: the collection loop cadence and the optional OTLP push exporter. Vendor exporters embed Base alongside their own collector config.
type CollectionConfig ¶
CollectionConfig configures the collection loop's polling cadence.
func (*CollectionConfig) UnmarshalYAML ¶
func (c *CollectionConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
UnmarshalYAML lets collection.interval accept a Go duration string ("2h").
type Collector ¶
type Collector struct {
// contains filtered or unexported fields
}
Collector runs the background collection loop and publishes snapshots.
func NewCollector ¶
func (*Collector) CollectOnce ¶
CollectOnce fans out over every source, builds one snapshot, publishes it, and returns it. A per-source failure degrades to license_up=0 (no seats emitted).
func (*Collector) Run ¶
Run collects immediately, then every interval until ctx is canceled. It is a thin convenience over CollectOnce + RunTicker for callers that want the leading collect folded in.
func (*Collector) RunTicker ¶
RunTicker collects every interval until ctx is canceled. Unlike Run it does NOT collect immediately on entry — the caller is expected to have already run the first cycle (e.g. Server.RunCollection collects once to mark health ready, then hands off to RunTicker). This avoids a double back-to-back initial collect — two vCenter logins / two Graph fetches — on every startup and reload.
type Health ¶
type Health struct {
// contains filtered or unexported fields
}
Health reports 503 "starting" until the first collection cycle completes, then 200 "ok" (design spec §2).
type OTLPConfig ¶
OTLPConfig configures the optional OTLP/gRPC push exporter. Endpoint empty disables OTLP entirely (the exporter then serves Prometheus only).
type PromCollector ¶
type PromCollector struct {
// contains filtered or unexported fields
}
PromCollector is an unchecked prometheus.Collector: Describe sends nothing, so the emitted metric-name/label set may vary snapshot to snapshot.
func NewPromCollector ¶
func NewPromCollector(store *SnapshotStore) *PromCollector
func (*PromCollector) Collect ¶
func (p *PromCollector) Collect(ch chan<- prometheus.Metric)
func (*PromCollector) Describe ¶
func (p *PromCollector) Describe(chan<- *prometheus.Desc)
type Sample ¶
Sample is one vendor-agnostic metric point. Name is the full metric name (already prefixed with license_); Labels are in canonical (sorted-by-key) order for its metric name.
func BuildInfoSample ¶
BuildInfoSample builds the constant license_build_info gauge (value 1).
func ExpirationSample ¶
ExpirationSample builds a license_expiration_timestamp_seconds sample ({instance,product,vendor}). Callers omit it entirely for perpetual licenses.
func LastSuccessSample ¶
LastSuccessSample builds license_collector_last_success_timestamp_seconds.
func ScrapeDurationSample ¶
ScrapeDurationSample builds license_scrape_duration_seconds.
func SeatSample ¶
SeatSample builds a seats_total/seats_used sample with the canonical {instance,product,unit,vendor} label set (sorted by key).
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the process-lifetime serving stack: one shared SnapshotStore, the Prometheus registry+collector, the OTLP push exporter, the /health handler, and a single bound HTTP server. It is built ONCE (NewServer) and reused across every reload — RunCollection swaps only the collection loop, never the store or the listener, so /metrics never blanks and /health never flips back to 503 on a reload (ADR-0008 §4).
func NewServer ¶
NewServer builds+starts the process-lifetime serving stack (shared store, Prometheus registry+collector, OTLP exporter, /health, one bound listener). Bind failure is returned (fatal at startup only); a runtime serve error is LOGGED, never fatal.
func (*Server) ReloadLoop ¶
func (s *Server) ReloadLoop(initialBase Base, initialSources []Source, reloads, shutdown <-chan struct{}, load func() (Base, []Source, error))
ReloadLoop is the reload state machine. It runs one collection loop under a cancelable context, then on each reload trigger validates a candidate (via load) and — only if it loads/validates — cancels the running loop and respawns collection with the new base/sources on the SAME server/store. A candidate that fails to load is logged and skipped: the running collection and server are left untouched. A shutdown trigger cancels the active loop and returns.
Callers adapt OS signals (SIGHUP → reloads, SIGINT/SIGTERM → shutdown) and fsnotify write/create events (→ reloads) into the two trigger channels; keeping them as plain channels makes this loop testable without real signals or files.
func (*Server) RunCollection ¶
RunCollection runs one collection into the SHARED store until ctx is canceled: build a collector from sources, CollectOnce, SetReady, then RunTicker(interval). Exactly one leading collect (no double initial collect). Publishing into the existing shared store means the prior snapshot keeps serving until the first CollectOnce swaps. Reloads call this repeatedly on the same *Server.
type Snapshot ¶
type Snapshot struct {
Samples []Sample
}
Snapshot is an immutable set of samples produced by one collection cycle. Callers MUST NOT mutate Samples after publishing to a SnapshotStore.
func ColdStartSnapshot ¶
ColdStartSnapshot is served before the first collection cycle completes: it carries only license_build_info so no target series (license_up, seats_*) flap or read as a transient zero (design spec §2).
type SnapshotStore ¶
type SnapshotStore struct {
// contains filtered or unexported fields
}
SnapshotStore holds the current snapshot behind an RWMutex pointer-swap.
func NewSnapshotStore ¶
func NewSnapshotStore(initial *Snapshot) *SnapshotStore
func (*SnapshotStore) Load ¶
func (s *SnapshotStore) Load() *Snapshot
func (*SnapshotStore) Swap ¶
func (s *SnapshotStore) Swap(next *Snapshot)
type Source ¶
type Source interface {
Vendor() string
Instance() string
Collect(ctx context.Context) ([]Sample, error)
}
Source collects license facts from a single configured target (one tenant or one vCenter). It returns samples already carrying vendor+instance labels; the collection loop stamps health/duration metrics around it.