Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

153 Commits

Repository files navigation

Facts — every fact about the machine. One canonical tree.

testsGo Report Cardcodecovgo.dev referencego versionLicense

Library · CLI · Platforms · Documentation


Facts is a Go port of Puppet Facter. It discovers facts about the system it runs on — hardware, networking, OS, installed packages, 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 onlyiferr!=nil {
log.Fatal(err)
}
snap, err:=eng.Discover(ctx)
iferr!=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, executablesfacts.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.

typeOSstruct {
Namestring`json:"name"`Familystring`json:"family"`
}
osFact, err:=facts.As[OS](snap, "os") // decode any subtree, loudly on mismatchforname, value:=rangesnap.All() { // sorted top-level iterationfmt.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, 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.

Every fact is on by default. Disable any fact or fact group with --disable, the FACTS_DISABLE environment variable, or the facts.confdisable key (the Facter blocklist key stays as its compatibility alias). Disabling is resolution-gated — a disabled standalone resolver is skipped, not just hidden — and --no-block clears every disable.

$ brew install ncode/tap/facts
$ facts os.nameDarwin
$ facts --json os.family kernel.version.full{ "kernel.version.full": "25.5.0", "os.family": "Darwin"}
$ facts --external-dir ./facts.d site_roleweb
$ facts --disable networking os.name # disable facts or groups; also FACTS_DISABLE, facts.conf 'disable'Darwin
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. Plan 9 has lab-validated fact support for plan9/amd64; it is not listed as a published release artifact target until that tuple is promoted in the release matrix.

PlatformArchitecturesGateSupported facts
Linuxx64, arm64native runners + container distro matrixLinux facts
macOSarm64, x64native runnersmacOS / Darwin facts
WindowsServer 2022, 2025native runners + release-gate fact setWindows facts
FreeBSDamd64, arm, arm64VM job + release-gate fact setFreeBSD facts
OpenBSDamd64, arm, arm64VM job + release-gate fact setOpenBSD facts
NetBSDamd64, arm, arm64VM job + release-gate fact setNetBSD facts
DragonFly BSDamd64VM job + release-gate fact setDragonFly BSD facts
illumosamd64VM job + release-gate fact setillumos facts
Plan 9amd64facts-lab release gatePlan 9 facts

Requires Go 1.26+.


DOCS

Go deeper.

pkg.go.dev/github.com/ncode/factsAPI reference
docs/schema/facts.yamlevery supported fact, with types, platforms, and descriptions — enforced in CI
docs/supported-facts/generated per-platform supported fact pages with production-shaped examples
CONTEXT.md · docs/adr/the project's language and architectural decisions
docs/HISTORY.mdhow the port happened, the verification record, and how to recover the full porting docs from git history
docs/CUSTOM_FACT_MIGRATION.mdmigrating Ruby custom facts to external facts (no .rb files are read)
docs/FACTER_CONF_COMPATIBILITY.mdoperator input compatibility: facts-native names, facter-compat reads, facter.conf semantics
man/man8/facts.8the 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.

About

Collect, consume and/or display system facts

Resources

Contributing

Security policy

Stars

9 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages