feat: Host environment (#1293)
This commit is contained in:
@@ -352,7 +352,7 @@ func newStepContainer(ctx context.Context, step step, image string, cmd []string
|
||||
stepContainer := container.NewContainer(&container.NewContainerInput{
|
||||
Cmd: cmd,
|
||||
Entrypoint: entrypoint,
|
||||
WorkingDir: rc.Config.ContainerWorkdir(),
|
||||
WorkingDir: rc.JobContainer.ToContainerPath(rc.Config.Workdir),
|
||||
Image: image,
|
||||
Username: rc.Config.Secrets["DOCKER_USERNAME"],
|
||||
Password: rc.Config.Secrets["DOCKER_PASSWORD"],
|
||||
@@ -396,11 +396,11 @@ func getContainerActionPaths(step *model.Step, actionDir string, rc *RunContext)
|
||||
containerActionDir := "."
|
||||
if step.Type() != model.StepTypeUsesActionRemote {
|
||||
actionName = getOsSafeRelativePath(actionDir, rc.Config.Workdir)
|
||||
containerActionDir = rc.Config.ContainerWorkdir() + "/" + actionName
|
||||
containerActionDir = rc.JobContainer.ToContainerPath(rc.Config.Workdir) + "/" + actionName
|
||||
actionName = "./" + actionName
|
||||
} else if step.Type() == model.StepTypeUsesActionRemote {
|
||||
actionName = getOsSafeRelativePath(actionDir, rc.ActionCacheDir())
|
||||
containerActionDir = ActPath + "/actions/" + actionName
|
||||
containerActionDir = rc.JobContainer.GetActPath() + "/actions/" + actionName
|
||||
}
|
||||
|
||||
if actionName == "" {
|
||||
|
@@ -11,6 +11,7 @@ import (
|
||||
type containerMock struct {
|
||||
mock.Mock
|
||||
container.Container
|
||||
container.LinuxContainerEnvironmentExtensions
|
||||
}
|
||||
|
||||
func (cm *containerMock) Create(capAdd []string, capDrop []string) common.Executor {
|
||||
|
@@ -7,7 +7,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/nektos/act/pkg/common"
|
||||
"github.com/nektos/act/pkg/container"
|
||||
"github.com/nektos/act/pkg/exprparser"
|
||||
"github.com/nektos/act/pkg/model"
|
||||
"gopkg.in/yaml.v3"
|
||||
@@ -23,20 +22,22 @@ type ExpressionEvaluator interface {
|
||||
// NewExpressionEvaluator creates a new evaluator
|
||||
func (rc *RunContext) NewExpressionEvaluator(ctx context.Context) ExpressionEvaluator {
|
||||
// todo: cleanup EvaluationEnvironment creation
|
||||
job := rc.Run.Job()
|
||||
strategy := make(map[string]interface{})
|
||||
if job.Strategy != nil {
|
||||
strategy["fail-fast"] = job.Strategy.FailFast
|
||||
strategy["max-parallel"] = job.Strategy.MaxParallel
|
||||
}
|
||||
|
||||
jobs := rc.Run.Workflow.Jobs
|
||||
jobNeeds := rc.Run.Job().Needs()
|
||||
|
||||
using := make(map[string]map[string]map[string]string)
|
||||
for _, needs := range jobNeeds {
|
||||
using[needs] = map[string]map[string]string{
|
||||
"outputs": jobs[needs].Outputs,
|
||||
strategy := make(map[string]interface{})
|
||||
if rc.Run != nil {
|
||||
job := rc.Run.Job()
|
||||
if job != nil && job.Strategy != nil {
|
||||
strategy["fail-fast"] = job.Strategy.FailFast
|
||||
strategy["max-parallel"] = job.Strategy.MaxParallel
|
||||
}
|
||||
|
||||
jobs := rc.Run.Workflow.Jobs
|
||||
jobNeeds := rc.Run.Job().Needs()
|
||||
|
||||
for _, needs := range jobNeeds {
|
||||
using[needs] = map[string]map[string]string{
|
||||
"outputs": jobs[needs].Outputs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,19 +50,16 @@ func (rc *RunContext) NewExpressionEvaluator(ctx context.Context) ExpressionEval
|
||||
Job: rc.getJobContext(),
|
||||
// todo: should be unavailable
|
||||
// but required to interpolate/evaluate the step outputs on the job
|
||||
Steps: rc.getStepsContext(),
|
||||
Runner: map[string]interface{}{
|
||||
"os": "Linux",
|
||||
"arch": container.RunnerArch(ctx),
|
||||
"temp": "/tmp",
|
||||
"tool_cache": "/opt/hostedtoolcache",
|
||||
},
|
||||
Steps: rc.getStepsContext(),
|
||||
Secrets: rc.Config.Secrets,
|
||||
Strategy: strategy,
|
||||
Matrix: rc.Matrix,
|
||||
Needs: using,
|
||||
Inputs: inputs,
|
||||
}
|
||||
if rc.JobContainer != nil {
|
||||
ee.Runner = rc.JobContainer.GetRunnerContext(ctx)
|
||||
}
|
||||
return expressionEvaluator{
|
||||
interpreter: exprparser.NewInterpeter(ee, exprparser.Config{
|
||||
Run: rc.Run,
|
||||
@@ -95,16 +93,10 @@ func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step)
|
||||
inputs := getEvaluatorInputs(ctx, rc, step, ghc)
|
||||
|
||||
ee := &exprparser.EvaluationEnvironment{
|
||||
Github: step.getGithubContext(ctx),
|
||||
Env: *step.getEnv(),
|
||||
Job: rc.getJobContext(),
|
||||
Steps: rc.getStepsContext(),
|
||||
Runner: map[string]interface{}{
|
||||
"os": "Linux",
|
||||
"arch": container.RunnerArch(ctx),
|
||||
"temp": "/tmp",
|
||||
"tool_cache": "/opt/hostedtoolcache",
|
||||
},
|
||||
Github: step.getGithubContext(ctx),
|
||||
Env: *step.getEnv(),
|
||||
Job: rc.getJobContext(),
|
||||
Steps: rc.getStepsContext(),
|
||||
Secrets: rc.Config.Secrets,
|
||||
Strategy: strategy,
|
||||
Matrix: rc.Matrix,
|
||||
@@ -113,6 +105,9 @@ func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step)
|
||||
// but required to interpolate/evaluate the inputs in actions/composite
|
||||
Inputs: inputs,
|
||||
}
|
||||
if rc.JobContainer != nil {
|
||||
ee.Runner = rc.JobContainer.GetRunnerContext(ctx)
|
||||
}
|
||||
return expressionEvaluator{
|
||||
interpreter: exprparser.NewInterpeter(ee, exprparser.Config{
|
||||
Run: rc.Run,
|
||||
|
@@ -117,7 +117,6 @@ func TestEvaluateRunContext(t *testing.T) {
|
||||
{"github.run_id", "1", ""},
|
||||
{"github.run_number", "1", ""},
|
||||
{"job.status", "success", ""},
|
||||
{"runner.os", "Linux", ""},
|
||||
{"matrix.os", "Linux", ""},
|
||||
{"matrix.foo", "bar", ""},
|
||||
{"env.key", "value", ""},
|
||||
|
@@ -38,6 +38,20 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
|
||||
return common.NewDebugExecutor("No steps found")
|
||||
}
|
||||
|
||||
preSteps = append(preSteps, func(ctx context.Context) error {
|
||||
// Have to be skipped for some Tests
|
||||
if rc.Run == nil {
|
||||
return nil
|
||||
}
|
||||
rc.ExprEval = rc.NewExpressionEvaluator(ctx)
|
||||
// evaluate environment variables since they can contain
|
||||
// GitHub's special environment variables.
|
||||
for k, v := range rc.GetEnv() {
|
||||
rc.Env[k] = rc.ExprEval.Interpolate(ctx, v)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
for i, stepModel := range infoSteps {
|
||||
stepModel := stepModel
|
||||
if stepModel == nil {
|
||||
|
@@ -79,6 +79,7 @@ func (jim *jobInfoMock) result(result string) {
|
||||
|
||||
type jobContainerMock struct {
|
||||
container.Container
|
||||
container.LinuxContainerEnvironmentExtensions
|
||||
}
|
||||
|
||||
func (jcm *jobContainerMock) ReplaceLogWriter(stdout, stderr io.Writer) (io.Writer, io.Writer) {
|
||||
|
@@ -2,7 +2,10 @@ package runner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -21,26 +24,25 @@ import (
|
||||
"github.com/nektos/act/pkg/model"
|
||||
)
|
||||
|
||||
const ActPath string = "/var/run/act"
|
||||
|
||||
// RunContext contains info about current job
|
||||
type RunContext struct {
|
||||
Name string
|
||||
Config *Config
|
||||
Matrix map[string]interface{}
|
||||
Run *model.Run
|
||||
EventJSON string
|
||||
Env map[string]string
|
||||
ExtraPath []string
|
||||
CurrentStep string
|
||||
StepResults map[string]*model.StepResult
|
||||
ExprEval ExpressionEvaluator
|
||||
JobContainer container.Container
|
||||
OutputMappings map[MappableOutput]MappableOutput
|
||||
JobName string
|
||||
ActionPath string
|
||||
Parent *RunContext
|
||||
Masks []string
|
||||
Name string
|
||||
Config *Config
|
||||
Matrix map[string]interface{}
|
||||
Run *model.Run
|
||||
EventJSON string
|
||||
Env map[string]string
|
||||
ExtraPath []string
|
||||
CurrentStep string
|
||||
StepResults map[string]*model.StepResult
|
||||
ExprEval ExpressionEvaluator
|
||||
JobContainer container.ExecutionsEnvironment
|
||||
OutputMappings map[MappableOutput]MappableOutput
|
||||
JobName string
|
||||
ActionPath string
|
||||
Parent *RunContext
|
||||
Masks []string
|
||||
cleanUpJobContainer common.Executor
|
||||
}
|
||||
|
||||
func (rc *RunContext) AddMask(mask string) {
|
||||
@@ -59,7 +61,13 @@ func (rc *RunContext) String() string {
|
||||
// GetEnv returns the env for the context
|
||||
func (rc *RunContext) GetEnv() map[string]string {
|
||||
if rc.Env == nil {
|
||||
rc.Env = mergeMaps(rc.Run.Workflow.Env, rc.Run.Job().Environment(), rc.Config.Env)
|
||||
rc.Env = map[string]string{}
|
||||
if rc.Run != nil && rc.Run.Workflow != nil && rc.Config != nil {
|
||||
job := rc.Run.Job()
|
||||
if job != nil {
|
||||
rc.Env = mergeMaps(rc.Run.Workflow.Env, job.Environment(), rc.Config.Env)
|
||||
}
|
||||
}
|
||||
}
|
||||
rc.Env["ACT"] = "true"
|
||||
return rc.Env
|
||||
@@ -81,9 +89,11 @@ func (rc *RunContext) GetBindsAndMounts() ([]string, map[string]string) {
|
||||
fmt.Sprintf("%s:%s", rc.Config.ContainerDaemonSocket, "/var/run/docker.sock"),
|
||||
}
|
||||
|
||||
ext := container.LinuxContainerEnvironmentExtensions{}
|
||||
|
||||
mounts := map[string]string{
|
||||
"act-toolcache": "/toolcache",
|
||||
name + "-env": ActPath,
|
||||
name + "-env": ext.GetActPath(),
|
||||
}
|
||||
|
||||
if job := rc.Run.Job(); job != nil {
|
||||
@@ -109,14 +119,84 @@ func (rc *RunContext) GetBindsAndMounts() ([]string, map[string]string) {
|
||||
if selinux.GetEnabled() {
|
||||
bindModifiers = ":z"
|
||||
}
|
||||
binds = append(binds, fmt.Sprintf("%s:%s%s", rc.Config.Workdir, rc.Config.ContainerWorkdir(), bindModifiers))
|
||||
binds = append(binds, fmt.Sprintf("%s:%s%s", rc.Config.Workdir, ext.ToContainerPath(rc.Config.Workdir), bindModifiers))
|
||||
} else {
|
||||
mounts[name] = rc.Config.ContainerWorkdir()
|
||||
mounts[name] = ext.ToContainerPath(rc.Config.Workdir)
|
||||
}
|
||||
|
||||
return binds, mounts
|
||||
}
|
||||
|
||||
func (rc *RunContext) startHostEnvironment() common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
logger := common.Logger(ctx)
|
||||
rawLogger := logger.WithField("raw_output", true)
|
||||
logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) bool {
|
||||
if rc.Config.LogOutput {
|
||||
rawLogger.Infof("%s", s)
|
||||
} else {
|
||||
rawLogger.Debugf("%s", s)
|
||||
}
|
||||
return true
|
||||
})
|
||||
cacheDir := rc.ActionCacheDir()
|
||||
randBytes := make([]byte, 8)
|
||||
_, _ = rand.Read(randBytes)
|
||||
miscpath := filepath.Join(cacheDir, hex.EncodeToString(randBytes))
|
||||
actPath := filepath.Join(miscpath, "act")
|
||||
if err := os.MkdirAll(actPath, 0777); err != nil {
|
||||
return err
|
||||
}
|
||||
path := filepath.Join(miscpath, "hostexecutor")
|
||||
if err := os.MkdirAll(path, 0777); err != nil {
|
||||
return err
|
||||
}
|
||||
runnerTmp := filepath.Join(miscpath, "tmp")
|
||||
if err := os.MkdirAll(runnerTmp, 0777); err != nil {
|
||||
return err
|
||||
}
|
||||
toolCache := filepath.Join(cacheDir, "tool_cache")
|
||||
rc.JobContainer = &container.HostEnvironment{
|
||||
Path: path,
|
||||
TmpDir: runnerTmp,
|
||||
ToolCache: toolCache,
|
||||
Workdir: rc.Config.Workdir,
|
||||
ActPath: actPath,
|
||||
CleanUp: func() {
|
||||
os.RemoveAll(miscpath)
|
||||
},
|
||||
StdOut: logWriter,
|
||||
}
|
||||
rc.cleanUpJobContainer = rc.JobContainer.Remove()
|
||||
rc.Env["RUNNER_TOOL_CACHE"] = toolCache
|
||||
rc.Env["RUNNER_OS"] = runtime.GOOS
|
||||
rc.Env["RUNNER_ARCH"] = runtime.GOARCH
|
||||
rc.Env["RUNNER_TEMP"] = runnerTmp
|
||||
for _, env := range os.Environ() {
|
||||
i := strings.Index(env, "=")
|
||||
if i > 0 {
|
||||
rc.Env[env[0:i]] = env[i+1:]
|
||||
}
|
||||
}
|
||||
|
||||
return common.NewPipelineExecutor(
|
||||
rc.JobContainer.Copy(rc.JobContainer.GetActPath()+"/", &container.FileEntry{
|
||||
Name: "workflow/event.json",
|
||||
Mode: 0644,
|
||||
Body: rc.EventJSON,
|
||||
}, &container.FileEntry{
|
||||
Name: "workflow/envs.txt",
|
||||
Mode: 0666,
|
||||
Body: "",
|
||||
}, &container.FileEntry{
|
||||
Name: "workflow/paths.txt",
|
||||
Mode: 0666,
|
||||
Body: "",
|
||||
}),
|
||||
)(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *RunContext) startJobContainer() common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
logger := common.Logger(ctx)
|
||||
@@ -146,12 +226,22 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
||||
envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_ARCH", container.RunnerArch(ctx)))
|
||||
envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_TEMP", "/tmp"))
|
||||
|
||||
ext := container.LinuxContainerEnvironmentExtensions{}
|
||||
binds, mounts := rc.GetBindsAndMounts()
|
||||
|
||||
rc.cleanUpJobContainer = func(ctx context.Context) error {
|
||||
if rc.JobContainer != nil && !rc.Config.ReuseContainers {
|
||||
return rc.JobContainer.Remove().
|
||||
Then(container.NewDockerVolumeRemoveExecutor(rc.jobContainerName(), false)).
|
||||
Then(container.NewDockerVolumeRemoveExecutor(rc.jobContainerName()+"-env", false))(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
rc.JobContainer = container.NewContainer(&container.NewContainerInput{
|
||||
Cmd: nil,
|
||||
Entrypoint: []string{"/usr/bin/tail", "-f", "/dev/null"},
|
||||
WorkingDir: rc.Config.ContainerWorkdir(),
|
||||
WorkingDir: ext.ToContainerPath(rc.Config.Workdir),
|
||||
Image: image,
|
||||
Username: username,
|
||||
Password: password,
|
||||
@@ -167,6 +257,9 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
||||
Platform: rc.Config.ContainerArchitecture,
|
||||
Options: rc.options(ctx),
|
||||
})
|
||||
if rc.JobContainer == nil {
|
||||
return errors.New("Failed to create job container")
|
||||
}
|
||||
|
||||
return common.NewPipelineExecutor(
|
||||
rc.JobContainer.Pull(rc.Config.ForcePull),
|
||||
@@ -175,7 +268,7 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
||||
rc.JobContainer.Start(false),
|
||||
rc.JobContainer.UpdateFromImageEnv(&rc.Env),
|
||||
rc.JobContainer.UpdateFromEnv("/etc/environment", &rc.Env),
|
||||
rc.JobContainer.Copy(ActPath+"/", &container.FileEntry{
|
||||
rc.JobContainer.Copy(rc.JobContainer.GetActPath()+"/", &container.FileEntry{
|
||||
Name: "workflow/event.json",
|
||||
Mode: 0644,
|
||||
Body: rc.EventJSON,
|
||||
@@ -201,10 +294,8 @@ func (rc *RunContext) execJobContainer(cmd []string, env map[string]string, user
|
||||
// stopJobContainer removes the job container (if it exists) and its volume (if it exists) if !rc.Config.ReuseContainers
|
||||
func (rc *RunContext) stopJobContainer() common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
if rc.JobContainer != nil && !rc.Config.ReuseContainers {
|
||||
return rc.JobContainer.Remove().
|
||||
Then(container.NewDockerVolumeRemoveExecutor(rc.jobContainerName(), false)).
|
||||
Then(container.NewDockerVolumeRemoveExecutor(rc.jobContainerName()+"-env", false))(ctx)
|
||||
if rc.cleanUpJobContainer != nil && !rc.Config.ReuseContainers {
|
||||
return rc.cleanUpJobContainer(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -241,7 +332,13 @@ func (rc *RunContext) interpolateOutputs() common.Executor {
|
||||
}
|
||||
|
||||
func (rc *RunContext) startContainer() common.Executor {
|
||||
return rc.startJobContainer()
|
||||
return func(ctx context.Context) error {
|
||||
image := rc.platformImage(ctx)
|
||||
if strings.EqualFold(image, "-self-hosted") {
|
||||
return rc.startHostEnvironment()(ctx)
|
||||
}
|
||||
return rc.startJobContainer()(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *RunContext) stopContainer() common.Executor {
|
||||
@@ -409,13 +506,11 @@ func (rc *RunContext) getGithubContext(ctx context.Context) *model.GithubContext
|
||||
logger := common.Logger(ctx)
|
||||
ghc := &model.GithubContext{
|
||||
Event: make(map[string]interface{}),
|
||||
EventPath: ActPath + "/workflow/event.json",
|
||||
Workflow: rc.Run.Workflow.Name,
|
||||
RunID: rc.Config.Env["GITHUB_RUN_ID"],
|
||||
RunNumber: rc.Config.Env["GITHUB_RUN_NUMBER"],
|
||||
Actor: rc.Config.Actor,
|
||||
EventName: rc.Config.EventName,
|
||||
Workspace: rc.Config.ContainerWorkdir(),
|
||||
Action: rc.CurrentStep,
|
||||
Token: rc.Config.Token,
|
||||
ActionPath: rc.ActionPath,
|
||||
@@ -424,6 +519,10 @@ func (rc *RunContext) getGithubContext(ctx context.Context) *model.GithubContext
|
||||
RunnerPerflog: rc.Config.Env["RUNNER_PERFLOG"],
|
||||
RunnerTrackingID: rc.Config.Env["RUNNER_TRACKING_ID"],
|
||||
}
|
||||
if rc.JobContainer != nil {
|
||||
ghc.EventPath = rc.JobContainer.GetActPath() + "/workflow/event.json"
|
||||
ghc.Workspace = rc.JobContainer.ToContainerPath(rc.Config.Workdir)
|
||||
}
|
||||
|
||||
if ghc.RunID == "" {
|
||||
ghc.RunID = "1"
|
||||
@@ -538,8 +637,8 @@ func nestedMapLookup(m map[string]interface{}, ks ...string) (rval interface{})
|
||||
|
||||
func (rc *RunContext) withGithubEnv(ctx context.Context, github *model.GithubContext, env map[string]string) map[string]string {
|
||||
env["CI"] = "true"
|
||||
env["GITHUB_ENV"] = ActPath + "/workflow/envs.txt"
|
||||
env["GITHUB_PATH"] = ActPath + "/workflow/paths.txt"
|
||||
env["GITHUB_ENV"] = rc.JobContainer.GetActPath() + "/workflow/envs.txt"
|
||||
env["GITHUB_PATH"] = rc.JobContainer.GetActPath() + "/workflow/paths.txt"
|
||||
env["GITHUB_WORKFLOW"] = github.Workflow
|
||||
env["GITHUB_RUN_ID"] = github.RunID
|
||||
env["GITHUB_RUN_NUMBER"] = github.RunNumber
|
||||
|
@@ -385,14 +385,12 @@ func TestGetGitHubContext(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.Equal(t, ghc.RunID, "1")
|
||||
assert.Equal(t, ghc.Workspace, rc.Config.containerPath(cwd))
|
||||
assert.Equal(t, ghc.RunNumber, "1")
|
||||
assert.Equal(t, ghc.RetentionDays, "0")
|
||||
assert.Equal(t, ghc.Actor, actor)
|
||||
assert.Equal(t, ghc.Repository, repo)
|
||||
assert.Equal(t, ghc.RepositoryOwner, owner)
|
||||
assert.Equal(t, ghc.RunnerPerflog, "/dev/null")
|
||||
assert.Equal(t, ghc.EventPath, ActPath+"/workflow/event.json")
|
||||
assert.Equal(t, ghc.Token, rc.Config.Secrets["GITHUB_TOKEN"])
|
||||
}
|
||||
|
||||
|
@@ -4,10 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -57,46 +53,6 @@ type Config struct {
|
||||
ReplaceGheActionTokenWithGithubCom string // Token of private action repo on GitHub.
|
||||
}
|
||||
|
||||
// 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
|
||||
// For use in docker volumes and binds
|
||||
func (config *Config) containerPath(path string) string {
|
||||
if runtime.GOOS == "windows" && strings.Contains(path, "/") {
|
||||
log.Error("You cannot specify linux style local paths (/mnt/etc) on Windows as it does not understand them.")
|
||||
return ""
|
||||
}
|
||||
|
||||
abspath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return ""
|
||||
}
|
||||
|
||||
// Test if the path is a windows path
|
||||
windowsPathRegex := regexp.MustCompile(`^([a-zA-Z]):\\(.+)$`)
|
||||
windowsPathComponents := windowsPathRegex.FindStringSubmatch(abspath)
|
||||
|
||||
// Return as-is if no match
|
||||
if windowsPathComponents == nil {
|
||||
return abspath
|
||||
}
|
||||
|
||||
// Convert to WSL2-compatible path if it is a windows path
|
||||
// NOTE: Cannot use filepath because it will use the wrong path separators assuming we want the path to be windows
|
||||
// based if running on Windows, and because we are feeding this to Docker, GoLang auto-path-translate doesn't work.
|
||||
driveLetter := strings.ToLower(windowsPathComponents[1])
|
||||
translatedPath := strings.ReplaceAll(windowsPathComponents[2], `\`, `/`)
|
||||
// Should make something like /mnt/c/Users/person/My Folder/MyActProject
|
||||
result := strings.Join([]string{"/mnt", driveLetter, translatedPath}, `/`)
|
||||
return result
|
||||
}
|
||||
|
||||
// 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
|
||||
func (config *Config) ContainerWorkdir() string {
|
||||
return config.containerPath(config.Workdir)
|
||||
}
|
||||
|
||||
type runnerImpl struct {
|
||||
config *Config
|
||||
eventJSON string
|
||||
@@ -163,11 +119,6 @@ func (runner *runnerImpl) NewPlanExecutor(plan *model.Plan) common.Executor {
|
||||
if len(matrixes) > 1 {
|
||||
rc.Name = fmt.Sprintf("%s-%d", rc.Name, i+1)
|
||||
}
|
||||
// evaluate environment variables since they can contain
|
||||
// GitHub's special environment variables.
|
||||
for k, v := range rc.GetEnv() {
|
||||
rc.Env[k] = rc.ExprEval.Interpolate(ctx, v)
|
||||
}
|
||||
if len(rc.String()) > maxJobNameLen {
|
||||
maxJobNameLen = len(rc.String())
|
||||
}
|
||||
|
@@ -205,6 +205,95 @@ func TestRunEvent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunEventHostEnvironment(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
tables := []TestJobFileInfo{}
|
||||
|
||||
if runtime.GOOS == "linux" {
|
||||
platforms := map[string]string{
|
||||
"ubuntu-latest": "-self-hosted",
|
||||
}
|
||||
|
||||
tables = append(tables, []TestJobFileInfo{
|
||||
// Shells
|
||||
{workdir, "shells/defaults", "push", "", platforms},
|
||||
{workdir, "shells/pwsh", "push", "", platforms},
|
||||
{workdir, "shells/bash", "push", "", platforms},
|
||||
{workdir, "shells/python", "push", "", platforms},
|
||||
{workdir, "shells/sh", "push", "", platforms},
|
||||
|
||||
// Local action
|
||||
{workdir, "local-action-js", "push", "", platforms},
|
||||
|
||||
// Uses
|
||||
{workdir, "uses-composite", "push", "", platforms},
|
||||
{workdir, "uses-composite-with-error", "push", "Job 'failing-composite-action' failed", platforms},
|
||||
{workdir, "uses-nested-composite", "push", "", platforms},
|
||||
{workdir, "act-composite-env-test", "push", "", platforms},
|
||||
|
||||
// Eval
|
||||
{workdir, "evalmatrix", "push", "", platforms},
|
||||
{workdir, "evalmatrixneeds", "push", "", platforms},
|
||||
{workdir, "evalmatrixneeds2", "push", "", platforms},
|
||||
{workdir, "evalmatrix-merge-map", "push", "", platforms},
|
||||
{workdir, "evalmatrix-merge-array", "push", "", platforms},
|
||||
{workdir, "issue-1195", "push", "", platforms},
|
||||
|
||||
{workdir, "fail", "push", "exit with `FAILURE`: 1", platforms},
|
||||
{workdir, "runs-on", "push", "", platforms},
|
||||
{workdir, "checkout", "push", "", platforms},
|
||||
{workdir, "remote-action-js", "push", "", platforms},
|
||||
{workdir, "matrix", "push", "", platforms},
|
||||
{workdir, "matrix-include-exclude", "push", "", platforms},
|
||||
{workdir, "commands", "push", "", platforms},
|
||||
{workdir, "defaults-run", "push", "", platforms},
|
||||
{workdir, "composite-fail-with-output", "push", "", platforms},
|
||||
{workdir, "issue-597", "push", "", platforms},
|
||||
{workdir, "issue-598", "push", "", platforms},
|
||||
{workdir, "if-env-act", "push", "", platforms},
|
||||
{workdir, "env-and-path", "push", "", platforms},
|
||||
{workdir, "non-existent-action", "push", "Job 'nopanic' failed", platforms},
|
||||
{workdir, "outputs", "push", "", platforms},
|
||||
{workdir, "steps-context/conclusion", "push", "", platforms},
|
||||
{workdir, "steps-context/outcome", "push", "", platforms},
|
||||
{workdir, "job-status-check", "push", "job 'fail' failed", platforms},
|
||||
{workdir, "if-expressions", "push", "Job 'mytest' failed", platforms},
|
||||
{workdir, "uses-action-with-pre-and-post-step", "push", "", platforms},
|
||||
{workdir, "evalenv", "push", "", platforms},
|
||||
{workdir, "ensure-post-steps", "push", "Job 'second-post-step-should-fail' failed", platforms},
|
||||
}...)
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
platforms := map[string]string{
|
||||
"windows-latest": "-self-hosted",
|
||||
}
|
||||
|
||||
tables = append(tables, []TestJobFileInfo{
|
||||
{workdir, "windows-prepend-path", "push", "", platforms},
|
||||
{workdir, "windows-add-env", "push", "", platforms},
|
||||
}...)
|
||||
} else {
|
||||
platforms := map[string]string{
|
||||
"self-hosted": "-self-hosted",
|
||||
}
|
||||
|
||||
tables = append(tables, []TestJobFileInfo{
|
||||
{workdir, "nix-prepend-path", "push", "", platforms},
|
||||
}...)
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
t.Run(table.workflowPath, func(t *testing.T) {
|
||||
table.runTest(ctx, t, &Config{})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDryrunEvent(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
@@ -320,60 +409,3 @@ func TestRunEventPullRequest(t *testing.T) {
|
||||
|
||||
tjfi.runTest(context.Background(), t, &Config{EventPath: filepath.Join(workdir, workflowPath, "event.json")})
|
||||
}
|
||||
|
||||
func TestContainerPath(t *testing.T) {
|
||||
type containerPathJob struct {
|
||||
destinationPath string
|
||||
sourcePath string
|
||||
workDir string
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
rootDrive := os.Getenv("SystemDrive")
|
||||
rootDriveLetter := strings.ReplaceAll(strings.ToLower(rootDrive), `:`, "")
|
||||
for _, v := range []containerPathJob{
|
||||
{"/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/c/windows/to/unix", "windows\\to\\unix", fmt.Sprintf("%s\\", rootDrive)},
|
||||
{fmt.Sprintf("/mnt/%v/act", rootDriveLetter), "act", fmt.Sprintf("%s\\", rootDrive)},
|
||||
} {
|
||||
if v.workDir != "" {
|
||||
if err := os.Chdir(v.workDir); err != nil {
|
||||
log.Error(err)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
runnerConfig := &Config{
|
||||
Workdir: v.sourcePath,
|
||||
}
|
||||
|
||||
assert.Equal(t, v.destinationPath, runnerConfig.containerPath(runnerConfig.Workdir))
|
||||
}
|
||||
|
||||
if err := os.Chdir(cwd); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
} else {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
for _, v := range []containerPathJob{
|
||||
{"/home/act/go/src/github.com/nektos/act", "/home/act/go/src/github.com/nektos/act", ""},
|
||||
{"/home/act", `/home/act/`, ""},
|
||||
{cwd, ".", ""},
|
||||
} {
|
||||
runnerConfig := &Config{
|
||||
Workdir: v.sourcePath,
|
||||
}
|
||||
|
||||
assert.Equal(t, v.destinationPath, runnerConfig.containerPath(runnerConfig.Workdir))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -162,13 +162,12 @@ func mergeEnv(ctx context.Context, step step) {
|
||||
mergeIntoMap(env, rc.GetEnv())
|
||||
}
|
||||
|
||||
if (*env)["PATH"] == "" {
|
||||
(*env)["PATH"] = `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`
|
||||
path := rc.JobContainer.GetPathVariableName()
|
||||
if (*env)[path] == "" {
|
||||
(*env)[path] = rc.JobContainer.DefaultPathVariable()
|
||||
}
|
||||
if rc.ExtraPath != nil && len(rc.ExtraPath) > 0 {
|
||||
p := (*env)["PATH"]
|
||||
(*env)["PATH"] = strings.Join(rc.ExtraPath, `:`)
|
||||
(*env)["PATH"] += `:` + p
|
||||
(*env)[path] = rc.JobContainer.JoinPathVariable(append(rc.ExtraPath, (*env)[path])...)
|
||||
}
|
||||
|
||||
rc.withGithubEnv(ctx, step.getGithubContext(ctx), *env)
|
||||
|
@@ -118,7 +118,7 @@ func (sar *stepActionRemote) main() common.Executor {
|
||||
return nil
|
||||
}
|
||||
eval := sar.RunContext.NewExpressionEvaluator(ctx)
|
||||
copyToPath := path.Join(sar.RunContext.Config.ContainerWorkdir(), eval.Interpolate(ctx, sar.Step.With["path"]))
|
||||
copyToPath := path.Join(sar.RunContext.JobContainer.ToContainerPath(sar.RunContext.Config.Workdir), eval.Interpolate(ctx, sar.Step.With["path"]))
|
||||
return sar.RunContext.JobContainer.CopyDir(copyToPath, sar.RunContext.Config.Workdir+string(filepath.Separator)+".", sar.RunContext.Config.UseGitIgnore)(ctx)
|
||||
}
|
||||
|
||||
|
@@ -116,7 +116,7 @@ func (sd *stepDocker) newStepContainer(ctx context.Context, image string, cmd []
|
||||
stepContainer := ContainerNewContainer(&container.NewContainerInput{
|
||||
Cmd: cmd,
|
||||
Entrypoint: entrypoint,
|
||||
WorkingDir: rc.Config.ContainerWorkdir(),
|
||||
WorkingDir: rc.JobContainer.ToContainerPath(rc.Config.Workdir),
|
||||
Image: image,
|
||||
Username: rc.Config.Secrets["DOCKER_USERNAME"],
|
||||
Password: rc.Config.Secrets["DOCKER_PASSWORD"],
|
||||
|
@@ -17,7 +17,7 @@ func TestStepDockerMain(t *testing.T) {
|
||||
|
||||
// mock the new container call
|
||||
origContainerNewContainer := ContainerNewContainer
|
||||
ContainerNewContainer = func(containerInput *container.NewContainerInput) container.Container {
|
||||
ContainerNewContainer = func(containerInput *container.NewContainerInput) container.ExecutionsEnvironment {
|
||||
input = containerInput
|
||||
return cm
|
||||
}
|
||||
|
@@ -68,7 +68,8 @@ func (sr *stepRun) setupShellCommandExecutor() common.Executor {
|
||||
return err
|
||||
}
|
||||
|
||||
return sr.RunContext.JobContainer.Copy(ActPath, &container.FileEntry{
|
||||
rc := sr.getRunContext()
|
||||
return rc.JobContainer.Copy(rc.JobContainer.GetActPath(), &container.FileEntry{
|
||||
Name: scriptName,
|
||||
Mode: 0755,
|
||||
Body: script,
|
||||
@@ -128,7 +129,8 @@ func (sr *stepRun) setupShellCommand(ctx context.Context) (name, script string,
|
||||
logger.Debugf("Wrote add-mask command to '%s'", name)
|
||||
}
|
||||
|
||||
scriptPath := fmt.Sprintf("%s/%s", ActPath, name)
|
||||
rc := sr.getRunContext()
|
||||
scriptPath := fmt.Sprintf("%s/%s", rc.JobContainer.GetActPath(), name)
|
||||
sr.cmd, err = shellquote.Split(strings.Replace(scCmd, `{0}`, scriptPath, 1))
|
||||
|
||||
return name, script, err
|
||||
|
26
pkg/runner/testdata/nix-prepend-path/push.yml
vendored
Normal file
26
pkg/runner/testdata/nix-prepend-path/push.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
on:
|
||||
push:
|
||||
defaults:
|
||||
run:
|
||||
shell: sh
|
||||
jobs:
|
||||
test:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- run: |
|
||||
mkdir build
|
||||
echo '#!/usr/bin/env sh' > build/testtool
|
||||
echo 'echo Hi' >> build/testtool
|
||||
chmod +x build/testtool
|
||||
- run: |
|
||||
echo '${{ tojson(runner) }}'
|
||||
ls
|
||||
echo '${{ github.workspace }}'
|
||||
working-directory: ${{ github.workspace }}/build
|
||||
- run: |
|
||||
echo "$GITHUB_PATH"
|
||||
echo '${{ github.workspace }}/build' > "$GITHUB_PATH"
|
||||
cat "$GITHUB_PATH"
|
||||
- run: |
|
||||
echo "$PATH"
|
||||
testtool
|
27
pkg/runner/testdata/windows-add-env/push.yml
vendored
Normal file
27
pkg/runner/testdata/windows-add-env/push.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
on:
|
||||
push:
|
||||
defaults:
|
||||
run:
|
||||
shell: pwsh
|
||||
jobs:
|
||||
test:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- run: |
|
||||
echo $env:GITHUB_ENV
|
||||
echo "key=val" > $env:GITHUB_ENV
|
||||
echo "key2<<EOF" >> $env:GITHUB_ENV
|
||||
echo "line1" >> $env:GITHUB_ENV
|
||||
echo "line2" >> $env:GITHUB_ENV
|
||||
echo "EOF" >> $env:GITHUB_ENV
|
||||
cat $env:GITHUB_ENV
|
||||
- run: |
|
||||
ls env:
|
||||
if($env:key -ne 'val') {
|
||||
echo "Unexpected value for `$env:key: $env:key"
|
||||
exit 1
|
||||
}
|
||||
if($env:key2 -ne "line1`nline2") {
|
||||
echo "Unexpected value for `$env:key2: $env:key2"
|
||||
exit 1
|
||||
}
|
25
pkg/runner/testdata/windows-prepend-path/push.yml
vendored
Normal file
25
pkg/runner/testdata/windows-prepend-path/push.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
on:
|
||||
push:
|
||||
defaults:
|
||||
run:
|
||||
shell: pwsh
|
||||
jobs:
|
||||
test:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- run: |
|
||||
mkdir build
|
||||
echo '@echo off' > build/test.cmd
|
||||
echo 'echo Hi' >> build/test.cmd
|
||||
- run: |
|
||||
echo '${{ tojson(runner) }}'
|
||||
ls
|
||||
echo '${{ github.workspace }}'
|
||||
working-directory: ${{ github.workspace }}\build
|
||||
- run: |
|
||||
echo $env:GITHUB_PATH
|
||||
echo '${{ github.workspace }}\build' > $env:GITHUB_PATH
|
||||
cat $env:GITHUB_PATH
|
||||
- run: |
|
||||
echo $env:PATH
|
||||
test
|
Reference in New Issue
Block a user