$GITHUB_PATH support (#566)
* Regression in the .golangci.yml file * This looks like an even better fix to #451 The previous solution only prevented the `starting container process caused "exec: \"bash\"` error when someone added an "extra" path in the workflow using `::add-path` * Add support for >> $GITHUB_PATH * The newRunCommand has too high cyclomatic complexity * Add "linux/arm64" to new test * The cyclop linter was complaining so I extracted some funcs * Close some readers * Fix typo * fix: add missing composite function * Fix regress from merging * Keep the error messages as is * consolidate with master * Close the tar reader on defer * New way to get ContainerWorkdir * Remove arch from runner test * Separate the UpdateFromEnv and UpdateFromPath Co-authored-by: hackercat <me@hackerc.at>
This commit is contained in:
parent
8153dc92e5
commit
92eec3a526
8 changed files with 234 additions and 135 deletions
|
@ -4,7 +4,7 @@ run:
|
||||||
linters-settings:
|
linters-settings:
|
||||||
gocyclo:
|
gocyclo:
|
||||||
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||||
mi-complexity: 15
|
min-complexity: 15
|
||||||
gocritic:
|
gocritic:
|
||||||
disabled-checks:
|
disabled-checks:
|
||||||
- ifElseChain
|
- ifElseChain
|
||||||
|
|
|
@ -145,6 +145,7 @@ func readEnvs(path string, envs map[string]string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:gocyclo
|
||||||
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 {
|
||||||
log.Debugf("Loading environment from %s", input.Envfile())
|
log.Debugf("Loading environment from %s", input.Envfile())
|
||||||
|
|
|
@ -70,6 +70,7 @@ type Container interface {
|
||||||
Start(attach bool) common.Executor
|
Start(attach bool) common.Executor
|
||||||
Exec(command []string, env map[string]string) common.Executor
|
Exec(command []string, env map[string]string) common.Executor
|
||||||
UpdateFromEnv(srcPath string, env *map[string]string) common.Executor
|
UpdateFromEnv(srcPath string, env *map[string]string) common.Executor
|
||||||
|
UpdateFromPath(env *map[string]string) common.Executor
|
||||||
Remove() common.Executor
|
Remove() common.Executor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,6 +156,10 @@ func (cr *containerReference) UpdateFromEnv(srcPath string, env *map[string]stri
|
||||||
return cr.extractEnv(srcPath, env).IfNot(common.Dryrun)
|
return cr.extractEnv(srcPath, env).IfNot(common.Dryrun)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cr *containerReference) UpdateFromPath(env *map[string]string) common.Executor {
|
||||||
|
return cr.extractPath(env).IfNot(common.Dryrun)
|
||||||
|
}
|
||||||
|
|
||||||
func (cr *containerReference) Exec(command []string, env map[string]string) common.Executor {
|
func (cr *containerReference) Exec(command []string, env map[string]string) common.Executor {
|
||||||
return common.NewPipelineExecutor(
|
return common.NewPipelineExecutor(
|
||||||
cr.connect(),
|
cr.connect(),
|
||||||
|
@ -342,6 +347,7 @@ func (cr *containerReference) extractEnv(srcPath string, env *map[string]string)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
defer envTar.Close()
|
||||||
reader := tar.NewReader(envTar)
|
reader := tar.NewReader(envTar)
|
||||||
_, err = reader.Next()
|
_, err = reader.Next()
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
|
@ -376,6 +382,31 @@ func (cr *containerReference) extractEnv(srcPath string, env *map[string]string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cr *containerReference) extractPath(env *map[string]string) common.Executor {
|
||||||
|
localEnv := *env
|
||||||
|
return func(ctx context.Context) error {
|
||||||
|
pathTar, _, err := cr.cli.CopyFromContainer(ctx, cr.id, localEnv["GITHUB_PATH"])
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
defer pathTar.Close()
|
||||||
|
|
||||||
|
reader := tar.NewReader(pathTar)
|
||||||
|
_, err = reader.Next()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
s := bufio.NewScanner(reader)
|
||||||
|
for s.Scan() {
|
||||||
|
line := s.Text()
|
||||||
|
localEnv["PATH"] = fmt.Sprintf("%s:%s", localEnv["PATH"], line)
|
||||||
|
}
|
||||||
|
|
||||||
|
env = &localEnv
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cr *containerReference) exec(cmd []string, env map[string]string) common.Executor {
|
func (cr *containerReference) exec(cmd []string, env map[string]string) common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
|
@ -413,6 +444,8 @@ func (cr *containerReference) exec(cmd []string, env map[string]string) common.E
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
defer resp.Close()
|
||||||
|
|
||||||
var outWriter io.Writer
|
var outWriter io.Writer
|
||||||
outWriter = cr.input.Stdout
|
outWriter = cr.input.Stdout
|
||||||
if outWriter == nil {
|
if outWriter == nil {
|
||||||
|
|
|
@ -90,6 +90,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
|
||||||
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 {
|
||||||
|
|
|
@ -154,6 +154,10 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
||||||
Name: "workflow/envs.txt",
|
Name: "workflow/envs.txt",
|
||||||
Mode: 0644,
|
Mode: 0644,
|
||||||
Body: "",
|
Body: "",
|
||||||
|
}, &container.FileEntry{
|
||||||
|
Name: "workflow/paths.txt",
|
||||||
|
Mode: 0644,
|
||||||
|
Body: "",
|
||||||
}),
|
}),
|
||||||
)(ctx)
|
)(ctx)
|
||||||
}
|
}
|
||||||
|
@ -628,6 +632,7 @@ func (rc *RunContext) withGithubEnv(env map[string]string) map[string]string {
|
||||||
github := rc.getGithubContext()
|
github := rc.getGithubContext()
|
||||||
env["CI"] = "true"
|
env["CI"] = "true"
|
||||||
env["GITHUB_ENV"] = "/tmp/workflow/envs.txt"
|
env["GITHUB_ENV"] = "/tmp/workflow/envs.txt"
|
||||||
|
env["GITHUB_PATH"] = "/tmp/workflow/paths.txt"
|
||||||
env["GITHUB_WORKFLOW"] = github.Workflow
|
env["GITHUB_WORKFLOW"] = github.Workflow
|
||||||
env["GITHUB_RUN_ID"] = github.RunID
|
env["GITHUB_RUN_ID"] = github.RunID
|
||||||
env["GITHUB_RUN_NUMBER"] = github.RunNumber
|
env["GITHUB_RUN_NUMBER"] = github.RunNumber
|
||||||
|
|
|
@ -107,6 +107,7 @@ func TestRunEvent(t *testing.T) {
|
||||||
{"testdata", "uses-composite", "push", "", platforms, ""},
|
{"testdata", "uses-composite", "push", "", platforms, ""},
|
||||||
{"testdata", "issue-597", "push", "", platforms, ""},
|
{"testdata", "issue-597", "push", "", platforms, ""},
|
||||||
{"testdata", "issue-598", "push", "", platforms, ""},
|
{"testdata", "issue-598", "push", "", platforms, ""},
|
||||||
|
{"testdata", "env-and-path", "push", "", platforms, ""},
|
||||||
// {"testdata", "issue-228", "push", "", platforms, ""}, // TODO [igni]: Remove this once everything passes
|
// {"testdata", "issue-228", "push", "", platforms, ""}, // TODO [igni]: Remove this once everything passes
|
||||||
|
|
||||||
// single test for different architecture: linux/arm64
|
// single test for different architecture: linux/arm64
|
||||||
|
|
|
@ -112,6 +112,7 @@ func (sc *StepContext) mergeEnv() map[string]string {
|
||||||
env = mergeMaps(rc.GetEnv(), step.GetEnv())
|
env = mergeMaps(rc.GetEnv(), step.GetEnv())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
env["PATH"] = `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`
|
||||||
if (rc.ExtraPath != nil) && (len(rc.ExtraPath) > 0) {
|
if (rc.ExtraPath != nil) && (len(rc.ExtraPath) > 0) {
|
||||||
env["PATH"] = strings.Join(rc.ExtraPath, `:`)
|
env["PATH"] = strings.Join(rc.ExtraPath, `:`)
|
||||||
}
|
}
|
||||||
|
@ -134,6 +135,10 @@ func (sc *StepContext) setupEnv(ctx context.Context) (ExpressionEvaluator, error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
err = rc.JobContainer.UpdateFromPath(&sc.Env)(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
evaluator := sc.NewExpressionEvaluator()
|
evaluator := sc.NewExpressionEvaluator()
|
||||||
sc.interpolateEnv(evaluator)
|
sc.interpolateEnv(evaluator)
|
||||||
|
@ -390,14 +395,7 @@ func (sc *StepContext) runAction(actionDir string, actionPath string) common.Exe
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
action := sc.Action
|
action := sc.Action
|
||||||
log.Debugf("About to run action %v", action)
|
log.Debugf("About to run action %v", action)
|
||||||
for inputID, input := range action.Inputs {
|
sc.populateEnvsFromInput(action, rc)
|
||||||
envKey := regexp.MustCompile("[^A-Z0-9-]").ReplaceAllString(strings.ToUpper(inputID), "_")
|
|
||||||
envKey = fmt.Sprintf("INPUT_%s", envKey)
|
|
||||||
if _, ok := sc.Env[envKey]; !ok {
|
|
||||||
sc.Env[envKey] = rc.ExprEval.Interpolate(input.Default)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
actionLocation := ""
|
actionLocation := ""
|
||||||
if actionPath != "" {
|
if actionPath != "" {
|
||||||
actionLocation = path.Join(actionDir, actionPath)
|
actionLocation = path.Join(actionDir, actionPath)
|
||||||
|
@ -434,133 +432,9 @@ func (sc *StepContext) runAction(actionDir string, actionPath string) common.Exe
|
||||||
log.Debugf("executing remote job container: %s", containerArgs)
|
log.Debugf("executing remote job container: %s", containerArgs)
|
||||||
return rc.execJobContainer(containerArgs, sc.Env)(ctx)
|
return rc.execJobContainer(containerArgs, sc.Env)(ctx)
|
||||||
case model.ActionRunsUsingDocker:
|
case model.ActionRunsUsingDocker:
|
||||||
var prepImage common.Executor
|
return sc.execAsDocker(ctx, action, actionName, actionDir, actionPath, rc, step)
|
||||||
var image string
|
|
||||||
if strings.HasPrefix(action.Runs.Image, "docker://") {
|
|
||||||
image = strings.TrimPrefix(action.Runs.Image, "docker://")
|
|
||||||
} else {
|
|
||||||
image = fmt.Sprintf("%s:%s", regexp.MustCompile("[^a-zA-Z0-9]").ReplaceAllString(actionName, "-"), "latest")
|
|
||||||
image = fmt.Sprintf("act-%s", strings.TrimLeft(image, "-"))
|
|
||||||
image = strings.ToLower(image)
|
|
||||||
contextDir := filepath.Join(actionDir, actionPath, action.Runs.Main)
|
|
||||||
|
|
||||||
anyArchExists, err := container.ImageExistsLocally(ctx, image, "any")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
correctArchExists, err := container.ImageExistsLocally(ctx, image, rc.Config.ContainerArchitecture)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if anyArchExists && !correctArchExists {
|
|
||||||
wasRemoved, err := container.RemoveImage(ctx, image, true, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !wasRemoved {
|
|
||||||
return fmt.Errorf("failed to remove image '%s'", image)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !correctArchExists {
|
|
||||||
log.Debugf("image '%s' for architecture '%s' will be built from context '%s", image, rc.Config.ContainerArchitecture, contextDir)
|
|
||||||
prepImage = container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
|
|
||||||
ContextDir: contextDir,
|
|
||||||
ImageTag: image,
|
|
||||||
Platform: rc.Config.ContainerArchitecture,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
log.Debugf("image '%s' for architecture '%s' already exists", image, rc.Config.ContainerArchitecture)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd, err := shellquote.Split(step.With["args"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(cmd) == 0 {
|
|
||||||
cmd = action.Runs.Args
|
|
||||||
}
|
|
||||||
entrypoint := strings.Fields(step.With["entrypoint"])
|
|
||||||
if len(entrypoint) == 0 {
|
|
||||||
entrypoint = action.Runs.Entrypoint
|
|
||||||
}
|
|
||||||
stepContainer := sc.newStepContainer(ctx, image, cmd, entrypoint)
|
|
||||||
return common.NewPipelineExecutor(
|
|
||||||
prepImage,
|
|
||||||
stepContainer.Pull(rc.Config.ForcePull),
|
|
||||||
stepContainer.Remove().IfBool(!rc.Config.ReuseContainers),
|
|
||||||
stepContainer.Create(),
|
|
||||||
stepContainer.Start(true),
|
|
||||||
).Finally(
|
|
||||||
stepContainer.Remove().IfBool(!rc.Config.ReuseContainers),
|
|
||||||
)(ctx)
|
|
||||||
case model.ActionRunsUsingComposite:
|
case model.ActionRunsUsingComposite:
|
||||||
err := maybeCopyToActionDir()
|
return sc.execAsComposite(ctx, step, actionDir, rc, containerActionDir, actionName, actionPath, action, maybeCopyToActionDir)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for outputName, output := range action.Outputs {
|
|
||||||
re := regexp.MustCompile(`\${{ steps\.([a-zA-Z_][a-zA-Z0-9_-]+)\.outputs\.([a-zA-Z_][a-zA-Z0-9_-]+) }}`)
|
|
||||||
matches := re.FindStringSubmatch(output.Value)
|
|
||||||
if len(matches) > 2 {
|
|
||||||
if sc.RunContext.OutputMappings == nil {
|
|
||||||
sc.RunContext.OutputMappings = make(map[MappableOutput]MappableOutput)
|
|
||||||
}
|
|
||||||
|
|
||||||
k := MappableOutput{StepID: matches[1], OutputName: matches[2]}
|
|
||||||
v := MappableOutput{StepID: step.ID, OutputName: outputName}
|
|
||||||
sc.RunContext.OutputMappings[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var executors []common.Executor
|
|
||||||
stepID := 0
|
|
||||||
for _, compositeStep := range action.Runs.Steps {
|
|
||||||
stepClone := compositeStep
|
|
||||||
// Take a copy of the run context structure (rc is a pointer)
|
|
||||||
// Then take the address of the new structure
|
|
||||||
rcCloneStr := *rc
|
|
||||||
rcClone := &rcCloneStr
|
|
||||||
if stepClone.ID == "" {
|
|
||||||
stepClone.ID = fmt.Sprintf("composite-%d", stepID)
|
|
||||||
stepID++
|
|
||||||
}
|
|
||||||
rcClone.CurrentStep = stepClone.ID
|
|
||||||
|
|
||||||
if err := compositeStep.Validate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the outputs for the composite steps
|
|
||||||
if _, ok := rcClone.StepResults[stepClone.ID]; !ok {
|
|
||||||
rcClone.StepResults[stepClone.ID] = &stepResult{
|
|
||||||
Success: true,
|
|
||||||
Outputs: make(map[string]string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stepClone.Run = strings.ReplaceAll(stepClone.Run, "${{ github.action_path }}", filepath.Join(containerActionDir, actionName))
|
|
||||||
|
|
||||||
stepContext := StepContext{
|
|
||||||
RunContext: rcClone,
|
|
||||||
Step: &stepClone,
|
|
||||||
Env: mergeMaps(sc.Env, stepClone.Env),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interpolate the outer inputs into the composite step with items
|
|
||||||
exprEval := sc.NewExpressionEvaluator()
|
|
||||||
for k, v := range stepContext.Step.With {
|
|
||||||
if strings.Contains(v, "inputs") {
|
|
||||||
stepContext.Step.With[k] = exprEval.Interpolate(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
executors = append(executors, stepContext.Executor())
|
|
||||||
}
|
|
||||||
return common.NewPipelineExecutor(executors...)(ctx)
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf(fmt.Sprintf("The runs.using key must be one of: %v, got %s", []string{
|
return fmt.Errorf(fmt.Sprintf("The runs.using key must be one of: %v, got %s", []string{
|
||||||
model.ActionRunsUsingDocker,
|
model.ActionRunsUsingDocker,
|
||||||
|
@ -571,6 +445,149 @@ func (sc *StepContext) runAction(actionDir string, actionPath string) common.Exe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sc *StepContext) execAsDocker(ctx context.Context, action *model.Action, actionName string, actionDir string, actionPath string, rc *RunContext, step *model.Step) error {
|
||||||
|
var prepImage common.Executor
|
||||||
|
var image string
|
||||||
|
if strings.HasPrefix(action.Runs.Image, "docker://") {
|
||||||
|
image = strings.TrimPrefix(action.Runs.Image, "docker://")
|
||||||
|
} else {
|
||||||
|
image = fmt.Sprintf("%s:%s", regexp.MustCompile("[^a-zA-Z0-9]").ReplaceAllString(actionName, "-"), "latest")
|
||||||
|
image = fmt.Sprintf("act-%s", strings.TrimLeft(image, "-"))
|
||||||
|
image = strings.ToLower(image)
|
||||||
|
contextDir := filepath.Join(actionDir, actionPath, action.Runs.Main)
|
||||||
|
|
||||||
|
anyArchExists, err := container.ImageExistsLocally(ctx, image, "any")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
correctArchExists, err := container.ImageExistsLocally(ctx, image, rc.Config.ContainerArchitecture)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if anyArchExists && !correctArchExists {
|
||||||
|
wasRemoved, err := container.RemoveImage(ctx, image, true, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !wasRemoved {
|
||||||
|
return fmt.Errorf("failed to remove image '%s'", image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !correctArchExists {
|
||||||
|
log.Debugf("image '%s' for architecture '%s' will be built from context '%s", image, rc.Config.ContainerArchitecture, contextDir)
|
||||||
|
prepImage = container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
|
||||||
|
ContextDir: contextDir,
|
||||||
|
ImageTag: image,
|
||||||
|
Platform: rc.Config.ContainerArchitecture,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
log.Debugf("image '%s' for architecture '%s' already exists", image, rc.Config.ContainerArchitecture)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err := shellquote.Split(step.With["args"])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(cmd) == 0 {
|
||||||
|
cmd = action.Runs.Args
|
||||||
|
}
|
||||||
|
entrypoint := strings.Fields(step.With["entrypoint"])
|
||||||
|
if len(entrypoint) == 0 {
|
||||||
|
entrypoint = action.Runs.Entrypoint
|
||||||
|
}
|
||||||
|
stepContainer := sc.newStepContainer(ctx, image, cmd, entrypoint)
|
||||||
|
return common.NewPipelineExecutor(
|
||||||
|
prepImage,
|
||||||
|
stepContainer.Pull(rc.Config.ForcePull),
|
||||||
|
stepContainer.Remove().IfBool(!rc.Config.ReuseContainers),
|
||||||
|
stepContainer.Create(),
|
||||||
|
stepContainer.Start(true),
|
||||||
|
).Finally(
|
||||||
|
stepContainer.Remove().IfBool(!rc.Config.ReuseContainers),
|
||||||
|
)(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *StepContext) execAsComposite(ctx context.Context, step *model.Step, _ string, rc *RunContext, containerActionDir string, actionName string, _ string, action *model.Action, maybeCopyToActionDir func() error) error {
|
||||||
|
err := maybeCopyToActionDir()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for outputName, output := range action.Outputs {
|
||||||
|
re := regexp.MustCompile(`\${{ steps\.([a-zA-Z_][a-zA-Z0-9_-]+)\.outputs\.([a-zA-Z_][a-zA-Z0-9_-]+) }}`)
|
||||||
|
matches := re.FindStringSubmatch(output.Value)
|
||||||
|
if len(matches) > 2 {
|
||||||
|
if sc.RunContext.OutputMappings == nil {
|
||||||
|
sc.RunContext.OutputMappings = make(map[MappableOutput]MappableOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
k := MappableOutput{StepID: matches[1], OutputName: matches[2]}
|
||||||
|
v := MappableOutput{StepID: step.ID, OutputName: outputName}
|
||||||
|
sc.RunContext.OutputMappings[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executors := make([]common.Executor, 0, len(action.Runs.Steps))
|
||||||
|
stepID := 0
|
||||||
|
for _, compositeStep := range action.Runs.Steps {
|
||||||
|
stepClone := compositeStep
|
||||||
|
// Take a copy of the run context structure (rc is a pointer)
|
||||||
|
// Then take the address of the new structure
|
||||||
|
rcCloneStr := *rc
|
||||||
|
rcClone := &rcCloneStr
|
||||||
|
if stepClone.ID == "" {
|
||||||
|
stepClone.ID = fmt.Sprintf("composite-%d", stepID)
|
||||||
|
stepID++
|
||||||
|
}
|
||||||
|
rcClone.CurrentStep = stepClone.ID
|
||||||
|
|
||||||
|
if err := compositeStep.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the outputs for the composite steps
|
||||||
|
if _, ok := rcClone.StepResults[stepClone.ID]; !ok {
|
||||||
|
rcClone.StepResults[stepClone.ID] = &stepResult{
|
||||||
|
Success: true,
|
||||||
|
Outputs: make(map[string]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stepClone.Run = strings.ReplaceAll(stepClone.Run, "${{ github.action_path }}", filepath.Join(containerActionDir, actionName))
|
||||||
|
|
||||||
|
stepContext := StepContext{
|
||||||
|
RunContext: rcClone,
|
||||||
|
Step: &stepClone,
|
||||||
|
Env: mergeMaps(sc.Env, stepClone.Env),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpolate the outer inputs into the composite step with items
|
||||||
|
exprEval := sc.NewExpressionEvaluator()
|
||||||
|
for k, v := range stepContext.Step.With {
|
||||||
|
if strings.Contains(v, "inputs") {
|
||||||
|
stepContext.Step.With[k] = exprEval.Interpolate(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executors = append(executors, stepContext.Executor())
|
||||||
|
}
|
||||||
|
return common.NewPipelineExecutor(executors...)(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *StepContext) populateEnvsFromInput(action *model.Action, rc *RunContext) {
|
||||||
|
for inputID, input := range action.Inputs {
|
||||||
|
envKey := regexp.MustCompile("[^A-Z0-9-]").ReplaceAllString(strings.ToUpper(inputID), "_")
|
||||||
|
envKey = fmt.Sprintf("INPUT_%s", envKey)
|
||||||
|
if _, ok := sc.Env[envKey]; !ok {
|
||||||
|
sc.Env[envKey] = rc.ExprEval.Interpolate(input.Default)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type remoteAction struct {
|
type remoteAction struct {
|
||||||
URL string
|
URL string
|
||||||
Org string
|
Org string
|
||||||
|
|
41
pkg/runner/testdata/env-and-path/push.yaml
vendored
Normal file
41
pkg/runner/testdata/env-and-path/push.yaml
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
name: env-and-path
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: "Append to $GITHUB_PATH"
|
||||||
|
run: |
|
||||||
|
echo "$HOME/someFolder" >> $GITHUB_PATH
|
||||||
|
- name: "Append some more to $GITHUB_PATH"
|
||||||
|
run: |
|
||||||
|
echo "$HOME/someOtherFolder" >> $GITHUB_PATH
|
||||||
|
- name: "Check PATH"
|
||||||
|
run: |
|
||||||
|
if [[ ! "${PATH}" =~ .*"$HOME/"someFolder.*"$HOME/"someOtherFolder ]]; then
|
||||||
|
echo "${PATH} doesn't match .*someFolder.*someOtherFolder"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- name: "Write single line env to $GITHUB_ENV"
|
||||||
|
run: |
|
||||||
|
echo "KEY=value" >> $GITHUB_ENV
|
||||||
|
- name: "Check single line env"
|
||||||
|
run: |
|
||||||
|
if [[ "${KEY}" != "value" ]]; then
|
||||||
|
echo "${KEY} dosen't == 'value'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- name: "Write multiline env to $GITHUB_ENV"
|
||||||
|
run: |
|
||||||
|
echo 'KEY2<<EOF' >> $GITHUB_ENV
|
||||||
|
echo value2 >> $GITHUB_ENV
|
||||||
|
echo 'EOF' >> $GITHUB_ENV
|
||||||
|
- name: "Check multiline line env"
|
||||||
|
run: |
|
||||||
|
if [[ "${KEY2}" != "value2" ]]; then
|
||||||
|
echo "${KEY2} dosen't == 'value'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue