facts

package module
v0.0.2 Latest Latest
Warning

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

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

README

tests Go Report Card codecov go.dev reference go version License

Library · CLI · Platforms · Documentation


Facts is a Go port of Puppet Facter. It discovers facts about the system it runs on — hardware, networking, OS, cloud metadata — and serves them two ways: as an embeddable Go library, and as the facts CLI. It reads your existing facter configuration, fact directories, and FACTER_* environment facts as the compatibility tier of its input surface, so facter-configured hosts keep working unchanged.

Ruby Facter compatibility is promised exactly where it matters and nowhere else: at the CLI process boundary (the output contract) and for operator-supplied fact sources — external facts and facter.conf (the input contract, now read under facts-native names first; see the input-compatibility reference). Ruby DSL fact files are not read; rewrite them as external facts (migration guide). The Go API itself is just Go.


LIBRARY

Hermetic by default.

An Engine is an isolated, immutable unit of fact-discovery configuration. A fresh engine resolves core facts only — it reads no config files, scans no directories, executes no scripts — until you opt in. No package globals, no shared state: two engines in one process never see each other.

eng, err := facts.New() // hermetic: core facts only
if err != nil {
	log.Fatal(err)
}
snap, err := eng.Discover(ctx)
if err != nil {
	log.Printf("partial discovery: %v", err) // snapshot still valid
}

osName, err := snap.Value("os.name") // same answer as `facts os.name`

Every source is an explicit option:

eng, err := facts.New(
	facts.WithExternalDirs("/etc/myagent/facts.d/external"), // data files, executables
	facts.WithFact("agent_version", func(ctx context.Context) (any, error) {
		return "1.2.3", nil
	}),
	facts.WithLogger(slog.Default()), // diagnostics; discarded without it
)

Or follow the system exactly like the CLI does — the default config file (facts.conf, then facter.conf), default fact directories, FACTS_*/FACTER_* environment facts:

eng, err := facts.New(facts.WithSystemDefaults())

SNAPSHOT

Discover once. Query forever.

Discover(ctx) runs the resolvers and returns an immutable Snapshot — the canonical tree plus pure query and decode operations over it. Freshness means discovering again; the snapshot you hold is the cache. Both types are safe for concurrent use.

type OS struct {
	Name   string `json:"name"`
	Family string `json:"family"`
}

osFact, err := facts.As[OS](snap, "os") // decode any subtree, loudly on mismatch

for name, value := range snap.All() {   // sorted top-level iteration
	fmt.Println(name, value)
}

Errors are honest: missing facts are ErrFactNotFound, a registered fact that legitimately resolved to nil is (nil, nil), partial failures arrive joined while the snapshot keeps every fact that did resolve, and cancellation flows through ctx into command execution and cloud-metadata requests.


CLI

The CLI. Every fact, one command.

The shipped binary is facts (ADR-0008 — no facter alias), and it keeps Ruby Facter's output contract — formatting, exit statuses, stderr diagnostics (with the program token rebranded to Facts), facter.conf semantics. Existing facter inputs keep working as the compat tier: facter.conf default paths, FACTER_* environment facts, and the puppetlabs fact directories are all still read, with the facts-native names winning when both are present. One deliberate break: Ruby Facter's deprecated legacy alias facts (operatingsystem, hostname, processorcount, …) and --show-legacy are gone — the structured tree (os.name, networking.hostname, processors.count) is the only fact surface. The alias-to-structured table lives in ADR-0007.

$ facts os.name
Darwin

$ facts --json os.family kernelversion
{
  "kernelversion": "25.5.0",
  "os.family": "Darwin"
}

$ facts --external-dir ./facts.d site_role
web
make build   # builds ./facts from the project root

PLATFORMS

Tested where it ships.

Every release target is a blocking CI gate — unit tests, the race detector over the engine, a built-binary smoke, and per-platform release-gate fact checks.

Platform Architectures Gate
Linux x64, arm64 native runners + container distro matrix
macOS arm64, x64 native runners
Windows Server 2022, 2025 native runners + release-gate fact set
FreeBSD 14 VM job + release-gate fact set

Requires Go 1.26+.


DOCS

Go deeper.

pkg.go.dev/github.com/ncode/facts API reference
docs/schema/facts.yaml every supported fact, with types, platforms, and descriptions — enforced in CI
CONTEXT.md · docs/adr/ the project's language and architectural decisions
docs/HISTORY.md how the port happened, the verification record, and how to recover the full porting docs from git history
docs/CUSTOM_FACT_MIGRATION.md migrating Ruby custom facts to external facts (no .rb files are read)
docs/FACTER_CONF_COMPATIBILITY.md operator input compatibility: facts-native names, facter-compat reads, facter.conf semantics
man/man8/facts.8 the CLI manual

DEVELOPING

Working on Facts.

make test          # go test ./...
make race          # race detector across the module
make bench-stable  # repeated benchmark baseline for hot paths

When changing fact resolution or formatting paths, capture a benchmark baseline before and after:

make bench-stable BENCH=BenchmarkFormatJSON PACKAGES=./internal/engine


APACHE-2.0 · LICENSE · NOTICE

Copyright 2026 Juliano Martinez.

Documentation

Overview

Package facts discovers and reports facts about the system it runs on — hardware details, network settings, OS type and version, cloud metadata, and more. It is the library form of the facts CLI this module ships: both read the same canonical fact tree, so library answers never drift from CLI answers.

