skip to content

Your Configuration as a Graph

A small fleet has two web servers and a database, two operator accounts, and a few features. Drawn on a whiteboard it is a graph: hosts, users and features are boxes, and the lines between them say what relates to what. web-1 has the user deploy, web-1 uses the webserver feature, and webserver includes ssh.

gen keeps that picture as the configuration itself. Each box is a node. Each line is an edge with a label that says what kind of relation it is.

let
gen = inputs.gen.lib.mkGenLibs { };
edges = [
{ from = "web-1"; to = "deploy"; label = "user"; }
{ from = "web-2"; to = "deploy"; label = "user"; }
{ from = "db-1"; to = "deploy"; label = "user"; }
{ from = "db-1"; to = "dba"; label = "user"; }
{ from = "web-1"; to = "webserver"; label = "feature"; }
{ from = "web-2"; to = "webserver"; label = "feature"; }
{ from = "db-1"; to = "monitoring"; label = "feature"; }
{ from = "webserver"; to = "ssh"; label = "includes"; }
];
nodes = [ "web-1" "web-2" "db-1" "deploy" "dba" "webserver" "ssh" "monitoring" ];
follow = label: id:
map (e: e.to) (builtins.filter (e: e.label == label && e.from == id) edges);
# An evaluated scope: each label `l` is read from the attribute `edges-l`.
scope = gen.scope;
fleet = scope.eval { parseParent = _: null; } {
children = _: _: { };
imports = _: _: [ ];
marks = _: _: [ ];
edges-user = _: follow "user";
edges-feature = _: follow "feature";
edges-includes = _: follow "includes";
} (scope.buildRoots { parentGraph = scope.overlays (map scope.vertex nodes); decls = { }; });
in
{
# Which features does web-1 get, counting what each feature includes?
web1Features = map (a: a.node) (scope.resolve {
wf = scope.wellFormed { alphabet = [ "user" "feature" "includes" ]; expression = "feature includes*"; };
dataFilter = n: n.id;
} fleet "web-1").answers;
# Which hosts have the dba account?
dbaHosts = gen.graph.dependents { } {
edges = follow "user";
inherit nodes;
} "dba";
}
{ dbaHosts = [ "db-1" ]; web1Features = [ "ssh" "webserver" ]; }

The first query reads as a sentence: start at web-1, follow one feature edge, then follow any number of includes edges. The wellFormed expression is a regular expression over edge labels: a label matches itself, labels written in order match in order, and * matches zero or more repeats. resolve answers every node at the end of a matching path.

The second query walks edges backwards. dependents returns every node that can reach dba, which in the user relation means every host that has the account.

gen reads your data through the functions you pass (follow above), so the data can stay in whatever shape you already keep it in. The same approach answers the other common questions about a graph: roots and leaves, cycles, a safe build order, all paths between two nodes. The Graphs and Queries page lists them.

A scope graph is a graph whose edges carry labels, used to resolve names in programming languages: to find what a name refers to, you follow edges whose labels match a regular expression. gen applies the same model to configuration. A host, a feature and an environment are scopes, and a question about them is a path over labelled edges. See our reading of Neron et al. (2015).

Next: Reusable Features.

palette
dark
light
↑↓ select↵ apply esc close

Palettes adapted from Catppuccin (Macchiato) (MIT), Tokyo Night (Apache-2.0), gruvbox (MIT), Catppuccin (Latte) (MIT), Rosé Pine (Dawn) (MIT).