1
0
mirror of https://gitea.com/gitea/act synced 2026-05-01 01:27:48 +02:00

Use golangci-lint fmt to format code (#163)

Use `golangci-lint fmt` to format code, upgrading `.golangci.yml` to v2 and mirroring the linter configuration used by https://github.com/go-gitea/gitea. `gci` now handles import ordering into standard, project-local, blank, and default groups.

Mirrors https://github.com/go-gitea/gitea/pull/37194.

Changes:
- Upgrade `.golangci.yml` to v2 format with the same linter set as gitea (minus `prealloc`, `unparam`, `testifylint`, `nilnil` which produced too many pre-existing issues)
- Add path-based exclusions (`bodyclose`, `gosec` in tests; `gosec:G115`/`G117` globally)
- Run lint via `make lint-go` in CI instead of `golangci/golangci-lint-action`, matching the pattern used by other Gitea repos
- Apply safe auto-fixes (`modernize`, `perfsprint`, `usetesting`, etc.)
- Add explanations to existing `//nolint` directives
- Remove dead code (unused `newRemoteReusableWorkflow` and `networkName`), duplicate imports, and shadowed `max` builtins
- Replace deprecated `docker/distribution/reference` with `distribution/reference`
- Fix `Deprecated:` comment casing and simplify nil/len checks

---
This PR was written with the help of Claude Opus 4.7

Reviewed-on: https://gitea.com/gitea/act/pulls/163
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
This commit is contained in:
silverwind
2026-04-18 09:10:09 +00:00
committed by silverwind
parent 3232358e71
commit f923badec7
89 changed files with 821 additions and 791 deletions

View File

@@ -22,10 +22,8 @@ jobs:
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
- uses: golangci/golangci-lint-action@v3.7.0 - name: lint
with: run: make lint-go
version: v1.53
only-new-issues: true
- uses: megalinter/megalinter/flavors/go@v7.8.0 - uses: megalinter/megalinter/flavors/go@v7.8.0
env: env:
DEFAULT_BRANCH: master DEFAULT_BRANCH: master

View File

@@ -1,51 +1,146 @@
# Minimum golangci-lint version required: v1.46.0 version: "2"
run: output:
timeout: 3m sort-order:
- file
skip-dirs:
- report # megalinter results+fixes
linters-settings:
gocyclo:
# minimal code complexity to report, 30 by default (but we recommend 10-20)
min-complexity: 15
gocritic:
disabled-checks:
- ifElseChain
importas:
aliases:
- pkg: 'github.com/sirupsen/logrus'
alias: log
- pkg: 'github.com/stretchr/testify/assert'
alias: assert
depguard:
rules:
main:
deny:
- pkg: github.com/pkg/errors
desc: Please use "errors" package from standard library
- pkg: gotest.tools/v3
desc: Please keep tests unified using only github.com/stretchr/testify
- pkg: log
desc: Please keep logging unified using only github.com/sirupsen/logrus
linters: linters:
default: none
enable: enable:
- megacheck - bidichk
- govet - bodyclose
- revive - contextcheck
- depguard
- dupl
- errcheck
- gocheckcompilerdirectives
- gocritic
- gocyclo - gocyclo
- gosec - gosec
- unconvert - govet
- dupl
- nakedret
- prealloc
- exportloopref
- gocritic
- goimports
- whitespace
- misspell
- depguard
- importas - importas
- contextcheck - ineffassign
- misspell
- mirror
- modernize
- nakedret
- nolintlint - nolintlint
- perfsprint
- revive - revive
- staticcheck
- unconvert
- unused
- usestdlibvars
- usetesting
- wastedassign
- whitespace
settings:
depguard:
rules:
main:
deny:
- pkg: github.com/pkg/errors
desc: Please use "errors" package from standard library
- pkg: gotest.tools/v3
desc: Please keep tests unified using only github.com/stretchr/testify
- pkg: log
desc: Please keep logging unified using only github.com/sirupsen/logrus
gocyclo:
min-complexity: 15
gocritic:
disabled-checks:
- ifElseChain
importas:
alias:
- pkg: github.com/sirupsen/logrus
alias: log
- pkg: github.com/stretchr/testify/assert
alias: assert
nolintlint:
allow-unused: false
require-explanation: true
require-specific: true
revive:
severity: error
rules:
- name: blank-imports
- name: constant-logical-expr
- name: context-as-argument
- name: context-keys-type
- name: dot-imports
- name: empty-lines
- name: error-return
- name: error-strings
- name: exported
- name: identical-branches
- name: if-return
- name: increment-decrement
- name: modifies-value-receiver
- name: package-comments
- name: redefines-builtin-id
- name: superfluous-else
- name: time-naming
- name: unexported-return
- name: var-declaration
- name: var-naming
staticcheck:
checks:
- all
- -ST1003
- -ST1005
- -QF1001
- -QF1006
- -QF1008
usetesting:
os-temp-dir: true
perfsprint:
concat-loop: false
govet:
enable:
- nilness
- unusedwrite
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- bodyclose
- dupl
- errcheck
- gosec
- staticcheck
path: _test\.go
- linters:
- gosec
text: 'G115:'
- linters:
- gosec
text: 'G117:'
paths:
- report
issues:
max-issues-per-linter: 0
max-same-issues: 0
formatters:
enable:
- gci
- gofumpt
settings:
gci:
custom-order: true
sections:
- standard
- prefix(github.com/nektos/act)
- blank
- default
gofumpt:
extra-rules: true
exclusions:
generated: lax
paths:
- report
run:
timeout: 10m

View File

@@ -13,6 +13,8 @@ endif
ACT ?= go run main.go ACT ?= go run main.go
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4
HAS_TOKEN = $(if $(test -e ~/.config/github/token),true,false) HAS_TOKEN = $(if $(test -e ~/.config/github/token),true,false)
ifeq (true,$(HAS_TOKEN)) ifeq (true,$(HAS_TOKEN))
export GITHUB_TOKEN := $(shell cat ~/.config/github/token) export GITHUB_TOKEN := $(shell cat ~/.config/github/token)
@@ -27,11 +29,19 @@ build:
.PHONY: format .PHONY: format
format: format:
go fmt ./... go run $(GOLANGCI_LINT_PACKAGE) fmt
.PHONY: format-check
format-check: format
@diff=$$(git diff --color=always); \
if [ -n "$$diff" ]; then \
echo "Please run 'make format' and commit the result:"; \
printf "%s" "$${diff}"; \
exit 1; \
fi
.PHONY: format-all .PHONY: format-all
format-all: format-all: format
go fmt ./...
npx prettier --write . npx prettier --write .
.PHONY: test .PHONY: test
@@ -41,7 +51,7 @@ test:
.PHONY: lint-go .PHONY: lint-go
lint-go: lint-go:
golangci-lint run $(FIX) go run $(GOLANGCI_LINT_PACKAGE) run $(FIX)
.PHONY: lint-js .PHONY: lint-js
lint-js: lint-js:

View File

@@ -76,7 +76,7 @@ func getVersionNotices(version string) []Notice {
noticeURL.RawQuery = query.Encode() noticeURL.RawQuery = query.Encode()
client := &http.Client{} client := &http.Client{}
req, err := http.NewRequest("GET", noticeURL.String(), nil) req, err := http.NewRequest(http.MethodGet, noticeURL.String(), nil)
if err != nil { if err != nil {
log.Debug(err) log.Debug(err)
return nil return nil
@@ -102,7 +102,7 @@ func getVersionNotices(version string) []Notice {
defer resp.Body.Close() defer resp.Body.Close()
notices := []Notice{} notices := []Notice{}
if resp.StatusCode == 304 { if resp.StatusCode == http.StatusNotModified {
log.Debug("No new notices") log.Debug("No new notices")
return nil return nil
} }

View File

@@ -4,13 +4,22 @@ import (
"bufio" "bufio"
"context" "context"
"fmt" "fmt"
"maps"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
"strconv"
"strings" "strings"
"github.com/nektos/act/pkg/artifactcache"
"github.com/nektos/act/pkg/artifacts"
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/model"
"github.com/nektos/act/pkg/runner"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/adrg/xdg" "github.com/adrg/xdg"
"github.com/andreaskoch/go-fswatch" "github.com/andreaskoch/go-fswatch"
@@ -20,13 +29,6 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.yaml.in/yaml/v4" "go.yaml.in/yaml/v4"
"github.com/nektos/act/pkg/artifactcache"
"github.com/nektos/act/pkg/artifacts"
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/model"
"github.com/nektos/act/pkg/runner"
) )
// Execute is the entry point to running the CLI // Execute is the entry point to running the CLI
@@ -147,7 +149,7 @@ func bugReport(ctx context.Context, version string) error {
report := sprintf("act version:", version) report := sprintf("act version:", version)
report += sprintf("GOOS:", runtime.GOOS) report += sprintf("GOOS:", runtime.GOOS)
report += sprintf("GOARCH:", runtime.GOARCH) report += sprintf("GOARCH:", runtime.GOARCH)
report += sprintf("NumCPU:", fmt.Sprint(runtime.NumCPU())) report += sprintf("NumCPU:", strconv.Itoa(runtime.NumCPU()))
var dockerHost string var dockerHost string
var exists bool var exists bool
@@ -216,7 +218,7 @@ func bugReport(ctx context.Context, version string) error {
report += sprintf("\tOS version:", info.OSVersion) report += sprintf("\tOS version:", info.OSVersion)
report += sprintf("\tOS arch:", info.Architecture) report += sprintf("\tOS arch:", info.Architecture)
report += sprintf("\tOS kernel:", info.KernelVersion) report += sprintf("\tOS kernel:", info.KernelVersion)
report += sprintf("\tOS CPU:", fmt.Sprint(info.NCPU)) report += sprintf("\tOS CPU:", strconv.Itoa(info.NCPU))
report += sprintf("\tOS memory:", fmt.Sprintf("%d MB", info.MemTotal/1024/1024)) report += sprintf("\tOS memory:", fmt.Sprintf("%d MB", info.MemTotal/1024/1024))
report += fmt.Sprintln("\tSecurity options:") report += fmt.Sprintln("\tSecurity options:")
@@ -305,9 +307,7 @@ func readEnvs(path string, envs map[string]string) bool {
if err != nil { if err != nil {
log.Fatalf("Error loading from %s: %v", path, err) log.Fatalf("Error loading from %s: %v", path, err)
} }
for k, v := range env { maps.Copy(envs, env)
envs[k] = v
}
return true return true
} }
return false return false
@@ -330,7 +330,7 @@ func parseMatrix(matrix []string) map[string]map[string]bool {
return matrixes return matrixes
} }
//nolint:gocyclo //nolint:gocyclo // function handles many cases
func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []string) error { func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error {
if input.jsonLogger { if input.jsonLogger {
@@ -511,7 +511,7 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
log.Warnf(deprecationWarning, "privileged", "--privileged") log.Warnf(deprecationWarning, "privileged", "--privileged")
} }
if len(input.usernsMode) > 0 { if len(input.usernsMode) > 0 {
log.Warnf(deprecationWarning, "userns", fmt.Sprintf("--userns=%s", input.usernsMode)) log.Warnf(deprecationWarning, "userns", "--userns="+input.usernsMode)
} }
if len(input.containerCapAdd) > 0 { if len(input.containerCapAdd) > 0 {
log.Warnf(deprecationWarning, "container-cap-add", fmt.Sprintf("--cap-add=%s", input.containerCapAdd)) log.Warnf(deprecationWarning, "container-cap-add", fmt.Sprintf("--cap-add=%s", input.containerCapAdd))

View File

@@ -2,12 +2,13 @@ package main
import ( import (
"context" "context"
_ "embed"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"github.com/nektos/act/cmd" "github.com/nektos/act/cmd"
_ "embed"
) )
//go:embed VERSION //go:embed VERSION

View File

@@ -15,12 +15,12 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/nektos/act/pkg/common"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/timshannon/bolthold" "github.com/timshannon/bolthold"
"go.etcd.io/bbolt" "go.etcd.io/bbolt"
"github.com/nektos/act/pkg/common"
) )
const ( const (
@@ -74,7 +74,7 @@ func StartHandler(dir, outboundIP string, port uint16, logger logrus.FieldLogger
if outboundIP != "" { if outboundIP != "" {
h.outboundIP = outboundIP h.outboundIP = outboundIP
} else if ip := common.GetOutboundIP(); ip == nil { } else if ip := common.GetOutboundIP(); ip == nil {
return nil, fmt.Errorf("unable to determine outbound IP address") return nil, errors.New("unable to determine outbound IP address")
} else { } else {
h.outboundIP = ip.String() h.outboundIP = ip.String()
} }
@@ -363,7 +363,7 @@ func findCache(db *bolthold.Store, keys []string, version string) (*Cache, error
} }
return cache, nil return cache, nil
} }
prefixPattern := fmt.Sprintf("^%s", regexp.QuoteMeta(prefix)) prefixPattern := "^" + regexp.QuoteMeta(prefix)
re, err := regexp.Compile(prefixPattern) re, err := regexp.Compile(prefixPattern)
if err != nil { if err != nil {
continue continue
@@ -415,6 +415,7 @@ const (
keepOld = 5 * time.Minute keepOld = 5 * time.Minute
) )
//nolint:gocyclo // function handles many cases
func (h *Handler) gcCache() { func (h *Handler) gcCache() {
if h.gcing.Load() { if h.gcing.Load() {
return return

View File

@@ -64,7 +64,7 @@ func TestHandler(t *testing.T) {
}) })
t.Run("clean", func(t *testing.T) { t.Run("clean", func(t *testing.T) {
resp, err := http.Post(fmt.Sprintf("%s/clean", base), "", nil) resp, err := http.Post(base+"/clean", "", nil)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, 200, resp.StatusCode)
}) })
@@ -72,7 +72,7 @@ func TestHandler(t *testing.T) {
t.Run("reserve with bad request", func(t *testing.T) { t.Run("reserve with bad request", func(t *testing.T) {
body := []byte(`invalid json`) body := []byte(`invalid json`)
require.NoError(t, err) require.NoError(t, err)
resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) resp, err := http.Post(base+"/caches", "application/json", bytes.NewReader(body))
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 400, resp.StatusCode) assert.Equal(t, 400, resp.StatusCode)
}) })
@@ -90,7 +90,7 @@ func TestHandler(t *testing.T) {
Size: 100, Size: 100,
}) })
require.NoError(t, err) require.NoError(t, err)
resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) resp, err := http.Post(base+"/caches", "application/json", bytes.NewReader(body))
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, 200, resp.StatusCode)
@@ -104,7 +104,7 @@ func TestHandler(t *testing.T) {
Size: 100, Size: 100,
}) })
require.NoError(t, err) require.NoError(t, err)
resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) resp, err := http.Post(base+"/caches", "application/json", bytes.NewReader(body))
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, 200, resp.StatusCode)
@@ -117,7 +117,7 @@ func TestHandler(t *testing.T) {
t.Run("upload with bad id", func(t *testing.T) { t.Run("upload with bad id", func(t *testing.T) {
req, err := http.NewRequest(http.MethodPatch, req, err := http.NewRequest(http.MethodPatch,
fmt.Sprintf("%s/caches/invalid_id", base), bytes.NewReader(nil)) base+"/caches/invalid_id", bytes.NewReader(nil))
require.NoError(t, err) require.NoError(t, err)
req.Header.Set("Content-Type", "application/octet-stream") req.Header.Set("Content-Type", "application/octet-stream")
req.Header.Set("Content-Range", "bytes 0-99/*") req.Header.Set("Content-Range", "bytes 0-99/*")
@@ -151,7 +151,7 @@ func TestHandler(t *testing.T) {
Size: 100, Size: 100,
}) })
require.NoError(t, err) require.NoError(t, err)
resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) resp, err := http.Post(base+"/caches", "application/json", bytes.NewReader(body))
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, 200, resp.StatusCode)
@@ -202,7 +202,7 @@ func TestHandler(t *testing.T) {
Size: 100, Size: 100,
}) })
require.NoError(t, err) require.NoError(t, err)
resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) resp, err := http.Post(base+"/caches", "application/json", bytes.NewReader(body))
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, 200, resp.StatusCode)
@@ -226,7 +226,7 @@ func TestHandler(t *testing.T) {
t.Run("commit with bad id", func(t *testing.T) { t.Run("commit with bad id", func(t *testing.T) {
{ {
resp, err := http.Post(fmt.Sprintf("%s/caches/invalid_id", base), "", nil) resp, err := http.Post(base+"/caches/invalid_id", "", nil)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 400, resp.StatusCode) assert.Equal(t, 400, resp.StatusCode)
} }
@@ -254,7 +254,7 @@ func TestHandler(t *testing.T) {
Size: 100, Size: 100,
}) })
require.NoError(t, err) require.NoError(t, err)
resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) resp, err := http.Post(base+"/caches", "application/json", bytes.NewReader(body))
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, 200, resp.StatusCode)
@@ -300,7 +300,7 @@ func TestHandler(t *testing.T) {
Size: 100, Size: 100,
}) })
require.NoError(t, err) require.NoError(t, err)
resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) resp, err := http.Post(base+"/caches", "application/json", bytes.NewReader(body))
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, 200, resp.StatusCode)
@@ -328,7 +328,7 @@ func TestHandler(t *testing.T) {
}) })
t.Run("get with bad id", func(t *testing.T) { t.Run("get with bad id", func(t *testing.T) {
resp, err := http.Get(fmt.Sprintf("%s/artifacts/invalid_id", base)) resp, err := http.Get(base + "/artifacts/invalid_id")
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 400, resp.StatusCode) require.Equal(t, 400, resp.StatusCode)
}) })
@@ -537,7 +537,7 @@ func uploadCacheNormally(t *testing.T, base, key, version string, content []byte
Size: int64(len(content)), Size: int64(len(content)),
}) })
require.NoError(t, err) require.NoError(t, err)
resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) resp, err := http.Post(base+"/caches", "application/json", bytes.NewReader(body))
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, 200, resp.StatusCode)
@@ -578,7 +578,7 @@ func uploadCacheNormally(t *testing.T, base, key, version string, content []byte
archiveLocation = got.ArchiveLocation archiveLocation = got.ArchiveLocation
} }
{ {
resp, err := http.Get(archiveLocation) //nolint:gosec resp, err := http.Get(archiveLocation)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 200, resp.StatusCode) require.Equal(t, 200, resp.StatusCode)
got, err := io.ReadAll(resp.Body) got, err := io.ReadAll(resp.Body)

View File

@@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
) )
type Storage struct { type Storage struct {
@@ -103,11 +104,11 @@ func (s *Storage) Remove(id uint64) {
} }
func (s *Storage) filename(id uint64) string { func (s *Storage) filename(id uint64) string {
return filepath.Join(s.rootDir, fmt.Sprintf("%02x", id%0xff), fmt.Sprint(id)) return filepath.Join(s.rootDir, fmt.Sprintf("%02x", id%0xff), strconv.FormatUint(id, 10))
} }
func (s *Storage) tempDir(id uint64) string { func (s *Storage) tempDir(id uint64) string {
return filepath.Join(s.rootDir, "tmp", fmt.Sprint(id)) return filepath.Join(s.rootDir, "tmp", strconv.FormatUint(id, 10))
} }
func (s *Storage) tempName(id uint64, offset int64) string { func (s *Storage) tempName(id uint64, offset int64) string {

View File

@@ -13,9 +13,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/julienschmidt/httprouter"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/julienschmidt/httprouter"
) )
type FileContainerResourceURL struct { type FileContainerResourceURL struct {
@@ -55,8 +55,7 @@ type WriteFS interface {
OpenAppendable(name string) (WritableFile, error) OpenAppendable(name string) (WritableFile, error)
} }
type readWriteFSImpl struct { type readWriteFSImpl struct{}
}
func (fwfs readWriteFSImpl) Open(name string) (fs.File, error) { func (fwfs readWriteFSImpl) Open(name string) (fs.File, error) {
return os.Open(name) return os.Open(name)
@@ -74,7 +73,6 @@ func (fwfs readWriteFSImpl) OpenAppendable(name string) (WritableFile, error) {
return nil, err return nil, err
} }
file, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR, 0o644) file, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR, 0o644)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -88,7 +86,7 @@ func (fwfs readWriteFSImpl) OpenAppendable(name string) (WritableFile, error) {
var gzipExtension = ".gz__" var gzipExtension = ".gz__"
func safeResolve(baseDir string, relPath string) string { func safeResolve(baseDir, relPath string) string {
return filepath.Join(baseDir, filepath.Clean(filepath.Join(string(os.PathSeparator), relPath))) return filepath.Join(baseDir, filepath.Clean(filepath.Join(string(os.PathSeparator), relPath)))
} }
@@ -127,7 +125,6 @@ func uploads(router *httprouter.Router, baseDir string, fsys WriteFS) {
} }
return fsys.OpenWritable(safePath) return fsys.OpenWritable(safePath)
}() }()
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -275,7 +272,7 @@ func downloads(router *httprouter.Router, baseDir string, fsys fs.FS) {
}) })
} }
func Serve(ctx context.Context, artifactPath string, addr string, port string) context.CancelFunc { func Serve(ctx context.Context, artifactPath, addr, port string) context.CancelFunc {
serverContext, cancel := context.WithCancel(ctx) serverContext, cancel := context.WithCancel(ctx)
logger := common.Logger(serverContext) logger := common.Logger(serverContext)

View File

@@ -13,12 +13,12 @@ import (
"testing" "testing"
"testing/fstest" "testing/fstest"
"github.com/nektos/act/pkg/model"
"github.com/nektos/act/pkg/runner"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/nektos/act/pkg/model"
"github.com/nektos/act/pkg/runner"
) )
type writableMapFile struct { type writableMapFile struct {
@@ -39,7 +39,7 @@ type writeMapFS struct {
} }
func (fsys writeMapFS) OpenWritable(name string) (WritableFile, error) { func (fsys writeMapFS) OpenWritable(name string) (WritableFile, error) {
var file = &writableMapFile{ file := &writableMapFile{
MapFile: fstest.MapFile{ MapFile: fstest.MapFile{
Data: []byte("content2"), Data: []byte("content2"),
}, },
@@ -50,7 +50,7 @@ func (fsys writeMapFS) OpenWritable(name string) (WritableFile, error) {
} }
func (fsys writeMapFS) OpenAppendable(name string) (WritableFile, error) { func (fsys writeMapFS) OpenAppendable(name string) (WritableFile, error) {
var file = &writableMapFile{ file := &writableMapFile{
MapFile: fstest.MapFile{ MapFile: fstest.MapFile{
Data: []byte("content2"), Data: []byte("content2"),
}, },
@@ -63,12 +63,12 @@ func (fsys writeMapFS) OpenAppendable(name string) (WritableFile, error) {
func TestNewArtifactUploadPrepare(t *testing.T) { func TestNewArtifactUploadPrepare(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
var memfs = fstest.MapFS(map[string]*fstest.MapFile{}) memfs := fstest.MapFS(map[string]*fstest.MapFile{})
router := httprouter.New() router := httprouter.New()
uploads(router, "artifact/server/path", writeMapFS{memfs}) uploads(router, "artifact/server/path", writeMapFS{memfs})
req, _ := http.NewRequest("POST", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil) req, _ := http.NewRequest(http.MethodPost, "http://localhost/_apis/pipelines/workflows/1/artifacts", nil)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
router.ServeHTTP(rr, req) router.ServeHTTP(rr, req)
@@ -89,12 +89,12 @@ func TestNewArtifactUploadPrepare(t *testing.T) {
func TestArtifactUploadBlob(t *testing.T) { func TestArtifactUploadBlob(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
var memfs = fstest.MapFS(map[string]*fstest.MapFile{}) memfs := fstest.MapFS(map[string]*fstest.MapFile{})
router := httprouter.New() router := httprouter.New()
uploads(router, "artifact/server/path", writeMapFS{memfs}) uploads(router, "artifact/server/path", writeMapFS{memfs})
req, _ := http.NewRequest("PUT", "http://localhost/upload/1?itemPath=some/file", strings.NewReader("content")) req, _ := http.NewRequest(http.MethodPut, "http://localhost/upload/1?itemPath=some/file", strings.NewReader("content"))
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
router.ServeHTTP(rr, req) router.ServeHTTP(rr, req)
@@ -116,12 +116,12 @@ func TestArtifactUploadBlob(t *testing.T) {
func TestFinalizeArtifactUpload(t *testing.T) { func TestFinalizeArtifactUpload(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
var memfs = fstest.MapFS(map[string]*fstest.MapFile{}) memfs := fstest.MapFS(map[string]*fstest.MapFile{})
router := httprouter.New() router := httprouter.New()
uploads(router, "artifact/server/path", writeMapFS{memfs}) uploads(router, "artifact/server/path", writeMapFS{memfs})
req, _ := http.NewRequest("PATCH", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil) req, _ := http.NewRequest(http.MethodPatch, "http://localhost/_apis/pipelines/workflows/1/artifacts", nil)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
router.ServeHTTP(rr, req) router.ServeHTTP(rr, req)
@@ -142,7 +142,7 @@ func TestFinalizeArtifactUpload(t *testing.T) {
func TestListArtifacts(t *testing.T) { func TestListArtifacts(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
var memfs = fstest.MapFS(map[string]*fstest.MapFile{ memfs := fstest.MapFS(map[string]*fstest.MapFile{
"artifact/server/path/1/file.txt": { "artifact/server/path/1/file.txt": {
Data: []byte(""), Data: []byte(""),
}, },
@@ -151,7 +151,7 @@ func TestListArtifacts(t *testing.T) {
router := httprouter.New() router := httprouter.New()
downloads(router, "artifact/server/path", memfs) downloads(router, "artifact/server/path", memfs)
req, _ := http.NewRequest("GET", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil) req, _ := http.NewRequest(http.MethodGet, "http://localhost/_apis/pipelines/workflows/1/artifacts", nil)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
router.ServeHTTP(rr, req) router.ServeHTTP(rr, req)
@@ -174,7 +174,7 @@ func TestListArtifacts(t *testing.T) {
func TestListArtifactContainer(t *testing.T) { func TestListArtifactContainer(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
var memfs = fstest.MapFS(map[string]*fstest.MapFile{ memfs := fstest.MapFS(map[string]*fstest.MapFile{
"artifact/server/path/1/some/file": { "artifact/server/path/1/some/file": {
Data: []byte(""), Data: []byte(""),
}, },
@@ -183,7 +183,7 @@ func TestListArtifactContainer(t *testing.T) {
router := httprouter.New() router := httprouter.New()
downloads(router, "artifact/server/path", memfs) downloads(router, "artifact/server/path", memfs)
req, _ := http.NewRequest("GET", "http://localhost/download/1?itemPath=some/file", nil) req, _ := http.NewRequest(http.MethodGet, "http://localhost/download/1?itemPath=some/file", nil)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
router.ServeHTTP(rr, req) router.ServeHTTP(rr, req)
@@ -207,7 +207,7 @@ func TestListArtifactContainer(t *testing.T) {
func TestDownloadArtifactFile(t *testing.T) { func TestDownloadArtifactFile(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
var memfs = fstest.MapFS(map[string]*fstest.MapFile{ memfs := fstest.MapFS(map[string]*fstest.MapFile{
"artifact/server/path/1/some/file": { "artifact/server/path/1/some/file": {
Data: []byte("content"), Data: []byte("content"),
}, },
@@ -216,7 +216,7 @@ func TestDownloadArtifactFile(t *testing.T) {
router := httprouter.New() router := httprouter.New()
downloads(router, "artifact/server/path", memfs) downloads(router, "artifact/server/path", memfs)
req, _ := http.NewRequest("GET", "http://localhost/artifact/1/some/file", nil) req, _ := http.NewRequest(http.MethodGet, "http://localhost/artifact/1/some/file", nil)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
router.ServeHTTP(rr, req) router.ServeHTTP(rr, req)
@@ -344,7 +344,7 @@ func TestMkdirFsImplSafeResolve(t *testing.T) {
func TestDownloadArtifactFileUnsafePath(t *testing.T) { func TestDownloadArtifactFileUnsafePath(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
var memfs = fstest.MapFS(map[string]*fstest.MapFile{ memfs := fstest.MapFS(map[string]*fstest.MapFile{
"artifact/server/path/some/file": { "artifact/server/path/some/file": {
Data: []byte("content"), Data: []byte("content"),
}, },
@@ -353,7 +353,7 @@ func TestDownloadArtifactFileUnsafePath(t *testing.T) {
router := httprouter.New() router := httprouter.New()
downloads(router, "artifact/server/path", memfs) downloads(router, "artifact/server/path", memfs)
req, _ := http.NewRequest("GET", "http://localhost/artifact/2/../../some/file", nil) req, _ := http.NewRequest(http.MethodGet, "http://localhost/artifact/2/../../some/file", nil)
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
router.ServeHTTP(rr, req) router.ServeHTTP(rr, req)
@@ -370,12 +370,12 @@ func TestDownloadArtifactFileUnsafePath(t *testing.T) {
func TestArtifactUploadBlobUnsafePath(t *testing.T) { func TestArtifactUploadBlobUnsafePath(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
var memfs = fstest.MapFS(map[string]*fstest.MapFile{}) memfs := fstest.MapFS(map[string]*fstest.MapFile{})
router := httprouter.New() router := httprouter.New()
uploads(router, "artifact/server/path", writeMapFS{memfs}) uploads(router, "artifact/server/path", writeMapFS{memfs})
req, _ := http.NewRequest("PUT", "http://localhost/upload/1?itemPath=../../some/file", strings.NewReader("content")) req, _ := http.NewRequest(http.MethodPut, "http://localhost/upload/1?itemPath=../../some/file", strings.NewReader("content"))
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
router.ServeHTTP(rr, req) router.ServeHTTP(rr, req)

View File

@@ -1,9 +1,9 @@
package common package common
// CartesianProduct takes map of lists and returns list of unique tuples // CartesianProduct takes map of lists and returns list of unique tuples
func CartesianProduct(mapOfLists map[string][]interface{}) []map[string]interface{} { func CartesianProduct(mapOfLists map[string][]any) []map[string]any {
listNames := make([]string, 0) listNames := make([]string, 0)
lists := make([][]interface{}, 0) lists := make([][]any, 0)
for k, v := range mapOfLists { for k, v := range mapOfLists {
listNames = append(listNames, k) listNames = append(listNames, k)
lists = append(lists, v) lists = append(lists, v)
@@ -11,9 +11,9 @@ func CartesianProduct(mapOfLists map[string][]interface{}) []map[string]interfac
listCart := cartN(lists...) listCart := cartN(lists...)
rtn := make([]map[string]interface{}, 0) rtn := make([]map[string]any, 0)
for _, list := range listCart { for _, list := range listCart {
vMap := make(map[string]interface{}) vMap := make(map[string]any)
for i, v := range list { for i, v := range list {
vMap[listNames[i]] = v vMap[listNames[i]] = v
} }
@@ -22,7 +22,7 @@ func CartesianProduct(mapOfLists map[string][]interface{}) []map[string]interfac
return rtn return rtn
} }
func cartN(a ...[]interface{}) [][]interface{} { func cartN(a ...[]any) [][]any {
c := 1 c := 1
for _, a := range a { for _, a := range a {
c *= len(a) c *= len(a)
@@ -30,8 +30,8 @@ func cartN(a ...[]interface{}) [][]interface{} {
if c == 0 || len(a) == 0 { if c == 0 || len(a) == 0 {
return nil return nil
} }
p := make([][]interface{}, c) p := make([][]any, c)
b := make([]interface{}, c*len(a)) b := make([]any, c*len(a))
n := make([]int, len(a)) n := make([]int, len(a))
s := 0 s := 0
for i := range p { for i := range p {

View File

@@ -8,7 +8,7 @@ import (
func TestCartesianProduct(t *testing.T) { func TestCartesianProduct(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
input := map[string][]interface{}{ input := map[string][]any{
"foo": {1, 2, 3, 4}, "foo": {1, 2, 3, 4},
"bar": {"a", "b", "c"}, "bar": {"a", "b", "c"},
"baz": {false, true}, "baz": {false, true},
@@ -25,7 +25,7 @@ func TestCartesianProduct(t *testing.T) {
assert.Contains(v, "baz") assert.Contains(v, "baz")
} }
input = map[string][]interface{}{ input = map[string][]any{
"foo": {1, 2, 3, 4}, "foo": {1, 2, 3, 4},
"bar": {}, "bar": {},
"baz": {false, true}, "baz": {false, true},
@@ -33,7 +33,7 @@ func TestCartesianProduct(t *testing.T) {
output = CartesianProduct(input) output = CartesianProduct(input)
assert.Len(output, 0) assert.Len(output, 0)
input = map[string][]interface{}{} input = map[string][]any{}
output = CartesianProduct(input) output = CartesianProduct(input)
assert.Len(output, 0) assert.Len(output, 0)
} }

View File

@@ -72,6 +72,7 @@ func (p *Pen) drawTopBars(buf io.Writer, labels ...string) {
} }
fmt.Fprintf(buf, "\n") fmt.Fprintf(buf, "\n")
} }
func (p *Pen) drawBottomBars(buf io.Writer, labels ...string) { func (p *Pen) drawBottomBars(buf io.Writer, labels ...string) {
style := styleDefs[p.style] style := styleDefs[p.style]
for _, label := range labels { for _, label := range labels {
@@ -83,6 +84,7 @@ func (p *Pen) drawBottomBars(buf io.Writer, labels ...string) {
} }
fmt.Fprintf(buf, "\n") fmt.Fprintf(buf, "\n")
} }
func (p *Pen) drawLabels(buf io.Writer, labels ...string) { func (p *Pen) drawLabels(buf io.Writer, labels ...string) {
style := styleDefs[p.style] style := styleDefs[p.style]
for _, label := range labels { for _, label := range labels {
@@ -125,11 +127,8 @@ func (p *Pen) DrawBoxes(labels ...string) *Drawing {
// Draw to writer // Draw to writer
func (d *Drawing) Draw(writer io.Writer, centerOnWidth int) { func (d *Drawing) Draw(writer io.Writer, centerOnWidth int) {
padSize := (centerOnWidth - d.GetWidth()) / 2 padSize := max((centerOnWidth-d.GetWidth())/2, 0)
if padSize < 0 { for l := range strings.SplitSeq(d.buf.String(), "\n") {
padSize = 0
}
for _, l := range strings.Split(d.buf.String(), "\n") {
if len(l) > 0 { if len(l) > 0 {
padding := strings.Repeat(" ", padSize) padding := strings.Repeat(" ", padSize)
fmt.Fprintf(writer, "%s%s\n", padding, l) fmt.Fprintf(writer, "%s%s\n", padding, l)

View File

@@ -19,7 +19,7 @@ func (w Warning) Error() string {
} }
// Warningf create a warning // Warningf create a warning
func Warningf(format string, args ...interface{}) Warning { func Warningf(format string, args ...any) Warning {
w := Warning{ w := Warning{
Message: fmt.Sprintf(format, args...), Message: fmt.Sprintf(format, args...),
} }
@@ -33,7 +33,7 @@ type Executor func(ctx context.Context) error
type Conditional func(ctx context.Context) bool type Conditional func(ctx context.Context) bool
// NewInfoExecutor is an executor that logs messages // NewInfoExecutor is an executor that logs messages
func NewInfoExecutor(format string, args ...interface{}) Executor { func NewInfoExecutor(format string, args ...any) Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
logger := Logger(ctx) logger := Logger(ctx)
logger.Infof(format, args...) logger.Infof(format, args...)
@@ -42,7 +42,7 @@ func NewInfoExecutor(format string, args ...interface{}) Executor {
} }
// NewDebugExecutor is an executor that logs messages // NewDebugExecutor is an executor that logs messages
func NewDebugExecutor(format string, args ...interface{}) Executor { func NewDebugExecutor(format string, args ...any) Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
logger := Logger(ctx) logger := Logger(ctx)
logger.Debugf(format, args...) logger.Debugf(format, args...)
@@ -69,7 +69,7 @@ func NewPipelineExecutor(executors ...Executor) Executor {
} }
// NewConditionalExecutor creates a new executor based on conditions // NewConditionalExecutor creates a new executor based on conditions
func NewConditionalExecutor(conditional Conditional, trueExecutor Executor, falseExecutor Executor) Executor { func NewConditionalExecutor(conditional Conditional, trueExecutor, falseExecutor Executor) Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
if conditional(ctx) { if conditional(ctx) {
if trueExecutor != nil { if trueExecutor != nil {
@@ -128,14 +128,14 @@ func NewParallelExecutor(parallel int, executors ...Executor) Executor {
}(i, work, errs) }(i, work, errs)
} }
for i := 0; i < len(executors); i++ { for i := range executors {
work <- executors[i] work <- executors[i]
} }
close(work) close(work)
// Executor waits all executors to cleanup these resources. // Executor waits all executors to cleanup these resources.
var firstErr error var firstErr error
for i := 0; i < len(executors); i++ { for range executors {
err := <-errs err := <-errs
if firstErr == nil { if firstErr == nil {
firstErr = err firstErr = err

View File

@@ -14,24 +14,24 @@ import (
func TestMaxParallel2Quick(t *testing.T) { func TestMaxParallel2Quick(t *testing.T) {
ctx := context.Background() ctx := context.Background()
var currentRunning int32 var currentRunning atomic.Int32
var maxSimultaneous int32 var maxSimultaneous atomic.Int32
executors := make([]Executor, 4) executors := make([]Executor, 4)
for i := 0; i < 4; i++ { for i := range 4 {
executors[i] = func(ctx context.Context) error { executors[i] = func(ctx context.Context) error {
current := atomic.AddInt32(&currentRunning, 1) current := currentRunning.Add(1)
// Update max if needed // Update max if needed
for { for {
maxValue := atomic.LoadInt32(&maxSimultaneous) maxValue := maxSimultaneous.Load()
if current <= maxValue || atomic.CompareAndSwapInt32(&maxSimultaneous, maxValue, current) { if current <= maxValue || maxSimultaneous.CompareAndSwap(maxValue, current) {
break break
} }
} }
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
atomic.AddInt32(&currentRunning, -1) currentRunning.Add(-1)
return nil return nil
} }
} }
@@ -39,7 +39,7 @@ func TestMaxParallel2Quick(t *testing.T) {
err := NewParallelExecutor(2, executors...)(ctx) err := NewParallelExecutor(2, executors...)(ctx)
assert.NoError(t, err) assert.NoError(t, err)
assert.LessOrEqual(t, atomic.LoadInt32(&maxSimultaneous), int32(2), assert.LessOrEqual(t, maxSimultaneous.Load(), int32(2),
"Should not exceed max-parallel: 2") "Should not exceed max-parallel: 2")
} }
@@ -47,16 +47,16 @@ func TestMaxParallel2Quick(t *testing.T) {
func TestMaxParallel1Sequential(t *testing.T) { func TestMaxParallel1Sequential(t *testing.T) {
ctx := context.Background() ctx := context.Background()
var currentRunning int32 var currentRunning atomic.Int32
var maxSimultaneous int32 var maxSimultaneous atomic.Int32
var executionOrder []int var executionOrder []int
var orderMutex sync.Mutex var orderMutex sync.Mutex
executors := make([]Executor, 5) executors := make([]Executor, 5)
for i := 0; i < 5; i++ { for i := range 5 {
taskID := i taskID := i
executors[i] = func(ctx context.Context) error { executors[i] = func(ctx context.Context) error {
current := atomic.AddInt32(&currentRunning, 1) current := currentRunning.Add(1)
// Track execution order // Track execution order
orderMutex.Lock() orderMutex.Lock()
@@ -65,14 +65,14 @@ func TestMaxParallel1Sequential(t *testing.T) {
// Update max if needed // Update max if needed
for { for {
maxValue := atomic.LoadInt32(&maxSimultaneous) maxValue := maxSimultaneous.Load()
if current <= maxValue || atomic.CompareAndSwapInt32(&maxSimultaneous, maxValue, current) { if current <= maxValue || maxSimultaneous.CompareAndSwap(maxValue, current) {
break break
} }
} }
time.Sleep(20 * time.Millisecond) time.Sleep(20 * time.Millisecond)
atomic.AddInt32(&currentRunning, -1) currentRunning.Add(-1)
return nil return nil
} }
} }
@@ -80,7 +80,7 @@ func TestMaxParallel1Sequential(t *testing.T) {
err := NewParallelExecutor(1, executors...)(ctx) err := NewParallelExecutor(1, executors...)(ctx)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, int32(1), atomic.LoadInt32(&maxSimultaneous), assert.Equal(t, int32(1), maxSimultaneous.Load(),
"max-parallel: 1 should only run 1 task at a time") "max-parallel: 1 should only run 1 task at a time")
assert.Len(t, executionOrder, 5, "All 5 tasks should have executed") assert.Len(t, executionOrder, 5, "All 5 tasks should have executed")
} }

View File

@@ -13,21 +13,21 @@ import (
// TestMaxParallelJobExecution tests actual job execution with max-parallel // TestMaxParallelJobExecution tests actual job execution with max-parallel
func TestMaxParallelJobExecution(t *testing.T) { func TestMaxParallelJobExecution(t *testing.T) {
t.Run("MaxParallel=1 Sequential", func(t *testing.T) { t.Run("MaxParallel=1 Sequential", func(t *testing.T) {
var currentRunning int32 var currentRunning atomic.Int32
var maxConcurrent int32 var maxConcurrent int32
var executionOrder []int var executionOrder []int
var mu sync.Mutex var mu sync.Mutex
executors := make([]Executor, 5) executors := make([]Executor, 5)
for i := 0; i < 5; i++ { for i := range 5 {
taskID := i taskID := i
executors[i] = func(ctx context.Context) error { executors[i] = func(ctx context.Context) error {
current := atomic.AddInt32(&currentRunning, 1) current := currentRunning.Add(1)
// Track max concurrent // Track max concurrent
for { for {
max := atomic.LoadInt32(&maxConcurrent) maxValue := atomic.LoadInt32(&maxConcurrent)
if current <= max || atomic.CompareAndSwapInt32(&maxConcurrent, max, current) { if current <= maxValue || atomic.CompareAndSwapInt32(&maxConcurrent, maxValue, current) {
break break
} }
} }
@@ -37,7 +37,7 @@ func TestMaxParallelJobExecution(t *testing.T) {
mu.Unlock() mu.Unlock()
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
atomic.AddInt32(&currentRunning, -1) currentRunning.Add(-1)
return nil return nil
} }
} }
@@ -51,23 +51,23 @@ func TestMaxParallelJobExecution(t *testing.T) {
}) })
t.Run("MaxParallel=3 Limited", func(t *testing.T) { t.Run("MaxParallel=3 Limited", func(t *testing.T) {
var currentRunning int32 var currentRunning atomic.Int32
var maxConcurrent int32 var maxConcurrent int32
executors := make([]Executor, 10) executors := make([]Executor, 10)
for i := 0; i < 10; i++ { for i := range 10 {
executors[i] = func(ctx context.Context) error { executors[i] = func(ctx context.Context) error {
current := atomic.AddInt32(&currentRunning, 1) current := currentRunning.Add(1)
for { for {
max := atomic.LoadInt32(&maxConcurrent) maxValue := atomic.LoadInt32(&maxConcurrent)
if current <= max || atomic.CompareAndSwapInt32(&maxConcurrent, max, current) { if current <= maxValue || atomic.CompareAndSwapInt32(&maxConcurrent, maxValue, current) {
break break
} }
} }
time.Sleep(20 * time.Millisecond) time.Sleep(20 * time.Millisecond)
atomic.AddInt32(&currentRunning, -1) currentRunning.Add(-1)
return nil return nil
} }
} }
@@ -82,22 +82,22 @@ func TestMaxParallelJobExecution(t *testing.T) {
t.Run("MaxParallel=0 Uses1Worker", func(t *testing.T) { t.Run("MaxParallel=0 Uses1Worker", func(t *testing.T) {
var maxConcurrent int32 var maxConcurrent int32
var currentRunning int32 var currentRunning atomic.Int32
executors := make([]Executor, 5) executors := make([]Executor, 5)
for i := 0; i < 5; i++ { for i := range 5 {
executors[i] = func(ctx context.Context) error { executors[i] = func(ctx context.Context) error {
current := atomic.AddInt32(&currentRunning, 1) current := currentRunning.Add(1)
for { for {
max := atomic.LoadInt32(&maxConcurrent) maxValue := atomic.LoadInt32(&maxConcurrent)
if current <= max || atomic.CompareAndSwapInt32(&maxConcurrent, max, current) { if current <= maxValue || atomic.CompareAndSwapInt32(&maxConcurrent, maxValue, current) {
break break
} }
} }
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
atomic.AddInt32(&currentRunning, -1) currentRunning.Add(-1)
return nil return nil
} }
} }
@@ -117,7 +117,7 @@ func TestMaxParallelWithErrors(t *testing.T) {
var successCount int32 var successCount int32
executors := make([]Executor, 5) executors := make([]Executor, 5)
for i := 0; i < 5; i++ { for i := range 5 {
taskID := i taskID := i
executors[i] = func(ctx context.Context) error { executors[i] = func(ctx context.Context) error {
if taskID == 2 { if taskID == 2 {
@@ -143,7 +143,7 @@ func TestMaxParallelWithErrors(t *testing.T) {
var startedCount int32 var startedCount int32
executors := make([]Executor, 10) executors := make([]Executor, 10)
for i := 0; i < 10; i++ { for i := range 10 {
executors[i] = func(ctx context.Context) error { executors[i] = func(ctx context.Context) error {
atomic.AddInt32(&startedCount, 1) atomic.AddInt32(&startedCount, 1)
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
@@ -175,7 +175,7 @@ func TestMaxParallelPerformance(t *testing.T) {
t.Run("ParallelFasterThanSequential", func(t *testing.T) { t.Run("ParallelFasterThanSequential", func(t *testing.T) {
executors := make([]Executor, 10) executors := make([]Executor, 10)
for i := 0; i < 10; i++ { for i := range 10 {
executors[i] = func(ctx context.Context) error { executors[i] = func(ctx context.Context) error {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
return nil return nil
@@ -203,7 +203,7 @@ func TestMaxParallelPerformance(t *testing.T) {
t.Run("OptimalWorkerCount", func(t *testing.T) { t.Run("OptimalWorkerCount", func(t *testing.T) {
executors := make([]Executor, 20) executors := make([]Executor, 20)
for i := 0; i < 20; i++ { for i := range 20 {
executors[i] = func(ctx context.Context) error { executors[i] = func(ctx context.Context) error {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
return nil return nil
@@ -236,7 +236,7 @@ func TestMaxParallelResourceSharing(t *testing.T) {
var mu sync.Mutex var mu sync.Mutex
executors := make([]Executor, 100) executors := make([]Executor, 100)
for i := 0; i < 100; i++ { for i := range 100 {
executors[i] = func(ctx context.Context) error { executors[i] = func(ctx context.Context) error {
mu.Lock() mu.Lock()
sharedCounter++ sharedCounter++
@@ -256,7 +256,7 @@ func TestMaxParallelResourceSharing(t *testing.T) {
resultChan := make(chan int, 50) resultChan := make(chan int, 50)
executors := make([]Executor, 50) executors := make([]Executor, 50)
for i := 0; i < 50; i++ { for i := range 50 {
taskID := i taskID := i
executors[i] = func(ctx context.Context) error { executors[i] = func(ctx context.Context) error {
resultChan <- taskID resultChan <- taskID

View File

@@ -2,7 +2,7 @@ package common
import ( import (
"context" "context"
"fmt" "errors"
"testing" "testing"
"time" "time"
@@ -19,7 +19,7 @@ func TestNewWorkflow(t *testing.T) {
assert.Nil(emptyWorkflow(ctx)) assert.Nil(emptyWorkflow(ctx))
// error case // error case
errorWorkflow := NewErrorExecutor(fmt.Errorf("test error")) errorWorkflow := NewErrorExecutor(errors.New("test error"))
assert.NotNil(errorWorkflow(ctx)) assert.NotNil(errorWorkflow(ctx))
// multiple success case // multiple success case
@@ -122,7 +122,7 @@ func TestNewParallelExecutorFailed(t *testing.T) {
count := 0 count := 0
errorWorkflow := NewPipelineExecutor(func(ctx context.Context) error { errorWorkflow := NewPipelineExecutor(func(ctx context.Context) error {
count++ count++
return fmt.Errorf("fake error") return errors.New("fake error")
}) })
err := NewParallelExecutor(1, errorWorkflow)(ctx) err := NewParallelExecutor(1, errorWorkflow)(ctx)
assert.Equal(1, count) assert.Equal(1, count)
@@ -135,7 +135,7 @@ func TestNewParallelExecutorCanceled(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
cancel() cancel()
errExpected := fmt.Errorf("fake error") errExpected := errors.New("fake error")
count := 0 count := 0
successWorkflow := NewPipelineExecutor(func(ctx context.Context) error { successWorkflow := NewPipelineExecutor(func(ctx context.Context) error {

View File

@@ -7,7 +7,7 @@ import (
) )
// CopyFile copy file // CopyFile copy file
func CopyFile(source string, dest string) (err error) { func CopyFile(source, dest string) (err error) {
sourcefile, err := os.Open(source) sourcefile, err := os.Open(source)
if err != nil { if err != nil {
return err return err
@@ -30,11 +30,11 @@ func CopyFile(source string, dest string) (err error) {
} }
} }
return return err
} }
// CopyDir recursive copy of directory // CopyDir recursive copy of directory
func CopyDir(source string, dest string) (err error) { func CopyDir(source, dest string) (err error) {
// get properties of source dir // get properties of source dir
sourceinfo, err := os.Stat(source) sourceinfo, err := os.Stat(source)
if err != nil { if err != nil {

View File

@@ -11,6 +11,8 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/nektos/act/pkg/common"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
@@ -18,8 +20,6 @@ import (
"github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/nektos/act/pkg/common"
) )
var ( var (
@@ -52,7 +52,7 @@ func (e *Error) Commit() string {
} }
// FindGitRevision get the current git revision // FindGitRevision get the current git revision
func FindGitRevision(ctx context.Context, file string) (shortSha string, sha string, err error) { func FindGitRevision(ctx context.Context, file string) (shortSha, sha string, err error) {
logger := common.Logger(ctx) logger := common.Logger(ctx)
gitDir, err := git.PlainOpenWithOptions( gitDir, err := git.PlainOpenWithOptions(
@@ -62,7 +62,6 @@ func FindGitRevision(ctx context.Context, file string) (shortSha string, sha str
EnableDotGitCommonDir: true, EnableDotGitCommonDir: true,
}, },
) )
if err != nil { if err != nil {
logger.WithError(err).Error("path", file, "not located inside a git repository") logger.WithError(err).Error("path", file, "not located inside a git repository")
return "", "", err return "", "", err
@@ -74,7 +73,7 @@ func FindGitRevision(ctx context.Context, file string) (shortSha string, sha str
} }
if head.Hash().IsZero() { if head.Hash().IsZero() {
return "", "", fmt.Errorf("HEAD sha1 could not be resolved") return "", "", errors.New("HEAD sha1 could not be resolved")
} }
hash := head.Hash().String() hash := head.Hash().String()
@@ -96,8 +95,8 @@ func FindGitRef(ctx context.Context, file string) (string, error) {
logger.Debugf("HEAD points to '%s'", ref) logger.Debugf("HEAD points to '%s'", ref)
// Prefer the git library to iterate over the references and find a matching tag or branch. // Prefer the git library to iterate over the references and find a matching tag or branch.
var refTag = "" refTag := ""
var refBranch = "" refBranch := ""
repo, err := git.PlainOpenWithOptions( repo, err := git.PlainOpenWithOptions(
file, file,
&git.PlainOpenOptions{ &git.PlainOpenOptions{
@@ -105,7 +104,6 @@ func FindGitRef(ctx context.Context, file string) (string, error) {
EnableDotGitCommonDir: true, EnableDotGitCommonDir: true,
}, },
) )
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -144,7 +142,6 @@ func FindGitRef(ctx context.Context, file string) (string, error) {
return nil return nil
}) })
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -198,7 +195,7 @@ func findGitRemoteURL(_ context.Context, file, remoteName string) (string, error
return remote.Config().URLs[0], nil return remote.Config().URLs[0], nil
} }
func findGitSlug(url string, githubInstance string) (string, string, error) { func findGitSlug(url, githubInstance string) (string, string, error) {
if matches := codeCommitHTTPRegex.FindStringSubmatch(url); matches != nil { if matches := codeCommitHTTPRegex.FindStringSubmatch(url); matches != nil {
return "CodeCommit", matches[2], nil return "CodeCommit", matches[2], nil
} else if matches := codeCommitSSHRegex.FindStringSubmatch(url); matches != nil { } else if matches := codeCommitSSHRegex.FindStringSubmatch(url); matches != nil {
@@ -209,7 +206,7 @@ func findGitSlug(url string, githubInstance string) (string, string, error) {
return "GitHub", fmt.Sprintf("%s/%s", matches[1], matches[2]), nil return "GitHub", fmt.Sprintf("%s/%s", matches[1], matches[2]), nil
} else if githubInstance != "github.com" { } else if githubInstance != "github.com" {
gheHTTPRegex := regexp.MustCompile(fmt.Sprintf(`^https?://%s/(.+)/(.+?)(?:.git)?$`, githubInstance)) gheHTTPRegex := regexp.MustCompile(fmt.Sprintf(`^https?://%s/(.+)/(.+?)(?:.git)?$`, githubInstance))
gheSSHRegex := regexp.MustCompile(fmt.Sprintf(`%s[:/](.+)/(.+?)(?:.git)?$`, githubInstance)) gheSSHRegex := regexp.MustCompile(githubInstance + "[:/](.+)/(.+?)(?:.git)?$")
if matches := gheHTTPRegex.FindStringSubmatch(url); matches != nil { if matches := gheHTTPRegex.FindStringSubmatch(url); matches != nil {
return "GitHubEnterprise", fmt.Sprintf("%s/%s", matches[1], matches[2]), nil return "GitHubEnterprise", fmt.Sprintf("%s/%s", matches[1], matches[2]), nil
} else if matches := gheSSHRegex.FindStringSubmatch(url); matches != nil { } else if matches := gheSSHRegex.FindStringSubmatch(url); matches != nil {
@@ -292,7 +289,7 @@ func gitOptions(token string) (fetchOptions git.FetchOptions, pullOptions git.Pu
// NewGitCloneExecutor creates an executor to clone git repos // NewGitCloneExecutor creates an executor to clone git repos
// //
//nolint:gocyclo //nolint:gocyclo // function handles many cases
func NewGitCloneExecutor(input NewGitCloneExecutorInput) common.Executor { func NewGitCloneExecutor(input NewGitCloneExecutorInput) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
logger := common.Logger(ctx) logger := common.Logger(ctx)
@@ -302,7 +299,7 @@ func NewGitCloneExecutor(input NewGitCloneExecutorInput) common.Executor {
cloneLock.Lock() cloneLock.Lock()
defer cloneLock.Unlock() defer cloneLock.Unlock()
refName := plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", input.Ref)) refName := plumbing.ReferenceName("refs/heads/" + input.Ref)
r, err := CloneIfRequired(ctx, refName, input, logger) r, err := CloneIfRequired(ctx, refName, input, logger)
if err != nil { if err != nil {
return err return err

View File

@@ -17,7 +17,7 @@ import (
func TestFindGitSlug(t *testing.T) { func TestFindGitSlug(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
var slugTests = []struct { slugTests := []struct {
url string // input url string // input
provider string // expected result provider string // expected result
slug string // expected result slug string // expected result
@@ -44,10 +44,7 @@ func TestFindGitSlug(t *testing.T) {
} }
func testDir(t *testing.T) string { func testDir(t *testing.T) string {
basedir, err := os.MkdirTemp("", "act-test") return t.TempDir()
require.NoError(t, err)
t.Cleanup(func() { _ = os.RemoveAll(basedir) })
return basedir
} }
func cleanGitHooks(dir string) error { func cleanGitHooks(dir string) error {
@@ -163,8 +160,6 @@ func TestGitFindRef(t *testing.T) {
}, },
}, },
} { } {
tt := tt
name := name
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
dir := filepath.Join(basedir, name) dir := filepath.Join(basedir, name)
require.NoError(t, os.MkdirAll(dir, 0o755)) require.NoError(t, os.MkdirAll(dir, 0o755))

View File

@@ -4,8 +4,9 @@ import (
"context" "context"
"io" "io"
"github.com/docker/go-connections/nat"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/docker/go-connections/nat"
) )
// NewContainerInput the input for the New function // NewContainerInput the input for the New function
@@ -45,11 +46,11 @@ type FileEntry struct {
// Container for managing docker run containers // Container for managing docker run containers
type Container interface { type Container interface {
Create(capAdd []string, capDrop []string) common.Executor Create(capAdd, capDrop []string) common.Executor
ConnectToNetwork(name string) common.Executor ConnectToNetwork(name string) common.Executor
Copy(destPath string, files ...*FileEntry) common.Executor Copy(destPath string, files ...*FileEntry) common.Executor
CopyTarStream(ctx context.Context, destPath string, tarStream io.Reader) error CopyTarStream(ctx context.Context, destPath string, tarStream io.Reader) error
CopyDir(destPath string, srcPath string, useGitIgnore bool) common.Executor CopyDir(destPath, srcPath string, useGitIgnore bool) common.Executor
GetContainerArchive(ctx context.Context, srcPath string) (io.ReadCloser, error) GetContainerArchive(ctx context.Context, srcPath string) (io.ReadCloser, error)
Pull(forcePull bool) common.Executor Pull(forcePull bool) common.Executor
Start(attach bool) common.Executor Start(attach bool) common.Executor

View File

@@ -6,10 +6,11 @@ import (
"context" "context"
"strings" "strings"
"github.com/nektos/act/pkg/common"
"github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/credentials" "github.com/docker/cli/cli/config/credentials"
"github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/registry"
"github.com/nektos/act/pkg/common"
) )
func LoadDockerAuthConfig(ctx context.Context, image string) (registry.AuthConfig, error) { func LoadDockerAuthConfig(ctx context.Context, image string) (registry.AuthConfig, error) {

View File

@@ -8,14 +8,13 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/nektos/act/pkg/common"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/archive"
// github.com/docker/docker/builder/dockerignore is deprecated // github.com/docker/docker/builder/dockerignore is deprecated
"github.com/moby/buildkit/frontend/dockerfile/dockerignore" "github.com/moby/buildkit/frontend/dockerfile/dockerignore"
"github.com/moby/patternmatcher" "github.com/moby/patternmatcher"
"github.com/nektos/act/pkg/common"
) )
// NewDockerBuildExecutor function to create a run executor for the container // NewDockerBuildExecutor function to create a run executor for the container
@@ -69,7 +68,8 @@ func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor {
return nil return nil
} }
} }
func createBuildContext(ctx context.Context, contextDir string, relDockerfile string) (io.ReadCloser, error) {
func createBuildContext(ctx context.Context, contextDir, relDockerfile string) (io.ReadCloser, error) {
common.Logger(ctx).Debugf("Creating archive for build context dir '%s' with relative dockerfile '%s'", contextDir, relDockerfile) common.Logger(ctx).Debugf("Creating archive for build context dir '%s' with relative dockerfile '%s'", contextDir, relDockerfile)
// And canonicalize dockerfile name to a platform-independent one // And canonicalize dockerfile name to a platform-independent one
@@ -96,7 +96,7 @@ func createBuildContext(ctx context.Context, contextDir string, relDockerfile st
// removed. The daemon will remove them for us, if needed, after it // removed. The daemon will remove them for us, if needed, after it
// parses the Dockerfile. Ignore errors here, as they will have been // parses the Dockerfile. Ignore errors here, as they will have been
// caught by validateContextDirectory above. // caught by validateContextDirectory above.
var includes = []string{"."} includes := []string{"."}
keepThem1, _ := patternmatcher.Matches(".dockerignore", excludes) keepThem1, _ := patternmatcher.Matches(".dockerignore", excludes)
keepThem2, _ := patternmatcher.Matches(relDockerfile, excludes) keepThem2, _ := patternmatcher.Matches(relDockerfile, excludes)
if keepThem1 || keepThem2 { if keepThem1 || keepThem2 {

View File

@@ -7,7 +7,7 @@
// See DOCKER_LICENSE for the full license text. // See DOCKER_LICENSE for the full license text.
// //
//nolint:unparam,errcheck,depguard,deadcode,unused //nolint:unparam,errcheck,depguard,unused // verbatim copy from docker/cli with minimal changes
package container package container
import ( import (
@@ -19,6 +19,7 @@ import (
"path/filepath" "path/filepath"
"reflect" "reflect"
"regexp" "regexp"
"slices"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -37,9 +38,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
var ( var deviceCgroupRuleRegexp = regexp.MustCompile(`^[acb] ([0-9]+|\*):([0-9]+|\*) [rwm]{1,3}$`)
deviceCgroupRuleRegexp = regexp.MustCompile(`^[acb] ([0-9]+|\*):([0-9]+|\*) [rwm]{1,3}$`)
)
// containerOptions is a data object with all the options for creating a container // containerOptions is a data object with all the options for creating a container
type containerOptions struct { type containerOptions struct {
@@ -322,7 +321,7 @@ type containerConfig struct {
// a HostConfig and returns them with the specified command. // a HostConfig and returns them with the specified command.
// If the specified args are not valid, it will return an error. // If the specified args are not valid, it will return an error.
// //
//nolint:gocyclo //nolint:gocyclo // function handles many cases
func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*containerConfig, error) { func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*containerConfig, error) {
var ( var (
attachStdin = copts.attach.Get("stdin") attachStdin = copts.attach.Get("stdin")
@@ -559,7 +558,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
return nil, errors.Errorf("--health-retries cannot be negative") return nil, errors.Errorf("--health-retries cannot be negative")
} }
if copts.healthStartPeriod < 0 { if copts.healthStartPeriod < 0 {
return nil, fmt.Errorf("--health-start-period cannot be negative") return nil, errors.New("--health-start-period cannot be negative")
} }
healthConfig = &container.HealthConfig{ healthConfig = &container.HealthConfig{
@@ -722,7 +721,6 @@ func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.Endpoin
) )
for i, n := range copts.netMode.Value() { for i, n := range copts.netMode.Value() {
n := n
if container.NetworkMode(n.Target).IsUserDefined() { if container.NetworkMode(n.Target).IsUserDefined() {
hasUserDefined = true hasUserDefined = true
} else { } else {
@@ -837,7 +835,7 @@ func convertToStandardNotation(ports []string) ([]string, error) {
for _, publish := range ports { for _, publish := range ports {
if strings.Contains(publish, "=") { if strings.Contains(publish, "=") {
params := map[string]string{"protocol": "tcp"} params := map[string]string{"protocol": "tcp"}
for _, param := range strings.Split(publish, ",") { for param := range strings.SplitSeq(publish, ",") {
opt := strings.Split(param, "=") opt := strings.Split(param, "=")
if len(opt) < 2 { if len(opt) < 2 {
return optsList, errors.Errorf("invalid publish opts format (should be name=value but got '%s')", param) return optsList, errors.Errorf("invalid publish opts format (should be name=value but got '%s')", param)
@@ -988,7 +986,7 @@ func validateDeviceCgroupRule(val string) (string, error) {
// validDeviceMode checks if the mode for device is valid or not. // validDeviceMode checks if the mode for device is valid or not.
// Valid mode is a composition of r (read), w (write), and m (mknod). // Valid mode is a composition of r (read), w (write), and m (mknod).
func validDeviceMode(mode string) bool { func validDeviceMode(mode string) bool {
var legalDeviceMode = map[rune]bool{ legalDeviceMode := map[rune]bool{
'r': true, 'r': true,
'w': true, 'w': true,
'm': true, 'm': true,
@@ -1006,7 +1004,7 @@ func validDeviceMode(mode string) bool {
} }
// validateDevice validates a path for devices // validateDevice validates a path for devices
func validateDevice(val string, serverOS string) (string, error) { func validateDevice(val, serverOS string) (string, error) {
switch serverOS { switch serverOS {
case "linux": case "linux":
return validateLinuxPath(val, validDeviceMode) return validateLinuxPath(val, validDeviceMode)
@@ -1067,10 +1065,8 @@ func validateLinuxPath(val string, validator func(string) bool) (string, error)
// validateAttach validates that the specified string is a valid attach option. // validateAttach validates that the specified string is a valid attach option.
func validateAttach(val string) (string, error) { func validateAttach(val string) (string, error) {
s := strings.ToLower(val) s := strings.ToLower(val)
for _, str := range []string{"stdin", "stdout", "stderr"} { if slices.Contains([]string{"stdin", "stdout", "stderr"}, s) {
if s == str { return s, nil
return s, nil
}
} }
return val, errors.Errorf("valid streams are STDIN, STDOUT and STDERR") return val, errors.Errorf("valid streams are STDIN, STDOUT and STDERR")
} }

View File

@@ -6,7 +6,7 @@
// See DOCKER_LICENSE for the full license text. // See DOCKER_LICENSE for the full license text.
// //
//nolint:unparam,whitespace,depguard,dupl,gocritic //nolint:unparam,depguard,gocritic // verbatim copy from docker/cli tests
package container package container
import ( import (
@@ -135,7 +135,6 @@ func TestParseRunAttach(t *testing.T) {
}, },
} }
for _, tc := range tests { for _, tc := range tests {
tc := tc
t.Run(tc.input, func(t *testing.T) { t.Run(tc.input, func(t *testing.T) {
config, _ := mustParse(t, tc.input) config, _ := mustParse(t, tc.input)
assert.Equal(t, config.AttachStdin, tc.expected.AttachStdin) assert.Equal(t, config.AttachStdin, tc.expected.AttachStdin)
@@ -191,9 +190,8 @@ func TestParseRunWithInvalidArgs(t *testing.T) {
} }
} }
//nolint:gocyclo //nolint:gocyclo // function handles many cases
func TestParseWithVolumes(t *testing.T) { func TestParseWithVolumes(t *testing.T) {
// A single volume // A single volume
arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`}) arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`})
if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil { if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
@@ -261,14 +259,13 @@ func TestParseWithVolumes(t *testing.T) {
t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0]) t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0])
} }
} }
} }
// setupPlatformVolume takes two arrays of volume specs - a Unix style // setupPlatformVolume takes two arrays of volume specs - a Unix style
// spec and a Windows style spec. Depending on the platform being unit tested, // spec and a Windows style spec. Depending on the platform being unit tested,
// it returns one of them, along with a volume string that would be passed // it returns one of them, along with a volume string that would be passed
// on the docker CLI (e.g. -v /bar -v /foo). // on the docker CLI (e.g. -v /bar -v /foo).
func setupPlatformVolume(u []string, w []string) ([]string, string) { func setupPlatformVolume(u, w []string) ([]string, string) {
var a []string var a []string
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
a = w a = w
@@ -340,7 +337,7 @@ func TestParseHostname(t *testing.T) {
hostnameWithDomain := "--hostname=hostname.domainname" hostnameWithDomain := "--hostname=hostname.domainname"
hostnameWithDomainTld := "--hostname=hostname.domainname.tld" hostnameWithDomainTld := "--hostname=hostname.domainname.tld"
for hostname, expectedHostname := range validHostnames { for hostname, expectedHostname := range validHostnames {
if config, _ := mustParse(t, fmt.Sprintf("--hostname=%s", hostname)); config.Hostname != expectedHostname { if config, _ := mustParse(t, "--hostname="+hostname); config.Hostname != expectedHostname {
t.Fatalf("Expected the config to have 'hostname' as %q, got %q", expectedHostname, config.Hostname) t.Fatalf("Expected the config to have 'hostname' as %q, got %q", expectedHostname, config.Hostname)
} }
} }
@@ -462,7 +459,6 @@ func TestParseDevice(t *testing.T) {
t.Fatalf("Expected %v, got %v", deviceMapping, hostconfig.Devices) t.Fatalf("Expected %v, got %v", deviceMapping, hostconfig.Devices)
} }
} }
} }
func TestParseNetworkConfig(t *testing.T) { func TestParseNetworkConfig(t *testing.T) {
@@ -634,7 +630,7 @@ func TestParseModes(t *testing.T) {
} }
// uts ko // uts ko
_, _, _, err = parseRun([]string{"--uts=container:", "img", "cmd"}) //nolint:dogsled _, _, _, err = parseRun([]string{"--uts=container:", "img", "cmd"}) //nolint:dogsled // ignoring multiple returns in test helpers
assert.ErrorContains(t, err, "--uts: invalid UTS mode") assert.ErrorContains(t, err, "--uts: invalid UTS mode")
// uts ok // uts ok
@@ -678,7 +674,7 @@ func TestParseRestartPolicy(t *testing.T) {
}, },
} }
for restart, expectedError := range invalids { for restart, expectedError := range invalids {
if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError { if _, _, _, err := parseRun([]string{"--restart=" + restart, "img", "cmd"}); err == nil || err.Error() != expectedError {
t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err) t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
} }
} }
@@ -695,7 +691,7 @@ func TestParseRestartPolicy(t *testing.T) {
func TestParseRestartPolicyAutoRemove(t *testing.T) { func TestParseRestartPolicyAutoRemove(t *testing.T) {
expected := "Conflicting options: --restart and --rm" expected := "Conflicting options: --restart and --rm"
_, _, _, err := parseRun([]string{"--rm", "--restart=always", "img", "cmd"}) //nolint:dogsled _, _, _, err := parseRun([]string{"--rm", "--restart=always", "img", "cmd"}) //nolint:dogsled // ignoring multiple returns in test helpers
if err == nil || err.Error() != expected { if err == nil || err.Error() != expected {
t.Fatalf("Expected error %v, but got none", expected) t.Fatalf("Expected error %v, but got none", expected)
} }
@@ -967,7 +963,6 @@ func TestConvertToStandardNotation(t *testing.T) {
for key, ports := range valid { for key, ports := range valid {
convertedPorts, err := convertToStandardNotation(ports) convertedPorts, err := convertToStandardNotation(ports)
if err != nil { if err != nil {
assert.NilError(t, err) assert.NilError(t, err)
} }

View File

@@ -12,7 +12,7 @@ import (
// ImageExistsLocally returns a boolean indicating if an image with the // ImageExistsLocally returns a boolean indicating if an image with the
// requested name, tag and architecture exists in the local docker image store // requested name, tag and architecture exists in the local docker image store
func ImageExistsLocally(ctx context.Context, imageName string, platform string) (bool, error) { func ImageExistsLocally(ctx context.Context, imageName, platform string) (bool, error) {
cli, err := GetDockerClient(ctx) cli, err := GetDockerClient(ctx)
if err != nil { if err != nil {
return false, err return false, err
@@ -35,7 +35,7 @@ func ImageExistsLocally(ctx context.Context, imageName string, platform string)
// RemoveImage removes image from local store, the function is used to run different // RemoveImage removes image from local store, the function is used to run different
// container image architectures // container image architectures
func RemoveImage(ctx context.Context, imageName string, force bool, pruneChildren bool) (bool, error) { func RemoveImage(ctx context.Context, imageName string, force, pruneChildren bool) (bool, error) {
cli, err := GetDockerClient(ctx) cli, err := GetDockerClient(ctx)
if err != nil { if err != nil {
return false, err return false, err

View File

@@ -74,7 +74,7 @@ func logDockerResponse(logger logrus.FieldLogger, dockerResponse io.ReadCloser,
return nil return nil
} }
func writeLog(logger logrus.FieldLogger, isError bool, format string, args ...interface{}) { func writeLog(logger logrus.FieldLogger, isError bool, format string, args ...any) {
if isError { if isError {
logger.Errorf(format, args...) logger.Errorf(format, args...)
} else { } else {

View File

@@ -5,9 +5,9 @@ package container
import ( import (
"context" "context"
"github.com/docker/docker/api/types"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/docker/docker/api/types"
) )
func NewDockerNetworkCreateExecutor(name string) common.Executor { func NewDockerNetworkCreateExecutor(name string) common.Executor {

View File

@@ -9,11 +9,11 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/docker/distribution/reference" "github.com/nektos/act/pkg/common"
"github.com/distribution/reference"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/registry"
"github.com/nektos/act/pkg/common"
) )
// NewDockerPullExecutor function to create a run executor for the container // NewDockerPullExecutor function to create a run executor for the container

View File

@@ -5,7 +5,6 @@ import (
"testing" "testing"
"github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
assert "github.com/stretchr/testify/assert" assert "github.com/stretchr/testify/assert"
) )

View File

@@ -16,6 +16,9 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/filecollector"
"github.com/Masterminds/semver" "github.com/Masterminds/semver"
"github.com/docker/cli/cli/compose/loader" "github.com/docker/cli/cli/compose/loader"
"github.com/docker/cli/cli/connhelper" "github.com/docker/cli/cli/connhelper"
@@ -23,7 +26,6 @@ import (
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/stdcopy"
"github.com/go-git/go-billy/v5/helper/polyfill" "github.com/go-git/go-billy/v5/helper/polyfill"
@@ -36,9 +38,6 @@ import (
specs "github.com/opencontainers/image-spec/specs-go/v1" specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"golang.org/x/term" "golang.org/x/term"
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/filecollector"
) )
// NewContainer creates a reference to a container // NewContainer creates a reference to a container
@@ -61,7 +60,7 @@ func (cr *containerReference) ConnectToNetwork(name string) common.Executor {
func (cr *containerReference) connectToNetwork(name string, aliases []string) common.Executor { func (cr *containerReference) connectToNetwork(name string, aliases []string) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
return cr.cli.NetworkConnect(ctx, name, cr.input.Name, &networktypes.EndpointSettings{ return cr.cli.NetworkConnect(ctx, name, cr.input.Name, &network.EndpointSettings{
Aliases: aliases, Aliases: aliases,
}) })
} }
@@ -85,7 +84,7 @@ func supportsContainerImagePlatform(ctx context.Context, cli client.APIClient) b
return constraint.Check(sv) return constraint.Check(sv)
} }
func (cr *containerReference) Create(capAdd []string, capDrop []string) common.Executor { func (cr *containerReference) Create(capAdd, capDrop []string) common.Executor {
return common. return common.
NewInfoExecutor("%sdocker create image=%s platform=%s entrypoint=%+q cmd=%+q network=%+q", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd, cr.input.NetworkMode). NewInfoExecutor("%sdocker create image=%s platform=%s entrypoint=%+q cmd=%+q network=%+q", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd, cr.input.NetworkMode).
Then( Then(
@@ -142,7 +141,7 @@ func (cr *containerReference) Copy(destPath string, files ...*FileEntry) common.
).IfNot(common.Dryrun) ).IfNot(common.Dryrun)
} }
func (cr *containerReference) CopyDir(destPath string, srcPath string, useGitIgnore bool) common.Executor { func (cr *containerReference) CopyDir(destPath, srcPath string, useGitIgnore bool) common.Executor {
return common.NewPipelineExecutor( return common.NewPipelineExecutor(
common.NewInfoExecutor("%sdocker cp src=%s dst=%s", logPrefix, srcPath, destPath), common.NewInfoExecutor("%sdocker cp src=%s dst=%s", logPrefix, srcPath, destPath),
cr.copyDir(destPath, srcPath, useGitIgnore), cr.copyDir(destPath, srcPath, useGitIgnore),
@@ -158,7 +157,7 @@ func (cr *containerReference) CopyDir(destPath string, srcPath string, useGitIgn
func (cr *containerReference) GetContainerArchive(ctx context.Context, srcPath string) (io.ReadCloser, error) { func (cr *containerReference) GetContainerArchive(ctx context.Context, srcPath string) (io.ReadCloser, error) {
if common.Dryrun(ctx) { if common.Dryrun(ctx) {
return nil, fmt.Errorf("DRYRUN is not supported in GetContainerArchive") return nil, errors.New("DRYRUN is not supported in GetContainerArchive")
} }
a, _, err := cr.cli.CopyFromContainer(ctx, cr.id, srcPath) a, _, err := cr.cli.CopyFromContainer(ctx, cr.id, srcPath)
return a, err return a, err
@@ -190,7 +189,7 @@ func (cr *containerReference) Remove() common.Executor {
).IfNot(common.Dryrun) ).IfNot(common.Dryrun)
} }
func (cr *containerReference) ReplaceLogWriter(stdout io.Writer, stderr io.Writer) (io.Writer, io.Writer) { func (cr *containerReference) ReplaceLogWriter(stdout, stderr io.Writer) (io.Writer, io.Writer) {
out := cr.input.Stdout out := cr.input.Stdout
err := cr.input.Stderr err := cr.input.Stderr
@@ -429,7 +428,7 @@ func (cr *containerReference) mergeContainerConfigs(ctx context.Context, config
return config, hostConfig, nil return config, hostConfig, nil
} }
func (cr *containerReference) create(capAdd []string, capDrop []string) common.Executor { func (cr *containerReference) create(capAdd, capDrop []string) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
if cr.id != "" { if cr.id != "" {
return nil return nil
@@ -682,7 +681,7 @@ func (cr *containerReference) tryReadGID() common.Executor {
return cr.tryReadID("-g", func(id int) { cr.GID = id }) return cr.tryReadID("-g", func(id int) { cr.GID = id })
} }
func (cr *containerReference) waitForCommand(ctx context.Context, isTerminal bool, resp types.HijackedResponse, _ types.IDResponse, _ string, _ string) error { func (cr *containerReference) waitForCommand(ctx context.Context, isTerminal bool, resp types.HijackedResponse, _ types.IDResponse, _, _ string) error {
logger := common.Logger(ctx) logger := common.Logger(ctx)
cmdResponse := make(chan error) cmdResponse := make(chan error)
@@ -753,7 +752,7 @@ func (cr *containerReference) CopyTarStream(ctx context.Context, destPath string
return nil return nil
} }
func (cr *containerReference) copyDir(dstPath string, srcPath string, useGitIgnore bool) common.Executor { func (cr *containerReference) copyDir(dstPath, srcPath string, useGitIgnore bool) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
logger := common.Logger(ctx) logger := common.Logger(ctx)
tarFile, err := os.CreateTemp("", "act") tarFile, err := os.CreateTemp("", "act")

View File

@@ -4,7 +4,7 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"context" "context"
"fmt" "errors"
"io" "io"
"net" "net"
"strings" "strings"
@@ -81,7 +81,7 @@ func (m *mockDockerClient) ContainerExecInspect(ctx context.Context, execID stri
return args.Get(0).(types.ContainerExecInspect), args.Error(1) return args.Get(0).(types.ContainerExecInspect), args.Error(1)
} }
func (m *mockDockerClient) CopyToContainer(ctx context.Context, id string, path string, content io.Reader, options types.CopyToContainerOptions) error { func (m *mockDockerClient) CopyToContainer(ctx context.Context, id, path string, content io.Reader, options types.CopyToContainerOptions) error {
args := m.Called(ctx, id, path, content, options) args := m.Called(ctx, id, path, content, options)
return args.Error(0) return args.Error(0)
} }
@@ -203,7 +203,7 @@ func TestDockerCopyTarStreamErrorInCopyFiles(t *testing.T) {
conn := &mockConn{} conn := &mockConn{}
merr := fmt.Errorf("Failure") merr := errors.New("Failure")
client := &mockDockerClient{} client := &mockDockerClient{}
client.On("CopyToContainer", ctx, "123", "/", mock.Anything, mock.AnythingOfType("types.CopyToContainerOptions")).Return(merr) client.On("CopyToContainer", ctx, "123", "/", mock.Anything, mock.AnythingOfType("types.CopyToContainerOptions")).Return(merr)
@@ -228,7 +228,7 @@ func TestDockerCopyTarStreamErrorInMkdir(t *testing.T) {
conn := &mockConn{} conn := &mockConn{}
merr := fmt.Errorf("Failure") merr := errors.New("Failure")
client := &mockDockerClient{} client := &mockDockerClient{}
client.On("CopyToContainer", ctx, "123", "/", mock.Anything, mock.AnythingOfType("types.CopyToContainerOptions")).Return(nil) client.On("CopyToContainer", ctx, "123", "/", mock.Anything, mock.AnythingOfType("types.CopyToContainerOptions")).Return(nil)

View File

@@ -43,8 +43,8 @@ func socketLocation() (string, bool) {
// indicating that the `daemonPath` is a Docker host URI. If it doesn't, or if the "://" delimiter // indicating that the `daemonPath` is a Docker host URI. If it doesn't, or if the "://" delimiter
// is not found in the `daemonPath`, the function returns false. // is not found in the `daemonPath`, the function returns false.
func isDockerHostURI(daemonPath string) bool { func isDockerHostURI(daemonPath string) bool {
if protoIndex := strings.Index(daemonPath, "://"); protoIndex != -1 { if before, _, ok := strings.Cut(daemonPath, "://"); ok {
scheme := daemonPath[:protoIndex] scheme := before
if strings.IndexFunc(scheme, func(r rune) bool { if strings.IndexFunc(scheme, func(r rune) bool {
return (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') return (r < 'a' || r > 'z') && (r < 'A' || r > 'Z')
}) == -1 { }) == -1 {

View File

@@ -19,7 +19,7 @@ func TestGetSocketAndHostWithSocket(t *testing.T) {
CommonSocketLocations = originalCommonSocketLocations CommonSocketLocations = originalCommonSocketLocations
dockerHost := "unix:///my/docker/host.sock" dockerHost := "unix:///my/docker/host.sock"
socketURI := "/path/to/my.socket" socketURI := "/path/to/my.socket"
os.Setenv("DOCKER_HOST", dockerHost) t.Setenv("DOCKER_HOST", dockerHost)
// Act // Act
ret, err := GetSocketAndHost(socketURI) ret, err := GetSocketAndHost(socketURI)
@@ -32,7 +32,7 @@ func TestGetSocketAndHostWithSocket(t *testing.T) {
func TestGetSocketAndHostNoSocket(t *testing.T) { func TestGetSocketAndHostNoSocket(t *testing.T) {
// Arrange // Arrange
dockerHost := "unix:///my/docker/host.sock" dockerHost := "unix:///my/docker/host.sock"
os.Setenv("DOCKER_HOST", dockerHost) t.Setenv("DOCKER_HOST", dockerHost)
// Act // Act
ret, err := GetSocketAndHost("") ret, err := GetSocketAndHost("")
@@ -63,7 +63,7 @@ func TestGetSocketAndHostDontMount(t *testing.T) {
// Arrange // Arrange
CommonSocketLocations = originalCommonSocketLocations CommonSocketLocations = originalCommonSocketLocations
dockerHost := "unix:///my/docker/host.sock" dockerHost := "unix:///my/docker/host.sock"
os.Setenv("DOCKER_HOST", dockerHost) t.Setenv("DOCKER_HOST", dockerHost)
// Act // Act
ret, err := GetSocketAndHost("-") ret, err := GetSocketAndHost("-")
@@ -93,7 +93,7 @@ func TestGetSocketAndHostNoHostNoSocket(t *testing.T) {
// > This happens if neither DOCKER_HOST nor --container-daemon-socket has a value, but socketLocation() returns a URI // > This happens if neither DOCKER_HOST nor --container-daemon-socket has a value, but socketLocation() returns a URI
func TestGetSocketAndHostNoHostNoSocketDefaultLocation(t *testing.T) { func TestGetSocketAndHostNoHostNoSocketDefaultLocation(t *testing.T) {
// Arrange // Arrange
mySocketFile, tmpErr := os.CreateTemp("", "act-*.sock") mySocketFile, tmpErr := os.CreateTemp(t.TempDir(), "act-*.sock")
mySocket := mySocketFile.Name() mySocket := mySocketFile.Name()
unixSocket := "unix://" + mySocket unixSocket := "unix://" + mySocket
defer os.RemoveAll(mySocket) defer os.RemoveAll(mySocket)

View File

@@ -6,20 +6,21 @@ import (
"context" "context"
"runtime" "runtime"
"github.com/docker/docker/api/types"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/docker/docker/api/types"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// ImageExistsLocally returns a boolean indicating if an image with the // ImageExistsLocally returns a boolean indicating if an image with the
// requested name, tag and architecture exists in the local docker image store // requested name, tag and architecture exists in the local docker image store
func ImageExistsLocally(ctx context.Context, imageName string, platform string) (bool, error) { func ImageExistsLocally(ctx context.Context, imageName, platform string) (bool, error) {
return false, errors.New("Unsupported Operation") return false, errors.New("Unsupported Operation")
} }
// RemoveImage removes image from local store, the function is used to run different // RemoveImage removes image from local store, the function is used to run different
// container image architectures // container image architectures
func RemoveImage(ctx context.Context, imageName string, force bool, pruneChildren bool) (bool, error) { func RemoveImage(ctx context.Context, imageName string, force, pruneChildren bool) (bool, error) {
return false, errors.New("Unsupported Operation") return false, errors.New("Unsupported Operation")
} }

View File

@@ -5,9 +5,10 @@ package container
import ( import (
"context" "context"
"github.com/nektos/act/pkg/common"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/volume" "github.com/docker/docker/api/types/volume"
"github.com/nektos/act/pkg/common"
) )
func NewDockerVolumeRemoveExecutor(volumeName string, force bool) common.Executor { func NewDockerVolumeRemoveExecutor(volumeName string, force bool) common.Executor {

View File

@@ -9,7 +9,7 @@ type ExecutionsEnvironment interface {
GetPathVariableName() string GetPathVariableName() string
DefaultPathVariable() string DefaultPathVariable() string
JoinPathVariable(...string) string JoinPathVariable(...string) string
GetRunnerContext(ctx context.Context) map[string]interface{} GetRunnerContext(ctx context.Context) map[string]any
// On windows PATH and Path are the same key // On windows PATH and Path are the same key
IsEnvironmentCaseInsensitive() bool IsEnvironmentCaseInsensitive() bool
} }

View File

@@ -15,14 +15,14 @@ import (
"strings" "strings"
"time" "time"
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/filecollector"
"github.com/nektos/act/pkg/lookpath"
"github.com/go-git/go-billy/v5/helper/polyfill" "github.com/go-git/go-billy/v5/helper/polyfill"
"github.com/go-git/go-billy/v5/osfs" "github.com/go-git/go-billy/v5/osfs"
"github.com/go-git/go-git/v5/plumbing/format/gitignore" "github.com/go-git/go-git/v5/plumbing/format/gitignore"
"golang.org/x/term" "golang.org/x/term"
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/filecollector"
"github.com/nektos/act/pkg/lookpath"
) )
type HostEnvironment struct { type HostEnvironment struct {
@@ -35,7 +35,7 @@ type HostEnvironment struct {
StdOut io.Writer StdOut io.Writer
} }
func (e *HostEnvironment) Create(_ []string, _ []string) common.Executor { func (e *HostEnvironment) Create(_, _ []string) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
return nil return nil
} }
@@ -86,7 +86,7 @@ func (e *HostEnvironment) CopyTarStream(ctx context.Context, destPath string, ta
continue continue
} }
if ctx.Err() != nil { if ctx.Err() != nil {
return fmt.Errorf("CopyTarStream has been cancelled") return errors.New("CopyTarStream has been cancelled")
} }
if err := cp.WriteFile(ti.Name, ti.FileInfo(), ti.Linkname, tr); err != nil { if err := cp.WriteFile(ti.Name, ti.FileInfo(), ti.Linkname, tr); err != nil {
return err return err
@@ -94,7 +94,7 @@ func (e *HostEnvironment) CopyTarStream(ctx context.Context, destPath string, ta
} }
} }
func (e *HostEnvironment) CopyDir(destPath string, srcPath string, useGitIgnore bool) common.Executor { func (e *HostEnvironment) CopyDir(destPath, srcPath string, useGitIgnore bool) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
logger := common.Logger(ctx) logger := common.Logger(ctx)
srcPrefix := filepath.Dir(srcPath) srcPrefix := filepath.Dir(srcPath)
@@ -227,7 +227,7 @@ func (l *localEnv) Getenv(name string) string {
func lookupPathHost(cmd string, env map[string]string, writer io.Writer) (string, error) { func lookupPathHost(cmd string, env map[string]string, writer io.Writer) (string, error) {
f, err := lookpath.LookPath2(cmd, &localEnv{env: env}) f, err := lookpath.LookPath2(cmd, &localEnv{env: env})
if err != nil { if err != nil {
err := "Cannot find: " + fmt.Sprint(cmd) + " in PATH" err := "Cannot find: " + cmd + " in PATH"
if _, _err := writer.Write([]byte(err + "\n")); _err != nil { if _, _err := writer.Write([]byte(err + "\n")); _err != nil {
return "", fmt.Errorf("%v: %w", err, _err) return "", fmt.Errorf("%v: %w", err, _err)
} }
@@ -346,7 +346,7 @@ func (e *HostEnvironment) exec(ctx context.Context, command []string, cmdline st
} }
if tty != nil { if tty != nil {
writer.AutoStop = true writer.AutoStop = true
if _, err := tty.Write([]byte("\x04")); err != nil { if _, err := tty.WriteString("\x04"); err != nil {
common.Logger(ctx).Debug("Failed to write EOT") common.Logger(ctx).Debug("Failed to write EOT")
} }
} }
@@ -408,9 +408,10 @@ func (e *HostEnvironment) GetActPath() string {
} }
func (*HostEnvironment) GetPathVariableName() string { func (*HostEnvironment) GetPathVariableName() string {
if runtime.GOOS == "plan9" { switch runtime.GOOS {
case "plan9":
return "path" return "path"
} else if runtime.GOOS == "windows" { case "windows":
return "Path" // Actually we need a case insensitive map return "Path" // Actually we need a case insensitive map
} }
return "PATH" return "PATH"
@@ -449,8 +450,8 @@ func goOsToActionOs(os string) string {
return os return os
} }
func (e *HostEnvironment) GetRunnerContext(_ context.Context) map[string]interface{} { func (e *HostEnvironment) GetRunnerContext(_ context.Context) map[string]any {
return map[string]interface{}{ return map[string]any{
"os": goOsToActionOs(runtime.GOOS), "os": goOsToActionOs(runtime.GOOS),
"arch": goArchToActionArch(runtime.GOARCH), "arch": goArchToActionArch(runtime.GOARCH),
"temp": e.TmpDir, "temp": e.TmpDir,
@@ -458,7 +459,7 @@ func (e *HostEnvironment) GetRunnerContext(_ context.Context) map[string]interfa
} }
} }
func (e *HostEnvironment) ReplaceLogWriter(stdout io.Writer, _ io.Writer) (io.Writer, io.Writer) { func (e *HostEnvironment) ReplaceLogWriter(stdout, _ io.Writer) (io.Writer, io.Writer) {
org := e.StdOut org := e.StdOut
e.StdOut = stdout e.StdOut = stdout
return org, org return org, org

View File

@@ -16,9 +16,7 @@ import (
var _ ExecutionsEnvironment = &HostEnvironment{} var _ ExecutionsEnvironment = &HostEnvironment{}
func TestCopyDir(t *testing.T) { func TestCopyDir(t *testing.T) {
dir, err := os.MkdirTemp("", "test-host-env-*") dir := t.TempDir()
assert.NoError(t, err)
defer os.RemoveAll(dir)
ctx := context.Background() ctx := context.Background()
e := &HostEnvironment{ e := &HostEnvironment{
Path: filepath.Join(dir, "path"), Path: filepath.Join(dir, "path"),
@@ -28,18 +26,16 @@ func TestCopyDir(t *testing.T) {
StdOut: os.Stdout, StdOut: os.Stdout,
Workdir: path.Join("testdata", "scratch"), Workdir: path.Join("testdata", "scratch"),
} }
_ = os.MkdirAll(e.Path, 0700) _ = os.MkdirAll(e.Path, 0o700)
_ = os.MkdirAll(e.TmpDir, 0700) _ = os.MkdirAll(e.TmpDir, 0o700)
_ = os.MkdirAll(e.ToolCache, 0700) _ = os.MkdirAll(e.ToolCache, 0o700)
_ = os.MkdirAll(e.ActPath, 0700) _ = os.MkdirAll(e.ActPath, 0o700)
err = e.CopyDir(e.Workdir, e.Path, true)(ctx) err := e.CopyDir(e.Workdir, e.Path, true)(ctx)
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestGetContainerArchive(t *testing.T) { func TestGetContainerArchive(t *testing.T) {
dir, err := os.MkdirTemp("", "test-host-env-*") dir := t.TempDir()
assert.NoError(t, err)
defer os.RemoveAll(dir)
ctx := context.Background() ctx := context.Background()
e := &HostEnvironment{ e := &HostEnvironment{
Path: filepath.Join(dir, "path"), Path: filepath.Join(dir, "path"),
@@ -49,12 +45,12 @@ func TestGetContainerArchive(t *testing.T) {
StdOut: os.Stdout, StdOut: os.Stdout,
Workdir: path.Join("testdata", "scratch"), Workdir: path.Join("testdata", "scratch"),
} }
_ = os.MkdirAll(e.Path, 0700) _ = os.MkdirAll(e.Path, 0o700)
_ = os.MkdirAll(e.TmpDir, 0700) _ = os.MkdirAll(e.TmpDir, 0o700)
_ = os.MkdirAll(e.ToolCache, 0700) _ = os.MkdirAll(e.ToolCache, 0o700)
_ = os.MkdirAll(e.ActPath, 0700) _ = os.MkdirAll(e.ActPath, 0o700)
expectedContent := []byte("sdde/7sh") expectedContent := []byte("sdde/7sh")
err = os.WriteFile(filepath.Join(e.Path, "action.yml"), expectedContent, 0600) err := os.WriteFile(filepath.Join(e.Path, "action.yml"), expectedContent, 0o600)
assert.NoError(t, err) assert.NoError(t, err)
archive, err := e.GetContainerArchive(ctx, e.Path) archive, err := e.GetContainerArchive(ctx, e.Path)
assert.NoError(t, err) assert.NoError(t, err)

View File

@@ -10,8 +10,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
type LinuxContainerEnvironmentExtensions struct { type LinuxContainerEnvironmentExtensions struct{}
}
// Resolves the equivalent host path inside the container // Resolves the equivalent host path inside the container
// This is required for windows and WSL 2 to translate things like C:\Users\Myproject to /mnt/users/Myproject // This is required for windows and WSL 2 to translate things like C:\Users\Myproject to /mnt/users/Myproject
@@ -63,8 +62,8 @@ func (*LinuxContainerEnvironmentExtensions) JoinPathVariable(paths ...string) st
return strings.Join(paths, ":") return strings.Join(paths, ":")
} }
func (*LinuxContainerEnvironmentExtensions) GetRunnerContext(ctx context.Context) map[string]interface{} { func (*LinuxContainerEnvironmentExtensions) GetRunnerContext(ctx context.Context) map[string]any {
return map[string]interface{}{ return map[string]any{
"os": "Linux", "os": "Linux",
"arch": RunnerArch(ctx), "arch": RunnerArch(ctx),
"temp": "/tmp", "temp": "/tmp",

View File

@@ -31,22 +31,17 @@ func TestContainerPath(t *testing.T) {
for _, v := range []containerPathJob{ for _, v := range []containerPathJob{
{"/mnt/c/Users/act/go/src/github.com/nektos/act", "C:\\Users\\act\\go\\src\\github.com\\nektos\\act\\", ""}, {"/mnt/c/Users/act/go/src/github.com/nektos/act", "C:\\Users\\act\\go\\src\\github.com\\nektos\\act\\", ""},
{"/mnt/f/work/dir", `F:\work\dir`, ""}, {"/mnt/f/work/dir", `F:\work\dir`, ""},
{"/mnt/c/windows/to/unix", "windows\\to\\unix", fmt.Sprintf("%s\\", rootDrive)}, {"/mnt/c/windows/to/unix", "windows\\to\\unix", rootDrive + "\\"},
{fmt.Sprintf("/mnt/%v/act", rootDriveLetter), "act", fmt.Sprintf("%s\\", rootDrive)}, {fmt.Sprintf("/mnt/%v/act", rootDriveLetter), "act", rootDrive + "\\"},
} { } {
if v.workDir != "" { if v.workDir != "" {
if err := os.Chdir(v.workDir); err != nil { t.Chdir(v.workDir)
log.Error(err)
t.Fail()
}
} }
assert.Equal(t, v.destinationPath, linuxcontainerext.ToContainerPath(v.sourcePath)) assert.Equal(t, v.destinationPath, linuxcontainerext.ToContainerPath(v.sourcePath))
} }
if err := os.Chdir(cwd); err != nil { t.Chdir(cwd)
log.Error(err)
}
} else { } else {
cwd, err := os.Getwd() cwd, err := os.Getwd()
if err != nil { if err != nil {

View File

@@ -4,6 +4,7 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
@@ -13,9 +14,9 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/go-git/go-git/v5/plumbing/format/gitignore"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/go-git/go-git/v5/plumbing/format/gitignore"
"github.com/rhysd/actionlint" "github.com/rhysd/actionlint"
) )
@@ -66,7 +67,7 @@ const (
func (impl *interperterImpl) format(str reflect.Value, replaceValue ...reflect.Value) (string, error) { func (impl *interperterImpl) format(str reflect.Value, replaceValue ...reflect.Value) (string, error) {
input := impl.coerceToString(str).String() input := impl.coerceToString(str).String()
output := "" var output strings.Builder
replacementIndex := "" replacementIndex := ""
state := passThrough state := passThrough
@@ -81,13 +82,13 @@ func (impl *interperterImpl) format(str reflect.Value, replaceValue ...reflect.V
state = bracketClose state = bracketClose
default: default:
output += string(character) output.WriteRune(character)
} }
case bracketOpen: // found { case bracketOpen: // found {
switch character { switch character {
case '{': case '{':
output += "{" output.WriteString("{")
replacementIndex = "" replacementIndex = ""
state = passThrough state = passThrough
@@ -103,7 +104,7 @@ func (impl *interperterImpl) format(str reflect.Value, replaceValue ...reflect.V
return "", fmt.Errorf("The following format string references more arguments than were supplied: '%s'", input) return "", fmt.Errorf("The following format string references more arguments than were supplied: '%s'", input)
} }
output += impl.coerceToString(replaceValue[index]).String() output.WriteString(impl.coerceToString(replaceValue[index]).String())
state = passThrough state = passThrough
@@ -114,7 +115,7 @@ func (impl *interperterImpl) format(str reflect.Value, replaceValue ...reflect.V
case bracketClose: // found } case bracketClose: // found }
switch character { switch character {
case '}': case '}':
output += "}" output.WriteString("}")
replacementIndex = "" replacementIndex = ""
state = passThrough state = passThrough
@@ -134,10 +135,10 @@ func (impl *interperterImpl) format(str reflect.Value, replaceValue ...reflect.V
} }
} }
return output, nil return output.String(), nil
} }
func (impl *interperterImpl) join(array reflect.Value, sep reflect.Value) (string, error) { func (impl *interperterImpl) join(array, sep reflect.Value) (string, error) {
separator := impl.coerceToString(sep).String() separator := impl.coerceToString(sep).String()
switch array.Kind() { switch array.Kind() {
case reflect.Slice: case reflect.Slice:
@@ -165,12 +166,12 @@ func (impl *interperterImpl) toJSON(value reflect.Value) (string, error) {
return string(json), nil return string(json), nil
} }
func (impl *interperterImpl) fromJSON(value reflect.Value) (interface{}, error) { func (impl *interperterImpl) fromJSON(value reflect.Value) (any, error) {
if value.Kind() != reflect.String { if value.Kind() != reflect.String {
return nil, fmt.Errorf("Cannot parse non-string type %v as JSON", value.Kind()) return nil, fmt.Errorf("Cannot parse non-string type %v as JSON", value.Kind())
} }
var data interface{} var data any
err := json.Unmarshal([]byte(value.String()), &data) err := json.Unmarshal([]byte(value.String()), &data)
if err != nil { if err != nil {
@@ -195,7 +196,7 @@ func (impl *interperterImpl) hashFiles(paths ...reflect.Value) (string, error) {
} }
ps = append(ps, gitignore.ParsePattern(cleanPath, nil)) ps = append(ps, gitignore.ParsePattern(cleanPath, nil))
} else { } else {
return "", fmt.Errorf("Non-string path passed to hashFiles") return "", errors.New("Non-string path passed to hashFiles")
} }
} }

View File

@@ -5,13 +5,14 @@ import (
"testing" "testing"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestFunctionContains(t *testing.T) { func TestFunctionContains(t *testing.T) {
table := []struct { table := []struct {
input string input string
expected interface{} expected any
name string name string
}{ }{
{"contains('search', 'item') }}", false, "contains-str-str"}, {"contains('search', 'item') }}", false, "contains-str-str"},
@@ -48,7 +49,7 @@ func TestFunctionContains(t *testing.T) {
func TestFunctionStartsWith(t *testing.T) { func TestFunctionStartsWith(t *testing.T) {
table := []struct { table := []struct {
input string input string
expected interface{} expected any
name string name string
}{ }{
{"startsWith('search', 'se') }}", true, "startswith-string"}, {"startsWith('search', 'se') }}", true, "startswith-string"},
@@ -77,7 +78,7 @@ func TestFunctionStartsWith(t *testing.T) {
func TestFunctionEndsWith(t *testing.T) { func TestFunctionEndsWith(t *testing.T) {
table := []struct { table := []struct {
input string input string
expected interface{} expected any
name string name string
}{ }{
{"endsWith('search', 'ch') }}", true, "endsWith-string"}, {"endsWith('search', 'ch') }}", true, "endsWith-string"},
@@ -106,7 +107,7 @@ func TestFunctionEndsWith(t *testing.T) {
func TestFunctionJoin(t *testing.T) { func TestFunctionJoin(t *testing.T) {
table := []struct { table := []struct {
input string input string
expected interface{} expected any
name string name string
}{ }{
{"join(fromJSON('[\"a\", \"b\"]'), ',')", "a,b", "join-arr"}, {"join(fromJSON('[\"a\", \"b\"]'), ',')", "a,b", "join-arr"},
@@ -133,7 +134,7 @@ func TestFunctionJoin(t *testing.T) {
func TestFunctionToJSON(t *testing.T) { func TestFunctionToJSON(t *testing.T) {
table := []struct { table := []struct {
input string input string
expected interface{} expected any
name string name string
}{ }{
{"toJSON(env) }}", "{\n \"key\": \"value\"\n}", "toJSON"}, {"toJSON(env) }}", "{\n \"key\": \"value\"\n}", "toJSON"},
@@ -159,10 +160,10 @@ func TestFunctionToJSON(t *testing.T) {
func TestFunctionFromJSON(t *testing.T) { func TestFunctionFromJSON(t *testing.T) {
table := []struct { table := []struct {
input string input string
expected interface{} expected any
name string name string
}{ }{
{"fromJSON('{\"foo\":\"bar\"}') }}", map[string]interface{}{ {"fromJSON('{\"foo\":\"bar\"}') }}", map[string]any{
"foo": "bar", "foo": "bar",
}, "fromJSON"}, }, "fromJSON"},
} }
@@ -182,7 +183,7 @@ func TestFunctionFromJSON(t *testing.T) {
func TestFunctionHashFiles(t *testing.T) { func TestFunctionHashFiles(t *testing.T) {
table := []struct { table := []struct {
input string input string
expected interface{} expected any
name string name string
}{ }{
{"hashFiles('**/non-extant-files') }}", "", "hash-non-existing-file"}, {"hashFiles('**/non-extant-files') }}", "", "hash-non-existing-file"},
@@ -212,8 +213,8 @@ func TestFunctionHashFiles(t *testing.T) {
func TestFunctionFormat(t *testing.T) { func TestFunctionFormat(t *testing.T) {
table := []struct { table := []struct {
input string input string
expected interface{} expected any
error interface{} error any
name string name string
}{ }{
{"format('text')", "text", nil, "format-plain-string"}, {"format('text')", "text", nil, "format-plain-string"},

View File

@@ -2,12 +2,14 @@ package exprparser
import ( import (
"encoding" "encoding"
"errors"
"fmt" "fmt"
"math" "math"
"reflect" "reflect"
"strings" "strings"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/rhysd/actionlint" "github.com/rhysd/actionlint"
) )
@@ -17,14 +19,14 @@ type EvaluationEnvironment struct {
Job *model.JobContext Job *model.JobContext
Jobs *map[string]*model.WorkflowCallResult Jobs *map[string]*model.WorkflowCallResult
Steps map[string]*model.StepResult Steps map[string]*model.StepResult
Runner map[string]interface{} Runner map[string]any
Secrets map[string]string Secrets map[string]string
Vars map[string]string Vars map[string]string
Strategy map[string]interface{} Strategy map[string]any
Matrix map[string]interface{} Matrix map[string]any
Needs map[string]Needs Needs map[string]Needs
Inputs map[string]interface{} Inputs map[string]any
HashFiles func([]reflect.Value) (interface{}, error) HashFiles func([]reflect.Value) (any, error)
} }
type Needs struct { type Needs struct {
@@ -63,7 +65,7 @@ func (dsc DefaultStatusCheck) String() string {
} }
type Interpreter interface { type Interpreter interface {
Evaluate(input string, defaultStatusCheck DefaultStatusCheck) (interface{}, error) Evaluate(input string, defaultStatusCheck DefaultStatusCheck) (any, error)
} }
type interperterImpl struct { type interperterImpl struct {
@@ -78,7 +80,7 @@ func NewInterpeter(env *EvaluationEnvironment, config Config) Interpreter {
} }
} }
func (impl *interperterImpl) Evaluate(input string, defaultStatusCheck DefaultStatusCheck) (interface{}, error) { func (impl *interperterImpl) Evaluate(input string, defaultStatusCheck DefaultStatusCheck) (any, error) {
input = strings.TrimPrefix(input, "${{") input = strings.TrimPrefix(input, "${{")
if defaultStatusCheck != DefaultStatusCheckNone && input == "" { if defaultStatusCheck != DefaultStatusCheckNone && input == "" {
input = "success()" input = "success()"
@@ -117,7 +119,7 @@ func (impl *interperterImpl) Evaluate(input string, defaultStatusCheck DefaultSt
return result, err2 return result, err2
} }
func (impl *interperterImpl) evaluateNode(exprNode actionlint.ExprNode) (interface{}, error) { func (impl *interperterImpl) evaluateNode(exprNode actionlint.ExprNode) (any, error) {
switch node := exprNode.(type) { switch node := exprNode.(type) {
case *actionlint.VariableNode: case *actionlint.VariableNode:
return impl.evaluateVariable(node) return impl.evaluateVariable(node)
@@ -150,8 +152,8 @@ func (impl *interperterImpl) evaluateNode(exprNode actionlint.ExprNode) (interfa
} }
} }
//nolint:gocyclo //nolint:gocyclo // function handles many cases
func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableNode) (interface{}, error) { func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableNode) (any, error) {
switch strings.ToLower(variableNode.Name) { switch strings.ToLower(variableNode.Name) {
case "github": case "github":
return impl.env.Github, nil return impl.env.Github, nil
@@ -163,7 +165,7 @@ func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableN
return impl.env.Job, nil return impl.env.Job, nil
case "jobs": case "jobs":
if impl.env.Jobs == nil { if impl.env.Jobs == nil {
return nil, fmt.Errorf("Unavailable context: jobs") return nil, errors.New("Unavailable context: jobs")
} }
return impl.env.Jobs, nil return impl.env.Jobs, nil
case "steps": case "steps":
@@ -191,7 +193,7 @@ func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableN
} }
} }
func (impl *interperterImpl) evaluateIndexAccess(indexAccessNode *actionlint.IndexAccessNode) (interface{}, error) { func (impl *interperterImpl) evaluateIndexAccess(indexAccessNode *actionlint.IndexAccessNode) (any, error) {
left, err := impl.evaluateNode(indexAccessNode.Operand) left, err := impl.evaluateNode(indexAccessNode.Operand)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -226,7 +228,7 @@ func (impl *interperterImpl) evaluateIndexAccess(indexAccessNode *actionlint.Ind
} }
} }
func (impl *interperterImpl) evaluateObjectDeref(objectDerefNode *actionlint.ObjectDerefNode) (interface{}, error) { func (impl *interperterImpl) evaluateObjectDeref(objectDerefNode *actionlint.ObjectDerefNode) (any, error) {
left, err := impl.evaluateNode(objectDerefNode.Receiver) left, err := impl.evaluateNode(objectDerefNode.Receiver)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -235,7 +237,7 @@ func (impl *interperterImpl) evaluateObjectDeref(objectDerefNode *actionlint.Obj
return impl.getPropertyValue(reflect.ValueOf(left), objectDerefNode.Property) return impl.getPropertyValue(reflect.ValueOf(left), objectDerefNode.Property)
} }
func (impl *interperterImpl) evaluateArrayDeref(arrayDerefNode *actionlint.ArrayDerefNode) (interface{}, error) { func (impl *interperterImpl) evaluateArrayDeref(arrayDerefNode *actionlint.ArrayDerefNode) (any, error) {
left, err := impl.evaluateNode(arrayDerefNode.Receiver) left, err := impl.evaluateNode(arrayDerefNode.Receiver)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -244,17 +246,17 @@ func (impl *interperterImpl) evaluateArrayDeref(arrayDerefNode *actionlint.Array
return impl.getSafeValue(reflect.ValueOf(left)), nil return impl.getSafeValue(reflect.ValueOf(left)), nil
} }
func (impl *interperterImpl) getPropertyValue(left reflect.Value, property string) (value interface{}, err error) { func (impl *interperterImpl) getPropertyValue(left reflect.Value, property string) (value any, err error) {
switch left.Kind() { switch left.Kind() {
case reflect.Ptr: case reflect.Ptr:
return impl.getPropertyValue(left.Elem(), property) return impl.getPropertyValue(left.Elem(), property)
case reflect.Struct: case reflect.Struct:
leftType := left.Type() leftType := left.Type()
for i := 0; i < leftType.NumField(); i++ { for field := range leftType.Fields() {
jsonName := leftType.Field(i).Tag.Get("json") jsonName := field.Tag.Get("json")
if jsonName == property { if jsonName == property {
property = leftType.Field(i).Name property = field.Name
break break
} }
} }
@@ -298,7 +300,7 @@ func (impl *interperterImpl) getPropertyValue(left reflect.Value, property strin
return nil, nil return nil, nil
case reflect.Slice: case reflect.Slice:
var values []interface{} var values []any
for i := 0; i < left.Len(); i++ { for i := 0; i < left.Len(); i++ {
value, err := impl.getPropertyValue(left.Index(i).Elem(), property) value, err := impl.getPropertyValue(left.Index(i).Elem(), property)
@@ -315,7 +317,7 @@ func (impl *interperterImpl) getPropertyValue(left reflect.Value, property strin
return nil, nil return nil, nil
} }
func (impl *interperterImpl) getMapValue(value reflect.Value) (interface{}, error) { func (impl *interperterImpl) getMapValue(value reflect.Value) (any, error) {
if value.Kind() == reflect.Ptr { if value.Kind() == reflect.Ptr {
return impl.getMapValue(value.Elem()) return impl.getMapValue(value.Elem())
} }
@@ -323,7 +325,7 @@ func (impl *interperterImpl) getMapValue(value reflect.Value) (interface{}, erro
return value.Interface(), nil return value.Interface(), nil
} }
func (impl *interperterImpl) evaluateNot(notNode *actionlint.NotOpNode) (interface{}, error) { func (impl *interperterImpl) evaluateNot(notNode *actionlint.NotOpNode) (any, error) {
operand, err := impl.evaluateNode(notNode.Operand) operand, err := impl.evaluateNode(notNode.Operand)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -332,7 +334,7 @@ func (impl *interperterImpl) evaluateNot(notNode *actionlint.NotOpNode) (interfa
return !IsTruthy(operand), nil return !IsTruthy(operand), nil
} }
func (impl *interperterImpl) evaluateCompare(compareNode *actionlint.CompareOpNode) (interface{}, error) { func (impl *interperterImpl) evaluateCompare(compareNode *actionlint.CompareOpNode) (any, error) {
left, err := impl.evaluateNode(compareNode.Left) left, err := impl.evaluateNode(compareNode.Left)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -349,7 +351,7 @@ func (impl *interperterImpl) evaluateCompare(compareNode *actionlint.CompareOpNo
return impl.compareValues(leftValue, rightValue, compareNode.Kind) return impl.compareValues(leftValue, rightValue, compareNode.Kind)
} }
func (impl *interperterImpl) compareValues(leftValue reflect.Value, rightValue reflect.Value, kind actionlint.CompareOpNodeKind) (interface{}, error) { func (impl *interperterImpl) compareValues(leftValue, rightValue reflect.Value, kind actionlint.CompareOpNodeKind) (any, error) {
if leftValue.Kind() != rightValue.Kind() { if leftValue.Kind() != rightValue.Kind() {
if !impl.isNumber(leftValue) { if !impl.isNumber(leftValue) {
leftValue = impl.coerceToNumber(leftValue) leftValue = impl.coerceToNumber(leftValue)
@@ -461,7 +463,7 @@ func (impl *interperterImpl) coerceToString(value reflect.Value) reflect.Value {
return value return value
} }
func (impl *interperterImpl) compareString(left string, right string, kind actionlint.CompareOpNodeKind) (bool, error) { func (impl *interperterImpl) compareString(left, right string, kind actionlint.CompareOpNodeKind) (bool, error) {
switch kind { switch kind {
case actionlint.CompareOpNodeKindLess: case actionlint.CompareOpNodeKindLess:
return left < right, nil return left < right, nil
@@ -480,7 +482,7 @@ func (impl *interperterImpl) compareString(left string, right string, kind actio
} }
} }
func (impl *interperterImpl) compareNumber(left float64, right float64, kind actionlint.CompareOpNodeKind) (bool, error) { func (impl *interperterImpl) compareNumber(left, right float64, kind actionlint.CompareOpNodeKind) (bool, error) {
switch kind { switch kind {
case actionlint.CompareOpNodeKindLess: case actionlint.CompareOpNodeKindLess:
return left < right, nil return left < right, nil
@@ -499,7 +501,7 @@ func (impl *interperterImpl) compareNumber(left float64, right float64, kind act
} }
} }
func IsTruthy(input interface{}) bool { func IsTruthy(input any) bool {
value := reflect.ValueOf(input) value := reflect.ValueOf(input)
switch value.Kind() { switch value.Kind() {
case reflect.Bool: case reflect.Bool:
@@ -535,7 +537,7 @@ func (impl *interperterImpl) isNumber(value reflect.Value) bool {
} }
} }
func (impl *interperterImpl) getSafeValue(value reflect.Value) interface{} { func (impl *interperterImpl) getSafeValue(value reflect.Value) any {
switch value.Kind() { switch value.Kind() {
case reflect.Invalid: case reflect.Invalid:
return nil return nil
@@ -549,7 +551,7 @@ func (impl *interperterImpl) getSafeValue(value reflect.Value) interface{} {
return value.Interface() return value.Interface()
} }
func (impl *interperterImpl) evaluateLogicalCompare(compareNode *actionlint.LogicalOpNode) (interface{}, error) { func (impl *interperterImpl) evaluateLogicalCompare(compareNode *actionlint.LogicalOpNode) (any, error) {
left, err := impl.evaluateNode(compareNode.Left) left, err := impl.evaluateNode(compareNode.Left)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -578,8 +580,8 @@ func (impl *interperterImpl) evaluateLogicalCompare(compareNode *actionlint.Logi
return nil, fmt.Errorf("Unable to compare incompatibles types '%s' and '%s'", leftValue.Kind(), rightValue.Kind()) return nil, fmt.Errorf("Unable to compare incompatibles types '%s' and '%s'", leftValue.Kind(), rightValue.Kind())
} }
//nolint:gocyclo //nolint:gocyclo // function handles many cases
func (impl *interperterImpl) evaluateFuncCall(funcCallNode *actionlint.FuncCallNode) (interface{}, error) { func (impl *interperterImpl) evaluateFuncCall(funcCallNode *actionlint.FuncCallNode) (any, error) {
args := make([]reflect.Value, 0) args := make([]reflect.Value, 0)
for _, arg := range funcCallNode.Args { for _, arg := range funcCallNode.Args {

View File

@@ -5,13 +5,14 @@ import (
"testing" "testing"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestLiterals(t *testing.T) { func TestLiterals(t *testing.T) {
table := []struct { table := []struct {
input string input string
expected interface{} expected any
name string name string
}{ }{
{"true", true, "true"}, {"true", true, "true"},
@@ -40,7 +41,7 @@ func TestLiterals(t *testing.T) {
func TestOperators(t *testing.T) { func TestOperators(t *testing.T) {
table := []struct { table := []struct {
input string input string
expected interface{} expected any
name string name string
error string error string
}{ }{
@@ -68,7 +69,7 @@ func TestOperators(t *testing.T) {
{`true && false`, false, "and", ""}, {`true && false`, false, "and", ""},
{`true || false`, true, "or", ""}, {`true || false`, true, "or", ""},
{`fromJSON('{}') && true`, true, "and-boolean-object", ""}, {`fromJSON('{}') && true`, true, "and-boolean-object", ""},
{`fromJSON('{}') || false`, make(map[string]interface{}), "or-boolean-object", ""}, {`fromJSON('{}') || false`, make(map[string]any), "or-boolean-object", ""},
{"github.event.commits[0].author.username != github.event.commits[1].author.username", true, "property-comparison1", ""}, {"github.event.commits[0].author.username != github.event.commits[1].author.username", true, "property-comparison1", ""},
{"github.event.commits[0].author.username1 != github.event.commits[1].author.username", true, "property-comparison2", ""}, {"github.event.commits[0].author.username1 != github.event.commits[1].author.username", true, "property-comparison2", ""},
{"github.event.commits[0].author.username != github.event.commits[1].author.username1", true, "property-comparison3", ""}, {"github.event.commits[0].author.username != github.event.commits[1].author.username1", true, "property-comparison3", ""},
@@ -79,15 +80,15 @@ func TestOperators(t *testing.T) {
env := &EvaluationEnvironment{ env := &EvaluationEnvironment{
Github: &model.GithubContext{ Github: &model.GithubContext{
Action: "push", Action: "push",
Event: map[string]interface{}{ Event: map[string]any{
"commits": []interface{}{ "commits": []any{
map[string]interface{}{ map[string]any{
"author": map[string]interface{}{ "author": map[string]any{
"username": "someone", "username": "someone",
}, },
}, },
map[string]interface{}{ map[string]any{
"author": map[string]interface{}{ "author": map[string]any{
"username": "someone-else", "username": "someone-else",
}, },
}, },
@@ -114,7 +115,7 @@ func TestOperators(t *testing.T) {
func TestOperatorsCompare(t *testing.T) { func TestOperatorsCompare(t *testing.T) {
table := []struct { table := []struct {
input string input string
expected interface{} expected any
name string name string
}{ }{
{"!null", true, "not-null"}, {"!null", true, "not-null"},
@@ -162,7 +163,7 @@ func TestOperatorsCompare(t *testing.T) {
func TestOperatorsBooleanEvaluation(t *testing.T) { func TestOperatorsBooleanEvaluation(t *testing.T) {
table := []struct { table := []struct {
input string input string
expected interface{} expected any
name string name string
}{ }{
// true && // true &&
@@ -529,7 +530,7 @@ func TestOperatorsBooleanEvaluation(t *testing.T) {
func TestContexts(t *testing.T) { func TestContexts(t *testing.T) {
table := []struct { table := []struct {
input string input string
expected interface{} expected any
name string name string
}{ }{
{"github.action", "push", "github-context"}, {"github.action", "push", "github-context"},
@@ -586,7 +587,7 @@ func TestContexts(t *testing.T) {
Conclusion: model.StepStatusSkipped, Conclusion: model.StepStatusSkipped,
}, },
}, },
Runner: map[string]interface{}{ Runner: map[string]any{
"os": "Linux", "os": "Linux",
"temp": "/tmp", "temp": "/tmp",
"tool_cache": "/opt/hostedtoolcache", "tool_cache": "/opt/hostedtoolcache",
@@ -597,10 +598,10 @@ func TestContexts(t *testing.T) {
Vars: map[string]string{ Vars: map[string]string{
"name": "value", "name": "value",
}, },
Strategy: map[string]interface{}{ Strategy: map[string]any{
"fail-fast": true, "fail-fast": true,
}, },
Matrix: map[string]interface{}{ Matrix: map[string]any{
"os": "Linux", "os": "Linux",
}, },
Needs: map[string]Needs{ Needs: map[string]Needs{
@@ -611,7 +612,7 @@ func TestContexts(t *testing.T) {
Result: "success", Result: "success",
}, },
}, },
Inputs: map[string]interface{}{ Inputs: map[string]any{
"name": "value", "name": "value",
}, },
} }

View File

@@ -3,6 +3,7 @@ package filecollector
import ( import (
"archive/tar" "archive/tar"
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
@@ -97,8 +98,7 @@ type Fs interface {
Readlink(path string) (string, error) Readlink(path string) (string, error)
} }
type DefaultFs struct { type DefaultFs struct{}
}
func (*DefaultFs) Walk(root string, fn filepath.WalkFunc) error { func (*DefaultFs) Walk(root string, fn filepath.WalkFunc) error {
return filepath.Walk(root, fn) return filepath.Walk(root, fn)
@@ -124,7 +124,7 @@ func (*DefaultFs) Readlink(path string) (string, error) {
return os.Readlink(path) return os.Readlink(path)
} }
//nolint:gocyclo //nolint:gocyclo // function handles many cases
func (fc *FileCollector) CollectFiles(ctx context.Context, submodulePath []string) filepath.WalkFunc { func (fc *FileCollector) CollectFiles(ctx context.Context, submodulePath []string) filepath.WalkFunc {
i, _ := fc.Fs.OpenGitIndex(path.Join(fc.SrcPath, path.Join(submodulePath...))) i, _ := fc.Fs.OpenGitIndex(path.Join(fc.SrcPath, path.Join(submodulePath...)))
return func(file string, fi os.FileInfo, err error) error { return func(file string, fi os.FileInfo, err error) error {
@@ -134,7 +134,7 @@ func (fc *FileCollector) CollectFiles(ctx context.Context, submodulePath []strin
if ctx != nil { if ctx != nil {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return fmt.Errorf("copy cancelled") return errors.New("copy cancelled")
default: default:
} }
} }

View File

@@ -27,7 +27,7 @@ func (mfs *memoryFs) walk(root string, fn filepath.WalkFunc) error {
if err != nil { if err != nil {
return err return err
} }
for i := 0; i < len(dir); i++ { for i := range dir {
filename := filepath.Join(root, dir[i].Name()) filename := filepath.Join(root, dir[i].Name())
err = fn(filename, dir[i], nil) err = fn(filename, dir[i], nil)
if dir[i].IsDir() { if dir[i].IsDir() {

View File

@@ -6,8 +6,7 @@ type Env interface {
Getenv(name string) string Getenv(name string) string
} }
type defaultEnv struct { type defaultEnv struct{}
}
func (*defaultEnv) Getenv(name string) string { func (*defaultEnv) Getenv(name string) string {
return os.Getenv(name) return os.Getenv(name)

View File

@@ -20,7 +20,7 @@ func findExecutable(file string) error {
if err != nil { if err != nil {
return err return err
} }
if m := d.Mode(); !m.IsDir() && m&0111 != 0 { if m := d.Mode(); !m.IsDir() && m&0o111 != 0 {
return nil return nil
} }
return fs.ErrPermission return fs.ErrPermission

View File

@@ -22,7 +22,7 @@ func findExecutable(file string) error {
if err != nil { if err != nil {
return err return err
} }
if m := d.Mode(); !m.IsDir() && m&0111 != 0 { if m := d.Mode(); !m.IsDir() && m&0o111 != 0 {
return nil return nil
} }
return fs.ErrPermission return fs.ErrPermission

View File

@@ -11,7 +11,7 @@ import (
// ActionRunsUsing is the type of runner for the action // ActionRunsUsing is the type of runner for the action
type ActionRunsUsing string type ActionRunsUsing string
func (a *ActionRunsUsing) UnmarshalYAML(unmarshal func(interface{}) error) error { func (a *ActionRunsUsing) UnmarshalYAML(unmarshal func(any) error) error {
var using string var using string
if err := unmarshal(&using); err != nil { if err := unmarshal(&using); err != nil {
return err return err

View File

@@ -10,41 +10,41 @@ import (
) )
type GithubContext struct { type GithubContext struct {
Event map[string]interface{} `json:"event"` Event map[string]any `json:"event"`
EventPath string `json:"event_path"` EventPath string `json:"event_path"`
Workflow string `json:"workflow"` Workflow string `json:"workflow"`
RunID string `json:"run_id"` RunID string `json:"run_id"`
RunNumber string `json:"run_number"` RunNumber string `json:"run_number"`
Actor string `json:"actor"` Actor string `json:"actor"`
Repository string `json:"repository"` Repository string `json:"repository"`
EventName string `json:"event_name"` EventName string `json:"event_name"`
Sha string `json:"sha"` Sha string `json:"sha"`
Ref string `json:"ref"` Ref string `json:"ref"`
RefName string `json:"ref_name"` RefName string `json:"ref_name"`
RefType string `json:"ref_type"` RefType string `json:"ref_type"`
HeadRef string `json:"head_ref"` HeadRef string `json:"head_ref"`
BaseRef string `json:"base_ref"` BaseRef string `json:"base_ref"`
Token string `json:"token"` Token string `json:"token"`
Workspace string `json:"workspace"` Workspace string `json:"workspace"`
Action string `json:"action"` Action string `json:"action"`
ActionPath string `json:"action_path"` ActionPath string `json:"action_path"`
ActionRef string `json:"action_ref"` ActionRef string `json:"action_ref"`
ActionRepository string `json:"action_repository"` ActionRepository string `json:"action_repository"`
Job string `json:"job"` Job string `json:"job"`
JobName string `json:"job_name"` JobName string `json:"job_name"`
RepositoryOwner string `json:"repository_owner"` RepositoryOwner string `json:"repository_owner"`
RetentionDays string `json:"retention_days"` RetentionDays string `json:"retention_days"`
RunnerPerflog string `json:"runner_perflog"` RunnerPerflog string `json:"runner_perflog"`
RunnerTrackingID string `json:"runner_tracking_id"` RunnerTrackingID string `json:"runner_tracking_id"`
ServerURL string `json:"server_url"` ServerURL string `json:"server_url"`
APIURL string `json:"api_url"` APIURL string `json:"api_url"`
GraphQLURL string `json:"graphql_url"` GraphQLURL string `json:"graphql_url"`
// For Gitea // For Gitea
RunAttempt string `json:"run_attempt"` RunAttempt string `json:"run_attempt"`
} }
func asString(v interface{}) string { func asString(v any) string {
if v == nil { if v == nil {
return "" return ""
} else if s, ok := v.(string); ok { } else if s, ok := v.(string); ok {
@@ -53,7 +53,7 @@ func asString(v interface{}) string {
return "" return ""
} }
func nestedMapLookup(m map[string]interface{}, ks ...string) (rval interface{}) { func nestedMapLookup(m map[string]any, ks ...string) (rval any) {
var ok bool var ok bool
if len(ks) == 0 { // degenerate input if len(ks) == 0 { // degenerate input
@@ -63,20 +63,20 @@ func nestedMapLookup(m map[string]interface{}, ks ...string) (rval interface{})
return nil return nil
} else if len(ks) == 1 { // we've reached the final key } else if len(ks) == 1 { // we've reached the final key
return rval return rval
} else if m, ok = rval.(map[string]interface{}); !ok { } else if m, ok = rval.(map[string]any); !ok {
return nil return nil
} else { // 1+ more keys } else { // 1+ more keys
return nestedMapLookup(m, ks[1:]...) return nestedMapLookup(m, ks[1:]...)
} }
} }
func withDefaultBranch(ctx context.Context, b string, event map[string]interface{}) map[string]interface{} { func withDefaultBranch(ctx context.Context, b string, event map[string]any) map[string]any {
repoI, ok := event["repository"] repoI, ok := event["repository"]
if !ok { if !ok {
repoI = make(map[string]interface{}) repoI = make(map[string]any)
} }
repo, ok := repoI.(map[string]interface{}) repo, ok := repoI.(map[string]any)
if !ok { if !ok {
common.Logger(ctx).Warnf("unable to set default branch to %v", b) common.Logger(ctx).Warnf("unable to set default branch to %v", b)
return event return event
@@ -93,29 +93,31 @@ func withDefaultBranch(ctx context.Context, b string, event map[string]interface
return event return event
} }
var findGitRef = git.FindGitRef var (
var findGitRevision = git.FindGitRevision findGitRef = git.FindGitRef
findGitRevision = git.FindGitRevision
)
func (ghc *GithubContext) SetRef(ctx context.Context, defaultBranch string, repoPath string) { func (ghc *GithubContext) SetRef(ctx context.Context, defaultBranch, repoPath string) {
logger := common.Logger(ctx) logger := common.Logger(ctx)
// https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows // https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows
// https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads // https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads
switch ghc.EventName { switch ghc.EventName {
case "pull_request_target": case "pull_request_target":
ghc.Ref = fmt.Sprintf("refs/heads/%s", ghc.BaseRef) ghc.Ref = "refs/heads/" + ghc.BaseRef
case "pull_request", "pull_request_review", "pull_request_review_comment": case "pull_request", "pull_request_review", "pull_request_review_comment":
ghc.Ref = fmt.Sprintf("refs/pull/%.0f/merge", ghc.Event["number"]) ghc.Ref = fmt.Sprintf("refs/pull/%.0f/merge", ghc.Event["number"])
case "deployment", "deployment_status": case "deployment", "deployment_status":
ghc.Ref = asString(nestedMapLookup(ghc.Event, "deployment", "ref")) ghc.Ref = asString(nestedMapLookup(ghc.Event, "deployment", "ref"))
case "release": case "release":
ghc.Ref = fmt.Sprintf("refs/tags/%s", asString(nestedMapLookup(ghc.Event, "release", "tag_name"))) ghc.Ref = "refs/tags/" + asString(nestedMapLookup(ghc.Event, "release", "tag_name"))
case "push", "create", "workflow_dispatch": case "push", "create", "workflow_dispatch":
ghc.Ref = asString(ghc.Event["ref"]) ghc.Ref = asString(ghc.Event["ref"])
default: default:
defaultBranch := asString(nestedMapLookup(ghc.Event, "repository", "default_branch")) defaultBranch := asString(nestedMapLookup(ghc.Event, "repository", "default_branch"))
if defaultBranch != "" { if defaultBranch != "" {
ghc.Ref = fmt.Sprintf("refs/heads/%s", defaultBranch) ghc.Ref = "refs/heads/" + defaultBranch
} }
} }
@@ -136,7 +138,7 @@ func (ghc *GithubContext) SetRef(ctx context.Context, defaultBranch string, repo
} }
if ghc.Ref == "" { if ghc.Ref == "" {
ghc.Ref = fmt.Sprintf("refs/heads/%s", asString(nestedMapLookup(ghc.Event, "repository", "default_branch"))) ghc.Ref = "refs/heads/" + asString(nestedMapLookup(ghc.Event, "repository", "default_branch"))
} }
} }
} }
@@ -167,7 +169,7 @@ func (ghc *GithubContext) SetSha(ctx context.Context, repoPath string) {
} }
} }
func (ghc *GithubContext) SetRepositoryAndOwner(ctx context.Context, githubInstance string, remoteName string, repoPath string) { func (ghc *GithubContext) SetRepositoryAndOwner(ctx context.Context, githubInstance, remoteName, repoPath string) {
if ghc.Repository == "" { if ghc.Repository == "" {
repo, err := git.FindGithubRepo(ctx, repoPath, githubInstance, remoteName) repo, err := git.FindGithubRepo(ctx, repoPath, githubInstance, remoteName)
if err != nil { if err != nil {

View File

@@ -2,7 +2,7 @@ package model
import ( import (
"context" "context"
"fmt" "errors"
"testing" "testing"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@@ -27,19 +27,19 @@ func TestSetRef(t *testing.T) {
tables := []struct { tables := []struct {
eventName string eventName string
event map[string]interface{} event map[string]any
ref string ref string
refName string refName string
}{ }{
{ {
eventName: "pull_request_target", eventName: "pull_request_target",
event: map[string]interface{}{}, event: map[string]any{},
ref: "refs/heads/master", ref: "refs/heads/master",
refName: "master", refName: "master",
}, },
{ {
eventName: "pull_request", eventName: "pull_request",
event: map[string]interface{}{ event: map[string]any{
"number": 1234., "number": 1234.,
}, },
ref: "refs/pull/1234/merge", ref: "refs/pull/1234/merge",
@@ -47,8 +47,8 @@ func TestSetRef(t *testing.T) {
}, },
{ {
eventName: "deployment", eventName: "deployment",
event: map[string]interface{}{ event: map[string]any{
"deployment": map[string]interface{}{ "deployment": map[string]any{
"ref": "refs/heads/somebranch", "ref": "refs/heads/somebranch",
}, },
}, },
@@ -57,8 +57,8 @@ func TestSetRef(t *testing.T) {
}, },
{ {
eventName: "release", eventName: "release",
event: map[string]interface{}{ event: map[string]any{
"release": map[string]interface{}{ "release": map[string]any{
"tag_name": "v1.0.0", "tag_name": "v1.0.0",
}, },
}, },
@@ -67,7 +67,7 @@ func TestSetRef(t *testing.T) {
}, },
{ {
eventName: "push", eventName: "push",
event: map[string]interface{}{ event: map[string]any{
"ref": "refs/heads/somebranch", "ref": "refs/heads/somebranch",
}, },
ref: "refs/heads/somebranch", ref: "refs/heads/somebranch",
@@ -75,8 +75,8 @@ func TestSetRef(t *testing.T) {
}, },
{ {
eventName: "unknown", eventName: "unknown",
event: map[string]interface{}{ event: map[string]any{
"repository": map[string]interface{}{ "repository": map[string]any{
"default_branch": "main", "default_branch": "main",
}, },
}, },
@@ -85,7 +85,7 @@ func TestSetRef(t *testing.T) {
}, },
{ {
eventName: "no-event", eventName: "no-event",
event: map[string]interface{}{}, event: map[string]any{},
ref: "refs/heads/master", ref: "refs/heads/master",
refName: "master", refName: "master",
}, },
@@ -109,12 +109,12 @@ func TestSetRef(t *testing.T) {
t.Run("no-default-branch", func(t *testing.T) { t.Run("no-default-branch", func(t *testing.T) {
findGitRef = func(ctx context.Context, file string) (string, error) { findGitRef = func(ctx context.Context, file string) (string, error) {
return "", fmt.Errorf("no default branch") return "", errors.New("no default branch")
} }
ghc := &GithubContext{ ghc := &GithubContext{
EventName: "no-default-branch", EventName: "no-default-branch",
Event: map[string]interface{}{}, Event: map[string]any{},
} }
ghc.SetRef(context.Background(), "", "/some/dir") ghc.SetRef(context.Background(), "", "/some/dir")
@@ -141,14 +141,14 @@ func TestSetSha(t *testing.T) {
tables := []struct { tables := []struct {
eventName string eventName string
event map[string]interface{} event map[string]any
sha string sha string
}{ }{
{ {
eventName: "pull_request_target", eventName: "pull_request_target",
event: map[string]interface{}{ event: map[string]any{
"pull_request": map[string]interface{}{ "pull_request": map[string]any{
"base": map[string]interface{}{ "base": map[string]any{
"sha": "pr-base-sha", "sha": "pr-base-sha",
}, },
}, },
@@ -157,15 +157,15 @@ func TestSetSha(t *testing.T) {
}, },
{ {
eventName: "pull_request", eventName: "pull_request",
event: map[string]interface{}{ event: map[string]any{
"number": 1234., "number": 1234.,
}, },
sha: "1234fakesha", sha: "1234fakesha",
}, },
{ {
eventName: "deployment", eventName: "deployment",
event: map[string]interface{}{ event: map[string]any{
"deployment": map[string]interface{}{ "deployment": map[string]any{
"sha": "deployment-sha", "sha": "deployment-sha",
}, },
}, },
@@ -173,12 +173,12 @@ func TestSetSha(t *testing.T) {
}, },
{ {
eventName: "release", eventName: "release",
event: map[string]interface{}{}, event: map[string]any{},
sha: "1234fakesha", sha: "1234fakesha",
}, },
{ {
eventName: "push", eventName: "push",
event: map[string]interface{}{ event: map[string]any{
"after": "push-sha", "after": "push-sha",
"deleted": false, "deleted": false,
}, },
@@ -186,12 +186,12 @@ func TestSetSha(t *testing.T) {
}, },
{ {
eventName: "unknown", eventName: "unknown",
event: map[string]interface{}{}, event: map[string]any{},
sha: "1234fakesha", sha: "1234fakesha",
}, },
{ {
eventName: "no-event", eventName: "no-event",
event: map[string]interface{}{}, event: map[string]any{},
sha: "1234fakesha", sha: "1234fakesha",
}, },
} }

View File

@@ -1,6 +1,7 @@
package model package model
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
@@ -8,7 +9,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"sort" "slices"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@@ -57,7 +58,7 @@ type WorkflowFiles struct {
// NewWorkflowPlanner will load a specific workflow, all workflows from a directory or all workflows from a directory and its subdirectories // NewWorkflowPlanner will load a specific workflow, all workflows from a directory or all workflows from a directory and its subdirectories
// //
//nolint:gocyclo //nolint:gocyclo // function handles many cases
func NewWorkflowPlanner(path string, noWorkflowRecurse bool) (WorkflowPlanner, error) { func NewWorkflowPlanner(path string, noWorkflowRecurse bool) (WorkflowPlanner, error) {
path, err := filepath.Abs(path) path, err := filepath.Abs(path)
if err != nil { if err != nil {
@@ -115,9 +116,6 @@ func NewWorkflowPlanner(path string, noWorkflowRecurse bool) (WorkflowPlanner, e
workflowDirEntry: fs.FileInfoToDirEntry(fi), workflowDirEntry: fs.FileInfoToDirEntry(fi),
}) })
} }
if err != nil {
return nil, err
}
wp := new(workflowPlanner) wp := new(workflowPlanner)
for _, wf := range workflows { for _, wf := range workflows {
@@ -288,11 +286,8 @@ func (wp *workflowPlanner) GetEvents() []string {
for _, w := range wp.workflows { for _, w := range wp.workflows {
found := false found := false
for _, e := range events { for _, e := range events {
for _, we := range w.On() { if slices.Contains(w.On(), e) {
if e == we { found = true
found = true
break
}
} }
if found { if found {
break break
@@ -305,9 +300,7 @@ func (wp *workflowPlanner) GetEvents() []string {
} }
// sort the list based on depth of dependencies // sort the list based on depth of dependencies
sort.Slice(events, func(i, j int) bool { slices.Sort(events)
return events[i] < events[j]
})
return events return events
} }
@@ -338,7 +331,7 @@ func (s *Stage) GetJobIDs() []string {
// Merge stages with existing stages in plan // Merge stages with existing stages in plan
func (p *Plan) mergeStages(stages []*Stage) { func (p *Plan) mergeStages(stages []*Stage) {
newStages := make([]*Stage, int(math.Max(float64(len(p.Stages)), float64(len(stages))))) newStages := make([]*Stage, int(math.Max(float64(len(p.Stages)), float64(len(stages)))))
for i := 0; i < len(newStages); i++ { for i := range newStages {
newStages[i] = new(Stage) newStages[i] = new(Stage)
if i >= len(p.Stages) { if i >= len(p.Stages) {
newStages[i].Runs = append(newStages[i].Runs, stages[i].Runs...) newStages[i].Runs = append(newStages[i].Runs, stages[i].Runs...)
@@ -390,7 +383,7 @@ func createStages(w *Workflow, jobIDs ...string) ([]*Stage, error) {
} }
if len(stages) == 0 { if len(stages) == 0 {
return nil, fmt.Errorf("Could not find any stages to run. View the valid jobs with `act --list`. Use `act --help` to find how to filter by Job ID/Workflow/Event Name") return nil, errors.New("Could not find any stages to run. View the valid jobs with `act --list`. Use `act --help` to find how to filter by Job ID/Workflow/Event Name")
} }
return stages, nil return stages, nil

View File

@@ -4,15 +4,17 @@ import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"io" "io"
"maps"
"reflect" "reflect"
"regexp" "regexp"
"slices"
"strconv" "strconv"
"strings" "strings"
"github.com/nektos/act/pkg/common"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"go.yaml.in/yaml/v4" "go.yaml.in/yaml/v4"
"github.com/nektos/act/pkg/common"
) )
// Workflow is the structure of the files in .github/workflows // Workflow is the structure of the files in .github/workflows
@@ -45,7 +47,7 @@ func (w *Workflow) On() []string {
} }
return val return val
case yaml.MappingNode: case yaml.MappingNode:
var val map[string]interface{} var val map[string]any
err := w.RawOn.Decode(&val) err := w.RawOn.Decode(&val)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@@ -59,9 +61,9 @@ func (w *Workflow) On() []string {
return nil return nil
} }
func (w *Workflow) OnEvent(event string) interface{} { func (w *Workflow) OnEvent(event string) any {
if w.RawOn.Kind == yaml.MappingNode { if w.RawOn.Kind == yaml.MappingNode {
var val map[string]interface{} var val map[string]any
if !decodeNode(w.RawOn, &val) { if !decodeNode(w.RawOn, &val) {
return nil return nil
} }
@@ -77,10 +79,10 @@ func (w *Workflow) OnSchedule() []string {
} }
switch val := schedules.(type) { switch val := schedules.(type) {
case []interface{}: case []any:
allSchedules := []string{} allSchedules := []string{}
for _, v := range val { for _, v := range val {
for k, cron := range v.(map[string]interface{}) { for k, cron := range v.(map[string]any) {
if k != "cron" { if k != "cron" {
continue continue
} }
@@ -121,10 +123,8 @@ func (w *Workflow) WorkflowDispatchConfig() *WorkflowDispatch {
if !decodeNode(w.RawOn, &val) { if !decodeNode(w.RawOn, &val) {
return nil return nil
} }
for _, v := range val { if slices.Contains(val, "workflow_dispatch") {
if v == "workflow_dispatch" { return &WorkflowDispatch{}
return &WorkflowDispatch{}
}
} }
case yaml.MappingNode: case yaml.MappingNode:
var val map[string]yaml.Node var val map[string]yaml.Node
@@ -199,7 +199,7 @@ type Job struct {
Defaults Defaults `yaml:"defaults"` Defaults Defaults `yaml:"defaults"`
Outputs map[string]string `yaml:"outputs"` Outputs map[string]string `yaml:"outputs"`
Uses string `yaml:"uses"` Uses string `yaml:"uses"`
With map[string]interface{} `yaml:"with"` With map[string]any `yaml:"with"`
RawSecrets yaml.Node `yaml:"secrets"` RawSecrets yaml.Node `yaml:"secrets"`
RawPermissions yaml.Node `yaml:"permissions"` RawPermissions yaml.Node `yaml:"permissions"`
Result string Result string
@@ -378,9 +378,9 @@ func (j *Job) Environment() map[string]string {
} }
// Matrix decodes RawMatrix YAML node // Matrix decodes RawMatrix YAML node
func (j *Job) Matrix() map[string][]interface{} { func (j *Job) Matrix() map[string][]any {
if j.Strategy.RawMatrix.Kind == yaml.MappingNode { if j.Strategy.RawMatrix.Kind == yaml.MappingNode {
var val map[string][]interface{} var val map[string][]any
if !decodeNode(j.Strategy.RawMatrix, &val) { if !decodeNode(j.Strategy.RawMatrix, &val) {
return nil return nil
} }
@@ -392,22 +392,22 @@ func (j *Job) Matrix() map[string][]interface{} {
// GetMatrixes returns the matrix cross product // GetMatrixes returns the matrix cross product
// It skips includes and hard fails excludes for non-existing keys // It skips includes and hard fails excludes for non-existing keys
// //
//nolint:gocyclo //nolint:gocyclo // function handles many cases
func (j *Job) GetMatrixes() ([]map[string]interface{}, error) { func (j *Job) GetMatrixes() ([]map[string]any, error) {
matrixes := make([]map[string]interface{}, 0) matrixes := make([]map[string]any, 0)
if j.Strategy != nil { if j.Strategy != nil {
// Always set these values, even if there's an error later // Always set these values, even if there's an error later
j.Strategy.FailFast = j.Strategy.GetFailFast() j.Strategy.FailFast = j.Strategy.GetFailFast()
j.Strategy.MaxParallel = j.Strategy.GetMaxParallel() j.Strategy.MaxParallel = j.Strategy.GetMaxParallel()
if m := j.Matrix(); m != nil { if m := j.Matrix(); m != nil {
includes := make([]map[string]interface{}, 0) includes := make([]map[string]any, 0)
extraIncludes := make([]map[string]interface{}, 0) extraIncludes := make([]map[string]any, 0)
for _, v := range m["include"] { for _, v := range m["include"] {
switch t := v.(type) { switch t := v.(type) {
case []interface{}: case []any:
for _, i := range t { for _, i := range t {
i := i.(map[string]interface{}) i := i.(map[string]any)
extraInclude := true extraInclude := true
for k := range i { for k := range i {
if _, ok := m[k]; ok { if _, ok := m[k]; ok {
@@ -420,8 +420,8 @@ func (j *Job) GetMatrixes() ([]map[string]interface{}, error) {
extraIncludes = append(extraIncludes, i) extraIncludes = append(extraIncludes, i)
} }
} }
case interface{}: case any:
v := v.(map[string]interface{}) v := v.(map[string]any)
extraInclude := true extraInclude := true
for k := range v { for k := range v {
if _, ok := m[k]; ok { if _, ok := m[k]; ok {
@@ -437,9 +437,9 @@ func (j *Job) GetMatrixes() ([]map[string]interface{}, error) {
} }
delete(m, "include") delete(m, "include")
excludes := make([]map[string]interface{}, 0) excludes := make([]map[string]any, 0)
for _, e := range m["exclude"] { for _, e := range m["exclude"] {
e := e.(map[string]interface{}) e := e.(map[string]any)
for k := range e { for k := range e {
if _, ok := m[k]; ok { if _, ok := m[k]; ok {
excludes = append(excludes, e) excludes = append(excludes, e)
@@ -468,9 +468,7 @@ func (j *Job) GetMatrixes() ([]map[string]interface{}, error) {
if commonKeysMatch2(matrix, include, m) { if commonKeysMatch2(matrix, include, m) {
matched = true matched = true
log.Debugf("Adding include values '%v' to existing entry", include) log.Debugf("Adding include values '%v' to existing entry", include)
for k, v := range include { maps.Copy(matrix, include)
matrix[k] = v
}
} }
} }
if !matched { if !matched {
@@ -482,19 +480,19 @@ func (j *Job) GetMatrixes() ([]map[string]interface{}, error) {
matrixes = append(matrixes, include) matrixes = append(matrixes, include)
} }
if len(matrixes) == 0 { if len(matrixes) == 0 {
matrixes = append(matrixes, make(map[string]interface{})) matrixes = append(matrixes, make(map[string]any))
} }
} else { } else {
matrixes = append(matrixes, make(map[string]interface{})) matrixes = append(matrixes, make(map[string]any))
} }
} else { } else {
matrixes = append(matrixes, make(map[string]interface{})) matrixes = append(matrixes, make(map[string]any))
log.Debugf("Empty Strategy, matrixes=%v", matrixes) log.Debugf("Empty Strategy, matrixes=%v", matrixes)
} }
return matrixes, nil return matrixes, nil
} }
func commonKeysMatch(a map[string]interface{}, b map[string]interface{}) bool { func commonKeysMatch(a, b map[string]any) bool {
for aKey, aVal := range a { for aKey, aVal := range a {
if bVal, ok := b[aKey]; ok && !reflect.DeepEqual(aVal, bVal) { if bVal, ok := b[aKey]; ok && !reflect.DeepEqual(aVal, bVal) {
return false return false
@@ -503,7 +501,7 @@ func commonKeysMatch(a map[string]interface{}, b map[string]interface{}) bool {
return true return true
} }
func commonKeysMatch2(a map[string]interface{}, b map[string]interface{}, m map[string][]interface{}) bool { func commonKeysMatch2(a, b map[string]any, m map[string][]any) bool {
for aKey, aVal := range a { for aKey, aVal := range a {
_, useKey := m[aKey] _, useKey := m[aKey]
if bVal, ok := b[aKey]; useKey && ok && !reflect.DeepEqual(aVal, bVal) { if bVal, ok := b[aKey]; useKey && ok && !reflect.DeepEqual(aVal, bVal) {
@@ -623,7 +621,7 @@ func (s *Step) GetEnv() map[string]string {
for k, v := range s.With { for k, v := range s.With {
envKey := regexp.MustCompile("[^A-Z0-9-]").ReplaceAllString(strings.ToUpper(k), "_") envKey := regexp.MustCompile("[^A-Z0-9-]").ReplaceAllString(strings.ToUpper(k), "_")
envKey = fmt.Sprintf("INPUT_%s", strings.ToUpper(envKey)) envKey = "INPUT_" + strings.ToUpper(envKey)
env[envKey] = v env[envKey] = v
} }
return env return env
@@ -631,7 +629,7 @@ func (s *Step) GetEnv() map[string]string {
// ShellCommand returns the command for the shell // ShellCommand returns the command for the shell
func (s *Step) ShellCommand() string { func (s *Step) ShellCommand() string {
shellCommand := "" var shellCommand string
// Reference: https://github.com/actions/runner/blob/8109c962f09d9acc473d92c595ff43afceddb347/src/Runner.Worker/Handlers/ScriptHandlerHelpers.cs#L9-L17 // Reference: https://github.com/actions/runner/blob/8109c962f09d9acc473d92c595ff43afceddb347/src/Runner.Worker/Handlers/ScriptHandlerHelpers.cs#L9-L17
switch s.Shell { switch s.Shell {
@@ -760,11 +758,11 @@ func (w *Workflow) GetJobIDs() []string {
return ids return ids
} }
var OnDecodeNodeError = func(node yaml.Node, out interface{}, err error) { var OnDecodeNodeError = func(node yaml.Node, out any, err error) {
log.Fatalf("Failed to decode node %v into %T: %v", node, out, err) log.Fatalf("Failed to decode node %v into %T: %v", node, out, err)
} }
func decodeNode(node yaml.Node, out interface{}) bool { func decodeNode(node yaml.Node, out any) bool {
if err := node.Decode(out); err != nil { if err := node.Decode(out); err != nil {
if OnDecodeNodeError != nil { if OnDecodeNodeError != nil {
OnDecodeNodeError(node, out, err) OnDecodeNodeError(node, out, err)
@@ -791,7 +789,7 @@ func (r *RawConcurrency) UnmarshalYAML(n *yaml.Node) error {
return n.Decode((*objectConcurrency)(r)) return n.Decode((*objectConcurrency)(r))
} }
func (r *RawConcurrency) MarshalYAML() (interface{}, error) { func (r *RawConcurrency) MarshalYAML() (any, error) {
if r.RawExpression != "" { if r.RawExpression != "" {
return r.RawExpression, nil return r.RawExpression, nil
} }

View File

@@ -430,24 +430,24 @@ func TestReadWorkflow_Strategy(t *testing.T) {
job := wf.Jobs["strategy-only-max-parallel"] job := wf.Jobs["strategy-only-max-parallel"]
matrixes, err := job.GetMatrixes() matrixes, err := job.GetMatrixes()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, matrixes, []map[string]interface{}{{}}) assert.Equal(t, matrixes, []map[string]any{{}})
assert.Equal(t, job.Matrix(), map[string][]interface{}(nil)) assert.Equal(t, job.Matrix(), map[string][]any(nil))
assert.Equal(t, job.Strategy.MaxParallel, 2) assert.Equal(t, job.Strategy.MaxParallel, 2)
assert.Equal(t, job.Strategy.FailFast, true) assert.Equal(t, job.Strategy.FailFast, true)
job = wf.Jobs["strategy-only-fail-fast"] job = wf.Jobs["strategy-only-fail-fast"]
matrixes, err = job.GetMatrixes() matrixes, err = job.GetMatrixes()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, matrixes, []map[string]interface{}{{}}) assert.Equal(t, matrixes, []map[string]any{{}})
assert.Equal(t, job.Matrix(), map[string][]interface{}(nil)) assert.Equal(t, job.Matrix(), map[string][]any(nil))
assert.Equal(t, job.Strategy.MaxParallel, 4) assert.Equal(t, job.Strategy.MaxParallel, 4)
assert.Equal(t, job.Strategy.FailFast, false) assert.Equal(t, job.Strategy.FailFast, false)
job = wf.Jobs["strategy-no-matrix"] job = wf.Jobs["strategy-no-matrix"]
matrixes, err = job.GetMatrixes() matrixes, err = job.GetMatrixes()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, matrixes, []map[string]interface{}{{}}) assert.Equal(t, matrixes, []map[string]any{{}})
assert.Equal(t, job.Matrix(), map[string][]interface{}(nil)) assert.Equal(t, job.Matrix(), map[string][]any(nil))
assert.Equal(t, job.Strategy.MaxParallel, 2) assert.Equal(t, job.Strategy.MaxParallel, 2)
assert.Equal(t, job.Strategy.FailFast, false) assert.Equal(t, job.Strategy.FailFast, false)
@@ -455,7 +455,7 @@ func TestReadWorkflow_Strategy(t *testing.T) {
matrixes, err = job.GetMatrixes() matrixes, err = job.GetMatrixes()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, matrixes, assert.Equal(t, matrixes,
[]map[string]interface{}{ []map[string]any{
{"datacenter": "site-c", "node-version": "14.x", "site": "staging"}, {"datacenter": "site-c", "node-version": "14.x", "site": "staging"},
{"datacenter": "site-c", "node-version": "16.x", "site": "staging"}, {"datacenter": "site-c", "node-version": "16.x", "site": "staging"},
{"datacenter": "site-d", "node-version": "16.x", "site": "staging"}, {"datacenter": "site-d", "node-version": "16.x", "site": "staging"},
@@ -465,15 +465,15 @@ func TestReadWorkflow_Strategy(t *testing.T) {
}, },
) )
assert.Equal(t, job.Matrix(), assert.Equal(t, job.Matrix(),
map[string][]interface{}{ map[string][]any{
"datacenter": {"site-c", "site-d"}, "datacenter": {"site-c", "site-d"},
"exclude": { "exclude": {
map[string]interface{}{"datacenter": "site-d", "node-version": "14.x", "site": "staging"}, map[string]any{"datacenter": "site-d", "node-version": "14.x", "site": "staging"},
}, },
"include": { "include": {
map[string]interface{}{"php-version": 5.4}, map[string]any{"php-version": 5.4},
map[string]interface{}{"datacenter": "site-a", "node-version": "10.x", "site": "prod"}, map[string]any{"datacenter": "site-a", "node-version": "10.x", "site": "prod"},
map[string]interface{}{"datacenter": "site-b", "node-version": "12.x", "site": "dev"}, map[string]any{"datacenter": "site-b", "node-version": "12.x", "site": "dev"},
}, },
"node-version": {"14.x", "16.x"}, "node-version": {"14.x", "16.x"},
"site": {"staging"}, "site": {"staging"},

View File

@@ -14,11 +14,11 @@ import (
"runtime" "runtime"
"strings" "strings"
"github.com/kballard/go-shellquote"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container" "github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/kballard/go-shellquote"
) )
type actionStep interface { type actionStep interface {
@@ -29,7 +29,7 @@ type actionStep interface {
getCompositeSteps() *compositeSteps getCompositeSteps() *compositeSteps
} }
type readAction func(ctx context.Context, step *model.Step, actionDir string, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) type readAction func(ctx context.Context, step *model.Step, actionDir, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error)
type actionYamlReader func(filename string) (io.Reader, io.Closer, error) type actionYamlReader func(filename string) (io.Reader, io.Closer, error)
@@ -40,7 +40,7 @@ type runAction func(step actionStep, actionDir string, remoteAction *remoteActio
//go:embed res/trampoline.js //go:embed res/trampoline.js
var trampoline embed.FS var trampoline embed.FS
func readActionImpl(ctx context.Context, step *model.Step, actionDir string, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) { func readActionImpl(ctx context.Context, step *model.Step, actionDir, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) {
logger := common.Logger(ctx) logger := common.Logger(ctx)
allErrors := []error{} allErrors := []error{}
addError := func(fileName string, err error) { addError := func(fileName string, err error) {
@@ -117,7 +117,7 @@ func readActionImpl(ctx context.Context, step *model.Step, actionDir string, act
return action, err return action, err
} }
func maybeCopyToActionDir(ctx context.Context, step actionStep, actionDir string, actionPath string, containerActionDir string) error { func maybeCopyToActionDir(ctx context.Context, step actionStep, actionDir, actionPath, containerActionDir string) error {
logger := common.Logger(ctx) logger := common.Logger(ctx)
rc := step.getRunContext() rc := step.getRunContext()
stepModel := step.getStepModel() stepModel := step.getStepModel()
@@ -207,7 +207,7 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction
rc.ApplyExtraPath(ctx, step.getEnv()) rc.ApplyExtraPath(ctx, step.getEnv())
execFileName := fmt.Sprintf("%s.out", action.Runs.Main) execFileName := action.Runs.Main + ".out"
buildArgs := []string{"go", "build", "-o", execFileName, action.Runs.Main} buildArgs := []string{"go", "build", "-o", execFileName, action.Runs.Main}
execArgs := []string{filepath.Join(containerActionDir, execFileName)} execArgs := []string{filepath.Join(containerActionDir, execFileName)}
@@ -262,8 +262,8 @@ func removeGitIgnore(ctx context.Context, directory string) error {
// TODO: break out parts of function to reduce complexicity // TODO: break out parts of function to reduce complexicity
// //
//nolint:gocyclo //nolint:gocyclo // function handles many cases
func execAsDocker(ctx context.Context, step actionStep, actionName string, basedir string, localAction bool) error { func execAsDocker(ctx context.Context, step actionStep, actionName, basedir string, localAction bool) error {
logger := common.Logger(ctx) logger := common.Logger(ctx)
rc := step.getRunContext() rc := step.getRunContext()
action := step.getActionModel() action := step.getActionModel()
@@ -271,14 +271,14 @@ func execAsDocker(ctx context.Context, step actionStep, actionName string, based
var prepImage common.Executor var prepImage common.Executor
var image string var image string
forcePull := false forcePull := false
if strings.HasPrefix(action.Runs.Image, "docker://") { if after, ok := strings.CutPrefix(action.Runs.Image, "docker://"); ok {
image = strings.TrimPrefix(action.Runs.Image, "docker://") image = after
// Apply forcePull only for prebuild docker images // Apply forcePull only for prebuild docker images
forcePull = rc.Config.ForcePull forcePull = rc.Config.ForcePull
} else { } else {
// "-dockeraction" enshures that "./", "./test " won't get converted to "act-:latest", "act-test-:latest" which are invalid docker image names // "-dockeraction" enshures that "./", "./test " won't get converted to "act-:latest", "act-test-:latest" which are invalid docker image names
image = fmt.Sprintf("%s-dockeraction:%s", regexp.MustCompile("[^a-zA-Z0-9]").ReplaceAllString(actionName, "-"), "latest") image = fmt.Sprintf("%s-dockeraction:%s", regexp.MustCompile("[^a-zA-Z0-9]").ReplaceAllString(actionName, "-"), "latest")
image = fmt.Sprintf("act-%s", strings.TrimLeft(image, "-")) image = "act-" + strings.TrimLeft(image, "-")
image = strings.ToLower(image) image = strings.ToLower(image)
contextDir, fileName := filepath.Split(filepath.Join(basedir, action.Runs.Image)) contextDir, fileName := filepath.Split(filepath.Join(basedir, action.Runs.Image))
@@ -391,7 +391,7 @@ func evalDockerArgs(ctx context.Context, step step, action *model.Action, cmd *[
} }
} }
func newStepContainer(ctx context.Context, step step, image string, cmd []string, entrypoint []string) container.Container { func newStepContainer(ctx context.Context, step step, image string, cmd, entrypoint []string) container.Container {
rc := step.getRunContext() rc := step.getRunContext()
stepModel := step.getStepModel() stepModel := step.getStepModel()
rawLogger := common.Logger(ctx).WithField("raw_output", true) rawLogger := common.Logger(ctx).WithField("raw_output", true)
@@ -414,7 +414,7 @@ func newStepContainer(ctx context.Context, step step, image string, cmd []string
envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_TEMP", "/tmp")) envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_TEMP", "/tmp"))
binds, mounts := rc.GetBindsAndMounts() binds, mounts := rc.GetBindsAndMounts()
networkMode := fmt.Sprintf("container:%s", rc.jobContainerName()) networkMode := "container:" + rc.jobContainerName()
if rc.IsHostEnv(ctx) { if rc.IsHostEnv(ctx) {
networkMode = "default" networkMode = "default"
} }
@@ -446,7 +446,7 @@ func populateEnvsFromSavedState(env *map[string]string, step actionStep, rc *Run
state, ok := rc.IntraActionState[step.getStepModel().ID] state, ok := rc.IntraActionState[step.getStepModel().ID]
if ok { if ok {
for name, value := range state { for name, value := range state {
envName := fmt.Sprintf("STATE_%s", name) envName := "STATE_" + name
(*env)[envName] = value (*env)[envName] = value
} }
} }
@@ -456,7 +456,7 @@ func populateEnvsFromInput(ctx context.Context, env *map[string]string, action *
eval := rc.NewExpressionEvaluator(ctx) eval := rc.NewExpressionEvaluator(ctx)
for inputID, input := range action.Inputs { for inputID, input := range action.Inputs {
envKey := regexp.MustCompile("[^A-Z0-9-]").ReplaceAllString(strings.ToUpper(inputID), "_") envKey := regexp.MustCompile("[^A-Z0-9-]").ReplaceAllString(strings.ToUpper(inputID), "_")
envKey = fmt.Sprintf("INPUT_%s", envKey) envKey = "INPUT_" + envKey
if _, ok := (*env)[envKey]; !ok { if _, ok := (*env)[envKey]; !ok {
(*env)[envKey] = eval.Interpolate(ctx, input.Default) (*env)[envKey] = eval.Interpolate(ctx, input.Default)
} }
@@ -543,7 +543,7 @@ func runPreStep(step actionStep) common.Executor {
actionPath = "" actionPath = ""
} }
actionLocation := "" var actionLocation string
if actionPath != "" { if actionPath != "" {
actionLocation = path.Join(actionDir, actionPath) actionLocation = path.Join(actionDir, actionPath)
} else { } else {
@@ -571,7 +571,7 @@ func runPreStep(step actionStep) common.Executor {
if steps := step.getCompositeSteps(); steps != nil && steps.pre != nil { if steps := step.getCompositeSteps(); steps != nil && steps.pre != nil {
return steps.pre(ctx) return steps.pre(ctx)
} }
return fmt.Errorf("missing steps in composite action") return errors.New("missing steps in composite action")
case x == model.ActionRunsUsingGo: case x == model.ActionRunsUsingGo:
// defaults in pre steps were missing, however provided inputs are available // defaults in pre steps were missing, however provided inputs are available
@@ -587,7 +587,7 @@ func runPreStep(step actionStep) common.Executor {
actionPath = "" actionPath = ""
} }
actionLocation := "" var actionLocation string
if actionPath != "" { if actionPath != "" {
actionLocation = path.Join(actionDir, actionPath) actionLocation = path.Join(actionDir, actionPath)
} else { } else {
@@ -602,7 +602,7 @@ func runPreStep(step actionStep) common.Executor {
rc.ApplyExtraPath(ctx, step.getEnv()) rc.ApplyExtraPath(ctx, step.getEnv())
execFileName := fmt.Sprintf("%s.out", action.Runs.Pre) execFileName := action.Runs.Pre + ".out"
buildArgs := []string{"go", "build", "-o", execFileName, action.Runs.Pre} buildArgs := []string{"go", "build", "-o", execFileName, action.Runs.Pre}
execArgs := []string{filepath.Join(containerActionDir, execFileName)} execArgs := []string{filepath.Join(containerActionDir, execFileName)}
@@ -672,7 +672,7 @@ func runPostStep(step actionStep) common.Executor {
actionPath = "" actionPath = ""
} }
actionLocation := "" var actionLocation string
if actionPath != "" { if actionPath != "" {
actionLocation = path.Join(actionDir, actionPath) actionLocation = path.Join(actionDir, actionPath)
} else { } else {
@@ -702,13 +702,13 @@ func runPostStep(step actionStep) common.Executor {
if steps := step.getCompositeSteps(); steps != nil && steps.post != nil { if steps := step.getCompositeSteps(); steps != nil && steps.post != nil {
return steps.post(ctx) return steps.post(ctx)
} }
return fmt.Errorf("missing steps in composite action") return errors.New("missing steps in composite action")
case x == model.ActionRunsUsingGo: case x == model.ActionRunsUsingGo:
populateEnvsFromSavedState(step.getEnv(), step, rc) populateEnvsFromSavedState(step.getEnv(), step, rc)
rc.ApplyExtraPath(ctx, step.getEnv()) rc.ApplyExtraPath(ctx, step.getEnv())
execFileName := fmt.Sprintf("%s.out", action.Runs.Post) execFileName := action.Runs.Post + ".out"
buildArgs := []string{"go", "build", "-o", execFileName, action.Runs.Post} buildArgs := []string{"go", "build", "-o", execFileName, action.Runs.Post}
execArgs := []string{filepath.Join(containerActionDir, execFileName)} execArgs := []string{filepath.Join(containerActionDir, execFileName)}

View File

@@ -5,17 +5,15 @@ import (
"bytes" "bytes"
"context" "context"
"io" "io"
"os"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
//nolint:gosec
func TestActionCache(t *testing.T) { func TestActionCache(t *testing.T) {
a := assert.New(t) a := assert.New(t)
cache := &GoGitActionCache{ cache := &GoGitActionCache{
Path: os.TempDir(), Path: t.TempDir(),
} }
ctx := context.Background() ctx := context.Background()
cacheDir := "nektos/act-test-actions" cacheDir := "nektos/act-test-actions"

View File

@@ -2,8 +2,9 @@ package runner
import ( import (
"context" "context"
"fmt" "errors"
"regexp" "regexp"
"strconv"
"strings" "strings"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
@@ -25,7 +26,7 @@ func evaluateCompositeInputAndEnv(ctx context.Context, parent *RunContext, step
for inputID, input := range step.getActionModel().Inputs { for inputID, input := range step.getActionModel().Inputs {
envKey := regexp.MustCompile("[^A-Z0-9-]").ReplaceAllString(strings.ToUpper(inputID), "_") envKey := regexp.MustCompile("[^A-Z0-9-]").ReplaceAllString(strings.ToUpper(inputID), "_")
envKey = fmt.Sprintf("INPUT_%s", strings.ToUpper(envKey)) envKey = "INPUT_" + strings.ToUpper(envKey)
// lookup if key is defined in the step but the already // lookup if key is defined in the step but the already
// evaluated value from the environment // evaluated value from the environment
@@ -90,7 +91,7 @@ func execAsComposite(step actionStep) common.Executor {
steps := step.getCompositeSteps() steps := step.getCompositeSteps()
if steps == nil || steps.main == nil { if steps == nil || steps.main == nil {
return fmt.Errorf("missing steps in composite action") return errors.New("missing steps in composite action")
} }
ctx = WithCompositeLogger(ctx, &compositeRC.Masks) ctx = WithCompositeLogger(ctx, &compositeRC.Masks)
@@ -138,7 +139,7 @@ func (rc *RunContext) compositeExecutor(action *model.Action) *compositeSteps {
for i, step := range action.Runs.Steps { for i, step := range action.Runs.Steps {
if step.ID == "" { if step.ID == "" {
step.ID = fmt.Sprintf("%d", i) step.ID = strconv.Itoa(i)
} }
step.Number = i step.Number = i

View File

@@ -8,6 +8,7 @@ import (
"testing" "testing"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )
@@ -122,7 +123,7 @@ runs:
writeFile := func(filename string, data []byte, perm fs.FileMode) error { writeFile := func(filename string, data []byte, perm fs.FileMode) error {
assert.Equal(t, "actionDir/actionPath/trampoline.js", filename) assert.Equal(t, "actionDir/actionPath/trampoline.js", filename)
assert.Equal(t, fs.FileMode(0400), perm) assert.Equal(t, fs.FileMode(0o400), perm)
return nil return nil
} }

View File

@@ -29,7 +29,7 @@ func tryParseRawActionCommand(line string) (command string, kvPairs map[string]s
arg = m[4] arg = m[4]
ok = true ok = true
} }
return return command, kvPairs, arg, ok
} }
func (rc *RunContext) commandHandler(ctx context.Context) common.LineHandler { func (rc *RunContext) commandHandler(ctx context.Context) common.LineHandler {
@@ -135,10 +135,10 @@ func (rc *RunContext) addPath(ctx context.Context, arg string) {
rc.ExtraPath = extraPath rc.ExtraPath = extraPath
} }
func parseKeyValuePairs(kvPairs string, separator string) map[string]string { func parseKeyValuePairs(kvPairs, separator string) map[string]string {
rtn := make(map[string]string) rtn := make(map[string]string)
kvPairList := strings.Split(kvPairs, separator) kvPairList := strings.SplitSeq(kvPairs, separator)
for _, kvPair := range kvPairList { for kvPair := range kvPairList {
kv := strings.Split(kvPair, "=") kv := strings.Split(kvPair, "=")
if len(kv) == 2 { if len(kv) == 2 {
rtn[kv[0]] = kv[1] rtn[kv[0]] = kv[1]

View File

@@ -7,11 +7,11 @@ import (
"os" "os"
"testing" "testing"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
) )
func TestSetEnv(t *testing.T) { func TestSetEnv(t *testing.T) {
@@ -164,7 +164,7 @@ func TestAddmaskUsemask(t *testing.T) {
re := captureOutput(t, func() { re := captureOutput(t, func() {
ctx := context.Background() ctx := context.Background()
ctx = WithJobLogger(ctx, "0", "testjob", config, &rc.Masks, map[string]interface{}{}) ctx = WithJobLogger(ctx, "0", "testjob", config, &rc.Masks, map[string]any{})
handler := rc.commandHandler(ctx) handler := rc.commandHandler(ctx)
handler("::add-mask::secret\n") handler("::add-mask::secret\n")

View File

@@ -6,6 +6,7 @@ import (
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container" "github.com/nektos/act/pkg/container"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )
@@ -15,7 +16,7 @@ type containerMock struct {
container.LinuxContainerEnvironmentExtensions container.LinuxContainerEnvironmentExtensions
} }
func (cm *containerMock) Create(capAdd []string, capDrop []string) common.Executor { func (cm *containerMock) Create(capAdd, capDrop []string) common.Executor {
args := cm.Called(capAdd, capDrop) args := cm.Called(capAdd, capDrop)
return args.Get(0).(func(context.Context) error) return args.Get(0).(func(context.Context) error)
} }
@@ -55,7 +56,7 @@ func (cm *containerMock) Copy(destPath string, files ...*container.FileEntry) co
return args.Get(0).(func(context.Context) error) return args.Get(0).(func(context.Context) error)
} }
func (cm *containerMock) CopyDir(destPath string, srcPath string, useGitIgnore bool) common.Executor { func (cm *containerMock) CopyDir(destPath, srcPath string, useGitIgnore bool) common.Executor {
args := cm.Called(destPath, srcPath, useGitIgnore) args := cm.Called(destPath, srcPath, useGitIgnore)
return args.Get(0).(func(context.Context) error) return args.Get(0).(func(context.Context) error)
} }

View File

@@ -4,24 +4,26 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"maps"
"path" "path"
"reflect" "reflect"
"regexp" "regexp"
"strings" "strings"
"time" "time"
_ "embed"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container" "github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/exprparser" "github.com/nektos/act/pkg/exprparser"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
_ "embed"
"go.yaml.in/yaml/v4" "go.yaml.in/yaml/v4"
) )
// ExpressionEvaluator is the interface for evaluating expressions // ExpressionEvaluator is the interface for evaluating expressions
type ExpressionEvaluator interface { type ExpressionEvaluator interface {
evaluate(context.Context, string, exprparser.DefaultStatusCheck) (interface{}, error) evaluate(context.Context, string, exprparser.DefaultStatusCheck) (any, error)
EvaluateYamlNode(context.Context, *yaml.Node) error EvaluateYamlNode(context.Context, *yaml.Node) error
Interpolate(context.Context, string) string Interpolate(context.Context, string) string
} }
@@ -36,7 +38,7 @@ func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map
// todo: cleanup EvaluationEnvironment creation // todo: cleanup EvaluationEnvironment creation
using := make(map[string]exprparser.Needs) using := make(map[string]exprparser.Needs)
strategy := make(map[string]interface{}) strategy := make(map[string]any)
if rc.Run != nil { if rc.Run != nil {
job := rc.Run.Job() job := rc.Run.Job()
if job != nil && job.Strategy != nil { if job != nil && job.Strategy != nil {
@@ -64,9 +66,7 @@ func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map
result := model.WorkflowCallResult{ result := model.WorkflowCallResult{
Outputs: map[string]string{}, Outputs: map[string]string{},
} }
for k, v := range job.Outputs { maps.Copy(result.Outputs, job.Outputs)
result.Outputs[k] = v
}
workflowCallResult[jobName] = &result workflowCallResult[jobName] = &result
} }
} }
@@ -110,7 +110,7 @@ var hashfiles string
func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step) ExpressionEvaluator { func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step) ExpressionEvaluator {
// todo: cleanup EvaluationEnvironment creation // todo: cleanup EvaluationEnvironment creation
job := rc.Run.Job() job := rc.Run.Job()
strategy := make(map[string]interface{}) strategy := make(map[string]any)
if job.Strategy != nil { if job.Strategy != nil {
strategy["fail-fast"] = job.Strategy.FailFast strategy["fail-fast"] = job.Strategy.FailFast
strategy["max-parallel"] = job.Strategy.MaxParallel strategy["max-parallel"] = job.Strategy.MaxParallel
@@ -157,8 +157,8 @@ func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step)
} }
} }
func getHashFilesFunction(ctx context.Context, rc *RunContext) func(v []reflect.Value) (interface{}, error) { func getHashFilesFunction(ctx context.Context, rc *RunContext) func(v []reflect.Value) (any, error) {
hashFiles := func(v []reflect.Value) (interface{}, error) { hashFiles := func(v []reflect.Value) (any, error) {
if rc.JobContainer != nil { if rc.JobContainer != nil {
timeed, cancel := context.WithTimeout(ctx, time.Minute) timeed, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel() defer cancel()
@@ -182,9 +182,7 @@ func getHashFilesFunction(ctx context.Context, rc *RunContext) func(v []reflect.
patterns = append(patterns, s) patterns = append(patterns, s)
} }
env := map[string]string{} env := map[string]string{}
for k, v := range rc.Env { maps.Copy(env, rc.Env)
env[k] = v
}
env["patterns"] = strings.Join(patterns, "\n") env["patterns"] = strings.Join(patterns, "\n")
if followSymlink { if followSymlink {
env["followSymbolicLinks"] = "true" env["followSymbolicLinks"] = "true"
@@ -222,7 +220,7 @@ type expressionEvaluator struct {
interpreter exprparser.Interpreter interpreter exprparser.Interpreter
} }
func (ee expressionEvaluator) evaluate(ctx context.Context, in string, defaultStatusCheck exprparser.DefaultStatusCheck) (interface{}, error) { func (ee expressionEvaluator) evaluate(ctx context.Context, in string, defaultStatusCheck exprparser.DefaultStatusCheck) (any, error) {
logger := common.Logger(ctx) logger := common.Logger(ctx)
logger.Debugf("evaluating expression '%s'", in) logger.Debugf("evaluating expression '%s'", in)
evaluated, err := ee.interpreter.Evaluate(in, defaultStatusCheck) evaluated, err := ee.interpreter.Evaluate(in, defaultStatusCheck)
@@ -403,7 +401,7 @@ func escapeFormatString(in string) string {
return strings.ReplaceAll(strings.ReplaceAll(in, "{", "{{"), "}", "}}") return strings.ReplaceAll(strings.ReplaceAll(in, "{", "{{"), "}", "}}")
} }
//nolint:gocyclo //nolint:gocyclo // function handles many cases
func rewriteSubExpression(ctx context.Context, in string, forceFormat bool) (string, error) { func rewriteSubExpression(ctx context.Context, in string, forceFormat bool) (string, error) {
if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") { if !strings.Contains(in, "${{") || !strings.Contains(in, "}}") {
return in, nil return in, nil
@@ -414,7 +412,7 @@ func rewriteSubExpression(ctx context.Context, in string, forceFormat bool) (str
exprStart := -1 exprStart := -1
strStart := -1 strStart := -1
var results []string var results []string
formatOut := "" var formatOut strings.Builder
for pos < len(in) { for pos < len(in) {
if strStart > -1 { if strStart > -1 {
matches := strPattern.FindStringIndex(in[pos:]) matches := strPattern.FindStringIndex(in[pos:])
@@ -437,7 +435,7 @@ func rewriteSubExpression(ctx context.Context, in string, forceFormat bool) (str
} }
if exprEnd > -1 { if exprEnd > -1 {
formatOut += fmt.Sprintf("{%d}", len(results)) fmt.Fprintf(&formatOut, "{%d}", len(results))
results = append(results, strings.TrimSpace(in[exprStart:pos+exprEnd])) results = append(results, strings.TrimSpace(in[exprStart:pos+exprEnd]))
pos += exprEnd + 2 pos += exprEnd + 2
exprStart = -1 exprStart = -1
@@ -449,30 +447,30 @@ func rewriteSubExpression(ctx context.Context, in string, forceFormat bool) (str
} else { } else {
exprStart = strings.Index(in[pos:], "${{") exprStart = strings.Index(in[pos:], "${{")
if exprStart != -1 { if exprStart != -1 {
formatOut += escapeFormatString(in[pos : pos+exprStart]) formatOut.WriteString(escapeFormatString(in[pos : pos+exprStart]))
exprStart = pos + exprStart + 3 exprStart = pos + exprStart + 3
pos = exprStart pos = exprStart
} else { } else {
formatOut += escapeFormatString(in[pos:]) formatOut.WriteString(escapeFormatString(in[pos:]))
pos = len(in) pos = len(in)
} }
} }
} }
if len(results) == 1 && formatOut == "{0}" && !forceFormat { if len(results) == 1 && formatOut.String() == "{0}" && !forceFormat {
return in, nil return in, nil
} }
out := fmt.Sprintf("format('%s', %s)", strings.ReplaceAll(formatOut, "'", "''"), strings.Join(results, ", ")) out := fmt.Sprintf("format('%s', %s)", strings.ReplaceAll(formatOut.String(), "'", "''"), strings.Join(results, ", "))
if in != out { if in != out {
common.Logger(ctx).Debugf("expression '%s' rewritten to '%s'", in, out) common.Logger(ctx).Debugf("expression '%s' rewritten to '%s'", in, out)
} }
return out, nil return out, nil
} }
//nolint:gocyclo //nolint:gocyclo // function handles many cases
func getEvaluatorInputs(ctx context.Context, rc *RunContext, step step, ghc *model.GithubContext) map[string]interface{} { func getEvaluatorInputs(ctx context.Context, rc *RunContext, step step, ghc *model.GithubContext) map[string]any {
inputs := map[string]interface{}{} inputs := map[string]any{}
setupWorkflowInputs(ctx, &inputs, rc) setupWorkflowInputs(ctx, &inputs, rc)
@@ -484,8 +482,8 @@ func getEvaluatorInputs(ctx context.Context, rc *RunContext, step step, ghc *mod
} }
for k, v := range env { for k, v := range env {
if strings.HasPrefix(k, "INPUT_") { if after, ok := strings.CutPrefix(k, "INPUT_"); ok {
inputs[strings.ToLower(strings.TrimPrefix(k, "INPUT_"))] = v inputs[strings.ToLower(after)] = v
} }
} }
@@ -525,7 +523,7 @@ func getEvaluatorInputs(ctx context.Context, rc *RunContext, step step, ghc *mod
return inputs return inputs
} }
func setupWorkflowInputs(ctx context.Context, inputs *map[string]interface{}, rc *RunContext) { func setupWorkflowInputs(ctx context.Context, inputs *map[string]any, rc *RunContext) {
if rc.caller != nil { if rc.caller != nil {
config := rc.Run.Workflow.WorkflowCallConfig() config := rc.Run.Workflow.WorkflowCallConfig()

View File

@@ -6,17 +6,19 @@ import (
"os" "os"
"regexp" "regexp"
"sort" "sort"
"strings"
"testing" "testing"
"github.com/nektos/act/pkg/exprparser" "github.com/nektos/act/pkg/exprparser"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
assert "github.com/stretchr/testify/assert" assert "github.com/stretchr/testify/assert"
yaml "go.yaml.in/yaml/v4" yaml "go.yaml.in/yaml/v4"
) )
func createRunContext(t *testing.T) *RunContext { func createRunContext(t *testing.T) *RunContext {
var yml yaml.Node var yml yaml.Node
err := yml.Encode(map[string][]interface{}{ err := yml.Encode(map[string][]any{
"os": {"Linux", "Windows"}, "os": {"Linux", "Windows"},
"foo": {"bar", "baz"}, "foo": {"bar", "baz"},
}) })
@@ -48,7 +50,7 @@ func createRunContext(t *testing.T) *RunContext {
}, },
}, },
}, },
Matrix: map[string]interface{}{ Matrix: map[string]any{
"os": "Linux", "os": "Linux",
"foo": "bar", "foo": "bar",
}, },
@@ -84,7 +86,7 @@ func TestEvaluateRunContext(t *testing.T) {
tables := []struct { tables := []struct {
in string in string
out interface{} out any
errMesg string errMesg string
}{ }{
{" 1 ", 1, ""}, {" 1 ", 1, ""},
@@ -138,7 +140,6 @@ func TestEvaluateRunContext(t *testing.T) {
} }
for _, table := range tables { for _, table := range tables {
table := table
t.Run(table.in, func(t *testing.T) { t.Run(table.in, func(t *testing.T) {
assertObject := assert.New(t) assertObject := assert.New(t)
out, err := ee.evaluate(context.Background(), table.in, exprparser.DefaultStatusCheckNone) out, err := ee.evaluate(context.Background(), table.in, exprparser.DefaultStatusCheckNone)
@@ -163,7 +164,7 @@ func TestEvaluateStep(t *testing.T) {
tables := []struct { tables := []struct {
in string in string
out interface{} out any
errMesg string errMesg string
}{ }{
{"steps.idwithnothing.conclusion", model.StepStatusSuccess.String(), ""}, {"steps.idwithnothing.conclusion", model.StepStatusSuccess.String(), ""},
@@ -178,7 +179,6 @@ func TestEvaluateStep(t *testing.T) {
} }
for _, table := range tables { for _, table := range tables {
table := table
t.Run(table.in, func(t *testing.T) { t.Run(table.in, func(t *testing.T) {
assertObject := assert.New(t) assertObject := assert.New(t)
out, err := ee.evaluate(context.Background(), table.in, exprparser.DefaultStatusCheckNone) out, err := ee.evaluate(context.Background(), table.in, exprparser.DefaultStatusCheckNone)
@@ -262,7 +262,6 @@ func TestInterpolate(t *testing.T) {
updateTestExpressionWorkflow(t, tables, rc) updateTestExpressionWorkflow(t, tables, rc)
for _, table := range tables { for _, table := range tables {
table := table
t.Run("interpolate", func(t *testing.T) { t.Run("interpolate", func(t *testing.T) {
assertObject := assert.New(t) assertObject := assert.New(t)
out := ee.Interpolate(context.Background(), table.in) out := ee.Interpolate(context.Background(), table.in)
@@ -274,19 +273,21 @@ func TestInterpolate(t *testing.T) {
func updateTestExpressionWorkflow(t *testing.T, tables []struct { func updateTestExpressionWorkflow(t *testing.T, tables []struct {
in string in string
out string out string
}, rc *RunContext) { }, rc *RunContext,
var envs string ) {
var envs strings.Builder
keys := make([]string, 0, len(rc.Env)) keys := make([]string, 0, len(rc.Env))
for k := range rc.Env { for k := range rc.Env {
keys = append(keys, k) keys = append(keys, k)
} }
sort.Strings(keys) sort.Strings(keys)
for _, k := range keys { for _, k := range keys {
envs += fmt.Sprintf(" %s: %s\n", k, rc.Env[k]) envs.WriteString(fmt.Sprintf(" %s: %s\n", k, rc.Env[k]))
} }
// editorconfig-checker-disable // editorconfig-checker-disable
workflow := fmt.Sprintf(` var workflow strings.Builder
workflow.WriteString(fmt.Sprintf(`
name: "Test how expressions are handled on GitHub" name: "Test how expressions are handled on GitHub"
on: push on: push
@@ -297,7 +298,7 @@ jobs:
test-espressions: test-espressions:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
`, envs) `, envs.String()))
// editorconfig-checker-enable // editorconfig-checker-enable
for _, table := range tables { for _, table := range tables {
expressionPattern := regexp.MustCompile(`\${{\s*(.+?)\s*}}`) expressionPattern := regexp.MustCompile(`\${{\s*(.+?)\s*}}`)
@@ -307,7 +308,7 @@ jobs:
}) })
name := fmt.Sprintf(`%s -> %s should be equal to %s`, expr, table.in, table.out) name := fmt.Sprintf(`%s -> %s should be equal to %s`, expr, table.in, table.out)
echo := `run: echo "Done "` echo := `run: echo "Done "`
workflow += fmt.Sprintf("\n - name: %s\n %s\n", name, echo) workflow.WriteString(fmt.Sprintf("\n - name: %s\n %s\n", name, echo))
} }
file, err := os.Create("../../.github/workflows/test-expressions.yml") file, err := os.Create("../../.github/workflows/test-expressions.yml")
@@ -315,7 +316,7 @@ jobs:
t.Fatal(err) t.Fatal(err)
} }
_, err = file.WriteString(workflow) _, err = file.WriteString(workflow.String())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -3,6 +3,7 @@ package runner
import ( import (
"context" "context"
"fmt" "fmt"
"strconv"
"time" "time"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
@@ -10,7 +11,7 @@ import (
) )
type jobInfo interface { type jobInfo interface {
matrix() map[string]interface{} matrix() map[string]any
steps() []*model.Step steps() []*model.Step
startContainer() common.Executor startContainer() common.Executor
stopContainer() common.Executor stopContainer() common.Executor
@@ -19,7 +20,7 @@ type jobInfo interface {
result(result string) result(result string)
} }
//nolint:contextcheck,gocyclo //nolint:contextcheck,gocyclo // composes many step executors
func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executor { func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executor {
steps := make([]common.Executor, 0) steps := make([]common.Executor, 0)
preSteps := make([]common.Executor, 0) preSteps := make([]common.Executor, 0)
@@ -54,19 +55,17 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
}) })
for i, stepModel := range infoSteps { for i, stepModel := range infoSteps {
stepModel := stepModel
if stepModel == nil { if stepModel == nil {
return func(ctx context.Context) error { return func(ctx context.Context) error {
return fmt.Errorf("invalid Step %v: missing run or uses key", i) return fmt.Errorf("invalid Step %v: missing run or uses key", i)
} }
} }
if stepModel.ID == "" { if stepModel.ID == "" {
stepModel.ID = fmt.Sprintf("%d", i) stepModel.ID = strconv.Itoa(i)
} }
stepModel.Number = i stepModel.Number = i
step, err := sf.newStep(stepModel, rc) step, err := sf.newStep(stepModel, rc)
if err != nil { if err != nil {
return common.NewErrorExecutor(err) return common.NewErrorExecutor(err)
} }
@@ -154,7 +153,7 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
pipeline = append(pipeline, steps...) pipeline = append(pipeline, steps...)
return common.NewPipelineExecutor(info.startContainer(), common.NewPipelineExecutor(pipeline...). return common.NewPipelineExecutor(info.startContainer(), common.NewPipelineExecutor(pipeline...).
Finally(func(ctx context.Context) error { //nolint:contextcheck Finally(func(ctx context.Context) error { //nolint:contextcheck // intentionally detaches from canceled parent
var cancel context.CancelFunc var cancel context.CancelFunc
if ctx.Err() == context.Canceled { if ctx.Err() == context.Canceled {
// in case of an aborted run, we still should execute the // in case of an aborted run, we still should execute the

View File

@@ -2,13 +2,16 @@ package runner
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"slices"
"testing" "testing"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container" "github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )
@@ -38,9 +41,9 @@ type jobInfoMock struct {
mock.Mock mock.Mock
} }
func (jim *jobInfoMock) matrix() map[string]interface{} { func (jim *jobInfoMock) matrix() map[string]any {
args := jim.Called() args := jim.Called()
return args.Get(0).(map[string]interface{}) return args.Get(0).(map[string]any)
} }
func (jim *jobInfoMock) steps() []*model.Step { func (jim *jobInfoMock) steps() []*model.Step {
@@ -232,12 +235,7 @@ func TestNewJobExecutor(t *testing.T) {
} }
contains := func(needle string, haystack []string) bool { contains := func(needle string, haystack []string) bool {
for _, item := range haystack { return slices.Contains(haystack, needle)
if item == needle {
return true
}
}
return false
} }
for _, tt := range table { for _, tt := range table {
@@ -272,9 +270,6 @@ func TestNewJobExecutor(t *testing.T) {
} }
for i, stepModel := range tt.steps { for i, stepModel := range tt.steps {
i := i
stepModel := stepModel
sm := &stepMock{} sm := &stepMock{}
sfm.On("newStep", stepModel, rc).Return(sm, nil) sfm.On("newStep", stepModel, rc).Return(sm, nil)
@@ -289,7 +284,7 @@ func TestNewJobExecutor(t *testing.T) {
sm.On("main").Return(func(ctx context.Context) error { sm.On("main").Return(func(ctx context.Context) error {
executorOrder = append(executorOrder, "step"+stepModel.ID) executorOrder = append(executorOrder, "step"+stepModel.ID)
if tt.hasError { if tt.hasError {
return fmt.Errorf("error") return errors.New("error")
} }
return nil return nil
}) })
@@ -305,7 +300,7 @@ func TestNewJobExecutor(t *testing.T) {
} }
if len(tt.steps) > 0 { if len(tt.steps) > 0 {
jim.On("matrix").Return(map[string]interface{}{}) jim.On("matrix").Return(map[string]any{})
jim.On("interpolateOutputs").Return(func(ctx context.Context) error { jim.On("interpolateOutputs").Return(func(ctx context.Context) error {
executorOrder = append(executorOrder, "interpolateOutputs") executorOrder = append(executorOrder, "interpolateOutputs")

View File

@@ -26,9 +26,11 @@ const (
gray = 37 gray = 37
) )
var colors []int var (
var nextColor int colors []int
var mux sync.Mutex nextColor int
mux sync.Mutex
)
func init() { func init() {
nextColor = 0 nextColor = 0
@@ -70,7 +72,7 @@ func WithJobLoggerFactory(ctx context.Context, factory JobLoggerFactory) context
} }
// WithJobLogger attaches a new logger to context that is aware of steps // WithJobLogger attaches a new logger to context that is aware of steps
func WithJobLogger(ctx context.Context, jobID string, jobName string, config *Config, masks *[]string, matrix map[string]interface{}) context.Context { func WithJobLogger(ctx context.Context, jobID, jobName string, config *Config, masks *[]string, matrix map[string]any) context.Context {
ctx = WithMasks(ctx, masks) ctx = WithMasks(ctx, masks)
var logger *logrus.Logger var logger *logrus.Logger

View File

@@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.yaml.in/yaml/v4" "go.yaml.in/yaml/v4"
) )
@@ -38,7 +39,7 @@ func TestMaxParallelStrategy(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
matrix := map[string][]interface{}{ matrix := map[string][]any{
"version": {1, 2, 3, 4, 5}, "version": {1, 2, 3, 4, 5},
} }

View File

@@ -94,7 +94,7 @@ func newActionCacheReusableWorkflowExecutor(rc *RunContext, filename string, rem
if err != nil { if err != nil {
return err return err
} }
archive, err := rc.Config.ActionCache.GetTarArchive(ctx, filename, sha, fmt.Sprintf(".github/workflows/%s", remoteReusableWorkflow.Filename)) archive, err := rc.Config.ActionCache.GetTarArchive(ctx, filename, sha, ".github/workflows/"+remoteReusableWorkflow.Filename)
if err != nil { if err != nil {
return err return err
} }
@@ -121,9 +121,7 @@ func newActionCacheReusableWorkflowExecutor(rc *RunContext, filename string, rem
} }
} }
var ( var executorLock sync.Mutex
executorLock sync.Mutex
)
func newMutexExecutor(executor common.Executor) common.Executor { func newMutexExecutor(executor common.Executor) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
@@ -160,7 +158,7 @@ func cloneIfRequired(rc *RunContext, remoteReusableWorkflow remoteReusableWorkfl
) )
} }
func newReusableWorkflowExecutor(rc *RunContext, directory string, workflow string) common.Executor { func newReusableWorkflowExecutor(rc *RunContext, directory, workflow string) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
planner, err := model.NewWorkflowPlanner(path.Join(directory, workflow), true) planner, err := model.NewWorkflowPlanner(path.Join(directory, workflow), true)
if err != nil { if err != nil {
@@ -260,24 +258,6 @@ func newRemoteReusableWorkflowFromAbsoluteURL(uses string) *remoteReusableWorkfl
} }
} }
// deprecated: use newRemoteReusableWorkflowWithPlat
func newRemoteReusableWorkflow(uses string) *remoteReusableWorkflow {
// GitHub docs:
// https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_iduses
r := regexp.MustCompile(`^([^/]+)/([^/]+)/.github/workflows/([^@]+)@(.*)$`)
matches := r.FindStringSubmatch(uses)
if len(matches) != 5 {
return nil
}
return &remoteReusableWorkflow{
Org: matches[1],
Repo: matches[2],
Filename: matches[3],
Ref: matches[4],
URL: "https://github.com",
}
}
// For Gitea // For Gitea
func setReusedWorkflowCallerResult(rc *RunContext, runner Runner) common.Executor { func setReusedWorkflowCallerResult(rc *RunContext, runner Runner) common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {

View File

@@ -11,6 +11,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
maps0 "maps"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@@ -18,20 +19,20 @@ import (
"strings" "strings"
"time" "time"
"github.com/docker/go-connections/nat"
"github.com/opencontainers/selinux/go-selinux"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container" "github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/exprparser" "github.com/nektos/act/pkg/exprparser"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/docker/go-connections/nat"
"github.com/opencontainers/selinux/go-selinux"
) )
// RunContext contains info about current job // RunContext contains info about current job
type RunContext struct { type RunContext struct {
Name string Name string
Config *Config Config *Config
Matrix map[string]interface{} Matrix map[string]any
Run *model.Run Run *model.Run
EventJSON string EventJSON string
Env map[string]string Env map[string]string
@@ -100,19 +101,6 @@ func (rc *RunContext) jobContainerName() string {
return createSimpleContainerName(nameParts...) // For Gitea return createSimpleContainerName(nameParts...) // For Gitea
} }
// Deprecated: use `networkNameForGitea`
// networkName return the name of the network which will be created by `act` automatically for job,
// only create network if using a service container
func (rc *RunContext) networkName() (string, bool) {
if len(rc.Run.Job().Services) > 0 {
return fmt.Sprintf("%s-%s-network", rc.jobContainerName(), rc.Run.JobID), true
}
if rc.Config.ContainerNetworkMode == "" {
return "host", false
}
return string(rc.Config.ContainerNetworkMode), false
}
// networkNameForGitea return the name of the network // networkNameForGitea return the name of the network
func (rc *RunContext) networkNameForGitea() (string, bool) { func (rc *RunContext) networkNameForGitea() (string, bool) {
if rc.Config.ContainerNetworkMode != "" { if rc.Config.ContainerNetworkMode != "" {
@@ -122,13 +110,13 @@ func (rc *RunContext) networkNameForGitea() (string, bool) {
} }
func getDockerDaemonSocketMountPath(daemonPath string) string { func getDockerDaemonSocketMountPath(daemonPath string) string {
if protoIndex := strings.Index(daemonPath, "://"); protoIndex != -1 { if before, after, ok := strings.Cut(daemonPath, "://"); ok {
scheme := daemonPath[:protoIndex] scheme := before
if strings.EqualFold(scheme, "npipe") { if strings.EqualFold(scheme, "npipe") {
// linux container mount on windows, use the default socket path of the VM / wsl2 // linux container mount on windows, use the default socket path of the VM / wsl2
return "/var/run/docker.sock" return "/var/run/docker.sock"
} else if strings.EqualFold(scheme, "unix") { } else if strings.EqualFold(scheme, "unix") {
return daemonPath[protoIndex+3:] return after
} else if strings.IndexFunc(scheme, func(r rune) bool { } else if strings.IndexFunc(scheme, func(r rune) bool {
return (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') return (r < 'a' || r > 'z') && (r < 'A' || r > 'Z')
}) == -1 { }) == -1 {
@@ -242,7 +230,7 @@ func (rc *RunContext) startHostEnvironment() common.Executor {
rc.cleanUpJobContainer = rc.JobContainer.Remove() rc.cleanUpJobContainer = rc.JobContainer.Remove()
for k, v := range rc.JobContainer.GetRunnerContext(ctx) { for k, v := range rc.JobContainer.GetRunnerContext(ctx) {
if v, ok := v.(string); ok { if v, ok := v.(string); ok {
rc.Env[fmt.Sprintf("RUNNER_%s", strings.ToUpper(k))] = v rc.Env["RUNNER_"+strings.ToUpper(k)] = v
} }
} }
for _, env := range os.Environ() { for _, env := range os.Environ() {
@@ -268,7 +256,7 @@ func (rc *RunContext) startHostEnvironment() common.Executor {
} }
} }
//nolint:gocyclo //nolint:gocyclo // function handles many cases
func (rc *RunContext) startJobContainer() common.Executor { func (rc *RunContext) startJobContainer() common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
logger := common.Logger(ctx) logger := common.Logger(ctx)
@@ -404,15 +392,8 @@ func (rc *RunContext) startJobContainer() common.Executor {
return nil return nil
} }
jobContainerNetwork := rc.Config.ContainerNetworkMode.NetworkName()
if rc.containerImage(ctx) != "" {
jobContainerNetwork = networkName
} else if jobContainerNetwork == "" {
jobContainerNetwork = "host"
}
// For Gitea, `jobContainerNetwork` should be the same as `networkName` // For Gitea, `jobContainerNetwork` should be the same as `networkName`
jobContainerNetwork = networkName jobContainerNetwork := networkName
rc.JobContainer = container.NewContainer(&container.NewContainerInput{ rc.JobContainer = container.NewContainer(&container.NewContainerInput{
Cmd: nil, Cmd: nil,
@@ -468,7 +449,7 @@ func (rc *RunContext) execJobContainer(cmd []string, env map[string]string, user
} }
func (rc *RunContext) ApplyExtraPath(ctx context.Context, env *map[string]string) { func (rc *RunContext) ApplyExtraPath(ctx context.Context, env *map[string]string) {
if rc.ExtraPath != nil && len(rc.ExtraPath) > 0 { if len(rc.ExtraPath) > 0 {
path := rc.JobContainer.GetPathVariableName() path := rc.JobContainer.GetPathVariableName()
if rc.JobContainer.IsEnvironmentCaseInsensitive() { if rc.JobContainer.IsEnvironmentCaseInsensitive() {
// On windows system Path and PATH could also be in the map // On windows system Path and PATH could also be in the map
@@ -627,7 +608,7 @@ func (rc *RunContext) closeContainer() common.Executor {
} }
} }
func (rc *RunContext) matrix() map[string]interface{} { func (rc *RunContext) matrix() map[string]any {
return rc.Matrix return rc.Matrix
} }
@@ -642,7 +623,7 @@ func (rc *RunContext) steps() []*model.Step {
// Executor returns a pipeline executor for all the steps in the job // Executor returns a pipeline executor for all the steps in the job
func (rc *RunContext) Executor() (common.Executor, error) { func (rc *RunContext) Executor() (common.Executor, error) {
var executor common.Executor var executor common.Executor
var jobType, err = rc.Run.Job().Type() jobType, err := rc.Run.Job().Type()
switch jobType { switch jobType {
case model.JobTypeDefault: case model.JobTypeDefault:
@@ -779,14 +760,12 @@ func (rc *RunContext) isEnabled(ctx context.Context) (bool, error) {
func mergeMaps(maps ...map[string]string) map[string]string { func mergeMaps(maps ...map[string]string) map[string]string {
rtnMap := make(map[string]string) rtnMap := make(map[string]string)
for _, m := range maps { for _, m := range maps {
for k, v := range m { maps0.Copy(rtnMap, m)
rtnMap[k] = v
}
} }
return rtnMap return rtnMap
} }
// deprecated: use createSimpleContainerName // Deprecated: use createSimpleContainerName
func createContainerName(parts ...string) string { func createContainerName(parts ...string) string {
name := strings.Join(parts, "-") name := strings.Join(parts, "-")
pattern := regexp.MustCompile("[^a-zA-Z0-9]") pattern := regexp.MustCompile("[^a-zA-Z0-9]")
@@ -843,10 +822,11 @@ func (rc *RunContext) getStepsContext() map[string]*model.StepResult {
return rc.StepResults return rc.StepResults
} }
//nolint:gocyclo // function handles many cases
func (rc *RunContext) getGithubContext(ctx context.Context) *model.GithubContext { func (rc *RunContext) getGithubContext(ctx context.Context) *model.GithubContext {
logger := common.Logger(ctx) logger := common.Logger(ctx)
ghc := &model.GithubContext{ ghc := &model.GithubContext{
Event: make(map[string]interface{}), Event: make(map[string]any),
Workflow: rc.Run.Workflow.Name, Workflow: rc.Run.Workflow.Name,
RunID: rc.Config.Env["GITHUB_RUN_ID"], RunID: rc.Config.Env["GITHUB_RUN_ID"],
RunNumber: rc.Config.Env["GITHUB_RUN_NUMBER"], RunNumber: rc.Config.Env["GITHUB_RUN_NUMBER"],
@@ -954,7 +934,7 @@ func (rc *RunContext) getGithubContext(ctx context.Context) *model.GithubContext
ghc.GraphQLURL = "https://api.github.com/graphql" ghc.GraphQLURL = "https://api.github.com/graphql"
// per GHES // per GHES
if rc.Config.GitHubInstance != "github.com" { if rc.Config.GitHubInstance != "github.com" {
ghc.ServerURL = fmt.Sprintf("https://%s", rc.Config.GitHubInstance) ghc.ServerURL = "https://" + rc.Config.GitHubInstance
ghc.APIURL = fmt.Sprintf("https://%s/api/v3", rc.Config.GitHubInstance) ghc.APIURL = fmt.Sprintf("https://%s/api/v3", rc.Config.GitHubInstance)
ghc.GraphQLURL = fmt.Sprintf("https://%s/api/graphql", rc.Config.GitHubInstance) ghc.GraphQLURL = fmt.Sprintf("https://%s/api/graphql", rc.Config.GitHubInstance)
} }
@@ -1010,7 +990,7 @@ func isLocalCheckout(ghc *model.GithubContext, step *model.Step) bool {
return true return true
} }
func nestedMapLookup(m map[string]interface{}, ks ...string) (rval interface{}) { func nestedMapLookup(m map[string]any, ks ...string) (rval any) {
var ok bool var ok bool
if len(ks) == 0 { // degenerate input if len(ks) == 0 { // degenerate input
@@ -1020,7 +1000,7 @@ func nestedMapLookup(m map[string]interface{}, ks ...string) (rval interface{})
return nil return nil
} else if len(ks) == 1 { // we've reached the final key } else if len(ks) == 1 { // we've reached the final key
return rval return rval
} else if m, ok = rval.(map[string]interface{}); !ok { } else if m, ok = rval.(map[string]any); !ok {
return nil return nil
} else { // 1+ more keys } else { // 1+ more keys
return nestedMapLookup(m, ks[1:]...) return nestedMapLookup(m, ks[1:]...)
@@ -1113,22 +1093,22 @@ func (rc *RunContext) handleCredentials(ctx context.Context) (string, string, er
} }
if container.Credentials != nil && len(container.Credentials) != 2 { if container.Credentials != nil && len(container.Credentials) != 2 {
err := fmt.Errorf("invalid property count for key 'credentials:'") err := errors.New("invalid property count for key 'credentials:'")
return "", "", err return "", "", err
} }
ee := rc.NewExpressionEvaluator(ctx) ee := rc.NewExpressionEvaluator(ctx)
if username = ee.Interpolate(ctx, container.Credentials["username"]); username == "" { if username = ee.Interpolate(ctx, container.Credentials["username"]); username == "" {
err := fmt.Errorf("failed to interpolate container.credentials.username") err := errors.New("failed to interpolate container.credentials.username")
return "", "", err return "", "", err
} }
if password = ee.Interpolate(ctx, container.Credentials["password"]); password == "" { if password = ee.Interpolate(ctx, container.Credentials["password"]); password == "" {
err := fmt.Errorf("failed to interpolate container.credentials.password") err := errors.New("failed to interpolate container.credentials.password")
return "", "", err return "", "", err
} }
if container.Credentials["username"] == "" || container.Credentials["password"] == "" { if container.Credentials["username"] == "" || container.Credentials["password"] == "" {
err := fmt.Errorf("container.credentials cannot be empty") err := errors.New("container.credentials cannot be empty")
return "", "", err return "", "", err
} }
@@ -1137,25 +1117,25 @@ func (rc *RunContext) handleCredentials(ctx context.Context) (string, string, er
func (rc *RunContext) handleServiceCredentials(ctx context.Context, creds map[string]string) (username, password string, err error) { func (rc *RunContext) handleServiceCredentials(ctx context.Context, creds map[string]string) (username, password string, err error) {
if creds == nil { if creds == nil {
return return username, password, err
} }
if len(creds) != 2 { if len(creds) != 2 {
err = fmt.Errorf("invalid property count for key 'credentials:'") err = errors.New("invalid property count for key 'credentials:'")
return return username, password, err
} }
ee := rc.NewExpressionEvaluator(ctx) ee := rc.NewExpressionEvaluator(ctx)
if username = ee.Interpolate(ctx, creds["username"]); username == "" { if username = ee.Interpolate(ctx, creds["username"]); username == "" {
err = fmt.Errorf("failed to interpolate credentials.username") err = errors.New("failed to interpolate credentials.username")
return return username, password, err
} }
if password = ee.Interpolate(ctx, creds["password"]); password == "" { if password = ee.Interpolate(ctx, creds["password"]); password == "" {
err = fmt.Errorf("failed to interpolate credentials.password") err = errors.New("failed to interpolate credentials.password")
return return username, password, err
} }
return return username, password, err
} }
// GetServiceBindsAndMounts returns the binds and mounts for the service container, resolving paths as appopriate // GetServiceBindsAndMounts returns the binds and mounts for the service container, resolving paths as appopriate

View File

@@ -20,7 +20,7 @@ import (
func TestRunContext_EvalBool(t *testing.T) { func TestRunContext_EvalBool(t *testing.T) {
var yml yaml.Node var yml yaml.Node
err := yml.Encode(map[string][]interface{}{ err := yml.Encode(map[string][]any{
"os": {"Linux", "Windows"}, "os": {"Linux", "Windows"},
"foo": {"bar", "baz"}, "foo": {"bar", "baz"},
}) })
@@ -48,7 +48,7 @@ func TestRunContext_EvalBool(t *testing.T) {
}, },
}, },
}, },
Matrix: map[string]interface{}{ Matrix: map[string]any{
"os": "Linux", "os": "Linux",
"foo": "bar", "foo": "bar",
}, },
@@ -154,7 +154,6 @@ func TestRunContext_EvalBool(t *testing.T) {
updateTestIfWorkflow(t, tables, rc) updateTestIfWorkflow(t, tables, rc)
for _, table := range tables { for _, table := range tables {
table := table
t.Run(table.in, func(t *testing.T) { t.Run(table.in, func(t *testing.T) {
assertObject := assert.New(t) assertObject := assert.New(t)
b, err := EvalBool(context.Background(), rc.ExprEval, table.in, exprparser.DefaultStatusCheckSuccess) b, err := EvalBool(context.Background(), rc.ExprEval, table.in, exprparser.DefaultStatusCheckSuccess)
@@ -171,18 +170,20 @@ func updateTestIfWorkflow(t *testing.T, tables []struct {
in string in string
out bool out bool
wantErr bool wantErr bool
}, rc *RunContext) { }, rc *RunContext,
var envs string ) {
var envs strings.Builder
keys := make([]string, 0, len(rc.Env)) keys := make([]string, 0, len(rc.Env))
for k := range rc.Env { for k := range rc.Env {
keys = append(keys, k) keys = append(keys, k)
} }
sort.Strings(keys) sort.Strings(keys)
for _, k := range keys { for _, k := range keys {
envs += fmt.Sprintf(" %s: %s\n", k, rc.Env[k]) envs.WriteString(fmt.Sprintf(" %s: %s\n", k, rc.Env[k]))
} }
// editorconfig-checker-disable // editorconfig-checker-disable
workflow := fmt.Sprintf(` var workflow strings.Builder
workflow.WriteString(fmt.Sprintf(`
name: "Test what expressions result in true and false on GitHub" name: "Test what expressions result in true and false on GitHub"
on: push on: push
@@ -193,7 +194,7 @@ jobs:
test-ifs-and-buts: test-ifs-and-buts:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
`, envs) `, envs.String()))
// editorconfig-checker-enable // editorconfig-checker-enable
for i, table := range tables { for i, table := range tables {
@@ -211,9 +212,9 @@ jobs:
echo = `run: echo OK` echo = `run: echo OK`
name = fmt.Sprintf(`"✅ I should run, expr: %s"`, expr) name = fmt.Sprintf(`"✅ I should run, expr: %s"`, expr)
} }
workflow += fmt.Sprintf("\n - name: %s\n id: step%d\n if: %s\n %s\n", name, i, table.in, echo) workflow.WriteString(fmt.Sprintf("\n - name: %s\n id: step%d\n if: %s\n %s\n", name, i, table.in, echo))
if table.out { if table.out {
workflow += fmt.Sprintf("\n - name: \"Double checking expr: %s\"\n if: steps.step%d.conclusion == 'skipped'\n run: echo \"%s should have been true, but wasn't\"\n", expr, i, table.in) workflow.WriteString(fmt.Sprintf("\n - name: \"Double checking expr: %s\"\n if: steps.step%d.conclusion == 'skipped'\n run: echo \"%s should have been true, but wasn't\"\n", expr, i, table.in))
} }
} }
@@ -222,7 +223,7 @@ jobs:
t.Fatal(err) t.Fatal(err)
} }
_, err = file.WriteString(workflow) _, err = file.WriteString(workflow.String())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -259,10 +260,8 @@ func TestRunContext_GetBindsAndMounts(t *testing.T) {
for _, testcase := range tests { for _, testcase := range tests {
// pin for scopelint // pin for scopelint
testcase := testcase
for _, bindWorkDir := range []bool{true, false} { for _, bindWorkDir := range []bool{true, false} {
// pin for scopelint // pin for scopelint
bindWorkDir := bindWorkDir
testBindSuffix := "" testBindSuffix := ""
if bindWorkDir { if bindWorkDir {
testBindSuffix = "Bind" testBindSuffix = "Bind"
@@ -359,7 +358,7 @@ func TestGetGitHubContext(t *testing.T) {
}, },
Name: "GitHubContextTest", Name: "GitHubContextTest",
CurrentStep: "step", CurrentStep: "step",
Matrix: map[string]interface{}{}, Matrix: map[string]any{},
Env: map[string]string{}, Env: map[string]string{},
ExtraPath: []string{}, ExtraPath: []string{},
StepResults: map[string]*model.StepResult{}, StepResults: map[string]*model.StepResult{},
@@ -417,7 +416,6 @@ func TestGetGithubContextRef(t *testing.T) {
} }
for _, data := range table { for _, data := range table {
data := data
t.Run(data.event, func(t *testing.T) { t.Run(data.event, func(t *testing.T) {
rc := &RunContext{ rc := &RunContext{
EventJSON: data.json, EventJSON: data.json,
@@ -461,7 +459,7 @@ func createIfTestRunContext(jobs map[string]*model.Job) *RunContext {
return rc return rc
} }
func createJob(t *testing.T, input string, result string) *model.Job { func createJob(t *testing.T, input, result string) *model.Job {
var job *model.Job var job *model.Job
err := yaml.Unmarshal([]byte(input), &job) err := yaml.Unmarshal([]byte(input), &job)
assert.NoError(t, err) assert.NoError(t, err)

View File

@@ -9,11 +9,11 @@ import (
"sync" "sync"
"time" "time"
docker_container "github.com/docker/docker/api/types/container"
log "github.com/sirupsen/logrus"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
docker_container "github.com/docker/docker/api/types/container"
log "github.com/sirupsen/logrus"
) )
// Runner provides capabilities to run GitHub actions // Runner provides capabilities to run GitHub actions
@@ -133,6 +133,8 @@ func (runner *runnerImpl) configure() (Runner, error) {
} }
// NewPlanExecutor ... // NewPlanExecutor ...
//
//nolint:gocyclo // function handles many cases
func (runner *runnerImpl) NewPlanExecutor(plan *model.Plan) common.Executor { func (runner *runnerImpl) NewPlanExecutor(plan *model.Plan) common.Executor {
maxJobNameLen := 0 maxJobNameLen := 0
@@ -182,7 +184,7 @@ func (runner *runnerImpl) NewPlanExecutor(plan *model.Plan) common.Executor {
} }
} }
var matrixes []map[string]interface{} var matrixes []map[string]any
if m, err := job.GetMatrixes(); err != nil { if m, err := job.GetMatrixes(); err != nil {
log.Errorf("Error while get job's matrix: %v", err) log.Errorf("Error while get job's matrix: %v", err)
} else { } else {
@@ -210,7 +212,6 @@ func (runner *runnerImpl) NewPlanExecutor(plan *model.Plan) common.Executor {
log.Infof("Running job with maxParallel=%d for %d matrix combinations", maxParallel, len(matrixes)) log.Infof("Running job with maxParallel=%d for %d matrix combinations", maxParallel, len(matrixes))
for i, matrix := range matrixes { for i, matrix := range matrixes {
matrix := matrix
rc := runner.newRunContext(ctx, run, matrix) rc := runner.newRunContext(ctx, run, matrix)
rc.JobName = rc.Name rc.JobName = rc.Name
if len(matrixes) > 1 { if len(matrixes) > 1 {
@@ -225,7 +226,6 @@ func (runner *runnerImpl) NewPlanExecutor(plan *model.Plan) common.Executor {
stageExecutor = append(stageExecutor, func(ctx context.Context) error { stageExecutor = append(stageExecutor, func(ctx context.Context) error {
jobName := fmt.Sprintf("%-*s", maxJobNameLen, rc.String()) jobName := fmt.Sprintf("%-*s", maxJobNameLen, rc.String())
executor, err := rc.Executor() executor, err := rc.Executor()
if err != nil { if err != nil {
return err return err
} }
@@ -287,8 +287,8 @@ func handleFailure(plan *model.Plan) common.Executor {
} }
} }
func selectMatrixes(originalMatrixes []map[string]interface{}, targetMatrixValues map[string]map[string]bool) []map[string]interface{} { func selectMatrixes(originalMatrixes []map[string]any, targetMatrixValues map[string]map[string]bool) []map[string]any {
matrixes := make([]map[string]interface{}, 0) matrixes := make([]map[string]any, 0)
for _, original := range originalMatrixes { for _, original := range originalMatrixes {
flag := true flag := true
for key, val := range original { for key, val := range original {
@@ -306,7 +306,7 @@ func selectMatrixes(originalMatrixes []map[string]interface{}, targetMatrixValue
return matrixes return matrixes
} }
func (runner *runnerImpl) newRunContext(ctx context.Context, run *model.Run, matrix map[string]interface{}) *RunContext { func (runner *runnerImpl) newRunContext(ctx context.Context, run *model.Run, matrix map[string]any) *RunContext {
rc := &RunContext{ rc := &RunContext{
Config: runner.config, Config: runner.config,
Run: run, Run: run,
@@ -322,7 +322,7 @@ func (runner *runnerImpl) newRunContext(ctx context.Context, run *model.Run, mat
} }
// For Gitea // For Gitea
func (c *caller) setReusedWorkflowJobResult(jobName string, result string) { func (c *caller) setReusedWorkflowJobResult(jobName, result string) {
c.updateResultLock.Lock() c.updateResultLock.Lock()
defer c.updateResultLock.Unlock() defer c.updateResultLock.Unlock()
c.reusedWorkflowJobResults[jobName] = result c.reusedWorkflowJobResults[jobName] = result

View File

@@ -90,14 +90,12 @@ func TestMaxParallelConcurrencyTracking(t *testing.T) {
var wg sync.WaitGroup var wg sync.WaitGroup
semaphore := make(chan struct{}, 2) // Limit to 2 concurrent semaphore := make(chan struct{}, 2) // Limit to 2 concurrent
for i := 0; i < 6; i++ { for range 6 {
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
semaphore <- struct{}{} // Acquire semaphore <- struct{}{} // Acquire
defer func() { <-semaphore }() // Release defer func() { <-semaphore }() // Release
trackingFunc() trackingFunc()
}() })
} }
wg.Wait() wg.Wait()

View File

@@ -12,13 +12,13 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/model"
"github.com/joho/godotenv" "github.com/joho/godotenv"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
assert "github.com/stretchr/testify/assert" assert "github.com/stretchr/testify/assert"
"go.yaml.in/yaml/v4" "go.yaml.in/yaml/v4"
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/model"
) )
var ( var (
@@ -528,9 +528,9 @@ func (f *maskJobLoggerFactory) WithJobLogger() *log.Logger {
} }
func TestMaskValues(t *testing.T) { func TestMaskValues(t *testing.T) {
assertNoSecret := func(text string, secret string) { assertNoSecret := func(text, secret string) {
index := strings.Index(text, "composite secret") found := strings.Contains(text, "composite secret")
if index > -1 { if found {
fmt.Printf("\nFound Secret in the given text:\n%s\n", text) fmt.Printf("\nFound Secret in the given text:\n%s\n", text)
} }
assert.False(t, strings.Contains(text, "composite secret")) assert.False(t, strings.Contains(text, "composite secret"))
@@ -607,7 +607,7 @@ func TestRunWithService(t *testing.T) {
runner, err := New(runnerConfig) runner, err := New(runnerConfig)
assert.NoError(t, err, workflowPath) assert.NoError(t, err, workflowPath)
planner, err := model.NewWorkflowPlanner(fmt.Sprintf("testdata/%s", workflowPath), true) planner, err := model.NewWorkflowPlanner("testdata/"+workflowPath, true)
assert.NoError(t, err, workflowPath) assert.NoError(t, err, workflowPath)
plan, err := planner.PlanEvent(eventName) plan, err := planner.PlanEvent(eventName)

View File

@@ -3,6 +3,7 @@ package runner
import ( import (
"context" "context"
"fmt" "fmt"
maps0 "maps"
"path" "path"
"strconv" "strconv"
"strings" "strings"
@@ -142,7 +143,7 @@ func runStepExecutor(step step, stage stepStage, executor common.Executor) commo
Mode: 0o666, Mode: 0o666,
}, &container.FileEntry{ }, &container.FileEntry{
Name: envFileCommand, Name: envFileCommand,
Mode: 0666, Mode: 0o666,
}, &container.FileEntry{ }, &container.FileEntry{
Name: summaryFileCommand, Name: summaryFileCommand,
Mode: 0o666, Mode: 0o666,
@@ -295,9 +296,7 @@ func mergeIntoMap(step step, target *map[string]string, maps ...map[string]strin
func mergeIntoMapCaseSensitive(target map[string]string, maps ...map[string]string) { func mergeIntoMapCaseSensitive(target map[string]string, maps ...map[string]string) {
for _, m := range maps { for _, m := range maps {
for k, v := range m { maps0.Copy(target, m)
target[k] = v
}
} }
} }

View File

@@ -46,7 +46,7 @@ func (sal *stepActionLocal) main() common.Executor {
_, cpath := getContainerActionPaths(sal.Step, path.Join(actionDir, ""), sal.RunContext) _, cpath := getContainerActionPaths(sal.Step, path.Join(actionDir, ""), sal.RunContext)
return func(filename string) (io.Reader, io.Closer, error) { return func(filename string) (io.Reader, io.Closer, error) {
spath := path.Join(cpath, filename) spath := path.Join(cpath, filename)
for i := 0; i < maxSymlinkDepth; i++ { for range maxSymlinkDepth {
tars, err := sal.RunContext.JobContainer.GetContainerArchive(ctx, spath) tars, err := sal.RunContext.JobContainer.GetContainerArchive(ctx, spath)
if errors.Is(err, fs.ErrNotExist) { if errors.Is(err, fs.ErrNotExist) {
return nil, nil, err return nil, nil, err

View File

@@ -10,6 +10,7 @@ import (
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"go.yaml.in/yaml/v4" "go.yaml.in/yaml/v4"
@@ -24,7 +25,7 @@ func (salm *stepActionLocalMocks) runAction(step actionStep, actionDir string, r
return args.Get(0).(func(context.Context) error) return args.Get(0).(func(context.Context) error)
} }
func (salm *stepActionLocalMocks) readAction(_ context.Context, step *model.Step, actionDir string, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) { func (salm *stepActionLocalMocks) readAction(_ context.Context, step *model.Step, actionDir, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) {
args := salm.Called(step, actionDir, actionPath, readFile, writeFile) args := salm.Called(step, actionDir, actionPath, readFile, writeFile)
return args.Get(0).(*model.Action), args.Error(1) return args.Get(0).(*model.Action), args.Error(1)
} }
@@ -257,7 +258,7 @@ func TestStepActionLocalPost(t *testing.T) {
sal.RunContext.ExprEval = sal.RunContext.NewExpressionEvaluator(ctx) sal.RunContext.ExprEval = sal.RunContext.NewExpressionEvaluator(ctx)
if tt.mocks.exec { if tt.mocks.exec {
suffixMatcher := func(suffix string) interface{} { suffixMatcher := func(suffix string) any {
return mock.MatchedBy(func(array []string) bool { return mock.MatchedBy(func(array []string) bool {
return strings.HasSuffix(array[1], suffix) return strings.HasSuffix(array[1], suffix)
}) })

View File

@@ -12,11 +12,11 @@ import (
"regexp" "regexp"
"strings" "strings"
gogit "github.com/go-git/go-git/v5"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/common/git" "github.com/nektos/act/pkg/common/git"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
gogit "github.com/go-git/go-git/v5"
) )
type stepActionRemote struct { type stepActionRemote struct {
@@ -35,6 +35,7 @@ type stepActionRemote struct {
var stepActionRemoteNewCloneExecutor = git.NewGitCloneExecutor var stepActionRemoteNewCloneExecutor = git.NewGitCloneExecutor
//nolint:gocyclo // function handles many cases
func (sar *stepActionRemote) prepareActionExecutor() common.Executor { func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
return func(ctx context.Context) error { return func(ctx context.Context) error {
if sar.remoteAction != nil && sar.action != nil { if sar.remoteAction != nil && sar.action != nil {
@@ -80,7 +81,7 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
remoteReader := func(ctx context.Context) actionYamlReader { remoteReader := func(ctx context.Context) actionYamlReader {
return func(filename string) (io.Reader, io.Closer, error) { return func(filename string) (io.Reader, io.Closer, error) {
spath := path.Join(sar.remoteAction.Path, filename) spath := path.Join(sar.remoteAction.Path, filename)
for i := 0; i < maxSymlinkDepth; i++ { for range maxSymlinkDepth {
tars, err := cache.GetTarArchive(ctx, sar.cacheDir, sar.resolvedSha, spath) tars, err := cache.GetTarArchive(ctx, sar.cacheDir, sar.resolvedSha, spath)
if err != nil { if err != nil {
return nil, nil, os.ErrNotExist return nil, nil, os.ErrNotExist
@@ -301,8 +302,8 @@ func (ra *remoteAction) IsCheckout() bool {
func newRemoteAction(action string) *remoteAction { func newRemoteAction(action string) *remoteAction {
// support http(s)://host/owner/repo@v3 // support http(s)://host/owner/repo@v3
for _, schema := range []string{"https://", "http://"} { for _, schema := range []string{"https://", "http://"} {
if strings.HasPrefix(action, schema) { if after, ok := strings.CutPrefix(action, schema); ok {
splits := strings.SplitN(strings.TrimPrefix(action, schema), "/", 2) splits := strings.SplitN(after, "/", 2)
if len(splits) != 2 { if len(splits) != 2 {
return nil return nil
} }

View File

@@ -10,20 +10,20 @@ import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"go.yaml.in/yaml/v4"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/common/git" "github.com/nektos/act/pkg/common/git"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"go.yaml.in/yaml/v4"
) )
type stepActionRemoteMocks struct { type stepActionRemoteMocks struct {
mock.Mock mock.Mock
} }
func (sarm *stepActionRemoteMocks) readAction(_ context.Context, step *model.Step, actionDir string, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) { func (sarm *stepActionRemoteMocks) readAction(_ context.Context, step *model.Step, actionDir, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) {
args := sarm.Called(step, actionDir, actionPath, readFile, writeFile) args := sarm.Called(step, actionDir, actionPath, readFile, writeFile)
return args.Get(0).(*model.Action), args.Error(1) return args.Get(0).(*model.Action), args.Error(1)
} }
@@ -162,7 +162,7 @@ func TestStepActionRemote(t *testing.T) {
} }
sar.RunContext.ExprEval = sar.RunContext.NewExpressionEvaluator(ctx) sar.RunContext.ExprEval = sar.RunContext.NewExpressionEvaluator(ctx)
suffixMatcher := func(suffix string) interface{} { suffixMatcher := func(suffix string) any {
return mock.MatchedBy(func(actionDir string) bool { return mock.MatchedBy(func(actionDir string) bool {
return strings.HasSuffix(actionDir, suffix) return strings.HasSuffix(actionDir, suffix)
}) })
@@ -258,7 +258,7 @@ func TestStepActionRemotePre(t *testing.T) {
readAction: sarm.readAction, readAction: sarm.readAction,
} }
suffixMatcher := func(suffix string) interface{} { suffixMatcher := func(suffix string) any {
return mock.MatchedBy(func(actionDir string) bool { return mock.MatchedBy(func(actionDir string) bool {
return strings.HasSuffix(actionDir, suffix) return strings.HasSuffix(actionDir, suffix)
}) })
@@ -329,7 +329,7 @@ func TestStepActionRemotePreThroughAction(t *testing.T) {
readAction: sarm.readAction, readAction: sarm.readAction,
} }
suffixMatcher := func(suffix string) interface{} { suffixMatcher := func(suffix string) any {
return mock.MatchedBy(func(actionDir string) bool { return mock.MatchedBy(func(actionDir string) bool {
return strings.HasSuffix(actionDir, suffix) return strings.HasSuffix(actionDir, suffix)
}) })
@@ -405,7 +405,7 @@ func TestStepActionRemotePreThroughActionToken(t *testing.T) {
readAction: sarm.readAction, readAction: sarm.readAction,
} }
suffixMatcher := func(suffix string) interface{} { suffixMatcher := func(suffix string) any {
return mock.MatchedBy(func(actionDir string) bool { return mock.MatchedBy(func(actionDir string) bool {
return strings.HasSuffix(actionDir, suffix) return strings.HasSuffix(actionDir, suffix)
}) })

View File

@@ -5,10 +5,11 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/kballard/go-shellquote"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container" "github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/kballard/go-shellquote"
) )
type stepDocker struct { type stepDocker struct {
@@ -85,11 +86,9 @@ func (sd *stepDocker) runUsesContainer() common.Executor {
} }
} }
var ( var ContainerNewContainer = container.NewContainer
ContainerNewContainer = container.NewContainer
)
func (sd *stepDocker) newStepContainer(ctx context.Context, image string, cmd []string, entrypoint []string) container.Container { func (sd *stepDocker) newStepContainer(ctx context.Context, image string, cmd, entrypoint []string) container.Container {
rc := sd.RunContext rc := sd.RunContext
step := sd.Step step := sd.Step
@@ -123,7 +122,7 @@ func (sd *stepDocker) newStepContainer(ctx context.Context, image string, cmd []
Name: createSimpleContainerName(rc.jobContainerName(), "STEP-"+step.ID), Name: createSimpleContainerName(rc.jobContainerName(), "STEP-"+step.ID),
Env: envList, Env: envList,
Mounts: mounts, Mounts: mounts,
NetworkMode: fmt.Sprintf("container:%s", rc.jobContainerName()), NetworkMode: "container:" + rc.jobContainerName(),
Binds: binds, Binds: binds,
Stdout: logWriter, Stdout: logWriter,
Stderr: logWriter, Stderr: logWriter,

View File

@@ -8,6 +8,7 @@ import (
"github.com/nektos/act/pkg/container" "github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )

View File

@@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@@ -3,15 +3,16 @@ package runner
import ( import (
"context" "context"
"fmt" "fmt"
"maps"
"runtime" "runtime"
"strings" "strings"
"github.com/kballard/go-shellquote"
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container" "github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/lookpath" "github.com/nektos/act/pkg/lookpath"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/kballard/go-shellquote"
) )
type stepRun struct { type stepRun struct {
@@ -90,7 +91,7 @@ func getScriptName(rc *RunContext, step *model.Step) string {
for rcs := rc; rcs.Parent != nil; rcs = rcs.Parent { for rcs := rc; rcs.Parent != nil; rcs = rcs.Parent {
scriptName = fmt.Sprintf("%s-composite-%s", rcs.Parent.CurrentStep, scriptName) scriptName = fmt.Sprintf("%s-composite-%s", rcs.Parent.CurrentStep, scriptName)
} }
return fmt.Sprintf("workflow/%s", scriptName) return "workflow/" + scriptName
} }
// TODO: Currently we just ignore top level keys, BUT we should return proper error on them // TODO: Currently we just ignore top level keys, BUT we should return proper error on them
@@ -184,9 +185,7 @@ func (sr *stepRun) setupShell(ctx context.Context) {
} }
step.Shell = shellWithFallback[0] step.Shell = shellWithFallback[0]
lenv := &localEnv{env: map[string]string{}} lenv := &localEnv{env: map[string]string{}}
for k, v := range sr.env { maps.Copy(lenv.env, sr.env)
lenv.env[k] = v
}
sr.getRunContext().ApplyExtraPath(ctx, &lenv.env) sr.getRunContext().ApplyExtraPath(ctx, &lenv.env)
_, err := lookpath.LookPath2(shellWithFallback[0], lenv) _, err := lookpath.LookPath2(shellWithFallback[0], lenv)
if err != nil { if err != nil {
@@ -202,7 +201,7 @@ func (sr *stepRun) setupShell(ctx context.Context) {
func (sr *stepRun) setupWorkingDirectory(ctx context.Context) { func (sr *stepRun) setupWorkingDirectory(ctx context.Context) {
rc := sr.RunContext rc := sr.RunContext
step := sr.Step step := sr.Step
workingdirectory := "" var workingdirectory string
if step.WorkingDirectory == "" { if step.WorkingDirectory == "" {
workingdirectory = rc.Run.Job().Defaults.Run.WorkingDirectory workingdirectory = rc.Run.Job().Defaults.Run.WorkingDirectory

View File

@@ -6,11 +6,11 @@ import (
"io" "io"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/nektos/act/pkg/container" "github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
) )
func TestStepRun(t *testing.T) { func TestStepRun(t *testing.T) {

View File

@@ -6,6 +6,7 @@ import (
"github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"

View File

@@ -3,16 +3,16 @@ package workflowpattern
import "fmt" import "fmt"
type TraceWriter interface { type TraceWriter interface {
Info(string, ...interface{}) Info(string, ...any)
} }
type EmptyTraceWriter struct{} type EmptyTraceWriter struct{}
func (*EmptyTraceWriter) Info(string, ...interface{}) { func (*EmptyTraceWriter) Info(string, ...any) {
} }
type StdOutTraceWriter struct{} type StdOutTraceWriter struct{}
func (*StdOutTraceWriter) Info(format string, args ...interface{}) { func (*StdOutTraceWriter) Info(format string, args ...any) {
fmt.Printf(format+"\n", args...) fmt.Printf(format+"\n", args...)
} }

View File

@@ -34,7 +34,7 @@ func CompilePattern(rawpattern string) (*WorkflowPattern, error) {
}, nil }, nil
} }
//nolint:gocyclo //nolint:gocyclo // function handles many cases
func PatternToRegex(pattern string) (string, error) { func PatternToRegex(pattern string) (string, error) {
var rpattern strings.Builder var rpattern strings.Builder
rpattern.WriteString("^") rpattern.WriteString("^")
@@ -127,7 +127,7 @@ func PatternToRegex(pattern string) (string, error) {
if errorMessage.Len() > 0 { if errorMessage.Len() > 0 {
errorMessage.WriteString(", ") errorMessage.WriteString(", ")
} }
errorMessage.WriteString(fmt.Sprintf("Position: %d Error: %s", position, err)) fmt.Fprintf(&errorMessage, "Position: %d Error: %s", position, err)
} }
return "", fmt.Errorf("invalid Pattern '%s': %s", pattern, errorMessage.String()) return "", fmt.Errorf("invalid Pattern '%s': %s", pattern, errorMessage.String())
} }