Skip to content

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:

modules/fleet.nix
{ 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:

modules/aspects.nix
{ 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:

result
{
lb = [
{
at = "fleet:main/environment:prod/host:lb";
backends = [
"10.0.0.2"
"10.0.0.3"
];
cluster = "none";
env = "prod";
}
];
}

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

{ 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:

variants/missing.nix
# `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:

result
[
{
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.

variants/open.nix
# `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.

ContributeCommunitySponsor