Skip to main content

Crate runner_manager_github

Crate runner_manager_github 

Source
Expand description

The GitHub gateway.

This crate holds every line of code in the product that talks to GitHub, and the crate root holds the one client all of it goes through.

  • device_flow — the OAuth 2.0 Device Authorization Grant, which is the only way this product ever obtains a credential (D3, D16).
  • AuthenticatedClient — the shared api.github.com client. Every request in this crate is built by it, which is what makes “sets X-GitHub-Api-Version and an explicit Accept” a property of the design rather than of each call site, and what lets the authentication-failure taxonomy be implemented exactly once.
  • rest, demand, jit — typed adapters owned by c3 and c4, built on AuthenticatedClient.

§Three properties this crate is required to keep

It holds no client secret, and it renews without one. This paragraph used to say the opposite — that the App opts out of user-token expiration, that no renewal token is ever issued, and that renewing would require a client secret a public client cannot hold. All three were wrong, and a test in this crate enforced the error by forbidding the word “refresh token”.

The published App has user-token expiration on: a device-flow exchange returns expires_in: 28800 and a refresh_token. GitHub requires the client secret to refresh “unless the user access token was generated using the device flow”, and every one of this product’s is. So the credential renews itself, no server appears in the design, and the eight-hour life of an access token is invisible to a daemon that runs for months. See AuthenticatedClient::renew_once, and docs/spikes/token-expiry-and-renewal.md for the two renewals that confirmed it on real hosts.

AuthenticatedClient::revalidate is what still happens on a 401 for a credential with no refresh half — every one issued before 0.1.11.

It persists nothing. device_flow::DeviceFlow::complete returns the token; it never writes it anywhere. The machine-scoped secret store is d2 and the wiring is f1. That boundary is why this crate has no dependency on runner-manager-platform and performs no filesystem write outside its own tests — and it is what lets the whole gateway be tested with no platform dependency at all.

It never renders a secret. The device code, the user access token, and every header carrying either are absent from Debug, from Display, from errors, and from tracing output. Every type here that holds one wraps it in secrecy::SecretString and implements fmt::Debug by hand, because a #[derive(Debug)] added later to a struct with a plain String field is precisely how this control is lost. tests/no_secret_reaches_the_logs.rs drives a whole login and an authenticated round trip through a capturing tracing subscriber and fails if any of the three appears.

That scan is a separate test binary, and deliberately so. As a unit test it silently stopped working: tracing caches each callsite’s Interest process-wide while with_default installs a subscriber on one thread, and run concurrently with the crate’s other unit tests the scan captured only its own handful of events — passing with a real device-code leak on the live path. A binary holding one test has no concurrency to be poisoned by. The word “concurrently” is load-bearing and was measured; tests/no_secret_reaches_the_logs.rs records the numbers and what they rule out.

Modules§

demand
How much work is waiting for a runner that does not exist yet.
device_flow
The OAuth 2.0 Device Authorization Grant against the published GitHub App.
jit
Just-in-time runner registration: the one call in this product that returns a secret.
rest
This gateway is deliberately client-secret-free, as D3 requires.

Structs§

ApiRequest
One api.github.com request, before authentication headers are applied.
ApiResponse
One buffered api.github.com response.
AppRegistration
The published GitHub App this product authenticates as (D3, D16).
AuthenticatedClient
The one client every api.github.com request in this crate goes through.
Endpoints
Where GitHub is.
HeaderMap
Re-exported because GithubError::headers and ApiResponse::headers return one, and a consumer cannot name a type it has no path to.
Installation
One installation of the published App, and what it can actually reach.
RateLimitEvidence
What GitHub said about its own rate limit on a response that failed.
ReachableTargets
Everything the stored credential can reach.
Renewal
What a token needs in order to replace itself without a person.
TokioSleeper
The production adapter.
UserAccessToken
A user access token obtained from the device flow.

Enums§

ConfigError
A configuration value this crate refuses to start with.
GithubError
Everything AuthenticatedClient can fail with.
InstallationAccount
Whose account an installation sits on.
InstallationDiscovery
What auth status and auth login show after a successful sign-in.
RepositorySelection
Whether an installation can reach every repository on its account, or only the ones the user picked.
Revalidation
What a single re-validation of the held credential concluded.

Constants§

DEFAULT_LOCKOUT_BACKOFF
How long a lockout backs off for when GitHub sends no retry-after.
DEFAULT_REQUEST_TIMEOUT
Per-request ceiling, so one wedged connection cannot stall the agent’s reconciliation loop forever.
DEVICE_VERIFICATION_PATH
The canonical page a user types their code into.
GITHUB_ACCEPT
The media type every request asks for, stated rather than defaulted.
GITHUB_API_BASE
Production api.github.com.
GITHUB_API_VERSION
The REST API version every request pins.
GITHUB_WEB_BASE
Production github.com, which hosts the device-flow endpoints. They are on the web host, not the API host.
MAX_LOCKOUT_BACKOFF
The longest a lockout may silence this client, whatever Retry-After said.
MAX_PAGES
The most pages either pagination loop follows before giving up.
REVALIDATION_PATH
What a 401 re-validates the held credential against.
USER_AGENT
The User-Agent GitHub requires on every API request.

Traits§

CredentialRenewal
How a credential replaces itself.
CredentialSource
Where a client can go to find out that the stored credential changed under it.
Sleeper
The one way anything in this crate waits.