Skip to content

Commit

Permalink
Add support for validating one workflow
Browse files Browse the repository at this point in the history
Extend the `ghasum verify` command with the ability to verify a single
workflow. In particular, when the provided target is a file instead of a
directory it will treat it as if it's a workflow file inside a repo and
use the repo as target and limit the verification scope to only that
workflow.

The new behavior is tested primarily through `testscript` tests covering
both good weather and bad weather scenarios (this also improves the bad
weather scenarios for verification by 1. mixing valid and invalid
checksums for the "Checksum mismatch" scenario, which is a more useful
scenario; and 2. avoiding invalid checksums in the "Checksum missing"
scenario to make it more focussed). Additionally, local functionality
is unit tested in places where unit tests already exist.
  • Loading branch information
ericcornelissen committed Feb 24, 2024
1 parent ed78adc commit 867e2af
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 18 deletions.
27 changes: 24 additions & 3 deletions cmd/ghasum/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"flag"
"fmt"
"os"
"path"
"path/filepath"
"strings"

"github.com/ericcornelissen/ghasum/internal/cache"
Expand Down Expand Up @@ -52,10 +54,23 @@ func cmdVerify(argv []string) error {
return errors.Join(errCache, err)
}

stat, err := os.Stat(target)
if err != nil {
return errors.Join(errUnexpected, err)
}

var workflow string
if !stat.IsDir() {
repo := path.Join(path.Dir(target), "..", "..")
workflow, _ = filepath.Rel(repo, target)
target = repo
}

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

problems, err := ghasum.Verify(&cfg)
Expand Down Expand Up @@ -84,6 +99,12 @@ 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.
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.
If ghasum is not yet initialized this command errors (see "ghasum help init").
The available flags are:
Expand Down
19 changes: 17 additions & 2 deletions internal/gha/gha.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ type GitHubAction struct {
// WorkflowsPath is the relative path to the GitHub Actions workflow directory.
var WorkflowsPath = path.Join(".github", "workflows")

// RepoActions extracts the GitHub RepoActions used in the repository at the
// given file system hierarchy.
// RepoActions extracts the GitHub Actions used in the repository at the given
// file system hierarchy.
func RepoActions(repo fs.FS) ([]GitHubAction, error) {
rawWorkflows, err := workflowsInRepo(repo)
if err != nil {
Expand All @@ -62,3 +62,18 @@ 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)
if err != nil {
return nil, err
}

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

return actions, nil
}
54 changes: 54 additions & 0 deletions internal/gha/gha_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package gha

import (
"fmt"
"slices"
"testing"

Expand Down Expand Up @@ -144,3 +145,56 @@ func TestRealisticRepository(t *testing.T) {
}
}
}

func TestWorkflowActions(t *testing.T) {
t.Parallel()

type TestCase struct {
workflow []byte
wantErr bool
}

testCases := []TestCase{
{
workflow: []byte(workflowWithNoJobs),
wantErr: false,
},
{
workflow: []byte(workflowWithJobNoSteps),
wantErr: false,
},
{
workflow: []byte(workflowWithJobWithSteps),
wantErr: false,
},
{
workflow: []byte(workflowWithJobsWithSteps),
wantErr: false,
},
{
workflow: []byte(workflowWithNestedActions),
wantErr: false,
},
{
workflow: []byte(workflowWithSyntaxError),
wantErr: true,
},
{
workflow: []byte(workflowWithInvalidUses),
wantErr: true,
},
}

for i, tc := range testCases {
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
t.Parallel()

_, err := WorkflowActions(tc.workflow)
if err == nil && tc.wantErr {
t.Error("Unexpected success")
} else if err != nil && !tc.wantErr {
t.Error("Unexpected failure")
}
})
}
}
30 changes: 28 additions & 2 deletions internal/ghasum/atoms.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package ghasum
import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"path"
Expand Down Expand Up @@ -75,12 +76,37 @@ func compare(got, want []sumfile.Entry) []Problem {
return cmp(toMap(got), toMap(want))
}

