refactor: return more errors, add more tests (#679)
adding more tests returns same message for short SHA format like GitHub Actions refactor error checking add more errors :)
This commit is contained in:
parent
cc4e23d96c
commit
aa68181f6b
6 changed files with 101 additions and 59 deletions
pkg
|
@ -2,7 +2,6 @@ package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -14,10 +13,12 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
git "github.com/go-git/go-git/v5"
|
git "github.com/go-git/go-git/v5"
|
||||||
|
"github.com/go-git/go-git/v5/config"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||||
"github.com/go-ini/ini"
|
"github.com/go-ini/ini"
|
||||||
"github.com/mattn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -120,16 +121,13 @@ func findGitPrettyRef(head, root, sub string) (string, error) {
|
||||||
var name string
|
var name string
|
||||||
var err = filepath.Walk(filepath.Join(root, sub), func(path string, info os.FileInfo, err error) error {
|
var err = filepath.Walk(filepath.Join(root, sub), func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if name != "" || info.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if name != "" {
|
var bts []byte
|
||||||
return nil
|
if bts, err = ioutil.ReadFile(path); err != nil {
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
bts, err := ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var pointsTo = strings.TrimSpace(string(bts))
|
var pointsTo = strings.TrimSpace(string(bts))
|
||||||
|
@ -266,13 +264,17 @@ func CloneIfRequired(ctx context.Context, refName plumbing.ReferenceName, input
|
||||||
logger.Errorf("Unable to clone %v %s: %v", input.URL, refName, err)
|
logger.Errorf("Unable to clone %v %s: %v", input.URL, refName, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_ = os.Chmod(input.Dir, 0755)
|
|
||||||
|
if err = os.Chmod(input.Dir, 0755); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGitCloneExecutor creates an executor to clone git repos
|
// NewGitCloneExecutor creates an executor to clone git repos
|
||||||
|
// nolint:gocyclo
|
||||||
func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
|
func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
logger := Logger(ctx)
|
logger := Logger(ctx)
|
||||||
|
@ -288,18 +290,31 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w, err := r.Worktree()
|
// fetch latest changes
|
||||||
if err != nil {
|
err = r.Fetch(&git.FetchOptions{
|
||||||
|
RefSpecs: []config.RefSpec{"refs/*:refs/*", "HEAD:refs/heads/HEAD"},
|
||||||
|
})
|
||||||
|
if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hash *plumbing.Hash
|
||||||
|
rev := plumbing.Revision(input.Ref)
|
||||||
|
if hash, err = r.ResolveRevision(rev); err != nil {
|
||||||
|
logger.Errorf("Unable to resolve %s: %v", input.Ref, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hash.String() != input.Ref && strings.HasPrefix(hash.String(), input.Ref) {
|
||||||
|
return errors.Wrap(errors.New(hash.String()), "short SHA references are not supported")
|
||||||
|
}
|
||||||
|
|
||||||
// At this point we need to know if it's a tag or a branch
|
// At this point we need to know if it's a tag or a branch
|
||||||
// And the easiest way to do it is duck typing
|
// And the easiest way to do it is duck typing
|
||||||
//
|
//
|
||||||
// If err is nil, it's a tag so let's proceed with that hash like we would if
|
// If err is nil, it's a tag so let's proceed with that hash like we would if
|
||||||
// it was a sha
|
// it was a sha
|
||||||
refType := "tag"
|
refType := "tag"
|
||||||
rev := plumbing.Revision(path.Join("refs", "tags", input.Ref))
|
rev = plumbing.Revision(path.Join("refs", "tags", input.Ref))
|
||||||
if _, err := r.Tag(input.Ref); errors.Is(err, git.ErrTagNotFound) {
|
if _, err := r.Tag(input.Ref); errors.Is(err, git.ErrTagNotFound) {
|
||||||
rName := plumbing.ReferenceName(path.Join("refs", "remotes", "origin", input.Ref))
|
rName := plumbing.ReferenceName(path.Join("refs", "remotes", "origin", input.Ref))
|
||||||
if _, err := r.Reference(rName, false); errors.Is(err, plumbing.ErrReferenceNotFound) {
|
if _, err := r.Reference(rName, false); errors.Is(err, plumbing.ErrReferenceNotFound) {
|
||||||
|
@ -310,60 +325,53 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) Executor {
|
||||||
rev = plumbing.Revision(rName)
|
rev = plumbing.Revision(rName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hash, err := r.ResolveRevision(rev)
|
|
||||||
if err != nil {
|
if hash, err = r.ResolveRevision(rev); err != nil {
|
||||||
logger.Errorf("Unable to resolve %s: %v", input.Ref, err)
|
logger.Errorf("Unable to resolve %s: %v", input.Ref, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var w *git.Worktree
|
||||||
|
if w, err = r.Worktree(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// If the hash resolved doesn't match the ref provided in a workflow then we're
|
// If the hash resolved doesn't match the ref provided in a workflow then we're
|
||||||
// using a branch or tag ref, not a sha
|
// using a branch or tag ref, not a sha
|
||||||
//
|
//
|
||||||
// Repos on disk point to commit hashes, and need to checkout input.Ref before
|
// Repos on disk point to commit hashes, and need to checkout input.Ref before
|
||||||
// we try and pull down any changes
|
// we try and pull down any changes
|
||||||
if hash.String() != input.Ref {
|
if hash.String() != input.Ref && refType == "branch" {
|
||||||
// Run git fetch to make sure we have the latest sha
|
logger.Debugf("Provided ref is not a sha. Checking out branch before pulling changes")
|
||||||
err := r.Fetch(&git.FetchOptions{})
|
sourceRef := plumbing.ReferenceName(path.Join("refs", "remotes", "origin", input.Ref))
|
||||||
if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) {
|
if err = w.Checkout(&git.CheckoutOptions{
|
||||||
logger.Debugf("Unable to fetch: %v", err)
|
Branch: sourceRef,
|
||||||
}
|
Force: true,
|
||||||
|
}); err != nil {
|
||||||
if refType == "branch" {
|
logger.Errorf("Unable to checkout %s: %v", sourceRef, err)
|
||||||
logger.Debugf("Provided ref is not a sha. Checking out branch before pulling changes")
|
return err
|
||||||
sourceRef := plumbing.ReferenceName(path.Join("refs", "remotes", "origin", input.Ref))
|
|
||||||
err := w.Checkout(&git.CheckoutOptions{
|
|
||||||
Branch: sourceRef,
|
|
||||||
Force: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("Unable to checkout %s: %v", sourceRef, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = w.Pull(&git.PullOptions{
|
if err = w.Pull(&git.PullOptions{
|
||||||
Force: true,
|
Force: true,
|
||||||
})
|
}); err != nil && err.Error() != "already up-to-date" {
|
||||||
if err != nil && err.Error() != "already up-to-date" {
|
|
||||||
logger.Debugf("Unable to pull %s: %v", refName, err)
|
logger.Debugf("Unable to pull %s: %v", refName, err)
|
||||||
}
|
}
|
||||||
logger.Debugf("Cloned %s to %s", input.URL, input.Dir)
|
logger.Debugf("Cloned %s to %s", input.URL, input.Dir)
|
||||||
|
|
||||||
err = w.Checkout(&git.CheckoutOptions{
|
if err = w.Checkout(&git.CheckoutOptions{
|
||||||
Hash: *hash,
|
Hash: *hash,
|
||||||
Force: true,
|
Force: true,
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("Unable to checkout %s: %v", *hash, err)
|
logger.Errorf("Unable to checkout %s: %v", *hash, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = w.Reset(&git.ResetOptions{
|
if err = w.Reset(&git.ResetOptions{
|
||||||
Mode: git.HardReset,
|
Mode: git.HardReset,
|
||||||
Commit: *hash,
|
Commit: *hash,
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("Unable to reset to %s: %v", hash.String(), err)
|
logger.Errorf("Unable to reset to %s: %v", hash.String(), err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -171,44 +172,55 @@ func TestGitFindRef(t *testing.T) {
|
||||||
|
|
||||||
func TestGitCloneExecutor(t *testing.T) {
|
func TestGitCloneExecutor(t *testing.T) {
|
||||||
for name, tt := range map[string]struct {
|
for name, tt := range map[string]struct {
|
||||||
URL string
|
Err, URL, Ref string
|
||||||
Ref string
|
|
||||||
Err error
|
|
||||||
}{
|
}{
|
||||||
"tag": {
|
"tag": {
|
||||||
|
Err: "",
|
||||||
URL: "https://github.com/actions/checkout",
|
URL: "https://github.com/actions/checkout",
|
||||||
Ref: "v2",
|
Ref: "v2",
|
||||||
Err: nil,
|
|
||||||
},
|
},
|
||||||
"branch": {
|
"branch": {
|
||||||
|
Err: "",
|
||||||
URL: "https://github.com/anchore/scan-action",
|
URL: "https://github.com/anchore/scan-action",
|
||||||
Ref: "act-fails",
|
Ref: "act-fails",
|
||||||
Err: nil,
|
|
||||||
},
|
},
|
||||||
"sha": {
|
"sha": {
|
||||||
|
Err: "",
|
||||||
URL: "https://github.com/actions/checkout",
|
URL: "https://github.com/actions/checkout",
|
||||||
Ref: "5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f", // v2
|
Ref: "5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f", // v2
|
||||||
Err: nil,
|
},
|
||||||
|
"short-sha": {
|
||||||
|
Err: "short SHA references are not supported: 5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f",
|
||||||
|
URL: "https://github.com/actions/checkout",
|
||||||
|
Ref: "5a4ac90", // v2
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
tt := tt
|
|
||||||
name := name
|
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
clone := NewGitCloneExecutor(NewGitCloneExecutorInput{
|
clone := NewGitCloneExecutor(NewGitCloneExecutorInput{
|
||||||
URL: tt.URL,
|
URL: tt.URL,
|
||||||
Ref: tt.Ref,
|
Ref: tt.Ref,
|
||||||
Dir: testDir(t),
|
Dir: testDir(t),
|
||||||
})
|
})
|
||||||
|
|
||||||
err := clone(context.Background())
|
err := clone(context.Background())
|
||||||
assert.ErrorIs(t, err, tt.Err)
|
if tt.Err == "" {
|
||||||
|
assert.Empty(t, err)
|
||||||
|
} else {
|
||||||
|
assert.EqualError(t, err, tt.Err)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func gitConfig() {
|
func gitConfig() {
|
||||||
if os.Getenv("GITHUB_ACTIONS") == "true" {
|
if os.Getenv("GITHUB_ACTIONS") == "true" {
|
||||||
_ = gitCmd("config", "--global", "user.email", "test@test.com")
|
var err error
|
||||||
_ = gitCmd("config", "--global", "user.name", "Unit Test")
|
if err = gitCmd("config", "--global", "user.email", "test@test.com"); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
if err = gitCmd("config", "--global", "user.name", "Unit Test"); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
17
pkg/runner/step_context.go
Executable file → Normal file
17
pkg/runner/step_context.go
Executable file → Normal file
|
@ -14,6 +14,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/kballard/go-shellquote"
|
"github.com/kballard/go-shellquote"
|
||||||
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/common"
|
"github.com/nektos/act/pkg/common"
|
||||||
|
@ -82,13 +83,17 @@ func (sc *StepContext) Executor() common.Executor {
|
||||||
}
|
}
|
||||||
|
|
||||||
actionDir := fmt.Sprintf("%s/%s", rc.ActionCacheDir(), strings.ReplaceAll(step.Uses, "/", "-"))
|
actionDir := fmt.Sprintf("%s/%s", rc.ActionCacheDir(), strings.ReplaceAll(step.Uses, "/", "-"))
|
||||||
|
gitClone := common.NewGitCloneExecutor(common.NewGitCloneExecutorInput{
|
||||||
|
URL: remoteAction.CloneURL(),
|
||||||
|
Ref: remoteAction.Ref,
|
||||||
|
Dir: actionDir,
|
||||||
|
Token: github.Token,
|
||||||
|
})
|
||||||
|
if err := gitClone(context.TODO()); err != nil {
|
||||||
|
err = errors.Cause(err)
|
||||||
|
return common.NewErrorExecutor(fmt.Errorf("Unable to resolve action `%s`, the provided ref `%s` is the shortened version of a commit SHA, which is not supported. Please use the full commit SHA `%s` instead", step.Uses, remoteAction.Ref, err.Error()))
|
||||||
|
}
|
||||||
return common.NewPipelineExecutor(
|
return common.NewPipelineExecutor(
|
||||||
common.NewGitCloneExecutor(common.NewGitCloneExecutorInput{
|
|
||||||
URL: remoteAction.CloneURL(),
|
|
||||||
Ref: remoteAction.Ref,
|
|
||||||
Dir: actionDir,
|
|
||||||
Token: github.Token,
|
|
||||||
}),
|
|
||||||
sc.setupAction(actionDir, remoteAction.Path),
|
sc.setupAction(actionDir, remoteAction.Path),
|
||||||
sc.runAction(actionDir, remoteAction.Path),
|
sc.runAction(actionDir, remoteAction.Path),
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,6 +19,9 @@ func TestStepContextExecutor(t *testing.T) {
|
||||||
{"testdata", "uses-github-noref", "push", "Expected format {org}/{repo}[/path]@ref", platforms, ""},
|
{"testdata", "uses-github-noref", "push", "Expected format {org}/{repo}[/path]@ref", platforms, ""},
|
||||||
{"testdata", "uses-github-root", "push", "", platforms, ""},
|
{"testdata", "uses-github-root", "push", "", platforms, ""},
|
||||||
{"testdata", "uses-github-path", "push", "", platforms, ""},
|
{"testdata", "uses-github-path", "push", "", platforms, ""},
|
||||||
|
{"testdata", "uses-docker-url", "push", "", platforms, ""},
|
||||||
|
{"testdata", "uses-github-full-sha", "push", "", platforms, ""},
|
||||||
|
{"testdata", "uses-github-short-sha", "push", "Unable to resolve action `actions/hello-world-docker-action@b136eb8`, the provided ref `b136eb8` is the shortened version of a commit SHA, which is not supported. Please use the full commit SHA `b136eb8894c5cb1dd5807da824be97ccdf9b5423` instead", platforms, ""},
|
||||||
}
|
}
|
||||||
// These tests are sufficient to only check syntax.
|
// These tests are sufficient to only check syntax.
|
||||||
ctx := common.WithDryrun(context.Background(), true)
|
ctx := common.WithDryrun(context.Background(), true)
|
||||||
|
|
7
pkg/runner/testdata/uses-github-full-sha/main.yml
vendored
Normal file
7
pkg/runner/testdata/uses-github-full-sha/main.yml
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
name: uses-github-root
|
||||||
|
on: push
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/hello-world-docker-action@b136eb8894c5cb1dd5807da824be97ccdf9b5423
|
7
pkg/runner/testdata/uses-github-short-sha/main.yml
vendored
Normal file
7
pkg/runner/testdata/uses-github-short-sha/main.yml
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
name: uses-github-root
|
||||||
|
on: push
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/hello-world-docker-action@b136eb8
|
Loading…
Add table
Reference in a new issue