config PRs: remove the hive's config-PR webhook, poll and core merge
An operator's merge on the forge deploys a config PR through
swarm-controller's DeployRequest{rev}. The hive-side path that queued a
MergeConfigPr approval and merged the PR as `core` goes:
- the `/webhook/config-pr` receiver, its HMAC secret, the WebhookRegister
boot node and the org-hook registration; the hive vhost's `/webhook/`
location
- the 5-minute config-PR poll
- ApprovalKind::MergeConfigPr, its dashboard card, and the deploy DAG it
drove (DeployWindow, MergeVerify, DeployApply, FinalizeDeploy,
DeployTail), with verify_commit, the two-phase meta deploy, rollback
refs, the PR-failure comment and forge/pr_merge.rs
- `fetched_sha`, `sha_short`/`pr_number` on approval events, and
`sha`/`tag` on HelperEvent::ApprovalResolved: only the merge path set
them
`config_repo`, `merged_pr_for_commit` and `post_pr_comment` move to
forge/pr_comment.rs for the merged-rev deploy's refusal comment.
Approvals v5 drops stored `merge_config_pr` rows; a test reopens a v4
database holding them.
Closes #4850
This commit is contained in:
parent
a5ea015bc6
commit
efbfec6d01
39 changed files with 286 additions and 3199 deletions
|
|
@ -121,24 +121,13 @@ pub(super) async fn run_node(
|
|||
// takes it directly instead of re-matching the kind behind a `bail!`
|
||||
// that could never fire.
|
||||
NodeKind::WritePermFile { payload, .. } => run_write_perm_file(coord, agent, payload).await,
|
||||
NodeKind::MergeVerify { approval_id, .. } => {
|
||||
run_merge_verify(coord, *approval_id, id).await
|
||||
}
|
||||
NodeKind::DeployApply { approval_id, .. } => {
|
||||
run_deploy_apply(coord, *approval_id, id).await.map(|()| {
|
||||
super::templates::deploy_rebuild_nodes(&builder, agent, *approval_id);
|
||||
})
|
||||
}
|
||||
NodeKind::FinalizeDeploy { approval_id, .. } => {
|
||||
run_finalize_deploy(coord, *approval_id).await
|
||||
}
|
||||
NodeKind::DeployTail { approval_id, .. } => {
|
||||
run_deploy_tail(coord, coord.job_queue.root_of(id), agent, *approval_id).await
|
||||
}
|
||||
NodeKind::ResolveApproval {
|
||||
approval_id,
|
||||
outcome,
|
||||
} => run_resolve_approval(coord, coord.job_queue.root_of(id), *approval_id, *outcome).await,
|
||||
} => {
|
||||
run_resolve_approval(coord, coord.job_queue.root_of(id), *approval_id, *outcome);
|
||||
Ok(())
|
||||
}
|
||||
NodeKind::EmitRebuilt { ok, .. } => {
|
||||
run_emit_rebuilt(coord, agent, coord.job_queue.root_of(id), *ok).await;
|
||||
Ok(())
|
||||
|
|
@ -147,10 +136,9 @@ pub(super) async fn run_node(
|
|||
// Braces carry no work of their own; completing one lets it reach
|
||||
// `Finishing` so the nodes under it start. What they declare stays held
|
||||
// until their whole subtree settles.
|
||||
NodeKind::DeployWindow { .. } | NodeKind::AgentWindow { .. } => Ok(()),
|
||||
NodeKind::AgentWindow { .. } => Ok(()),
|
||||
NodeKind::ForgeSweep => run_forge_sweep().await,
|
||||
NodeKind::MatrixSweep => run_matrix_sweep().await,
|
||||
NodeKind::WebhookRegister => run_webhook_register().await,
|
||||
NodeKind::KnowledgePull => run_knowledge_pull(coord).await,
|
||||
NodeKind::WantedPull => run_wanted_pull(coord).await,
|
||||
};
|
||||
|
|
@ -218,34 +206,6 @@ fn matrix_sweep_banner(ctx: sweep_health::SweepFailure) -> String {
|
|||
)
|
||||
}
|
||||
|
||||
/// Boot-time Forgejo webhook management as a DAG node — see
|
||||
/// [`NodeKind::WebhookRegister`]. Mirrors the guard chain the
|
||||
/// `tokio::spawn` block it replaced used: no-op (not an error) when the
|
||||
/// HMAC secret, core token, or hive domain aren't available yet.
|
||||
///
|
||||
/// Registers the config-PR hook only. The knowledge hook is the swarm
|
||||
/// controller's, which addresses an event to each hive over the queue.
|
||||
async fn run_webhook_register() -> Result<()> {
|
||||
let Ok(webhook_secret) = crate::webhook_secret::load_or_generate() else {
|
||||
tracing::debug!("webhook secret unavailable; skipping hook registration");
|
||||
return Ok(());
|
||||
};
|
||||
let Some(token) = crate::forge::core_token() else {
|
||||
return Ok(());
|
||||
};
|
||||
let domain = std::env::var("HYPERHIVE_HIVE_DOMAIN")
|
||||
.ok()
|
||||
.filter(|v| !v.is_empty());
|
||||
let Some(domain) = domain else {
|
||||
tracing::debug!("HYPERHIVE_HIVE_DOMAIN unset; skipping webhook registration");
|
||||
return Ok(());
|
||||
};
|
||||
if let Err(e) = crate::forge::ensure_config_pr_webhook(&token, &domain, &webhook_secret).await {
|
||||
tracing::warn!(error = ?e, "forge: ensure_config_pr_webhook failed");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `/knowledge` pull as a DAG node — see [`NodeKind::KnowledgePull`]. Every
|
||||
/// pass reports here, whoever submitted it: as the node's own error on the
|
||||
/// dashboard, and into the debounced banner.
|
||||
|
|
@ -313,17 +273,16 @@ async fn run_wanted_pull(coord: &Arc<Coordinator>) -> Result<()> {
|
|||
/// answer. Best-effort — a resolution failure is logged inside
|
||||
/// [`crate::actions::resolve_approval_dag`], never surfaced as a node failure,
|
||||
/// since the work already happened and failing the tail would only misreport it.
|
||||
async fn run_resolve_approval(
|
||||
fn run_resolve_approval(
|
||||
coord: &Arc<Coordinator>,
|
||||
dag_id: Option<u64>,
|
||||
approval_id: i64,
|
||||
outcome: TerminalState,
|
||||
) -> Result<()> {
|
||||
) {
|
||||
let reason = (outcome == TerminalState::Failed)
|
||||
.then(|| dag_id.and_then(|dag| coord.job_queue.first_error(dag)))
|
||||
.flatten();
|
||||
crate::actions::resolve_approval_dag(coord, approval_id, outcome, reason.as_deref()).await;
|
||||
Ok(())
|
||||
crate::actions::resolve_approval_dag(coord, approval_id, outcome, reason.as_deref());
|
||||
}
|
||||
|
||||
/// Rerender the meta flake from whatever containers still exist on disk.
|
||||
|
|
@ -629,9 +588,8 @@ async fn run_meta_lock(
|
|||
// against whatever `applied/<name>` already happened to be locked to —
|
||||
// normally current, but silently stale forever if a past deploy failed
|
||||
// and nothing since retried it. Relocks against each agent's *declared*
|
||||
// input (the forge URL, not the local `applied/<name>` mirror
|
||||
// `prepare_deploy` uses for a reviewed deploy) — this path has no PR to
|
||||
// review, so there's nothing to gate. One combined call, not per-agent:
|
||||
// input (the forge URL, not the local `applied/<name>` mirror a rebuild
|
||||
// relocks against) — this path has nothing to gate. One combined call, not per-agent:
|
||||
// simpler, at the cost of one unreachable/broken agent repo failing the
|
||||
// whole cascade relock rather than just that agent.
|
||||
if !cascade.is_empty() {
|
||||
|
|
@ -890,50 +848,6 @@ async fn run_write_perm_file(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Deploy phase 1 — drift gate, fetch, eval-verify. Mutates nothing, so a
|
||||
/// failure here cancel-cascades the rest of the subtree with the forge and the
|
||||
/// applied repo exactly as they were.
|
||||
async fn run_merge_verify(coord: &Arc<Coordinator>, approval_id: i64, id: NodeId) -> Result<()> {
|
||||
crate::actions::run_deploy_merge_verify(coord, approval_id, Some(id.get())).await
|
||||
}
|
||||
|
||||
/// Deploy phase 2 — the irreversible half: ff-merge, then phase 1 of the
|
||||
/// two-phase meta deploy.
|
||||
///
|
||||
/// On success it grows the ordinary rebuild subgraph (plus its closing
|
||||
/// `FinalizeDeploy`) into this DAG rooted on *this* node — which is what puts
|
||||
/// the appended nodes inside the `DeployWindow`'s subtree, so the `MetaWindow`
|
||||
/// their `MetaSync` declares is re-entered rather than deadlocked against the
|
||||
/// ancestor already holding it. On failure nothing is appended and the tail
|
||||
/// compensates, exactly as before.
|
||||
async fn run_deploy_apply(coord: &Arc<Coordinator>, approval_id: i64, id: NodeId) -> Result<()> {
|
||||
crate::actions::run_deploy_apply(coord, approval_id, Some(id.get())).await
|
||||
}
|
||||
|
||||
/// Deploy phase 3 — close the staged-lock window once the appended rebuild has
|
||||
/// come up clean: drop the rollback ref, plant the `deployed/<id>` tag, commit
|
||||
/// the staged lock.
|
||||
async fn run_finalize_deploy(coord: &Arc<Coordinator>, approval_id: i64) -> Result<()> {
|
||||
crate::actions::run_finalize_deploy(coord, approval_id).await
|
||||
}
|
||||
|
||||
/// Deploy compensation + bookkeeping tail. `AfterAny` the apply node, so it
|
||||
/// runs on every outcome; it is deliberately infallible (see
|
||||
/// [`crate::actions::run_deploy_tail`]) — a failing tail must not flip an
|
||||
/// otherwise-successful deploy's DAG state.
|
||||
///
|
||||
/// Takes the agent from the node payload so the tail can still compensate when
|
||||
/// the approval row is gone (deny race, purge).
|
||||
async fn run_deploy_tail(
|
||||
coord: &Arc<Coordinator>,
|
||||
dag_id: Option<u64>,
|
||||
agent: &str,
|
||||
approval_id: i64,
|
||||
) -> Result<()> {
|
||||
crate::actions::run_deploy_tail(coord, dag_id, agent, approval_id).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute which agents a `nix flake update <inputs>` on the meta
|
||||
/// flake affects — the fan-out set for `MetaUpdate` DAGs. Empty
|
||||
/// `inputs` or any input under `hyperhive` → every container;
|
||||
|
|
|
|||
Loading…
Reference in a new issue