mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-03-20 03:46:09 +08:00
feat: make use new action cache the new default for downloading actions (#12)
* remove legacy action caching * migrate tests * clear old legacy action cache when run
This commit is contained in:
@@ -58,7 +58,6 @@ type Input struct {
|
||||
actionOfflineMode bool
|
||||
logPrefixJobID bool
|
||||
networkName string
|
||||
useNewActionCache bool
|
||||
localRepository []string
|
||||
}
|
||||
|
||||
|
||||
50
cmd/root.go
50
cmd/root.go
@@ -102,7 +102,6 @@ func Execute(ctx context.Context, version string) {
|
||||
rootCmd.PersistentFlags().StringVarP(&input.actionCachePath, "action-cache-path", "", filepath.Join(CacheHomeDir, "act"), "Defines the path where the actions get cached and host workspaces created.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&input.actionOfflineMode, "action-offline-mode", "", false, "If action contents exists, it will not be fetch and pull again. If turn on this, will turn off force pull")
|
||||
rootCmd.PersistentFlags().StringVarP(&input.networkName, "network", "", "host", "Sets a docker network name. Defaults to host.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&input.useNewActionCache, "use-new-action-cache", "", false, "Enable using the new Action Cache for storing Actions locally")
|
||||
rootCmd.PersistentFlags().StringArrayVarP(&input.localRepository, "local-repository", "", []string{}, "Replaces the specified repository and ref with a local folder (e.g. https://github.com/test/test@v0=/home/act/test or test/test@v0=/home/act/test, the latter matches any hosts or protocols)")
|
||||
rootCmd.SetArgs(args())
|
||||
|
||||
@@ -393,6 +392,16 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
|
||||
vars := newSecrets(input.vars)
|
||||
_ = readEnvs(input.Varfile(), vars)
|
||||
|
||||
log.Debugf("Cleaning up %s old action cache format", input.actionCachePath)
|
||||
entries, _ := os.ReadDir(input.actionCachePath)
|
||||
for _, entry := range entries {
|
||||
if strings.Contains(entry.Name(), "@") {
|
||||
fullPath := filepath.Join(input.actionCachePath, entry.Name())
|
||||
log.Debugf("Removing %s", fullPath)
|
||||
_ = os.RemoveAll(fullPath)
|
||||
}
|
||||
}
|
||||
|
||||
matrixes := parseMatrix(input.matrix)
|
||||
log.Debugf("Evaluated matrix inclusions: %v", matrixes)
|
||||
|
||||
@@ -588,31 +597,26 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
|
||||
Matrix: matrixes,
|
||||
ContainerNetworkMode: docker_container.NetworkMode(input.networkName),
|
||||
}
|
||||
if input.useNewActionCache || len(input.localRepository) > 0 {
|
||||
if input.actionOfflineMode {
|
||||
config.ActionCache = &runner.GoGitActionCacheOfflineMode{
|
||||
Parent: runner.GoGitActionCache{
|
||||
Path: config.ActionCacheDir,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
config.ActionCache = &runner.GoGitActionCache{
|
||||
if input.actionOfflineMode {
|
||||
config.ActionCache = &runner.GoGitActionCacheOfflineMode{
|
||||
Parent: runner.GoGitActionCache{
|
||||
Path: config.ActionCacheDir,
|
||||
}
|
||||
}
|
||||
if len(input.localRepository) > 0 {
|
||||
localRepositories := map[string]string{}
|
||||
for _, l := range input.localRepository {
|
||||
k, v, _ := strings.Cut(l, "=")
|
||||
localRepositories[k] = v
|
||||
}
|
||||
config.ActionCache = &runner.LocalRepositoryCache{
|
||||
Parent: config.ActionCache,
|
||||
LocalRepositories: localRepositories,
|
||||
CacheDirCache: map[string]string{},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
if len(input.localRepository) > 0 {
|
||||
localRepositories := map[string]string{}
|
||||
for _, l := range input.localRepository {
|
||||
k, v, _ := strings.Cut(l, "=")
|
||||
localRepositories[k] = v
|
||||
}
|
||||
config.ActionCache = &runner.LocalRepositoryCache{
|
||||
Parent: config.ActionCache,
|
||||
LocalRepositories: localRepositories,
|
||||
CacheDirCache: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
r, err := runner.New(config)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -699,6 +699,9 @@ func (cr *containerReference) waitForCommand(ctx context.Context, isTerminal boo
|
||||
}
|
||||
|
||||
func (cr *containerReference) CopyTarStream(ctx context.Context, destPath string, tarStream io.Reader) error {
|
||||
if common.Dryrun(ctx) {
|
||||
return nil
|
||||
}
|
||||
// Mkdir
|
||||
buf := &bytes.Buffer{}
|
||||
tw := tar.NewWriter(buf)
|
||||
|
||||
@@ -62,6 +62,9 @@ func (e *HostEnvironment) Copy(destPath string, files ...*FileEntry) common.Exec
|
||||
}
|
||||
|
||||
func (e *HostEnvironment) CopyTarStream(ctx context.Context, destPath string, tarStream io.Reader) error {
|
||||
if common.Dryrun(ctx) {
|
||||
return nil
|
||||
}
|
||||
if err := os.RemoveAll(destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ func readActionImpl(ctx context.Context, step *model.Step, actionDir string, act
|
||||
return action, err
|
||||
}
|
||||
|
||||
func maybeCopyToActionDir(ctx context.Context, step actionStep, actionDir string, actionPath string, containerActionDir string) error {
|
||||
func maybeCopyToActionDir(ctx context.Context, step actionStep, actionPath string, containerActionDir string) error {
|
||||
logger := common.Logger(ctx)
|
||||
rc := step.getRunContext()
|
||||
stepModel := step.getStepModel()
|
||||
@@ -133,21 +133,13 @@ func maybeCopyToActionDir(ctx context.Context, step actionStep, actionDir string
|
||||
containerActionDirCopy += `/`
|
||||
}
|
||||
|
||||
if rc.Config != nil && rc.Config.ActionCache != nil {
|
||||
raction := step.(*stepActionRemote)
|
||||
ta, err := rc.Config.ActionCache.GetTarArchive(ctx, raction.cacheDir, raction.resolvedSha, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ta.Close()
|
||||
return rc.JobContainer.CopyTarStream(ctx, containerActionDirCopy, ta)
|
||||
}
|
||||
|
||||
if err := removeGitIgnore(ctx, actionDir); err != nil {
|
||||
raction := step.(*stepActionRemote)
|
||||
ta, err := rc.getActionCache().GetTarArchive(ctx, raction.cacheDir, raction.resolvedSha, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return rc.JobContainer.CopyDir(containerActionDirCopy, actionDir+"/", rc.Config.UseGitIgnore)(ctx)
|
||||
defer ta.Close()
|
||||
return rc.JobContainer.CopyTarStream(ctx, containerActionDirCopy, ta)
|
||||
}
|
||||
|
||||
func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction) common.Executor {
|
||||
@@ -176,7 +168,7 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction
|
||||
|
||||
switch action.Runs.Using {
|
||||
case model.ActionRunsUsingNode12, model.ActionRunsUsingNode16, model.ActionRunsUsingNode20:
|
||||
if err := maybeCopyToActionDir(ctx, step, actionDir, actionPath, containerActionDir); err != nil {
|
||||
if err := maybeCopyToActionDir(ctx, step, actionPath, containerActionDir); err != nil {
|
||||
return err
|
||||
}
|
||||
containerArgs := []string{rc.GetNodeToolFullPath(ctx), path.Join(containerActionDir, action.Runs.Main)}
|
||||
@@ -186,13 +178,13 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction
|
||||
|
||||
return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx)
|
||||
case model.ActionRunsUsingDocker:
|
||||
location := actionLocation
|
||||
if remoteAction == nil {
|
||||
location = containerActionDir
|
||||
actionDir = ""
|
||||
actionPath = containerActionDir
|
||||
}
|
||||
return execAsDocker(ctx, step, actionName, location, remoteAction == nil, "entrypoint")
|
||||
return execAsDocker(ctx, step, actionName, actionDir, actionPath, remoteAction == nil, "entrypoint")
|
||||
case model.ActionRunsUsingComposite:
|
||||
if err := maybeCopyToActionDir(ctx, step, actionDir, actionPath, containerActionDir); err != nil {
|
||||
if err := maybeCopyToActionDir(ctx, step, actionPath, containerActionDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -223,27 +215,10 @@ func setupActionEnv(ctx context.Context, step actionStep, _ *remoteAction) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// https://github.com/nektos/act/issues/228#issuecomment-629709055
|
||||
// files in .gitignore are not copied in a Docker container
|
||||
// this causes issues with actions that ignore other important resources
|
||||
// such as `node_modules` for example
|
||||
func removeGitIgnore(ctx context.Context, directory string) error {
|
||||
gitIgnorePath := path.Join(directory, ".gitignore")
|
||||
if _, err := os.Stat(gitIgnorePath); err == nil {
|
||||
// .gitignore exists
|
||||
common.Logger(ctx).Debugf("Removing %s before docker cp", gitIgnorePath)
|
||||
err := os.Remove(gitIgnorePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: break out parts of function to reduce complexicity
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func execAsDocker(ctx context.Context, step actionStep, actionName string, basedir string, localAction bool, entrypointType string) error {
|
||||
func execAsDocker(ctx context.Context, step actionStep, actionName, basedir, subpath string, localAction bool, entrypointType string) error {
|
||||
logger := common.Logger(ctx)
|
||||
rc := step.getRunContext()
|
||||
action := step.getActionModel()
|
||||
@@ -260,7 +235,7 @@ func execAsDocker(ctx context.Context, step actionStep, actionName string, based
|
||||
image = fmt.Sprintf("%s-dockeraction:%s", regexp.MustCompile("[^a-zA-Z0-9]").ReplaceAllString(actionName, "-"), "latest")
|
||||
image = fmt.Sprintf("act-%s", strings.TrimLeft(image, "-"))
|
||||
image = strings.ToLower(image)
|
||||
contextDir, fileName := filepath.Split(filepath.Join(basedir, action.Runs.Image))
|
||||
contextDir, fileName := path.Split(path.Join(subpath, action.Runs.Image))
|
||||
|
||||
anyArchExists, err := container.ImageExistsLocally(ctx, image, "any")
|
||||
if err != nil {
|
||||
@@ -291,16 +266,16 @@ func execAsDocker(ctx context.Context, step actionStep, actionName string, based
|
||||
return err
|
||||
}
|
||||
defer buildContext.Close()
|
||||
} else if rc.Config.ActionCache != nil {
|
||||
} else {
|
||||
rstep := step.(*stepActionRemote)
|
||||
buildContext, err = rc.Config.ActionCache.GetTarArchive(ctx, rstep.cacheDir, rstep.resolvedSha, contextDir)
|
||||
buildContext, err = rc.getActionCache().GetTarArchive(ctx, rstep.cacheDir, rstep.resolvedSha, contextDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer buildContext.Close()
|
||||
}
|
||||
prepImage = container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
|
||||
ContextDir: contextDir,
|
||||
ContextDir: filepath.Join(basedir, contextDir),
|
||||
Dockerfile: fileName,
|
||||
ImageTag: image,
|
||||
BuildContext: buildContext,
|
||||
@@ -324,6 +299,7 @@ func execAsDocker(ctx context.Context, step actionStep, actionName string, based
|
||||
if len(entrypoint) == 0 {
|
||||
if entrypointType == "pre-entrypoint" && action.Runs.PreEntrypoint != "" {
|
||||
entrypoint, err = shellquote.Split(action.Runs.PreEntrypoint)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -545,7 +521,7 @@ func runPreStep(step actionStep) common.Executor {
|
||||
|
||||
switch action.Runs.Using {
|
||||
case model.ActionRunsUsingNode12, model.ActionRunsUsingNode16, model.ActionRunsUsingNode20:
|
||||
if err := maybeCopyToActionDir(ctx, step, actionDir, actionPath, containerActionDir); err != nil {
|
||||
if err := maybeCopyToActionDir(ctx, step, actionPath, containerActionDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -557,11 +533,11 @@ func runPreStep(step actionStep) common.Executor {
|
||||
return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx)
|
||||
|
||||
case model.ActionRunsUsingDocker:
|
||||
location := actionLocation
|
||||
if remoteAction == nil {
|
||||
location = containerActionDir
|
||||
actionDir = ""
|
||||
actionPath = containerActionDir
|
||||
}
|
||||
return execAsDocker(ctx, step, actionName, location, remoteAction == nil, "pre-entrypoint")
|
||||
return execAsDocker(ctx, step, actionName, actionDir, actionPath, remoteAction == nil, "pre-entrypoint")
|
||||
|
||||
case model.ActionRunsUsingComposite:
|
||||
if step.getCompositeSteps() == nil {
|
||||
@@ -662,14 +638,14 @@ func runPostStep(step actionStep) common.Executor {
|
||||
return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx)
|
||||
|
||||
case model.ActionRunsUsingDocker:
|
||||
location := actionLocation
|
||||
if remoteAction == nil {
|
||||
location = containerActionDir
|
||||
actionDir = ""
|
||||
actionPath = containerActionDir
|
||||
}
|
||||
return execAsDocker(ctx, step, actionName, location, remoteAction == nil, "post-entrypoint")
|
||||
return execAsDocker(ctx, step, actionName, actionDir, actionPath, remoteAction == nil, "post-entrypoint")
|
||||
|
||||
case model.ActionRunsUsingComposite:
|
||||
if err := maybeCopyToActionDir(ctx, step, actionDir, actionPath, containerActionDir); err != nil {
|
||||
if err := maybeCopyToActionDir(ctx, step, actionPath, containerActionDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -227,7 +227,10 @@ func TestActionRunner(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
cm := &containerMock{}
|
||||
cm.On("CopyDir", "/var/run/act/actions/dir/", "dir/", false).Return(func(_ context.Context) error { return nil })
|
||||
cm.Mock.On("CopyTarStream", ctx, "/var/run/act/actions/dir/", mock.Anything).Return(nil)
|
||||
|
||||
cacheMock := &TestRepositoryCache{}
|
||||
cacheMock.Mock.On("GetTarArchive", ctx, "", "", "").Return(io.NopCloser(io.MultiReader()))
|
||||
|
||||
envMatcher := mock.MatchedBy(func(env map[string]string) bool {
|
||||
for k, v := range tt.expectedEnv {
|
||||
@@ -241,6 +244,7 @@ func TestActionRunner(t *testing.T) {
|
||||
cm.On("Exec", []string{"node", "/var/run/act/actions/dir/path"}, envMatcher, "", "").Return(func(_ context.Context) error { return nil })
|
||||
|
||||
tt.step.getRunContext().JobContainer = cm
|
||||
tt.step.getRunContext().Config.ActionCache = cacheMock
|
||||
|
||||
err := runActionImpl(tt.step, "dir", newRemoteAction("org/repo/path@ref"))(ctx)
|
||||
|
||||
|
||||
@@ -73,3 +73,12 @@ func (cm *containerMock) GetContainerArchive(ctx context.Context, srcPath string
|
||||
}
|
||||
return args.Get(0).(io.ReadCloser), err
|
||||
}
|
||||
|
||||
func (cm *containerMock) CopyTarStream(ctx context.Context, destPath string, tarStream io.Reader) error {
|
||||
args := cm.Mock.Called(ctx, destPath, tarStream)
|
||||
err, hasErr := args.Get(0).(error)
|
||||
if !hasErr {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,16 +3,11 @@ package runner
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"github.com/nektos/act/pkg/common"
|
||||
"github.com/nektos/act/pkg/common/git"
|
||||
"github.com/nektos/act/pkg/model"
|
||||
)
|
||||
|
||||
@@ -32,27 +27,20 @@ func newRemoteReusableWorkflowExecutor(rc *RunContext) common.Executor {
|
||||
// instead we will just use {owner}-{repo}@{ref} as our target directory. This should also improve performance when we are using
|
||||
// multiple reusable workflows from the same repository and ref since for each workflow we won't have to clone it again
|
||||
filename := fmt.Sprintf("%s/%s@%s", remoteReusableWorkflow.Org, remoteReusableWorkflow.Repo, remoteReusableWorkflow.Ref)
|
||||
workflowDir := fmt.Sprintf("%s/%s", rc.ActionCacheDir(), safeFilename(filename))
|
||||
|
||||
if rc.Config.ActionCache != nil {
|
||||
return newActionCacheReusableWorkflowExecutor(rc, filename, remoteReusableWorkflow)
|
||||
}
|
||||
|
||||
return common.NewPipelineExecutor(
|
||||
newMutexExecutor(cloneIfRequired(rc, *remoteReusableWorkflow, workflowDir)),
|
||||
newReusableWorkflowExecutor(rc, workflowDir, fmt.Sprintf("./.github/workflows/%s", remoteReusableWorkflow.Filename)),
|
||||
)
|
||||
return newActionCacheReusableWorkflowExecutor(rc, filename, remoteReusableWorkflow)
|
||||
}
|
||||
|
||||
func newActionCacheReusableWorkflowExecutor(rc *RunContext, filename string, remoteReusableWorkflow *remoteReusableWorkflow) common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
ghctx := rc.getGithubContext(ctx)
|
||||
remoteReusableWorkflow.URL = ghctx.ServerURL
|
||||
sha, err := rc.Config.ActionCache.Fetch(ctx, filename, remoteReusableWorkflow.CloneURL(), remoteReusableWorkflow.Ref, ghctx.Token)
|
||||
cache := rc.getActionCache()
|
||||
sha, err := cache.Fetch(ctx, filename, remoteReusableWorkflow.CloneURL(), remoteReusableWorkflow.Ref, ghctx.Token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
archive, err := rc.Config.ActionCache.GetTarArchive(ctx, filename, sha, fmt.Sprintf(".github/workflows/%s", remoteReusableWorkflow.Filename))
|
||||
archive, err := cache.GetTarArchive(ctx, filename, sha, fmt.Sprintf(".github/workflows/%s", remoteReusableWorkflow.Filename))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -79,40 +67,6 @@ func newActionCacheReusableWorkflowExecutor(rc *RunContext, filename string, rem
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
executorLock sync.Mutex
|
||||
)
|
||||
|
||||
func newMutexExecutor(executor common.Executor) common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
executorLock.Lock()
|
||||
defer executorLock.Unlock()
|
||||
|
||||
return executor(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func cloneIfRequired(rc *RunContext, remoteReusableWorkflow remoteReusableWorkflow, targetDirectory string) common.Executor {
|
||||
return common.NewConditionalExecutor(
|
||||
func(_ context.Context) bool {
|
||||
_, err := os.Stat(targetDirectory)
|
||||
notExists := errors.Is(err, fs.ErrNotExist)
|
||||
return notExists
|
||||
},
|
||||
func(ctx context.Context) error {
|
||||
remoteReusableWorkflow.URL = rc.getGithubContext(ctx).ServerURL
|
||||
return git.NewGitCloneExecutor(git.NewGitCloneExecutorInput{
|
||||
URL: remoteReusableWorkflow.CloneURL(),
|
||||
Ref: remoteReusableWorkflow.Ref,
|
||||
Dir: targetDirectory,
|
||||
Token: rc.Config.Token,
|
||||
OfflineMode: rc.Config.ActionOfflineMode,
|
||||
})(ctx)
|
||||
},
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
func newReusableWorkflowExecutor(rc *RunContext, directory string, workflow string) common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
planner, err := model.NewWorkflowPlanner(path.Join(directory, workflow), true)
|
||||
|
||||
@@ -627,6 +627,15 @@ func (rc *RunContext) ActionCacheDir() string {
|
||||
return filepath.Join(xdgCache, "act")
|
||||
}
|
||||
|
||||
func (rc *RunContext) getActionCache() ActionCache {
|
||||
if rc.Config.ActionCache == nil {
|
||||
rc.Config.ActionCache = &GoGitActionCache{
|
||||
Path: rc.ActionCacheDir(),
|
||||
}
|
||||
}
|
||||
return rc.Config.ActionCache
|
||||
}
|
||||
|
||||
// Interpolate outputs after a job is done
|
||||
func (rc *RunContext) interpolateOutputs() common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
|
||||
@@ -3,7 +3,6 @@ package runner
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -12,10 +11,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
gogit "github.com/go-git/go-git/v5"
|
||||
|
||||
"github.com/nektos/act/pkg/common"
|
||||
"github.com/nektos/act/pkg/common/git"
|
||||
"github.com/nektos/act/pkg/model"
|
||||
)
|
||||
|
||||
@@ -33,10 +29,6 @@ type stepActionRemote struct {
|
||||
resolvedSha string
|
||||
}
|
||||
|
||||
var (
|
||||
stepActionRemoteNewCloneExecutor = git.NewGitCloneExecutor
|
||||
)
|
||||
|
||||
func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
if sar.remoteAction != nil && sar.action != nil {
|
||||
@@ -63,84 +55,46 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
|
||||
github.Token = sar.RunContext.Config.ReplaceGheActionTokenWithGithubCom
|
||||
}
|
||||
}
|
||||
if sar.RunContext.Config.ActionCache != nil {
|
||||
cache := sar.RunContext.Config.ActionCache
|
||||
cache := sar.RunContext.getActionCache()
|
||||
|
||||
var err error
|
||||
sar.cacheDir = fmt.Sprintf("%s/%s", sar.remoteAction.Org, sar.remoteAction.Repo)
|
||||
repoURL := sar.remoteAction.URL + "/" + sar.cacheDir
|
||||
repoRef := sar.remoteAction.Ref
|
||||
sar.resolvedSha, err = cache.Fetch(ctx, sar.cacheDir, repoURL, repoRef, github.Token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch \"%s\" version \"%s\": %w", repoURL, repoRef, err)
|
||||
}
|
||||
|
||||
remoteReader := func(ctx context.Context) actionYamlReader {
|
||||
return func(filename string) (io.Reader, io.Closer, error) {
|
||||
spath := path.Join(sar.remoteAction.Path, filename)
|
||||
for i := 0; i < maxSymlinkDepth; i++ {
|
||||
tars, err := cache.GetTarArchive(ctx, sar.cacheDir, sar.resolvedSha, spath)
|
||||
if err != nil {
|
||||
return nil, nil, os.ErrNotExist
|
||||
}
|
||||
treader := tar.NewReader(tars)
|
||||
header, err := treader.Next()
|
||||
if err != nil {
|
||||
return nil, nil, os.ErrNotExist
|
||||
}
|
||||
if header.FileInfo().Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
spath, err = symlinkJoin(spath, header.Linkname, ".")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else {
|
||||
return treader, tars, nil
|
||||
}
|
||||
}
|
||||
return nil, nil, fmt.Errorf("max depth %d of symlinks exceeded while reading %s", maxSymlinkDepth, spath)
|
||||
}
|
||||
}
|
||||
|
||||
actionModel, err := sar.readAction(ctx, sar.Step, sar.resolvedSha, sar.remoteAction.Path, remoteReader(ctx), os.WriteFile)
|
||||
sar.action = actionModel
|
||||
return err
|
||||
}
|
||||
|
||||
actionDir := fmt.Sprintf("%s/%s", sar.RunContext.ActionCacheDir(), safeFilename(sar.Step.Uses))
|
||||
gitClone := stepActionRemoteNewCloneExecutor(git.NewGitCloneExecutorInput{
|
||||
URL: sar.remoteAction.CloneURL(),
|
||||
Ref: sar.remoteAction.Ref,
|
||||
Dir: actionDir,
|
||||
Token: github.Token,
|
||||
OfflineMode: sar.RunContext.Config.ActionOfflineMode,
|
||||
})
|
||||
var ntErr common.Executor
|
||||
if err := gitClone(ctx); err != nil {
|
||||
if errors.Is(err, git.ErrShortRef) {
|
||||
return 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",
|
||||
sar.Step.Uses, sar.remoteAction.Ref, err.(*git.Error).Commit())
|
||||
} else if errors.Is(err, gogit.ErrForceNeeded) { // TODO: figure out if it will be easy to shadow/alias go-git err's
|
||||
ntErr = common.NewInfoExecutor("Non-terminating error while running 'git clone': %v", err)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
sar.cacheDir = fmt.Sprintf("%s/%s", sar.remoteAction.Org, sar.remoteAction.Repo)
|
||||
repoURL := sar.remoteAction.URL + "/" + sar.cacheDir
|
||||
repoRef := sar.remoteAction.Ref
|
||||
sar.resolvedSha, err = cache.Fetch(ctx, sar.cacheDir, repoURL, repoRef, github.Token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch \"%s\" version \"%s\": %w", repoURL, repoRef, err)
|
||||
}
|
||||
|
||||
remoteReader := func(_ context.Context) actionYamlReader {
|
||||
return func(filename string) (io.Reader, io.Closer, error) {
|
||||
f, err := os.Open(filepath.Join(actionDir, sar.remoteAction.Path, filename))
|
||||
return f, f, err
|
||||
spath := path.Join(sar.remoteAction.Path, filename)
|
||||
for i := 0; i < maxSymlinkDepth; i++ {
|
||||
tars, err := cache.GetTarArchive(ctx, sar.cacheDir, sar.resolvedSha, spath)
|
||||
if err != nil {
|
||||
return nil, nil, os.ErrNotExist
|
||||
}
|
||||
treader := tar.NewReader(tars)
|
||||
header, err := treader.Next()
|
||||
if err != nil {
|
||||
return nil, nil, os.ErrNotExist
|
||||
}
|
||||
if header.FileInfo().Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
spath, err = symlinkJoin(spath, header.Linkname, ".")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else {
|
||||
return treader, tars, nil
|
||||
}
|
||||
}
|
||||
return nil, nil, fmt.Errorf("max depth %d of symlinks exceeded while reading %s", maxSymlinkDepth, spath)
|
||||
}
|
||||
}
|
||||
|
||||
return common.NewPipelineExecutor(
|
||||
ntErr,
|
||||
func(ctx context.Context) error {
|
||||
actionModel, err := sar.readAction(ctx, sar.Step, actionDir, sar.remoteAction.Path, remoteReader(ctx), os.WriteFile)
|
||||
sar.action = actionModel
|
||||
return err
|
||||
},
|
||||
)(ctx)
|
||||
actionModel, err := sar.readAction(ctx, sar.Step, sar.resolvedSha, sar.remoteAction.Path, remoteReader(ctx), os.WriteFile)
|
||||
sar.action = actionModel
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/nektos/act/pkg/common"
|
||||
"github.com/nektos/act/pkg/common/git"
|
||||
"github.com/nektos/act/pkg/model"
|
||||
)
|
||||
|
||||
@@ -31,6 +30,20 @@ func (sarm *stepActionRemoteMocks) runAction(step actionStep, actionDir string,
|
||||
return args.Get(0).(func(context.Context) error)
|
||||
}
|
||||
|
||||
type TestRepositoryCache struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (l *TestRepositoryCache) Fetch(ctx context.Context, cacheDir, url, ref, token string) (string, error) {
|
||||
args := l.Mock.Called(ctx, cacheDir, url, ref, token)
|
||||
return args.Get(0).(string), nil
|
||||
}
|
||||
|
||||
func (l *TestRepositoryCache) GetTarArchive(ctx context.Context, cacheDir, sha, includePrefix string) (io.ReadCloser, error) {
|
||||
args := l.Mock.Called(ctx, cacheDir, sha, includePrefix)
|
||||
return args.Get(0).(io.ReadCloser), nil
|
||||
}
|
||||
|
||||
func TestStepActionRemote(t *testing.T) {
|
||||
table := []struct {
|
||||
name string
|
||||
@@ -124,23 +137,13 @@ func TestStepActionRemote(t *testing.T) {
|
||||
cm := &containerMock{}
|
||||
sarm := &stepActionRemoteMocks{}
|
||||
|
||||
clonedAction := false
|
||||
|
||||
origStepAtionRemoteNewCloneExecutor := stepActionRemoteNewCloneExecutor
|
||||
stepActionRemoteNewCloneExecutor = func(_ git.NewGitCloneExecutorInput) common.Executor {
|
||||
return func(_ context.Context) error {
|
||||
clonedAction = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
defer (func() {
|
||||
stepActionRemoteNewCloneExecutor = origStepAtionRemoteNewCloneExecutor
|
||||
})()
|
||||
cacheMock := &TestRepositoryCache{}
|
||||
|
||||
sar := &stepActionRemote{
|
||||
RunContext: &RunContext{
|
||||
Config: &Config{
|
||||
GitHubInstance: "github.com",
|
||||
ActionCache: cacheMock,
|
||||
},
|
||||
Run: &model.Run{
|
||||
JobID: "1",
|
||||
@@ -159,6 +162,7 @@ func TestStepActionRemote(t *testing.T) {
|
||||
}
|
||||
sar.RunContext.ExprEval = sar.RunContext.NewExpressionEvaluator(ctx)
|
||||
|
||||
cacheMock.Mock.On("Fetch", ctx, mock.AnythingOfType("string"), "https://github.com/remote/action", "v1", "").Return("someval")
|
||||
suffixMatcher := func(suffix string) interface{} {
|
||||
return mock.MatchedBy(func(actionDir string) bool {
|
||||
return strings.HasSuffix(actionDir, suffix)
|
||||
@@ -166,7 +170,7 @@ func TestStepActionRemote(t *testing.T) {
|
||||
}
|
||||
|
||||
if tt.mocks.read {
|
||||
sarm.On("readAction", sar.Step, suffixMatcher("act/remote-action@v1"), "", mock.Anything, mock.Anything).Return(&model.Action{}, nil)
|
||||
sarm.Mock.On("readAction", sar.Step, "someval", "", mock.Anything, mock.Anything).Return(&model.Action{}, nil)
|
||||
}
|
||||
if tt.mocks.run {
|
||||
sarm.On("runAction", sar, suffixMatcher("act/remote-action@v1"), newRemoteAction(sar.Step.Uses)).Return(func(_ context.Context) error { return tt.runError })
|
||||
@@ -196,11 +200,11 @@ func TestStepActionRemote(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.runError, err)
|
||||
assert.Equal(t, tt.mocks.cloned, clonedAction)
|
||||
assert.Equal(t, tt.result, sar.RunContext.StepResults["step"])
|
||||
|
||||
sarm.AssertExpectations(t)
|
||||
cm.AssertExpectations(t)
|
||||
cacheMock.Mock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -222,25 +226,15 @@ func TestStepActionRemotePre(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
clonedAction := false
|
||||
cacheMock := &TestRepositoryCache{}
|
||||
sarm := &stepActionRemoteMocks{}
|
||||
|
||||
origStepAtionRemoteNewCloneExecutor := stepActionRemoteNewCloneExecutor
|
||||
stepActionRemoteNewCloneExecutor = func(_ git.NewGitCloneExecutorInput) common.Executor {
|
||||
return func(_ context.Context) error {
|
||||
clonedAction = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
defer (func() {
|
||||
stepActionRemoteNewCloneExecutor = origStepAtionRemoteNewCloneExecutor
|
||||
})()
|
||||
|
||||
sar := &stepActionRemote{
|
||||
Step: tt.stepModel,
|
||||
RunContext: &RunContext{
|
||||
Config: &Config{
|
||||
GitHubInstance: "https://github.com",
|
||||
GitHubInstance: "github.com",
|
||||
ActionCache: cacheMock,
|
||||
},
|
||||
Run: &model.Run{
|
||||
JobID: "1",
|
||||
@@ -254,20 +248,15 @@ func TestStepActionRemotePre(t *testing.T) {
|
||||
readAction: sarm.readAction,
|
||||
}
|
||||
|
||||
suffixMatcher := func(suffix string) interface{} {
|
||||
return mock.MatchedBy(func(actionDir string) bool {
|
||||
return strings.HasSuffix(actionDir, suffix)
|
||||
})
|
||||
}
|
||||
|
||||
sarm.On("readAction", sar.Step, suffixMatcher("org-repo-path@ref"), "path", mock.Anything, mock.Anything).Return(&model.Action{}, nil)
|
||||
sarm.Mock.On("readAction", sar.Step, "someval", "path", mock.Anything, mock.Anything).Return(&model.Action{}, nil)
|
||||
cacheMock.Mock.On("Fetch", ctx, mock.AnythingOfType("string"), "https://github.com/org/repo", "ref", "").Return("someval")
|
||||
|
||||
err := sar.pre()(ctx)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, true, clonedAction)
|
||||
|
||||
sarm.AssertExpectations(t)
|
||||
cacheMock.Mock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -289,28 +278,16 @@ func TestStepActionRemotePreThroughAction(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
clonedAction := false
|
||||
cacheMock := &TestRepositoryCache{}
|
||||
sarm := &stepActionRemoteMocks{}
|
||||
|
||||
origStepAtionRemoteNewCloneExecutor := stepActionRemoteNewCloneExecutor
|
||||
stepActionRemoteNewCloneExecutor = func(input git.NewGitCloneExecutorInput) common.Executor {
|
||||
return func(_ context.Context) error {
|
||||
if input.URL == "https://github.com/org/repo" {
|
||||
clonedAction = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
defer (func() {
|
||||
stepActionRemoteNewCloneExecutor = origStepAtionRemoteNewCloneExecutor
|
||||
})()
|
||||
|
||||
sar := &stepActionRemote{
|
||||
Step: tt.stepModel,
|
||||
RunContext: &RunContext{
|
||||
Config: &Config{
|
||||
GitHubInstance: "https://enterprise.github.com",
|
||||
ReplaceGheActionWithGithubCom: []string{"org/repo"},
|
||||
ActionCache: cacheMock,
|
||||
},
|
||||
Run: &model.Run{
|
||||
JobID: "1",
|
||||
@@ -324,20 +301,15 @@ func TestStepActionRemotePreThroughAction(t *testing.T) {
|
||||
readAction: sarm.readAction,
|
||||
}
|
||||
|
||||
suffixMatcher := func(suffix string) interface{} {
|
||||
return mock.MatchedBy(func(actionDir string) bool {
|
||||
return strings.HasSuffix(actionDir, suffix)
|
||||
})
|
||||
}
|
||||
|
||||
sarm.On("readAction", sar.Step, suffixMatcher("org-repo-path@ref"), "path", mock.Anything, mock.Anything).Return(&model.Action{}, nil)
|
||||
sarm.Mock.On("readAction", sar.Step, mock.AnythingOfType("string"), "path", mock.Anything, mock.Anything).Return(&model.Action{}, nil)
|
||||
cacheMock.Mock.On("Fetch", ctx, mock.AnythingOfType("string"), "https://github.com/org/repo", "ref", "").Return("someval")
|
||||
|
||||
err := sar.pre()(ctx)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, true, clonedAction)
|
||||
|
||||
sarm.AssertExpectations(t)
|
||||
cacheMock.Mock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -359,22 +331,9 @@ func TestStepActionRemotePreThroughActionToken(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
clonedAction := false
|
||||
sarm := &stepActionRemoteMocks{}
|
||||
|
||||
origStepAtionRemoteNewCloneExecutor := stepActionRemoteNewCloneExecutor
|
||||
stepActionRemoteNewCloneExecutor = func(input git.NewGitCloneExecutorInput) common.Executor {
|
||||
return func(_ context.Context) error {
|
||||
if input.URL == "https://github.com/org/repo" && input.Token == "PRIVATE_ACTIONS_TOKEN_ON_GITHUB" {
|
||||
clonedAction = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
defer (func() {
|
||||
stepActionRemoteNewCloneExecutor = origStepAtionRemoteNewCloneExecutor
|
||||
})()
|
||||
|
||||
cacheMock := &TestRepositoryCache{}
|
||||
sar := &stepActionRemote{
|
||||
Step: tt.stepModel,
|
||||
RunContext: &RunContext{
|
||||
@@ -382,6 +341,7 @@ func TestStepActionRemotePreThroughActionToken(t *testing.T) {
|
||||
GitHubInstance: "https://enterprise.github.com",
|
||||
ReplaceGheActionWithGithubCom: []string{"org/repo"},
|
||||
ReplaceGheActionTokenWithGithubCom: "PRIVATE_ACTIONS_TOKEN_ON_GITHUB",
|
||||
ActionCache: cacheMock,
|
||||
},
|
||||
Run: &model.Run{
|
||||
JobID: "1",
|
||||
@@ -395,20 +355,15 @@ func TestStepActionRemotePreThroughActionToken(t *testing.T) {
|
||||
readAction: sarm.readAction,
|
||||
}
|
||||
|
||||
suffixMatcher := func(suffix string) interface{} {
|
||||
return mock.MatchedBy(func(actionDir string) bool {
|
||||
return strings.HasSuffix(actionDir, suffix)
|
||||
})
|
||||
}
|
||||
|
||||
sarm.On("readAction", sar.Step, suffixMatcher("org-repo-path@ref"), "path", mock.Anything, mock.Anything).Return(&model.Action{}, nil)
|
||||
sarm.Mock.On("readAction", sar.Step, mock.AnythingOfType("string"), "path", mock.Anything, mock.Anything).Return(&model.Action{}, nil)
|
||||
cacheMock.Mock.On("Fetch", ctx, mock.AnythingOfType("string"), "https://github.com/org/repo", "ref", "PRIVATE_ACTIONS_TOKEN_ON_GITHUB").Return("someval")
|
||||
|
||||
err := sar.pre()(ctx)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, true, clonedAction)
|
||||
|
||||
sarm.AssertExpectations(t)
|
||||
cacheMock.Mock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user