Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/nix/module-eval/builder.nix

212 lines
8.3 KiB
Nix

# `checks.module-eval-builder` — see ./lib.nix for the shared rationale (why
# this suite exists, naming convention, "evaluates not executes"). The swarm
# builder's container, its default and its queue and IdP registrations; its
# store grants are in ./bao-grants.nix beside every other principal's.
{
pkgs,
lib,
self,
nixosSystem,
shared,
}:
let
inherit
(import ./lib.nix {
inherit
pkgs
lib
self
nixosSystem
;
})
hive
runGroup
;
bare = shared.bareWithController;
# On the store's host, which mints the builder's store identity.
withBuilder = hive {
deploy.bao.enable = true;
deploy.builder.enable = true;
};
servicesHere = shared.swarmServicesWithController;
# Short-circuits on `assertion`, so only a failing one has its message read.
refusedWithoutIdentity =
m:
lib.any (a: !a.assertion && lib.hasInfix "deploy.builder.enable requires" a.message) m.assertions;
natsHere = shared.natsQueueOnly;
autheliaHere = hive {
deploy.bao.enable = true;
deploy.authelia.enable = true;
};
container = withBuilder.containers.swarm-builder;
inner = container.config;
svc = inner.systemd.services.swarm-builder;
env = svc.environment;
# The check flake on the inputs the builder's unit names, called as
# `nix build` calls it. `hyperhive` is entered through the filtered source's
# own `flake.nix`.
builderCheck =
agentModule:
let
src = env.SWARM_BUILDER_HYPERHIVE_FLAKE;
hyperhive = (import "${src}/flake.nix").outputs (
self.inputs
// {
self = hyperhive // {
outPath = src;
inherit (self) inputs;
};
}
);
in
((import "${env.SWARM_BUILDER_CHECK_FLAKE}/flake.nix").outputs {
inherit hyperhive;
agent.nixosModules.default = agentModule;
}).packages.x86_64-linux.check;
cases = [
{
name = "the swarm builder's container exists only where its host enables it";
ok = !(bare.containers ? swarm-builder) && withBuilder.containers ? swarm-builder;
}
{
# The store's host mints the builder's leaf, so the switch needs no
# manual identity step: the pair is set and the module's identity
# assertion, which nothing else reads in this fixture, passes. `nats` is
# the control that the switch is on.
name = "hosting the shared services turns the builder on, with its store identity";
ok =
servicesHere.services.hyperhive.deploy.builder.enable
&& servicesHere.containers ? swarm-builder
&& servicesHere.services.hyperhive.deploy.bao.builderClientCertFile != null
&& servicesHere.services.hyperhive.deploy.bao.builderClientKeyFile != null
&& !(refusedWithoutIdentity servicesHere)
&& servicesHere.services.hyperhive.deploy.bao.enable
&& servicesHere.services.hyperhive.deploy.nats.enable;
}
{
# A config PR is unreviewed input to an evaluation: the container keeps
# its own netns, and reaches the host's nix daemon through its socket
# alone, with no daemon of its own racing the bind.
name = "the builder's container has its own netns and builds only through the host's daemon socket";
ok =
container.privateNetwork
&& inner.services.hyperhive.swarmContainer.privateNetwork
&& container.bindMounts."/nix/var/nix/daemon-socket".hostPath == "/nix/var/nix/daemon-socket"
&& !inner.systemd.services.nix-daemon.enable
&& !inner.systemd.sockets.nix-daemon.enable
&& inner.systemd.globalEnvironment.NIX_REMOTE == "daemon";
}
{
# The daemon holds its store leaf, handed in by nspawn without a copy on
# disk; the forge token is read per build and reaches only `git`.
name = "the builder loads only its store leaf, runs as a dynamic user and carries no token in its unit";
ok =
lib.elem "builder-bao-key" svc.serviceConfig.LoadCredential
&& env.BAO_CLIENT_KEY == "/run/credentials/swarm-builder.service/builder-bao-key"
&& env.SWARM_BUILDER_OIDC_CLIENT_ID == "swarm-builder"
&& lib.elem "--load-credential=builder-bao-key:/var/lib/swarm-bao-pki/builder-key.pem" container.extraFlags
&& svc.serviceConfig.DynamicUser
&& !(lib.any (lib.hasSuffix "TOKEN") (lib.attrNames env));
}
{
# The minting unit goes inactive after each run, so a start that pulls it
# runs it again and mints a leaf that is missing; an active
# `RemainAfterExit` unit would satisfy the pull without running.
name = "the builder's container runs the leaf-minting unit before every start";
ok =
let
pki = withBuilder.systemd.services.swarm-bao-pki;
c = withBuilder.systemd.services."container@swarm-builder";
in
pki.serviceConfig.Type == "oneshot"
&& !(pki.serviceConfig.RemainAfterExit or false)
&& lib.elem "multi-user.target" pki.wantedBy
&& lib.elem "swarm-bao-pki.service" c.wants
&& lib.elem "swarm-bao-pki.service" c.after;
}
{
# The check builds on the sources this host was deployed with, so a green check means
# the same hyperhive and nixpkgs base.
name = "the builder builds on the hyperhive and nixpkgs this host was deployed with";
ok =
env.SWARM_BUILDER_HYPERHIVE_FLAKE == withBuilder.services.hyperhive.c0re.hyperhiveFlake
&& env.SWARM_BUILDER_NIXPKGS_FLAKE == withBuilder.services.hyperhive.c0re.nixpkgsFlake
&& lib.hasSuffix "-check-flake" env.SWARM_BUILDER_CHECK_FLAKE
&& env.SWARM_BUILDER_CONFIG_ORG == "agent-configs"
&& env.SWARM_BUILDER_FORGE_URL == "https://${withBuilder.services.hyperhive.swarm.forge.domain}";
}
{
# The builder evaluates `builtins.mapAttrs (_: a: a.meta)` over this
# output and prints it as JSON. A derivation or a path in `meta` would
# make that evaluation build or copy something; a function would not
# print at all.
name = "the built-in archetypes carry a module and a meta of plain data, and reviewer can run commands and android-dev installs its toolchain";
ok =
let
plain =
v:
if lib.isDerivation v || lib.isFunction v || builtins.isPath v then
false
else if lib.isAttrs v then
lib.all plain (lib.attrValues v)
else if lib.isList v then
lib.all plain v
else
true;
archetypes = self.hyperhiveArchetypes;
metas = builtins.mapAttrs (_: a: a.meta) archetypes;
reviewer = metas.reviewer.permissions;
in
lib.all (a: a ? module && a.meta.description != "") (lib.attrValues archetypes)
&& plain metas
&& lib.elem "messaging" reviewer
&& lib.elem "inbox" reviewer
&& lib.elem "execution" reviewer
&& !(lib.elem "meta" reviewer)
&&
lib.elem pkgs.android-tools
(archetypes.android-dev.module { inherit pkgs; }).environment.systemPackages;
}
{
# `docs.enable` reads `docs.source`, whose default builds from the
# `docs/` the `hyperhive` source has none of; an eval error there fails
# this whole group.
name = "the builder's check evaluates a config with the reference docs on";
ok =
lib.hasSuffix ".drv"
(builderCheck {
services.hyperhive.agent.user.name = "a1";
services.hyperhive.agent.docs.enable = true;
}).drvPath;
}
{
# The responder denies a client id it does not know, and a NATS denial
# arrives as a timeout.
name = "the queue's responder grants the builder's client its subject";
ok = lib.hasInfix "--builder-client swarm-builder" natsHere.containers.swarm-nats.config.systemd.services.swarm-nats-auth.serviceConfig.ExecStart;
}
{
name = "the builder's client is registered as a machine, and the publisher carries its secret";
ok =
let
c = lib.findFirst (
c: c.id == "swarm-builder"
) null autheliaHere.services.hyperhive.swarm.authelia.oidc.clients;
in
c != null
&& c.kind == "machine"
&& lib.hasInfix "secret/swarm/services/swarm-builder/oidc/client" (
autheliaHere.containers.swarm-authelia.config.systemd.services.swarm-secret-publish.script
);
}
];
in
runGroup "builder" cases