hive-priv: extract exec's non-delegating match arms into named functions
This commit is contained in:
parent
f1c5d49289
commit
584a973344
1 changed files with 86 additions and 70 deletions
|
|
@ -332,34 +332,23 @@ async fn write_line_event(writer: &mut OwnedWriteHalf, stream: PrivStream, data:
|
|||
/// For streaming ops (`CreateContainer`/`UpdateContainer` with `stream: true`)
|
||||
/// output lines are forwarded to `writer` as `PrivEvent::Line` messages and
|
||||
/// the returned strings are empty.
|
||||
// One match arm per priv op — a flat 1:1 dispatch table. The length tracks
|
||||
// the op count, not complexity; splitting it would just scatter the mapping.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
/// Execute one validated request.
|
||||
///
|
||||
/// `fd` is the descriptor that arrived with this request, already checked
|
||||
/// against the operation by [`check_fd_agreement`]: `Some` exactly for
|
||||
/// the variants that stream into a caller-supplied descriptor, `None`
|
||||
/// for every other operation.
|
||||
// One match arm per priv op — a flat 1:1 dispatch table. Every arm either
|
||||
// delegates directly or validates then delegates; an op whose handling is
|
||||
// more than that gets its own named function instead, so the match's length
|
||||
// tracks the op count, not complexity.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
async fn exec(
|
||||
req: PrivRequest,
|
||||
fd: Option<OwnedFd>,
|
||||
writer: &mut OwnedWriteHalf,
|
||||
) -> Result<(String, String)> {
|
||||
match req {
|
||||
PrivRequest::StartContainer { ref name } => {
|
||||
validate_container_name(name)?;
|
||||
let machine = container_system_name(name);
|
||||
// Clear any start-limit lockout left by earlier failures so a
|
||||
// now-correct start isn't blocked. nixos-container start does not
|
||||
// do this itself. Best-effort: if the unit doesn't exist yet
|
||||
// (first-time create) reset-failed is a no-op and we proceed.
|
||||
let _ = Command::new("systemctl")
|
||||
.args(["reset-failed", &format!("container@{machine}.service")])
|
||||
.status()
|
||||
.await;
|
||||
container_run(&["start", &machine]).await
|
||||
}
|
||||
PrivRequest::StartContainer { ref name } => start_container(name).await,
|
||||
|
||||
PrivRequest::StopContainer { ref name } => {
|
||||
validate_container_name(name)?;
|
||||
|
|
@ -419,12 +408,7 @@ async fn exec(
|
|||
|
||||
PrivRequest::ReloadGatewayNginx => sync_gateway_nginx().await,
|
||||
|
||||
PrivRequest::RunForgeAdmin { ref args } => {
|
||||
for arg in args {
|
||||
validate_forge_admin_arg(arg)?;
|
||||
}
|
||||
run_forge_admin(args).await
|
||||
}
|
||||
PrivRequest::RunForgeAdmin { ref args } => exec_forge_admin(args).await,
|
||||
|
||||
PrivRequest::SetAgentPaused {
|
||||
ref agent_name,
|
||||
|
|
@ -447,36 +431,7 @@ async fn exec(
|
|||
ref token,
|
||||
ref account,
|
||||
ref homeserver,
|
||||
} => {
|
||||
validate_agent_name(agent_name)?;
|
||||
// Build the token filename. `None` → the hive account's
|
||||
// `matrix-token`; `Some(a)` → `matrix-token-<a>`. The account
|
||||
// suffix MUST be validated as a plain identifier (no `/`, `.`,
|
||||
// `..`) before it goes into the filename, or a crafted account
|
||||
// could traverse out of the state dir — `write_agent_state_file`
|
||||
// trusts its `filename` argument.
|
||||
let filename = match account {
|
||||
None => "matrix-token".to_owned(),
|
||||
Some(a) => {
|
||||
validate_name_chars(a)?;
|
||||
format!("matrix-token-{a}")
|
||||
}
|
||||
};
|
||||
let res = write_agent_state_file(agent_name, &filename, &format!("{token}\n"))?;
|
||||
// For an extra account, persist its homeserver in a sidecar
|
||||
// (`matrix-account-<a>.json`) so the daemon can auto-discover the
|
||||
// account without a static `matrixAccounts` config entry. Only
|
||||
// when both `account` and `homeserver` are present; the account
|
||||
// suffix is already validated above.
|
||||
if let (Some(a), Some(hs)) = (account, homeserver) {
|
||||
let meta = serde_json::to_string(&MatrixAccountSidecar {
|
||||
homeserver: hs.as_str(),
|
||||
})
|
||||
.context("serialize matrix account sidecar")?;
|
||||
write_agent_state_file(agent_name, &format!("matrix-account-{a}.json"), &meta)?;
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
} => write_matrix_token(agent_name, token, account.as_deref(), homeserver.as_deref()),
|
||||
|
||||
PrivRequest::WriteAgentGithubToken {
|
||||
ref agent_name,
|
||||
|
|
@ -491,23 +446,7 @@ async fn exec(
|
|||
ref label,
|
||||
ref base_url,
|
||||
ref token,
|
||||
} => {
|
||||
validate_agent_name(agent_name)?;
|
||||
validate_name_chars(label)?;
|
||||
let res = write_agent_state_file(
|
||||
agent_name,
|
||||
&format!("forge-{label}-token"),
|
||||
&format!("{token}\n"),
|
||||
)?;
|
||||
// Sidecar carries the base URL — there's no host-side nix config
|
||||
// for extra forges, so this is the only place it's persisted.
|
||||
let meta = serde_json::to_string(&ForgeSidecar {
|
||||
base_url: base_url.as_str(),
|
||||
})
|
||||
.context("serialize forge account sidecar")?;
|
||||
write_agent_state_file(agent_name, &format!("forge-{label}.json"), &meta)?;
|
||||
Ok(res)
|
||||
}
|
||||
} => write_extra_forge_account(agent_name, label, base_url, token),
|
||||
|
||||
PrivRequest::DeleteAgentExtraForgeAccount {
|
||||
ref agent_name,
|
||||
|
|
@ -622,6 +561,83 @@ async fn exec(
|
|||
}
|
||||
}
|
||||
|
||||
/// `StartContainer`: clear any start-limit lockout left by earlier failures
|
||||
/// so a now-correct start isn't blocked (`nixos-container start` does not do
|
||||
/// this itself). Best-effort — if the unit doesn't exist yet (first-time
|
||||
/// create), `reset-failed` is a no-op and the start proceeds regardless.
|
||||
async fn start_container(name: &str) -> Result<(String, String)> {
|
||||
validate_container_name(name)?;
|
||||
let machine = container_system_name(name);
|
||||
let _ = Command::new("systemctl")
|
||||
.args(["reset-failed", &format!("container@{machine}.service")])
|
||||
.status()
|
||||
.await;
|
||||
container_run(&["start", &machine]).await
|
||||
}
|
||||
|
||||
/// `RunForgeAdmin`: every arg must pass [`validate_forge_admin_arg`] before
|
||||
/// the admin CLI ever sees it.
|
||||
async fn exec_forge_admin(args: &[String]) -> Result<(String, String)> {
|
||||
for arg in args {
|
||||
validate_forge_admin_arg(arg)?;
|
||||
}
|
||||
run_forge_admin(args).await
|
||||
}
|
||||
|
||||
/// `WriteAgentMatrixToken`: `account = None` writes the hive account's
|
||||
/// `matrix-token`; `Some(a)` writes `matrix-token-<a>`. The account suffix
|
||||
/// MUST be validated as a plain identifier (no `/`, `.`, `..`) before it goes
|
||||
/// into the filename, or a crafted account could traverse out of the state
|
||||
/// dir — `write_agent_state_file` trusts its `filename` argument. When both
|
||||
/// `account` and `homeserver` are `Some`, also persists a
|
||||
/// `matrix-account-<a>.json` sidecar so the daemon can auto-discover the
|
||||
/// extra account without a static `matrixAccounts` config entry.
|
||||
fn write_matrix_token(
|
||||
agent_name: &str,
|
||||
token: &str,
|
||||
account: Option<&str>,
|
||||
homeserver: Option<&str>,
|
||||
) -> Result<(String, String)> {
|
||||
validate_agent_name(agent_name)?;
|
||||
let filename = match account {
|
||||
None => "matrix-token".to_owned(),
|
||||
Some(a) => {
|
||||
validate_name_chars(a)?;
|
||||
format!("matrix-token-{a}")
|
||||
}
|
||||
};
|
||||
let res = write_agent_state_file(agent_name, &filename, &format!("{token}\n"))?;
|
||||
if let (Some(a), Some(hs)) = (account, homeserver) {
|
||||
let meta = serde_json::to_string(&MatrixAccountSidecar { homeserver: hs })
|
||||
.context("serialize matrix account sidecar")?;
|
||||
write_agent_state_file(agent_name, &format!("matrix-account-{a}.json"), &meta)?;
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// `WriteAgentExtraForgeAccount`: writes the token, then a
|
||||
/// `forge-<label>.json` sidecar carrying the base URL — there's no
|
||||
/// host-side nix config for extra forges, so this is the only place it's
|
||||
/// persisted.
|
||||
fn write_extra_forge_account(
|
||||
agent_name: &str,
|
||||
label: &str,
|
||||
base_url: &str,
|
||||
token: &str,
|
||||
) -> Result<(String, String)> {
|
||||
validate_agent_name(agent_name)?;
|
||||
validate_name_chars(label)?;
|
||||
let res = write_agent_state_file(
|
||||
agent_name,
|
||||
&format!("forge-{label}-token"),
|
||||
&format!("{token}\n"),
|
||||
)?;
|
||||
let meta = serde_json::to_string(&ForgeSidecar { base_url })
|
||||
.context("serialize forge account sidecar")?;
|
||||
write_agent_state_file(agent_name, &format!("forge-{label}.json"), &meta)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Shared body for `CreateContainer` / `UpdateContainer`: validate the
|
||||
/// name, build the toplevel ourselves, and run
|
||||
/// `nixos-container <verb> … --system-path <built>` (streaming line
|
||||
|
|
|
|||
Loading…
Reference in a new issue