Feature: uses in composite (#793)
* Feature: uses in composite * Negate logic * Reduce complexity * Update step_context.go * Update step_context.go * Update step_context.go * Fix syntax error in test * Bump * Disable usage of actions/setup-node@v2 * Bump * Fix step id collision * Fix output command workaround * Make secrets context inaccessible in composite * Fix order after adding a workaround (needs tests) Fixes https://github.com/nektos/act/pull/793#issuecomment-922329838 * Evaluate env before passing one step deeper If env would contain any inputs, steps ctx or secrets there was undefined behaviour * [no ci] prepare secret test * Initial test pass inputs as env * Fix syntax error * extend test also for direct invoke * Fix passing provided env as composite output * Fix syntax error * toUpper 'no such secret', act has a bug * fix indent * Fix env outputs in composite * Test env outputs of composite * Fix inputs not defined in docker actions * Fix interpolate args input of docker actions * Fix lint * AllowCompositeIf now defaults to true see https://github.com/actions/runner/releases/tag/v2.284.0 * Fix lint * Fix env of docker action.yml * Test calling a local docker action from composite With input context hirachy * local-action-dockerfile Test pass on action/runner It seems action/runner ignores overrides of args, if the target docker action has the args property set. * Fix exec permissions of docker-local-noargs * Revert getStepsContext change * fix: handle composite action on error and continue This change is a follow up of https://github.com/nektos/act/pull/840 and integrates with https://github.com/nektos/act/pull/793 There are two things included here: - The default value for a step.if in an action need to be 'success()' - We need to hand the error from a composite action back to the calling executor Co-authored-by: Björn Brauer <bjoern.brauer@new-work.se> * Patch inputs can be bool, float64 and string for workflow_call Also inputs is now always defined, but may be null * Simplify cherry-picked commit * Minor style adjustments * Remove chmod +x from tests now fails on windows like before * Fix GITHUB_ACTION_PATH some action env vars Fixes GITHUB_ACTION_REPOSITORY, GITHUB_ACTION_REF. * Add comment to CompositeRestrictions Co-authored-by: Markus Wolf <markus.wolf@new-work.se> Co-authored-by: Björn Brauer <bjoern.brauer@new-work.se> Co-authored-by: Ryan <me@hackerc.at> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
parent
2ef30c3776
commit
9868e13772
19 changed files with 465 additions and 159 deletions
|
@ -83,5 +83,16 @@ type Output struct {
|
||||||
func ReadAction(in io.Reader) (*Action, error) {
|
func ReadAction(in io.Reader) (*Action, error) {
|
||||||
a := new(Action)
|
a := new(Action)
|
||||||
err := yaml.NewDecoder(in).Decode(a)
|
err := yaml.NewDecoder(in).Decode(a)
|
||||||
return a, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range a.Runs.Steps {
|
||||||
|
step := &a.Runs.Steps[i]
|
||||||
|
if step.If.Value == "" {
|
||||||
|
step.If.Value = "success()"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,21 @@ type Workflow struct {
|
||||||
Defaults Defaults `yaml:"defaults"`
|
Defaults Defaults `yaml:"defaults"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CompositeRestrictions is the structure to control what is allowed in composite actions
|
||||||
|
type CompositeRestrictions struct {
|
||||||
|
AllowCompositeUses bool
|
||||||
|
AllowCompositeIf bool
|
||||||
|
AllowCompositeContinueOnError bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultCompositeRestrictions() *CompositeRestrictions {
|
||||||
|
return &CompositeRestrictions{
|
||||||
|
AllowCompositeUses: true,
|
||||||
|
AllowCompositeIf: true,
|
||||||
|
AllowCompositeContinueOnError: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// On events for the workflow
|
// On events for the workflow
|
||||||
func (w *Workflow) On() []string {
|
func (w *Workflow) On() []string {
|
||||||
switch w.RawOn.Kind {
|
switch w.RawOn.Kind {
|
||||||
|
@ -411,11 +426,18 @@ func (s *Step) Type() StepType {
|
||||||
return StepTypeUsesActionRemote
|
return StepTypeUsesActionRemote
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Step) Validate() error {
|
func (s *Step) Validate(config *CompositeRestrictions) error {
|
||||||
if s.Type() != StepTypeRun {
|
if config == nil {
|
||||||
|
config = defaultCompositeRestrictions()
|
||||||
|
}
|
||||||
|
if s.Type() != StepTypeRun && !config.AllowCompositeUses {
|
||||||
return fmt.Errorf("(StepID: %s): Unexpected value 'uses'", s.String())
|
return fmt.Errorf("(StepID: %s): Unexpected value 'uses'", s.String())
|
||||||
} else if s.Shell == "" {
|
} else if s.Type() == StepTypeRun && s.Shell == "" {
|
||||||
return fmt.Errorf("(StepID: %s): Required property is missing: 'shell'", s.String())
|
return fmt.Errorf("(StepID: %s): Required property is missing: 'shell'", s.String())
|
||||||
|
} else if !s.If.IsZero() && !config.AllowCompositeIf {
|
||||||
|
return fmt.Errorf("(StepID: %s): Property is not available: 'if'", s.String())
|
||||||
|
} else if s.ContinueOnError && !config.AllowCompositeContinueOnError {
|
||||||
|
return fmt.Errorf("(StepID: %s): Property is not available: 'continue-on-error'", s.String())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,6 @@ func (sc *StepContext) NewExpressionEvaluator() ExpressionEvaluator {
|
||||||
vm := sc.RunContext.newVM()
|
vm := sc.RunContext.newVM()
|
||||||
configers := []func(*otto.Otto){
|
configers := []func(*otto.Otto){
|
||||||
sc.vmEnv(),
|
sc.vmEnv(),
|
||||||
sc.vmInputs(),
|
|
||||||
|
|
||||||
sc.vmNeeds(),
|
sc.vmNeeds(),
|
||||||
sc.vmSuccess(),
|
sc.vmSuccess(),
|
||||||
sc.vmFailure(),
|
sc.vmFailure(),
|
||||||
|
@ -237,6 +235,7 @@ func (rc *RunContext) newVM() *otto.Otto {
|
||||||
rc.vmMatrix(),
|
rc.vmMatrix(),
|
||||||
rc.vmEnv(),
|
rc.vmEnv(),
|
||||||
rc.vmNeeds(),
|
rc.vmNeeds(),
|
||||||
|
rc.vmInputs(),
|
||||||
}
|
}
|
||||||
vm := otto.New()
|
vm := otto.New()
|
||||||
for _, configer := range configers {
|
for _, configer := range configers {
|
||||||
|
@ -447,22 +446,9 @@ func (sc *StepContext) vmEnv() func(*otto.Otto) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *StepContext) vmInputs() func(*otto.Otto) {
|
func (rc *RunContext) vmInputs() func(*otto.Otto) {
|
||||||
inputs := make(map[string]string)
|
|
||||||
|
|
||||||
// Set Defaults
|
|
||||||
if sc.Action != nil {
|
|
||||||
for k, input := range sc.Action.Inputs {
|
|
||||||
inputs[k] = sc.RunContext.NewExpressionEvaluator().Interpolate(input.Default)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range sc.Step.With {
|
|
||||||
inputs[k] = sc.RunContext.NewExpressionEvaluator().Interpolate(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(vm *otto.Otto) {
|
return func(vm *otto.Otto) {
|
||||||
_ = vm.Set("inputs", inputs)
|
_ = vm.Set("inputs", rc.Inputs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -587,7 +573,10 @@ func (rc *RunContext) vmRunner() func(*otto.Otto) {
|
||||||
|
|
||||||
func (rc *RunContext) vmSecrets() func(*otto.Otto) {
|
func (rc *RunContext) vmSecrets() func(*otto.Otto) {
|
||||||
return func(vm *otto.Otto) {
|
return func(vm *otto.Otto) {
|
||||||
_ = vm.Set("secrets", rc.Config.Secrets)
|
// Hide secrets from composite actions
|
||||||
|
if rc.Composite == nil {
|
||||||
|
_ = vm.Set("secrets", rc.Config.Secrets)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,19 +27,35 @@ 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]*stepResult
|
StepResults map[string]*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
|
||||||
|
ActionRef string
|
||||||
|
ActionRepository string
|
||||||
|
Composite *model.Action
|
||||||
|
Inputs map[string]interface{}
|
||||||
|
Parent *RunContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *RunContext) Clone() *RunContext {
|
||||||
|
clone := *rc
|
||||||
|
clone.CurrentStep = ""
|
||||||
|
clone.Composite = nil
|
||||||
|
clone.Inputs = nil
|
||||||
|
clone.StepResults = make(map[string]*stepResult)
|
||||||
|
clone.Parent = rc
|
||||||
|
return &clone
|
||||||
}
|
}
|
||||||
|
|
||||||
type MappableOutput struct {
|
type MappableOutput struct {
|
||||||
|
@ -310,6 +326,22 @@ func (rc *RunContext) Executor() common.Executor {
|
||||||
}).If(rc.isEnabled)
|
}).If(rc.isEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Executor returns a pipeline executor for all the steps in the job
|
||||||
|
func (rc *RunContext) CompositeExecutor() common.Executor {
|
||||||
|
steps := make([]common.Executor, 0)
|
||||||
|
|
||||||
|
for i, step := range rc.Composite.Runs.Steps {
|
||||||
|
if step.ID == "" {
|
||||||
|
step.ID = fmt.Sprintf("%d", i)
|
||||||
|
}
|
||||||
|
stepcopy := step
|
||||||
|
steps = append(steps, rc.newStepExecutor(&stepcopy))
|
||||||
|
}
|
||||||
|
|
||||||
|
steps = append(steps, common.JobError)
|
||||||
|
return common.NewPipelineExecutor(steps...)
|
||||||
|
}
|
||||||
|
|
||||||
func (rc *RunContext) newStepExecutor(step *model.Step) common.Executor {
|
func (rc *RunContext) newStepExecutor(step *model.Step) common.Executor {
|
||||||
sc := &StepContext{
|
sc := &StepContext{
|
||||||
RunContext: rc,
|
RunContext: rc,
|
||||||
|
@ -568,9 +600,9 @@ func (rc *RunContext) getGithubContext() *githubContext {
|
||||||
Workspace: rc.Config.ContainerWorkdir(),
|
Workspace: rc.Config.ContainerWorkdir(),
|
||||||
Action: rc.CurrentStep,
|
Action: rc.CurrentStep,
|
||||||
Token: rc.Config.Secrets["GITHUB_TOKEN"],
|
Token: rc.Config.Secrets["GITHUB_TOKEN"],
|
||||||
ActionPath: rc.Config.Env["GITHUB_ACTION_PATH"],
|
ActionPath: rc.ActionPath,
|
||||||
ActionRef: rc.Config.Env["RUNNER_ACTION_REF"],
|
ActionRef: rc.ActionRef,
|
||||||
ActionRepository: rc.Config.Env["RUNNER_ACTION_REPOSITORY"],
|
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"],
|
||||||
|
@ -737,9 +769,9 @@ func (rc *RunContext) withGithubEnv(env map[string]string) map[string]string {
|
||||||
env["GITHUB_RUN_ID"] = github.RunID
|
env["GITHUB_RUN_ID"] = github.RunID
|
||||||
env["GITHUB_RUN_NUMBER"] = github.RunNumber
|
env["GITHUB_RUN_NUMBER"] = github.RunNumber
|
||||||
env["GITHUB_ACTION"] = github.Action
|
env["GITHUB_ACTION"] = github.Action
|
||||||
if github.ActionPath != "" {
|
env["GITHUB_ACTION_PATH"] = github.ActionPath
|
||||||
env["GITHUB_ACTION_PATH"] = github.ActionPath
|
env["GITHUB_ACTION_REPOSITORY"] = github.ActionRepository
|
||||||
}
|
env["GITHUB_ACTION_REF"] = github.ActionRef
|
||||||
env["GITHUB_ACTIONS"] = "true"
|
env["GITHUB_ACTIONS"] = "true"
|
||||||
env["GITHUB_ACTOR"] = github.Actor
|
env["GITHUB_ACTOR"] = github.Actor
|
||||||
env["GITHUB_REPOSITORY"] = github.Repository
|
env["GITHUB_REPOSITORY"] = github.Repository
|
||||||
|
|
|
@ -21,31 +21,32 @@ type Runner interface {
|
||||||
|
|
||||||
// Config contains the config for a new runner
|
// Config contains the config for a new runner
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Actor string // the user that triggered the event
|
Actor string // the user that triggered the event
|
||||||
Workdir string // path to working directory
|
Workdir string // path to working directory
|
||||||
BindWorkdir bool // bind the workdir to the job container
|
BindWorkdir bool // bind the workdir to the job container
|
||||||
EventName string // name of event to run
|
EventName string // name of event to run
|
||||||
EventPath string // path to JSON file to use for event.json in containers
|
EventPath string // path to JSON file to use for event.json in containers
|
||||||
DefaultBranch string // name of the main branch for this repository
|
DefaultBranch string // name of the main branch for this repository
|
||||||
ReuseContainers bool // reuse containers to maintain state
|
ReuseContainers bool // reuse containers to maintain state
|
||||||
ForcePull bool // force pulling of the image, even if already present
|
ForcePull bool // force pulling of the image, even if already present
|
||||||
ForceRebuild bool // force rebuilding local docker image action
|
ForceRebuild bool // force rebuilding local docker image action
|
||||||
LogOutput bool // log the output from docker run
|
LogOutput bool // log the output from docker run
|
||||||
Env map[string]string // env for containers
|
Env map[string]string // env for containers
|
||||||
Secrets map[string]string // list of secrets
|
Secrets map[string]string // list of secrets
|
||||||
InsecureSecrets bool // switch hiding output when printing to terminal
|
InsecureSecrets bool // switch hiding output when printing to terminal
|
||||||
Platforms map[string]string // list of platforms
|
Platforms map[string]string // list of platforms
|
||||||
Privileged bool // use privileged mode
|
Privileged bool // use privileged mode
|
||||||
UsernsMode string // user namespace to use
|
UsernsMode string // user namespace to use
|
||||||
ContainerArchitecture string // Desired OS/architecture platform for running containers
|
ContainerArchitecture string // Desired OS/architecture platform for running containers
|
||||||
ContainerDaemonSocket string // Path to Docker daemon socket
|
ContainerDaemonSocket string // Path to Docker daemon socket
|
||||||
UseGitIgnore bool // controls if paths in .gitignore should not be copied into container, default true
|
UseGitIgnore bool // controls if paths in .gitignore should not be copied into container, default true
|
||||||
GitHubInstance string // GitHub instance to use, default "github.com"
|
GitHubInstance string // GitHub instance to use, default "github.com"
|
||||||
ContainerCapAdd []string // list of kernel capabilities to add to the containers
|
ContainerCapAdd []string // list of kernel capabilities to add to the containers
|
||||||
ContainerCapDrop []string // list of kernel capabilities to remove from the containers
|
ContainerCapDrop []string // list of kernel capabilities to remove from the containers
|
||||||
AutoRemove bool // controls if the container is automatically removed upon workflow completion
|
AutoRemove bool // controls if the container is automatically removed upon workflow completion
|
||||||
ArtifactServerPath string // the path where the artifact server stores uploads
|
ArtifactServerPath string // the path where the artifact server stores uploads
|
||||||
ArtifactServerPort string // the port the artifact server binds to
|
ArtifactServerPort string // the port the artifact server binds to
|
||||||
|
CompositeRestrictions *model.CompositeRestrictions // describes which features are available in composite actions
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolves the equivalent host path inside the container
|
// Resolves the equivalent host path inside the container
|
||||||
|
|
|
@ -112,6 +112,7 @@ func TestRunEvent(t *testing.T) {
|
||||||
{"testdata", "remote-action-js", "push", "", platforms, ""},
|
{"testdata", "remote-action-js", "push", "", platforms, ""},
|
||||||
{"testdata", "local-action-docker-url", "push", "", platforms, ""},
|
{"testdata", "local-action-docker-url", "push", "", platforms, ""},
|
||||||
{"testdata", "local-action-dockerfile", "push", "", platforms, ""},
|
{"testdata", "local-action-dockerfile", "push", "", platforms, ""},
|
||||||
|
{"testdata", "local-action-via-composite-dockerfile", "push", "", platforms, ""},
|
||||||
{"testdata", "local-action-js", "push", "", platforms, ""},
|
{"testdata", "local-action-js", "push", "", platforms, ""},
|
||||||
{"testdata", "matrix", "push", "", platforms, ""},
|
{"testdata", "matrix", "push", "", platforms, ""},
|
||||||
{"testdata", "matrix-include-exclude", "push", "", platforms, ""},
|
{"testdata", "matrix-include-exclude", "push", "", platforms, ""},
|
||||||
|
@ -119,6 +120,8 @@ func TestRunEvent(t *testing.T) {
|
||||||
{"testdata", "workdir", "push", "", platforms, ""},
|
{"testdata", "workdir", "push", "", platforms, ""},
|
||||||
{"testdata", "defaults-run", "push", "", platforms, ""},
|
{"testdata", "defaults-run", "push", "", platforms, ""},
|
||||||
{"testdata", "uses-composite", "push", "", platforms, ""},
|
{"testdata", "uses-composite", "push", "", platforms, ""},
|
||||||
|
{"testdata", "uses-composite-with-error", "push", "Job 'failing-composite-action' failed", platforms, ""},
|
||||||
|
{"testdata", "uses-nested-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", "env-and-path", "push", "", platforms, ""},
|
||||||
|
|
|
@ -66,7 +66,7 @@ func (sc *StepContext) Executor(ctx context.Context) common.Executor {
|
||||||
actionDir := filepath.Join(rc.Config.Workdir, step.Uses)
|
actionDir := filepath.Join(rc.Config.Workdir, step.Uses)
|
||||||
return common.NewPipelineExecutor(
|
return common.NewPipelineExecutor(
|
||||||
sc.setupAction(actionDir, "", true),
|
sc.setupAction(actionDir, "", true),
|
||||||
sc.runAction(actionDir, "", true),
|
sc.runAction(actionDir, "", "", "", true),
|
||||||
)
|
)
|
||||||
case model.StepTypeUsesActionRemote:
|
case model.StepTypeUsesActionRemote:
|
||||||
remoteAction := newRemoteAction(step.Uses)
|
remoteAction := newRemoteAction(step.Uses)
|
||||||
|
@ -105,7 +105,7 @@ func (sc *StepContext) Executor(ctx context.Context) common.Executor {
|
||||||
return common.NewPipelineExecutor(
|
return common.NewPipelineExecutor(
|
||||||
ntErr,
|
ntErr,
|
||||||
sc.setupAction(actionDir, remoteAction.Path, false),
|
sc.setupAction(actionDir, remoteAction.Path, false),
|
||||||
sc.runAction(actionDir, remoteAction.Path, false),
|
sc.runAction(actionDir, remoteAction.Path, remoteAction.Repo, remoteAction.Ref, false),
|
||||||
)
|
)
|
||||||
case model.StepTypeInvalid:
|
case model.StepTypeInvalid:
|
||||||
return common.NewErrorExecutor(fmt.Errorf("Invalid run/uses syntax for job:%s step:%+v", rc.Run, step))
|
return common.NewErrorExecutor(fmt.Errorf("Invalid run/uses syntax for job:%s step:%+v", rc.Run, step))
|
||||||
|
@ -228,6 +228,14 @@ func (sc *StepContext) setupShell() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getScriptName(rc *RunContext, step *model.Step) string {
|
||||||
|
scriptName := step.ID
|
||||||
|
for rcs := rc; rcs.Parent != nil; rcs = rcs.Parent {
|
||||||
|
scriptName = fmt.Sprintf("%s-composite-%s", rcs.Parent.CurrentStep, scriptName)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("workflow/%s", scriptName)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Currently we just ignore top level keys, BUT we should return proper error on them
|
// TODO: Currently we just ignore top level keys, BUT we should return proper error on them
|
||||||
// BUTx2 I leave this for when we rewrite act to use actionlint for workflow validation
|
// BUTx2 I leave this for when we rewrite act to use actionlint for workflow validation
|
||||||
// so we return proper errors before any execution or spawning containers
|
// so we return proper errors before any execution or spawning containers
|
||||||
|
@ -243,7 +251,7 @@ func (sc *StepContext) setupShellCommand() (name, script string, err error) {
|
||||||
|
|
||||||
scCmd := step.ShellCommand()
|
scCmd := step.ShellCommand()
|
||||||
|
|
||||||
name = fmt.Sprintf("workflow/%s", step.ID)
|
name = getScriptName(sc.RunContext, step)
|
||||||
|
|
||||||
// Reference: https://github.com/actions/runner/blob/8109c962f09d9acc473d92c595ff43afceddb347/src/Runner.Worker/Handlers/ScriptHandlerHelpers.cs#L47-L64
|
// Reference: https://github.com/actions/runner/blob/8109c962f09d9acc473d92c595ff43afceddb347/src/Runner.Worker/Handlers/ScriptHandlerHelpers.cs#L47-L64
|
||||||
// Reference: https://github.com/actions/runner/blob/8109c962f09d9acc473d92c595ff43afceddb347/src/Runner.Worker/Handlers/ScriptHandlerHelpers.cs#L19-L27
|
// Reference: https://github.com/actions/runner/blob/8109c962f09d9acc473d92c595ff43afceddb347/src/Runner.Worker/Handlers/ScriptHandlerHelpers.cs#L19-L27
|
||||||
|
@ -305,13 +313,6 @@ func (sc *StepContext) newStepContainer(ctx context.Context, image string, cmd [
|
||||||
for k, v := range sc.Env {
|
for k, v := range sc.Env {
|
||||||
envList = append(envList, fmt.Sprintf("%s=%s", k, v))
|
envList = append(envList, fmt.Sprintf("%s=%s", k, v))
|
||||||
}
|
}
|
||||||
stepEE := sc.NewExpressionEvaluator()
|
|
||||||
for i, v := range cmd {
|
|
||||||
cmd[i] = stepEE.Interpolate(v)
|
|
||||||
}
|
|
||||||
for i, v := range entrypoint {
|
|
||||||
entrypoint[i] = stepEE.Interpolate(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_TOOL_CACHE", "/opt/hostedtoolcache"))
|
envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_TOOL_CACHE", "/opt/hostedtoolcache"))
|
||||||
envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_OS", "Linux"))
|
envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_OS", "Linux"))
|
||||||
|
@ -345,11 +346,12 @@ func (sc *StepContext) runUsesContainer() common.Executor {
|
||||||
step := sc.Step
|
step := sc.Step
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
image := strings.TrimPrefix(step.Uses, "docker://")
|
image := strings.TrimPrefix(step.Uses, "docker://")
|
||||||
cmd, err := shellquote.Split(sc.RunContext.NewExpressionEvaluator().Interpolate(step.With["args"]))
|
eval := sc.RunContext.NewExpressionEvaluator()
|
||||||
|
cmd, err := shellquote.Split(eval.Interpolate(step.With["args"]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
entrypoint := strings.Fields(step.With["entrypoint"])
|
entrypoint := strings.Fields(eval.Interpolate(step.With["entrypoint"]))
|
||||||
stepContainer := sc.newStepContainer(ctx, image, cmd, entrypoint)
|
stepContainer := sc.newStepContainer(ctx, image, cmd, entrypoint)
|
||||||
|
|
||||||
return common.NewPipelineExecutor(
|
return common.NewPipelineExecutor(
|
||||||
|
@ -482,10 +484,21 @@ func (sc *StepContext) getContainerActionPaths(step *model.Step, actionDir strin
|
||||||
return actionName, containerActionDir
|
return actionName, containerActionDir
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *StepContext) runAction(actionDir string, actionPath string, localAction bool) common.Executor {
|
func (sc *StepContext) runAction(actionDir string, actionPath string, actionRepository string, actionRef string, localAction bool) common.Executor {
|
||||||
rc := sc.RunContext
|
rc := sc.RunContext
|
||||||
step := sc.Step
|
step := sc.Step
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
|
// Backup the parent composite action path and restore it on continue
|
||||||
|
parentActionPath := rc.ActionPath
|
||||||
|
parentActionRepository := rc.ActionRepository
|
||||||
|
parentActionRef := rc.ActionRef
|
||||||
|
defer func() {
|
||||||
|
rc.ActionPath = parentActionPath
|
||||||
|
rc.ActionRef = parentActionRef
|
||||||
|
rc.ActionRepository = parentActionRepository
|
||||||
|
}()
|
||||||
|
rc.ActionRef = actionRef
|
||||||
|
rc.ActionRepository = actionRepository
|
||||||
action := sc.Action
|
action := sc.Action
|
||||||
log.Debugf("About to run action %v", action)
|
log.Debugf("About to run action %v", action)
|
||||||
sc.populateEnvsFromInput(action, rc)
|
sc.populateEnvsFromInput(action, rc)
|
||||||
|
@ -497,17 +510,10 @@ func (sc *StepContext) runAction(actionDir string, actionPath string, localActio
|
||||||
}
|
}
|
||||||
actionName, containerActionDir := sc.getContainerActionPaths(step, actionLocation, rc)
|
actionName, containerActionDir := sc.getContainerActionPaths(step, actionLocation, rc)
|
||||||
|
|
||||||
sc.Env = mergeMaps(sc.Env, action.Runs.Env)
|
|
||||||
|
|
||||||
ee := sc.NewExpressionEvaluator()
|
|
||||||
for k, v := range sc.Env {
|
|
||||||
sc.Env[k] = ee.Interpolate(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("type=%v actionDir=%s actionPath=%s workdir=%s actionCacheDir=%s actionName=%s containerActionDir=%s", step.Type(), actionDir, actionPath, rc.Config.Workdir, rc.ActionCacheDir(), actionName, containerActionDir)
|
log.Debugf("type=%v actionDir=%s actionPath=%s workdir=%s actionCacheDir=%s actionName=%s containerActionDir=%s", step.Type(), actionDir, actionPath, rc.Config.Workdir, rc.ActionCacheDir(), actionName, containerActionDir)
|
||||||
|
|
||||||
maybeCopyToActionDir := func() error {
|
maybeCopyToActionDir := func() error {
|
||||||
sc.Env["GITHUB_ACTION_PATH"] = containerActionDir
|
rc.ActionPath = containerActionDir
|
||||||
if step.Type() != model.StepTypeUsesActionRemote {
|
if step.Type() != model.StepTypeUsesActionRemote {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -547,6 +553,37 @@ func (sc *StepContext) runAction(actionDir string, actionPath string, localActio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sc *StepContext) evalDockerArgs(action *model.Action, cmd *[]string) {
|
||||||
|
rc := sc.RunContext
|
||||||
|
step := sc.Step
|
||||||
|
oldInputs := rc.Inputs
|
||||||
|
defer func() {
|
||||||
|
rc.Inputs = oldInputs
|
||||||
|
}()
|
||||||
|
inputs := make(map[string]interface{})
|
||||||
|
eval := sc.RunContext.NewExpressionEvaluator()
|
||||||
|
// Set Defaults
|
||||||
|
for k, input := range action.Inputs {
|
||||||
|
inputs[k] = eval.Interpolate(input.Default)
|
||||||
|
}
|
||||||
|
if step.With != nil {
|
||||||
|
for k, v := range step.With {
|
||||||
|
inputs[k] = eval.Interpolate(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rc.Inputs = inputs
|
||||||
|
stepEE := sc.NewExpressionEvaluator()
|
||||||
|
for i, v := range *cmd {
|
||||||
|
(*cmd)[i] = stepEE.Interpolate(v)
|
||||||
|
}
|
||||||
|
sc.Env = mergeMaps(sc.Env, action.Runs.Env)
|
||||||
|
|
||||||
|
ee := sc.NewExpressionEvaluator()
|
||||||
|
for k, v := range sc.Env {
|
||||||
|
sc.Env[k] = ee.Interpolate(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: break out parts of function to reduce complexicity
|
// TODO: break out parts of function to reduce complexicity
|
||||||
// nolint:gocyclo
|
// nolint:gocyclo
|
||||||
func (sc *StepContext) execAsDocker(ctx context.Context, action *model.Action, actionName string, containerLocation string, actionLocation string, rc *RunContext, step *model.Step, localAction bool) error {
|
func (sc *StepContext) execAsDocker(ctx context.Context, action *model.Action, actionName string, containerLocation string, actionLocation string, rc *RunContext, step *model.Step, localAction bool) error {
|
||||||
|
@ -601,15 +638,16 @@ func (sc *StepContext) execAsDocker(ctx context.Context, action *model.Action, a
|
||||||
log.Debugf("image '%s' for architecture '%s' already exists", image, rc.Config.ContainerArchitecture)
|
log.Debugf("image '%s' for architecture '%s' already exists", image, rc.Config.ContainerArchitecture)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
eval := sc.NewExpressionEvaluator()
|
||||||
cmd, err := shellquote.Split(step.With["args"])
|
cmd, err := shellquote.Split(eval.Interpolate(step.With["args"]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
cmd = action.Runs.Args
|
cmd = action.Runs.Args
|
||||||
|
sc.evalDockerArgs(action, &cmd)
|
||||||
}
|
}
|
||||||
entrypoint := strings.Fields(step.With["entrypoint"])
|
entrypoint := strings.Fields(eval.Interpolate(step.With["entrypoint"]))
|
||||||
if len(entrypoint) == 0 {
|
if len(entrypoint) == 0 {
|
||||||
if action.Runs.Entrypoint != "" {
|
if action.Runs.Entrypoint != "" {
|
||||||
entrypoint, err = shellquote.Split(action.Runs.Entrypoint)
|
entrypoint, err = shellquote.Split(action.Runs.Entrypoint)
|
||||||
|
@ -638,74 +676,74 @@ func (sc *StepContext) execAsComposite(ctx context.Context, step *model.Step, _
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for outputName, output := range action.Outputs {
|
// Disable some features of composite actions, only for feature parity with github
|
||||||
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 {
|
for _, compositeStep := range action.Runs.Steps {
|
||||||
stepClone := compositeStep
|
if err := compositeStep.Validate(rc.Config.CompositeRestrictions); err != nil {
|
||||||
// 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the outputs for the composite steps
|
|
||||||
if _, ok := rcClone.StepResults[stepClone.ID]; !ok {
|
|
||||||
rcClone.StepResults[stepClone.ID] = &stepResult{
|
|
||||||
Conclusion: stepStatusSuccess,
|
|
||||||
Outputs: make(map[string]string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
env := stepClone.Environment()
|
|
||||||
stepContext := StepContext{
|
|
||||||
RunContext: rcClone,
|
|
||||||
Step: step,
|
|
||||||
Env: mergeMaps(sc.Env, env),
|
|
||||||
Action: action,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required to set github.action_path
|
|
||||||
if rcClone.Config.Env == nil {
|
|
||||||
// Workaround to get test working
|
|
||||||
rcClone.Config.Env = make(map[string]string)
|
|
||||||
}
|
|
||||||
rcClone.Config.Env["GITHUB_ACTION_PATH"] = sc.Env["GITHUB_ACTION_PATH"]
|
|
||||||
ev := stepContext.NewExpressionEvaluator()
|
|
||||||
// Required to interpolate inputs and github.action_path into the env map
|
|
||||||
stepContext.interpolateEnv(ev)
|
|
||||||
// Required to interpolate inputs, env and github.action_path into run steps
|
|
||||||
ev = stepContext.NewExpressionEvaluator()
|
|
||||||
stepClone.Run = ev.Interpolate(stepClone.Run)
|
|
||||||
stepClone.Shell = ev.Interpolate(stepClone.Shell)
|
|
||||||
stepClone.WorkingDirectory = ev.Interpolate(stepClone.WorkingDirectory)
|
|
||||||
|
|
||||||
stepContext.Step = &stepClone
|
|
||||||
|
|
||||||
executors = append(executors, stepContext.Executor(ctx))
|
|
||||||
}
|
}
|
||||||
return common.NewPipelineExecutor(executors...)(ctx)
|
inputs := make(map[string]interface{})
|
||||||
|
eval := sc.RunContext.NewExpressionEvaluator()
|
||||||
|
// Set Defaults
|
||||||
|
for k, input := range action.Inputs {
|
||||||
|
inputs[k] = eval.Interpolate(input.Default)
|
||||||
|
}
|
||||||
|
if step.With != nil {
|
||||||
|
for k, v := range step.With {
|
||||||
|
inputs[k] = eval.Interpolate(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Doesn't work with the command processor has a pointer to the original rc
|
||||||
|
// compositerc := rc.Clone()
|
||||||
|
// Workaround start
|
||||||
|
backup := *rc
|
||||||
|
defer func() { *rc = backup }()
|
||||||
|
*rc = *rc.Clone()
|
||||||
|
scriptName := backup.CurrentStep
|
||||||
|
for rcs := &backup; rcs.Parent != nil; rcs = rcs.Parent {
|
||||||
|
scriptName = fmt.Sprintf("%s-composite-%s", rcs.Parent.CurrentStep, scriptName)
|
||||||
|
}
|
||||||
|
compositerc := rc
|
||||||
|
compositerc.Parent = &RunContext{
|
||||||
|
CurrentStep: scriptName,
|
||||||
|
}
|
||||||
|
// Workaround end
|
||||||
|
compositerc.Composite = action
|
||||||
|
envToEvaluate := mergeMaps(compositerc.Env, step.Environment())
|
||||||
|
compositerc.Env = make(map[string]string)
|
||||||
|
// origEnvMap: is used to pass env changes back to parent runcontext
|
||||||
|
origEnvMap := make(map[string]string)
|
||||||
|
for k, v := range envToEvaluate {
|
||||||
|
ev := eval.Interpolate(v)
|
||||||
|
origEnvMap[k] = ev
|
||||||
|
compositerc.Env[k] = ev
|
||||||
|
}
|
||||||
|
compositerc.Inputs = inputs
|
||||||
|
compositerc.ExprEval = compositerc.NewExpressionEvaluator()
|
||||||
|
err = compositerc.CompositeExecutor()(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map outputs to parent rc
|
||||||
|
eval = (&StepContext{
|
||||||
|
Env: compositerc.Env,
|
||||||
|
RunContext: compositerc,
|
||||||
|
}).NewExpressionEvaluator()
|
||||||
|
for outputName, output := range action.Outputs {
|
||||||
|
backup.setOutput(ctx, map[string]string{
|
||||||
|
"name": outputName,
|
||||||
|
}, eval.Interpolate(output.Value))
|
||||||
|
}
|
||||||
|
// Test if evaluated parent env was altered by this composite step
|
||||||
|
// Known Issues:
|
||||||
|
// - you try to set an env variable to the same value as a scoped step env, will be discared
|
||||||
|
for k, v := range compositerc.Env {
|
||||||
|
if ov, ok := origEnvMap[k]; !ok || ov != v {
|
||||||
|
backup.Env[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *StepContext) populateEnvsFromInput(action *model.Action, rc *RunContext) {
|
func (sc *StepContext) populateEnvsFromInput(action *model.Action, rc *RunContext) {
|
||||||
|
|
8
pkg/runner/testdata/actions/docker-local-noargs/Dockerfile
vendored
Normal file
8
pkg/runner/testdata/actions/docker-local-noargs/Dockerfile
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Container image that runs your code
|
||||||
|
FROM node:12-buster-slim
|
||||||
|
|
||||||
|
# Copies your code file from your action repository to the filesystem path `/` of the container
|
||||||
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
|
# Code file to execute when the docker container starts up (`entrypoint.sh`)
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
15
pkg/runner/testdata/actions/docker-local-noargs/action.yml
vendored
Normal file
15
pkg/runner/testdata/actions/docker-local-noargs/action.yml
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
name: 'Hello World'
|
||||||
|
description: 'Greet someone and record the time'
|
||||||
|
inputs:
|
||||||
|
who-to-greet: # id of input
|
||||||
|
description: 'Who to greet'
|
||||||
|
required: true
|
||||||
|
default: 'World'
|
||||||
|
outputs:
|
||||||
|
time: # id of output
|
||||||
|
description: 'The time we greeted you'
|
||||||
|
runs:
|
||||||
|
using: 'docker'
|
||||||
|
image: 'Dockerfile'
|
||||||
|
env:
|
||||||
|
WHOAMI: ${{ inputs.who-to-greet }}
|
8
pkg/runner/testdata/actions/docker-local-noargs/entrypoint.sh
vendored
Executable file
8
pkg/runner/testdata/actions/docker-local-noargs/entrypoint.sh
vendored
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/sh -l
|
||||||
|
|
||||||
|
echo "Hello $1"
|
||||||
|
time=$(date)
|
||||||
|
echo ::set-output name=time::$time
|
||||||
|
echo ::set-output name=whoami::$WHOAMI
|
||||||
|
|
||||||
|
echo "SOMEVAR=$1" >>$GITHUB_ENV
|
|
@ -1,5 +1,8 @@
|
||||||
name: local-action-dockerfile
|
name: local-action-dockerfile
|
||||||
on: push
|
on: push
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
@ -15,4 +18,10 @@ jobs:
|
||||||
env:
|
env:
|
||||||
SOMEVAR: 'Not Mona'
|
SOMEVAR: 'Not Mona'
|
||||||
- run: '[[ "${{ steps.dockerlocal.outputs.whoami }}" == "Mona the Octocat" ]]'
|
- run: '[[ "${{ steps.dockerlocal.outputs.whoami }}" == "Mona the Octocat" ]]'
|
||||||
|
# Test if overriding args doesn't leak inputs
|
||||||
|
- uses: ./actions/docker-local-noargs
|
||||||
|
with:
|
||||||
|
args: ${{format('"{0}"', 'Mona is not the Octocat') }}
|
||||||
|
who-to-greet: 'Mona the Octocat'
|
||||||
|
- run: '[[ "${{ env.SOMEVAR }}" == "Mona is not the Octocat" ]]'
|
||||||
- uses: ./localdockerimagetest_
|
- uses: ./localdockerimagetest_
|
||||||
|
|
44
pkg/runner/testdata/local-action-via-composite-dockerfile/action.yml
vendored
Normal file
44
pkg/runner/testdata/local-action-via-composite-dockerfile/action.yml
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
inputs:
|
||||||
|
who-to-greet:
|
||||||
|
default: 'Mona the Octocat'
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
# Test if GITHUB_ACTION_PATH is set correctly before all steps
|
||||||
|
- run: stat $GITHUB_ACTION_PATH/push.yml
|
||||||
|
shell: bash
|
||||||
|
- run: stat $GITHUB_ACTION_PATH/action.yml
|
||||||
|
shell: bash
|
||||||
|
- run: '[[ "$GITHUB_ACTION_REPOSITORY" == "" ]] && [[ "$GITHUB_ACTION_REF" == "" ]]'
|
||||||
|
shell: bash
|
||||||
|
- uses: ./actions/docker-local
|
||||||
|
id: dockerlocal
|
||||||
|
with:
|
||||||
|
who-to-greet: ${{inputs.who-to-greet}}
|
||||||
|
- run: '[[ "${{ env.SOMEVAR }}" == "${{inputs.who-to-greet}}" ]]'
|
||||||
|
shell: bash
|
||||||
|
- run: '[ "${SOMEVAR}" = "Not Mona" ] || exit 1'
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
SOMEVAR: 'Not Mona'
|
||||||
|
- run: '[[ "${{ steps.dockerlocal.outputs.whoami }}" == "${{inputs.who-to-greet}}" ]]'
|
||||||
|
shell: bash
|
||||||
|
# Test if overriding args doesn't leak inputs
|
||||||
|
- uses: ./actions/docker-local-noargs
|
||||||
|
with:
|
||||||
|
args: ${{format('"{0}"', 'Mona is not the Octocat') }}
|
||||||
|
who-to-greet: ${{inputs.who-to-greet}}
|
||||||
|
- run: '[[ "${{ env.SOMEVAR }}" == "Mona is not the Octocat" ]]'
|
||||||
|
shell: bash
|
||||||
|
- uses: ./localdockerimagetest_
|
||||||
|
# Also test a remote docker action here
|
||||||
|
- uses: actions/hello-world-docker-action@main
|
||||||
|
with:
|
||||||
|
who-to-greet: 'Mona the Octocat'
|
||||||
|
# Test if GITHUB_ACTION_PATH is set correctly after all steps
|
||||||
|
- run: stat $GITHUB_ACTION_PATH/push.yml
|
||||||
|
shell: bash
|
||||||
|
- run: stat $GITHUB_ACTION_PATH/action.yml
|
||||||
|
shell: bash
|
||||||
|
- run: '[[ "$GITHUB_ACTION_REPOSITORY" == "" ]] && [[ "$GITHUB_ACTION_REF" == "" ]]'
|
||||||
|
shell: bash
|
9
pkg/runner/testdata/local-action-via-composite-dockerfile/push.yml
vendored
Normal file
9
pkg/runner/testdata/local-action-via-composite-dockerfile/push.yml
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
name: local-action-dockerfile
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: ./local-action-via-composite-dockerfile
|
11
pkg/runner/testdata/uses-composite-with-error/composite_action2/action.yml
vendored
Normal file
11
pkg/runner/testdata/uses-composite-with-error/composite_action2/action.yml
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
name: "Test Composite Action"
|
||||||
|
description: "Test action uses composite"
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- run: exit 1
|
||||||
|
shell: bash
|
||||||
|
- run: echo should not run in composite steps
|
||||||
|
shell: bash
|
12
pkg/runner/testdata/uses-composite-with-error/push.yml
vendored
Normal file
12
pkg/runner/testdata/uses-composite-with-error/push.yml
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
name: uses-docker-url
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
failing-composite-action:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: ./uses-composite-with-error/composite_action2
|
||||||
|
- run: echo should run
|
||||||
|
if: failure()
|
||||||
|
- run: echo should not run
|
|
@ -22,11 +22,15 @@ inputs:
|
||||||
description: "Required with default, due to an old bug of github actions this is allowed"
|
description: "Required with default, due to an old bug of github actions this is allowed"
|
||||||
required: true
|
required: true
|
||||||
default: "test_input_optional_value"
|
default: "test_input_optional_value"
|
||||||
|
secret_input:
|
||||||
|
description: test pass a secret as input
|
||||||
outputs:
|
outputs:
|
||||||
test_output:
|
test_output:
|
||||||
description: "Output value to pass up"
|
description: "Output value to pass up"
|
||||||
value: ${{ step.output.outputs.test_output }}
|
value: ${{ steps.output.outputs.test_output }}
|
||||||
|
secret_output:
|
||||||
|
description: test pass a secret as output
|
||||||
|
value: ${{ format('{0}/{1}', inputs.secret_input, env.secret_input) }}
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
|
@ -85,6 +89,8 @@ runs:
|
||||||
|
|
||||||
# Let's send up an output to test
|
# Let's send up an output to test
|
||||||
- run: echo "::set-output name=test_output::test_output_value"
|
- run: echo "::set-output name=test_output::test_output_value"
|
||||||
|
id: output
|
||||||
|
shell: bash
|
||||||
|
- run: echo "COMPOSITE_ACTION_ENV_OUTPUT=my test value" >> $GITHUB_ENV
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
|
|
||||||
|
|
14
pkg/runner/testdata/uses-composite/push.yml
vendored
14
pkg/runner/testdata/uses-composite/push.yml
vendored
|
@ -14,11 +14,21 @@ jobs:
|
||||||
test_input_optional_with_default_overriden: 'test_input_optional_with_default_overriden'
|
test_input_optional_with_default_overriden: 'test_input_optional_with_default_overriden'
|
||||||
test_input_required_with_default: 'test_input_optional_value'
|
test_input_required_with_default: 'test_input_optional_value'
|
||||||
test_input_required_with_default_overriden: 'test_input_required_with_default_overriden'
|
test_input_required_with_default_overriden: 'test_input_required_with_default_overriden'
|
||||||
|
secret_input: ${{secrets.test_input_optional || 'NO SUCH SECRET'}}
|
||||||
|
env:
|
||||||
|
secret_input: ${{secrets.test_input_optional || 'NO SUCH SECRET'}}
|
||||||
- if: steps.composite.outputs.test_output != "test_output_value"
|
- if: steps.composite.outputs.test_output != "test_output_value"
|
||||||
run: |
|
run: |
|
||||||
echo "steps.composite.outputs.test_output=${{ steps.composite.outputs.test_output }}"
|
echo "steps.composite.outputs.test_output=${{ steps.composite.outputs.test_output }}"
|
||||||
exit 1
|
exit 1
|
||||||
|
- run: |
|
||||||
|
echo "steps.composite.outputs.secret_output=${{ steps.composite.outputs.secret_output }}"
|
||||||
|
[[ "${{steps.composite.outputs.secret_output == format('{0}/{0}', secrets.test_input_optional || 'NO SUCH SECRET')}}" = "true" ]] || exit 1
|
||||||
|
shell: bash
|
||||||
|
- run: |
|
||||||
|
echo "steps.composite.outputs.secret_output=$COMPOSITE_ACTION_ENV_OUTPUT"
|
||||||
|
[[ "${{env.COMPOSITE_ACTION_ENV_OUTPUT == 'my test value' }}" = "true" ]] || exit 1
|
||||||
|
shell: bash
|
||||||
|
|
||||||
# Now test again with default values
|
# Now test again with default values
|
||||||
- uses: ./uses-composite/composite_action
|
- uses: ./uses-composite/composite_action
|
||||||
|
@ -30,5 +40,5 @@ jobs:
|
||||||
|
|
||||||
- if: steps.composite2.outputs.test_output != "test_output_value"
|
- if: steps.composite2.outputs.test_output != "test_output_value"
|
||||||
run: |
|
run: |
|
||||||
echo "steps.composite.outputs.test_output=${{ steps.composite.outputs.test_output }}"
|
echo "steps.composite.outputs.test_output=${{ steps.composite2.outputs.test_output }}"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
63
pkg/runner/testdata/uses-nested-composite/composite_action2/action.yml
vendored
Normal file
63
pkg/runner/testdata/uses-nested-composite/composite_action2/action.yml
vendored
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
---
|
||||||
|
name: "Test Composite Action"
|
||||||
|
description: "Test action uses composite"
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
test_input_optional:
|
||||||
|
description: Test
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
# The output of actions/setup-node@v2 seems to fail the workflow
|
||||||
|
# - uses: actions/setup-node@v2
|
||||||
|
# with:
|
||||||
|
# node-version: '16'
|
||||||
|
# - run: |
|
||||||
|
# console.log(process.version);
|
||||||
|
# console.log("Hi from node");
|
||||||
|
# console.log("${{ inputs.test_input_optional }}");
|
||||||
|
# if("${{ inputs.test_input_optional }}" !== "Test") {
|
||||||
|
# console.log("Invalid input test_input_optional expected \"Test\" as value");
|
||||||
|
# process.exit(1);
|
||||||
|
# }
|
||||||
|
# if(!process.version.startsWith('v16')) {
|
||||||
|
# console.log("Expected node v16, but got " + process.version);
|
||||||
|
# process.exit(1);
|
||||||
|
# }
|
||||||
|
# shell: node {0}
|
||||||
|
- uses: ./uses-composite/composite_action
|
||||||
|
id: composite
|
||||||
|
with:
|
||||||
|
test_input_required: 'test_input_required_value'
|
||||||
|
test_input_optional: 'test_input_optional_value'
|
||||||
|
test_input_optional_with_default_overriden: 'test_input_optional_with_default_overriden'
|
||||||
|
test_input_required_with_default: 'test_input_optional_value'
|
||||||
|
test_input_required_with_default_overriden: 'test_input_required_with_default_overriden'
|
||||||
|
secret_input: ${{inputs.test_input_optional}}
|
||||||
|
env:
|
||||||
|
secret_input: ${{inputs.test_input_optional}}
|
||||||
|
- run: |
|
||||||
|
echo "steps.composite.outputs.test_output=${{ steps.composite.outputs.test_output }}"
|
||||||
|
[[ "${{steps.composite.outputs.test_output == 'test_output_value'}}" = "true" ]] || exit 1
|
||||||
|
shell: bash
|
||||||
|
- run: |
|
||||||
|
echo "steps.composite.outputs.secret_output=${{ steps.composite.outputs.secret_output }}"
|
||||||
|
[[ "${{steps.composite.outputs.secret_output == format('{0}/{0}', inputs.test_input_optional)}}" = "true" ]] || exit 1
|
||||||
|
shell: bash
|
||||||
|
# Now test again with default values
|
||||||
|
- uses: ./uses-composite/composite_action
|
||||||
|
id: composite2
|
||||||
|
with:
|
||||||
|
test_input_required: 'test_input_required_value'
|
||||||
|
test_input_optional_with_default_overriden: 'test_input_optional_with_default_overriden'
|
||||||
|
test_input_required_with_default_overriden: 'test_input_required_with_default_overriden'
|
||||||
|
|
||||||
|
- run: |
|
||||||
|
echo "steps.composite2.outputs.test_output=${{ steps.composite2.outputs.test_output }}"
|
||||||
|
[[ "${{steps.composite2.outputs.test_output == 'test_output_value'}}" = "true" ]] || exit 1
|
||||||
|
shell: bash
|
||||||
|
- run: |
|
||||||
|
echo "steps.composite.outputs.secret_output=$COMPOSITE_ACTION_ENV_OUTPUT"
|
||||||
|
[[ "${{env.COMPOSITE_ACTION_ENV_OUTPUT == 'my test value' }}" = "true" ]] || exit 1
|
||||||
|
shell: bash
|
15
pkg/runner/testdata/uses-nested-composite/push.yml
vendored
Normal file
15
pkg/runner/testdata/uses-nested-composite/push.yml
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
name: uses-docker-url
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: ./uses-nested-composite/composite_action2
|
||||||
|
with:
|
||||||
|
test_input_optional: Test
|
||||||
|
- run: |
|
||||||
|
echo "steps.composite.outputs.secret_output=$COMPOSITE_ACTION_ENV_OUTPUT"
|
||||||
|
[[ "${{env.COMPOSITE_ACTION_ENV_OUTPUT == 'my test value' }}" = "true" ]] || exit 1
|
||||||
|
shell: bash
|
Loading…
Reference in a new issue