hostfacts

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Oct 1, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

Documentation

Overview

Package hostfacts collects normalized Linux host state, reports per-fact availability, and tracks it as protocol ops.

Index

Constants

View Source
const (
	FactOS           = "fact/os"
	FactHostname     = "fact/hostname"
	FactMachineID    = "fact/machine_id"
	FactKernel       = "fact/kernel"
	FactCPU          = "fact/cpu"
	FactMemory       = "fact/memory"
	FactBlockDevices = "fact/block_devices"
	FactFilesystems  = "fact/filesystems"
	FactMounts       = "fact/mounts"
	FactInterfaces   = "fact/interfaces"
	FactSockets      = "fact/sockets"
	FactUnits        = "fact/units"
	FactTimers       = "fact/timers"
	FactPackages     = "fact/packages"
	FactProcesses    = "fact/processes"

	EdgeIDUnitProcess     = "edge/unit_process"
	EdgeIDMountDevice     = "edge/mount_device"
	EdgeIDMountFilesystem = "edge/mount_filesystem"
	EdgeIDPartitionDisk   = "edge/partition_disk"
	EdgeIDPackageUnit     = "edge/package_unit"
	EdgeIDSocketProcess   = "edge/socket_process"
	EdgeIDSocketUnit      = "edge/socket_unit"
	EdgeIDTimerUnit       = "edge/timer_unit"
)

Catalog IDs of facts and edges.

View Source
const (
	KindOS          = "host/OS"
	KindKernel      = "host/Kernel"
	KindCPU         = "host/CPU"
	KindMemory      = "host/Memory"
	KindBlockDevice = "host/BlockDevice"
	KindFilesystem  = "host/Filesystem"
	KindMount       = "host/Mount"
	KindInterface   = "host/Interface"
	KindSocket      = "host/Socket"
	KindUnit        = "host/Unit"
	KindTimer       = "host/Timer"
	KindPackage     = "host/Package"
	KindProcess     = "host/Process"
)

Resource kinds.

View Source
const (
	EdgeMainProcess  = "main_process"
	EdgeDevice       = "device"
	EdgeFilesystem   = "filesystem"
	EdgePartitionOf  = "partition_of"
	EdgeProvidesUnit = "provides_unit"
	EdgeProcess      = "process"
	EdgeUnit         = "unit"
	EdgeTriggers     = "triggers"
)

Edge types.

View Source
const (
	ReasonSourceAbsent    = "source_absent"
	ReasonPermission      = "permission_denied"
	ReasonDBus            = "dbus_unavailable"
	ReasonReadFailed      = "read_failed"
	ReasonOtherUserFD     = "other_user_fd"
	ReasonOwnerNotFound   = "owner_not_found"
	ReasonDependency      = "dependency_unavailable"
	ReasonProcessVanished = "process_vanished"
	ReasonCapacityDenied  = "capacity_permission_denied"
)

Reason codes carried in scope statuses and socket fields.

Variables

View Source
var (
	DefaultMountPointsExclude = regexp.MustCompile(`^/(dev|proc|sys|run/credentials/.+|run/user/.+|run/netns/.+|run/docker/.+|run/containerd/.+|run/snapd/ns(/.*)?|var/lib/docker/.+|var/lib/containers/storage/.+|var/lib/kubelet/pods/.+)($|/)`)
	DefaultFSTypesExclude     = regexp.MustCompile(`^(autofs|binfmt_misc|bpf|cgroup2?|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|mqueue|nsfs|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|selinuxfs|sysfs|tracefs|efivarfs)$`)
	DefaultInterfacesExclude  = regexp.MustCompile(`^veth`)
	DefaultUnitTypes          = []string{"service", "socket", "target", "timer", "mount", "automount", "swap", "path", "slice"}
)

Default exclusions follow node_exporter's filesystem collector plus per-session and container paths.

