parent
0fc77df1a9
commit
8e09f8360a
47 changed files with 98 additions and 1026 deletions
|
|
@ -37,7 +37,7 @@ let
|
|||
m:
|
||||
lib.any (a: !a.assertion && lib.hasInfix "deploy.builder.enable requires" a.message) m.assertions;
|
||||
|
||||
natsHere = shared.natsOldPath;
|
||||
natsHere = shared.natsQueueOnly;
|
||||
|
||||
autheliaHere = hive {
|
||||
deploy.bao.enable = true;
|
||||
|
|
|
|||
|
|
@ -995,36 +995,6 @@ let
|
|||
&& bare.services.dnsmasq.enable
|
||||
&& bare.networking.bridges ? hive-br0;
|
||||
}
|
||||
{
|
||||
# A host config written against either old spelling still runs its hive.
|
||||
# The builder's own `true` is lowered to `mkDefault false`, so only the
|
||||
# forwarded definition can turn it on.
|
||||
name = "the old hive toggles forward to deploy.hive-controller.enable and warn";
|
||||
ok =
|
||||
let
|
||||
renamedFrom =
|
||||
old: m:
|
||||
lib.any (
|
||||
w: lib.hasInfix old w && lib.hasInfix "services.hyperhive.deploy.hive-controller.enable" w
|
||||
) m.warnings;
|
||||
viaOld =
|
||||
extra:
|
||||
hive (
|
||||
extra
|
||||
// {
|
||||
deploy.hive-controller.enable = lib.mkDefault false;
|
||||
}
|
||||
);
|
||||
oldEnable = viaOld { enable = true; };
|
||||
oldC0re = viaOld { c0re.enable = true; };
|
||||
in
|
||||
oldEnable.services.hyperhive.deploy.hive-controller.enable
|
||||
&& oldEnable.systemd.services ? hive-c0re
|
||||
&& renamedFrom "services.hyperhive.enable" oldEnable
|
||||
&& oldC0re.services.hyperhive.deploy.hive-controller.enable
|
||||
&& oldC0re.systemd.services ? hive-c0re
|
||||
&& renamedFrom "services.hyperhive.c0re.enable" oldC0re;
|
||||
}
|
||||
{
|
||||
# Refused wherever something turns the name into an identifier, and only
|
||||
# there. The store host is the control that the refusal is not the hive's
|
||||
|
|
|
|||
|
|
@ -82,16 +82,6 @@ let
|
|||
deploy.hive-controller.enable = false;
|
||||
deploy.forgejo.ci.enable = true;
|
||||
};
|
||||
# The older spelling, under the swarm-level forge options.
|
||||
ciUnderSwarmForge = hive {
|
||||
swarm.forge.ci.enable = true;
|
||||
swarm.forge.ci.name = "older-runner";
|
||||
swarm.forge.ci.concurrency = 2;
|
||||
swarm.forge.ci.labels = [ "nix:host" ];
|
||||
swarm.forge.ci.package = pkgs.hello;
|
||||
deploy.bao.ciRunnerClientCertFile = "/etc/pki/bao-ci-runner.pem";
|
||||
deploy.bao.ciRunnerClientKeyFile = "/etc/pki/bao-ci-runner-key.pem";
|
||||
};
|
||||
|
||||
runsForge = m: m.services.hyperhive.deploy.forgejo.enable && m.containers ? hive-forge;
|
||||
|
||||
|
|
@ -299,22 +289,6 @@ let
|
|||
&& lib.any (lib.hasInfix "services.hyperhive.deploy.forgejo.ci.enable") ciUnderForge.warnings
|
||||
&& !(lib.any (a: !a.assertion) ciUnderForge.assertions);
|
||||
}
|
||||
{
|
||||
# The control is the container: it only exists if `enable` arrived.
|
||||
name = "a CI option under swarm.forge sets the same option under deploy.ci-runner";
|
||||
ok =
|
||||
let
|
||||
r = ciUnderSwarmForge.services.hyperhive.deploy.ci-runner;
|
||||
in
|
||||
r.enable
|
||||
&& r.name == "older-runner"
|
||||
&& r.concurrency == 2
|
||||
&& r.labels == [ "nix:host" ]
|
||||
&& r.package == pkgs.hello
|
||||
&& ciUnderSwarmForge.containers ? swarm-ci-runner
|
||||
&& lib.any (lib.hasInfix "services.hyperhive.swarm.forge.ci.enable") ciUnderSwarmForge.warnings
|
||||
&& !(lib.any (a: !a.assertion) ciUnderSwarmForge.assertions);
|
||||
}
|
||||
{
|
||||
# The rename carries `enable` but not the identity: an old config with
|
||||
# only the old name is refused, and the message says what to set. The
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@ let
|
|||
runGroup
|
||||
;
|
||||
|
||||
grafanaOldPath = hive {
|
||||
grafanaHere = hive {
|
||||
deploy.grafana.enable = true;
|
||||
swarm.grafana.datasourceUrl = "http://127.0.0.1:19999";
|
||||
swarm.grafana.logsDatasourceUrl = "http://127.0.0.1:19998";
|
||||
swarm.grafana.plugins = [ ];
|
||||
swarm.grafana.package = pkgs.emptyDirectory;
|
||||
deploy.grafana.datasourceUrl = "http://127.0.0.1:19999";
|
||||
deploy.grafana.logsDatasourceUrl = "http://127.0.0.1:19998";
|
||||
deploy.grafana.plugins = [ ];
|
||||
deploy.grafana.package = pkgs.emptyDirectory;
|
||||
};
|
||||
|
||||
# A host that tries to move the socket directory; reading the option throws.
|
||||
|
|
@ -120,7 +120,7 @@ let
|
|||
ok =
|
||||
!(builtins.tryEval grafanaSocketDirSet.services.hyperhive.deploy.grafana.socketDir).success
|
||||
&&
|
||||
grafanaOldPath.services.nginx.virtualHosts.${grafanaOldPath.services.hyperhive.swarm.grafana.domain}.locations."= /metrics".proxyPass
|
||||
grafanaHere.services.nginx.virtualHosts.${grafanaHere.services.hyperhive.swarm.grafana.domain}.locations."= /metrics".proxyPass
|
||||
== "http://unix:/run/swarm-grafana/grafana.sock:/metrics";
|
||||
}
|
||||
{
|
||||
|
|
@ -132,8 +132,8 @@ let
|
|||
name = "grafana's published vhost serves /metrics only behind the collector's auth subrequest";
|
||||
ok =
|
||||
let
|
||||
g = grafanaOldPath.services.hyperhive.swarm.grafana;
|
||||
locs = grafanaOldPath.services.nginx.virtualHosts.${g.domain}.locations;
|
||||
g = grafanaHere.services.hyperhive.swarm.grafana;
|
||||
locs = grafanaHere.services.nginx.virtualHosts.${g.domain}.locations;
|
||||
checksOf = l: lib.filter (lib.hasPrefix "auth_request") (lib.splitString "\n" l.extraConfig);
|
||||
authz = locs."= /__grafana_metrics_authz";
|
||||
in
|
||||
|
|
@ -143,7 +143,7 @@ let
|
|||
&& !(lib.hasInfix "error_page" locs."= /metrics".extraConfig)
|
||||
&& authz.proxyPass == "https://auth.t.local/api/authz/auth-request"
|
||||
&& lib.hasInfix "internal;" authz.extraConfig
|
||||
&& !(grafanaOldPath.services.nginx.virtualHosts ? "grafana-metrics")
|
||||
&& !(grafanaHere.services.nginx.virtualHosts ? "grafana-metrics")
|
||||
&& lib.hasPrefix "http://unix:" locs."/".proxyPass;
|
||||
}
|
||||
{
|
||||
|
|
@ -152,8 +152,7 @@ let
|
|||
# this host". Its vhost is on the public gateway, so a password box there
|
||||
# is a way in whatever the topology.
|
||||
name = "grafana disables its local login form even where authelia is not on this host";
|
||||
ok =
|
||||
grafanaOldPath.containers.swarm-grafana.config.services.grafana.settings.auth.disable_login_form;
|
||||
ok = grafanaHere.containers.swarm-grafana.config.services.grafana.settings.auth.disable_login_form;
|
||||
}
|
||||
{
|
||||
# The absence class this whole file is for, and the reported defect in one
|
||||
|
|
@ -445,7 +444,7 @@ let
|
|||
ok =
|
||||
let
|
||||
sources =
|
||||
grafanaOldPath.containers.swarm-grafana.config.services.grafana.provision.datasources.settings.datasources;
|
||||
grafanaHere.containers.swarm-grafana.config.services.grafana.provision.datasources.settings.datasources;
|
||||
logs = lib.head (lib.filter (d: d.uid == "swarm-victorialogs") sources);
|
||||
rules = logs.jsonData.logLevelRules or [ ];
|
||||
# `overwrite_text` makes the stored text the OpenTelemetry short name
|
||||
|
|
@ -473,7 +472,7 @@ let
|
|||
ok =
|
||||
let
|
||||
groups =
|
||||
grafanaOldPath.containers.swarm-grafana.config.services.grafana.provision.alerting.rules.settings.groups;
|
||||
grafanaHere.containers.swarm-grafana.config.services.grafana.provision.alerting.rules.settings.groups;
|
||||
rules = lib.concatMap (g: g.rules) groups;
|
||||
rule = lib.findFirst (r: r.uid == "hh-agent-turn-failed") null rules;
|
||||
expr = (lib.head rule.data).model.expr;
|
||||
|
|
@ -517,9 +516,8 @@ let
|
|||
name = "grafana's vhost is rendered with the gateway's listen set, TLS and security headers";
|
||||
ok =
|
||||
let
|
||||
gw = grafanaOldPath.services.hyperhive.gateway;
|
||||
v =
|
||||
grafanaOldPath.services.nginx.virtualHosts.${grafanaOldPath.services.hyperhive.swarm.grafana.domain};
|
||||
gw = grafanaHere.services.hyperhive.gateway;
|
||||
v = grafanaHere.services.nginx.virtualHosts.${grafanaHere.services.hyperhive.swarm.grafana.domain};
|
||||
# nginx's `listen` entries come back with defaults filled in
|
||||
# (`extraParameters`, `proxyProtocol`), so compare the fields set.
|
||||
listenOf = map (l: {
|
||||
|
|
|
|||
|
|
@ -103,13 +103,7 @@ let
|
|||
];
|
||||
}).config;
|
||||
|
||||
# Note `hyperhive`, not `services.hyperhive`: the agent tier's options moved
|
||||
# under `services.hyperhive.agent`, and ../agent-modules/renamed-options.nix
|
||||
# keeps the top-level spelling reaching them. ⚠️ That shim covers the
|
||||
# options that existed when the tier moved and nothing since, so a fixture
|
||||
# for an option added afterwards has to go through [`agentWith`] and name
|
||||
# the real path.
|
||||
agent = extra: agentWith { hyperhive = extra; };
|
||||
agent = extra: agentWith { services.hyperhive.agent = extra; };
|
||||
|
||||
baoNames = machine: machine.services.hyperhive.gateway.localNames;
|
||||
|
||||
|
|
|
|||
|
|
@ -22,25 +22,18 @@ let
|
|||
runGroup
|
||||
;
|
||||
|
||||
# The queue's two old `swarm.nats` paths that still rename; dropping either
|
||||
# shim fails the eval. No store identity, which the first case below relies on.
|
||||
natsOldPath = shared.natsOldPath;
|
||||
# No store identity, which the first case below relies on.
|
||||
natsQueueOnly = shared.natsQueueOnly;
|
||||
|
||||
# Seventh split slice, plus slice 10's two authelia packages. Of slice 7's
|
||||
# movers only `usersFile` has a rename entry — the other two are `readOnly`,
|
||||
# and a rename module contributes a definition, which a read-only option
|
||||
# refuses; see ./host-modules/deploy.nix. `package` and `bridgePackage` are
|
||||
# ordinary options, so they do carry one. The two arms
|
||||
# below have different jobs. `usersFile` tests the rename; the nats one tests
|
||||
# that a reader pointed at the `deploy` namespace still renders the derived
|
||||
# path — seven of those reads go through an alias a path-shaped grep
|
||||
# cannot see.
|
||||
autheliaOldPath = hive {
|
||||
# The IdP and the queue on one host. A reader of authelia's host paths has to
|
||||
# follow the `deploy` namespace; seven of those reads go through an alias a
|
||||
# path-shaped grep cannot see.
|
||||
autheliaAndNats = hive {
|
||||
deploy.authelia.enable = true;
|
||||
deploy.nats.enable = true;
|
||||
swarm.authelia.usersFile = "/var/lib/test-authelia/users.yml";
|
||||
swarm.authelia.package = pkgs.emptyDirectory;
|
||||
swarm.authelia.bridgePackage = pkgs.emptyDirectory;
|
||||
deploy.authelia.usersFile = "/var/lib/test-authelia/users.yml";
|
||||
deploy.authelia.package = pkgs.emptyDirectory;
|
||||
deploy.authelia.bridgePackage = pkgs.emptyDirectory;
|
||||
# Renders the publisher, which reads the host client-secret dir.
|
||||
deploy.swarm-secret-publisher.baoClientCertFile = "/etc/pki/publisher.pem";
|
||||
deploy.swarm-secret-publisher.baoClientKeyFile = "/etc/pki/publisher-key.pem";
|
||||
|
|
@ -297,9 +290,9 @@ let
|
|||
{
|
||||
name = "authelia and nats declare no scrape target on a host with no collector";
|
||||
ok =
|
||||
!(autheliaOldPath.services.hyperhive.deploy.swarm-otel.enable)
|
||||
&& !(autheliaOldPath.services.hyperhive.swarm.otel.scrapeTargets ? authelia)
|
||||
&& !(autheliaOldPath.services.hyperhive.swarm.otel.scrapeTargets ? nats);
|
||||
!(autheliaAndNats.services.hyperhive.deploy.swarm-otel.enable)
|
||||
&& !(autheliaAndNats.services.hyperhive.swarm.otel.scrapeTargets ? authelia)
|
||||
&& !(autheliaAndNats.services.hyperhive.swarm.otel.scrapeTargets ? nats);
|
||||
}
|
||||
{
|
||||
# The xkey and the responder's OIDC client secret are read only from
|
||||
|
|
@ -308,7 +301,7 @@ let
|
|||
# minted one.
|
||||
name = "a queue with no responder store identity is refused at eval, one with a leaf is not";
|
||||
ok =
|
||||
refusedWithoutIdentity natsOldPath
|
||||
refusedWithoutIdentity natsQueueOnly
|
||||
&& !(refusedWithoutIdentity natsWithStoreIdentity)
|
||||
&& !(refusedWithoutIdentity natsOnStoreHost);
|
||||
}
|
||||
|
|
@ -327,10 +320,10 @@ let
|
|||
# this key would still evaluate cleanly — the only reader that would
|
||||
# notice is a publisher whose row exceeds upstream's much smaller
|
||||
# default, and by then it is a dropped row, not an eval failure.
|
||||
# Piggybacks on the pre-rename nats fixture above, which already
|
||||
# Piggybacks on the queue-only fixture above, which already
|
||||
# renders this container's full config.
|
||||
name = "the queue's payload ceiling is set, not inherited from the server's default";
|
||||
ok = natsOldPath.containers.swarm-nats.config.services.nats.settings.max_payload == 8388608;
|
||||
ok = natsQueueOnly.containers.swarm-nats.config.services.nats.settings.max_payload == 8388608;
|
||||
}
|
||||
{
|
||||
# What keeps a callout request inside one principal: the responder is
|
||||
|
|
@ -351,7 +344,7 @@ let
|
|||
&& !(settings ? system_account)
|
||||
)
|
||||
[
|
||||
natsOldPath
|
||||
natsQueueOnly
|
||||
natsOnStoreHost
|
||||
];
|
||||
}
|
||||
|
|
@ -375,7 +368,7 @@ let
|
|||
&& !(c.services.nats.settings.authorization.auth_callout ? xkey)
|
||||
)
|
||||
[
|
||||
natsOldPath
|
||||
natsQueueOnly
|
||||
natsOnStoreHost
|
||||
];
|
||||
}
|
||||
|
|
@ -384,7 +377,7 @@ let
|
|||
# responder with it: the responder requires the server, so a server that
|
||||
# failed to start left the responder inactive rather than failed.
|
||||
name = "starting the queue's server starts its responder";
|
||||
ok = lib.elem "nats.service" (responderOf natsOldPath).wantedBy;
|
||||
ok = lib.elem "nats.service" (responderOf natsQueueOnly).wantedBy;
|
||||
}
|
||||
{
|
||||
# The keys are created before anything needs them, by the responder's
|
||||
|
|
@ -480,7 +473,7 @@ let
|
|||
ok =
|
||||
let
|
||||
exec =
|
||||
natsOldPath.containers.swarm-nats.config.systemd.services.swarm-nats-auth.serviceConfig.ExecStart;
|
||||
natsQueueOnly.containers.swarm-nats.config.systemd.services.swarm-nats-auth.serviceConfig.ExecStart;
|
||||
in
|
||||
!(lib.hasInfix "--agent-publish-subject " exec);
|
||||
}
|
||||
|
|
@ -491,7 +484,7 @@ let
|
|||
name = "the responder grants a verified agent its own hive-free subjects";
|
||||
ok =
|
||||
let
|
||||
exec = (responderOf natsOldPath).serviceConfig.ExecStart;
|
||||
exec = (responderOf natsQueueOnly).serviceConfig.ExecStart;
|
||||
in
|
||||
lib.hasInfix "--agent-token-publish-subject '$$SWARM.term.{agent}'" exec
|
||||
&& lib.hasInfix "--agent-token-publish-subject '$$SWARM.agent-state.{agent}'" exec
|
||||
|
|
@ -503,7 +496,7 @@ let
|
|||
name = "the responder grants a verified agent its own icon key";
|
||||
ok =
|
||||
let
|
||||
exec = (responderOf natsOldPath).serviceConfig.ExecStart;
|
||||
exec = (responderOf natsQueueOnly).serviceConfig.ExecStart;
|
||||
in
|
||||
lib.hasInfix "--agent-token-publish-subject '$$KV.agent-icons.{agent}'" exec;
|
||||
}
|
||||
|
|
@ -514,7 +507,7 @@ let
|
|||
name = "a verified agent's grant is exactly its own subjects, and its JetStream API subjects read its own schedule, inference and permissions keys";
|
||||
ok =
|
||||
let
|
||||
args = lib.splitString " " (responderOf natsOldPath).serviceConfig.ExecStart;
|
||||
args = lib.splitString " " (responderOf natsQueueOnly).serviceConfig.ExecStart;
|
||||
granted = lib.concatLists (
|
||||
lib.imap0 (
|
||||
i: a:
|
||||
|
|
@ -685,7 +678,7 @@ let
|
|||
# through an `autheliaCfg` alias, where a path-shaped grep does not see it.
|
||||
name = "a consumer of authelia's host client-secret dir renders it from the deploy namespace";
|
||||
ok =
|
||||
autheliaOldPath.systemd.paths.swarm-secret-publish.pathConfig.PathChanged
|
||||
autheliaAndNats.systemd.paths.swarm-secret-publish.pathConfig.PathChanged
|
||||
== "/var/lib/nixos-containers/swarm-authelia/var/lib/authelia-swarm/oidc-clients";
|
||||
}
|
||||
{
|
||||
|
|
|
|||
|
|
@ -55,11 +55,10 @@ in
|
|||
# shape that hives elsewhere join.
|
||||
swarmServicesWithController = hiveWithController { deploy.allSwarmServices = true; };
|
||||
|
||||
# The queue's two old `swarm.nats` paths that still rename, `enable` and
|
||||
# `authPackage`, with no store identity.
|
||||
natsOldPath = hive {
|
||||
swarm.nats.enable = true;
|
||||
swarm.nats.authPackage = pkgs.emptyDirectory;
|
||||
# The queue with no store identity.
|
||||
natsQueueOnly = hive {
|
||||
deploy.nats.enable = true;
|
||||
deploy.nats.authPackage = pkgs.emptyDirectory;
|
||||
};
|
||||
|
||||
controllerAlone = hive { deploy.swarm-controller.enable = true; };
|
||||
|
|
|
|||
|
|
@ -61,11 +61,6 @@ let
|
|||
# authelia somewhere else, the credential delivered by hand. Whether this
|
||||
# collector authenticates must follow the credential, never another
|
||||
# service's placement.
|
||||
#
|
||||
# The `swarm.otel.clientSecretFile` below is the PRE-RENAME path. It predates
|
||||
# the split and is deliberately left spelled that way: it makes this fixture
|
||||
# the old-path case for that option too, so dropping its rename entry fails
|
||||
# the eval here rather than only in a real operator's config.
|
||||
otelRemoteAuthelia = hive {
|
||||
deploy.swarm-otel.enable = true;
|
||||
deploy.authelia.enable = false;
|
||||
|
|
@ -74,7 +69,7 @@ let
|
|||
# `token_url`, so a hive with a secret and no URL has a credential it can
|
||||
# present nowhere.
|
||||
swarm.authelia.url = "https://auth.example.invalid";
|
||||
swarm.otel.clientSecretFile = "/var/lib/swarm-otel-oidc/by-hand.secret";
|
||||
deploy.swarm-otel.clientSecretFile = "/var/lib/swarm-otel-oidc/by-hand.secret";
|
||||
};
|
||||
|
||||
# A collector whose ONLY scrape work is published: loopback targets forced
|
||||
|
|
@ -148,10 +143,8 @@ let
|
|||
{
|
||||
# The collector's half of the same split, and a different arm from the
|
||||
# authenticator case below: this one reads the PATH the unit loads, so a
|
||||
# reader left on a source that is non-null but wrong still fails. The
|
||||
# fixture spells the option its pre-rename way, so it covers the rename
|
||||
# entry at the same time.
|
||||
name = "a config written against the pre-rename otel secret path still loads it as a credential";
|
||||
# reader left on a source that is non-null but wrong still fails.
|
||||
name = "a by-hand otel secret path is the one the collector loads as a credential";
|
||||
ok =
|
||||
lib.any (c: lib.hasInfix "/var/lib/swarm-otel-oidc/by-hand.secret" c)
|
||||
otelRemoteAuthelia.containers.swarm-otel.config.systemd.services.opentelemetry-collector.serviceConfig.LoadCredential;
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ let
|
|||
deploy.forgejo.enable = true;
|
||||
deploy.forgejo.mirrors = [ aMirror ];
|
||||
deploy.forgejo.actionsFromSelf = false;
|
||||
c0re.orgAvatarPng = "/etc/marker-avatar.png";
|
||||
deploy.swarm-controller.configOrgAvatarPng = "/etc/marker-avatar.png";
|
||||
};
|
||||
mirrorsNoController = hive {
|
||||
deploy.forgejo.enable = true;
|
||||
|
|
@ -796,7 +796,7 @@ let
|
|||
&& lib.elem "actions/checkout" (map (m: m.dest) (mirrorsOf forgeWithController));
|
||||
}
|
||||
{
|
||||
name = "the old c0re org-avatar option lands on the controller's unit";
|
||||
name = "the org avatar lands on the controller's unit, not hive-c0re's";
|
||||
ok =
|
||||
controllerWithMirrors.systemd.services.swarm-controller.environment.SWARM_CONTROLLER_CONFIG_ORG_AVATAR_PNG
|
||||
== "/etc/marker-avatar.png"
|
||||
|
|
|
|||
Loading…
Reference in a new issue