Skip to content

Commit

Permalink
Add support for ghasum update to ignore errors in the sumfile
Browse files Browse the repository at this point in the history
Update the `ghasum update` CLI to accept a `-force` flag that can be
used to force updating to ignore and fix any errors in the gha.sum file.
  • Loading branch information
ericcornelissen committed Mar 2, 2024
1 parent 720c940 commit 1e89ccb
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 16 deletions.
17 changes: 12 additions & 5 deletions SPECIFICATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,18 @@ by another process leading to an inconsistent state).

If the file lock is obtained, the process shall first read it and parse it
completely to extract the sumfile version. If this fails the process shall exit
immediately. Else it shall recompute checksums for all actions used in the
repository (see [Computing Checksums]) using the best available hashing
algorithm. It shall then store them in a sumfile (see [Storing Checksums]) using
the same sumfile version as before and releases the lock. As a consequence, this
adds missing and removes redundant checksums from the sumfile.
immediately unless the `-force` flag is used (see details below). Else it shall
recompute checksums for all actions used in the repository (see [Computing
Checksums]) using the best available hashing algorithm. It shall then store them
in a sumfile (see [Storing Checksums]) using the same sumfile version as before
and releases the lock. As a consequence, this adds missing and removes redundant
checksums from the sumfile.

With the `-force` flag the process will ignore errors in the sumfile and fix
those while updating. If the sumfile version can still be determined from
sumfile it will be used, otherwise the latest available version is used instead.
This option is disabled by default to avoid unknowingly fixing syntax errors in
a sumfile, which is an important fact to know about from a security perspective.

This process does not verify any of the checksums currently in the sumfile.

