tensile

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

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

Go to latest
Published: Nov 4, 2026 License: BSD-3-Clause Imports: 7 Imported by: 0

README

tensile

tensile is a yak shaving exercise to write a config management ecosystem a la Puppet, Ansible, Chef etc.pp. in Go.

Documentation

Overview

Package tensile is a config management library a la Puppet, Ansible, Chef, etc.pp. written in Go.

Index

Constants

View Source
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.

View Source
const IdentityRegex = `\w+\[(\w+="(\\.|[^"\\])*" ?)*\]`

IdentityRegex is a regex to get the string form of a full Identity out of e.g. log statements.

Variables

View Source
var ErrNoTopology = errors.New("wire carries no topology")

ErrNoTopology is returned when a 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.

func Cond

func Cond(fn func(Wire) (bool, error), reads ...Identity) Condition

Cond returns a Condition from fn and the dependencies it reads.

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

type Diff interface {
	fmt.Stringer
	slog.LogValuer
}

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

func AsIdentity(kind string, kv ...string) Identity

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

func (id Identity) Identity() Identity

Identity implements Identifier, returning itself.

func (Identity) Kind

func (id Identity) Kind() string

Kind returns the kind.

func (Identity) LogValue

func (id Identity) LogValue() slog.Value

LogValue implements slog.LogValuer.

func (Identity) String

func (id Identity) String() string

String implements fmt.Stringer, returning the identity in the form `kind[key1="value1" key2="value2"]`.

func (Identity) Value

func (id Identity) Value(key string) (string, bool)

Value returns the value of key and whether key is set.

type Node

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

Node is a single step to be executed by an engine.

func NewNode

func NewNode(input Identifier) *Node

NewNode wraps an Identifier into a Node.

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) Conflicts

func (n *Node) Conflicts() ([]Identity, error)

Conflicts calls .Conflicts on the wrapped node if it implements it.

func (*Node) DependsOn

func (n *Node) DependsOn() ([]Identity, error)

DependsOn calls .DependsOn on the wrapped node if it implements it.

func (*Node) Enabled

func (n *Node) Enabled(wire Wire) (bool, error)

Enabled evaluates the when condition, nil means enabled.

func (*Node) Execute

func (n *Node) Execute(wire Wire) (Diff, error)

Execute calls .Execute on the wrapped node if it implements it.

func (*Node) Identity

func (n *Node) Identity() Identity

Identity returns the identity of the wrapped node.

func (*Node) IsHandler

func (n *Node) IsHandler() bool

IsHandler returns true if the node is a handler.

func (*Node) LogValue

func (n *Node) LogValue() slog.Value

LogValue implements slog.LogValuer. LogValue just defers to the LogValue of the nodes identity.

func (*Node) NeedsExecution

func (n *Node) NeedsExecution(wire Wire) (bool, Diff, error)

NeedsExecution calls .NeedsExecution on the wrapped node if it implements it.

func (*Node) Notifies

func (n *Node) Notifies() ([]Identity, error)

Notifies calls .Notifies on the wrapped node if it implements it.

func (*Node) Report

func (n *Node) Report(wire Wire) (any, bool, error)

Report calls .Report on the wrapped node if it implements it. The bool reports whether the wrapped node is a Reporter.

func (*Node) RequiredBy

func (n *Node) RequiredBy() ([]Identity, error)

RequiredBy calls .RequiredBy on the wrapped node if it implements it.

func (*Node) SerializesOn

func (n *Node) SerializesOn() []string

SerializesOn calls .SerializesOn on the wrapped node if it implements it.

func (*Node) Validate

func (n *Node) Validate(wire Wire) error

Validate calls .Validate on the wrapped node if it implements it. A disabled node validates nothing.

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"
)

func (State) OrDefault

func (s State) OrDefault() State

OrDefault returns the state or Present if it is empty string.

func (State) Valid

func (s State) Valid() error

Valid returns an error if s isn't a defined State or empty string.

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

Jump to

Keyboard shortcuts

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