View Source
var Catalog = []Entry{
	{ID: FactOS, Kind: KindOS, Fields: []string{"id", "id_like", "name", "pretty_name", "version_id", "version_codename", "variant_id"},
		Source: "`/etc/os-release`, falling back to `/usr/lib/os-release`", Permission: "world-readable file", NonRoot: "available"},
	{ID: FactHostname, Kind: KindOS, Fields: []string{"hostname"},
		Source: "`uname(2)` nodename", Permission: "none", NonRoot: "available"},
	{ID: FactMachineID, Kind: KindOS, Fields: []string{"machine_id"},
		Source: "`/etc/machine-id`", Permission: "world-readable file", NonRoot: "available"},
	{ID: FactKernel, Kind: KindKernel, Whole: true, Fields: []string{"sysname", "release", "version", "machine"},
		Source: "`uname(2)`", Permission: "none", NonRoot: "available"},
	{ID: FactCPU, Kind: KindCPU, Whole: true, Fields: []string{"model_name", "vendor_id", "logical_cpus", "cores", "packages"},
		Source: "`/proc/cpuinfo`", Permission: "world-readable procfs file", NonRoot: "available"},
	{ID: FactMemory, Kind: KindMemory, Whole: true, Fields: []string{"mem_total_bytes", "swap_total_bytes"},
		Source: "`/proc/meminfo`", Permission: "world-readable procfs file", NonRoot: "available"},
	{ID: FactBlockDevices, Kind: KindBlockDevice, Whole: true, Fields: []string{"device", "size_bytes", "rotational", "removable", "read_only", "model", "partition", "dm_name"},
		Source: "`/sys/block/<dev>`: `dev`, `size`, `queue/rotational`, `removable`, `ro`, `device/model`, `dm/name`, partition subdirectories", Permission: "world-readable sysfs", NonRoot: "available"},
	{ID: FactFilesystems, Kind: KindFilesystem, Whole: true, Fields: []string{"fstype", "source", "device", "size_bytes", "used_pct_bucket", "inodes_total", "capacity_unavailable"},
		Source: "`/proc/self/mountinfo` grouped by superblock, capacity from `statfs(2)` on the first mount point; used space is bucketed to 10 percent", Permission: "search permission on the mount point path", NonRoot: "available; a mount point below a directory the agent cannot search reports `capacity_unavailable`"},
	{ID: FactMounts, Kind: KindMount, Whole: true, Fields: []string{"mount_point", "source", "fstype", "root", "device", "read_only", "options"},
		Source: "`/proc/self/mountinfo`, excluding pseudo filesystems and per-session or container mount points", Permission: "world-readable procfs file", NonRoot: "available"},
	{ID: FactInterfaces, Kind: KindInterface, Whole: true, Fields: []string{"index", "mtu", "hardware_addr", "flags", "addresses", "operstate"},
		Source: "netlink link and address dumps (`net.Interfaces`), `/proc/net/if_inet6` to drop temporary IPv6 addresses, `/sys/class/net/<if>/operstate`", Permission: "none", NonRoot: "available"},
	{ID: FactSockets, Kind: KindSocket, Whole: true, Fields: []string{"protocol", "address", "port", "uid", "uids", "inodes", "unavailable_edges", "unavailable_reason"},
		Source: "`/proc/net/tcp`, `tcp6` (state LISTEN), `udp`, `udp6` (unconnected bound sockets) of the agent's network namespace", Permission: "world-readable procfs files", NonRoot: "available"},
	{ID: FactUnits, Kind: KindUnit, Whole: true, Fields: []string{"description", "load_state", "active_state", "sub_state", "unit_file_state", "fragment_path"},
		Source: "D-Bus `org.freedesktop.systemd1.Manager.ListUnits`; `Unit` properties `FragmentPath`, `UnitFileState`; `Service` property `MainPID`", Permission: "system bus connection; read-only property queries are allowed to unprivileged clients by the systemd bus policy", NonRoot: "available where systemd runs"},
	{ID: FactTimers, Kind: KindTimer, Whole: true, Fields: []string{"description", "load_state", "active_state", "sub_state", "unit_file_state", "fragment_path", "scheduled", "last_trigger_day"},
		Source: "D-Bus `Timer` properties `Unit`, `NextElapseUSecRealtime` (reported only as `scheduled`), `LastTriggerUSec` (bucketed to the UTC day)", Permission: "system bus connection", NonRoot: "available where systemd runs"},
	{ID: FactPackages, Kind: KindPackage, Whole: true, Fields: []string{"manager", "name", "version", "arch", "state"},
		Source: "dpkg `/var/lib/dpkg/status`; rpm database (`rpmdb.sqlite`, `Packages.db`, or Berkeley DB `Packages`) via go-rpmdb; apk `/lib/apk/db/installed`", Permission: "world-readable package databases", NonRoot: "available"},
	{ID: FactProcesses, Kind: KindProcess, Whole: true, Fields: []string{"pid", "comm", "uid", "cmdline"},
		Source: "`/proc/<pid>/stat`, `/proc/<pid>/status`; `cmdline` only for allowlisted comm names, redacted. Only unit main processes and socket owners are collected", Permission: "world-readable procfs files (hidden when procfs is mounted with `hidepid`)", NonRoot: "available unless `hidepid` is set"},

	{ID: EdgeIDUnitProcess, Kind: KindUnit, EdgeType: EdgeMainProcess, ToKinds: []string{KindProcess}, DependsOn: []string{FactUnits},
		Source: "`Service.MainPID` resolved to `/proc/<pid>/stat`", Permission: "as host/Process", NonRoot: "available"},
	{ID: EdgeIDMountDevice, Kind: KindMount, EdgeType: EdgeDevice, ToKinds: []string{KindBlockDevice}, DependsOn: []string{FactMounts, FactBlockDevices},
		Source: "mountinfo `major:minor`, else the mount source path, matched to `/sys/block` devices and device-mapper names", Permission: "none", NonRoot: "available"},
	{ID: EdgeIDMountFilesystem, Kind: KindMount, EdgeType: EdgeFilesystem, ToKinds: []string{KindFilesystem}, DependsOn: []string{FactMounts, FactFilesystems},
		Source: "mountinfo superblock `major:minor`", Permission: "none", NonRoot: "available"},
	{ID: EdgeIDPartitionDisk, Kind: KindBlockDevice, EdgeType: EdgePartitionOf, ToKinds: []string{KindBlockDevice}, DependsOn: []string{FactBlockDevices},
		Source: "partition subdirectories of `/sys/block/<disk>`", Permission: "none", NonRoot: "available"},
	{ID: EdgeIDPackageUnit, Kind: KindPackage, EdgeType: EdgeProvidesUnit, ToKinds: []string{KindUnit, KindTimer}, DependsOn: []string{FactPackages, FactUnits, FactTimers},
		Source: "unit `FragmentPath` matched to dpkg `/var/lib/dpkg/info/*.list` or rpm file lists (usr-merge aliases included)", Permission: "world-readable package databases", NonRoot: "available"},
	{ID: EdgeIDSocketProcess, Kind: KindSocket, EdgeType: EdgeProcess, ToKinds: []string{KindProcess}, DependsOn: []string{FactSockets},
		Source: "`socket:[inode]` links in `/proc/<pid>/fd`, read only for processes of the agent's own UID", Permission: "ptrace read access to the owning process, which a non-root user has only for its own processes", NonRoot: "partial: sockets owned by other users report `unavailable_edges` with reason `other_user_fd`"},
	{ID: EdgeIDSocketUnit, Kind: KindSocket, EdgeType: EdgeUnit, ToKinds: []string{KindUnit}, DependsOn: []string{FactSockets, FactUnits},
		Source: "`/proc/<pid>/cgroup` of the owning process matched to a loaded unit", Permission: "requires the socket to process edge", NonRoot: "partial, as the socket to process edge"},
	{ID: EdgeIDTimerUnit, Kind: KindTimer, EdgeType: EdgeTriggers, ToKinds: []string{KindUnit}, DependsOn: []string{FactTimers, FactUnits},
		Source: "`Timer.Unit` property", Permission: "system bus connection", NonRoot: "available where systemd runs"},
}

