module-eval: evaluate the swarm.peers removal shim

`nix/host-modules/swarm-peers-removed.nix` exists to turn an "option does
not exist" error into a warning that says where the entries went. That
warning is its whole deliverable, and no fixture set `swarm.peers` — the
string appeared 0 times in module-eval.nix, so the shim was never
evaluated by anything.

It differs from its ten siblings in what a broken shim looks like. The
others re-route a value, so a failure shows up as a wrong rendered
config. This one renders prose nothing reads back, so a `mkIf` that
stops matching or a rename of the `swarm.hives` it points at fails
silently, and lands on the one operator who needed it.

The check already carries an old-path fixture for ten migrated
namespaces (wireguard, forge, matrix, nats, authelia, controller, ui,
stores, grafana, statusPublish). `swarm.peers` was the eleventh and the
only uncovered one.

Two peers, only one carrying `caCert`, because the module emits a second
warning filtered on exactly that attribute — with a single peer the
filtered list and `attrNames` are the same list, so a `withCaCert` that
had collapsed into `attrNames` would still read green. The third case is
the control: a hive that never set `peers` must get neither warning,
without which the other two pass on any config whose warning list
happens to carry the string.

Closes #4188.
This commit is contained in:
atlas 2026-09-11 11:28:56 +02:00 committed by mara
commit f918cea957

View file

@ -235,6 +235,25 @@ let
swarm.statusPublish.clientSecretFile = "/run/secrets/status-client.secret";
};
# The eleventh migration, and the only one of the eleven with no old-path
# fixture. It differs from its ten siblings in what a shim DOES: the others
# re-route a value, so a broken one shows up as a wrong rendered config. This
# one's entire deliverable is a warning, which nothing reads back — so an
# unevaluated shim is a warning nobody sees, and it fails silently onto the
# one operator who needed it.
#
# Two peers, only ONE carrying `caCert`, because the module emits a SECOND
# warning filtered on exactly that attribute. With a single peer the filtered
# list and `attrNames` are the same list, and a `withCaCert` that had
# collapsed into `attrNames` would still read green.
peersOldPath = hive {
swarm.peers.old-one.domain = "old-one.t.local";
swarm.peers.old-two = {
domain = "old-two.t.local";
caCert = "/etc/ssl/old-two-ca.pem";
};
};
baoPkcs11 = hive {
deploy.bao.enable = true;
deploy.bao.seal = "pkcs11";
@ -990,6 +1009,41 @@ let
name = "a bootstrap token on a host that runs no store grants nothing";
ok = !(baoGrantNoStore.systemd.services ? swarm-bao-bootstrap-dir);
}
{
# Matches the option path, not the surrounding prose: the warning should
# stay rewordable, but it must not stop naming what was set or where it
# went.
name = "a config still setting the removed swarm.peers is told where the entries moved";
ok =
let
hit = lib.filter (
w: lib.hasInfix "services.hyperhive.swarm.peers is removed" w
) peersOldPath.warnings;
in
lib.length hit == 1
&& lib.hasInfix "services.hyperhive.swarm.hives" (lib.head hit)
&& lib.hasInfix "old-one, old-two" (lib.head hit);
}
{
# The caCert half is a separately filtered warning, so it has to name
# ONLY the peer that set one. Asserting the peer that did NOT is absent
# is what distinguishes the filter from `attrNames`.
name = "the removed peers.caCert warns about only the peers that set one";
ok =
let
hit = lib.filter (w: lib.hasInfix "caCert is removed" w) peersOldPath.warnings;
in
lib.length hit == 1
&& lib.hasInfix "Still set on: old-two" (lib.head hit)
&& !(lib.hasInfix "old-one" (lib.head hit));
}
{
# Control for both cases above. Without it they pass on any config whose
# warning list happens to carry the string, including one where the shim
# fires unconditionally — which is the same defect wearing the other sign.
name = "a hive that never set swarm.peers gets neither removal warning";
ok = !(lib.any (w: lib.hasInfix "swarm.peers" w) bare.warnings);
}
{
name = "a config written against the pre-rename authelia usersFile still reaches the bridge";
ok =