Skip to main content

GitHubForge

Struct GitHubForge 

Source
pub struct GitHubForge { /* private fields */ }
Expand description

The GitHub adapter: one community’s App on one GitHub (github.com or a GHES instance).

Holds the App’s key (behind AppKeySigner), its webhook secret, and the namespaces the core has bound. It holds no forge token between calls: each operation mints an installation token scoped to the one repository and the permissions that operation needs, and drops it on return.

Implementations§

Source§

impl GitHubForge

Source

pub fn parse_check_trigger( &self, headers: &HeaderMap, body: &[u8], ) -> Result<Vec<CheckTrigger>>

Verify a webhook and, if it calls for the bridge-posted check, say on what. Empty for a verified delivery that does not (another event, a closed pull request, a title edit). Err for one that failed verification, which must not be acted on.

Triggers: pull_request opened / synchronize / reopened, and edited when the base branch changed; merge_group checks_requested; this App’s check_run / check_suite rerequested (one trigger per pull request the delivery names). The repository is the delivery’s repository — the base the check is posted on — never the fork a pull request came from.

Source

pub async fn default_branch(&self, repo: &Resource) -> Result<String>

The repository’s default branch — the one branch the managed ruleset protects (~DEFAULT_BRANCH), read from GitHub now rather than from a delivery.

Source

pub async fn pull_request( &self, repo: &Resource, number: u64, ) -> Result<PullRequestInfo>

Pull request number on repo, as GitHub reports it now.

Source

pub async fn compare_commits( &self, repo: &Resource, base: &str, head: &str, ) -> Result<Comparison>

The commits in base...head on repo, oldest first (GET /repos/{o}/{r}/compare/{base}...{head}, every page: GitHub lists 100 per page and 250 in all).

Source

pub async fn merge_base( &self, repo: &Resource, a: &str, b: &str, ) -> Result<String>

The merge base of a and b on repo: GitHub’s merge_base_commit for a...b, one commit listed at most. For recomputing a merge from a fetch too shallow to search for the base.

Source

pub async fn contents_read_token(&self, repo: &Resource) -> Result<Secret>

A token that can read (fetch) repo and nothing else, for one fetch of the commits under test. The caller holds it for that fetch only and drops it; it lapses on its own within the hour either way.

Source

pub fn clone_url(&self, repo: &Resource) -> Result<Url>

The HTTPS clone URL of repo on this GitHub (<web>/<owner>/<repo>.git).

Source

pub async fn start_check_run( &self, repo: &Resource, head_sha: &str, name: &str, external_id: &str, ) -> Result<u64>

Post the check on head_sha as in progress, and return its id.

Source

pub async fn finish_check_run( &self, repo: &Resource, id: u64, conclusion: CheckConclusion, title: &str, summary: &str, ) -> Result<()>

Complete check run id with conclusion, a one-line title and a Markdown summary (truncated to what GitHub accepts).

Source§

impl GitHubForge

Source

pub fn new( config: GitHubConfig, signer: Arc<dyn AppKeySigner>, webhook_secret: Secret, ) -> Result<Self>

An adapter for config’s App, signing with signer and verifying webhooks with webhook_secret.

Source

pub fn with_client_secret(self, secret: Secret) -> Self

Give the adapter the App’s OAuth client secret (from the manifest exchange). With it, the member’s user token from an account link is revoked as soon as their id is read; without it the token is only dropped and lapses on its own (eight hours for an expiring App user token), because revocation is authenticated with the client secret.

Source

pub fn config(&self) -> &GitHubConfig

The configuration.

Source

pub fn register_namespace(&self, ns: Namespace) -> Result<()>

Tell the adapter about a bound namespace (from the VTC’s store, after the admin confirmed the bind). Operations on repositories in a namespace that was never registered are refused with ForgeError::NotBound: the binding, not whatever the App happens to be installed on, is what authorises the bridge to act.

Source

pub fn unregister_namespace(&self, ns: &Resource)

Forget a namespace (unbind).

Source

pub fn set_managed_repositories( &self, ns: &Resource, ids: impl IntoIterator<Item = u64>, )

Tell the adapter which repositories (by forge id) it manages in organisation ns, from the bridge’s store: at start-up and whenever the set changes. The org ruleset lists exactly these (plus a repository being bootstrapped); ids GitHub lists that are not here — archived, deleted or never managed — are dropped from it. Until it is set, the required-workflow step refuses to run rather than guess the set from GitHub.

Source

pub fn managed_repositories(&self, ns: &Resource) -> Option<BTreeSet<u64>>

The managed set as the adapter holds it (it adds each repository it bootstraps under a required workflow, and drops each it archives).

Source

pub fn set_bridge_checks_ready(&self, ns: &Resource, ready: bool)

Record whether ns’s installation carries the bridge-posted check (from GitHubForge::detect_bridge_checks, or the bridge’s store after a restart). Until it is known, a namespace without a required workflow keeps the in-repo Actions workflow.

Source

pub fn bridge_checks_ready(&self, ns: &Resource) -> Option<bool>

Whether ns’s installation is known to carry the bridge-posted check (None: not known yet).

Source

pub async fn detect_bridge_checks(&self, ns: &Resource) -> Result<bool>

Read ns’s installation and record whether it grants what the bridge-posted check needs: checks: write, pull_requests: read, merge_queues: read and the pull_request and merge_group subscriptions (crate::manifest::check_ready). An App registered before these were in the manifest lacks them until its owner updates the App’s settings and each installation approves the change; the bridge probes again when an installation accepts new permissions.

Source

pub async fn detect_installation( &self, ns: &Resource, ) -> Result<(bool, Vec<String>)>

GitHubForge::detect_bridge_checks, and what the installation lacks of what the App asks for (as name:level, and event:<name> for a missing subscription) — the same list a bind reports in NamespaceBinding::missing_permissions, read again now (after an owner approved an upgrade, say).

Source

pub fn set_required_workflow(&self, ns: &Resource, available: bool)

Record whether org rulesets — and so a required workflow — are available in organisation ns (from GitHubForge::detect_required_workflow, or from the bridge’s store after a restart). Until it is known the namespace plans the owner-review fallback.

Source

pub fn set_required_workflow_pin(&self, ns: &Resource, pin: RequiredWorkflowPin)

Restore the pin the required-workflow step last made in ns.

Source

pub fn required_workflow_pin( &self, ns: &Resource, ) -> Option<RequiredWorkflowPin>

The pin the required-workflow step last made in ns, to persist.

Source

pub async fn detect_required_workflow(&self, ns: &Resource) -> Result<bool>

Find out whether organisation ns can have a required workflow, and record the answer.

The probe is GET /orgs/{org}/rulesets with an organization Administration token. Org rulesets exist on GitHub Team and Enterprise plans only; on a Free organisation, or where the owner has not granted the App organization Administration, GitHub refuses (403/404, or 422 when minting the token) and the answer is false. A personal account, or manual mode, is always false. Network and rate-limit failures are returned, not guessed at.

A true here is necessary, not sufficient: GitHub documents the workflows rule for Enterprise Cloud. If the org ruleset is then refused, the required-workflow step records false and asks for a re-plan.

Source

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.

Source

pub async fn fetch_web_flow_key(&self) -> Result<Vec<u8>>

Download GitHub’s web-flow public key (<web>/web-flow.gpg) for the platform keyring. Never called implicitly: the keyring is configuration, and fetching it is a choice the operator makes and can review.

Source

pub async fn repository_by_id( &self, ns: &Resource, id: u64, ) -> Result<Option<Resource>>

Where repository id is now, as namespace ns’s installation sees it (GET /repositories/{id} with that installation’s token): None if it cannot see it. What a transfer into ns is confirmed by — GitHub’s word, not a webhook’s.

Source§

impl GitHubForge

Source

pub fn parse_push( &self, headers: &HeaderMap, body: &[u8], ) -> Result<Option<PushEvent>>

Verify a webhook and, if it is a push, read it. Ok(None) for a verified delivery of any other event; Err for one that failed verification or is malformed, which must not be acted on.

Source

