keep the mirror hint off forge errors, forward the hyperhive mirror from any controller host
This commit is contained in:
parent
a6b5bafa83
commit
52cdfe84a0
5 changed files with 70 additions and 36 deletions
|
|
@ -89,9 +89,9 @@ Run `nixos-rebuild switch --flake .#my-host`. The swarm UI then answers at
|
||||||
|
|
||||||
The swarm controller pins the hive's flake inputs to a commit of hyperhive on
|
The swarm controller pins the hive's flake inputs to a commit of hyperhive on
|
||||||
the swarm's own forge, so the forge needs a `hyperhive/hyperhive` repo before
|
the swarm's own forge, so the forge needs a `hyperhive/hyperhive` repo before
|
||||||
the first agent builds. On a host that runs the forge and the controller,
|
the first agent builds. The controller creates it as a pull-mirror of
|
||||||
the controller creates it as a pull-mirror of
|
`https://forge.darkest.space/hyperhive/hyperhive`, on the swarm's forge wherever
|
||||||
`https://forge.darkest.space/hyperhive/hyperhive`. You run no extra step.
|
that runs. You run no extra step.
|
||||||
|
|
||||||
| option | what it does |
|
| option | what it does |
|
||||||
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
# Glue: wire the forge and the swarm-controller together when one host runs both.
|
# Glue: wire the swarm-controller's forge seeding to the forge options.
|
||||||
#
|
#
|
||||||
# ONE PAIRING PER FILE — forge ← swarm-controller, and nothing else. Deleting
|
# ONE PAIRING PER FILE — forge ← swarm-controller, and nothing else. Deleting
|
||||||
# this leaves the controller seeding no pull-mirrors, the forge resolving
|
# this leaves the controller seeding no pull-mirrors, the forge resolving
|
||||||
# workflow actions from `data.forgejo.org` and a fresh swarm without the hyperhive
|
# workflow actions from `data.forgejo.org` and a fresh swarm without the hyperhive
|
||||||
# repo its meta flake pins.
|
# repo its meta flake pins.
|
||||||
#
|
#
|
||||||
# ⚠️ Gated on BOTH services being here: the mirrors reach a controller on this
|
# ⚠️ The mirror list is forwarded wherever the controller runs: it creates the
|
||||||
# host only, and the forge's own `config` block hangs off
|
# mirrors through the forge's API, so the forge may be on another host. The
|
||||||
# `deploy.forgejo.enable`. The forge's `mirrors` warning covers the split case.
|
# list is that of the controller's own host; the forge's `mirrors` warning
|
||||||
|
# covers a list declared on a forge host without one. `actionsFromSelf` changes
|
||||||
|
# the forge's own settings, so it follows only a forge beside the controller.
|
||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
config,
|
config,
|
||||||
|
|
@ -17,14 +19,12 @@ let
|
||||||
deployCfg = config.services.hyperhive.deploy;
|
deployCfg = config.services.hyperhive.deploy;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = lib.mkIf (deployCfg.forgejo.enable && deployCfg.swarm-controller.enable) {
|
config = lib.mkMerge [
|
||||||
# The controller seeds the `actions/*` mirrors, so a forge beside one can
|
(lib.mkIf deployCfg.swarm-controller.enable {
|
||||||
# resolve actions from itself. `mkDefault`: an operator's explicit value wins.
|
# The meta flake pins the hyperhive repo on the swarm's forge, and a fresh
|
||||||
services.hyperhive.deploy.forgejo.actionsFromSelf = lib.mkDefault true;
|
# forge has none. The controller creates the mirror only if
|
||||||
|
# `hyperhive/hyperhive` is absent, so a swarm that hosts the real repo is
|
||||||
# The meta flake pins the hyperhive repo on this forge, and a fresh forge has
|
# not touched. `mkDefault`: an operator's explicit value wins.
|
||||||
# none. The controller creates the mirror only if `hyperhive/hyperhive` is
|
|
||||||
# absent, so a swarm that hosts the real repo is not touched.
|
|
||||||
services.hyperhive.deploy.forgejo.hyperhiveUpstream =
|
services.hyperhive.deploy.forgejo.hyperhiveUpstream =
|
||||||
lib.mkDefault "https://forge.darkest.space/hyperhive/hyperhive";
|
lib.mkDefault "https://forge.darkest.space/hyperhive/hyperhive";
|
||||||
|
|
||||||
|
|
@ -32,5 +32,12 @@ in
|
||||||
# forge-objects pass (`swarm-controller/src/forge/objects.rs`).
|
# forge-objects pass (`swarm-controller/src/forge/objects.rs`).
|
||||||
systemd.services.swarm-controller.environment.SWARM_CONTROLLER_FORGE_MIRRORS =
|
systemd.services.swarm-controller.environment.SWARM_CONTROLLER_FORGE_MIRRORS =
|
||||||
builtins.toJSON deployCfg.forgejo.effectiveMirrors;
|
builtins.toJSON deployCfg.forgejo.effectiveMirrors;
|
||||||
};
|
})
|
||||||
|
|
||||||
|
(lib.mkIf (deployCfg.forgejo.enable && deployCfg.swarm-controller.enable) {
|
||||||
|
# The controller seeds the `actions/*` mirrors, so a forge beside one can
|
||||||
|
# resolve actions from itself. `mkDefault`: an operator's explicit value wins.
|
||||||
|
services.hyperhive.deploy.forgejo.actionsFromSelf = lib.mkDefault true;
|
||||||
|
})
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,9 @@ in
|
||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
./service.nix
|
./service.nix
|
||||||
|
# Set on every host, not only the forge's: the controller's host forwards the
|
||||||
|
# list whether or not the forge runs beside it.
|
||||||
|
{ services.hyperhive.deploy.forgejo.effectiveMirrors = effectiveMirrors; }
|
||||||
(lib.mkRemovedOptionModule [ "services" "hyperhive" "deploy" "forgejo" "sso" "clientSecretFile" ] ''
|
(lib.mkRemovedOptionModule [ "services" "hyperhive" "deploy" "forgejo" "sso" "clientSecretFile" ] ''
|
||||||
The forge's OIDC client secret is fetched from the swarm secret store
|
The forge's OIDC client secret is fetched from the swarm secret store
|
||||||
(swarm-bao-forge-oidc) into a fixed runtime path. Remove this
|
(swarm-bao-forge-oidc) into a fixed runtime path. Remove this
|
||||||
|
|
@ -311,12 +314,13 @@ in
|
||||||
build fetches it from there, so without it the first agent's
|
build fetches it from there, so without it the first agent's
|
||||||
`update_meta` step fails and no agent container is built.
|
`update_meta` step fails and no agent container is built.
|
||||||
|
|
||||||
Defaults to `https://forge.darkest.space/hyperhive/hyperhive` when this
|
Defaults to `https://forge.darkest.space/hyperhive/hyperhive` on a host
|
||||||
host also runs the swarm-controller, which seeds the mirror (set by
|
that runs the swarm-controller, which seeds the mirror on the swarm's
|
||||||
`glue-forge-controller.nix`), and to `null` otherwise. The controller
|
forge wherever that runs (set by `glue-forge-controller.nix`), and to
|
||||||
creates the mirror only when `hyperhive/hyperhive` does not exist: a
|
`null` otherwise. The controller creates the mirror only when
|
||||||
swarm that already holds the repo as a normal repo keeps it unchanged. A `hyperhive/hyperhive` entry in
|
`hyperhive/hyperhive` does not exist: a swarm that already holds the
|
||||||
{option}`services.hyperhive.deploy.forgejo.mirrors` takes precedence.
|
repo as a normal repo keeps it unchanged. A `hyperhive/hyperhive` entry
|
||||||
|
in {option}`services.hyperhive.deploy.forgejo.mirrors` takes precedence.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1400,7 +1404,6 @@ in
|
||||||
# ../glue-forge-controller.nix); a controller elsewhere reads its own
|
# ../glue-forge-controller.nix); a controller elsewhere reads its own
|
||||||
# host's list, so the warning below says so at eval rather than leaving
|
# host's list, so the warning below says so at eval rather than leaving
|
||||||
# it to be noticed as a missing repo.
|
# it to be noticed as a missing repo.
|
||||||
services.hyperhive.deploy.forgejo.effectiveMirrors = effectiveMirrors;
|
|
||||||
warnings = lib.optional (effectiveMirrors != [ ] && !deployCfg.swarm-controller.enable) ''
|
warnings = lib.optional (effectiveMirrors != [ ] && !deployCfg.swarm-controller.enable) ''
|
||||||
services.hyperhive.deploy.forgejo.mirrors (or the actions/* and hyperhive mirrors
|
services.hyperhive.deploy.forgejo.mirrors (or the actions/* and hyperhive mirrors
|
||||||
deploy.forgejo.actionsFromSelf and hyperhiveUpstream add) is set on a host that does not run the
|
deploy.forgejo.actionsFromSelf and hyperhiveUpstream add) is set on a host that does not run the
|
||||||
|
|
|
||||||
|
|
@ -1410,19 +1410,20 @@ let
|
||||||
# forge has none, so the controller seeds a mirror of it: by default,
|
# forge has none, so the controller seeds a mirror of it: by default,
|
||||||
# with another upstream when the option says so, and not at all when it
|
# with another upstream when the option says so, and not at all when it
|
||||||
# is null (controllerWithMirrors) or without the controller (forgeOnly).
|
# is null (controllerWithMirrors) or without the controller (forgeOnly).
|
||||||
name = "a forge beside the controller mirrors hyperhive/hyperhive unless told not to";
|
# controllerAlone has the forge on another host and still gets the default.
|
||||||
|
name = "the controller mirrors hyperhive/hyperhive on its forge unless told not to";
|
||||||
ok =
|
ok =
|
||||||
let
|
let
|
||||||
mirrorsOf =
|
mirrorsOf =
|
||||||
m: builtins.fromJSON m.systemd.services.swarm-controller.environment.SWARM_CONTROLLER_FORGE_MIRRORS;
|
m: builtins.fromJSON m.systemd.services.swarm-controller.environment.SWARM_CONTROLLER_FORGE_MIRRORS;
|
||||||
hyperhiveOf = m: lib.filter (e: e.dest == "hyperhive/hyperhive") (mirrorsOf m);
|
hyperhiveOf = m: lib.filter (e: e.dest == "hyperhive/hyperhive") (mirrorsOf m);
|
||||||
in
|
defaultHyperhive = {
|
||||||
hyperhiveOf forgeWithController == [
|
|
||||||
{
|
|
||||||
upstream = "https://forge.darkest.space/hyperhive/hyperhive";
|
upstream = "https://forge.darkest.space/hyperhive/hyperhive";
|
||||||
dest = "hyperhive/hyperhive";
|
dest = "hyperhive/hyperhive";
|
||||||
}
|
};
|
||||||
]
|
in
|
||||||
|
hyperhiveOf forgeWithController == [ defaultHyperhive ]
|
||||||
|
&& hyperhiveOf controllerAlone == [ defaultHyperhive ]
|
||||||
&& map (e: e.upstream) (hyperhiveOf controllerWithSelfActions) == [ hyperhiveElsewhere ]
|
&& map (e: e.upstream) (hyperhiveOf controllerWithSelfActions) == [ hyperhiveElsewhere ]
|
||||||
&& hyperhiveOf controllerWithMirrors == [ ]
|
&& hyperhiveOf controllerWithMirrors == [ ]
|
||||||
&& forgeOnly.services.hyperhive.deploy.forgejo.hyperhiveUpstream == null;
|
&& forgeOnly.services.hyperhive.deploy.forgejo.hyperhiveUpstream == null;
|
||||||
|
|
|
||||||
|
|
@ -374,8 +374,7 @@ async fn hyperhive_pin(
|
||||||
let Some(flake) = committed_flake else {
|
let Some(flake) = committed_flake else {
|
||||||
let head = forge
|
let head = forge
|
||||||
.head_files(HYPERHIVE_OWNER, HYPERHIVE_REPO, None, &[])
|
.head_files(HYPERHIVE_OWNER, HYPERHIVE_REPO, None, &[])
|
||||||
.await
|
.await?
|
||||||
.with_context(missing_hyperhive)?
|
|
||||||
.with_context(missing_hyperhive)?;
|
.with_context(missing_hyperhive)?;
|
||||||
return Ok(HyperhivePin {
|
return Ok(HyperhivePin {
|
||||||
rev: head.commit,
|
rev: head.commit,
|
||||||
|
|
@ -575,6 +574,8 @@ mod tests {
|
||||||
compares: Mutex<Vec<(String, String)>>,
|
compares: Mutex<Vec<(String, String)>>,
|
||||||
/// Every compare fails, as a forge that is down does.
|
/// Every compare fails, as a forge that is down does.
|
||||||
compare_fails: bool,
|
compare_fails: bool,
|
||||||
|
/// Reads of this `owner/repo` fail, as on a forge that refuses or is down.
|
||||||
|
head_fails: Option<String>,
|
||||||
/// Every `head_files` read, as `("owner/repo", branch)`.
|
/// Every `head_files` read, as `("owner/repo", branch)`.
|
||||||
head_reads: Mutex<Vec<(String, Option<String>)>>,
|
head_reads: Mutex<Vec<(String, Option<String>)>>,
|
||||||
}
|
}
|
||||||
|
|
@ -603,6 +604,9 @@ mod tests {
|
||||||
branch: Option<&str>,
|
branch: Option<&str>,
|
||||||
paths: &[&str],
|
paths: &[&str],
|
||||||
) -> Result<Option<DefaultBranchFiles>> {
|
) -> Result<Option<DefaultBranchFiles>> {
|
||||||
|
if self.head_fails.as_deref() == Some(&format!("{owner}/{repo}")) {
|
||||||
|
anyhow::bail!("forge answered 503");
|
||||||
|
}
|
||||||
self.head_reads
|
self.head_reads
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -874,6 +878,25 @@ mod tests {
|
||||||
assert!(forge.commits.lock().unwrap().is_empty());
|
assert!(forge.commits.lock().unwrap().is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn a_forge_error_reading_hyperhive_does_not_carry_the_mirror_hint() {
|
||||||
|
let mut forge = forge_with(&[]);
|
||||||
|
forge.head_fails = Some("hyperhive/hyperhive".to_owned());
|
||||||
|
let err = update(
|
||||||
|
&forge,
|
||||||
|
&FakeLocker::locked(),
|
||||||
|
&settings(),
|
||||||
|
None,
|
||||||
|
&argus_and_reviewer(),
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
let err = format!("{err:#}");
|
||||||
|
assert!(err.contains("forge answered 503"), "{err}");
|
||||||
|
assert!(!err.contains("hyperhiveUpstream"), "{err}");
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn a_refused_lock_commits_nothing() {
|
async fn a_refused_lock_commits_nothing() {
|
||||||
let forge = forge_with(&[]);
|
let forge = forge_with(&[]);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue