pub struct ForgejoForge { /* private fields */ }Expand description
The Forgejo adapter: one bot user on one Forgejo (or Gitea) instance.
Holds the bot’s token (swappable, for rotation), the OAuth client secret,
the webhook secret, and the namespaces the core has bound. Build it with
ForgejoForge::connect, which probes the instance’s version and
confirms the token is the bot’s.
Implementations§
Source§impl ForgejoForge
impl ForgejoForge
Sourcepub async fn connect(
config: ForgejoConfig,
credentials: Credentials,
) -> Result<Self>
pub async fn connect( config: ForgejoConfig, credentials: Credentials, ) -> Result<Self>
Connect to config’s instance: probe /api/v1/version (switching off
what the instance lacks), confirm credentials.bot_token belongs to
config.bot_login, and — in the signing-key merge fallback on an
instance without fast-forward-only merges — fetch the instance’s
signing key for the plan.
Sourcepub fn config(&self) -> &ForgejoConfig
pub fn config(&self) -> &ForgejoConfig
The configuration.
Sourcepub fn instance(&self) -> InstanceInfo
pub fn instance(&self) -> InstanceInfo
What the last probe found.
Sourcepub fn bot(&self) -> ForgeAccount
pub fn bot(&self) -> ForgeAccount
The bot’s account.
Sourcepub async fn refresh(&self) -> Result<InstanceInfo>
pub async fn refresh(&self) -> Result<InstanceInfo>
Probe the instance again (after an upgrade, say).
Sourcepub fn register_namespace(&self, ns: Namespace) -> Result<()>
pub fn register_namespace(&self, ns: Namespace) -> Result<()>
Tell the adapter about a bound namespace (from the VTC’s store).
Operations on repositories in a namespace never registered are
refused with ForgeError::NotBound.
Sourcepub fn unregister_namespace(&self, ns: &Resource)
pub fn unregister_namespace(&self, ns: &Resource)
Forget a namespace (unbind).
Sourcepub fn new_state() -> Result<String>
pub fn new_state() -> Result<String>
A fresh bind state nonce: 256 bits from the system CSPRNG,
base64url. The caller stores it with its expiry and hands it back to
Forge::complete_bind.
Sourcepub async fn fetch_signing_key(&self) -> Result<Vec<u8>>
pub async fn fetch_signing_key(&self) -> Result<Vec<u8>>
The instance’s merge-signing public key (/api/v1/signing-key.gpg).
Sourcepub async fn replace_token(&self, new: Secret) -> Result<()>
pub async fn replace_token(&self, new: Secret) -> Result<()>
Swap in a token an operator minted (manual rotation). It is checked to be the bot’s before it replaces the current one; the old token is not deleted — that is the operator’s to do.
Sourcepub async fn mint_token(&self) -> Result<MintedToken>
pub async fn mint_token(&self) -> Result<MintedToken>
Rotation, phase 1: mint a new bot token (basic auth with the bot’s
password), verify it is the bot’s, and put it in use. Needs
TokenRotation::WithPassword.
The new secret is returned: persist it (sealed) before calling
ForgejoForge::retire_token, or a restart after the old token is
deleted comes back with a dead credential. Nothing is deleted here —
other bridge replicas using the old token keep working until the
caller has distributed the new one and retires the old. The token it
replaced is identified before anything is minted (by the id this
adapter recorded when it minted it, or else by its last eight
characters in the bot’s token list), so once the new token is in use
nothing is left that can fail.
Sourcepub async fn retire_token(&self, old: &TokenRef) -> Result<()>
pub async fn retire_token(&self, old: &TokenRef) -> Result<()>
Rotation, phase 2: delete a token this bridge replaced — exactly the one named, never a pattern, so another bridge’s (or a person’s) tokens on the same bot are never touched. Refuses the token in use. A token already gone is not an error.
Sourcepub async fn refresh_managed_files(
&self,
repo: &Resource,
files: &[ExtraFile],
message: &str,
) -> Result<RefreshReport>
pub async fn refresh_managed_files( &self, repo: &Resource, files: &[ExtraFile], message: &str, ) -> Result<RefreshReport>
The Forgejo job for StepAction::RefreshProtectedFiles: the one
sanctioned way the bridge changes a protected path (the managed
workflow, the keyring) after bootstrap.
If every file already matches, nothing is touched. Otherwise the
managed rule is opened for the bot alone — pushes enabled with a push
allow-list of just the bot, the protected-file patterns cleared,
since Forgejo refuses protected files even to an allowed pusher —
the files are written, and the rule’s exact prior push and
protected-file settings are restored and read back. The restore is
attempted (twice) whatever happened to the writes. While open, an
inspect reports the bot as a bypass actor and the paths as
unprotected: critical drift, so a restore that failed is re-applied
by the next sweep’s protection step.
Sourcepub fn refresh_plan(
&self,
repo: &RepoSpec,
cfg: &VgiConfig,
) -> Result<Vec<BootstrapStep>>
pub fn refresh_plan( &self, repo: &RepoSpec, cfg: &VgiConfig, ) -> Result<Vec<BootstrapStep>>
The single maintenance step that brings the managed workflow (and, in
the signing-key fallback, the keyring) up to date on a bootstrapped
repository — see ForgejoForge::refresh_managed_files.
Trait Implementations§
Source§impl Debug for ForgejoForge
impl Debug for ForgejoForge
Source§impl Forge for ForgejoForge
impl Forge for ForgejoForge
Source§fn diff(&self, observed: &RepoState, desired: &Projection) -> Vec<Drift>
fn diff(&self, observed: &RepoState, desired: &Projection) -> Vec<Drift>
The neutral comparison, with the check named as Forgejo reports it
(<workflow> / <job> (pull_request)), plus what makes the check mean
something on Forgejo: the protected workflow paths, fast-forward-only
merges, and Actions being on.
Source§fn host(&self) -> &str
fn host(&self) -> &str
github.com, a GHES host,
codeberg.org). Every resource it accepts starts with it; a
resource on another host is refused rather than sent to the wrong
forge.Source§fn capabilities(&self, ns: &Namespace) -> Capabilities
fn capabilities(&self, ns: &Namespace) -> Capabilities
Forge::kind.Source§fn begin_bind<'life0, 'async_trait>(
&'life0 self,
req: BindRequest,
) -> Pin<Box<dyn Future<Output = Result<BindStep>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn begin_bind<'life0, 'async_trait>(
&'life0 self,
req: BindRequest,
) -> Pin<Box<dyn Future<Output = Result<BindStep>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Source§fn complete_bind<'life0, 'async_trait>(
&'life0 self,
cb: BindCallback,
) -> Pin<Box<dyn Future<Output = Result<NamespaceBinding>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn complete_bind<'life0, 'async_trait>(
&'life0 self,
cb: BindCallback,
) -> Pin<Box<dyn Future<Output = Result<NamespaceBinding>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Source§fn begin_account_link<'life0, 'life1, 'async_trait>(
&'life0 self,
member: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<LinkStep>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn begin_account_link<'life0, 'life1, 'async_trait>(
&'life0 self,
member: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<LinkStep>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
member is their DID, for
the adapter’s audit trail; nothing forge-side sees it.Source§fn complete_account_link<'life0, 'async_trait>(
&'life0 self,
cb: LinkCallback,
) -> Pin<Box<dyn Future<Output = Result<ForgeAccount>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn complete_account_link<'life0, 'async_trait>(
&'life0 self,
cb: LinkCallback,
) -> Pin<Box<dyn Future<Output = Result<ForgeAccount>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Source§fn inspect<'life0, 'life1, 'async_trait>(
&'life0 self,
repo: &'life1 Resource,
) -> Pin<Box<dyn Future<Output = Result<RepoState>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn inspect<'life0, 'life1, 'async_trait>(
&'life0 self,
repo: &'life1 Resource,
) -> Pin<Box<dyn Future<Output = Result<RepoState>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn create_repo<'life0, 'life1, 'async_trait>(
&'life0 self,
spec: &'life1 RepoSpec,
) -> Pin<Box<dyn Future<Output = Result<RepoState>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn create_repo<'life0, 'life1, 'async_trait>(
&'life0 self,
spec: &'life1 RepoSpec,
) -> Pin<Box<dyn Future<Output = Result<RepoState>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
ForgeError::AlreadyExists — adopting it is a separate, elevated
decision (§5.6), not something a retry should do silently.Source§fn archive_repo<'life0, 'life1, 'async_trait>(
&'life0 self,
repo: &'life1 Resource,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn archive_repo<'life0, 'life1, 'async_trait>(
&'life0 self,
repo: &'life1 Resource,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn apply_roles<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
repo: &'life1 Resource,
desired: &'life2 [RoleAssignment],
unlisted: Unlisted,
) -> Pin<Box<dyn Future<Output = Result<ApplyReport>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn apply_roles<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
repo: &'life1 Resource,
desired: &'life2 [RoleAssignment],
unlisted: Unlisted,
) -> Pin<Box<dyn Future<Output = Result<ApplyReport>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
desired.
Collaborators desired does not mention are handled per unlisted.Source§fn bootstrap_plan(
&self,
repo: &RepoSpec,
cfg: &VgiConfig,
) -> Result<Vec<BootstrapStep>>
fn bootstrap_plan( &self, repo: &RepoSpec, cfg: &VgiConfig, ) -> Result<Vec<BootstrapStep>>
Source§fn run_step<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
repo: &'life1 Resource,
step: &'life2 BootstrapStep,
) -> Pin<Box<dyn Future<Output = Result<StepOutcome>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn run_step<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
repo: &'life1 Resource,
step: &'life2 BootstrapStep,
) -> Pin<Box<dyn Future<Output = Result<StepOutcome>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Source§fn parse_event(
&self,
headers: &HeaderMap,
body: &[u8],
) -> Result<Option<ForgeEvent>>
fn parse_event( &self, headers: &HeaderMap, body: &[u8], ) -> Result<Option<ForgeEvent>>
Ok(None) for a verified delivery
the core has no use for; Err for one that failed verification —
which must not be acted on.Source§impl ForgeHooks for ForgejoForge
impl ForgeHooks for ForgejoForge
Source§fn before_apply_roles(
&self,
repo: &Resource,
desired: &[RoleAssignment],
) -> HookDecision<Vec<RoleAssignment>>
fn before_apply_roles( &self, repo: &Resource, desired: &[RoleAssignment], ) -> HookDecision<Vec<RoleAssignment>>
The holder of a personal namespace owns every repository in it and cannot be added as a collaborator, so they are dropped from the desired set before it reaches Forgejo (and before the core reports their “missing” role as drift).
Source§fn before_create(&self, _spec: &RepoSpec) -> HookDecision<RepoSpec>
fn before_create(&self, _spec: &RepoSpec) -> HookDecision<RepoSpec>
Modify replaces the spec.Source§fn after_create(&self, _state: &RepoState) -> HookDecision<Vec<BootstrapStep>>
fn after_create(&self, _state: &RepoState) -> HookDecision<Vec<BootstrapStep>>
Modify adds steps for the core to
run before the bootstrap plan (Forgejo sets fast-forward-only merges
here).Source§fn after_bootstrap(
&self,
_repo: &Resource,
_outcomes: &[(String, StepOutcome)],
) -> HookDecision<Vec<BootstrapStep>>
fn after_bootstrap( &self, _repo: &Resource, _outcomes: &[(String, StepOutcome)], ) -> HookDecision<Vec<BootstrapStep>>
Modify adds follow-up steps.Source§fn on_event(&self, _event: &ForgeEvent) -> HookDecision<ForgeEvent>
fn on_event(&self, _event: &ForgeEvent) -> HookDecision<ForgeEvent>
Modify replaces it; Abort drops it.