Skip to content

Commit

Permalink
Feature: uses in composite (#793)
Browse files Browse the repository at this point in the history
* 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 #793 (comment)

* 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 #840
and integrates with #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>
  • Loading branch information
5 people authored Dec 22, 2021
1 parent 2ef30c3 commit 9868e13
Show file tree
Hide file tree
Showing 19 changed files with 463 additions and 157 deletions.
13 changes: 12 additions & 1 deletion pkg/model/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,16 @@ type Output struct {
func ReadAction(in io.Reader) (*Action, error) {
a := new(Action)
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
}
28 changes: 25 additions & 3 deletions pkg/model/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ type Workflow struct {
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
func (w *Workflow) On() []string {
switch w.RawOn.Kind {
Expand Down Expand Up @@ -411,11 +426,18 @@ func (s *Step) Type() StepType {
return StepTypeUsesActionRemote
}

func (s *Step) Validate() error {
if s.Type() != StepTypeRun {
func (s *Step) Validate(config *CompositeRestrictions) error {
if config == nil {
config = defaultCompositeRestrictions()
}
if s.Type() != StepTypeRun && !config.AllowCompositeUses {
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())
} 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
}
Expand Down
25 changes: 7 additions & 18 deletions pkg/runner/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ func (sc *StepContext) NewExpressionEvaluator() ExpressionEvaluator {
vm := sc.RunContext.newVM()
configers := []func(*otto.Otto){
sc.vmEnv(),
sc.vmInputs(),

sc.vmNeeds(),
sc.vmSuccess(),
sc.vmFailure(),
Expand Down Expand Up @@ -237,6 +235,7 @@ func (rc *RunContext) newVM() *otto.Otto {
rc.vmMatrix(),
rc.vmEnv(),
rc.vmNeeds(),
rc.vmInputs(),
}
vm := otto.New()
for _, configer := range configers {
Expand Down Expand Up @@ -447,22 +446,9 @@ func (sc *StepContext) vmEnv() func(*otto.Otto) {
}
}

func (sc *StepContext) 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)
}

func (rc *RunContext) vmInputs() func(*otto.Otto) {
return func(vm *otto.Otto) {
_ = vm.Set("inputs", inputs)
_ = vm.Set("inputs", rc.Inputs)
}
}

Expand Down Expand Up @@ -587,7 +573,10 @@ func (rc *RunContext) vmRunner() func(*otto.Otto) {

func (rc *RunContext) vmSecrets() func(*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)
}
}
}

Expand Down
70 changes: 51 additions & 19 deletions pkg/runner/run_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,35 @@ const ActPath string = "/var/run/act"

// RunContext contains info about current job
type RunContext struct {
Name string
Config *Config
Matrix map[string]interface{}
Run *model.Run
EventJSON string
Env map[string]string
ExtraPath []string
CurrentStep string
StepResults map[string]*stepResult
ExprEval ExpressionEvaluator
JobContainer container.Container
OutputMappings map[MappableOutput]MappableOutput
JobName string
Name string
Config *Config
Matrix map[string]interface{}
Run *model.Run
EventJSON string
Env map[string]string
ExtraPath []string
CurrentStep string
StepResults map[string]*stepResult
ExprEval ExpressionEvaluator
JobContainer container.Container
OutputMappings map[MappableOutput]MappableOutput
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 {
Expand Down Expand Up @@ -310,6 +326,22 @@ func (rc *RunContext) Executor() common.Executor {
}).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 {
sc := &StepContext{
RunContext: rc,
Expand Down Expand Up @@ -568,9 +600,9 @@ func (rc *RunContext) getGithubContext() *githubContext {
Workspace: rc.Config.ContainerWorkdir(),
Action: rc.CurrentStep,
Token: rc.Config.Secrets["GITHUB_TOKEN"],
ActionPath: rc.Config.Env["GITHUB_ACTION_PATH"],
ActionRef: rc.Config.Env["RUNNER_ACTION_REF"],
ActionRepository: rc.Config.Env["RUNNER_ACTION_REPOSITORY"],
ActionPath: rc.ActionPath,
ActionRef: rc.ActionRef,
ActionRepository: rc.ActionRepository,
RepositoryOwner: rc.Config.Env["GITHUB_REPOSITORY_OWNER"],
RetentionDays: rc.Config.Env["GITHUB_RETENTION_DAYS"],
RunnerPerflog: rc.Config.Env["RUNNER_PERFLOG"],
Expand Down Expand Up @@ -737,9 +769,9 @@ func (rc *RunContext) withGithubEnv(env map[string]string) map[string]string {
env["GITHUB_RUN_ID"] = github.RunID
env["GITHUB_RUN_NUMBER"] = github.RunNumber
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_ACTOR"] = github.Actor
env["GITHUB_REPOSITORY"] = github.Repository
Expand Down
51 changes: 26 additions & 25 deletions pkg/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,32 @@ type Runner interface {

// Config contains the config for a new runner
type Config struct {
Actor string // the user that triggered the event
Workdir string // path to working directory
BindWorkdir bool // bind the workdir to the job container
EventName string // name of event to run
EventPath string // path to JSON file to use for event.json in containers
DefaultBranch string // name of the main branch for this repository
ReuseContainers bool // reuse containers to maintain state
ForcePull bool // force pulling of the image, even if already present
ForceRebuild bool // force rebuilding local docker image action
LogOutput bool // log the output from docker run
Env map[string]string // env for containers
Secrets map[string]string // list of secrets
InsecureSecrets bool // switch hiding output when printing to terminal
Platforms map[string]string // list of platforms
Privileged bool // use privileged mode
UsernsMode string // user namespace to use
ContainerArchitecture string // Desired OS/architecture platform for running containers
ContainerDaemonSocket string // Path to Docker daemon socket
UseGitIgnore bool // controls if paths in .gitignore should not be copied into container, default true
GitHubInstance string // GitHub instance to use, default "github.com"
ContainerCapAdd []string // list of kernel capabilities to add to 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
ArtifactServerPath string // the path where the artifact server stores uploads
ArtifactServerPort string // the port the artifact server binds to
Actor string // the user that triggered the event
Workdir string // path to working directory
BindWorkdir bool // bind the workdir to the job container
EventName string // name of event to run
EventPath string // path to JSON file to use for event.json in containers
DefaultBranch string // name of the main branch for this repository
ReuseContainers bool // reuse containers to maintain state
ForcePull bool // force pulling of the image, even if already present
ForceRebuild bool // force rebuilding local docker image action
LogOutput bool // log the output from docker run
Env map[string]string // env for containers
Secrets map[string]string // list of secrets
InsecureSecrets bool // switch hiding output when printing to terminal
Platforms map[string]string // list of platforms
Privileged bool // use privileged mode
UsernsMode string // user namespace to use
ContainerArchitecture string // Desired OS/architecture platform for running containers
ContainerDaemonSocket string // Path to Docker daemon socket
UseGitIgnore bool // controls if paths in .gitignore should not be copied into container, default true
GitHubInstance string // GitHub instance to use, default "github.com"
ContainerCapAdd []string // list of kernel capabilities to add to 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
ArtifactServerPath string // the path where the artifact server stores uploads
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
Expand Down
3 changes: 3 additions & 0 deletions pkg/runner/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,16 @@ func TestRunEvent(t *testing.T) {
{"testdata", "remote-action-js", "push", "", platforms, ""},
{"testdata", "local-action-docker-url", "push", "", platforms, ""},
{"testdata", "local-action-dockerfile", "push", "", platforms, ""},
{"testdata", "local-action-via-composite-dockerfile", "push", "", platforms, ""},
{"testdata", "local-action-js", "push", "", platforms, ""},
{"testdata", "matrix", "push", "", platforms, ""},
{"testdata", "matrix-include-exclude", "push", "", platforms, ""},
{"testdata", "commands", "push", "", platforms, ""},
{"testdata", "workdir", "push", "", platforms, ""},
{"testdata", "defaults-run", "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-598", "push", "", platforms, ""},
{"testdata", "env-and-path", "push", "", platforms, ""},
Expand Down
Loading

0 comments on commit 9868e13

Please sign in to comment.