Documentation
¶
Index ¶
- Constants
- Variables
- func CrossReference(discovered []ActionRef, used JobUses) ([]ActionRef, []LocalUse)
- func DefaultActionsCacheDir() (string, error)
- func DefaultGithubRepo() string
- func DefaultJobID() string
- func DefaultWorkflowFile() string
- func ErrCacheNotReadable() error
- func IsLocalUse(raw string) bool
- func RenderReportTable(rows []ActionReportRow, withParent bool) string
- type ActionCacheScan
- type ActionCurationDecider
- type ActionCurationResult
- type ActionCurationStatus
- type ActionRef
- type ActionReportRow
- type ArtifactoryVcsRepoResolver
- type JobUses
- type LocalUse
- type UnaccountedEntry
- type WorkflowUse
Constants ¶
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 ¶
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.
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 ¶
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 ¶
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 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 ¶
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 ¶
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.