Fix composite input handling (#1345)
* test: add test case for #1319 * fix: setup of composite inputs This change fixes the composite action setup handling of inputs. All inputs are taken from the env now. The env is composed of the 'level above'. For example: - step env -> taken from run context - action env -> taken from step env - composite env -> taken from action env Before this change the env setup for steps, actions and composite run contexts was harder to understand as all parts looked into one of these: parent run context, step, action, composite run context. Now the 'data flow' is from higher levels to lower levels which should make it more clean. Fixes #1319 * test: add simple remote composite action test Since we don't have a remote composite test at all before this, we need at least the simplest case. This does not check every feature, but ensures basic availability of remote composite actions. * refactor: move ActionRef and ActionRepository Moving ActionRef and ActionRepository from RunContext into the step, allows us to remove the - more or less - ugly copy operations from the RunContext. This is more clean, as each step does hold the data required anyway and the RunContext shouldn't know about the action details. * refactor: remove unused properties
This commit is contained in:
parent
1bade27534
commit
679cac1677
17 changed files with 246 additions and 149 deletions
|
@ -215,7 +215,8 @@ 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
|
||||||
func (j *Job) GetMatrixes() []map[string]interface{} {
|
func (j *Job) GetMatrixes() []map[string]interface{} {
|
||||||
matrixes := make([]map[string]interface{}, 0)
|
matrixes := make([]map[string]interface{}, 0)
|
||||||
if j.Strategy != nil {
|
if j.Strategy != nil {
|
||||||
|
@ -376,8 +377,16 @@ func (s *Step) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Environments returns string-based key=value map for a step
|
// Environments returns string-based key=value map for a step
|
||||||
|
// Note: all keys are uppercase
|
||||||
func (s *Step) Environment() map[string]string {
|
func (s *Step) Environment() map[string]string {
|
||||||
return environment(s.Env)
|
env := environment(s.Env)
|
||||||
|
|
||||||
|
for k, v := range env {
|
||||||
|
delete(env, k)
|
||||||
|
env[strings.ToUpper(k)] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return env
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEnv gets the env for a step
|
// GetEnv gets the env for a step
|
||||||
|
@ -436,6 +445,22 @@ const (
|
||||||
StepTypeInvalid
|
StepTypeInvalid
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (s StepType) String() string {
|
||||||
|
switch s {
|
||||||
|
case StepTypeInvalid:
|
||||||
|
return "invalid"
|
||||||
|
case StepTypeRun:
|
||||||
|
return "run"
|
||||||
|
case StepTypeUsesActionLocal:
|
||||||
|
return "local-action"
|
||||||
|
case StepTypeUsesActionRemote:
|
||||||
|
return "remote-action"
|
||||||
|
case StepTypeUsesDockerURL:
|
||||||
|
return "docker"
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
// Type returns the type of the step
|
// Type returns the type of the step
|
||||||
func (s *Step) Type() StepType {
|
func (s *Step) Type() StepType {
|
||||||
if s.Run == "" && s.Uses == "" {
|
if s.Run == "" && s.Uses == "" {
|
||||||
|
|
|
@ -136,26 +136,10 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction
|
||||||
action := step.getActionModel()
|
action := step.getActionModel()
|
||||||
logger.Debugf("About to run action %v", action)
|
logger.Debugf("About to run action %v", action)
|
||||||
|
|
||||||
if remoteAction != nil {
|
err := setupActionEnv(ctx, step, remoteAction)
|
||||||
rc.ActionRepository = fmt.Sprintf("%s/%s", remoteAction.Org, remoteAction.Repo)
|
if err != nil {
|
||||||
rc.ActionRef = remoteAction.Ref
|
return err
|
||||||
} else {
|
|
||||||
rc.ActionRepository = ""
|
|
||||||
rc.ActionRef = ""
|
|
||||||
}
|
}
|
||||||
defer (func() {
|
|
||||||
// cleanup after the action is done, to avoid side-effects in
|
|
||||||
// the next step/action
|
|
||||||
rc.ActionRepository = ""
|
|
||||||
rc.ActionRef = ""
|
|
||||||
})()
|
|
||||||
|
|
||||||
// we need to merge with github-env again, since at the step setup
|
|
||||||
// time, we don't have all environment prepared
|
|
||||||
mergeIntoMap(step.getEnv(), rc.withGithubEnv(ctx, map[string]string{}))
|
|
||||||
|
|
||||||
populateEnvsFromSavedState(step.getEnv(), step, rc)
|
|
||||||
populateEnvsFromInput(ctx, step.getEnv(), action, rc)
|
|
||||||
|
|
||||||
actionLocation := path.Join(actionDir, actionPath)
|
actionLocation := path.Join(actionDir, actionPath)
|
||||||
actionName, containerActionDir := getContainerActionPaths(stepModel, actionLocation, rc)
|
actionName, containerActionDir := getContainerActionPaths(stepModel, actionLocation, rc)
|
||||||
|
@ -169,6 +153,7 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction
|
||||||
}
|
}
|
||||||
containerArgs := []string{"node", path.Join(containerActionDir, action.Runs.Main)}
|
containerArgs := []string{"node", path.Join(containerActionDir, action.Runs.Main)}
|
||||||
logger.Debugf("executing remote job container: %s", containerArgs)
|
logger.Debugf("executing remote job container: %s", containerArgs)
|
||||||
|
|
||||||
return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx)
|
return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx)
|
||||||
case model.ActionRunsUsingDocker:
|
case model.ActionRunsUsingDocker:
|
||||||
location := actionLocation
|
location := actionLocation
|
||||||
|
@ -193,6 +178,20 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setupActionEnv(ctx context.Context, step actionStep, remoteAction *remoteAction) error {
|
||||||
|
rc := step.getRunContext()
|
||||||
|
|
||||||
|
// A few fields in the environment (e.g. GITHUB_ACTION_REPOSITORY)
|
||||||
|
// are dependent on the action. That means we can complete the
|
||||||
|
// setup only after resolving the whole action model and cloning
|
||||||
|
// the action
|
||||||
|
rc.withGithubEnv(ctx, step.getGithubContext(ctx), *step.getEnv())
|
||||||
|
populateEnvsFromSavedState(step.getEnv(), step, rc)
|
||||||
|
populateEnvsFromInput(ctx, step.getEnv(), step.getActionModel(), rc)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// https://github.com/nektos/act/issues/228#issuecomment-629709055
|
// https://github.com/nektos/act/issues/228#issuecomment-629709055
|
||||||
// files in .gitignore are not copied in a Docker container
|
// files in .gitignore are not copied in a Docker container
|
||||||
// this causes issues with actions that ignore other important resources
|
// this causes issues with actions that ignore other important resources
|
||||||
|
@ -211,7 +210,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
|
||||||
func execAsDocker(ctx context.Context, step actionStep, actionName string, basedir string, localAction bool) error {
|
func execAsDocker(ctx context.Context, step actionStep, actionName string, basedir string, localAction bool) error {
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
rc := step.getRunContext()
|
rc := step.getRunContext()
|
||||||
|
@ -299,11 +299,8 @@ func execAsDocker(ctx context.Context, step actionStep, actionName string, based
|
||||||
func evalDockerArgs(ctx context.Context, step step, action *model.Action, cmd *[]string) {
|
func evalDockerArgs(ctx context.Context, step step, action *model.Action, cmd *[]string) {
|
||||||
rc := step.getRunContext()
|
rc := step.getRunContext()
|
||||||
stepModel := step.getStepModel()
|
stepModel := step.getStepModel()
|
||||||
oldInputs := rc.Inputs
|
|
||||||
defer func() {
|
inputs := make(map[string]string)
|
||||||
rc.Inputs = oldInputs
|
|
||||||
}()
|
|
||||||
inputs := make(map[string]interface{})
|
|
||||||
eval := rc.NewExpressionEvaluator(ctx)
|
eval := rc.NewExpressionEvaluator(ctx)
|
||||||
// Set Defaults
|
// Set Defaults
|
||||||
for k, input := range action.Inputs {
|
for k, input := range action.Inputs {
|
||||||
|
@ -314,7 +311,8 @@ func evalDockerArgs(ctx context.Context, step step, action *model.Action, cmd *[
|
||||||
inputs[k] = eval.Interpolate(ctx, v)
|
inputs[k] = eval.Interpolate(ctx, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rc.Inputs = inputs
|
mergeIntoMap(step.getEnv(), inputs)
|
||||||
|
|
||||||
stepEE := rc.NewStepExpressionEvaluator(ctx, step)
|
stepEE := rc.NewStepExpressionEvaluator(ctx, step)
|
||||||
for i, v := range *cmd {
|
for i, v := range *cmd {
|
||||||
(*cmd)[i] = stepEE.Interpolate(ctx, v)
|
(*cmd)[i] = stepEE.Interpolate(ctx, v)
|
||||||
|
@ -372,29 +370,6 @@ func newStepContainer(ctx context.Context, step step, image string, cmd []string
|
||||||
return stepContainer
|
return stepContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RunContext) setupActionInputs(ctx context.Context, step actionStep) {
|
|
||||||
if step.getActionModel() == nil {
|
|
||||||
// e.g. local checkout skip has no action model
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
stepModel := step.getStepModel()
|
|
||||||
action := step.getActionModel()
|
|
||||||
|
|
||||||
eval := rc.NewExpressionEvaluator(ctx)
|
|
||||||
inputs := make(map[string]interface{})
|
|
||||||
for k, input := range action.Inputs {
|
|
||||||
inputs[k] = eval.Interpolate(ctx, input.Default)
|
|
||||||
}
|
|
||||||
if stepModel.With != nil {
|
|
||||||
for k, v := range stepModel.With {
|
|
||||||
inputs[k] = eval.Interpolate(ctx, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rc.Inputs = inputs
|
|
||||||
}
|
|
||||||
|
|
||||||
func populateEnvsFromSavedState(env *map[string]string, step actionStep, rc *RunContext) {
|
func populateEnvsFromSavedState(env *map[string]string, step actionStep, rc *RunContext) {
|
||||||
stepResult := rc.StepResults[step.getStepModel().ID]
|
stepResult := rc.StepResults[step.getStepModel().ID]
|
||||||
if stepResult != nil {
|
if stepResult != nil {
|
||||||
|
@ -513,7 +488,10 @@ func runPreStep(step actionStep) common.Executor {
|
||||||
return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx)
|
return rc.execJobContainer(containerArgs, *step.getEnv(), "", "")(ctx)
|
||||||
|
|
||||||
case model.ActionRunsUsingComposite:
|
case model.ActionRunsUsingComposite:
|
||||||
step.getCompositeRunContext(ctx).updateCompositeRunContext(ctx, step.getRunContext(), step)
|
if step.getCompositeSteps() == nil {
|
||||||
|
step.getCompositeRunContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
return step.getCompositeSteps().pre(ctx)
|
return step.getCompositeSteps().pre(ctx)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -601,7 +579,6 @@ func runPostStep(step actionStep) common.Executor {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
step.getCompositeRunContext(ctx).updateCompositeRunContext(ctx, step.getRunContext(), step)
|
|
||||||
return step.getCompositeSteps().post(ctx)
|
return step.getCompositeSteps().post(ctx)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -3,37 +3,43 @@ package runner
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/common"
|
"github.com/nektos/act/pkg/common"
|
||||||
"github.com/nektos/act/pkg/model"
|
"github.com/nektos/act/pkg/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func evaluteCompositeInputAndEnv(ctx context.Context, parent *RunContext, step actionStep) (inputs map[string]interface{}, env map[string]string) {
|
func evaluateCompositeInputAndEnv(ctx context.Context, parent *RunContext, step actionStep) map[string]string {
|
||||||
eval := parent.NewExpressionEvaluator(ctx)
|
env := make(map[string]string)
|
||||||
|
stepEnv := *step.getEnv()
|
||||||
inputs = make(map[string]interface{})
|
for k, v := range stepEnv {
|
||||||
for k, input := range step.getActionModel().Inputs {
|
// do not set current inputs into composite action
|
||||||
inputs[k] = eval.Interpolate(ctx, input.Default)
|
// the required inputs are added in the second loop
|
||||||
}
|
if !strings.HasPrefix(k, "INPUT_") {
|
||||||
if step.getStepModel().With != nil {
|
env[k] = v
|
||||||
for k, v := range step.getStepModel().With {
|
|
||||||
inputs[k] = eval.Interpolate(ctx, v)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
env = make(map[string]string)
|
for inputID, input := range step.getActionModel().Inputs {
|
||||||
for k, v := range parent.Env {
|
envKey := regexp.MustCompile("[^A-Z0-9-]").ReplaceAllString(strings.ToUpper(inputID), "_")
|
||||||
env[k] = eval.Interpolate(ctx, v)
|
envKey = fmt.Sprintf("INPUT_%s", strings.ToUpper(envKey))
|
||||||
}
|
|
||||||
for k, v := range step.getStepModel().Environment() {
|
// lookup if key is defined in the step but the the already
|
||||||
env[k] = eval.Interpolate(ctx, v)
|
// evaluated value from the environment
|
||||||
|
_, defined := step.getStepModel().With[inputID]
|
||||||
|
if value, ok := stepEnv[envKey]; defined && ok {
|
||||||
|
env[envKey] = value
|
||||||
|
} else {
|
||||||
|
env[envKey] = input.Default
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return inputs, env
|
return env
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCompositeRunContext(ctx context.Context, parent *RunContext, step actionStep, actionPath string) *RunContext {
|
func newCompositeRunContext(ctx context.Context, parent *RunContext, step actionStep, actionPath string) *RunContext {
|
||||||
inputs, env := evaluteCompositeInputAndEnv(ctx, parent, step)
|
env := evaluateCompositeInputAndEnv(ctx, parent, step)
|
||||||
|
|
||||||
// run with the global config but without secrets
|
// run with the global config but without secrets
|
||||||
configCopy := *(parent.Config)
|
configCopy := *(parent.Config)
|
||||||
|
@ -52,57 +58,42 @@ func newCompositeRunContext(ctx context.Context, parent *RunContext, step action
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Config: &configCopy,
|
Config: &configCopy,
|
||||||
StepResults: map[string]*model.StepResult{},
|
StepResults: map[string]*model.StepResult{},
|
||||||
JobContainer: parent.JobContainer,
|
JobContainer: parent.JobContainer,
|
||||||
Inputs: inputs,
|
ActionPath: actionPath,
|
||||||
ActionPath: actionPath,
|
Env: env,
|
||||||
ActionRepository: parent.ActionRepository,
|
Masks: parent.Masks,
|
||||||
ActionRef: parent.ActionRef,
|
ExtraPath: parent.ExtraPath,
|
||||||
Env: env,
|
Parent: parent,
|
||||||
Masks: parent.Masks,
|
|
||||||
ExtraPath: parent.ExtraPath,
|
|
||||||
Parent: parent,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return compositerc
|
return compositerc
|
||||||
}
|
}
|
||||||
|
|
||||||
// This updates a composite context inputs, env and masks.
|
|
||||||
// This is needed to re-evalute/update that context between pre/main/post steps.
|
|
||||||
// Some of the inputs/env may requires the results of in-between steps.
|
|
||||||
func (rc *RunContext) updateCompositeRunContext(ctx context.Context, parent *RunContext, step actionStep) {
|
|
||||||
inputs, env := evaluteCompositeInputAndEnv(ctx, parent, step)
|
|
||||||
|
|
||||||
rc.Inputs = inputs
|
|
||||||
rc.Env = env
|
|
||||||
rc.Masks = append(rc.Masks, parent.Masks...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func execAsComposite(step actionStep) common.Executor {
|
func execAsComposite(step actionStep) common.Executor {
|
||||||
rc := step.getRunContext()
|
rc := step.getRunContext()
|
||||||
action := step.getActionModel()
|
action := step.getActionModel()
|
||||||
|
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
compositerc := step.getCompositeRunContext(ctx)
|
compositeRC := step.getCompositeRunContext(ctx)
|
||||||
|
|
||||||
steps := step.getCompositeSteps()
|
steps := step.getCompositeSteps()
|
||||||
|
|
||||||
ctx = WithCompositeLogger(ctx, &compositerc.Masks)
|
ctx = WithCompositeLogger(ctx, &compositeRC.Masks)
|
||||||
|
|
||||||
compositerc.updateCompositeRunContext(ctx, rc, step)
|
|
||||||
err := steps.main(ctx)
|
err := steps.main(ctx)
|
||||||
|
|
||||||
// Map outputs from composite RunContext to job RunContext
|
// Map outputs from composite RunContext to job RunContext
|
||||||
eval := compositerc.NewExpressionEvaluator(ctx)
|
eval := compositeRC.NewExpressionEvaluator(ctx)
|
||||||
for outputName, output := range action.Outputs {
|
for outputName, output := range action.Outputs {
|
||||||
rc.setOutput(ctx, map[string]string{
|
rc.setOutput(ctx, map[string]string{
|
||||||
"name": outputName,
|
"name": outputName,
|
||||||
}, eval.Interpolate(ctx, output.Value))
|
}, eval.Interpolate(ctx, output.Value))
|
||||||
}
|
}
|
||||||
|
|
||||||
rc.Masks = append(rc.Masks, compositerc.Masks...)
|
rc.Masks = append(rc.Masks, compositeRC.Masks...)
|
||||||
rc.ExtraPath = compositerc.ExtraPath
|
rc.ExtraPath = compositeRC.ExtraPath
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,10 +187,8 @@ func TestActionRunner(t *testing.T) {
|
||||||
Uses: "org/repo/path@ref",
|
Uses: "org/repo/path@ref",
|
||||||
},
|
},
|
||||||
RunContext: &RunContext{
|
RunContext: &RunContext{
|
||||||
ActionRepository: "org/repo",
|
ActionPath: "path",
|
||||||
ActionPath: "path",
|
Config: &Config{},
|
||||||
ActionRef: "ref",
|
|
||||||
Config: &Config{},
|
|
||||||
Run: &model.Run{
|
Run: &model.Run{
|
||||||
JobID: "job",
|
JobID: "job",
|
||||||
Workflow: &model.Workflow{
|
Workflow: &model.Workflow{
|
||||||
|
|
|
@ -39,6 +39,13 @@ func (rc *RunContext) NewExpressionEvaluator(ctx context.Context) ExpressionEval
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inputs := make(map[string]interface{})
|
||||||
|
for k, v := range rc.GetEnv() {
|
||||||
|
if strings.HasPrefix(k, "INPUT_") {
|
||||||
|
inputs[strings.ToLower(strings.TrimPrefix(k, "INPUT_"))] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ee := &exprparser.EvaluationEnvironment{
|
ee := &exprparser.EvaluationEnvironment{
|
||||||
Github: rc.getGithubContext(ctx),
|
Github: rc.getGithubContext(ctx),
|
||||||
Env: rc.GetEnv(),
|
Env: rc.GetEnv(),
|
||||||
|
@ -56,7 +63,7 @@ func (rc *RunContext) NewExpressionEvaluator(ctx context.Context) ExpressionEval
|
||||||
Strategy: strategy,
|
Strategy: strategy,
|
||||||
Matrix: rc.Matrix,
|
Matrix: rc.Matrix,
|
||||||
Needs: using,
|
Needs: using,
|
||||||
Inputs: rc.Inputs,
|
Inputs: inputs,
|
||||||
}
|
}
|
||||||
return expressionEvaluator{
|
return expressionEvaluator{
|
||||||
interpreter: exprparser.NewInterpeter(ee, exprparser.Config{
|
interpreter: exprparser.NewInterpeter(ee, exprparser.Config{
|
||||||
|
@ -87,8 +94,15 @@ func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inputs := make(map[string]interface{})
|
||||||
|
for k, v := range *step.getEnv() {
|
||||||
|
if strings.HasPrefix(k, "INPUT_") {
|
||||||
|
inputs[strings.ToLower(strings.TrimPrefix(k, "INPUT_"))] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ee := &exprparser.EvaluationEnvironment{
|
ee := &exprparser.EvaluationEnvironment{
|
||||||
Github: rc.getGithubContext(ctx),
|
Github: step.getGithubContext(ctx),
|
||||||
Env: *step.getEnv(),
|
Env: *step.getEnv(),
|
||||||
Job: rc.getJobContext(),
|
Job: rc.getJobContext(),
|
||||||
Steps: rc.getStepsContext(),
|
Steps: rc.getStepsContext(),
|
||||||
|
@ -104,7 +118,7 @@ func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step)
|
||||||
Needs: using,
|
Needs: using,
|
||||||
// todo: should be unavailable
|
// todo: should be unavailable
|
||||||
// but required to interpolate/evaluate the inputs in actions/composite
|
// but required to interpolate/evaluate the inputs in actions/composite
|
||||||
Inputs: rc.Inputs,
|
Inputs: inputs,
|
||||||
}
|
}
|
||||||
return expressionEvaluator{
|
return expressionEvaluator{
|
||||||
interpreter: exprparser.NewInterpeter(ee, exprparser.Config{
|
interpreter: exprparser.NewInterpeter(ee, exprparser.Config{
|
||||||
|
|
|
@ -27,25 +27,22 @@ const ActPath string = "/var/run/act"
|
||||||
|
|
||||||
// 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]interface{}
|
||||||
Run *model.Run
|
Run *model.Run
|
||||||
EventJSON string
|
EventJSON string
|
||||||
Env map[string]string
|
Env map[string]string
|
||||||
ExtraPath []string
|
ExtraPath []string
|
||||||
CurrentStep string
|
CurrentStep string
|
||||||
StepResults map[string]*model.StepResult
|
StepResults map[string]*model.StepResult
|
||||||
ExprEval ExpressionEvaluator
|
ExprEval ExpressionEvaluator
|
||||||
JobContainer container.Container
|
JobContainer container.Container
|
||||||
OutputMappings map[MappableOutput]MappableOutput
|
OutputMappings map[MappableOutput]MappableOutput
|
||||||
JobName string
|
JobName string
|
||||||
ActionPath string
|
ActionPath string
|
||||||
ActionRef string
|
Parent *RunContext
|
||||||
ActionRepository string
|
Masks []string
|
||||||
Inputs map[string]interface{}
|
|
||||||
Parent *RunContext
|
|
||||||
Masks []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RunContext) AddMask(mask string) {
|
func (rc *RunContext) AddMask(mask string) {
|
||||||
|
@ -438,8 +435,6 @@ func (rc *RunContext) getGithubContext(ctx context.Context) *model.GithubContext
|
||||||
Action: rc.CurrentStep,
|
Action: rc.CurrentStep,
|
||||||
Token: rc.Config.Token,
|
Token: rc.Config.Token,
|
||||||
ActionPath: rc.ActionPath,
|
ActionPath: rc.ActionPath,
|
||||||
ActionRef: rc.ActionRef,
|
|
||||||
ActionRepository: rc.ActionRepository,
|
|
||||||
RepositoryOwner: rc.Config.Env["GITHUB_REPOSITORY_OWNER"],
|
RepositoryOwner: rc.Config.Env["GITHUB_REPOSITORY_OWNER"],
|
||||||
RetentionDays: rc.Config.Env["GITHUB_RETENTION_DAYS"],
|
RetentionDays: rc.Config.Env["GITHUB_RETENTION_DAYS"],
|
||||||
RunnerPerflog: rc.Config.Env["RUNNER_PERFLOG"],
|
RunnerPerflog: rc.Config.Env["RUNNER_PERFLOG"],
|
||||||
|
@ -557,8 +552,7 @@ func nestedMapLookup(m map[string]interface{}, ks ...string) (rval interface{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RunContext) withGithubEnv(ctx context.Context, env map[string]string) map[string]string {
|
func (rc *RunContext) withGithubEnv(ctx context.Context, github *model.GithubContext, env map[string]string) map[string]string {
|
||||||
github := rc.getGithubContext(ctx)
|
|
||||||
env["CI"] = "true"
|
env["CI"] = "true"
|
||||||
env["GITHUB_ENV"] = ActPath + "/workflow/envs.txt"
|
env["GITHUB_ENV"] = ActPath + "/workflow/envs.txt"
|
||||||
env["GITHUB_PATH"] = ActPath + "/workflow/paths.txt"
|
env["GITHUB_PATH"] = ActPath + "/workflow/paths.txt"
|
||||||
|
|
|
@ -16,6 +16,7 @@ type step interface {
|
||||||
post() common.Executor
|
post() common.Executor
|
||||||
|
|
||||||
getRunContext() *RunContext
|
getRunContext() *RunContext
|
||||||
|
getGithubContext(ctx context.Context) *model.GithubContext
|
||||||
getStepModel() *model.Step
|
getStepModel() *model.Step
|
||||||
getEnv() *map[string]string
|
getEnv() *map[string]string
|
||||||
getIfExpression(context context.Context, stage stepStage) string
|
getIfExpression(context context.Context, stage stepStage) string
|
||||||
|
@ -136,9 +137,10 @@ func setupEnv(ctx context.Context, step step) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
mergeIntoMap(step.getEnv(), step.getStepModel().GetEnv()) // step env should not be overwritten
|
// merge step env last, since it should not be overwritten
|
||||||
|
mergeIntoMap(step.getEnv(), step.getStepModel().GetEnv())
|
||||||
|
|
||||||
exprEval := rc.NewStepExpressionEvaluator(ctx, step)
|
exprEval := rc.NewExpressionEvaluator(ctx)
|
||||||
for k, v := range *step.getEnv() {
|
for k, v := range *step.getEnv() {
|
||||||
(*step.getEnv())[k] = exprEval.Interpolate(ctx, v)
|
(*step.getEnv())[k] = exprEval.Interpolate(ctx, v)
|
||||||
}
|
}
|
||||||
|
@ -169,7 +171,7 @@ func mergeEnv(ctx context.Context, step step) {
|
||||||
(*env)["PATH"] += `:` + p
|
(*env)["PATH"] += `:` + p
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeIntoMap(env, rc.withGithubEnv(ctx, *env))
|
rc.withGithubEnv(ctx, step.getGithubContext(ctx), *env)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isStepEnabled(ctx context.Context, expr string, step step, stage stepStage) (bool, error) {
|
func isStepEnabled(ctx context.Context, expr string, step step, stage stepStage) (bool, error) {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -55,7 +54,7 @@ func (sal *stepActionLocal) main() common.Executor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actionModel, err := sal.readAction(ctx, sal.Step, actionDir, "", localReader(ctx), ioutil.WriteFile)
|
actionModel, err := sal.readAction(ctx, sal.Step, actionDir, "", localReader(ctx), os.WriteFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -74,6 +73,10 @@ func (sal *stepActionLocal) getRunContext() *RunContext {
|
||||||
return sal.RunContext
|
return sal.RunContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sal *stepActionLocal) getGithubContext(ctx context.Context) *model.GithubContext {
|
||||||
|
return sal.getRunContext().getGithubContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func (sal *stepActionLocal) getStepModel() *model.Step {
|
func (sal *stepActionLocal) getStepModel() *model.Step {
|
||||||
return sal.Step
|
return sal.Step
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -49,7 +48,7 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
|
||||||
|
|
||||||
sar.remoteAction.URL = sar.RunContext.Config.GitHubInstance
|
sar.remoteAction.URL = sar.RunContext.Config.GitHubInstance
|
||||||
|
|
||||||
github := sar.RunContext.getGithubContext(ctx)
|
github := sar.getGithubContext(ctx)
|
||||||
if sar.remoteAction.IsCheckout() && isLocalCheckout(github, sar.Step) && !sar.RunContext.Config.NoSkipCheckout {
|
if sar.remoteAction.IsCheckout() && isLocalCheckout(github, sar.Step) && !sar.RunContext.Config.NoSkipCheckout {
|
||||||
common.Logger(ctx).Debugf("Skipping local actions/checkout because workdir was already copied")
|
common.Logger(ctx).Debugf("Skipping local actions/checkout because workdir was already copied")
|
||||||
return nil
|
return nil
|
||||||
|
@ -92,14 +91,10 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
|
||||||
return common.NewPipelineExecutor(
|
return common.NewPipelineExecutor(
|
||||||
ntErr,
|
ntErr,
|
||||||
func(ctx context.Context) error {
|
func(ctx context.Context) error {
|
||||||
actionModel, err := sar.readAction(ctx, sar.Step, actionDir, sar.remoteAction.Path, remoteReader(ctx), ioutil.WriteFile)
|
actionModel, err := sar.readAction(ctx, sar.Step, actionDir, sar.remoteAction.Path, remoteReader(ctx), os.WriteFile)
|
||||||
sar.action = actionModel
|
sar.action = actionModel
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
func(ctx context.Context) error {
|
|
||||||
sar.RunContext.setupActionInputs(ctx, sar)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
)(ctx)
|
)(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,7 +111,7 @@ func (sar *stepActionRemote) main() common.Executor {
|
||||||
return common.NewPipelineExecutor(
|
return common.NewPipelineExecutor(
|
||||||
sar.prepareActionExecutor(),
|
sar.prepareActionExecutor(),
|
||||||
runStepExecutor(sar, stepStageMain, func(ctx context.Context) error {
|
runStepExecutor(sar, stepStageMain, func(ctx context.Context) error {
|
||||||
github := sar.RunContext.getGithubContext(ctx)
|
github := sar.getGithubContext(ctx)
|
||||||
if sar.remoteAction.IsCheckout() && isLocalCheckout(github, sar.Step) && !sar.RunContext.Config.NoSkipCheckout {
|
if sar.remoteAction.IsCheckout() && isLocalCheckout(github, sar.Step) && !sar.RunContext.Config.NoSkipCheckout {
|
||||||
if sar.RunContext.Config.BindWorkdir {
|
if sar.RunContext.Config.BindWorkdir {
|
||||||
common.Logger(ctx).Debugf("Skipping local actions/checkout because you bound your workspace")
|
common.Logger(ctx).Debugf("Skipping local actions/checkout because you bound your workspace")
|
||||||
|
@ -129,9 +124,7 @@ func (sar *stepActionRemote) main() common.Executor {
|
||||||
|
|
||||||
actionDir := fmt.Sprintf("%s/%s", sar.RunContext.ActionCacheDir(), strings.ReplaceAll(sar.Step.Uses, "/", "-"))
|
actionDir := fmt.Sprintf("%s/%s", sar.RunContext.ActionCacheDir(), strings.ReplaceAll(sar.Step.Uses, "/", "-"))
|
||||||
|
|
||||||
return common.NewPipelineExecutor(
|
return sar.runAction(sar, actionDir, sar.remoteAction)(ctx)
|
||||||
sar.runAction(sar, actionDir, sar.remoteAction),
|
|
||||||
)(ctx)
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -144,6 +137,19 @@ func (sar *stepActionRemote) getRunContext() *RunContext {
|
||||||
return sar.RunContext
|
return sar.RunContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sar *stepActionRemote) getGithubContext(ctx context.Context) *model.GithubContext {
|
||||||
|
ghc := sar.getRunContext().getGithubContext(ctx)
|
||||||
|
|
||||||
|
// extend github context if we already have an initialized remoteAction
|
||||||
|
remoteAction := sar.remoteAction
|
||||||
|
if remoteAction != nil {
|
||||||
|
ghc.ActionRepository = fmt.Sprintf("%s/%s", remoteAction.Org, remoteAction.Repo)
|
||||||
|
ghc.ActionRef = remoteAction.Ref
|
||||||
|
}
|
||||||
|
|
||||||
|
return ghc
|
||||||
|
}
|
||||||
|
|
||||||
func (sar *stepActionRemote) getStepModel() *model.Step {
|
func (sar *stepActionRemote) getStepModel() *model.Step {
|
||||||
return sar.Step
|
return sar.Step
|
||||||
}
|
}
|
||||||
|
@ -155,7 +161,7 @@ func (sar *stepActionRemote) getEnv() *map[string]string {
|
||||||
func (sar *stepActionRemote) getIfExpression(ctx context.Context, stage stepStage) string {
|
func (sar *stepActionRemote) getIfExpression(ctx context.Context, stage stepStage) string {
|
||||||
switch stage {
|
switch stage {
|
||||||
case stepStagePre:
|
case stepStagePre:
|
||||||
github := sar.RunContext.getGithubContext(ctx)
|
github := sar.getGithubContext(ctx)
|
||||||
if sar.remoteAction.IsCheckout() && isLocalCheckout(github, sar.Step) && !sar.RunContext.Config.NoSkipCheckout {
|
if sar.remoteAction.IsCheckout() && isLocalCheckout(github, sar.Step) && !sar.RunContext.Config.NoSkipCheckout {
|
||||||
// skip local checkout pre step
|
// skip local checkout pre step
|
||||||
return "false"
|
return "false"
|
||||||
|
|
|
@ -39,6 +39,10 @@ func (sd *stepDocker) getRunContext() *RunContext {
|
||||||
return sd.RunContext
|
return sd.RunContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sd *stepDocker) getGithubContext(ctx context.Context) *model.GithubContext {
|
||||||
|
return sd.getRunContext().getGithubContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func (sd *stepDocker) getStepModel() *model.Step {
|
func (sd *stepDocker) getStepModel() *model.Step {
|
||||||
return sd.Step
|
return sd.Step
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,10 @@ func (sr *stepRun) getRunContext() *RunContext {
|
||||||
return sr.RunContext
|
return sr.RunContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sr *stepRun) getGithubContext(ctx context.Context) *model.GithubContext {
|
||||||
|
return sr.getRunContext().getGithubContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func (sr *stepRun) getStepModel() *model.Step {
|
func (sr *stepRun) getStepModel() *model.Step {
|
||||||
return sr.Step
|
return sr.Step
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,11 @@ func (sm *stepMock) getRunContext() *RunContext {
|
||||||
return args.Get(0).(*RunContext)
|
return args.Get(0).(*RunContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sm *stepMock) getGithubContext(ctx context.Context) *model.GithubContext {
|
||||||
|
args := sm.Called()
|
||||||
|
return args.Get(0).(*RunContext).getGithubContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func (sm *stepMock) getStepModel() *model.Step {
|
func (sm *stepMock) getStepModel() *model.Step {
|
||||||
args := sm.Called()
|
args := sm.Called()
|
||||||
return args.Get(0).(*model.Step)
|
return args.Get(0).(*model.Step)
|
||||||
|
@ -142,6 +147,7 @@ func TestSetupEnv(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
sm.On("getRunContext").Return(rc)
|
sm.On("getRunContext").Return(rc)
|
||||||
|
sm.On("getGithubContext").Return(rc)
|
||||||
sm.On("getStepModel").Return(step)
|
sm.On("getStepModel").Return(step)
|
||||||
sm.On("getEnv").Return(&env)
|
sm.On("getEnv").Return(&env)
|
||||||
|
|
||||||
|
|
19
pkg/runner/testdata/uses-composite-with-inputs/action/action.yml
vendored
Normal file
19
pkg/runner/testdata/uses-composite-with-inputs/action/action.yml
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
name: "action"
|
||||||
|
description: "action"
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
some:
|
||||||
|
description: "some input"
|
||||||
|
required: true
|
||||||
|
outputs:
|
||||||
|
out:
|
||||||
|
description: "some output"
|
||||||
|
value: "output value"
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- run: |
|
||||||
|
echo "action input=${{ inputs.some }}"
|
||||||
|
[[ "${{ inputs.some == 'value' }}" = "true" ]] || exit 1
|
||||||
|
shell: bash
|
22
pkg/runner/testdata/uses-composite-with-inputs/composite/action.yml
vendored
Normal file
22
pkg/runner/testdata/uses-composite-with-inputs/composite/action.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
name: "composite"
|
||||||
|
description: "composite"
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
composite-input:
|
||||||
|
description: "value"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: test input value
|
||||||
|
run: |
|
||||||
|
echo "input value 1=${{ inputs.composite-input }}"
|
||||||
|
[[ "${{ inputs.composite-input == 'value' }}" = "true" ]] || exit 1
|
||||||
|
shell: bash
|
||||||
|
- uses: nektos/act-test-actions/js@main
|
||||||
|
- name: test input value again
|
||||||
|
run: |
|
||||||
|
echo "input value 2=${{ inputs.composite-input }}"
|
||||||
|
[[ "${{ inputs.composite-input == 'value' }}" = "true" ]] || exit 1
|
||||||
|
shell: bash
|
30
pkg/runner/testdata/uses-composite-with-inputs/push.yml
vendored
Normal file
30
pkg/runner/testdata/uses-composite-with-inputs/push.yml
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
name: push
|
||||||
|
on: push
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: use simple composite action
|
||||||
|
uses: ./uses-composite-with-inputs/action
|
||||||
|
with:
|
||||||
|
some: value
|
||||||
|
- name: use nested composite action
|
||||||
|
uses: ./uses-composite-with-inputs/composite
|
||||||
|
with:
|
||||||
|
composite-input: value
|
||||||
|
###
|
||||||
|
#
|
||||||
|
# Remote composite test
|
||||||
|
#
|
||||||
|
- name: use remote composite action
|
||||||
|
id: remote-composite
|
||||||
|
uses: nektos/act-test-actions/composite@main
|
||||||
|
with:
|
||||||
|
input: value
|
||||||
|
- name: test remote composite output
|
||||||
|
run: |
|
||||||
|
echo "steps.remote-composite.outputs.output=${{ steps.remote-composite.outputs.output }}"
|
||||||
|
[[ "${{ steps.remote-composite.outputs.output == 'value' }}" = "true" ]] || exit 1
|
||||||
|
#
|
||||||
|
###
|
|
@ -35,7 +35,8 @@ outputs:
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- run: |
|
- name: echo inputs
|
||||||
|
run: |
|
||||||
echo "#####################################"
|
echo "#####################################"
|
||||||
echo "Inputs:"
|
echo "Inputs:"
|
||||||
echo "---"
|
echo "---"
|
||||||
|
|
|
@ -46,7 +46,8 @@ runs:
|
||||||
[[ "${{steps.composite.outputs.secret_output == format('{0}/{0}', inputs.test_input_optional)}}" = "true" ]] || exit 1
|
[[ "${{steps.composite.outputs.secret_output == format('{0}/{0}', inputs.test_input_optional)}}" = "true" ]] || exit 1
|
||||||
shell: bash
|
shell: bash
|
||||||
# Now test again with default values
|
# Now test again with default values
|
||||||
- uses: ./uses-composite/composite_action
|
- name: ./uses-composite/composite_action with defaults
|
||||||
|
uses: ./uses-composite/composite_action
|
||||||
id: composite2
|
id: composite2
|
||||||
with:
|
with:
|
||||||
test_input_required: 'test_input_required_value'
|
test_input_required: 'test_input_required_value'
|
||||||
|
@ -60,4 +61,4 @@ runs:
|
||||||
- run: |
|
- run: |
|
||||||
echo "steps.composite.outputs.secret_output=$COMPOSITE_ACTION_ENV_OUTPUT"
|
echo "steps.composite.outputs.secret_output=$COMPOSITE_ACTION_ENV_OUTPUT"
|
||||||
[[ "${{env.COMPOSITE_ACTION_ENV_OUTPUT == 'my test value' }}" = "true" ]] || exit 1
|
[[ "${{env.COMPOSITE_ACTION_ENV_OUTPUT == 'my test value' }}" = "true" ]] || exit 1
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
Loading…
Reference in a new issue