augeas

package module
v0.0.0-...-fd646be Latest Latest
Warning

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

Go to latest
Published: Jul 11, 2026 License: BSD-3-Clause Imports: 13 Imported by: 0

README

go-augeas/augeas

Pure-Go ([CGO=0], stdlib-only) implementation of the core of Augeas, the configuration-editing library from the Puppet ecosystem. It models configuration files as an ordered tree, exposes an XPath-like path language to query and edit that tree, and uses lenses to translate between the tree and concrete file syntax.

a := augeas.New()
lens, _ := augeas.LensByName("Hosts")
_ = a.TextStore(lens, "/files/etc/hosts", "127.0.0.1 localhost\n")

v, _ := a.Get("/files/etc/hosts/1/canonical") // "localhost"
_ = a.Set("/files/etc/hosts/1/alias", "loopback")
out, _ := a.TextRetrieve(lens, "/files/etc/hosts", nil)

The adapter github.com/go-ruby-augeas/augeas layers the ruby-augeas gem API over this engine, mirroring the go-facter/go-ruby-facter engine+adapter split.

Core API

New returns an empty tree. On *Augeas:

Get, Exists, Set, SetMultiple, Insert, Remove, Move, Match, Label, DefineVariable, DefineNode, TextStore, TextRetrieve, Load, Save, Span, Error, Root, SetFileSystem.

