Skip to content

Scopes and spawns

A scope is one instance at one place in the tree. A kind says which instances live under it with spawns.<kind>. This file builds a tree of environments, hosts and users:

modules/tree.nix
# `spawns.<kind>` gives the children of a scope: a function of the parent scope.
{ yen, ... }:
{
yen.kinds.fleet.spawns.environment = _: yen.environment; # (1)
yen.kinds.environment.spawns.host = yen.lib.pointing "environment" yen.host; # (2)
yen.kinds.host = {
fields.environment = yen.kinds.environment;
spawns.user = yen.lib.pointing "hosts" yen.user; # (3)
};
yen.kinds.user.fields.hosts = yen.lib.listOf yen.kinds.host; # refs to the hosts the user logs in to
yen.fleet.main = { };
}

spawns.<kind> is a function of the parent scope. It gives the instances that live under it. (1) makes fleet spawn every environment. fleet is the root: no kind spawns it. (2) and (3) find the children by a ref field. yen.lib.pointing "hosts" yen.user gives the users whose hosts field holds the parent scope, as one ref or in a list. The root fleet has the instance yen.fleet.main, and this data fills the tree:

modules/instances.nix
{ yen, ... }:
{
yen.environment.prod = { };
yen.environment.lab = { };
yen.host.web.environment = yen.environment.prod;
yen.host.db.environment = yen.environment.prod;
yen.host.box.environment = yen.environment.lab;
yen.user.alice.hosts = [
yen.host.web
yen.host.box
]; # (4) one user, two scopes
yen.user.bob.hosts = [ yen.host.db ];
}

(4) gives one user two hosts, so that user gets two scopes.

The tree comes from the data. Nothing lists the children by hand:

flowchart TD
  n6c4c773b6d["fleet main"]
  nf631382c77["environment lab"]
  nc33ee814ad["host box"]
  n86d1c002a8["user alice"]
  n4a308930d1["environment prod"]
  n4019172c9d["host web"]
  n8bd47cb553["user alice"]
  nc60807bd8b["host db"]
  n614abf7a41["user bob"]
  n6c4c773b6d --> nf631382c77
  nf631382c77 --> nc33ee814ad
  nc33ee814ad --> n86d1c002a8
  n6c4c773b6d --> n4a308930d1
  n4a308930d1 --> n4019172c9d
  n4019172c9d --> n8bd47cb553
  n4a308930d1 --> nc60807bd8b
  nc60807bd8b --> n614abf7a41

The id of a scope is its path: fleet:main/environment:prod/host:web/user:alice. One instance can live in several places. alice has two scopes, one under each of her hosts. Names need not be unique.

result.scopes gives the scopes in order:

result
[
"fleet:main"
"fleet:main/environment:lab"
"fleet:main/environment:lab/host:box"
"fleet:main/environment:lab/host:box/user:alice"
"fleet:main/environment:prod"
"fleet:main/environment:prod/host:web"
"fleet:main/environment:prod/host:web/user:alice"
"fleet:main/environment:prod/host:db"
"fleet:main/environment:prod/host:db/user:bob"
]

A parent comes before its children. Siblings sort by the order sink (default 0), then by name. This aspect gives the host web an order of -1:

modules/order.nix
# Siblings sort by the `order` sink (default 0), then by name. A parent comes before its children.
{ yen, ... }:
{
yen.aspects.web-first = {
at = yen.host.web;
order = -1;
};
}

So web comes before db.

An aspect can read the tree. This example gives the scope ids as a flake output:

modules/outputs.nix
# The flake gives the scope ids. An aspect asks the tree for the whole fleet (`graph = true`) to read them.
{ yen, ... }:
let
whole.graph = true; # (1) a tree request: the whole fleet
fleetOf = tree: tree.select whole; # the whole fleet, with its data
in
{
yen.kinds.fleet.sinks.out.reduce = yen.lib.reduce.merge;
yen.flake = { out, ... }: out;
yen.aspects.tree = {
at = yen.kinds.fleet;
cycle =
{ tree, ... }:
{
tree = [ whole ];
out = [
{
scopes = map (s: s.id) (fleetOf tree).lib.graph.scopes; # (2)
}
];
};
};
}

(1) is a tree request: graph = true asks for the whole fleet. (2) reads the scopes of the answer. The cycle page explains tree requests.

ContributeCommunitySponsor