fix(#2174): create operators team in agent-configs org too so config-repo branch protection applies
This commit is contained in:
parent
cb0a66147a
commit
5d597d92ea
1 changed files with 27 additions and 14 deletions
|
|
@ -1059,29 +1059,36 @@ pub fn is_hive_managed_namespace(ns: &str) -> bool {
|
||||||
HIVE_MANAGED_NAMESPACES.contains(&ns)
|
HIVE_MANAGED_NAMESPACES.contains(&ns)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provision the [`OPERATORS_TEAM`] inside [`AGENTS_ORG`] as an **empty**
|
/// Provision the [`OPERATORS_TEAM`] inside `org` as an **empty** team.
|
||||||
/// team. Branch protection on agents-org repos references it as the
|
/// Branch protection on that org's repos references it as the
|
||||||
/// merge/approval whitelist; the operator adds herself as a member via the
|
/// merge/approval whitelist; the operator adds herself as a member via the
|
||||||
/// forge UI / hivectl. `includes_all_repositories` so the gate applies to
|
/// forge UI / hivectl. `includes_all_repositories` so the gate applies to
|
||||||
/// every agent repo; `write` is enough to approve + merge. hive-c0re never
|
/// every repo in the org; `write` is enough to approve + merge. hive-c0re
|
||||||
/// manages membership. Idempotent (422/409 = already exists).
|
/// never manages membership. Idempotent (422/409 = already exists).
|
||||||
async fn ensure_operators_team(token: &str) -> Result<()> {
|
///
|
||||||
let url = format!("{FORGE_HTTP}/api/v1/orgs/{AGENTS_ORG}/teams");
|
/// Must run for BOTH [`AGENTS_ORG`] and [`CONFIG_ORG`]: Gitea teams are
|
||||||
|
/// org-scoped, so a config-repo branch-protection rule referencing
|
||||||
|
/// `operators` needs the team to exist in `agent-configs` too. Missing it
|
||||||
|
/// there 422'd every `apply_config_repo_branch_protection`, leaving config
|
||||||
|
/// repos unprotected — operator-merged config PRs then bypassed the deploy
|
||||||
|
/// pipeline and silently didn't apply.
|
||||||
|
async fn ensure_operators_team(org: &str, token: &str) -> Result<()> {
|
||||||
|
let url = format!("{FORGE_HTTP}/api/v1/orgs/{org}/teams");
|
||||||
let body = format!(
|
let body = format!(
|
||||||
r#"{{"name":"{OPERATORS_TEAM}","description":"hyperhive operators — merge gate for agent repos","permission":"write","includes_all_repositories":true,"can_create_org_repo":false}}"#
|
r#"{{"name":"{OPERATORS_TEAM}","description":"hyperhive operators — merge gate for agent repos","permission":"write","includes_all_repositories":true,"can_create_org_repo":false}}"#
|
||||||
);
|
);
|
||||||
let (status, _) = forge_http(reqwest::Method::POST, &url, token, &body).await?;
|
let (status, _) = forge_http(reqwest::Method::POST, &url, token, &body).await?;
|
||||||
match status.as_u16() {
|
match status.as_u16() {
|
||||||
201 => {
|
201 => {
|
||||||
tracing::info!("forge: created {OPERATORS_TEAM} team in {AGENTS_ORG}");
|
tracing::info!(%org, "forge: created {OPERATORS_TEAM} team");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
409 | 422 => {
|
409 | 422 => {
|
||||||
tracing::debug!("forge: {OPERATORS_TEAM} team already exists");
|
tracing::debug!(%org, "forge: {OPERATORS_TEAM} team already exists");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
anyhow::bail!("POST /orgs/{AGENTS_ORG}/teams ({OPERATORS_TEAM}) returned HTTP {other}")
|
anyhow::bail!("POST /orgs/{org}/teams ({OPERATORS_TEAM}) returned HTTP {other}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1282,11 +1289,17 @@ pub async fn ensure_all() {
|
||||||
// `HYPERHIVE_FORGE_MIRRORS` env). Each ensures its own dest org, so
|
// `HYPERHIVE_FORGE_MIRRORS` env). Each ensures its own dest org, so
|
||||||
// this is independent of the SEEDED_ORGS loop above.
|
// this is independent of the SEEDED_ORGS loop above.
|
||||||
ensure_mirrors(token).await;
|
ensure_mirrors(token).await;
|
||||||
// Provision the operator merge-gate team (empty) inside the agents
|
// Provision the operator merge-gate team (empty) inside BOTH the
|
||||||
// org so branch protection can reference it before anyone joins
|
// agents org and the agent-configs org so branch protection in each
|
||||||
//. The operator adds herself as a member out-of-band.
|
// can reference it before anyone joins. Gitea teams are org-scoped —
|
||||||
if let Err(e) = ensure_operators_team(token).await {
|
// missing the agent-configs copy 422'd every config-repo protection
|
||||||
tracing::warn!(error = ?e, "forge: ensure_operators_team failed");
|
// apply, leaving those repos unprotected and letting operator-merged
|
||||||
|
// config PRs bypass the deploy pipeline. The operator adds herself as
|
||||||
|
// a member out-of-band.
|
||||||
|
for org in [AGENTS_ORG, CONFIG_ORG] {
|
||||||
|
if let Err(e) = ensure_operators_team(org, token).await {
|
||||||
|
tracing::warn!(%org, error = ?e, "forge: ensure_operators_team failed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Meta repo lives at core/meta — pushed from git_commit in
|
// Meta repo lives at core/meta — pushed from git_commit in
|
||||||
// meta.rs on every deploy/lock-update. Make sure it exists
|
// meta.rs on every deploy/lock-update. Make sure it exists
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue