Graphs
Fleet data is plain Nix, so a question about it is a function. yen.lib.graphs answers graph questions.
It works on succ, an attrset from a node to its successors. Every function sorts, so the order the data is
written in never changes an answer.
| Function | Answer |
|---|---|
reach succ n | every node reachable from n |
reachers succ n | every node that reaches n |
transpose succ | the edges reversed |
topo succ | a topological order, or { ok = false; stuck; } for a cycle |
layers succ | waves: each node after its successors |
sccs succ, cycles succ | strongly connected components, nodes on a cycle |
shortest succ a b, paths succ a b | the shortest path, all paths |
fromEdges es | succ from [ { from; to; } ] |
Hosts depend on hosts:
{ yen, lib, ... }:{ yen.kinds.fleet = { spawns.host = _: yen.host; sinks.out.reduce = yen.lib.reduce.merge; }; yen.kinds.host.fields.dependsOn = yen.lib.opt (yen.lib.is lib.isList) [ ]; # host refs
yen.fleet.main = { }; yen.host.db = { }; yen.host.cache = { }; yen.host.api = { dependsOn = [ yen.host.db yen.host.cache ]; }; yen.host.web = { dependsOn = [ yen.host.api ]; }; yen.host.lb = { dependsOn = [ yen.host.web ]; }; yen.flake = { out, ... }: out;}# `succ` is an attrset from a node to its successors. Here: a host to the hosts it depends on.{ yen, lib, ... }:let G = yen.lib.graphs; dependsOn = lib.mapAttrs (_: h: map (d: d.name) h.dependsOn) yen.host;in{ yen.aspects.deploy-plan = { at = yen.kinds.fleet; out.deploy = { waves = G.layers dependsOn; # dependencies first: each wave after the waves it depends on impact = lib.genAttrs (lib.attrNames yen.host) (G.reachers dependsOn); # who breaks if this host is down path = G.shortest dependsOn "lb" "db"; }; errors = map (c: "deploy: a dependency cycle: ${lib.concatStringsSep " -> " c}") ( lib.filter (c: lib.length c > 1) (G.sccs dependsOn) ); };}{ impact = { api = [ "lb" "web" ]; cache = [ "api" "lb" "web" ]; db = [ "api" "lb" "web" ]; lb = [ ]; web = [ "lb" ]; }; path = [ "lb" "web" "api" "db" ]; waves = [ [ "cache" "db" ] [ "api" ] [ "web" ] [ "lb" ] ];}The deploy waves as a diagram, from the same data:
flowchart LR cache --> w0["wave 1"] db --> w0["wave 1"] api --> w1["wave 2"] web --> w2["wave 3"] lb --> w3["wave 4"]
A cycle is an error
Section titled “A cycle is an error”A cycle is data, not a hang. sccs finds it, and the aspect gives an error record:
# db now depends on lb: a cycle.{ yen, ... }:{ yen.host.db.dependsOn = [ yen.host.lb ];}[ "deploy: a dependency cycle: api -> db -> lb -> web"]