Documentation
¶
Overview ¶
Package github wraps everything Zoomies needs from the GitHub API: App authentication, minting runner credentials, listing runners and queued jobs, and validating inbound webhooks.
Zoomies never asks an operator for a personal access token. It authenticates as a GitHub App and mints short-lived registration credentials itself, so there is no long-lived token sitting in a dotfile next to a runner.
Index ¶
- Constants
- Variables
- func ActionsIssuerFor(host string) string
- func BranchName(now time.Time) string
- func InstallURL(htmlURL string) string
- func IsEnterprise(apiBaseURL string) bool
- func IsPing(event string) bool
- func Manifest(o ManifestOptions) ([]byte, error)
- func ManifestURL(apiBaseURL, org string) string
- func NewServerError(err error) error
- func NormalizeAPIBaseURL(raw string) (string, error)
- func ParseEventType(header string) string
- func RetryAfterRateLimit(err error, now time.Time) (time.Time, bool)
- func RunnerName(pool *store.Pool) string
- func RunnerNameOn(pool *store.Pool, host *store.Host) string
- func SettingsURL(apiBaseURL, slug, org string) string
- func SplitTarget(target string) (owner, repo string, kind store.TargetType)
- func UnverifiedActionsIssuer(raw string) (string, error)
- func ValidCheckoutEvidence(e *CheckoutEvidence, repo string, s CheckoutSelection) bool
- func ValidCheckoutSelection(s CheckoutSelection) bool
- func ValidExperimentAttemptContext(event, workflowPath string) bool
- func ValidExperimentWorkflowSource(e *ExperimentAttemptEvidence) bool
- func ValidateSignature(payload []byte, header, secret string) error
- func WebURLForAPI(apiBaseURL string) string
- type ActionsClaims
- type ActionsKeysError
- type ActionsTokenVerifier
- type AgentGuidanceClient
- type AppFactory
- type AppInfo
- type CheckoutEvidence
- type CheckoutEvidenceReader
- type CheckoutSelection
- type Client
- type ContextIngestionClient
- type ContextPublication
- type ContextRun
- type ContextRunReader
- type ContextSetupClient
- type ContextSetupRequest
- type ContextSetupSource
- type ContextSetupStatusClient
- type ContextWorkflowDispatcher
- type ExperimentAttemptEvidence
- type ExperimentAttemptJob
- type ExperimentAttemptReader
- type ExperimentWorkflowSource
- type Factory
- type FakeActionsIssuer
- type FakeActionsSettings
- type FakeArtifact
- type FakeContextJob
- type FakeContextRun
- type FakeContextStep
- type FakeGitHub
- func (f *FakeGitHub) AddArtifacts(repo string, artifacts ...FakeArtifact)
- func (f *FakeGitHub) AddBranchRule(repo, branch string, checks ...FakeRequiredCheck)
- func (f *FakeGitHub) AddContextRun(repo string, run FakeContextRun) int64
- func (f *FakeGitHub) AddFile(repo, path, content string)
- func (f *FakeGitHub) AddQueuedJob(repo, workflow, jobName string, labels []string) QueuedJob
- func (f *FakeGitHub) AddRepo(fullName string)
- func (f *FakeGitHub) AddRunner(name string, labels []string) Runner
- func (f *FakeGitHub) AddRunnerGroup(name string) RunnerGroup
- func (f *FakeGitHub) AddWorkflow(repo, path, content string)
- func (f *FakeGitHub) AppID() int64
- func (f *FakeGitHub) Branches(repo string) []string
- func (f *FakeGitHub) ClearErrors()
- func (f *FakeGitHub) Client(target string, kind store.TargetType) Client
- func (f *FakeGitHub) Close()
- func (f *FakeGitHub) CompleteJob(jobID int64, conclusion string)
- func (f *FakeGitHub) ContextDispatches(repo string) int
- func (f *FakeGitHub) ContextLogAuthorizations() []string
- func (f *FakeGitHub) ContextRunJobID(runID int64, i int) int64
- func (f *FakeGitHub) FileContent(repo, path string) (string, bool)
- func (f *FakeGitHub) InstallationID() int64
- func (f *FakeGitHub) JobReruns() int
- func (f *FakeGitHub) LoseNextContextPullResponse(repo string)
- func (f *FakeGitHub) MergeContextPull(repo string, number int) bool
- func (f *FakeGitHub) NotModified() int
- func (f *FakeGitHub) PullRequestBody(repo string, number int) string
- func (f *FakeGitHub) RepositoryID(fullName string) int64
- func (f *FakeGitHub) Requests() []string
- func (f *FakeGitHub) Reruns() int
- func (f *FakeGitHub) Runners() []Runner
- func (f *FakeGitHub) Server() *httptest.Server
- func (f *FakeGitHub) SetActionsSettings(repo string, s FakeActionsSettings)
- func (f *FakeGitHub) SetArchived(repo string, archived bool)
- func (f *FakeGitHub) SetBranchProtection(repo, branch string, checks ...FakeRequiredCheck)
- func (f *FakeGitHub) SetContextBranch(repo, branch string, contents map[string]string)
- func (f *FakeGitHub) SetDefaultBranch(repo, branch string)
- func (f *FakeGitHub) SetDelay(method, pattern string, d time.Duration)
- func (f *FakeGitHub) SetError(pattern string, status int, message string)
- func (f *FakeGitHub) SetEvents(events ...string)
- func (f *FakeGitHub) SetFileMode(repo, path, mode string)
- func (f *FakeGitHub) SetLegacyBranchProtection(repo, branch string, contexts ...string)
- func (f *FakeGitHub) SetMethodError(method, pattern string, status int, message string)
- func (f *FakeGitHub) SetPermissions(p map[string]string)
- func (f *FakeGitHub) SetRateLimit(limit, remaining int, reset time.Time)
- func (f *FakeGitHub) SetRepairActor(login string, id int64, canWrite bool)
- func (f *FakeGitHub) SetRepositorySelection(selection string)
- func (f *FakeGitHub) SetRunTrigger(repo string, runID int64, event string, headRepositoryID int64)
- func (f *FakeGitHub) SetRunnerBusy(name string, busy bool)
- func (f *FakeGitHub) SetRunnerGroupPublicAccess(name string, allowed bool)
- func (f *FakeGitHub) SetVisibility(fullName, visibility string)
- func (f *FakeGitHub) SetWorkflowRunState(runID int64, status, conclusion string)
- func (f *FakeGitHub) StartJob(jobID int64, runnerName string)
- func (f *FakeGitHub) URL() string
- type FakeRequiredCheck
- type FileChange
- type JITConfig
- type JITRequest
- type KennelGuidanceInventory
- type KennelGuidanceReader
- type KennelPrivateFork
- type KennelProtection
- type KennelRun
- type KennelSettings
- type KennelSettingsReader
- type KennelSetup
- type KennelSetupReader
- type KennelWorkflowInventory
- type KennelWorkflowReader
- type ManifestCredentials
- type ManifestOptions
- type PRDelayCheck
- type PRDelayEvidence
- type PRDelayReader
- type PullRequest
- type PullRequestRequest
- type QueuedJob
- type RateLimit
- type RateLimitedError
- type Readme
- type RegistrationToken
- type RepairActor
- type RepairClient
- type RepairPull
- type RepairSource
- type RepairTargetChanged
- type RepoReader
- type Repository
- type Runner
- type RunnerGroup
- type RunnerGroupCreate
- type WorkflowFile
- type WorkflowFileRef
- type WorkflowJobEvent
- type WorkflowRunState
Constants ¶
const ( // SignatureHeader carries the HMAC-SHA256 of the body. SignatureHeader = gh.SHA256SignatureHeader // EventTypeHeader carries the event name, e.g. "workflow_job". EventTypeHeader = gh.EventTypeHeader // DeliveryIDHeader carries the delivery UUID, which is what an operator // quotes when comparing Zoomies' log with GitHub's redelivery page. DeliveryIDHeader = gh.DeliveryIDHeader )
Headers GitHub sets on every delivery. They are re-exported here so the API package does not need its own opinion about their spelling.
const ActionsIssuer = "https://token.actions.githubusercontent.com"
ActionsIssuer is the issuer of GitHub.com's Actions OIDC tokens. A token from any other issuer is not a statement GitHub made about a workflow run.
const CheckoutEvidenceMaximumRequests = 7
const ExperimentAttemptMaximumRequests = 8
const ExperimentWorkflowSourceBytes = 128 * 1024
const GitHubActionsAppID int64 = 15368
GitHubActionsAppID is the ID GitHub gives the app that posts a workflow job's check. A required status check is pinned to Actions when it names this app, and only then can the jobs the fleet saw say whether the check ever reports: a check that any app may post could come from somewhere the fleet cannot see.
const KennelWorkflowBytes = workflow.MaxBytes
const KennelWorkflowFiles = 50
const MigrationPermissionHint = `the migration wizard needs three repository permissions: ` +
`"Contents" (contents) read and write, "Pull requests" (pull_requests) read and write, and ` +
`"Workflows" (workflows) write, which GitHub requires specifically to change files under .github/workflows. ` +
`An App created by Zoomies asks for them only when it is created with the migration option ticked, so this ` +
`one was created without it, predates it, or had them removed: add them under the App's Permissions & events, ` +
`then accept the change on the installation`
MigrationPermissionHint names what an App needs before it can read a repository's workflows and open a migration pull request, in the words GitHub's own settings page uses.
const (
// RepositoryDiscoveryLimit bounds repository metadata discovery.
RepositoryDiscoveryLimit = 500
)
Bounds on what one migration scan will read.
A scan walks an organisation's repositories and reads every workflow file in each one, so it is by far the most expensive thing Zoomies asks GitHub for. These caps keep one operator's curiosity from spending the installation's hourly quota, which the scheduler shares.
Variables ¶
var ( ErrCheckoutNotFound = workflow.ErrCheckoutNotFound ErrCheckoutAmbiguous = workflow.ErrCheckoutAmbiguous )
ErrCheckoutNotFound and ErrCheckoutAmbiguous are what a Locate read says when the job has no single step that can be previewed.
ErrActionsKeysUnavailable is a token that could not be checked because GitHub's signing keys could not be fetched. It says nothing about the token, so it must not read as a refusal of it: the workflow's log should send the operator to the controller's network, not to the workflow.
var ErrActionsToken = errors.New("the upload's GitHub Actions OIDC token is not valid for this controller")
ErrActionsToken is returned for any token that is not a valid, current Actions token for this audience. The cause is wrapped for the log only.
var ErrContextMismatch = errors.New("github: published context does not match the trusted branch")
ErrContextMismatch is a published snapshot that no longer describes the trusted branch: the branch moved on, or the snapshot was made for another commit. It is the ordinary state between a push and the workflow finishing, so a caller can tell it apart from a publication that is genuinely broken.
var ErrExperimentAttemptChanged = errors.New("github: comparison attempt changed during collection; refresh its evidence")
var ErrForbidden = errors.New("github: forbidden")
ErrForbidden is returned for a 403 that is not a rate limit, usually meaning the App installation is missing a permission.
var ErrInvalid = errors.New("github: refused as invalid")
ErrInvalid is GitHub refusing a request as invalid rather than unauthorised: a runner name already taken, a label it will not accept. The detail carries the field-by-field reason, which is the part worth reading -- the message on a 422 is always "Validation Failed".
var ErrInvalidSignature = errors.New("github: webhook signature invalid")
ErrInvalidSignature is returned when a delivery is unsigned or its signature does not match. The API answers these with 401 and does not process the body.
var ErrNoReadme = errors.New("github: the repository has no README")
ErrNoReadme is returned when a repository has no README, which is not a failure either: the badge has nowhere to go, and the migration goes ahead without it.
var ErrNoSecret = errors.New("github: no webhook secret configured")
ErrNoSecret is returned when Zoomies holds no webhook secret to verify a delivery against. It is a configuration fault, not an attack, and the API answers it differently from a bad signature.
var ErrNoWorkflows = errors.New("github: the repository has no .github/workflows directory")
ErrNoWorkflows is returned when a repository has no .github/workflows at all, which is not a failure: most repositories in a large organisation do not.
var ErrNotFound = errors.New("github: not found")
ErrNotFound is returned when GitHub reports a 404 for something Zoomies expected to exist.
var ErrNotWorkflowJob = errors.New("github: not a workflow_job payload")
ErrNotWorkflowJob is returned when a payload is not a workflow_job event.
var ErrRateLimited = errors.New("github: rate limited")
ErrRateLimited is returned when the installation has exhausted its quota. The caller backs off rather than hammering.
var ErrRepairUnsupported = errors.New("unsupported PR repair target")
ErrRepairUnsupported identifies a run that cannot be admitted as a PR repair.
var ErrRunnerBusy = errors.New("github: runner is still running a job")
ErrRunnerBusy is DeleteRunner refused because GitHub still considers the runner to be running a job. It wraps ErrInvalid -- the same 422 GitHub uses for every other "refused as invalid" case -- so a caller that only checks for ErrInvalid still catches it, and one that wants to tell this apart from a genuinely bad request (a stale ID, a name GitHub never had) can.
This is usually not the operator's problem: GitHub's own bookkeeping can lag a few seconds behind the workflow_job "completed" webhook that told Zoomies the runner was free, and the delete loses that race. Deleting again once GitHub reports the runner idle succeeds without anyone doing anything.
var ErrServerError = errors.New("github: server error")
ErrServerError is GitHub itself failing -- any 5xx. It is nothing the caller did and nothing a permission fixes, so it is told apart from ErrForbidden: the callers that can wait (Kennel Club) back off on it instead of asking again.
var ErrSetupConflict = errors.New("github: repository setup changed; review it again")
var KennelEndpoints = []string{
"GET /installation/repositories",
"GET /repos/{owner}/{repo}",
"GET /repos/{owner}/{repo}/actions/runs/{run}",
"GET /orgs/{org}/actions/runner-groups",
"GET /rate_limit",
"GET /repos/{owner}/{repo}/git/trees/{tree}",
"GET /repos/{owner}/{repo}/git/blobs/{blob}",
"GET /repos/{owner}/{repo}/actions/permissions/workflow",
"GET /repos/{owner}/{repo}/actions/permissions/fork-pr-contributor-approval",
"GET /repos/{owner}/{repo}/actions/permissions/fork-pr-workflows-private-repos",
"GET /repos/{owner}/{repo}/branches/{branch}/protection",
"GET /repos/{owner}/{repo}/rules/branches/{branch}",
}
KennelEndpoints are the GitHub endpoints Kennel Club may call, in the form its documentation lists them. A test runs the reader against the fake and fails on any request that is not one of these, so a new call is a visible change to this list in review rather than something found in a log.
var MigrationPermissions = map[string]string{
"contents": "write",
"pull_requests": "write",
"workflows": "write",
}
MigrationPermissions is the same requirement as data: permission name to the level needed. The API compares it against what an installation was granted so the wizard can say what is missing before it tries.
Functions ¶
func ActionsIssuerFor ¶ added in v1.3.8
ActionsIssuerFor is the issuer of Actions OIDC tokens for workflows on host. GitHub.com's come from one shared issuer; an Enterprise Server signs its own, under /_services/token on the instance itself, so a GHES token is only ever checked against the keys of the server that ran the workflow.
func BranchName ¶
BranchName builds the branch a migration commits to.
The timestamp is what keeps two runs of the wizard from colliding: creating a ref that already exists fails, and the alternative -- reusing the branch -- would rewrite a pull request somebody might be reviewing.
func InstallURL ¶
InstallURL returns the page that installs an App on an account, given the html_url the manifest conversion returned.
func IsEnterprise ¶
IsEnterprise reports whether an API base URL points at GitHub Enterprise Server rather than github.com.
func IsPing ¶
IsPing reports whether an event is GitHub's "does this endpoint exist" probe, which is sent once when a webhook is configured and carries no job.
func Manifest ¶
func Manifest(o ManifestOptions) ([]byte, error)
Manifest renders the JSON an operator's browser POSTs to GitHub to create the App.
The permission set is deliberately minimal, because an App that manages a fleet's runners is a high-value credential: it asks for the runner administration permission for the kind of target it will manage, read access to Actions so it can see queued jobs, and nothing else. The three the migration wizard needs to rewrite a runs-on line and open a pull request for it are added only when the operator has said they want the wizard. Metadata read is mandatory for every App.
Every key here is one GitHub's manifest schema permits; it rejects anything else outright, so nothing speculative belongs in this struct.
func ManifestURL ¶
ManifestURL returns the page the manifest form posts to. GitHub has a separate endpoint for creating an App under an organisation, and posting the org manifest to the user endpoint silently creates a personal App instead.
func NewServerError ¶ added in v1.3.8
NewServerError marks err as GitHub failing, for the code that classifies a response and for tests that need the failure without the response.
func NormalizeAPIBaseURL ¶
NormalizeAPIBaseURL turns the forms operators actually type into the form go-github expects: an absolute URL ending in a slash, with /api/v3 appended for a bare GHES hostname.
func ParseEventType ¶
ParseEventType normalises the X-GitHub-Event header. GitHub sends it lowercase, but proxies have been known to rewrite header values.
func RetryAfterRateLimit ¶
RetryAfterRateLimit reports when err says an installation may be used again.
It answers false for anything that is not a rate limit and for a rate limit GitHub said nothing useful about, which is the same thing to a caller: fall back to your own wait.
func RunnerName ¶
RunnerName mints the name one runner of this pool registers under: "zoomies-4vcpu-ubuntu-2404-biscuit-a3f9qz2m".
This is the one name Zoomies puts in somebody else's account, so GitHub's constraints on it are worth stating where the registration is made. The name must be unique within the target -- two runners answering to one name is one registration being taken over, not two runners -- which is what the random token at the end is for, and why nothing may truncate it away. GitHub also shows it in three places a reader arrives at knowing nothing: the runner list, the job header, and the "Set up job" step of every log. That is what the shape in the middle answers, and store.NewRunnerName explains why it is worth the characters.
The 64-character limit GitHub enforces is internal/naming's MaxNameLength, so a name is trimmed to fit here rather than refused by the API at registration.
func RunnerNameOn ¶ added in v1.3.8
RunnerNameOn names a runner that is going to a known host, so the name GitHub shows says which machine a job ran on.
func SettingsURL ¶
SettingsURL returns the App's own settings page, which is the only place its logo can be set.
The App manifest schema has no field for an avatar -- GitHub takes it as an upload and nothing else -- so an App created by Zoomies arrives with the grey default, and every "Set up job" line in the organisation's logs is signed by an anonymous App. Pointing the operator at the page and handing them the mark is as close to automatic as GitHub allows.
An App owned by an organisation has its settings under that organisation; one owned by a user has them under the user's own settings, and GitHub answers the wrong URL with a 404 rather than a redirect.
func SplitTarget ¶
func SplitTarget(target string) (owner, repo string, kind store.TargetType)
SplitTarget parses "owner" or "owner/repo" into its parts.
func UnverifiedActionsIssuer ¶ added in v1.3.8
UnverifiedActionsIssuer reads a token's iss claim without checking anything. It is for routing only -- choosing which issuer's keys to verify against -- and a caller must accept the answer only if it names an issuer it already trusts, so a token cannot make the controller fetch keys from anywhere.
func ValidCheckoutEvidence ¶ added in v1.3.8
func ValidCheckoutEvidence(e *CheckoutEvidence, repo string, s CheckoutSelection) bool
func ValidCheckoutSelection ¶ added in v1.3.8
func ValidCheckoutSelection(s CheckoutSelection) bool
func ValidExperimentAttemptContext ¶ added in v1.3.8
ValidExperimentAttemptContext bounds optional, untrusted metadata before it is retained. A path is opaque: it is never resolved as a file, URL or source ref. Empty fields remain unknown, including in evidence captured before this slice.
func ValidExperimentWorkflowSource ¶ added in v1.3.8
func ValidExperimentWorkflowSource(e *ExperimentAttemptEvidence) bool
ValidExperimentWorkflowSource checks the retained association and digest forms. Collection checks bytes; replay has only the retained digests, not source text.
func ValidateSignature ¶
ValidateSignature checks the HMAC GitHub attached to a delivery.
It refuses to pass an unsigned delivery under any circumstances: an endpoint that accepts unsigned webhooks lets anyone on the internet make Zoomies start runners.
func WebURLForAPI ¶
WebURLForAPI derives the browser-facing base URL from an API base URL, which is what the UI links to.
It is also the URL a runner registers against, so a GHE.com tenant has to lose its api. prefix here: config.sh pointed at api.<tenant>.ghe.com/<org> fails to register, and the App-creation page there does not exist.
Types ¶
type ActionsClaims ¶ added in v1.3.8
type ActionsClaims struct {
ID string
Expiry time.Time
Repository string `json:"repository"`
RepositoryID string `json:"repository_id"`
RepositoryOwnerID string `json:"repository_owner_id"`
Ref string `json:"ref"`
SHA string `json:"sha"`
EventName string `json:"event_name"`
JobWorkflowRef string `json:"job_workflow_ref"`
RunID string `json:"run_id"`
RunAttempt string `json:"run_attempt"`
}
ActionsClaims are the parts of an Actions OIDC token Zoomies decides on. Each is something GitHub asserts about the run, not something the workflow chose: a step can ask for a token but cannot change what it says.
type ActionsKeysError ¶ added in v1.3.8
ActionsKeysError is ErrActionsKeysUnavailable with the host the keys live on, which is the host an operator has to let the controller reach: GitHub's shared issuer for GitHub.com, the server itself for Enterprise Server.
func (*ActionsKeysError) Error ¶ added in v1.3.8
func (e *ActionsKeysError) Error() string
func (*ActionsKeysError) Is ¶ added in v1.3.8
func (e *ActionsKeysError) Is(target error) bool
type ActionsTokenVerifier ¶ added in v1.3.8
type ActionsTokenVerifier struct {
// contains filtered or unexported fields
}
ActionsTokenVerifier checks an Actions OIDC token's signature against the issuer's published keys, and its issuer, audience and expiry.
func NewActionsTokenVerifier ¶ added in v1.3.8
func NewActionsTokenVerifier(issuer string) *ActionsTokenVerifier
NewActionsTokenVerifier verifies tokens from issuer; production passes ActionsIssuer, and tests a local one. The keys are fetched on first use and cached by go-oidc, which refetches when it sees a key it does not know -- GitHub rotates them.
func (*ActionsTokenVerifier) Verify ¶ added in v1.3.8
func (v *ActionsTokenVerifier) Verify(ctx context.Context, raw, audience string) (*ActionsClaims, error)
Verify returns the claims of a token minted for audience. The audience is the controller's own upload address, so a token minted for one Zoomies is useless against another.
type AgentGuidanceClient ¶ added in v1.3.8
type AgentGuidanceClient interface {
ReadAgentGuidance(context.Context, string, string) (*ContextSetupSource, error)
OpenContextSetup(context.Context, ContextSetupRequest) (*PullRequest, error)
}
AgentGuidanceClient reads evidence for a guidance proposal independently of AI Context registration and never needs Workflows write permission.
type AppFactory ¶
type AppFactory struct {
// contains filtered or unexported fields
}
AppFactory builds GitHub clients that authenticate as a GitHub App installation.
One factory is shared by the whole process so that every installation reuses the same TCP connection pool.
func NewAppFactory ¶
func NewAppFactory(httpClient *http.Client) *AppFactory
NewAppFactory returns a factory that dials GitHub with httpClient. Passing nil uses a client with a request timeout, which is what production wants; tests pass their own to point at a fake.
func (*AppFactory) For ¶
func (f *AppFactory) For(ctx context.Context, inst *store.Installation, privateKeyPEM []byte) (Client, error)
For returns a Client scoped to one installation, authenticated with the App's private key.
privateKeyPEM is passed in rather than read from the installation because the store keeps it sealed; unsealing is the caller's job and the plaintext key never lives longer than this call needs it to.
type AppInfo ¶
type AppInfo struct {
ID int64
Slug string
Name string
Owner string
// Permissions is what the App was granted, so setup can tell the operator
// exactly which permission is missing rather than "403".
Permissions map[string]string
Events []string
// RepositorySelection is GitHub's own word for how much of the target this
// installation covers: "all" or "selected". It is the difference between a
// pool that will see every repository somebody pushes to and one that
// silently sees none of them -- the second commonest setup mistake after a
// missing permission, and the one nothing in Zoomies could previously say
// anything about.
RepositorySelection string
}
AppInfo describes the authenticated GitHub App.
func (*AppInfo) CanReadContents ¶
CanReadContents reports whether this installation may read a repository's files at all.
It is a lower bar than MissingForMigration, and deliberately so: opening a pull request needs Contents at write, but merely reading a repository's workflows needs only read. The difference matters because GitHub answers 404 -- not 403 -- for contents an App may not see, which is the same answer it gives for a repository that genuinely has no .github/workflows. Without this, an App that was never granted Contents makes every repository in the organisation look empty.
func (*AppInfo) MissingForMigration ¶
MissingForMigration lists, in words, the permissions this App still lacks before it can open a pull request. It is empty when the App can.
A permission GitHub does not report at all is treated as missing: an older GHES that omits a key from the installation's permissions is not a reason to try the write and fail halfway through, having already created a branch.
func (*AppInfo) MissingRequirements ¶
func (a *AppInfo) MissingRequirements(kind store.TargetType) []string
MissingRequirements lists, in words an operator can act on, what this App is still missing for the given target type. It is empty when the App is correctly configured.
func (*AppInfo) SubscribedToJobs ¶
SubscribedToJobs reports whether the App will be told about queued jobs.
It is deliberately not one of MissingRequirements: every one of those is a permission the fleet cannot work without, and this is a subscription the fleet works without -- more slowly, on the fallback poller, which is a supported way to run. Bundling the two made a probe return an error for an App with every permission it needs, so an installation that was merely slower was recorded as unhealthy and the verify dialog's own "will run on the fallback poller" sentence could never be reached.
type CheckoutEvidence ¶ added in v1.3.8
type CheckoutEvidence struct {
Version string
Basis string
Repository string
Selection CheckoutSelection
StepNumber int64
BlobSHA string
JobNameSHA256 string
StepNameSHA256 string
Source optimisation.Source
Timing optimisation.Timing
}
This private source-bearing view associates a push file and uniquely named API step. It is descriptive evidence, not an attestation of executed bytes.
type CheckoutEvidenceReader ¶ added in v1.3.8
type CheckoutEvidenceReader interface {
ReadCheckoutEvidence(context.Context, string, CheckoutSelection) (*CheckoutEvidence, error)
}
type CheckoutSelection ¶ added in v1.3.8
type CheckoutSelection struct {
RunID int64
Attempt int
GitHubJobID int64
JobIndex int
StepIndex int
// Locate asks the reader to find the one previewable checkout step in the
// selected job from the source it reads, so nobody counts positions in a
// file. The positions must be zero when it is set, and the evidence that
// comes back carries the ones it found with Locate cleared.
Locate bool
}
type Client ¶
type Client interface {
// Target returns the org or repo this client acts on.
Target() (name string, kind store.TargetType)
// Probe verifies the credentials and permissions, returning an error whose
// message names the missing permission where GitHub tells us.
Probe(ctx context.Context) (*AppInfo, error)
// CreateJITConfig mints an ephemeral runner registration.
CreateJITConfig(ctx context.Context, req JITRequest) (*JITConfig, error)
// CreateRegistrationToken mints a one-hour token for config.sh.
CreateRegistrationToken(ctx context.Context) (*RegistrationToken, error)
// CreateRemoveToken mints a token for deregistering a runner from the host.
CreateRemoveToken(ctx context.Context) (*RegistrationToken, error)
// ListRunners returns every self-hosted runner registered on the target.
ListRunners(ctx context.Context) ([]Runner, error)
// DeleteRunner removes a registration. Deleting one that is already gone
// returns nil, because the desired end state has been reached.
DeleteRunner(ctx context.Context, id int64) error
// ListRunnerGroups returns the target's runner groups.
ListRunnerGroups(ctx context.Context) ([]RunnerGroup, error)
// CreateRunnerGroup creates an organisation runner group. Repository
// targets reject it because GitHub only supports groups at organisation
// scope.
CreateRunnerGroup(ctx context.Context, req RunnerGroupCreate) (*RunnerGroup, error)
// ListQueuedJobs is the webhook fallback: it walks recent workflow runs and
// returns the jobs still waiting for a runner and the ones running on one.
// Status tells the two apart.
ListQueuedJobs(ctx context.Context) ([]QueuedJob, error)
// GetWorkflowJob reconciles a known job even after its run has completed.
GetWorkflowJob(ctx context.Context, repo string, id int64) (*WorkflowJobEvent, error)
// GetWorkflowRun reads the run-level status used to distinguish one
// cancelled job from cancellation of the entire workflow run.
GetWorkflowRun(ctx context.Context, repo string, runID int64) (*WorkflowRunState, error)
// CancelWorkflowRun asks GitHub to cancel the entire run containing a job.
// Force bypasses conditions that may otherwise keep a run cancelling.
CancelWorkflowRun(ctx context.Context, repo string, runID int64, force bool) error
// RerunWorkflowJob asks GitHub to run one job again, together with any
// job that declares it in `needs`. It is the narrower of the two and the
// one to reach for: a job re-run on its own account should not drag the
// rest of the run's failures with it.
RerunWorkflowJob(ctx context.Context, repo string, jobID int64) error
// RerunFailedWorkflowJobs asks GitHub to run a run's failed jobs again.
// Like cancellation it is run-level: GitHub reruns the failed jobs of a
// run together, so a caller must be clear about what else goes with it.
// It is the fallback for a job whose GitHub job ID this fleet never saw.
RerunFailedWorkflowJobs(ctx context.Context, repo string, runID int64) error
// RateLimit reports remaining quota.
RateLimit(ctx context.Context) (*RateLimit, error)
// WebURL returns the browser URL for the target.
WebURL() string
// ListRepositories returns the repositories this installation can see.
ListRepositories(ctx context.Context, limit int) ([]Repository, error)
// ListWorkflows returns the workflow files at the top of a repository's
// .github/workflows, with their contents.
ListWorkflows(ctx context.Context, repo string) ([]WorkflowFile, error)
// ReadReadme returns the README GitHub renders on the repository's front
// page, or ErrNoReadme when there is none. It is where the badge goes.
ReadReadme(ctx context.Context, repo string) (*Readme, error)
// OpenPullRequest commits a set of files to a new branch and opens a pull
// request for it.
OpenPullRequest(ctx context.Context, req PullRequestRequest) (*PullRequest, error)
}
Client is the GitHub surface Zoomies uses, scoped to one installation.
It is an interface so that tests can run the whole controller against a fake GitHub without a network, and so that GHES differences stay behind one seam.
type ContextIngestionClient ¶ added in v1.3.8
type ContextIngestionClient interface {
ReadContextPublication(context.Context, string, string) (*ContextPublication, error)
ContextSetupMerged(context.Context, string, int, string) (bool, error)
VerifyContextSnapshot(ctx context.Context, repo, branch, commit string, snapshot *aicontext.Snapshot) error
}
ContextIngestionClient is read-only and does not widen runner permissions.
type ContextPublication ¶ added in v1.3.8
type ContextPublication struct {
Source *ContextSetupSource
OutputCommit string
Snapshot *aicontext.Snapshot
}
type ContextRun ¶ added in v1.3.8
type ContextRun struct {
ID int64
URL string
CreatedAt time.Time
UpdatedAt time.Time
Facts aicontext.RunFacts
// JobIDs are GitHub's ids for Facts.Jobs, in the same order.
JobIDs []int64
}
ContextRun is one run, in the terms aicontext.Diagnose reads.
type ContextRunReader ¶ added in v1.3.8
type ContextRunReader interface {
// LatestContextRun is the most informative run of the managed workflow for a
// commit, or nil when it has none.
LatestContextRun(ctx context.Context, repo, commit string) (*ContextRun, error)
// ContextJobErrors is the error lines of one job's log, bounded. It is nil,
// with no error, when the job has no log: it never started, or the log has
// expired.
ContextJobErrors(ctx context.Context, repo string, jobID int64) ([]string, error)
// ContextArtifactUsage is what the repository's unexpired artifacts hold.
ContextArtifactUsage(ctx context.Context, repo string) (*aicontext.ArtifactUsage, error)
}
ContextRunReader reads what the managed workflow did, so that a context that has not been published can be explained instead of only noticed. It is read-only and needs the Actions read permission the App already has for the fleet; starting a run is ContextWorkflowDispatcher's, and separate.
type ContextSetupClient ¶ added in v1.3.8
type ContextSetupClient interface {
ReadContextSetup(context.Context, string, string) (*ContextSetupSource, error)
OpenContextSetup(context.Context, ContextSetupRequest) (*PullRequest, error)
}
ContextSetupClient keeps source setup apart from the runner-only interface.
type ContextSetupRequest ¶ added in v1.3.8
type ContextSetupSource ¶ added in v1.3.8
type ContextSetupSource struct {
Repository Repository
Commit string
Inventory []string
Files []aicontext.SetupFile
}
type ContextSetupStatusClient ¶ added in v1.3.8
type ContextWorkflowDispatcher ¶ added in v1.3.8
type ContextWorkflowDispatcher interface {
DispatchContextWorkflow(ctx context.Context, repo, branch string) error
}
ContextWorkflowDispatcher starts the managed workflow by hand. It is a separate interface because it is the one AI Context call that writes to GitHub Actions, and so needs the Actions write permission the rest of this file never asks for.
type ExperimentAttemptEvidence ¶ added in v1.3.8
type ExperimentAttemptEvidence struct {
WorkflowSource *ExperimentWorkflowSource `json:"workflow_source,omitempty"`
// These are descriptive attempt metadata, not workflow contents or inputs.
Event string `json:"event,omitempty"`
WorkflowPath string `json:"workflow_path,omitempty"`
Repository string `json:"repository"`
RunID int64 `json:"run_id"`
Attempt int `json:"attempt"`
HeadSHA string `json:"head_sha"`
WorkflowID int64 `json:"workflow_id"`
Status string `json:"status"`
Conclusion string `json:"conclusion"`
RunStartedAt *time.Time `json:"run_started_at"`
UpdatedAt *time.Time `json:"updated_at"`
JobsCoverage string `json:"jobs_coverage"`
Jobs []ExperimentAttemptJob `json:"jobs"`
}
type ExperimentAttemptJob ¶ added in v1.3.8
type ExperimentAttemptReader ¶ added in v1.3.8
type ExperimentAttemptReader interface {
ReadExperimentAttempt(context.Context, string, int64, int) (*ExperimentAttemptEvidence, error)
}
ExperimentAttemptReader is optional and on demand. Controller admission and the caller's repository permission must be checked before spending its reads.
type ExperimentWorkflowSource ¶ added in v1.3.8
type ExperimentWorkflowSource struct {
Dependencies *workflow.DependencyInventory `json:"dependencies,omitempty"`
Version string `json:"version"`
Basis string `json:"basis"`
CommitSHA string `json:"commit_sha"`
Path string `json:"path"`
BlobSHA string `json:"blob_sha"`
ContentSHA256 string `json:"content_sha256"`
Bytes int `json:"bytes"`
}
ExperimentWorkflowSource describes only the root file read at a push commit. Its digest does not cover reusable workflows, actions, inputs or test scope.
type Factory ¶
type Factory interface {
For(ctx context.Context, inst *store.Installation, privateKeyPEM []byte) (Client, error)
}
Factory builds a Client for an installation. The controller holds one factory and caches clients per installation.
type FakeActionsIssuer ¶ added in v1.3.8
type FakeActionsIssuer struct {
URL string
// contains filtered or unexported fields
}
FakeActionsIssuer stands in for token.actions.githubusercontent.com: it publishes a key set and signs tokens with the matching private key, so the verifier is exercised end to end without the network.
func NewFakeActionsIssuer ¶ added in v1.3.8
func NewFakeActionsIssuer(t fakeT) *FakeActionsIssuer
type FakeActionsSettings ¶ added in v1.3.8
type FakeActionsSettings struct {
// DefaultTokenWrite makes the default workflow token read and write.
DefaultTokenWrite bool
// ForkApproval is the approval policy word for a public repository; empty
// is "first_time_contributors".
ForkApproval string
// The three answers for a private repository's fork pull requests.
PrivateForkRuns, PrivateForkSecrets, PrivateForkWriteToken bool
}
FakeActionsSettings is what the fake answers for a repository's Actions settings. A repository it has not been told about has the read-only default token and the policy GitHub applies when nobody has changed it.
type FakeArtifact ¶ added in v1.3.8
FakeArtifact is one artifact in a repository.
type FakeContextJob ¶ added in v1.3.8
type FakeContextJob struct {
Name string
Conclusion string
Steps []FakeContextStep
// Log is the job's log. Empty means it has none, which GitHub answers with a
// 404 for a job that never started.
Log string
}
FakeContextJob is one job of a run.
type FakeContextRun ¶ added in v1.3.8
type FakeContextRun struct {
// Commit is the head commit the run was for.
Commit string
// Status is queued, in_progress or completed; empty means completed.
Status string
Conclusion string
// Finished is when the run ended; empty means now. A test that needs a
// failure from hours ago says so here rather than waiting for it.
Finished time.Time
Jobs []FakeContextJob
}
FakeContextRun is one run of a repository's managed workflow.
type FakeContextStep ¶ added in v1.3.8
type FakeContextStep struct{ Name, Conclusion string }
FakeContextStep is one step of a job.
type FakeGitHub ¶
type FakeGitHub struct {
// contains filtered or unexported fields
}
FakeGitHub is an in-memory stand-in for the GitHub REST API.
It lives in the production package rather than a _test.go file on purpose: the controller, scheduler and API tests all need a GitHub to run the whole system against, and a second, subtly different fake in each of them would be worse than one honest fake here. "Honest" means it enforces the rules that actually bite -- runner names must be unique, a deleted runner is gone, a job only appears in a run's job list -- so a test that passes against it has some claim to passing against GitHub.
It serves both the github.com layout (/app) and the GitHub Enterprise Server layout (/api/v3/app), so the same fake exercises both code paths.
Pagination is not implemented: every list returns one page. Tests that need to exercise paging should drive appClient against their own handler.
func NewFake ¶
func NewFake() *FakeGitHub
NewFake starts a fake GitHub and returns it. The caller must Close it.
func (*FakeGitHub) AddArtifacts ¶ added in v1.3.8
func (f *FakeGitHub) AddArtifacts(repo string, artifacts ...FakeArtifact)
AddArtifacts adds artifacts to a repository.
func (*FakeGitHub) AddBranchRule ¶ added in v1.3.8
func (f *FakeGitHub) AddBranchRule(repo, branch string, checks ...FakeRequiredCheck)
AddBranchRule adds one rule, requiring these checks, to the rules that apply to a branch. Call it again for another rule.
func (*FakeGitHub) AddContextRun ¶ added in v1.3.8
func (f *FakeGitHub) AddContextRun(repo string, run FakeContextRun) int64
AddContextRun records a run of repo's managed workflow and returns its id. Runs are listed newest first by id, so the order they were added is their age.
func (*FakeGitHub) AddFile ¶
func (f *FakeGitHub) AddFile(repo, path, content string)
AddFile puts any file in a repository, creating the repository if this is the first thing in it. It is AddWorkflow under a name that does not lie about a README.
func (*FakeGitHub) AddQueuedJob ¶
func (f *FakeGitHub) AddQueuedJob(repo, workflow, jobName string, labels []string) QueuedJob
AddQueuedJob queues a job on repo ("owner/name") and returns it as the API will report it. The repository becomes visible to the installation.
func (*FakeGitHub) AddRepo ¶
func (f *FakeGitHub) AddRepo(fullName string)
AddRepo makes "owner/name" visible to the installation. AddQueuedJob does this for you; call it directly for a repository with no jobs.
func (*FakeGitHub) AddRunner ¶
func (f *FakeGitHub) AddRunner(name string, labels []string) Runner
AddRunner registers a runner that Zoomies did not create, which is how a test simulates a registration the controller has lost track of.
func (*FakeGitHub) AddRunnerGroup ¶
func (f *FakeGitHub) AddRunnerGroup(name string) RunnerGroup
AddRunnerGroup registers a runner group and returns it.
func (*FakeGitHub) AddWorkflow ¶
func (f *FakeGitHub) AddWorkflow(repo, path, content string)
AddWorkflow puts a workflow file in a repository, creating the repository if this is the first thing in it.
func (*FakeGitHub) AppID ¶
func (f *FakeGitHub) AppID() int64
AppID is the App ID the fake claims to be.
func (*FakeGitHub) Branches ¶
func (f *FakeGitHub) Branches(repo string) []string
Branches returns a repository's branch names, sorted.
func (*FakeGitHub) ClearErrors ¶
func (f *FakeGitHub) ClearErrors()
ClearErrors removes every failure injected with SetError.
func (*FakeGitHub) Client ¶
func (f *FakeGitHub) Client(target string, kind store.TargetType) Client
Client returns a Client wired to this fake for the given target. It is the quickest way for a test to get a working github.Client.
func (*FakeGitHub) CompleteJob ¶
func (f *FakeGitHub) CompleteJob(jobID int64, conclusion string)
CompleteJob finishes a job with the given conclusion ("success", "failure", "cancelled", ...).
func (*FakeGitHub) ContextDispatches ¶ added in v1.3.8
func (f *FakeGitHub) ContextDispatches(repo string) int
ContextDispatches is how many times the fake has been asked to run a repository's managed workflow by hand.
func (*FakeGitHub) ContextLogAuthorizations ¶ added in v1.3.8
func (f *FakeGitHub) ContextLogAuthorizations() []string
ContextLogAuthorizations is the Authorization header of each log download.
func (*FakeGitHub) ContextRunJobID ¶ added in v1.3.8
func (f *FakeGitHub) ContextRunJobID(runID int64, i int) int64
ContextRunJobID is the id of the i'th job of a run added with AddContextRun.
func (*FakeGitHub) FileContent ¶
func (f *FakeGitHub) FileContent(repo, path string) (string, bool)
FileContent returns what a repository's file holds now, which is how a test checks what a migration actually committed.
func (*FakeGitHub) InstallationID ¶
func (f *FakeGitHub) InstallationID() int64
InstallationID is the installation ID the fake serves.
func (*FakeGitHub) JobReruns ¶ added in v1.3.8
func (f *FakeGitHub) JobReruns() int
JobReruns is how many job-level re-run requests the fake has been sent.
func (*FakeGitHub) LoseNextContextPullResponse ¶ added in v1.3.8
func (f *FakeGitHub) LoseNextContextPullResponse(repo string)
func (*FakeGitHub) MergeContextPull ¶ added in v1.3.8
func (f *FakeGitHub) MergeContextPull(repo string, number int) bool
MergeContextPull models the reviewed setup becoming trusted branch content.
func (*FakeGitHub) NotModified ¶ added in v1.3.8
func (f *FakeGitHub) NotModified() int
NotModified is how many requests the fake has answered with a 304, which is how a test shows a repeated read really did not cost a full response.
func (*FakeGitHub) PullRequestBody ¶
func (f *FakeGitHub) PullRequestBody(repo string, number int) string
PullRequestBody returns the body a pull request was opened with, or "" for one that was never opened.
func (*FakeGitHub) RepositoryID ¶ added in v1.3.8
func (f *FakeGitHub) RepositoryID(fullName string) int64
RepositoryID is the numeric ID the fake gave a repository, so a test can say a run's head is in the same repository (the same ID) or in another (a different one).
func (*FakeGitHub) Requests ¶
func (f *FakeGitHub) Requests() []string
Requests returns every request the fake has served, as "GET /path". Tests use it to assert that a poll stayed within its call budget.
func (*FakeGitHub) Reruns ¶ added in v1.3.8
func (f *FakeGitHub) Reruns() int
Reruns is how many re-run requests the fake has been sent, so a test can assert that asking twice asked GitHub twice -- or, where the controller refuses, that it asked nothing at all.
func (*FakeGitHub) Runners ¶
func (f *FakeGitHub) Runners() []Runner
Runners returns a snapshot of the registered runners.
func (*FakeGitHub) Server ¶
func (f *FakeGitHub) Server() *httptest.Server
Server exposes the underlying test server, for tests that need its URL or its TLS-aware http.Client.
func (*FakeGitHub) SetActionsSettings ¶ added in v1.3.8
func (f *FakeGitHub) SetActionsSettings(repo string, s FakeActionsSettings)
SetActionsSettings replaces what the fake answers for a repository's Actions settings.
func (*FakeGitHub) SetArchived ¶
func (f *FakeGitHub) SetArchived(repo string, archived bool)
SetArchived archives or unarchives a repository, creating it if this is the first thing said about it.
func (*FakeGitHub) SetBranchProtection ¶ added in v1.3.8
func (f *FakeGitHub) SetBranchProtection(repo, branch string, checks ...FakeRequiredCheck)
SetBranchProtection gives a branch classic protection requiring these checks. With none, the branch is protected and requires nothing.
func (*FakeGitHub) SetContextBranch ¶ added in v1.3.8
func (f *FakeGitHub) SetContextBranch(repo, branch string, contents map[string]string)
SetContextBranch publishes complete immutable objects without touching source.
func (*FakeGitHub) SetDefaultBranch ¶
func (f *FakeGitHub) SetDefaultBranch(repo, branch string)
SetDefaultBranch names a repository's default branch. It is "main" until something says otherwise.
func (*FakeGitHub) SetDelay ¶ added in v1.3.8
func (f *FakeGitHub) SetDelay(method, pattern string, d time.Duration)
SetDelay holds every request matching method (empty for any) and pattern open for d before it is served, so a test can drive a GitHub call that takes real wall-clock time.
func (*FakeGitHub) SetError ¶
func (f *FakeGitHub) SetError(pattern string, status int, message string)
SetError makes every request whose path contains pattern fail with status and message. An empty pattern matches every request. Calls accumulate; use ClearErrors to reset.
A 403 carrying the rate-limit headers (see SetRateLimit) is how a test produces ErrRateLimited, because that is how GitHub signals it.
func (*FakeGitHub) SetEvents ¶
func (f *FakeGitHub) SetEvents(events ...string)
SetEvents replaces the App's event subscriptions.
func (*FakeGitHub) SetFileMode ¶ added in v1.3.8
func (f *FakeGitHub) SetFileMode(repo, path, mode string)
func (*FakeGitHub) SetLegacyBranchProtection ¶ added in v1.3.8
func (f *FakeGitHub) SetLegacyBranchProtection(repo, branch string, contexts ...string)
SetLegacyBranchProtection is SetBranchProtection answering only the deprecated list of contexts, which names no app, as an older protection rule does.
func (*FakeGitHub) SetMethodError ¶
func (f *FakeGitHub) SetMethodError(method, pattern string, status int, message string)
SetMethodError is SetError narrowed to one HTTP method.
Several verbs share a path: an orphan sweep lists an installation's runners and then deletes them at the same one, so a test about a refused delete cannot afford to refuse the listing that finds them too.
func (*FakeGitHub) SetPermissions ¶
func (f *FakeGitHub) SetPermissions(p map[string]string)
SetPermissions replaces what the App reports being granted, so tests can exercise the "missing permission" reporting.
func (*FakeGitHub) SetRateLimit ¶
func (f *FakeGitHub) SetRateLimit(limit, remaining int, reset time.Time)
SetRateLimit sets the quota reported by /rate_limit and by the rate-limit headers on every response.
func (*FakeGitHub) SetRepairActor ¶ added in v1.3.8
func (f *FakeGitHub) SetRepairActor(login string, id int64, canWrite bool)
SetRepairActor registers the collaborators-permission answer for a login, so a repair-flow test decides who GitHub says the account is, and what they may do.
func (*FakeGitHub) SetRepositorySelection ¶
func (f *FakeGitHub) SetRepositorySelection(selection string)
SetRepositorySelection sets GitHub's "all" or "selected", so a test can describe an App installed on a handful of repositories rather than on everything.
func (*FakeGitHub) SetRunTrigger ¶ added in v1.3.8
func (f *FakeGitHub) SetRunTrigger(repo string, runID int64, event string, headRepositoryID int64)
SetRunTrigger gives a run the event that started it and the ID of the repository its head commit is in; zero means that repository was deleted. It makes the run readable even if no job of it was ever queued.
func (*FakeGitHub) SetRunnerBusy ¶ added in v1.3.8
func (f *FakeGitHub) SetRunnerBusy(name string, busy bool)
SetRunnerBusy marks a registered runner busy or idle, the same field GitHub's own runner list carries. Tests use it to reproduce the race where GitHub still considers a runner busy after its completion webhook already fired: deleting it is refused the way GitHub refuses it for real.
func (*FakeGitHub) SetRunnerGroupPublicAccess ¶
func (f *FakeGitHub) SetRunnerGroupPublicAccess(name string, allowed bool)
SetRunnerGroupPublicAccess changes the public-repository policy GitHub reports for a group. It exists for controller tests of the otherwise easy to miss state where a runner is online but no public-repository job is eligible to use it.
func (*FakeGitHub) SetVisibility ¶ added in v1.3.8
func (f *FakeGitHub) SetVisibility(fullName, visibility string)
SetVisibility says whether a repository is "public", "private" or "internal". A repository the fake has not been told about is private, which is what it has always answered.
func (*FakeGitHub) SetWorkflowRunState ¶ added in v1.3.8
func (f *FakeGitHub) SetWorkflowRunState(runID int64, status, conclusion string)
SetWorkflowRunState changes the run record independently of its jobs. That distinction lets controller tests prove that one cancelled stage does not fan cancellation out to the rest of the workflow.
func (*FakeGitHub) StartJob ¶
func (f *FakeGitHub) StartJob(jobID int64, runnerName string)
StartJob marks a queued job as picked up by runnerName.
func (*FakeGitHub) URL ¶
func (f *FakeGitHub) URL() string
URL is the API base URL to hand to an installation.
type FakeRequiredCheck ¶ added in v1.3.8
FakeRequiredCheck is a status check a branch requires. AppID is the app that must post it, and zero means any app.
type FileChange ¶
type FileChange struct {
Path string
Content string
// SHA is the blob SHA the change was computed against.
SHA string
}
FileChange is one file a pull request writes.
type JITConfig ¶
type JITConfig struct {
// Encoded is the base64 configuration.
Encoded string
// RunnerID is the ID GitHub assigned, so Zoomies can delete the
// registration later if the runner never comes up.
RunnerID int64
// Name echoes the requested name.
Name string
}
JITConfig is the base64 blob handed to `actions-runner --jitconfig`. It registers an ephemeral runner exactly once and expires quickly, which is what makes it safe to pass through an environment variable.
type JITRequest ¶
type JITRequest struct {
// Name must be unique within the target. GitHub rejects reuse.
Name string
// Labels are the custom labels this runner advertises, without the
// implicit self-hosted/os/arch set.
Labels []string
// RunnerGroupID is 1 (Default) unless the pool names a group.
RunnerGroupID int64
// WorkFolder defaults to "_work".
WorkFolder string
}
JITRequest asks GitHub for a just-in-time runner configuration.
type KennelGuidanceInventory ¶ added in v1.3.8
type KennelGuidanceInventory struct {
Paths []string
Files []WorkflowFileRef
Skipped []WorkflowFileRef
Partial bool
TreePartial bool
}
type KennelGuidanceReader ¶ added in v1.3.8
type KennelPrivateFork ¶ added in v1.3.8
type KennelPrivateFork struct {
// Runs is true when fork pull requests run workflows at all.
Runs bool
// Secrets is true when they are given the repository's secrets and variables.
Secrets bool
// WriteToken is true when they are given a token that can write.
WriteToken bool
}
KennelPrivateFork is a private repository's policy for fork pull requests.
type KennelProtection ¶ added in v1.3.8
type KennelProtection struct {
// Required are the checks pinned to the GitHub Actions app, from every source
// that requires them, sorted and without repeats. They are names a
// repository chose, so the controller compares them with job names and keeps
// them out of every sentence.
Required []string
// Unpinned counts the required checks that name no app or another one, or
// that one source leaves open to any app. They are counted and never judged.
Unpinned int
// Partial is set when one half could not be read or a limit was reached, so
// the list may be short.
Partial bool
}
KennelProtection is the required status checks of one branch.
type KennelRun ¶ added in v1.3.8
type KennelRun struct {
ID int64
// Event is GitHub's name for what triggered the run, bounded to 64 bytes. The
// controller passes it through an allow-list before anything sees it.
Event string
// RepositoryID is the repository the run ran in; HeadRepositoryID is the one
// its head commit is in. They differ for a pull request from a fork, and a
// fork that has since been deleted has no head repository at all, which reads
// as zero.
RepositoryID int64
HeadRepositoryID int64
}
KennelRun is the four fields of a workflow run that the exposure checks read. Everything else on a run -- who triggered it, its branch, its title, the path of its workflow -- is written by whoever opened the pull request, and is never read, so there is nothing of theirs to leak.
type KennelSettings ¶ added in v1.3.8
type KennelSettings struct {
// DefaultTokenWrite is true when the default workflow token can write.
DefaultTokenWrite bool
// ForkApproval is GitHub's approval policy for a fork pull request from an
// outside contributor, for a public repository. It is empty when the policy
// was not read: a private repository, or a GitHub that lacks the endpoint.
ForkApproval string
// PrivateFork says what a private repository does with fork pull requests.
// It is nil for a public repository and when GitHub lacks the endpoint.
PrivateFork *KennelPrivateFork
// Partial is set when an endpoint this GitHub does not offer was left out, so
// a finding that needed it cannot be judged.
Partial bool
}
KennelSettings is the part of a repository's Actions settings that Kennel Club judges, as booleans and one policy word. Nothing a repository wrote is in it.
type KennelSettingsReader ¶ added in v1.3.8
type KennelSettingsReader interface {
// KennelSettings reads the Actions settings that govern a workflow's token
// and a fork's approval. public says which fork policy applies: a public
// repository has an approval policy, and a private one has the rules for
// fork pull requests. An internal repository is read as private.
KennelSettings(ctx context.Context, repo string, public bool) (*KennelSettings, error)
// KennelProtection reads the status checks a branch requires, from classic
// branch protection and from the rules that apply to the branch. Both are
// read, because a repository protected one way looks unprotected to a reader
// of the other.
KennelProtection(ctx context.Context, repo, branch string) (*KennelProtection, error)
}
KennelSettingsReader is optional, like the other Kennel Club readers: a client without it gives no settings coverage and needs no new capability. Everything on it is a GET, and nothing on it can write a setting.
Both methods need the App's Administration read permission, which GitHub offers no narrower form of, so it is asked for separately and only when the operator has switched the settings checks on.
type KennelSetup ¶ added in v1.3.8
KennelSetup is a file inventory, not a judgement about the files' contents. No raw path or content survives the reduction at this boundary.
type KennelSetupReader ¶ added in v1.3.8
type KennelSetupReader interface {
KennelSetup(context.Context, string, string) (*KennelSetup, error)
}
KennelSetupReader is optional: clients without Contents access still provide exposure and capacity facts, and never need a new write capability.
type KennelWorkflowInventory ¶ added in v1.3.8
type KennelWorkflowInventory struct {
Files []WorkflowFileRef
Skipped []WorkflowFileRef
Partial bool
}
KennelWorkflowInventory is the default branch's workflow files: those the collector may read, and those it may not, over the size or the file limit, so a file that is not judged is named rather than silently absent.
type KennelWorkflowReader ¶ added in v1.3.8
type ManifestCredentials ¶
type ManifestCredentials struct {
AppID int64
Slug string
Name string
// PEM is the App's private key. It is shown once and must be sealed
// immediately.
PEM string
// WebhookSecret is the secret GitHub generated for the App's webhook. It
// is the only copy: the manifest cannot ask for a particular one, and
// GitHub does not show it again.
WebhookSecret string
ClientID string
ClientSecret string
HTMLURL string
}
ManifestCredentials is everything GitHub hands back once the operator has created the App. Every field except HTMLURL is a secret or identifies one.
func ExchangeManifestCode ¶
func ExchangeManifestCode(ctx context.Context, apiBaseURL, code string) (*ManifestCredentials, error)
ExchangeManifestCode trades the temporary code GitHub redirected back with for the App's real credentials.
The code is valid for one hour and exactly one exchange, so a failure here means starting the manifest flow again rather than retrying.
type ManifestOptions ¶
type ManifestOptions struct {
// Name is the App's display name, unique across all of GitHub.
Name string
// URL is the App's homepage, shown on its GitHub page.
URL string
// WebhookURL is where GitHub delivers workflow_job events.
WebhookURL string
// Organization is the org the App is created under. Empty creates it on
// the operator's own account, which is the right choice for repo targets.
Organization string
// SetupURL is permanent App configuration: GitHub sends the operator here
// after every future install or reconfiguration of the App -- adding a
// repository to the installation, most commonly, years later. It has to be
// an address that still answers then.
SetupURL string
// RedirectURL is where GitHub returns during *this* handshake, carrying
// ?code=. Empty means "the same as SetupURL", which is right when the
// handshake is driven from the controller's own web UI.
//
// They are separate because the installer is not: it catches the callback
// on a temporary loopback listener that is gone minutes later, and writing
// that ephemeral port into setup_url left every App `zoomies init` ever
// created sending its operator to a refused connection.
RedirectURL string
// Public allows the App to be installed on accounts other than the one
// that created it. Zoomies defaults it off: a fleet controller's App has
// no business being installable by strangers.
Public bool
// AllowWorkflowCancellation raises Actions from read to write so Zoomies
// can cancel workflow runs. It is opt-in because write is not needed to run
// the fleet.
AllowWorkflowCancellation bool
// Migration adds the three repository permissions the migration wizard needs
// to rewrite a runs-on line and open a pull request for it: contents,
// pull_requests and workflows, all at write. It is opt-in because nothing
// else in Zoomies writes to a repository, so an App that carries them by
// default hands every installation write access to code and to CI workflows
// for a feature most of them will never use.
Migration bool
// KennelSettings adds Administration read to an organisation App, for the
// Kennel Club checks that read a repository's Actions settings and the status
// checks its default branch requires (kennel.settings_checks). GitHub offers
// no narrower permission for those settings, and it also lets an App read a
// repository's collaborators and configuration, which Kennel Club does not
// use, so it is asked for only when the operator says they want those checks.
// A repository App already holds Administration write, which includes read,
// so the option changes nothing for one.
KennelSettings bool
// KennelFiles adds Contents read, for the Kennel Club checks that read a
// repository's file inventory, its workflow files and its agent instruction
// files (kennel.repository_setup, kennel.workflow_checks and
// kennel.agent_guidance). A public repository needs none of it, and a private
// one is a coverage gap without it. It is asked for only when the operator says
// they want those checks, because read access to contents is read access to the
// code. It is not lowered when Migration already holds Contents write.
KennelFiles bool
}
ManifestOptions describes the App the installer asks GitHub to create.
type PRDelayCheck ¶ added in v1.3.8
type PRDelayEvidence ¶ added in v1.3.8
type PRDelayEvidence struct {
Number int
HeadSHA string
CurrentHeadSHA string
TargetBranch string
CurrentTargetBranch string
Open bool
StillOpen bool
Protection *KennelProtection `json:"-"`
ProtectionCoverage string
ChecksCoverage string
Checks []PRDelayCheck
}
PRDelayEvidence stays inside the controller. Required-check names are useful for matching but must not be serialised into an API or model payload.
type PRDelayReader ¶ added in v1.3.8
type PRDelayReader interface {
ReadPRDelay(context.Context, string, int) (*PRDelayEvidence, error)
}
PRDelayReader is an optional read-only capability. Implementing it does not start background reads or grant the caller a repository permission.
type PullRequest ¶
type PullRequest struct {
Number int `json:"number"`
HTMLURL string `json:"html_url"`
Branch string `json:"branch"`
}
PullRequest is what opening one produced.
type PullRequestRequest ¶
type PullRequestRequest struct {
// Repo is "owner/name".
Repo string
// Base is the branch to open against. Empty means the default branch.
Base string
// Head is the branch to create. It must not already exist.
Head string
Title string
Body string
CommitMessage string
Files []FileChange
}
PullRequestRequest describes the pull request to open.
type QueuedJob ¶
type QueuedJob struct {
ID int64
RunID int64
// RunNumber is GitHub's own sequential number for the workflow run --
// free from the same run listing this job was found in, unlike the
// webhook path, which has to ask for it separately.
RunNumber int64
Repo string
WorkflowName string
JobName string
Labels []string
QueuedAt time.Time
HTMLURL string
// RunnerName is set once GitHub has assigned the job.
RunnerName string
Status string
Conclusion string
StartedAt *time.Time
CompletedAt *time.Time
}
QueuedJob is a job the fallback poller found unfinished: waiting for a runner, or already running on one. It carries only what the scheduler needs to match it to a pool and, once it has started, to its runner.
type RateLimit ¶
RateLimit reports the installation's remaining API quota, which the UI shows on the Installations page.
type RateLimitedError ¶
type RateLimitedError struct {
// ResetAt is when the quota refills, zero when GitHub did not say.
ResetAt time.Time
// RetryAfter is the secondary limit's answer, which is a duration rather
// than an instant. Zero when unset.
RetryAfter time.Duration
Detail string
}
RateLimitedError is a refusal that says when to come back.
GitHub sends the answer on every rate-limited response -- a reset instant on the primary limit, a retry-after duration on the secondary one -- and it used to be formatted straight into the message and lost, leaving every caller to guess a fixed wait. Guessing long wastes quota that came back minutes ago; guessing short spends the next window on refusals. So the number is carried.
It wraps ErrRateLimited, so the callers that only ask "was this a rate limit" keep working through errors.Is and only the ones that want the time reach for errors.As.
func (*RateLimitedError) Error ¶
func (e *RateLimitedError) Error() string
func (*RateLimitedError) RetryAt ¶
RetryAt is when a caller may try this installation again, given the time it is asking at, and whether GitHub said anything at all.
A reset already in the past is no answer -- clocks drift, and a response can sit in a queue -- so it is reported as unknown and the caller keeps its own default rather than resuming into the same refusal.
func (*RateLimitedError) Unwrap ¶
func (e *RateLimitedError) Unwrap() error
type Readme ¶
type Readme struct {
// Path is repository-relative -- "README.md", but also "readme.markdown"
// or "README.rst", which is why the caller checks it before writing
// Markdown into it.
Path string
// SHA is the blob SHA, for the same reason a workflow file carries one.
SHA string
// Content is the decoded file.
Content string
}
Readme is a repository's README as GitHub finds it: whichever file at the root it would render on the repository's front page, whatever its case or extension.
type RegistrationToken ¶
RegistrationToken is the older credential, used by non-ephemeral pools that must run config.sh. It is valid for one hour.
type RepairActor ¶ added in v1.3.8
type RepairClient ¶ added in v1.3.8
type RepairClient interface {
RepairActor(context.Context, string, string) (RepairActor, error)
RepairPull(context.Context, string, int) (RepairPull, error)
RepairPullForRun(context.Context, string, int64) (RepairPull, error)
RepairSource(context.Context, string, RepairPull, bool) (*RepairSource, error)
RepairFailures(context.Context, string, RepairPull, int64) ([]string, error)
CommitRepair(context.Context, string, RepairPull, prrepair.Plan) (string, error)
RepairComment(context.Context, string, int, int64, string) (int64, error)
RepairReact(context.Context, string, int64, string) error
RepairChecks(context.Context, string, string) (string, error)
}
type RepairPull ¶ added in v1.3.8
type RepairPull struct {
Number int
AuthorID int64
Author string
Branch string
// Base is the branch the pull request merges into, which is not always the
// repository's default branch: it can target a release branch.
Base string
HeadSHA string
Title string
Body string
URL string
DefaultBranch string
Fork bool
Open bool
}
type RepairSource ¶ added in v1.3.8
type RepairTargetChanged ¶ added in v1.3.8
RepairTargetChanged describes an authoritative change since a failed run. Transport and permission errors never use this type: neither proves a fix.
func (*RepairTargetChanged) Error ¶ added in v1.3.8
func (e *RepairTargetChanged) Error() string
func (*RepairTargetChanged) Unwrap ¶ added in v1.3.8
func (e *RepairTargetChanged) Unwrap() error
type RepoReader ¶ added in v1.3.8
type RepoReader interface {
// KennelRepositories lists the repositories the installation can see, through
// the conditional client: Kennel Club asks again every refresh, and a listing
// GitHub says is unchanged costs nothing against the rate limit.
KennelRepositories(ctx context.Context, limit int) ([]Repository, error)
// KennelRun reads four fields of one workflow run, and no others.
KennelRun(ctx context.Context, repo string, runID int64) (*KennelRun, error)
}
RepoReader is everything Kennel Club asks GitHub, and it is read-only by construction: there is no method on it that writes, so no change to the controller can make Kennel Club write through it. It is not part of Client, for the reason ContextRunReader is not: a capability one feature uses does not belong on the interface every fake and every demo client has to satisfy, so the controller asks for it by type assertion and a client without it simply has no Kennel Club coverage to report.
type Repository ¶
type Repository struct {
ID int64 `json:"id"`
FullName string `json:"full_name"`
DefaultBranch string `json:"default_branch"`
Private bool `json:"private"`
Archived bool `json:"archived"`
HTMLURL string `json:"html_url"`
// Visibility is "public", "private" or "internal". GitHub sends it with every
// repository; an older GitHub Enterprise Server that does not is read from
// Private instead, so it is never empty.
Visibility string `json:"visibility"`
}
Repository is one repository an installation can see.
type Runner ¶
type Runner struct {
ID int64
Name string
OS string
Status string // online | offline
Busy bool
Labels []string
// Ephemeral is reported by GitHub for JIT-configured runners.
Ephemeral bool
}
Runner is a runner as GitHub sees it, used to reconcile Zoomies' view with reality and to clean up registrations Zoomies has lost track of.
type RunnerGroup ¶
type RunnerGroup struct {
ID int64
Name string
Default bool
Visibility string
// PublicRepositoryAccessKnown distinguishes an older GitHub Enterprise
// response that omitted the setting from a group that explicitly disables
// it. Treating both as false would raise a warning nobody could act on.
PublicRepositoryAccessKnown bool
AllowsPublicRepositories bool
RestrictedToWorkflows bool
}
RunnerGroup is a runner group in the target org.
type RunnerGroupCreate ¶
RunnerGroupCreate is the policy for a new organisation runner group.
type WorkflowFile ¶
type WorkflowFile struct {
// Path is repository-relative.
Path string
// SHA is the blob SHA. GitHub requires it to update the file, and it is
// what makes an update fail rather than clobber somebody's change.
SHA string
// Content is the decoded file.
Content string
}
WorkflowFile is one file under .github/workflows as it exists on the repository's default branch.
type WorkflowFileRef ¶ added in v1.3.8
WorkflowFileRef is one workflow file as the tree lists it. The SHA is the immutable object the collector reads; the path is a stranger's text, which the collector gates before it keeps it beside the evaluation and which never enters a stored finding.
type WorkflowJobEvent ¶
type WorkflowJobEvent struct {
// Action is "queued", "in_progress", "completed" or "waiting".
Action string
// InstallationID routes the delivery to one of Zoomies' installations.
InstallationID int64
JobID int64
RunID int64
Repo string // "acme/widgets"
WorkflowName string
JobName string
// Labels are the workflow's runs-on values, including implicit ones.
Labels []string
RunnerID int64
RunnerName string
Status string
Conclusion string
// QueuedAt is the job's created_at, i.e. when GitHub started looking for a
// runner.
QueuedAt time.Time
StartedAt time.Time
CompletedAt time.Time
HTMLURL string
// HeadBranch, HeadSHA and RunAttempt say what the job ran for.
HeadBranch string
HeadSHA string
RunAttempt int
// Steps are the job's steps as this delivery reports them. On a completed
// delivery every step carries its conclusion, which is what lets a failed
// job say where it failed.
Steps []store.JobStep
}
WorkflowJobEvent is the part of a workflow_job delivery Zoomies acts on.
It is a hand-written struct rather than go-github's event type because this payload is the load-bearing input of the whole scheduler: naming exactly the fields that matter keeps it obvious what a change to GitHub's payload would break.
func ParseWorkflowJob ¶
func ParseWorkflowJob(payload []byte) (*WorkflowJobEvent, error)
ParseWorkflowJob decodes a workflow_job delivery.
func (*WorkflowJobEvent) State ¶
func (e *WorkflowJobEvent) State() store.JobState
State maps the delivery's action onto the job state machine.
"waiting" means the job is held for a deployment review, which can take hours or days, and GitHub sends a real "queued" delivery once it is approved. It is its own state, ahead of queued in the lifecycle so the approval moves the job forward, and one the scheduler does not count: recording it as queued started a runner that idled out and was started again on the next pass, for as long as the review took.
func (*WorkflowJobEvent) ToJob ¶
func (e *WorkflowJobEvent) ToJob() *store.Job
ToJob maps the event onto the row the store keeps, stamping the timestamps that this transition is the first evidence of.
The returned Job has no ID: Store.UpsertJob keys on GitHubJobID and mints one for a job it has not seen before.
type WorkflowRunState ¶ added in v1.3.8
type WorkflowRunState struct {
Status string
Conclusion string
// RunNumber is the "#1009" GitHub's Actions UI shows next to the workflow
// name. workflow_job deliveries never carry it, which is what this lookup
// is for.
RunNumber int64
// Event is what started the run: push, pull_request, schedule and so on.
Event string
}
WorkflowRunState is GitHub's authoritative lifecycle for one workflow run. Status becomes "completed" before Conclusion is meaningful.
func (WorkflowRunState) Cancelled ¶ added in v1.3.8
func (s WorkflowRunState) Cancelled() bool
Cancelled reports whether GitHub has finished the whole run as cancelled. A cancelled workflow job by itself is deliberately not enough evidence.
Source Files
¶
- actions_oidc.go
- ai_context_ingestion.go
- ai_context_runs.go
- ai_context_setup.go
- app.go
- checkout_evidence.go
- client.go
- eli_repairs.go
- etag.go
- experiment_attempt.go
- experiment_source.go
- fake.go
- fake_actions_oidc.go
- fake_ai_context.go
- fake_ai_context_runs.go
- fake_kennel.go
- fake_kennel_settings.go
- fake_migrate.go
- kennel_guidance.go
- kennel_reader.go
- kennel_settings.go
- kennel_setup.go
- kennel_workflows.go
- manifest.go
- migrate.go
- pr_delay.go
- webhook.go