githubactions

package
v1.39.1 Latest Latest
Warning

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

Go to latest
Published: Oct 6, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// RunnerWorkspaceEnvVar is the workspace directory, e.g. /home/runner/work/<repo>, whose
	// sibling is _actions.
	RunnerWorkspaceEnvVar = "RUNNER_WORKSPACE"
	// WorkflowRefEnvVar is the running workflow's ref path, e.g.
	// "octocat/hello-world/.github/workflows/ci.yml@main".
	WorkflowRefEnvVar = "GITHUB_WORKFLOW_REF"
	// JobIDEnvVar is the running job's job_id - its key under `jobs:` in the workflow YAML.
	JobIDEnvVar = "GITHUB_JOB"
	// GithubRepoEnvVar is the repository running the job, as "<owner>/<repo>".
	GithubRepoEnvVar = "GITHUB_REPOSITORY"
)

What GitHub Actions sets on every runner.

Variables

View Source
var ErrJobUnknown = errors.New("cannot identify the job being curated in the workflow file")

ErrJobUnknown reports that the job being curated could not be identified in this workflow file - either no job id was given, or the file does not declare the one that was. It is not a failure of the run: callers treat it as "cannot attribute" and curate the cache as-is.

View Source
var ErrWorkflowUnparsable = errors.New("cannot parse the workflow file")

ErrWorkflowUnparsable reports that the workflow file was read but could not be parsed as YAML. The runner already accepted this file, so it is a divergence between its YAML reader and ours rather than a broken workflow - the same position parseCompositeActionUses is in one level down, and handled the same way. It is not a failure of the run: callers treat it as "cannot attribute" and curate the cache as-is.

Functions

func CrossReference

func CrossReference(discovered []ActionRef, used JobUses) ([]ActionRef, []LocalUse)

CrossReference enriches discovered entries with Subpaths and best-effort Parent metadata, and returns the enriched slice along with every local step it met on the way out. The local steps are a coverage statement, not attribution.

A directly-used entry takes its Subpaths from the job's own uses: lines. Every other entry is attributed by parsing the action.yaml of composite actions.

An action key can be invoked through more than one metadata location - its cache root, and/or one or more subpaths - and not always by the same parent: two different composites may each reference the same child at a different subpath. Every distinct location any parent references is scanned, regardless of which parent gets credited as Parent; only the Parent field is first-wins.

Rounds are consumed by pairs, not by cache entries: one key contributes a pair per location it is referenced through, so a monorepo action reached through a chain of its own subpaths can need more rounds than the cache holds entries. Any bound derived from the entry count is therefore too small, and truncates silently - the unscanned subpath is still listed in Subpaths, so the result reads as complete.

KNOWN LIMITATION: an action pulling others in via a run: step rather than its own uses:, and actions used by a called reusable workflow (jobs.<id>.uses:), are never attributed - Parent stays empty, never guessed.

func DefaultActionsCacheDir

func DefaultActionsCacheDir() (string, error)

DefaultActionsCacheDir derives the runner's _actions cache path from RUNNER_WORKSPACE (<_work>/<repo>) - _actions is its sibling, i.e. dirname(RUNNER_WORKSPACE)/_actions.

func DefaultGithubRepo

func DefaultGithubRepo() string

DefaultGithubRepo returns the running job's repository from GITHUB_REPOSITORY ("<owner>/<repo>"), or "" when unset.

func DefaultJobID

func DefaultJobID() string

DefaultJobID returns the running job's job_id from GITHUB_JOB, or "" when unset.

func DefaultWorkflowFile

func DefaultWorkflowFile() string

DefaultWorkflowFile derives the repo-relative path of the running workflow from GITHUB_WORKFLOW_REF, whose shape is "<owner>/<repo>/<path/to/workflow.yml>@<ref>".

Returns "" - never an error - when the variable is unset or doesn't have that shape. An unrecognized value must not fail the command: the caller falls back to curating the action cache structure alone, without parent attribution.

func ErrCacheNotReadable

func ErrCacheNotReadable() error

ErrCacheNotReadable reports a cache that is absent, or that resolved to no action at all

func IsLocalUse

func IsLocalUse(raw string) bool

func RenderReportTable

func RenderReportTable(rows []ActionReportRow, withParent bool) string

RenderReportTable renders rows as the console report's table. withParent controls whether the Parent column appears at all.

