forge: replace curl shell-outs with reqwest http helper (closes #249)

This commit is contained in:
damocles 2026-05-22 15:37:30 +02:00 committed by Mara
parent 5c360e8293
commit b283768f26
2 changed files with 34 additions and 52 deletions

View file

@ -9,6 +9,7 @@ workspace = true
[dependencies]
anyhow.workspace = true
axum.workspace = true
reqwest.workspace = true
clap.workspace = true
hive-fr0nt.workspace = true
hive-sh4re.workspace = true

View file

@ -20,6 +20,7 @@
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use reqwest::StatusCode;
use tokio::process::Command;
use crate::coordinator::Coordinator;
@ -142,6 +143,29 @@ fn agent_email(name: &str) -> String {
format!("{name}@hyperhive")
}
/// Thin Forgejo REST helper. Sends `method` to `url` with a JSON body
/// and `Authorization: token <token>`, returns the HTTP status code.
/// All Forgejo API calls that don't shell out to `forgejo admin` go
/// through here — one place for auth header, content-type, error
/// propagation, and the shared reqwest Client.
async fn forge_http(
method: reqwest::Method,
url: &str,
token: &str,
body: &str,
) -> Result<StatusCode> {
let client = reqwest::Client::new();
let resp = client
.request(method, url)
.header("Authorization", format!("token {token}"))
.header("Content-Type", "application/json")
.body(body.to_owned())
.send()
.await
.with_context(|| format!("forge HTTP request to {url}"))?;
Ok(resp.status())
}
/// Ensure a forgejo user named `name` exists. Idempotent: forgejo
/// returns a "user already exists" error which we treat as success.
/// `admin` adds `--admin` (site admin) — used for the bootstrap
@ -271,33 +295,13 @@ fn repo_body(name: &str) -> String {
/// (HTTP 409 / 422) into success. `label` is `<owner>/<name>` — purely
/// for log + error context.
async fn create_repo(url: &str, body: &str, token: &str, label: &str) -> Result<()> {
let out = Command::new("curl")
.args([
"-sS",
"-o",
"/dev/null",
"-w",
"%{http_code}",
"-X",
"POST",
"-H",
"Content-Type: application/json",
"-H",
&format!("Authorization: token {token}"),
"-d",
body,
url,
])
.output()
.await
.with_context(|| format!("invoke curl POST {url}"))?;
let code = String::from_utf8_lossy(&out.stdout).trim().to_owned();
match code.as_str() {
"201" => {
let status = forge_http(reqwest::Method::POST, url, token, body).await?;
match status.as_u16() {
201 => {
tracing::info!(%label, "forge: created repo");
Ok(())
}
"409" | "422" => {
409 | 422 => {
tracing::debug!(%label, "forge: repo already exists");
Ok(())
}
@ -501,40 +505,17 @@ pub async fn push_config(name: &str) -> Result<()> {
async fn ensure_org(name: &str, admin_token: &str) -> Result<()> {
let body = format!(r#"{{"username":"{name}"}}"#);
let url = format!("{FORGE_HTTP}/api/v1/orgs");
let out = Command::new("curl")
.args([
"-sS",
"-o",
"/dev/null",
"-w",
"%{http_code}",
"-X",
"POST",
"-H",
"Content-Type: application/json",
"-H",
&format!("Authorization: token {admin_token}"),
"-d",
&body,
&url,
])
.output()
.await
.context("invoke curl POST /api/v1/orgs")?;
let code = String::from_utf8_lossy(&out.stdout).trim().to_owned();
match code.as_str() {
"201" => {
let status = forge_http(reqwest::Method::POST, &url, admin_token, &body).await?;
match status.as_u16() {
201 => {
tracing::info!(%name, "forge: created org");
Ok(())
}
"422" | "409" => {
422 | 409 => {
tracing::debug!(%name, "forge: org already exists");
Ok(())
}
other => anyhow::bail!(
"POST /api/v1/orgs name={name} returned HTTP {other}: {}",
String::from_utf8_lossy(&out.stderr).trim()
),
other => anyhow::bail!("POST /api/v1/orgs name={name} returned HTTP {other}"),
}
}