swarm-controller: provision a forge user account before adding it as a collaborator
This commit is contained in:
parent
4de4878e74
commit
32c5973956
3 changed files with 177 additions and 30 deletions
|
|
@ -20,7 +20,8 @@ use forgejo_api::structs::{
|
|||
AddCollaboratorOption, AddCollaboratorOptionPermission, ChangeFileOperation,
|
||||
ChangeFileOperationOperation, ChangeFilesOptions, CreateBranchProtectionOption,
|
||||
CreateHookOption, CreateHookOptionConfig, CreateHookOptionType, CreateRepoOption,
|
||||
RepoGetContentsQuery, RepoListPullRequestsQuery, RepoListPullRequestsQueryState,
|
||||
CreateUserOption, RepoGetContentsQuery, RepoListPullRequestsQuery,
|
||||
RepoListPullRequestsQueryState,
|
||||
};
|
||||
use forgejo_api::{ApiErrorKind, Auth, Forgejo, ForgejoError};
|
||||
use reqwest::StatusCode;
|
||||
|
|
@ -288,6 +289,68 @@ impl Client {
|
|||
.await
|
||||
}
|
||||
|
||||
/// Ensure `agent` exists as a Forgejo user account — the whole job of
|
||||
/// the `CreateForgeUser` node, and the fix for the "user does not
|
||||
/// exist" failure `AddRepoMember` hit before this node existed: adding
|
||||
/// a nonexistent user as a collaborator is a Forgejo validation error,
|
||||
/// not an idempotent no-op, so something has to create the account
|
||||
/// first. Mirrors `hive-c0re::forge::users::ensure_user_exists`'s
|
||||
/// intent (an agent's Forgejo identity is provisioned once, up front,
|
||||
/// authenticates by token thereafter, and its password is never read)
|
||||
/// but not its mechanism: that function shells out to the local
|
||||
/// `forgejo admin` CLI, which assumes co-location with the forge host.
|
||||
/// This daemon has no such assumption — like every other call in this
|
||||
/// file, it only ever talks to the forge over HTTP — so this goes
|
||||
/// through `admin_create_user` instead.
|
||||
///
|
||||
/// The password itself is a throwaway: 32 random bytes, generated once,
|
||||
/// never persisted anywhere, and never needed again (unlike
|
||||
/// `hive-c0re`'s CLI path, which can ask forgejo to `--random-password`
|
||||
/// on its own, the HTTP admin API requires a real value up front — see
|
||||
/// [`crate::webhook::generate_hex_secret`], reused here rather than
|
||||
/// duplicated for the same reason a webhook secret and this password
|
||||
/// are both "32 random bytes nothing reads back").
|
||||
///
|
||||
/// Idempotent: an existing user (409/422) is folded into success, same
|
||||
/// as [`Self::ensure_org_repo`]. Deliberately does not attempt to align
|
||||
/// the account's email or disable its own repo-creation rights the way
|
||||
/// `hive-c0re`'s per-hive provisioning does (`ensure_user_email`,
|
||||
/// `ensure_repo_creation_disabled`) — this account only exists so
|
||||
/// `AddRepoMember` has something to add, and the agent's owning hive
|
||||
/// still runs its own full provisioning pass once the agent actually
|
||||
/// spawns there, which self-heals both of those.
|
||||
pub async fn ensure_agent_user(&self, agent: &str) -> Result<()> {
|
||||
let password = crate::webhook::generate_hex_secret()
|
||||
.context("generating a throwaway password for the agent's forge account")?;
|
||||
let res = self
|
||||
.api
|
||||
.admin_create_user(CreateUserOption {
|
||||
created_at: None,
|
||||
email: format!("{agent}@hyperhive.local"),
|
||||
full_name: None,
|
||||
login_name: None,
|
||||
must_change_password: Some(false),
|
||||
password: Some(password),
|
||||
restricted: None,
|
||||
send_notify: None,
|
||||
source_id: None,
|
||||
username: agent.to_owned(),
|
||||
visibility: None,
|
||||
})
|
||||
.await;
|
||||
match res {
|
||||
Ok(_) => {
|
||||
tracing::info!(%agent, "swarm forge: created agent forge user");
|
||||
Ok(())
|
||||
}
|
||||
Err(e) if is_already_exists(&e) => {
|
||||
tracing::debug!(%agent, "swarm forge: agent forge user already exists");
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e).with_context(|| format!("create forge user {agent}")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Seed `repo` with the two files every agent config repo needs:
|
||||
/// `agent.nix` (the agent's own module) and `flake.nix` (the
|
||||
/// boilerplate that lets the meta flake import this repo as a flake
|
||||
|
|
|
|||
Loading…
Reference in a new issue