github

package
v0.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 1, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package github wraps the GitHub REST API calls multirunner needs: JIT runner config generation and registration tokens, across repo / org / enterprise scopes, authenticated by either a PAT or a GitHub App.

Index

Constants

This section is empty.

Variables

View Source
var ErrContentsForbidden = errors.New("contents permission missing")

ErrContentsForbidden marks a tree read the credential could not perform even though it can see the repository: the contents permission is missing.

View Source
var ErrRunnerNotFound = errors.New("runner registration not found")

ErrRunnerNotFound reports that a runner registration is already gone. GitHub removes an ephemeral runner itself once it finishes its single job, so any best-effort cleanup races with that and should treat this as success.

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client talks to GitHub for a single configured scope.

func New

func New(ctx context.Context, gh config.GitHub, auth config.Auth) (*Client, error)

New builds a Client from config, selecting PAT or App auth and honoring a GHES base URL when github.url is not github.com.

func (*Client) ActionsEnabled

func (c *Client) ActionsEnabled(ctx context.Context) (bool, error)

ActionsEnabled reports whether GitHub Actions is enabled on the repo. A repo with Actions switched off accepts runner registrations and reports no queued jobs, exactly like an idle repo, so it is otherwise indistinguishable from one that simply has no work. Requires a repo-scoped client.

func (*Client) CheckRunnerAccess

func (c *Client) CheckRunnerAccess(ctx context.Context) error

CheckRunnerAccess lists one runner to prove the configured credential can reach the runner-admin API for this scope. Registration is the first thing that fails when the token is wrong, and it fails on the runner host rather than on the operator's terminal, so proving reachability up front turns a silent misconfiguration into an immediate error. 401/403 and 404 have different fixes (credential/scope versus owner or enterprise slug), so they are reported apart.

func (*Client) ClientFor

func (c *Client) ClientFor(repo string) *Client

ClientFor returns this client when it can serve repo.

func (*Client) ClientForSlot

func (c *Client) ClientForSlot(int) *Client

ClientForSlot returns the client itself (single-scope case).

func (*Client) CreateRegistrationToken

func (c *Client) CreateRegistrationToken(ctx context.Context) (string, error)

CreateRegistrationToken returns a short-lived registration token (config.sh fallback path when JIT is unavailable).

func (*Client) DeleteRunner

func (c *Client) DeleteRunner(ctx context.Context, runnerID int64) error

DeleteRunner removes a runner registration from GitHub by ID. Ephemeral runners self-remove after their one job, so this is the cleanup path for runners that exited without consuming their registration. Returns ErrRunnerNotFound if GitHub already removed it.

func (*Client) GenerateJITConfig

func (c *Client) GenerateJITConfig(ctx context.Context, in JITConfigRequest) (*JITConfig, error)

GenerateJITConfig requests a single-use JIT config for the configured scope.

func (*Client) QueuedJobLabels

func (c *Client) QueuedJobLabels(ctx context.Context) ([][]string, error)

QueuedJobLabels preserves the label-only view for diagnostics and callers that do not make authorization decisions.

func (*Client) QueuedJobs

func (c *Client) QueuedJobs(ctx context.Context) ([]QueuedJob, error)

QueuedJobs returns this client's queued jobs, each paired with the client.

func (*Client) QueuedWorkflowJobs

func (c *Client) QueuedWorkflowJobs(ctx context.Context) ([]QueuedJob, error)

QueuedWorkflowJobs returns queued jobs with the workflow metadata needed for pool authorization. Org/enterprise scope returns nil because there is no cheap repository workflow-runs endpoint for those scopes.

func (*Client) RepoFile

func (c *Client) RepoFile(ctx context.Context, p string) ([]byte, error)

RepoFile returns the contents of a repo-relative path on the default branch.

func (*Client) RepoFilePaths

func (c *Client) RepoFilePaths(ctx context.Context) ([]string, error)

RepoFilePaths returns every blob path in the repo's default-branch tree. Used by `multirunner detect --repo` to find language markers without a checkout. Requires the client to be repo-scoped (owner + repo set).

func (*Client) ResolveQueuedJob

func (c *Client) ResolveQueuedJob(ctx context.Context, repository string, runID int64, labels []string) (QueuedJob, error)

ResolveQueuedJob enriches a workflow_job webhook event from the authoritative workflow-run record before a privileged pool can authorize it.

func (*Client) RunnerBusy

func (c *Client) RunnerBusy(ctx context.Context, runnerID int64) (bool, error)

RunnerBusy reports whether GitHub currently shows the runner as running a job. Returns ErrRunnerNotFound when the registration is already gone.

