Instances and refs
A kind is also a type. This file declares two kinds, and a field of one holds a ref to an instance of the other:
# A kind is a type too: a field of kind `environment` holds a ref to an environment instance.# Nothing spawns `environment`, so it is a data kind: its instances have no scope.{ yen, lib, ... }:{ yen.kinds.environment.fields.domain = yen.lib.str; # (1) yen.kinds.host.fields = { environment = yen.kinds.environment; # (2) backup = yen.lib.maybe yen.kinds.host; # (3) a ref to another host, or null };}(1) declares the kind environment, with one field. (2) is a field of kind environment: it holds a ref to an
environment instance, never a string. (3) is a field that holds a ref to another host, or null.
The instances follow. yen.environment.prod is the ref that the field takes, never the string "prod".
{ yen, ... }:{ yen.environment.prod.domain = "example.com"; yen.environment.lab.domain = "lab.example.com";
yen.host.web = { environment = yen.environment.prod; # (1) backup = yen.host.db; # (2) }; yen.host.db = { environment = yen.environment.prod; backup = yen.host.web; }; # refs can point both ways yen.host.test.environment = yen.environment.lab;}(1) sets a field to the ref yen.environment.prod. (2) refs another host.
Refs are lazy, so two hosts can point at each other. There is no cycle error.
Now follow the refs. An aspect reads them like an attrset, and yen.lib.same compares two instances.
The cycle key asks for the tree of hosts, and report is the element it gives
to the sink of that name. The root kind that collects report is in
the example code.
{ yen, lib, ... }:let hosts.instances = yen.kinds.host; # a tree request: every host, with its data # the other hosts of the host's environment; `same` compares two instances by kind and name peersOf = tree: host: lib.filter (h: !yen.lib.same h host && yen.lib.same h.environment host.environment) ( # (1) tree.select hosts );in{ yen.aspects.report = { at = yen.kinds.host; cycle = { tree, scope, ... }: { tree = [ hosts ]; report = [ { ${scope.name} = { fqdn = "${scope.name}.${scope.environment.domain}"; # (2) backup = if scope.backup == null then null else scope.backup.name; # (3) peers = map (h: h.name) (peersOf tree scope); }; } ]; }; };}(1) finds the other hosts in the same environment. (2) follows a ref and reads a field of the instance it points to.
(3) tests the ref for null and reads the name of the host it points to. The aspect gives this:
{ db = { backup = "web"; fqdn = "db.example.com"; peers = [ "web" ]; }; test = { backup = null; fqdn = "test.lab.example.com"; peers = [ ]; }; web = { backup = "db"; fqdn = "web.example.com"; peers = [ "db" ]; };}Data kinds
Section titled “Data kinds”environment is a data kind: no kind spawns it. Its instances have no place in the scope tree,
and refs to them still work. The tree has the fleet and the hosts only:
flowchart TD n6c4c773b6d["fleet main"] nbe39bbfa9b["host db"] n493d96ad82["host test"] n0f3c4939ab["host web"] n6c4c773b6d --> nbe39bbfa9b n6c4c773b6d --> n493d96ad82 n6c4c773b6d --> n0f3c4939ab
Groups, channels and secrets are good data kinds. Scopes shows how a kind gets a place in the tree.
A string is not a ref
Section titled “A string is not a ref”A name where a ref goes is an error record, with the fix:
# A name where a ref goes.{ yen, ... }:{ yen.host.cache.environment = "prod";}[ { class = "fixable"; code = "ref"; error = "environment wants a environment ref"; fix = "use yen.environment.<name>, not a string"; from = "host:cache"; got = "prod"; path = [ "environment" ]; want = "a environment ref"; }]