Skip to content

Commit

Permalink
Add support for validating one job in a workflow
Browse files Browse the repository at this point in the history
Extend the `ghasum verify` command with the ability to verify a single
job in a workflow. In particular, when the provided target ends with
`:suffix` the suffix will be stripped and used as the job id/key, the
rest of the target is then treated as the workflow to verify. This will
result in the verification scope being limited to only that job (in that
workflow).

The new behavior is tested primarily through `testscript` tests covering
both good weather and bad weather scenarios (this also improves the good
weather scenarios for verification by covering the scenario where only
some of the checksums are valid and only those are being validated for
both workflow and job verification). Additionally, local functionality
is unit tested in places where unit tests already exist.
  • Loading branch information
ericcornelissen committed Mar 14, 2024
1 parent 0207c4e commit c7a2a33
Show file tree
Hide file tree
Showing 10 changed files with 368 additions and 41 deletions.
12 changes: 9 additions & 3 deletions SPECIFICATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,20 @@ error.

If the checksum file exists the process shall read and parse it fully. If this
fails the process shall exit immediately. Else it shall recompute the checksums
(see [Computing Checksums]) for all actions in the repository using the same
hashing algorithm as was used for the stored checksums. It shall then compare
the computed checksums against the stored checksums.
(see [Computing Checksums]) for all actions in the target using the same hashing
algorithm as was used for the stored checksums. It shall compare the computed
checksums against the stored checksums.

If any of the checksums does not match or is missing the process shall exit with
a non-zero exit code, for usability all values should be compared (and all
mismatches reported) before exiting.

The "target" can be one of a: a repository, a workflow, or a job. If the target
is a repository, all actions used in all jobs in all workflows in the repository
will be considered. If the target is a workflow, only actions used in all jobs
in the workflow will be considered. If the target is a job, only actions used in
the job will be considered.

Redundant checksums are ignored by this process.

## Procedures
Expand Down
36 changes: 27 additions & 9 deletions cmd/ghasum/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ func cmdVerify(argv []string) error {
return err
}

c, err := cache.New(*flagCache, *flagNoCache)
if err != nil {
return errors.Join(errCache, err)
var job string
if i := strings.LastIndexByte(target, 0x3A); i >= 0 {
job = target[i+1:]
target = target[0:i]
}

stat, err := os.Stat(target)
Expand All @@ -66,10 +67,16 @@ func cmdVerify(argv []string) error {
target = repo
}

c, err := cache.New(*flagCache, *flagNoCache)
if err != nil {
return errors.Join(errCache, err)
}

cfg := ghasum.Config{
Repo: os.DirFS(target),
Path: target,
Workflow: workflow,
Job: job,
Cache: c,
}

Expand Down Expand Up @@ -97,15 +104,26 @@ func helpVerify() string {
Verify the Actions in the target against the stored checksums. If no target is
provided it will default to the current working directory. If the checksums do
not match this command will error with a non-zero exit code.
not match this command will error with a non-zero exit code. If ghasum is not
yet initialized this command errors (see "ghasum help init").
The target can be either a directory or a file. If it is a directory it must be
the root of a repository (that is, it should contain the .github directory). In
this case checksums will be verified for every workflow in the repository. If it
is a file it must be a workflow file in a repository. In this case checksums
will be verified only for the given workflow.
the root of a repository (that is, it should contain the .github directory). For
example:
ghasum verify my-project
In this case checksums will be verified for every workflow in the repository. If
it is a file it must be a workflow file in a repository. For example:
ghasum verify my-project/.github/workflows/workflow.yml
In this case checksums will be verified for all jobs in the given workflow. If
it is a file it may specify a job by using a ":job" suffix. For example:
ghasum verify my-project/.github/workflows/workflow.yml:job-key
If ghasum is not yet initialized this command errors (see "ghasum help init").
In this case checksums will be verified only for the given job in the workflow.
The available flags are:
Expand Down
15 changes: 12 additions & 3 deletions internal/gha/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,11 @@ func workflowsInRepo(repo fs.FS) ([][]byte, error) {
return nil
}

file, err := repo.Open(entryPath)
data, err := workflowInRepo(repo, entryPath)
if err != nil {
return fmt.Errorf("could not open workflow at %q: %v", entryPath, err)
return err
}

data, _ := io.ReadAll(file)
workflows = append(workflows, data)
return nil
}
Expand All @@ -87,3 +86,13 @@ func workflowsInRepo(repo fs.FS) ([][]byte, error) {

return workflows, nil
}

func workflowInRepo(repo fs.FS, path string) ([]byte, error) {
file, err := repo.Open(path)
if err != nil {
return nil, fmt.Errorf("could not open workflow at %q: %v", path, err)
}

data, _ := io.ReadAll(file)
return data, nil
}
44 changes: 41 additions & 3 deletions internal/gha/gha.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package gha

import (
"fmt"
"io/fs"
"path"
)
Expand Down Expand Up @@ -63,13 +64,50 @@ func RepoActions(repo fs.FS) ([]GitHubAction, error) {
return actions, nil
}

// WorkflowActions extracts the GitHub Actions used in the provided workflow.
func WorkflowActions(rawWorkflow []byte) ([]GitHubAction, error) {
w, err := parseWorkflow(rawWorkflow)
// WorkflowActions extracts the GitHub Actions used in the specified workflow at
// the given file system hierarchy.
func WorkflowActions(repo fs.FS, path string) ([]GitHubAction, error) {
data, err := workflowInRepo(repo, path)
if err != nil {
return nil, err
}

w, err := parseWorkflow(data)
if err != nil {
return nil, err
}

actions, err := actionsInWorkflows([]workflow{w})
if err != nil {
return nil, err
}

return actions, nil
}

// JobActions extracts the GitHub Actions used in the specified job in the
// specified workflow at the given file system hierarchy.
func JobActions(repo fs.FS, path, name string) ([]GitHubAction, error) {
data, err := workflowInRepo(repo, path)
if err != nil {
return nil, err
}

w, err := parseWorkflow(data)
if err != nil {
return nil, err
}

for job := range w.Jobs {
if job != name {
delete(w.Jobs, job)
}
}

if len(w.Jobs) == 0 {
return nil, fmt.Errorf("job %q not found in workflow %q", name, path)
}

actions, err := actionsInWorkflows([]workflow{w})
if err != nil {
return nil, err
Expand Down
Loading

0 comments on commit c7a2a33

Please sign in to comment.