forge: replace curl shell-outs with reqwest http helper (closes #249)
This commit is contained in:
parent
5c360e8293
commit
b283768f26
2 changed files with 34 additions and 52 deletions
|
|
@ -9,6 +9,7 @@ workspace = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
|
reqwest.workspace = true
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
hive-fr0nt.workspace = true
|
hive-fr0nt.workspace = true
|
||||||
hive-sh4re.workspace = true
|
hive-sh4re.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use reqwest::StatusCode;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::coordinator::Coordinator;
|
use crate::coordinator::Coordinator;
|
||||||
|
|
@ -142,6 +143,29 @@ fn agent_email(name: &str) -> String {
|
||||||
format!("{name}@hyperhive")
|
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
|
/// Ensure a forgejo user named `name` exists. Idempotent: forgejo
|
||||||
/// returns a "user already exists" error which we treat as success.
|
/// returns a "user already exists" error which we treat as success.
|
||||||
/// `admin` adds `--admin` (site admin) — used for the bootstrap
|
/// `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
|
/// (HTTP 409 / 422) into success. `label` is `<owner>/<name>` — purely
|
||||||
/// for log + error context.
|
/// for log + error context.
|
||||||
async fn create_repo(url: &str, body: &str, token: &str, label: &str) -> Result<()> {
|
async fn create_repo(url: &str, body: &str, token: &str, label: &str) -> Result<()> {
|
||||||
let out = Command::new("curl")
|
let status = forge_http(reqwest::Method::POST, url, token, body).await?;
|
||||||
.args([
|
match status.as_u16() {
|
||||||
"-sS",
|
201 => {
|
||||||
"-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" => {
|
|
||||||
tracing::info!(%label, "forge: created repo");
|
tracing::info!(%label, "forge: created repo");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
"409" | "422" => {
|
409 | 422 => {
|
||||||
tracing::debug!(%label, "forge: repo already exists");
|
tracing::debug!(%label, "forge: repo already exists");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -501,40 +505,17 @@ pub async fn push_config(name: &str) -> Result<()> {
|
||||||
async fn ensure_org(name: &str, admin_token: &str) -> Result<()> {
|
async fn ensure_org(name: &str, admin_token: &str) -> Result<()> {
|
||||||
let body = format!(r#"{{"username":"{name}"}}"#);
|
let body = format!(r#"{{"username":"{name}"}}"#);
|
||||||
let url = format!("{FORGE_HTTP}/api/v1/orgs");
|
let url = format!("{FORGE_HTTP}/api/v1/orgs");
|
||||||
let out = Command::new("curl")
|
let status = forge_http(reqwest::Method::POST, &url, admin_token, &body).await?;
|
||||||
.args([
|
match status.as_u16() {
|
||||||
"-sS",
|
201 => {
|
||||||
"-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" => {
|
|
||||||
tracing::info!(%name, "forge: created org");
|
tracing::info!(%name, "forge: created org");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
"422" | "409" => {
|
422 | 409 => {
|
||||||
tracing::debug!(%name, "forge: org already exists");
|
tracing::debug!(%name, "forge: org already exists");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
other => anyhow::bail!(
|
other => anyhow::bail!("POST /api/v1/orgs name={name} returned HTTP {other}"),
|
||||||
"POST /api/v1/orgs name={name} returned HTTP {other}: {}",
|
|
||||||
String::from_utf8_lossy(&out.stderr).trim()
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue