Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion cmd/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ type Input struct {
actionOfflineMode bool
logPrefixJobID bool
networkName string
useNewActionCache bool
localRepository []string
}

Expand Down
48 changes: 26 additions & 22 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@
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())

Expand Down Expand Up @@ -393,6 +392,16 @@
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)
}

Check warning on line 402 in cmd/root.go

View check run for this annotation

Codecov / codecov/patch

cmd/root.go#L395-L402

Added lines #L395 - L402 were not covered by tests
}

matrixes := parseMatrix(input.matrix)
log.Debugf("Evaluated matrix inclusions: %v", matrixes)

Expand Down Expand Up @@ -588,31 +597,26 @@
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{

Check warning on line 602 in cmd/root.go

View check run for this annotation

Codecov / codecov/patch

cmd/root.go#L600-L602

Added lines #L600 - L602 were not covered by tests
Path: config.ActionCacheDir,
}
},

Check warning on line 604 in cmd/root.go

View check run for this annotation

Codecov / codecov/patch

cmd/root.go#L604

Added line #L604 was not covered by tests
}
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{},

Check warning on line 616 in cmd/root.go

View check run for this annotation

Codecov / codecov/patch

cmd/root.go#L606-L616

Added lines #L606 - L616 were not covered by tests
}
}

r, err := runner.New(config)
if err != nil {
return err
Expand Down
3 changes: 3 additions & 0 deletions pkg/container/docker_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions pkg/container/host_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@
}

func (e *HostEnvironment) CopyTarStream(ctx context.Context, destPath string, tarStream io.Reader) error {
if common.Dryrun(ctx) {
return nil
}

Check warning on line 67 in pkg/container/host_environment.go

View check run for this annotation

Codecov / codecov/patch

pkg/container/host_environment.go#L66-L67

Added lines #L66 - L67 were not covered by tests
if err := os.RemoveAll(destPath); err != nil {
return err
}
Expand Down
74 changes: 25 additions & 49 deletions pkg/runner/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
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()
Expand All @@ -133,21 +133,13 @@
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 {
Expand Down Expand Up @@ -176,7 +168,7 @@

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)}
Expand All @@ -186,13 +178,13 @@

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
}

Expand Down Expand Up @@ -223,27 +215,10 @@
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()
Expand All @@ -260,7 +235,7 @@
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 {
Expand Down Expand Up @@ -291,16 +266,16 @@
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,
Expand All @@ -324,6 +299,7 @@
if len(entrypoint) == 0 {
if entrypointType == "pre-entrypoint" && action.Runs.PreEntrypoint != "" {
entrypoint, err = shellquote.Split(action.Runs.PreEntrypoint)

Check warning on line 302 in pkg/runner/action.go

View check run for this annotation

Codecov / codecov/patch

pkg/runner/action.go#L302

Added line #L302 was not covered by tests
if err != nil {
return err
}
Expand Down Expand Up @@ -545,7 +521,7 @@

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
}

Expand All @@ -557,11 +533,11 @@
return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx)

case model.ActionRunsUsingDocker:
location := actionLocation
if remoteAction == nil {
location = containerActionDir
actionDir = ""
actionPath = containerActionDir

Check warning on line 538 in pkg/runner/action.go

View check run for this annotation

Codecov / codecov/patch

pkg/runner/action.go#L537-L538

Added lines #L537 - L538 were not covered by tests
}
return execAsDocker(ctx, step, actionName, location, remoteAction == nil, "pre-entrypoint")
return execAsDocker(ctx, step, actionName, actionDir, actionPath, remoteAction == nil, "pre-entrypoint")

Check warning on line 540 in pkg/runner/action.go

View check run for this annotation

Codecov / codecov/patch

pkg/runner/action.go#L540

Added line #L540 was not covered by tests

case model.ActionRunsUsingComposite:
if step.getCompositeSteps() == nil {
Expand Down Expand Up @@ -662,14 +638,14 @@
return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx)

case model.ActionRunsUsingDocker:
location := actionLocation
if remoteAction == nil {
location = containerActionDir
actionDir = ""
actionPath = containerActionDir

Check warning on line 643 in pkg/runner/action.go

View check run for this annotation

Codecov / codecov/patch

pkg/runner/action.go#L642-L643

Added lines #L642 - L643 were not covered by tests
}
return execAsDocker(ctx, step, actionName, location, remoteAction == nil, "post-entrypoint")
return execAsDocker(ctx, step, actionName, actionDir, actionPath, remoteAction == nil, "post-entrypoint")

Check warning on line 645 in pkg/runner/action.go

View check run for this annotation

Codecov / codecov/patch

pkg/runner/action.go#L645

Added line #L645 was not covered by tests

case model.ActionRunsUsingComposite:
if err := maybeCopyToActionDir(ctx, step, actionDir, actionPath, containerActionDir); err != nil {
if err := maybeCopyToActionDir(ctx, step, actionPath, containerActionDir); err != nil {
return err
}

Expand Down
6 changes: 5 additions & 1 deletion pkg/runner/action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)

Expand Down
9 changes: 9 additions & 0 deletions pkg/runner/container_mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
54 changes: 4 additions & 50 deletions pkg/runner/reusable_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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
}
Expand All @@ -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)
Expand Down
Loading
Loading