swarm-ci-runner: load the registration token in a register unit of its own
systemd hands a unit's credentials to every process of that unit, and a host job is a process of the runner daemon, so the `LoadCredential=` on gitea-runner-swarm still let any workflow read the registration token from `$CREDENTIALS_DIRECTORY`. swarm-ci-runner-register.service, a oneshot under the daemon's dynamic user and state dir, now holds the only `LoadCredential=` and runs the precond and `act_runner register` (only when `.runner` is missing). The daemon unit has no credential, no `EnvironmentFile` and no `TOKEN`; its pre-start step starts the register unit, so a missing token fails the daemon's start and `Restart=on-failure` retries it. The precond compares `.runner`'s labels in the form the daemon rewrites them to (`nix` -> `nix:host`); compared verbatim, a scheme-less label re-registered the runner on every start. tmpfiles removes the unused `registration-token.env` and upstream's `.token-hash`/`.labels` markers.
This commit is contained in:
parent
0302b29ca4
commit
8ffceea8e3
4 changed files with 184 additions and 100 deletions
|
|
@ -47,10 +47,14 @@ let
|
|||
};
|
||||
|
||||
# The runner alone: no hive, no forge, no store, its store identity placed
|
||||
# by hand.
|
||||
# by hand. `nix` is a label without a scheme.
|
||||
ciAlone = hive {
|
||||
deploy.hive-controller.enable = false;
|
||||
deploy.ci-runner.enable = true;
|
||||
deploy.ci-runner.labels = [
|
||||
"hive-ci:host"
|
||||
"nix"
|
||||
];
|
||||
deploy.bao.ciRunnerClientCertFile = "/etc/pki/bao-ci-runner.pem";
|
||||
deploy.bao.ciRunnerClientKeyFile = "/etc/pki/bao-ci-runner-key.pem";
|
||||
};
|
||||
|
|
@ -82,7 +86,8 @@ let
|
|||
forgeSettings = forgeHere.containers.hive-forge.config.services.forgejo.settings;
|
||||
|
||||
runnerService = ciAlone.containers.swarm-ci-runner.config.systemd.services.gitea-runner-swarm;
|
||||
registerStep = lib.findFirst lib.isDerivation null runnerService.serviceConfig.ExecStartPre;
|
||||
registerService =
|
||||
ciAlone.containers.swarm-ci-runner.config.systemd.services.swarm-ci-runner-register;
|
||||
|
||||
cases = [
|
||||
{
|
||||
|
|
@ -154,9 +159,10 @@ let
|
|||
ok = refusedOver ciNoIdentity "deploy.bao.ciRunnerClientCertFile";
|
||||
}
|
||||
{
|
||||
# A host job inherits the daemon's whole environment, and a token there
|
||||
# lets any workflow register runners against the forge.
|
||||
name = "the CI runner daemon carries no registration token in its environment";
|
||||
# systemd hands a credential to every process of the unit that loads
|
||||
# it, and a host job is one of the daemon's processes and inherits its
|
||||
# environment; a token in either lets any workflow register runners.
|
||||
name = "the CI runner daemon can read no registration token";
|
||||
ok =
|
||||
let
|
||||
sc = runnerService.serviceConfig;
|
||||
|
|
@ -164,26 +170,48 @@ let
|
|||
!(runnerService.environment ? TOKEN)
|
||||
&& lib.toList (sc.EnvironmentFile or [ ]) == [ ]
|
||||
&& !(lib.any (lib.hasPrefix "TOKEN=") (lib.toList (sc.Environment or [ ])))
|
||||
&& sc.LoadCredential == [ "registration-token:/var/lib/swarm-ci-runner/registration-token" ];
|
||||
&& !(sc ? LoadCredential)
|
||||
&& !(sc ? LoadCredentialEncrypted)
|
||||
&& !(sc ? SetCredential);
|
||||
}
|
||||
{
|
||||
# The only reader of the credential, and the only registration path.
|
||||
name = "the CI runner registers from its credential, and only without a .runner";
|
||||
name = "the CI register unit alone loads the registration token, and runs before the daemon";
|
||||
ok =
|
||||
let
|
||||
text = registerStep.text;
|
||||
beforeRegister = lib.head (lib.splitString "register --no-interactive" text);
|
||||
sc = registerService.serviceConfig;
|
||||
in
|
||||
registerStep != null
|
||||
sc.LoadCredential == [ "registration-token:/var/lib/swarm-ci-runner/registration-token" ]
|
||||
&& sc.Type == "oneshot"
|
||||
&& sc.User == runnerService.serviceConfig.User
|
||||
&& sc.DynamicUser
|
||||
&& sc.StateDirectory == runnerService.serviceConfig.StateDirectory
|
||||
&& lib.any (
|
||||
s: lib.hasSuffix "/bin/systemctl start swarm-ci-runner-register.service" s
|
||||
) runnerService.serviceConfig.ExecStartPre
|
||||
&& !(lib.any (
|
||||
s: lib.hasInfix "gitea-register-runner" (toString s)
|
||||
) runnerService.serviceConfig.ExecStartPre)
|
||||
&& lib.hasInfix "if [ -e .runner ]; then\n exit 0\nfi" beforeRegister
|
||||
) runnerService.serviceConfig.ExecStartPre);
|
||||
}
|
||||
{
|
||||
name = "the CI register unit registers from its credential, and only without a .runner";
|
||||
ok =
|
||||
let
|
||||
text = registerService.script;
|
||||
beforeRegister = lib.head (lib.splitString "register --no-interactive" text);
|
||||
in
|
||||
lib.hasInfix "if [ -e .runner ]; then\n exit 0\nfi" beforeRegister
|
||||
&& lib.hasInfix ''token="$(< "$CREDENTIALS_DIRECTORY/registration-token")"'' beforeRegister
|
||||
&& lib.hasInfix ''--token "$token"'' text
|
||||
&& !(lib.hasInfix "export" text)
|
||||
&& !(lib.hasInfix "TOKEN" text);
|
||||
}
|
||||
{
|
||||
# The daemon rewrites `.runner`'s labels to this form on start; compared
|
||||
# against the configured strings verbatim, a scheme-less label would
|
||||
# discard `.runner` and register a new runner on every start.
|
||||
name = "the CI runner compares .runner's labels in the form the daemon writes them";
|
||||
ok = registerService.environment.SWARM_CI_RUNNER_LABELS == "hive-ci:host,nix:host";
|
||||
}
|
||||
{
|
||||
# A removed option evaluates to an assertion naming the replacement.
|
||||
name = "a CI option under deploy.forgejo names its replacement under deploy.ci-runner";
|
||||
|
|
|
|||
Loading…
Reference in a new issue