Load/Save go through the injectable FileSystem seam (ReadFile, WriteFile, Glob); the default seam is the real OS. Load failures are recorded under /augeas/files/<name>/error (reachable via /augeas//error).

Supported path constructs

  • absolute paths (/files/etc/hosts) and relative paths (resolved from the root)
  • * — any child (name wildcard)
  • // — descendant axis (any depth)
  • . — self, .. — parent
  • positional predicates [n] (1-based) and [last()]
  • value predicates [subpath = 'value'] and [. = 'value']
  • regexp predicates [subpath =~ 'regexp'] (Go regexp / RE2 syntax)
  • existence predicates [subpath]
  • union |
  • variables: DefineVariable/DefineNode bind a name usable as $name at the head of a path ($name/child)

Subpaths inside predicates are simple relative paths (labels, *, ., slash-separated); they do not themselves take predicates.

Built-in lenses

Registered by name, each round-trips a canonical, newline-terminated text form:

Name File Notes
Hosts /etc/hosts numbered entries with ipaddr, canonical, alias, inline #comment
Fstab /etc/fstab spec/file/vfstype/opt*/dump/passno; dump & passno optional
Shellvars, Simplevars KEY=value one key per node, # comments
Ini, Keyvalue INI [section] grouping, top-level keys, #/; comments

Embedded lens corpus and go-augeas-original lenses

Beyond the hand-written Go lenses above, the repository embeds the upstream Augeas 1.14.1 lens corpus as verbatim .aug DSL sources under lenses/dist/ (LGPL v2+, see lenses/dist/NOTICE) and interprets them with a pure-Go engine (NewEngine().Lens("Hosts", "lns")). That corpus is a faithful mirror and is never edited; it is gated in CI by Augeas' own test assertions (TestCorpus: get 1533/1533, put 258/258).

Alongside it, lenses/contrib/ holds go-augeas-original lenses — lenses we wrote for formats upstream 1.14.1 does not cover. They live in a separate embed and directory so they are never confused with the upstream mirror, but they load and run like any other lens (importing Util/IniFile/Sep/Rx from the dist corpus) and are CI-gated the same way by TestContribCorpus. They keep the corpus LGPL v2+ license, not this repo's BSD-3 (see lenses/contrib/NOTICE).

Lens File Coverage Notes
Wireguard.lns /etc/wireguard/*.conf 4/4 (2 get, 2 put) [Interface]/[Peer] INI; verbatim to-EOL values so base64 keys ending in =, comma-separated AllowedIPs, and PostUp/PostDown shell hooks with ; round-trip
Rclone.lns rclone.conf 3/3 (1 get, 2 put) one [remote] section each; verbatim to-EOL values so OAuth JSON token blobs survive. Caveat: a bare key = empty value does not round-trip through INI separator defaults (rclone normally omits empty options)
Caddyfile.lns /etc/caddy/Caddyfile, /etc/caddy/conf.d/* 13/13 (10 get, 3 put) native Caddyfile via one recursive subtree with an optional inner block (no per-keyword allow-list). Round-trips: nested directive blocks ✅, @name matchers ✅, (snippet) definitions ✅, leading global-options block (@global) ✅, {placeholder} tokens as opaque args ✅, quoted args ✅, comments ✅
Nftables.lns /etc/nftables.conf, /etc/nftables/*.nft, /etc/sysconfig/nftables.conf 11/11 (8 get, 3 put) native nftables ruleset via the same recursive block pattern (tablechain/set/map/flowtable → rule lines). Round-trips: table <family> <name> blocks ✅, chain/set/map/flowtable blocks ✅, base-chain type … hook … priority …; policy …; line and rule lines as ordered verbatim rule nodes ✅, inline anonymous sets { 22, 80 } and verdict maps vmap { … } as opaque rule text ✅, define NAME = value (structured) ✅, include "…" (structured) ✅, comments ✅
Unbound.lns /etc/unbound/unbound.conf, /etc/unbound/unbound.conf.d/*.conf 7/7 (3 get, 4 put) Unbound resolver: colon-terminated clause headers (server:, forward-zone:, remote-control:, …) over indented key: value option lines, top-level include: directives and # comments. Repeated keys (interface:, access-control:, local-data:, forward-addr:, …) are an ordered list of nodes, so their order + multiplicity survive edit and append (not a map that collapses dups). Verbatim to-EOL values so quoted strings with spaces, CIDR+action, and IP@port round-trip. Indentation discriminates clause bodies from column-0 directives

Caddyfile.lns boundaries (enforced / documented, never silently lossy):

  • Heredocs are rejected, not misparsed. A Caddyfile using a <<MARKER here-document cannot be modelled by a regular lens — the closing token repeats the opening word, a context-free construct Augeas regexps cannot match. Rather than silently corrupt such a file (each body line would become a bogus sibling directive), the go-augeas API layer refuses it: Engine.Lens("Caddyfile", "lns").Parse(...) and LoadFile return Caddyfile heredocs unsupported by Caddyfile.lns when the input opens a heredoc. (The lens' own inline boundary test still pins the raw misparse at the interpreter level, so the limitation stays visible.)
  • Empty blocks dir { } are excluded. A block body is deliberately non-nullable so a bare directive dir never gains braces on put; the price is that a literal empty brace pair fails get. Empty blocks are practically never written in real Caddyfiles (a site or directive block always carries directives).

Nftables.lns boundaries (enforced / documented, never silently lossy):

  • Rule expressions are stored verbatim, not fully parsed. A base-chain setting line (type filter hook input priority 0; policy drop;) and every rule (ip saddr @blocklist tcp dport { 22, 80 } accept) become ordered rule nodes holding the exact line text. The block structure table → chain/set/map/flowtable is parsed (family, name), but the packet-match / verdict grammar inside a rule — including inline anonymous sets { 22, 80 }, verdict maps vmap { … }, ranges and concatenations — is kept as opaque text. This is by design: it round-trips faithfully without a full nft expression grammar. define/include get light structure.
  • Line-wrapped brace groups are rejected, not misparsed. nft list ruleset may wrap a long list across physical lines (elements = { a, on one line, b } on the next). The lens reads each physical line on its own — a line ending in { is a block opener, a line with an inline { … } closed on the same line is a rule — so a wrapped group would silently misparse into two bogus sibling rule nodes. Rather than corrupt such a file, the go-augeas API layer refuses it: Engine.Lens("Nftables", "lns").Parse(...) and LoadFile return nftables line N … brace groups wrapped across physical lines. (The lens' own inline boundary test still pins the raw misparse at the interpreter level, so the limitation stays visible.) Single-line lists (elements = { a, b }) are unaffected.

Unbound.lns boundaries (documented, never silently lossy):

  • No silent-misparse footgun, so no API guard. Unlike a Caddyfile heredoc, unbound.conf has no context-free / back-reference construct. Any input the lens does not model (an option line with no enclosing clause, a bare column-0 directive that is neither a known clause header nor include:, an unknown clause keyword) fails get loudly instead of being silently restructured. Two inline get ... = * tests pin this, so no Parse/LoadFile rejection guard is required (contrast Caddyfile.lns).
  • Inline trailing comments are not split. A value is stored verbatim to the end of the line, so verbosity: 1 # note keeps 1 # note as the value (it round-trips faithfully; the comment is simply not a separate node), the same verbatim-value choice as Wireguard.lns/Rclone.lns.
  • Clause bodies must be indented. The lens uses indentation to tell a clause option line (indented) from a top-level include:/clause header (column 0) — the universal real-world convention; a de-indented option line is rejected rather than misfiled.

As a related differentiator, go-augeas' interpreted Toml.lns put works (its embedded put test passes in TestCorpus) where upstream Augeas' Toml save/put is buggy (hercules-team/augeas issues #715, #699).

Deferred (not yet implemented — honest scope)

This is a faithful starter engine, not a drop-in replacement for upstream Augeas. Known gaps:

  • Lens catalogue: upstream ships ~200 lenses; this repo ships 4. Additional lenses are a follow-on.
  • The Augeas lens DSL (.aug regular-language definitions) is not interpreted; lenses here are hand-written Go implementing the Lens interface.
  • Span tracking: Span always returns ErrSpanUnsupported — byte offsets are not retained when a lens parses text.
  • Path language: node-set functions beyond last() (e.g. count(), position() arithmetic), the label/glob operators, re.sub-style transforms, and full XPath axes are not implemented.
  • Non-canonical whitespace/blank-line preservation: lenses normalise whitespace to a documented canonical form and drop blank lines; byte-exact round-trip is guaranteed only for canonical input.
  • aug_load autodetection via /augeas/load transforms is not modelled; Load takes an explicit lens, glob and mount point.

License

BSD-3-Clause. See LICENSE.

Documentation

Overview

Package augeas is a pure-Go (no cgo, stdlib-only) implementation of the core of Augeas, the configuration-editing library from the Puppet ecosystem.

Augeas models configuration files as an ordered tree and exposes an XPath-like path language to read and edit that tree; lenses translate the tree back and forth to the concrete file syntax. This package provides:

  • a tree model (Node) of ordered, labelled nodes with optional values (siblings that share a label are addressed with 1-based positional indices, exactly like Augeas);
  • an Augeas-subset path evaluator (see Augeas.Match) backing the whole read/write API;
  • the core editing API on Augeas: Get, Exists, Set, SetMultiple, Insert, Remove, Move, Match, Label, DefineVariable, DefineNode and an Error surface, plus a minimal Span;
  • a Lens framework (Parse/Build round-tripping text and tree) with the in-memory Augeas.TextStore and Augeas.TextRetrieve helpers;
  • a starter set of built-in lenses (Hosts, Fstab, Shellvars/Simplevars and Ini/Keyvalue), registered by name;
  • file Augeas.Load and Augeas.Save through a lens over an injectable FileSystem seam (the default seam is the real OS).

Supported path constructs, the shipped lenses and the explicit list of what is deferred are documented in the README. This package has no cgo and no third-party dependencies.

Index

Constants

This section is empty.

Variables

View Source
var ErrSpanUnsupported = errors.New("augeas: span information is not tracked")

ErrSpanUnsupported is returned by Augeas.Span; span tracking is a documented deferred feature.

Functions

func LensNames

func LensNames() []string

LensNames returns the sorted names of all registered lenses.

func Register

func Register(name string, lens Lens)

Register makes lens available under name for LensByName. It panics on a duplicate name so wiring mistakes surface at init time.

Types

type Augeas

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

Augeas holds a configuration tree and the editing state (variables, the filesystem seam and the last error). Create one with New.

func New

func New() *Augeas

New returns an empty Augeas tree backed by the real filesystem.

func (*Augeas) DefineNode

func (a *Augeas) DefineNode(name, expr, value string) (string, bool)

DefineNode binds name to the nodes matching expr. When expr matches nothing, a single node is created at expr with the given value. It returns the path of the (first) bound node and whether a node was created.

func (*Augeas) DefineVariable

func (a *Augeas) DefineVariable(name, expr string) (int, error)

DefineVariable binds name to the node-set produced by expr; the variable can then be used as "$name" at the head of a path. It returns the number of nodes bound.

func (*Augeas) Error

func (a *Augeas) Error() error

Error returns the last error recorded by an operation, or nil.

func (*Augeas) Exists

func (a *Augeas) Exists(path string) bool

Exists reports whether at least one node matches path. A malformed path sets the last error and yields false.

func (*Augeas) Get

func (a *Augeas) Get(path string) (string, bool)

Get returns the value of the single node matching path. The boolean reports whether exactly one node matched; a valueless node yields ("", true). If the path is malformed or matches more than one node the last error is set.

func (*Augeas) Insert

func (a *Augeas) Insert(path, label string, before bool) error

Insert inserts a new, valueless sibling labelled label next to the single node matching path, before it when before is true, otherwise after it.

func (*Augeas) Label

func (a *Augeas) Label(path string) (string, bool)

Label returns the label of the single node matching path.

func (*Augeas) Load

func (a *Augeas) Load(lens Lens, pattern, mount string) error

Load reads every file matching the glob pattern, parses it with lens and stores the resulting tree under mount+"/"+basename. A read or parse failure for one file is recorded under /augeas/files/<basename>/error and the remaining files are still processed; only a failing glob is returned as an error.

func (*Augeas) LoadFile

func (a *Augeas) LoadFile(path string) error

LoadFile reads path through the filesystem seam, selects the matching lens from the corpus autoload filters, and stores the parsed tree under /files/<path>. It is the interpreted-corpus analogue of Augeas' aug_load.

func (*Augeas) Match

func (a *Augeas) Match(path string) []string

Match returns the absolute paths of all nodes matching path, in document order. A malformed path sets the last error and yields nil.

func (*Augeas) Move

func (a *Augeas) Move(src, dst string) error

Move moves the single node matching src (with its subtree) onto dst. dst may match one existing node (overwritten) or none (created). dst must not be a descendant of src.

func (*Augeas) Remove

func (a *Augeas) Remove(path string) int

Remove deletes every node matching path together with its subtree, and returns the number of nodes removed. The root is never removed.

func (*Augeas) Root

func (a *Augeas) Root() *Node

Root returns the tree root. It is exposed for lens and test use.

func (*Augeas) Save

func (a *Augeas) Save(lens Lens, mount, filename string) error

Save serialises the single subtree at mount with lens and writes it to filename through the filesystem seam.

func (*Augeas) Set

func (a *Augeas) Set(path, value string) error

Set sets the value of the node matching path. The path must match exactly one node; if it matches none, the node (and any missing ancestors) is created.

func (*Augeas) SetFileSystem

func (a *Augeas) SetFileSystem(fs FileSystem)

SetFileSystem replaces the filesystem seam used by Load and Save. Passing nil is a no-op so callers cannot accidentally disable I/O.

func (*Augeas) SetMultiple

func (a *Augeas) SetMultiple(base, sub, value string) (int, error)

SetMultiple sets value on every node matching the sub path relative to each node matching base, creating the sub node where absent. It returns the number of nodes set.

func (*Augeas) Span

func (a *Augeas) Span(path string) (Span, error)

Span always returns ErrSpanUnsupported (see the type documentation).

func (*Augeas) TextRetrieve

func (a *Augeas) TextRetrieve(lens Lens, path string, node *Node) (string, error)

TextRetrieve serialises a subtree back to text with lens. When node is non-nil it is serialised directly; otherwise the single subtree at path is used. It mirrors the C aug_text_retrieve signature, in which the caller may supply either an explicit node or a tree path.

func (*Augeas) TextStore

func (a *Augeas) TextStore(lens Lens, path, text string) error

TextStore parses text with lens and stores the resulting entries as the children of the node at path (creating the path). Any existing children at path are replaced.

type Engine

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

Engine interprets the embedded lens corpus. Create one with NewEngine.

func NewEngine

func NewEngine() *Engine

NewEngine returns an Engine backed by the embedded lens corpus.

func (*Engine) Lens

func (e *Engine) Lens(module, binding string) (Lens, error)

Lens returns the Lens bound to binding in the named module of the corpus, e.g. Lens("Hosts", "lns").

type FileSystem

type FileSystem interface {
	ReadFile(name string) ([]byte, error)
	WriteFile(name string, data []byte, perm fs.FileMode) error
	Glob(pattern string) ([]string, error)
}

FileSystem is the seam through which Load and Save reach files. The default implementation (New installs it) is the real OS; tests inject an in-memory implementation so the engine can be exercised without touching disk.

type Lens

type Lens interface {
	Parse(text string) (*Node, error)
	Build(root *Node) (string, error)
}

Lens converts between the concrete text of a configuration file and a subtree in the Augeas tree. Parse turns text into a synthetic parent node whose children are the parsed entries; Build performs the reverse. A well-behaved lens round-trips: Build(Parse(text)) reproduces canonical text, and Parse(Build(node)) reproduces the tree.

func LensByName

func LensByName(name string) (Lens, bool)

LensByName returns the lens registered under name.

type Node

type Node struct {
	Label    string
	Value    *string
	Children []*Node
	Parent   *Node
}

Node is a single entry in the configuration tree. A node has a label, an optional value and an ordered list of children. Siblings may share a label; such siblings are distinguished by their 1-based position, matching Augeas' label[n] addressing.

func (*Node) ClearValue

func (n *Node) ClearValue()

ClearValue removes the node's value, leaving it valueless.

func (*Node) SetValue

func (n *Node) SetValue(v string)

SetValue sets the node's value.

type Span

type Span struct {
	Filename                                               string
	LabelStart, LabelEnd, ValueStart, ValueEnd, Start, End int
}

Span is not tracked by this engine: byte offsets are not retained when a lens parses text. It always returns ErrSpanUnsupported so callers can detect the gap explicitly rather than silently receiving zeroes.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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