An Engine is an isolated, immutable unit of fact-discovery configuration, built by New from functional options. Engines are hermetic by default: they resolve core facts only, and do not read config files, scan fact directories, execute external-fact scripts, read FACTS_*/FACTER_* environment variables, or touch the persistent cache until the corresponding option — WithConfigFile, WithExternalDirs, WithCache, or WithSystemDefaults for full CLI-equivalent behavior — opts in. Registered facts are fixed at construction via WithFact; once built, nothing mutates.

Resolution is explicit: Engine.Discover runs the configured resolvers and returns an immutable Snapshot of the canonical tree. Freshness is obtained by discovering again — the caller's Snapshot is the cache. Both types are safe for concurrent use, and independent Engines in one process share no state.

A Snapshot answers dot-notation queries (Snapshot.Value) with the same values the CLI reports, exposes the whole tree (Snapshot.Tree, Snapshot.All), and decodes subtrees into caller types (As). Missing facts are reported as ErrFactNotFound; a registered or external fact that legitimately resolved to nil returns (nil, nil). Discovery failures are partial: the Snapshot holds every fact that resolved and the returned error joins the per-source failures.

Engine diagnostics flow through log/slog (WithLogger) and are discarded by default. Ruby Facter compatibility is promised only at the facts CLI process boundary and for operator-supplied fact sources (external facts, facter.conf semantics) — this API makes no Ruby-compatibility promises of its own. Ruby DSL fact files are not read from any source (ADR-0006).

Index

Constants

This section is empty.

Variables

View Source
var ErrFactNotFound = engine.ErrFactNotFound

ErrFactNotFound reports a query that no fact resolved. A fact that legitimately resolved to nil is found, not missing: Snapshot.Value returns (nil, nil) for it.

Functions

func As

func As[T any](s *Snapshot, query string) (T, error)

As decodes the canonical-tree subtree selected by query into T, reading only from the resolved Snapshot — it never resolves facts itself. It works uniformly for core, custom, and external facts, whatever source won precedence. A shape mismatch between the subtree and T returns a non-nil error and the zero T — never a partially decoded value. A missing fact returns an error satisfying errors.Is(err, ErrFactNotFound).

Types

type Engine

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

Engine is an isolated, immutable unit of fact-discovery configuration. Construct one with New; nothing mutates after construction, and Engines are safe for concurrent use. A new Engine is hermetic: it discovers core facts only, until options opt it into registered facts, external facts, a config file, a cache, or full system-following behavior.

func New

func New(opts ...Option) (*Engine, error)

New returns an immutable Engine built from opts. With no options the Engine is hermetic: it resolves core facts only — it does not read config files, scan fact directories, execute external-fact scripts, read FACTS_*/FACTER_* environment variables, or touch the persistent cache.

func (*Engine) Discover

func (e *Engine) Discover(ctx context.Context, queries ...string) (*Snapshot, error)

Discover runs the configured resolvers and returns an immutable Snapshot of the canonical fact tree. Queries, when given, follow the CLI's dot-notation semantics.

Discovery failures are partial: the returned Snapshot holds every fact that resolved, and err joins each per-source failure. When ctx ends discovery early, err satisfies errors.Is(err, ctx.Err()). Not-applicable facts are absent from the Snapshot and contribute nothing to err.

type Option

type Option func(*engine.EngineConfig) error

Option configures an Engine under construction.

func WithCache

func WithCache() Option

WithCache opts into the persistent fact cache with facter.conf TTL semantics.

func WithConfigFile

func WithConfigFile(path string) Option

WithConfigFile opts into reading the given config file (facter.conf semantics), honoring its fact directories, toggles, blocklists, and cache TTLs with the same semantics as the facts CLI.

func WithExternalDirs

func WithExternalDirs(dirs ...string) Option

WithExternalDirs opts into loading external facts from exactly the given directories, with input-contract semantics identical to the CLI's.

func WithFact

func WithFact(name string, resolve func(ctx context.Context) (any, error)) Option

WithFact registers a fact programmatically, fixed at construction. The resolver runs once per Discover; a nil value with a nil error registers the fact as resolved-to-nil, and an error counts as a partial discovery failure. A registered fact overrides a core fact of the same name and is overridden by external facts.

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger routes engine diagnostics to logger with contract-pinned message text, mapped severities, and once-only conditions deduplicated per Engine. Without it, diagnostics are discarded.

func WithSystemDefaults

func WithSystemDefaults() Option

WithSystemDefaults configures full CLI-equivalent system-following behavior: the default config file (the facts-native facts.conf first, the facter-compatible facter.conf second), the default external fact directories (facts-native locations ahead of the facter-compatible ones), and FACTS_*/FACTER_* environment facts (FACTS_* wins name collisions).

type Snapshot

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

Snapshot is the immutable result of one discovery run: the canonical fact tree plus pure query and decode operations over it. Facts within a Snapshot are mutually consistent; freshness is obtained by discovering again, never by mutating. Safe for concurrent use.

func (*Snapshot) All

func (s *Snapshot) All() iter.Seq2[string, any]

All iterates the top-level canonical-tree entries in sorted name order. Yielded values are copies; mutating them does not affect the Snapshot.

func (*Snapshot) Tree

func (s *Snapshot) Tree() map[string]any

Tree returns a copy of the canonical tree — the same names, nesting, and value normalization the facts CLI reports. Mutating the returned tree does not affect the Snapshot.

func (*Snapshot) Value

func (s *Snapshot) Value(query string) (any, error)

Value returns the canonical-tree node selected by the dot-notation query — the same value the facts CLI reports for the same query. A name no fact resolved returns an error satisfying errors.Is(err, ErrFactNotFound); a custom or external fact that legitimately resolved to nil returns (nil, nil).

Directories

Path Synopsis
cmd
facts command
internal
app
cli

Jump to

Keyboard shortcuts

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