hive-forge repo-create: say the account can't create repos instead of a bare 500
This commit is contained in:
parent
5b319e0600
commit
6ec12e7d84
2 changed files with 83 additions and 19 deletions
|
|
@ -110,7 +110,7 @@ enum Verb {
|
|||
/// credentials autoinjected. Pairs with `pr create --agit`.
|
||||
Clone(verbs::clone::Args),
|
||||
/// Create a forge repo under the current user (or `--org`). Prints
|
||||
/// the repo URL.
|
||||
/// the repo URL. Agent accounts can't create repos (limit 0).
|
||||
RepoCreate(verbs::repo_create::Args),
|
||||
/// Add a collaborator to the active repo (`-r`/`HIVE_FORGE_REPO`)
|
||||
/// with a permission level. Companion to `repo-create`.
|
||||
|
|
|
|||
|
|
@ -3,15 +3,19 @@
|
|||
//! user (or under `--org`). Prints the new repo's web URL (or the full repo
|
||||
//! object under `--json`).
|
||||
//!
|
||||
//! Wraps `POST /api/v1/user/repos` (or `/orgs/{org}/repos`) so agents don't
|
||||
//! reach for raw API calls when the operator greenlights a new repo — the
|
||||
//! instance has push-to-create disabled, so `git push` to a non-existent
|
||||
//! repo 403s. Pairs with `clone` (then push your content) and
|
||||
//! `repo-add-collaborator` (add peers to a fresh agent-namespace repo).
|
||||
//! Wraps `POST /api/v1/user/repos` (or `/orgs/{org}/repos`). For accounts
|
||||
//! that may create repos (operators, non-agent users). Agent accounts
|
||||
//! cannot: swarm-controller sets their repo-creation limit to 0, and
|
||||
//! Forgejo reports that as a bare 500 on the user path, which
|
||||
//! `explain_create_error` turns into a message naming the operator path.
|
||||
//! The instance has push-to-create disabled, so `git push` to a
|
||||
//! non-existent repo 403s. Pairs with `clone` and `repo-add-collaborator`.
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Args as ClapArgs;
|
||||
use forgejo_api::ForgejoError;
|
||||
use forgejo_api::structs::CreateRepoOption;
|
||||
use reqwest::StatusCode;
|
||||
|
||||
use crate::client::Client;
|
||||
use crate::verbs::print_json;
|
||||
|
|
@ -40,6 +44,25 @@ pub struct Args {
|
|||
auto_init: bool,
|
||||
}
|
||||
|
||||
/// Forgejo reports "user has reached maximum limit of repositories" as a
|
||||
/// 500 the client keeps no body for, and `GET /user` does not expose
|
||||
/// `max_repo_creation`, so a 500 on the user path is matched as "most
|
||||
/// likely the creation limit". Every other error, and the org path,
|
||||
/// passes through unchanged.
|
||||
fn explain_create_error(org: Option<&str>, err: ForgejoError) -> anyhow::Error {
|
||||
let at_limit = org.is_none()
|
||||
&& matches!(&err, ForgejoError::UnexpectedStatusCode(s) if *s == StatusCode::INTERNAL_SERVER_ERROR);
|
||||
if !at_limit {
|
||||
return err.into();
|
||||
}
|
||||
anyhow::anyhow!(
|
||||
"this account most likely cannot create repositories (the forge answered 500, which is \
|
||||
how Forgejo reports a repo-creation limit of 0). On hyperhive, agent accounts are \
|
||||
locked to 0 by design, so an operator merge stays the only path to main. Ask the \
|
||||
operator to create the repo (for example `agents/<name>`) and add you as a collaborator."
|
||||
)
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
///
|
||||
/// Propagates any transport error from the Forgejo REST call (network
|
||||
|
|
@ -62,10 +85,12 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
|||
trust_model: None,
|
||||
};
|
||||
|
||||
let resp = match args.org.as_deref() {
|
||||
Some(org) => client.api().create_org_repo(org, payload).send()?,
|
||||
None => client.api().create_current_user_repo(payload).send()?,
|
||||
};
|
||||
let org = args.org.as_deref();
|
||||
let resp = match org {
|
||||
Some(org) => client.api().create_org_repo(org, payload).send(),
|
||||
None => client.api().create_current_user_repo(payload).send(),
|
||||
}
|
||||
.map_err(|e| explain_create_error(org, e))?;
|
||||
|
||||
if client.json_mode() {
|
||||
return print_json(&serde_json::to_value(&resp)?);
|
||||
|
|
@ -76,3 +101,40 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn status(code: StatusCode) -> ForgejoError {
|
||||
ForgejoError::UnexpectedStatusCode(code)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_path_500_names_the_limit_and_the_operator_path() {
|
||||
let msg = explain_create_error(None, status(StatusCode::INTERNAL_SERVER_ERROR)).to_string();
|
||||
assert!(msg.contains("cannot create repositories"), "{msg}");
|
||||
assert!(msg.contains("locked to 0"), "{msg}");
|
||||
assert!(msg.contains("add you as a collaborator"), "{msg}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn other_user_path_errors_pass_through() {
|
||||
for code in [
|
||||
StatusCode::FORBIDDEN,
|
||||
StatusCode::CONFLICT,
|
||||
StatusCode::BAD_GATEWAY,
|
||||
] {
|
||||
let msg = explain_create_error(None, status(code)).to_string();
|
||||
assert_eq!(msg, status(code).to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn org_path_errors_pass_through() {
|
||||
for code in [StatusCode::FORBIDDEN, StatusCode::INTERNAL_SERVER_ERROR] {
|
||||
let msg = explain_create_error(Some("acme"), status(code)).to_string();
|
||||
assert_eq!(msg, status(code).to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue