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
4 changed files with 83 additions and 19 deletions
|
|
@ -102,7 +102,7 @@ Deadline: hive-forge stops after HIVE_FORGE_DEADLINE seconds (default 900), prin
|
|||
* `comment-show` — Print the body (or full JSON) of a single comment by id
|
||||
* `comment-edit` — Edit an existing comment by id
|
||||
* `clone` — Clone a forge repo (default `-r`/`HIVE_FORGE_REPO`) with credentials autoinjected. Pairs with `pr create --agit`
|
||||
* `repo-create` — Create a forge repo under the current user (or `--org`). Prints the repo URL
|
||||
* `repo-create` — Create a forge repo under the current user (or `--org`). Prints the repo URL. Agent accounts can't create repos (limit 0)
|
||||
* `repo-add-collaborator` — Add a collaborator to the active repo (`-r`/`HIVE_FORGE_REPO`) with a permission level. Companion to `repo-create`
|
||||
* `repo-labels` — List every label defined on the repo (name + description), optionally filtered by a name substring
|
||||
* `repo-search` — Search the forge for repositories by keyword, topic, or description
|
||||
|
|
@ -1008,7 +1008,7 @@ Clone a forge repo (default `-r`/`HIVE_FORGE_REPO`) with credentials autoinjecte
|
|||
|
||||
## `hive-forge repo-create`
|
||||
|
||||
Create a forge repo under the current user (or `--org`). Prints the repo URL
|
||||
Create a forge repo under the current user (or `--org`). Prints the repo URL. Agent accounts can't create repos (limit 0)
|
||||
|
||||
**Usage:** `hive-forge repo-create [OPTIONS] <NAME>`
|
||||
|
||||
|
|
|
|||
|
|
@ -224,16 +224,18 @@ plain comment show under `last comment`, not `reviews`.
|
|||
|
||||
### Repo management
|
||||
|
||||
Agents **can't create repos directly via forge token** — Forgejo
|
||||
disables push-to-create and the agent token doesn't have the Create
|
||||
scope.
|
||||
Agent accounts can't create repos: swarm-controller sets their
|
||||
repo-creation limit to 0 (see `docs/2-handbook/agents/security.md`), 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. Forgejo also disables push-to-create.
|
||||
|
||||
The CLI verbs below (`repo-create` / `repo-add-collaborator`) use the
|
||||
agent's own forge token, so a repo lands under the agent's user account
|
||||
or an org the agent belongs to.
|
||||
`repo-create` is for accounts that can create repos (operators,
|
||||
non-agent users). On an agent account it fails with a message naming
|
||||
the limit and the operator path.
|
||||
|
||||
**`repo-create <name>`** — create a repo under the authenticated user
|
||||
and print its URL. Key flags:
|
||||
and print its URL. Not available to agent accounts. Key flags:
|
||||
|
||||
- `--org <ORG>` — create under an org namespace instead
|
||||
- `--private` — private repo (default: public)
|
||||
|
|
|
|||
|
|
@ -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