func compute(cfg *Config, algo checksum.Algo) ([]sumfile.Entry, error) {
actions, err := gha.RepoActions(cfg.Repo)
func find(cfg *Config) ([]gha.GitHubAction, error) {
var (
actions []gha.GitHubAction
err error
)

if cfg.Workflow == "" {
actions, err = gha.RepoActions(cfg.Repo)
} else {
var (
data []byte
file fs.File
)

file, err = cfg.Repo.Open(cfg.Workflow)
if err == nil {
data, err = io.ReadAll(file)
if err == nil {
actions, err = gha.WorkflowActions(data)
}
}
}

if err != nil {
return nil, fmt.Errorf("could not find GitHub Actions: %v", err)
}

return actions, nil
}

func compute(cfg *Config, actions []gha.GitHubAction, algo checksum.Algo) ([]sumfile.Entry, error) {
if err := cfg.Cache.Init(); err != nil {
return nil, fmt.Errorf("could not initialize cache: %v", err)
} else {
Expand Down
26 changes: 23 additions & 3 deletions internal/ghasum/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ type (
// non-read file system operation.
Path string

// Workflow is the file path (relative to Path) of the workflow that is the
// subject of the operation. If this has the zero value all workflows in the
// Repo will collectively be the subject of the operation instead.
Workflow string

// Cache is the cache that should be used for the operation.
Cache cache.Cache
}
Expand All @@ -61,7 +66,12 @@ func Initialize(cfg *Config) error {
}
}()

checksums, err := compute(cfg, checksum.BestAlgo)
actions, err := find(cfg)
if err != nil {
return err
}

checksums, err := compute(cfg, actions, checksum.BestAlgo)
if err != nil {
return err
}
Expand Down Expand Up @@ -104,7 +114,12 @@ func Update(cfg *Config) error {
return err
}

checksums, err := compute(cfg, checksum.BestAlgo)
actions, err := find(cfg)
if err != nil {
return err
}

checksums, err := compute(cfg, actions, checksum.BestAlgo)
if err != nil {
return err
}
Expand Down Expand Up @@ -145,7 +160,12 @@ func Verify(cfg *Config) ([]Problem, error) {
return nil, err
}

fresh, err := compute(cfg, checksum.Sha256)
actions, err := find(cfg)
if err != nil {
return nil, err
}

fresh, err := compute(cfg, actions, checksum.Sha256)
if err != nil {
return nil, err
}
Expand Down
31 changes: 29 additions & 2 deletions testdata/verify/error.txtar
Original file line number Diff line number Diff line change
@@ -1,15 +1,42 @@
# Repo without actions
# Repo that doesn't use GitHub Actions
! exec ghasum verify no-actions/
! stdout 'Ok'
stderr 'an unexpected error occurred'
stderr 'ghasum has not yet been initialized'

# Uninitialized repo with Actions
# Uninitialized repo that uses GitHub Actions
! exec ghasum verify uninitialized/
! stdout 'Ok'
stderr 'an unexpected error occurred'
stderr 'ghasum has not yet been initialized'

# Directory not found
! exec ghasum verify directory-not-found/
! stdout 'Ok'
stderr 'an unexpected error occurred'
stderr 'no such file or directory'

# Workflow not found
! exec ghasum verify initialized/.github/workflows/not-found.yml
! stdout 'Ok'
stderr 'an unexpected error occurred'
stderr 'no such file or directory'

-- initialized/.github/workflows/gha.sum --
version 1

actions/checkout@main PKruFKnotZi8RQ196H3R7c5bgw9+mfI7BN/h0A7XiV8=
-- initialized/.github/workflows/workflow.yml --
name: Example workflow
on: [push]

jobs:
example:
name: example
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@main
-- no-actions/.keep --
This file exists to create a repo that does not use Github Actions.
-- uninitialized/.github/workflows/workflow.yml --
Expand Down
20 changes: 16 additions & 4 deletions testdata/verify/problems.txtar
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
# Checksum mismatch
# Checksum mismatch - Repo
! exec ghasum verify -cache .cache/ mismatch/
stdout .
! stdout 'Ok'
! stderr .

# Checksum missing
# Checksum mismatch - Workflow
! exec ghasum verify -cache .cache/ mismatch/.github/workflows/workflow.yml
stdout .
! stdout 'Ok'
! stderr .

# Checksum missing - Repo
! exec ghasum verify -cache .cache/ missing/
stdout .
! stdout 'Ok'
! stderr .

# Checksum missing - Workflow
! exec ghasum verify -cache .cache/ missing/.github/workflows/workflow.yml
stdout .
! stdout 'Ok'
! stderr .

-- mismatch/.github/workflows/gha.sum --
version 1

actions/checkout@v4 xCHyD2IBscJ1q4pfZ3pVXntv0HgGM8tQrG2h3ZHQeuk=
actions/checkout@v4 oJp2lqI5zRjHTtu2vQ9/rfcqiYqRAnhqMjwnw/ss4x0=
actions/setup-go@v5 /ChzMZC1jsCd/aTotskAS7hl1cX9/M5XsV7mJHCMuME=
-- mismatch/.github/workflows/workflow.yml --
name: Example workflow
Expand All @@ -35,7 +47,7 @@ jobs:
-- missing/.github/workflows/gha.sum --
version 1

actions/checkout@v4 NQDx6JqrrKyfeOkbY1jy5XFTgfP3P/r9G9l17ENtfBA=
actions/checkout@v4 oJp2lqI5zRjHTtu2vQ9/rfcqiYqRAnhqMjwnw/ss4x0=
-- missing/.github/workflows/workflow.yml --
name: Example workflow
on: [push]
Expand Down
14 changes: 12 additions & 2 deletions testdata/verify/success.txtar
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
# Checksums match exactly
# Checksums match exactly - Repo
exec ghasum verify -cache .cache/ up-to-date/
stdout 'Ok'
! stderr .

# Redundant checksum stored
# Checksums match exactly - Workflow
exec ghasum verify -cache .cache/ up-to-date/.github/workflows/workflow.yml
stdout 'Ok'
! stderr .

# Redundant checksum stored - Repo
exec ghasum verify -cache .cache/ redundant/
stdout 'Ok'
! stderr .

# Redundant checksum stored - Workflow
exec ghasum verify -cache .cache/ redundant/.github/workflows/workflow.yml
stdout 'Ok'
! stderr .

-- up-to-date/.github/workflows/gha.sum --
version 1

Expand Down

0 comments on commit 867e2af

Please sign in to comment.