Pipe-delimited like the job summary's table, so the two read alike and either can be pasted where the other is expected - but the cells are escaped for a terminal, because this one is printed to the job log and read there. The job summary renders its own table from the recorded summary files; the shared piece is the cell escaping, not the table.

Types

type ActionCacheScan

type ActionCacheScan struct {
	// Refs holds one entry per action the walk resolved.
	Refs []ActionRef
	// Unaccounted holds the entries it could not.
	Unaccounted []UnaccountedEntry
}

ActionCacheScan is what one walk of the runner's action cache found.

func DiscoverActionCache

func DiscoverActionCache(actionsCacheDir string) (ActionCacheScan, error)

DiscoverActionCache walks actionsCacheDir (the runner's _work/_actions root) and returns one ActionRef per action the runner resolved.

The runner downloads resolved actions here before the job's steps run, including transitive ones pulled in by another action's action.yml that never appear in the job's own workflow file - so the directory is the account of what actually resolved.

The layout is <owner>/<repo>/<ref>, but <ref> is a git ref and may contain "/" - a branch such as copilot/backport-v4 lands at <owner>/<repo>/copilot/backport-v4. So the depth of a ref is not fixed, and the walk asks the runner where each one ends.

An entry the walk understands but that holds no action - a stray file, an owner directory with no repositories, a watermark - is skipped. An entry it cannot examine, or cannot resolve to a ref, is recorded in Unaccounted instead.

func (ActionCacheScan) UnaccountedError

func (s ActionCacheScan) UnaccountedError() error

UnaccountedError returns an error naming every entry the walk could not resolve, or nil when there are none.

type ActionCurationDecider

type ActionCurationDecider interface {
	// Decide returns the curation outcome for one action reference under the policies of
	// artifactoryVcsRepo. A non-nil error means no decision was reached - distinct from
	// Rejected, and fatal to the command.
	Decide(ctx context.Context, artifactoryVcsRepo string, ref ActionRef) (ActionCurationResult, error)
}

ActionCurationDecider decides the curation outcome for a single action reference. Only the mock implementation exists till support exists at Artifactory/Catalog.

An implementation must normalize ref before looking it up: lower-case Owner and Repo, and a Ref that is a hex SHA. Discovery reports what the cache directory is named, and the runner names it verbatim from the uses: line, so one action reaches Decide under as many identities as the workflow spelled it. Measured on a hosted runner: `uses: Actions/Checkout@v4` alongside `uses: actions/checkout@v4` produces both _actions/Actions/Checkout/v4 and _actions/actions/checkout/v4, and the same commit pinned in upper- and lower-case hex produces two directories likewise. GitHub resolves either spelling, so both are the same action to curate - but a case-sensitive catalog lookup would give one of them a different verdict, or no verdict at all, and fail a job over a spelling. Normalizing here rather than in discovery keeps the verbatim casing that attribution matches uses: lines on (see refKey in workflow.go).

func NewMockActionCurationDecider

func NewMockActionCurationDecider() ActionCurationDecider

NewMockActionCurationDecider returns the name-parity stand-in decider.

type ActionCurationResult

type ActionCurationResult struct {
	Status ActionCurationStatus
	Notes  string
}

ActionCurationResult is the decision for one resolved action.

type ActionCurationStatus

type ActionCurationStatus string

ActionCurationStatus is the curation outcome for one action.

const (
	ActionApproved ActionCurationStatus = "Approved"
	ActionRejected ActionCurationStatus = "Rejected"
)

type ActionRef

type ActionRef struct {
	Owner string
	Repo  string
	// Ref is taken verbatim from the cache directory name; it may be a SHA, tag or branch.
	Ref string
	// Path is the absolute path to _work/_actions/<Owner>/<Repo>/<Ref>.
	Path string
	// Subpaths holds every distinct subpath the job invoked this action through - a monorepo
	// action such as github/codeql-action can be used via several from one owner/repo/ref.
	Subpaths []string
	// Parent is the composite action that pulled this one in, "" when unattributed.
	Parent string
}

ActionRef is one resolved action instance found in the runner's action cache.

type ActionReportRow

type ActionReportRow struct {
	Action string // "owner/repo", plus " (subpath[, subpath...])" when invoked via subpaths
	Ref    string // verbatim from the cache directory name, uninterpreted
	Parent string // "" when directly referenced, or when attribution could not place it
	Status string
	Notes  string
}

ActionReportRow is one row of the curation report, already resolved from an ActionRef and its ActionCurationResult.

func NewActionReportRow

func NewActionReportRow(ref ActionRef, result ActionCurationResult) ActionReportRow

NewActionReportRow builds one report row. A monorepo action invoked via several subpaths (codeql-action's init@v3 and analyze@v3) shares one cache entry and one decision, so it is one row - every subpath used is listed so neither invocation is silently lost.

func NotApproved

func NotApproved(rows []ActionReportRow) []ActionReportRow

NotApproved returns every row whose Status is not exactly ActionApproved, for the command's exit-code decision.

An allow-list, deliberately, rather than a test for ActionRejected: ActionCurationStatus is an open string type, so a status this code does not recognize - one a later decider introduces, or the zero value of a result returned without one - would pass a deny-list while rendering as an empty cell. Only an explicit approval may clear a gate whose purpose is to stop whatever it has not cleared. This is not part of the mocked seam; the real decider replaces the verdict, not the enforcement.

type ArtifactoryVcsRepoResolver

type ArtifactoryVcsRepoResolver interface {
	// Resolve returns the Artifactory VCS repository key for githubRepo ("<owner>/<repo>", the
	// shape GITHUB_REPOSITORY carries).
	//
	// githubRepo decides which policies judge the job, so its only legitimate source is the
	// runner's own GITHUB_REPOSITORY - see DefaultGithubRepo. The runner does not let a
	// workflow-, job- or step-level env: block override that variable, which is what makes it
	// trustworthy; a value reaching this call from anywhere a workflow author can write would
	// let the subject of the check choose the policies applied to it. SetGithubRepo exists for
	// tests and is deliberately not reachable from the CLI.
	Resolve(ctx context.Context, githubRepo string) (string, error)
}

ArtifactoryVcsRepoResolver maps the GitHub repository running the current job to the Artifactory VCS repository whose curation policies govern it - per onboarded repository. Only the mock implementation exists.

func NewMockArtifactoryVcsRepoResolver

func NewMockArtifactoryVcsRepoResolver() ArtifactoryVcsRepoResolver

NewMockArtifactoryVcsRepoResolver returns a resolver that derives the repository key from the GitHub owner.

type JobUses

type JobUses struct {
	// Remote holds the owner/repo/ref references this job declares, in file order.
	Remote []WorkflowUse
	// Local holds the `uses: ./...` steps, in file order, deduplicated per declarer.
	Local []LocalUse
}

JobUses is what one job's steps reference.

func ParseWorkflowUses

func ParseWorkflowUses(workflowPath, jobID string) (JobUses, error)

ParseWorkflowUses parses the step-level `uses:` values of ONE job in a workflow YAML file. Docker-URI actions (uses: docker://...) are skipped - the runner pulls those images during job setup rather than into the action cache. Local actions (uses: ./path) are not parsed as references either, but are returned in JobUses.Local so the report can declare them uncovered.

jobID must name a job the file declares; otherwise it returns ErrJobUnknown and parses nothing. There is deliberately no fallback to the file's other jobs: each ran on its own runner with its own cache, so attributing from them would label an entry with a parent that never pulled it in. Attribution therefore needs both a file and a job id - no constraint on a runner, where GITHUB_JOB is always set.

KNOWN FAILURE - a called reusable workflow can attribute against the wrong job because GITHUB_WORKFLOW_REF holds the caller file name and GITHUB_JOB holds the callee job id. only attribution error and is unfixed at the moment.

type LocalUse

type LocalUse struct {
	// Raw is the `uses:` value verbatim, e.g. "./.github/actions/setup".
	Raw string
	// DeclaredBy is the composite action that declares this step, as "<owner>/<repo>@<ref>"
	DeclaredBy string
}

LocalUse is one `uses: ./...` step the job will run, and who declared it.

type UnaccountedEntry

type UnaccountedEntry struct {
	Path   string
	Reason string
}

UnaccountedEntry is a cache entry that exists but could not be resolved to an action.

type WorkflowUse

type WorkflowUse struct {
	Owner   string
	Repo    string
	Subpath string // "" handle mono repo github actions, e.g. github/codeql-action/analyze@v3
	Ref     string
	Raw     string // the original "uses:" string, for diagnostics
}

WorkflowUse is one `uses:` value parsed out of a workflow (or composite action) YAML file.

Jump to

Keyboard shortcuts

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