Documentation
¶
Overview ¶
Package tensile is a config management library a la Puppet, Ansible, Chef, etc.pp. written in Go.
Index ¶
- Constants
- Variables
- type Condition
- type Conflictor
- type DefaultWire
- type Depender
- type Diff
- type Executor
- type Handler
- type Identifier
- type Identity
- type Node
- func (n *Node) Conflicts() ([]Identity, error)
- func (n *Node) DependsOn() ([]Identity, error)
- func (n *Node) Enabled(wire Wire) (bool, error)
- func (n *Node) Execute(wire Wire) (Diff, error)
- func (n *Node) Identity() Identity
- func (n *Node) IsHandler() bool
- func (n *Node) LogValue() slog.Value
- func (n *Node) NeedsExecution(wire Wire) (bool, Diff, error)
- func (n *Node) Notifies() ([]Identity, error)
- func (n *Node) Report(wire Wire) (any, bool, error)
- func (n *Node) RequiredBy() ([]Identity, error)
- func (n *Node) SerializesOn() []string
- func (n *Node) Validate(wire Wire) error
- type Notifier
- type Reporter
- type Requisite
- type Serializer
- type State
- type Topology
- type Validator
- type Wire
Constants ¶
const IdentityMaxPairs = 8
IdentityMaxPairs bounds identity key/value pairs so Identity stays comparable and usable as a map key.
Identity is pass-by-value and used as a map key, costing 16+32*IdentityMaxPairs bytes per copy on 64-bit platforms. With 8 pairs that is 272 B, including empty slots, not including what filled slots contain. Hence a small-ish number to prevent sprawl - both the number and contents of identity fields should be kept small.
const IdentityRegex = `\w+\[(\w+="(\\.|[^"\\])*" ?)*\]`
IdentityRegex is a regex to get the string form of a full Identity out of e.g. log statements.
Variables ¶
var ErrNoTopology = errors.New("wire carries no topology")
Functions ¶
This section is empty.
Types ¶
type Condition ¶
type Condition struct {
// Reads are dependencies the condition may read.
Reads []Identity
// Cond decides whether the node runs.
// nil means always enabled.
Cond func(Wire) (bool, error)
}
Condition gates a Node.
type Conflictor ¶
type Conflictor interface {
// Conflicts returns the identities of resources the node touches,
// e.g. packages or files.
Conflicts() ([]Identity, error)
}
Conflictor is the interface to be satisfied by a Node when it touches resources beyond its own identity.
e.g. std.Symlink and std.FileContent both touch the file at their path.
A node implicitly conflicts with its own identity. Two nodes sharing any conflict identity is an error when building the final graph.
type DefaultWire ¶
type DefaultWire struct {
Ctx context.Context //nolint:containedctx
Log *slog.Logger
Store *storage.Store[Identity]
Topo Topology
}
DefaultWire is a basic Wire. Zero value is usable.
func (*DefaultWire) Context ¶
func (w *DefaultWire) Context() context.Context
Context returns the carried context, defaulting to context.Background.
func (*DefaultWire) Logger ¶
func (w *DefaultWire) Logger() *slog.Logger
Logger returns the carried logger, defaulting to slog.Default.
func (*DefaultWire) Storage ¶
func (w *DefaultWire) Storage() *storage.Store[Identity]
Storage returns the carried store, defaulting to an empty store.
func (*DefaultWire) Topology ¶
func (w *DefaultWire) Topology() (Topology, error)
Topology returns the carried topology, ErrNoTopology when none is set.
type Depender ¶
type Depender interface {
// DependsOn returns a list of resources the node depends on, e.g.
// packages or files.
DependsOn() ([]Identity, error)
}
Depender is the interface to be satisfied by a Node when it depends on other resources to be provided by other Node. E.g. a package that must be installed or a file to be ensured.
type Diff ¶
Diff describes a change a node would make or has made. Implementations must render human-readable via String and structured via LogValue.
type Executor ¶
type Executor interface {
// NeedsExecution is run before Execute. NeedsExecution must not
// mane any modifications to the system, it must be a stateless
// check to check if Node.Execute must be called.
//
// NeedsExecution is called e.g. for noop runs to check if any
// changes are needed.
//
// The returned Diff is an optional description of the change that would be made during Execute.
// If the returned bool is false Diff may be nil.
// If the returned bool is true Diff may be nil.
NeedsExecution(wire Wire) (bool, Diff, error)
// Execute is called for the node to make the desired change.
// The returned Diff is an optional description of the change made and may be nil.
Execute(wire Wire) (Diff, error)
}
Executor is the interface to be satisfied by a Node to be executed.
type Handler ¶
type Handler struct {
Node
}
Handler is a Node that is only executed when at least one of the nodes notifying it was executed.
func NewHandler ¶
func NewHandler(input Identifier) *Handler
NewHandler wraps an Identifier into a Handler.
type Identifier ¶
type Identifier interface {
// Identity returns the node's identity.
// It must be deterministic and build from [AsIdentity].
Identity() Identity
}
Identifier is the interface that must be implemented by all Node.
type Identity ¶
type Identity struct {
// contains filtered or unexported fields
}
Identity is a deterministic, human-readable node identifier. Identity is comparable and usable as a map key.
The string form is `kind[key1="value1" key2="value2"]`. It was chosen to be both readable and parseable. See IdentityRegex for a regex to parse identities from text. For JSON logs it supports slog.LogValuer natively.
func AsIdentity ¶
AsIdentity builds an Identity from a kind and key/value pairs. Pair order is preserved. Panics on an odd number of key/value elements, empty keys, or more than IdentityMaxPairs pairs.
func (Identity) Identity ¶
Identity implements Identifier, returning itself.
func (Identity) LogValue ¶
LogValue implements slog.LogValuer.
func (Identity) String ¶
String implements fmt.Stringer, returning the identity in the form `kind[key1="value1" key2="value2"]`.
type Node ¶
type Node struct {
// contains filtered or unexported fields
}
Node is a single step to be executed by an engine.
func When ¶
func When(cond Condition, input Identifier) *Node
When wraps input into a Node whose execution is gated by cond. It may be called multiple times to add multiple conditions for a single Node. If any condition returns false the node's validation and execution is skipped. Condition.Reads are added to the node's dependencies.
func (*Node) LogValue ¶
LogValue implements slog.LogValuer. LogValue just defers to the LogValue of the nodes identity.
func (*Node) NeedsExecution ¶
NeedsExecution calls .NeedsExecution on the wrapped node if it implements it.
func (*Node) Report ¶
Report calls .Report on the wrapped node if it implements it. The bool reports whether the wrapped node is a Reporter.
func (*Node) RequiredBy ¶
RequiredBy calls .RequiredBy on the wrapped node if it implements it.
func (*Node) SerializesOn ¶
SerializesOn calls .SerializesOn on the wrapped node if it implements it.
type Notifier ¶
type Notifier interface {
// Notifies returns a list of resources provided by handlers to
// notify.
Notifies() ([]Identity, error)
}
Notifier is the interface to be satisfied by a Node when it notifies handlers, e.g. restarting a service after changing its configuration.
type Reporter ¶
type Reporter interface {
// Report returns the node's output.
// It is called after the node completed and must not modify the system.
Report(wire Wire) (any, error)
}
Reporter is the interface to be satisfied by a Node when it reports an output.
type Requisite ¶
type Requisite interface {
// RequiredBy returns a list of resources the node is a requirement for.
RequiredBy() ([]Identity, error)
}
Requisite is the interface to be satisfied by a Node when it is a dependency for other resources to be provided by other Node.
type Serializer ¶
type Serializer interface {
// SerializesOn returns the keys to serialize on.
// Empty means no serialization.
SerializesOn() []string
}
Serializer is the interface to be satisfied by a Node whose lifecycle must not overlap with other nodes with the same key.
This is resolved during scheduling in the work queue, not at runtime in the engine. Meaning for each key yielded by a number of nodes even when multiple of them are ready only one of them is being scheduled to be executed at a time.
E.g. package managers often have an internal lock that prevents parallel execution of Package nodes.
type State ¶
type State string
State may be used by nodes to declare the desired state of the resource it manages. It is purely a convention. Nodes may also define their own type to express state.
const ( // Present is when the resource the node manages should be present. // For most nodes using [State] it should be the default. Present State = "present" // Absent is when the resource the node manages should not be present. Absent State = "absent" // ReadOnly may be supported by nodes that support only reporting on the managed resource. ReadOnly State = "read-only" )
type Topology ¶
type Topology interface {
// Claimer returns the node claiming identity.
Claimer(identity Identity) (Identity, bool)
}
Topology is a read-only view of the built execution graph.
type Validator ¶
type Validator interface {
// Validate validates the configuration of the node.
// It may be used to setup states in the node.
Validate(wire Wire) error
}
Validator is the interface to be satisfied by a Node when the configuration needs to be validated e.g. before execution.
type Wire ¶
type Wire interface {
// Context returns a context that is valid for the lifetime of the [Node].
// That means the Context is valid from the first call to Validation
// and until Execute has finished.
Context() context.Context
// Logger returns a logger for the [Node].
Logger() *slog.Logger
// Storage the [storage.Store], which provides output of other nodes.
// The [storage.Store] only gives access to output of [Node] declared as dependencies.
Storage() *storage.Store[Identity]
// Topology returns a read-only view of the execution graph.
// Errors when the wire carries no topology.
Topology() (Topology, error)
}
Wire provides some base functionality to Node.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
tester
command
A quick command to test things.
|
A quick command to test things. |
|
Package nodes contains various packages for tensile nodes.
|
Package nodes contains various packages for tensile nodes. |
|
dns
Package dns provides DNS-related tensile nodes and utilities.
|
Package dns provides DNS-related tensile nodes and utilities. |
|
openwrt
Package openwrt provides tensile nodes for OpenWrt.
|
Package openwrt provides tensile nodes for OpenWrt. |
|
std
Package std contains tensile.Node primitives to most common operations.
|
Package std contains tensile.Node primitives to most common operations. |
|
systemd
Package systemd provides tensile nodes for systemd.
|
Package systemd provides tensile nodes for systemd. |
|
pkg
|
|
|
app
Package app provides utilities that tensile binaries usually require, e.g.
|
Package app provides utilities that tensile binaries usually require, e.g. |
|
diff
Package diff provides generic diff types for tensile nodes.
|
Package diff provides generic diff types for tensile nodes. |
|
engine
Package engine contains execution engines for tensile.
|
Package engine contains execution engines for tensile. |
|
graph
Package graph provides a DAG with topological sorting.
|
Package graph provides a DAG with topological sorting. |
|
queue
Package queue implements the ordering logic to execute tensile.Node.
|
Package queue implements the ordering logic to execute tensile.Node. |
|
shadow
Package shadow reads and changes local users and groups through the shadow tools or busybox.
|
Package shadow reads and changes local users and groups through the shadow tools or busybox. |
|
storage
Package storage provides typed access to keyed values.
|
Package storage provides typed access to keyed values. |
|
test
|
|
|
e2e
module
|