Catalog lists every host fact and edge the collectors produce.

View Source
var DefaultRPMPaths = []string{
	"/var/lib/rpm/rpmdb.sqlite", "/usr/lib/sysimage/rpm/rpmdb.sqlite",
	"/var/lib/rpm/Packages.db", "/usr/lib/sysimage/rpm/Packages.db",
	"/var/lib/rpm/Packages", "/usr/lib/sysimage/rpm/Packages",
}

DefaultRPMPaths are probed in order; the first existing database is read.

View Source
var ErrNoSuchUnit = errors.New("hostfacts: no such unit")

ErrNoSuchUnit reports a unit that disappeared between listing and querying.

View Source
var Reasons = [][2]string{
	{ReasonSourceAbsent, "The documented source does not exist on this host (for example no systemd, or no package database)."},
	{ReasonPermission, "The agent user cannot read the source (restrictive modes, or procfs mounted with `hidepid`)."},
	{ReasonDBus, "The system bus or `org.freedesktop.systemd1` is not reachable."},
	{ReasonReadFailed, "The source exists but reading or parsing it failed."},
	{ReasonOtherUserFD, "Mapping the socket to its process needs `/proc/<pid>/fd` of another user, which a non-root user cannot read."},
	{ReasonOwnerNotFound, "The socket belongs to the agent's UID but no process of that UID holds it."},
	{ReasonDependency, "The edge depends on a fact that is unavailable in this collection."},
	{ReasonProcessVanished, "The process exited between listing and reading."},
	{ReasonCapacityDenied, "`statfs(2)` on the mount point was denied; the filesystem is reported without capacity."},
}