func (*Client) Scope

func (c *Client) Scope() config.Scope

Scope reports the configured scope.

func (*Client) Target

func (c *Client) Target() string

Target names the registration target for logging: "owner/repo" in repo scope, otherwise the org or enterprise slug.

type ClientProvider

type ClientProvider interface {
	// ClientForSlot returns the registration client for a stable warm-pool slot.
	// The same slot must keep the same target across retries and every pool must
	// distribute its own slots independently.
	ClientForSlot(slot int) *Client

	// ClientFor returns the client for "owner/repo", or nil when this provider
	// does not manage that repo.
	ClientFor(repo string) *Client

	// QueuedJobs returns queued jobs across all managed scopes, each paired with
	// the client for the repo that queued it.
	QueuedJobs(ctx context.Context) ([]QueuedJob, error)

	// Scope returns the configured scope.
	Scope() config.Scope
}

ClientProvider abstracts access to one or more GitHub API clients for runner registration. Single-scope configs wrap a *Client directly; multi-repo configs use a RepoSet holding one client per repo.

A repo-scoped runner registers to exactly one repo, so the client chosen for a launch decides which repo that runner can ever serve. Demand-driven launches must therefore place the runner on the repo that queued the job (QueuedJobs or ClientFor). Warm capacity, where no job has been queued yet to place against, uses deterministic per-pool slot placement.

Both GenerateJITConfig and DeleteRunner within a single RunOnce call must use the same *Client (the one chosen at the start of that call).

type JITConfig

type JITConfig struct {
	EncodedJITConfig string `json:"encoded_jit_config"`
	Runner           struct {
		ID   int64  `json:"id"`
		Name string `json:"name"`
	} `json:"runner"`
}

JITConfig is the relevant part of the generate-jitconfig response.

type JITConfigRequest

type JITConfigRequest struct {
	Name          string
	RunnerGroupID int64
	Labels        []string
	WorkFolder    string
}

JITConfigRequest is the input for generate-jitconfig.

type QueuedJob

type QueuedJob struct {
	Client       *Client
	Repository   string
	Labels       []string
	WorkflowPath string
	Event        string
	Actor        string
	Ref          string
	Status       string
}

QueuedJob is a queued workflow job together with the client for the repo that queued it. Carrying the client is what lets the scaler register the new runner where the work actually is instead of wherever rotation happens to point.

type RepoPollError

type RepoPollError struct {
	Failures map[string]error
	Total    int
}

RepoPollError reports the repositories that could not be polled. Results from healthy repositories remain usable, but callers can distinguish a partial result from a fully successful poll.

func (*RepoPollError) AllFailed

func (e *RepoPollError) AllFailed() bool

AllFailed reports whether no configured repository could be polled.

func (*RepoPollError) Error

func (e *RepoPollError) Error() string

type RepoSet

type RepoSet struct {
	// contains filtered or unexported fields
}

RepoSet wraps multiple per-repo *Clients. Warm-pool placement is stable by slot, while each queued-job poll rotates its starting repo for fairness.

func NewRepoSet

func NewRepoSet(clients []*Client, repos []string) *RepoSet

NewRepoSet builds a RepoSet from a list of per-repo clients. The repos slice provides "owner/repo" labels for logging (same length/order as clients).

func (*RepoSet) ClientFor

func (rs *RepoSet) ClientFor(repo string) *Client

ClientFor returns the client for "owner/repo", or nil when the repo is not in this set. Matching is case-insensitive because GitHub treats repo names that way, and webhook payloads echo whatever casing the caller used.

func (*RepoSet) ClientForSlot

func (rs *RepoSet) ClientForSlot(slot int) *Client

ClientForSlot maps each warm-pool slot deterministically. Because the slot index is local to a pool, concurrent pools cannot consume each other's placement sequence.

func (*RepoSet) Clients

func (rs *RepoSet) Clients() []*Client

Clients returns the underlying per-repo clients (for shutdown cleanup).

func (*RepoSet) Len

func (rs *RepoSet) Len() int

Len returns the number of repos.

func (*RepoSet) QueuedJobs

func (rs *RepoSet) QueuedJobs(ctx context.Context) ([]QueuedJob, error)

QueuedJobs aggregates queued jobs across all repos, rotating the first repo on every call so constrained autoscale capacity cannot perpetually favor earlier config entries.

func (*RepoSet) Repos

func (rs *RepoSet) Repos() []string

Repos returns the repo names in registration order.

func (*RepoSet) Scope

func (rs *RepoSet) Scope() config.Scope

Scope returns ScopeRepos.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL