Args
An element can be a function with named args. yen fills the args it knows. In this example, a load balancer reads the addresses that its environment collects. First the kinds and sinks it reads:
{ yen, ... }:let under = yen.lib.pointing "environment" yen.host;in{ yen.kinds.fleet = { spawns.environment = _: yen.environment; sinks.out.reduce = yen.lib.reduce.merge; }; yen.kinds.environment = { spawns.host = under; sinks.backends = { }; # (1) hosts give their address; a wall: it stays at the environment }; yen.kinds.host = { fields = { environment = yen.kinds.environment; addr = yen.lib.str; }; sinks.proxy.out = # (2) { host, proxy, ... }: { proxy.${host.name} = proxy; }; };
yen.kinds.cluster.fields = { }; # a kind that no scope here is under
yen.fleet.main = { }; yen.environment.prod = { }; yen.host.lb = { environment = yen.environment.prod; addr = "10.0.0.1"; }; yen.host.web-1 = { environment = yen.environment.prod; addr = "10.0.0.2"; }; yen.host.web-2 = { environment = yen.environment.prod; addr = "10.0.0.3"; }; yen.flake = { out, ... }: out;}(1) is the sink backends of environment. Hosts give their address to it. (2) is the sink proxy of host.
Now the aspects. Their elements are functions with args:
{ yen, ... }:{ yen.aspects.backend = { at = yen.lib.all [ yen.kinds.host (s: s.name != "lb") ]; backends = { host, ... }: host.addr; # (1) a kind arg: the nearest host }; yen.aspects.balancer = { at = yen.host.lb; proxy = { environment, # (2) a kind arg: the nearest environment backends, # (3) a sink arg: the reduced value at its nearest receiver (the environment) scope, # (4) the scope itself cluster ? null, # (5) optional: this fleet has no cluster ... }: { inherit backends; at = scope.id; env = environment.name; cluster = if cluster == null then "none" else cluster.name; }; };}(1) asks for host, the name of a kind. (2) also names a kind. (3) names a sink.
(4) is scope. (5) has a default, so it is optional. This is what the balancer gives:
{ lb = [ { at = "fleet:main/environment:prod/host:lb"; backends = [ "10.0.0.2" "10.0.0.3" ]; cluster = "none"; env = "prod"; } ];}The args yen fills
Section titled “The args yen fills”These are the rules:
| Arg | Value |
|---|---|
a kind name (host, environment) |
the nearest ancestor-or-self scope of that kind |
a sink name (backends) |
the reduced value of that sink at its nearest receiver |
scope |
the scope itself |
anything else (pkgs, config) |
stays open: the element is still a function |
Optional and missing args
Section titled “Optional and missing args”{ cluster ? null, ... } makes an arg optional. Without a default, an arg with no answer means the element
is not given. Add this file to the example:
# `cluster` without a default: no answer at lb, so the element is not given.{ yen, ... }:{ yen.aspects.cluster-name = { at = yen.host.lb; proxy = { cluster, ... }: cluster.name; # (1) };}(1) needs a cluster, and no scope here is under one. errors says why:
[ { class = "fixable"; code = "arg"; error = "aspect cluster-name: arg `cluster` has no answer"; fix = "give `cluster` a default (`cluster ? null`) or make the element active only where it has an answer"; from = "fleet:main/environment:prod/host:lb"; path = [ ]; want = "cluster"; }]Open args: modules for NixOS and home-manager
Section titled “Open args: modules for NixOS and home-manager”Args that yen does not know stay open. This is how a nixos element is a NixOS module. In this file, yen
fills host and the element stays a function of pkgs. NixOS fills pkgs, config and lib later.
# `pkgs` is not a kind, a sink or `scope`: it stays open. The element is a module function.{ yen, ... }:{ yen.aspects.open = { at = yen.host.lb; proxy = { host, pkgs, ... }: # (1) { name = host.name; shell = pkgs.bash; }; };}(1) has the arg host, which yen fills, and the arg pkgs, which stays open.