Expand Down
1 change: 1 addition & 0 deletions cmd/ghasum/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const (

const (
flagNameCache = "cache"
flagNameForce = "force"
flagNameNoCache = "no-cache"
)

Expand Down
6 changes: 5 additions & 1 deletion cmd/ghasum/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
func cmdUpdate(argv []string) error {
var (
flags = flag.NewFlagSet(cmdNameUpdate, flag.ContinueOnError)
flagForce = flags.Bool(flagNameForce, false, "")
flagCache = flags.String(flagNameCache, "", "")
flagNoCache = flags.Bool(flagNameNoCache, false, "")
)
Expand Down Expand Up @@ -57,7 +58,7 @@ func cmdUpdate(argv []string) error {
Cache: c,
}

if err := ghasum.Update(&cfg); err != nil {
if err := ghasum.Update(&cfg, *flagForce); err != nil {
return errors.Join(errUnexpected, err)
}

Expand All @@ -79,6 +80,9 @@ The available flags are:
The location of the cache directory. This is where ghasum stores and
looks up repositories it needs.
Defaults to a directory named .ghasum in the user's home directory.
-force
Force updating the gha.sum file, ignoring errors and fixing them in the
process.
-no-cache
Disable the use of the cache. Makes the -cache flag ineffective.`
}
10 changes: 8 additions & 2 deletions internal/ghasum/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func Initialize(cfg *Config) error {

// Update will update the ghasum checksums for the repository specified in the
// given configuration.
func Update(cfg *Config) error {
func Update(cfg *Config, force bool) error {
file, err := open(cfg.Path)
if err != nil {
return err
Expand All @@ -111,7 +111,13 @@ func Update(cfg *Config) error {

version, err := version(raw)
if err != nil {
return err
if !force {
return errors.Join(ErrSumfileRead, err)
}

if errors.Is(err, sumfile.ErrHeaders) || errors.Is(err, sumfile.ErrVersion) {
version = sumfile.VersionLatest
}
}

actions, err := find(cfg)
Expand Down
4 changes: 4 additions & 0 deletions internal/sumfile/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ var (

// ErrSyntax is the error when a checksum file has a syntax error.
ErrSyntax = errors.New("syntax error")

// ErrVersion is the error when the version is invalid or missing from the
// checksum file.
ErrVersion = errors.New("version error")
)
18 changes: 10 additions & 8 deletions internal/sumfile/sumfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@ func Decode(stored string) ([]Entry, error) {

// DecodeVersion parses the given checksum file content to extract the version.
func DecodeVersion(stored string) (Version, error) {
headers, _, err := parseFile(stored)
if err != nil {
return 0, err
headers, _, parseErr := parseFile(stored)
version, _ := extractVersion(headers)
if parseErr != nil {
return version, parseErr
}

return extractVersion(headers)
return version, nil
}

// Encode encodes the given checksums according to the specification of the
Expand Down Expand Up @@ -114,7 +115,7 @@ func parseHeaders(lines []string) (map[string]string, error) {
j := strings.IndexRune(line, ' ')
if j == -1 {
err := fmt.Errorf("invalid header on line %d", i)
return nil, errors.Join(ErrSyntax, err)
return nil, errors.Join(ErrHeaders, err)
}

key := line[0:j]
Expand All @@ -129,18 +130,19 @@ func extractVersion(headers map[string]string) (Version, error) {
version, ok := headers["version"]
if !ok {
err := errors.New("version not found")
return 0, errors.Join(ErrSyntax, err)
return 0, errors.Join(ErrVersion, err)
}

rawVersion, err := strconv.Atoi(version)
if err != nil {
err := errors.New("version not a number")
return 0, errors.Join(ErrSyntax, err)
return 0, errors.Join(ErrVersion, err)
}

return Version(rawVersion), nil
}

func unknownVersion(version Version) error {
return fmt.Errorf("unknown version %d", version)
err := fmt.Errorf("unknown version %d", version)
return errors.Join(ErrVersion, err)
}
112 changes: 112 additions & 0 deletions testdata/update/force.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Error in entries
exec ghasum update -cache .cache/ -force entries/
stdout 'Ok'
! stderr .
cmp entries/.github/workflows/gha.sum .want/gha.sum

# Error in headers
exec ghasum update -cache .cache/ -force headers/
stdout 'Ok'
! stderr .
cmp headers/.github/workflows/gha.sum .want/gha.sum

# Error in version
exec ghasum update -cache .cache/ -force nan-version/
stdout 'Ok'
! stderr .
cmp nan-version/.github/workflows/gha.sum .want/gha.sum

# Invalid version
exec ghasum update -cache .cache/ -force invalid-version/
stdout 'Ok'
! stderr .
cmp invalid-version/.github/workflows/gha.sum .want/gha.sum

# Missing version
exec ghasum update -cache .cache/ -force no-version/
stdout 'Ok'
! stderr .
cmp no-version/.github/workflows/gha.sum .want/gha.sum

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

this-action/is-missing@a-checksum
-- entries/.github/workflows/workflow.yml --
name: Example workflow
on: [push]

jobs:
example:
name: example
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/[email protected]
-- headers/.github/workflows/gha.sum --
invalid-header

actions/[email protected] GGAV+/JnlPt41B9iINyvcX5z6a4ue+NblmwiDNVORz0=
-- headers/.github/workflows/workflow.yml --
name: Example workflow
on: [push]

jobs:
example:
name: example
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/[email protected]
-- invalid-version/.github/workflows/gha.sum --
version 0

actions/[email protected] GGAV+/JnlPt41B9iINyvcX5z6a4ue+NblmwiDNVORz0=
-- invalid-version/.github/workflows/workflow.yml --
name: Example workflow
on: [push]

jobs:
example:
name: example
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/[email protected]
-- nan-version/.github/workflows/gha.sum --
version not-a-number

actions/[email protected] GGAV+/JnlPt41B9iINyvcX5z6a4ue+NblmwiDNVORz0=
-- nan-version/.github/workflows/workflow.yml --
name: Example workflow
on: [push]

jobs:
example:
name: example
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/[email protected]
-- no-version/.github/workflows/gha.sum --
version-header-missing 1

actions/[email protected] GGAV+/JnlPt41B9iINyvcX5z6a4ue+NblmwiDNVORz0=
-- no-version/.github/workflows/workflow.yml --
name: Example workflow
on: [push]

jobs:
example:
name: example
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/[email protected]
-- .cache/actions/checkout/v4.1.1/.keep --
This file exist to avoid fetching "actions/[email protected]" and give the Action
a unique checksum.
-- .want/gha.sum --
version 1

actions/[email protected] KsR9XQGH7ydTl01vlD8pIZrXhkzXyjcnzhmP+/KaJZI=

0 comments on commit 1e89ccb

Please sign in to comment.