Mapping workflow_dispatch inputs into the Expression inputs context (#1363)

* test: check workflow_dispatch inputs

This implements a test to check for `workflow_dispatch` inputs.
This will be a prerequisite for implementing the inputs.

* feat: map workflow_dispatch input to expression evaluator

This changes adds the workflow_dispatch event inputs
to the `inputs` context and maintaining the boolean type

* fix: coerce boolean input types

* fix: use step env if available, rc env otherwise
This commit is contained in:
Markus Wolf 2022-10-17 18:25:26 +02:00 committed by GitHub
parent 5d2eb1f238
commit 1e0ef8ce69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 136 additions and 14 deletions

View file

@ -55,6 +55,51 @@ func (w *Workflow) On() []string {
return nil
}
func (w *Workflow) OnEvent(event string) interface{} {
if w.RawOn.Kind == yaml.MappingNode {
var val map[string]interface{}
err := w.RawOn.Decode(&val)
if err != nil {
log.Fatal(err)
}
return val[event]
}
return nil
}
type WorkflowDispatchInput struct {
Description string `yaml:"description"`
Required bool `yaml:"required"`
Default string `yaml:"default"`
Type string `yaml:"type"`
Options []string `yaml:"options"`
}
type WorkflowDispatch struct {
Inputs map[string]WorkflowDispatchInput `yaml:"inputs"`
}
func (w *Workflow) WorkflowDispatchConfig() *WorkflowDispatch {
if w.RawOn.Kind != yaml.MappingNode {
return nil
}
var val map[string]yaml.Node
err := w.RawOn.Decode(&val)
if err != nil {
log.Fatal(err)
}
var config WorkflowDispatch
node := val["workflow_dispatch"]
err = node.Decode(&config)
if err != nil {
log.Fatal(err)
}
return &config
}
// Job is the structure of one job in a workflow
type Job struct {
Name string `yaml:"name"`

View file

@ -9,6 +9,7 @@ import (
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/exprparser"
"github.com/nektos/act/pkg/model"
"gopkg.in/yaml.v3"
)
@ -39,15 +40,11 @@ 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
}
}
ghc := rc.getGithubContext(ctx)
inputs := getEvaluatorInputs(ctx, rc, nil, ghc)
ee := &exprparser.EvaluationEnvironment{
Github: rc.getGithubContext(ctx),
Github: ghc,
Env: rc.GetEnv(),
Job: rc.getJobContext(),
// todo: should be unavailable
@ -94,12 +91,8 @@ 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
}
}
ghc := rc.getGithubContext(ctx)
inputs := getEvaluatorInputs(ctx, rc, step, ghc)
ee := &exprparser.EvaluationEnvironment{
Github: step.getGithubContext(ctx),
@ -319,3 +312,37 @@ func rewriteSubExpression(ctx context.Context, in string, forceFormat bool) (str
}
return out, nil
}
func getEvaluatorInputs(ctx context.Context, rc *RunContext, step step, ghc *model.GithubContext) map[string]interface{} {
inputs := map[string]interface{}{}
var env map[string]string
if step != nil {
env = *step.getEnv()
} else {
env = rc.GetEnv()
}
for k, v := range env {
if strings.HasPrefix(k, "INPUT_") {
inputs[strings.ToLower(strings.TrimPrefix(k, "INPUT_"))] = v
}
}
if ghc.EventName == "workflow_dispatch" {
config := rc.Run.Workflow.WorkflowDispatchConfig()
for k, v := range config.Inputs {
value := nestedMapLookup(ghc.Event, "inputs", k)
if value == nil {
value = v.Default
}
if v.Type == "boolean" {
inputs[k] = value == "true"
} else {
inputs[k] = value
}
}
}
return inputs
}

View file

@ -182,6 +182,7 @@ func TestRunEvent(t *testing.T) {
{workdir, "uses-action-with-pre-and-post-step", "push", "", platforms},
{workdir, "evalenv", "push", "", platforms},
{workdir, "ensure-post-steps", "push", "Job 'second-post-step-should-fail' failed", platforms},
{workdir, "workflow_dispatch", "workflow_dispatch", "", platforms},
{"../model/testdata", "strategy", "push", "", platforms}, // TODO: move all testdata into pkg so we can validate it with planner and runner
// {"testdata", "issue-228", "push", "", platforms, }, // TODO [igni]: Remove this once everything passes
{"../model/testdata", "container-volumes", "push", "", platforms},
@ -189,7 +190,14 @@ func TestRunEvent(t *testing.T) {
for _, table := range tables {
t.Run(table.workflowPath, func(t *testing.T) {
table.runTest(ctx, t, &Config{})
config := &Config{}
eventFile := filepath.Join(workdir, table.workflowPath, "event.json")
if _, err := os.Stat(eventFile); err == nil {
config.EventPath = eventFile
}
table.runTest(ctx, t, config)
})
}
}

View file

@ -0,0 +1,6 @@
{
"inputs": {
"required": "required input",
"boolean": "true"
}
}

View file

@ -0,0 +1,36 @@
name: workflow_dispatch
on:
workflow_dispatch:
inputs:
required:
description: a required input
required: true
with_default:
description: an input with default
required: false
default: default
boolean:
description: an input of type boolean
required: false
type: boolean
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: test required input
run: |
echo input.required=${{ inputs.required }}
[[ "${{ inputs.required }}" = "required input" ]] || exit 1
- name: test input with default
run: |
echo input.with_default=${{ inputs.with_default }}
[[ "${{ inputs.with_default }}" = "default" ]] || exit 1
- id: boolean-test
name: run on boolean input
if: ${{ inputs.boolean == true }}
run: echo "::set-output name=value::executed"
- name: has boolean test?
run: |
[[ "${{ steps.boolean-test.outputs.value }}" = "executed" ]] || exit 1