meta: non-mutating verify_commit + pre-flight eval before apply
This commit is contained in:
parent
2b4e928afe
commit
4beaeea7b3
2 changed files with 84 additions and 0 deletions
|
|
@ -493,6 +493,37 @@ async fn run_apply_commit(
|
|||
}
|
||||
};
|
||||
|
||||
// Pre-flight eval-verify the proposal commit against the meta flake
|
||||
// WITHOUT mutating applied/main or the meta lock, so an evaluation
|
||||
// error (bad nix, missing module option, unresolvable lock) fails
|
||||
// fast here instead of after we've fast-forwarded main and have to
|
||||
// roll it back. Skipped on first spawn: the agent has no
|
||||
// `agent-<name>` meta input to override yet (sync_agents adds it
|
||||
// below). This is the reusable verify primitive the PR-based config
|
||||
// flow gates its irreversible ff-push on.
|
||||
if !is_first_spawn {
|
||||
let proposal_sha = match lifecycle::git_rev_parse(applied_dir, &proposal_ref).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return (
|
||||
Err(anyhow::anyhow!("rev-parse {proposal_ref}: {e:#}")),
|
||||
None,
|
||||
is_first_spawn,
|
||||
);
|
||||
}
|
||||
};
|
||||
coord.set_queue_step(queue_entry_id, "verify proposal (eval)");
|
||||
if let Err(e) =
|
||||
crate::meta::verify_commit(&approval.agent, applied_dir, &proposal_sha).await
|
||||
{
|
||||
return (
|
||||
Err(anyhow::anyhow!("verify proposal {proposal_ref}: {e:#}")),
|
||||
None,
|
||||
is_first_spawn,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
coord.set_queue_step(queue_entry_id, "plant tags");
|
||||
if let Err(e) = lifecycle::git_tag(applied_dir, &format!("approved/{id}"), &proposal_ref).await
|
||||
{
|
||||
|
|
|
|||
|
|
@ -287,6 +287,50 @@ pub async fn lock_update_for_rebuild(name: &str) -> Result<()> {
|
|||
git_commit(&dir, &format!("rebuild {name}: lock update")).await
|
||||
}
|
||||
|
||||
/// Build the `--override-input` value pinning an agent's config repo to
|
||||
/// an exact revision: `git+file://<applied_dir>?rev=<sha>`. Pure so the
|
||||
/// URL shape is unit-testable without a nix shellout.
|
||||
fn agent_input_override(applied_dir: &Path, sha: &str) -> String {
|
||||
format!("git+file://{}?rev={sha}", applied_dir.display())
|
||||
}
|
||||
|
||||
/// Non-mutating "would this commit apply?" verify for the PR-based config
|
||||
/// flow. Evaluates the agent's nixos configuration with its meta
|
||||
/// input overridden to the exact `sha`, WITHOUT moving `applied/main` or
|
||||
/// writing the real meta `flake.lock`: `--override-input` pins the input
|
||||
/// at eval time only and `--no-write-lock-file` guarantees the on-disk
|
||||
/// lock is never touched (a genuinely-needed lock change surfaces as an
|
||||
/// error rather than a silent mutation). Confirms the flake at `sha`
|
||||
/// evaluates and the meta lock resolves around it. `Ok(())` = would
|
||||
/// apply; `Err` carries the eval/lock failure so the operator's approve
|
||||
/// is rejected before any live-state change.
|
||||
///
|
||||
/// Precondition: `sha` must be reachable in `applied_dir`'s object store
|
||||
/// — the caller fetches the PR head into `applied_dir` first. Eval-only
|
||||
/// (no build): catches nix / eval / module-option errors + lock
|
||||
/// resolution at the same cost profile as the legacy pre-merge check;
|
||||
/// the real container build still runs (and can still roll back) on the
|
||||
/// actual apply, so this is the right "would this apply" gate.
|
||||
pub async fn verify_commit(name: &str, applied_dir: &Path, sha: &str) -> Result<()> {
|
||||
let _guard = META_LOCK.lock().await;
|
||||
let dir = meta_dir();
|
||||
let input = format!("agent-{name}");
|
||||
let over = agent_input_override(applied_dir, sha);
|
||||
let attr = format!(".#nixosConfigurations.{name}.config.system.build.toplevel.drvPath");
|
||||
nix(
|
||||
&dir,
|
||||
&[
|
||||
"eval",
|
||||
&attr,
|
||||
"--override-input",
|
||||
&input,
|
||||
&over,
|
||||
"--no-write-lock-file",
|
||||
],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Update one or more named inputs in the meta flake and commit
|
||||
/// the resulting lock change with a single combined message.
|
||||
/// Used by the dashboard's "update meta inputs" form so the
|
||||
|
|
@ -1131,6 +1175,15 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agent_input_override_pins_exact_rev() {
|
||||
let p = Path::new("/var/lib/hyperhive/agents/iris/applied");
|
||||
assert_eq!(
|
||||
agent_input_override(p, "abc123def"),
|
||||
"git+file:///var/lib/hyperhive/agents/iris/applied?rev=abc123def"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_flake_uses_explicit_nixpkgs_url_when_provided() {
|
||||
let out = render_flake(
|
||||
|
|
|
|||
Loading…
Reference in a new issue