pub async fn contents_write_token(&self, repo: &Resource) -> Result<Secret>

A token that can push to repo and nothing else, for one push of re-signed commits. The caller holds it for that push only.

Trait Implementations§

Source§

impl Debug for GitHubForge

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Forge for GitHubForge

Source§

fn indirect_access<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, repo: &'life1 Resource, account: &'life2 ForgeAccount, ) -> Pin<Box<dyn Future<Output = Result<Option<IndirectAccess>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

GitHub’s effective permission for the account (/collaborators/{username}/permission counts teams, organisation ownership and the base permission), then where it comes from.

Source§

fn diff(&self, observed: &RepoState, desired: &Projection) -> Vec<Drift>

The default comparison, plus the owner-review guard against the projection’s owners (§9, the user’s decision on solo repositories): two or more owners must all be reviewers of a healthy guard; one owner needs no guard, and a guard left over from when there were more is a re-plan (it would lock the solo owner out).

Source§

fn kind(&self) -> ForgeKind

Which forge software this is.
Source§

fn host(&self) -> &str

The forge host this adapter serves (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

What this forge, and this namespace on it, can do. The core and the UX branch on this, never on 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,

Start binding a namespace: where to send the admin.
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,

Finish a bind from the forge’s callback. Validates the state nonce and that the credential landed on the expected owner.
Start linking a member’s forge account. member is their DID, for the adapter’s audit trail; nothing forge-side sees it.
Finish linking: the account’s numeric id and current login.
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,

Observe a repository’s current state.
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,

Create a repository. Refuses one that already exists with 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,

Archive a repository. Idempotent.
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,

Converge people’s direct roles on a repository to desired. Collaborators desired does not mention are handled per unlisted.
Source§

fn bootstrap_plan( &self, repo: &RepoSpec, cfg: &VgiConfig, ) -> Result<Vec<BootstrapStep>>

The steps that turn commit trust on for this forge’s CI.
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,

Run one step, check-then-apply.
Source§

fn parse_event( &self, headers: &HeaderMap, body: &[u8], ) -> Result<Option<ForgeEvent>>

Verify and translate a webhook. 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§

fn normalize(&self, raw: &str) -> Result<Resource, ForgeError>

Canonical form of a forge path. The default applies the owner[/repo] grammar GitHub and Forgejo share and refuses a resource on another host.
Source§

fn map_role( &self, ns: &Namespace, rights: EffectiveRights, map: &RoleMap, ) -> ForgeRole

Rights → this forge’s role for one person on one repository in ns. The default asks map for a role and rounds it down onto the namespace’s ladder.
Source§

fn is_protected_account(&self, ns: &Namespace, account: u64) -> bool

Whether the account with forge id account must never be taken off a repository in ns, whatever a job asks: the namespace’s owner (on a personal account, the implicit admin of every repository in it) and the adapter’s own automation identity (a Forgejo bot, a GitHub App’s bot user), without which nothing the bridge does would keep working. Read more
Source§

impl ForgeHooks for GitHubForge

Source§

fn before_apply_roles( &self, repo: &Resource, desired: &[RoleAssignment], ) -> HookDecision<Vec<RoleAssignment>>

In a personal-account namespace the owner is the repository’s implicit admin and GitHub refuses to add them as a collaborator, so they are dropped from the desired set before it reaches GitHub (and before the core reports their “missing” role as drift).

Source§

fn before_create(&self, _spec: &RepoSpec) -> HookDecision<RepoSpec>

Before a repository is created. Modify replaces the spec.
Source§

fn after_create(&self, _state: &RepoState) -> HookDecision<Vec<BootstrapStep>>

After a repository is created. 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>>

After a bootstrap plan ran. Modify adds follow-up steps.
Source§

fn on_event(&self, _event: &ForgeEvent) -> HookDecision<ForgeEvent>

On a verified event. Modify replaces it; Abort drops it.
Source§

fn on_drift( &self, _repo: &Resource, _drift: &[Drift], ) -> HookDecision<Vec<Drift>>

On drift found for a repository. Modify replaces the list (to suppress a forge’s known false positives).

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more