Reasons documents every reason code.

Functions

func IsKubernetesNode

func IsKubernetesNode(procRoot, fsRoot string) (bool, string, error)

IsKubernetesNode reports whether this host runs a kubelet or holds kubelet state, with the evidence; host mode then refuses to start.

func ParseOSRelease

func ParseOSRelease(b []byte) map[string]string

ParseOSRelease parses os-release(5) content.

func RenderCatalog

func RenderCatalog() string

RenderCatalog renders docs/host-facts.md.

func ScopeKey

func ScopeKey(id string) string

ScopeKey returns the scope key of a cataloged fact or edge.

Types

type Collector

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

Collector gathers host facts from the configured sources.

func NewCollector

func NewCollector(o Options) *Collector

NewCollector returns a collector over o.

func (*Collector) Close

func (c *Collector) Close() error

Close releases the D-Bus connection if one was dialed.

func (*Collector) Collect

func (c *Collector) Collect(ctx context.Context) *Snapshot

Collect gathers one snapshot. It never fails as a whole: every source error becomes a fact status.

type Entry

type Entry struct {
	ID         string
	Kind       string
	EdgeType   string
	ToKinds    []string
	Fields     []string
	Whole      bool
	Source     string
	Permission string
	NonRoot    string
	DependsOn  []string
}

Entry catalogs one fact or edge with its source and the permission it needs (H4a).

type HostTracker

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

HostTracker turns successive snapshots into canonical ops against the last emitted state.

func NewHostTracker

func NewHostTracker(src Source, base *protocol.State, now func() time.Time) *HostTracker

NewHostTracker diffs against base (an empty state when nil), for example the state recovered from the spool.

func (*HostTracker) Collect

func (t *HostTracker) Collect(ctx context.Context) (Result, error)

Collect gathers a snapshot, carries forward what failed to collect, and returns the ops from the previous state.

func (*HostTracker) State

func (t *HostTracker) State() *protocol.State

State returns a copy of the last emitted state.

type NetInterface

type NetInterface struct {
	Name         string
	Index        int
	MTU          int
	HardwareAddr string
	Flags        []string
	Addrs        []string
}

NetInterface is one network interface with its addresses in CIDR form.

func SystemInterfaces

func SystemInterfaces() ([]NetInterface, error)

SystemInterfaces lists interfaces and addresses through the standard library (netlink on Linux).

type Options

type Options struct {
	ProcRoot     string
	SysRoot      string
	EtcRoot      string
	UsrLibRoot   string
	DpkgDir      string
	RPMPaths     []string
	APKInstalled string

	// Systemd is used when set; otherwise DialSystemd is called on each collection until it succeeds.
	Systemd     Systemd
	DialSystemd func(ctx context.Context) (Systemd, error)

	Uname      func() (Uname, error)
	Interfaces func() ([]NetInterface, error)
	Statfs     func(path string, st *unix.Statfs_t) error
	UID        int

	CmdlineAllowlist   []string
	Redactor           *redact.Redactor
	MountPointsExclude *regexp.Regexp
	FSTypesExclude     *regexp.Regexp
	InterfacesExclude  *regexp.Regexp
	UnitTypes          []string
}

Options configures the collectors. Zero values are replaced by DefaultOptions values.

func DefaultOptions

func DefaultOptions() Options

DefaultOptions returns production paths and system sources.

type Package

type Package struct {
	Manager, Name, Version, Arch, State string
	UnitFiles                           []string
}

Package is one installed package with the systemd unit files it owns.

func ParseAPKInstalled

func ParseAPKInstalled(b []byte) []Package

ParseAPKInstalled parses the apk installed database.

func ParseDpkgStatus

func ParseDpkgStatus(b []byte) []Package

ParseDpkgStatus parses /var/lib/dpkg/status, returning packages that are not removed.

func ReadRPM

func ReadRPM(path string) ([]Package, error)

ReadRPM reads installed packages and their unit files from an rpm database of any supported backend.

type Result

type Result struct {
	State  *protocol.State
	Ops    []protocol.Op
	Scopes map[string]protocol.ScopeStatus
}

Result is one tracked collection.

type Snapshot

type Snapshot struct {
	Resources map[string]protocol.Resource
	Edges     map[protocol.EdgeKey]map[string]any
	Status    map[string]Status
}

Snapshot is the result of one collection before diffing.

type Source

type Source interface {
	Collect(ctx context.Context) *Snapshot
}

Source produces snapshots; *Collector implements it.

type Status

type Status struct {
	State  protocol.ScopeState
	Reason string
}

Status is the availability of one cataloged fact or edge in one collection.

type Systemd

type Systemd interface {
	ListUnits(ctx context.Context) ([]SystemdUnit, error)
	// Properties returns the named properties of iface on the object at path.
	Properties(ctx context.Context, path, iface string, names ...string) (map[string]any, error)
	Close() error
}

Systemd is the read-only systemd manager surface the collectors use.

func DialSystemBus

func DialSystemBus(context.Context) (Systemd, error)

DialSystemBus connects to the system bus.

func NewDBusSystemd

func NewDBusSystemd(conn *dbus.Conn) Systemd

NewDBusSystemd wraps an authenticated bus connection.

type SystemdUnit

type SystemdUnit struct {
	Name, Description, LoadState, ActiveState, SubState string
	Path                                                string
}

SystemdUnit is one entry of org.freedesktop.systemd1.Manager.ListUnits.

type Uname

type Uname struct {
	Sysname, Nodename, Release, Version, Machine string
}

Uname is the subset of utsname the collectors use.

func SystemUname

func SystemUname() (Uname, error)

SystemUname reads uname(2).

Directories

Path Synopsis
Package journal is a pure-Go, read-only reader of the systemd journal file format.
Package journal is a pure-Go, read-only reader of the systemd journal file format.
Package nodemetrics runs upstream node_exporter collectors in process so node_* series and upstream rules work unchanged.
Package nodemetrics runs upstream node_exporter collectors in process so node_* series and upstream rules work unchanged.
Package sqlitedb reads rowid tables of SQLite files (with committed WAL frames) in pure Go and serves them to go-rpmdb as a database/sql driver.
Package sqlitedb reads rowid tables of SQLite files (with committed WAL frames) in pure Go and serves them to go-rpmdb as a database/sql driver.

Jump to

Keyboard shortcuts

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