From 77f52c9d13281e0e0d9b843ccc72ad8727f6789d Mon Sep 17 00:00:00 2001 From: Clint Date: Wed, 18 Sep 2024 16:59:02 -0500 Subject: [PATCH 1/3] fix: sort slices before comparing in test (#142) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description The Runner test `TestRunner_GetBaseActionCfg/extraEnv_adds_and_overrides_defaults` in [`src/pkg/runner/actions_test.go`](https://github.com/defenseunicorns/maru-runner/blob/535258256cd1714f55e643075ae15919316e4aec/src/pkg/runner/actions_test.go#L502) is flaky because we don't sort the string slices before comparing them, and occasionally they get created in slightly different orders: ```zsh === RUN TestRunner_GetBaseActionCfg/extraEnv_adds_and_overrides_defaults actions_test.go:502: Error Trace: /Users/clint/go/github.com/defenseunicorns/maru-runner/src/pkg/runner/actions_test.go:502 Error: Not equal: expected: []string{"ENV1=fromDefault", "ENV2=xyz1", "ENV4=fromSet", "ENV2=alsoFromEnv", "ENV3=fromExtra"} actual : []string{"ENV1=fromDefault", "ENV2=xyz1", "ENV4=fromSet", "ENV3=fromExtra", "ENV2=alsoFromEnv"} ``` note the expected `"ENV2=alsoFromEnv", "ENV3=fromExtra"` vs. the actual `"ENV3=fromExtra", "ENV2=alsoFromEnv"`. Assuming a successful test run produces this output: ```zsh === RUN TestRunner_GetBaseActionCfg === RUN TestRunner_GetBaseActionCfg/extraEnv_adds_and_overrides_defaults --- PASS: TestRunner_GetBaseActionCfg (0.00s) --- PASS: TestRunner_GetBaseActionCfg/extraEnv_adds_and_overrides_defaults (0.00s) ``` If we run the test enough times it will fail, as shown below where we would expect 200 lines of `-- PASS` here when running the test 100 times (2x per run), and 0 for `--- FAIL`: ```zsh github.com/defenseunicorns/maru-runner on  main [$] ➜ go test ./src/pkg/runner/... -v -run=TestRunner_GetBaseActionCfg/extraEnv_adds_and_overrides_defaults -count=100 | grep '\-\-\- PASS' | wc -l 142 github.com/defenseunicorns/maru-runner on  main ➜ go test ./src/pkg/runner/... -v -run=TestRunner_GetBaseActionCfg/extraEnv_adds_and_overrides_defaults -count=100 | grep '\-\-\- FAIL' | wc -l 46 ``` It's flaky, so your numbers will vary each time you run it. In this PR we sort both slices before comparing. Here's the updated output: ```zsh github.com/defenseunicorns/maru-runner on  fix-runner-test-sort ➜ go test ./src/pkg/runner/... -v -run=TestRunner_GetBaseActionCfg/extraEnv_adds_and_overrides_defaults -count=100 | grep '\-\-\- PASS' | wc -l 200 github.com/defenseunicorns/maru-runner on  fix-runner-test-sort ➜ go test ./src/pkg/runner/... -v -run=TestRunner_GetBaseActionCfg/extraEnv_adds_and_overrides_defaults -count=100 | grep '\-\-\- FAIL' | wc -l 0 ``` ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Other (security config, docs update, etc) ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/maru-runner/blob/main/CONTRIBUTING.md) followed Signed-off-by: catsby --- src/pkg/runner/actions_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pkg/runner/actions_test.go b/src/pkg/runner/actions_test.go index 597bcb2..4d08de0 100644 --- a/src/pkg/runner/actions_test.go +++ b/src/pkg/runner/actions_test.go @@ -5,6 +5,7 @@ package runner import ( "reflect" + "slices" "testing" "github.com/defenseunicorns/maru-runner/src/config" @@ -498,7 +499,8 @@ func TestRunner_GetBaseActionCfg(t *testing.T) { } got := GetBaseActionCfg(tt.args.cfg, tt.args.a, tt.args.vars) - + slices.Sort(got.Env) + slices.Sort(tt.want) require.Equal(t, tt.want, got.Env, "The returned Env array did not match what was wanted") }) } From e72da683aa57811cb06678351ba7188f25a7435d Mon Sep 17 00:00:00 2001 From: Clint Date: Thu, 19 Sep 2024 15:51:42 -0500 Subject: [PATCH 2/3] chore: use go-list to filter out e2e tests in test-unit step (#140) ## Description NOTE: the below is now out of date... see UPDATE 2 Currently the makefile separates unit and e2e tests "physically", where unit tests *only* live in `src/pkg/...` and e2e tests *only* live in `src/test/e2e...`. The result is if you want to add tests to something like `src/message` or `src/types` you can't run them alongside the others unless you add a step to the makefile. In this PR we change ~both make steps~ _the test-unit make step_ to run tests across the entire codebase, ~but~ _and_ we restrict e2e tests behind the setting of an environment variable that's checked in the test themselves. The `test-unit` step does not set the variable, where as `test-e2e` does. Note that this does change the output of `test-unit` as it will now show the e2e tests as skipped. Alternatives to this would be to move the e2e tests outside of `src` but this seemed simple enough. The end goal is primarily because we can now add tests outside of `src/pkg` and not worry about making extra make steps to make sure they're ran in CI. UPDATE: I restored the part in the e2e step that did `cd src/test/e2e` because it assumed a maru binary there. **UPDATE 2:** after feedback I scrapped the env part and just used `go list ./... | grep` to filter out the e2e tests when running `test-unit`. Much simpler. ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [x] Other (security config, docs update, etc) ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/maru-runner/blob/main/CONTRIBUTING.md) followed --------- Signed-off-by: catsby Co-authored-by: Wayne Starr --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8407399..3a050fe 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,8 @@ build-cli-mac-apple: ## Build the CLI for Mac Apple .PHONY: test-unit test-unit: ## Run unit tests - cd src/pkg && go test ./... -failfast -v -timeout 30m + go test -failfast -v -timeout 30m $$(go list ./... | grep -v '^github.com/defenseunicorns/maru-runner/src/test/e2e') + .PHONY: test-e2e test-e2e: ## Run End to End (e2e) tests From 87c56e417aab68719545d46b0217d1b797bb6b12 Mon Sep 17 00:00:00 2001 From: zamaz <71521611+zachariahmiller@users.noreply.github.com> Date: Thu, 19 Sep 2024 17:06:58 -0400 Subject: [PATCH 3/3] feat: add templated conditionals to tasks (#139) ## Description Adds an `if` keyword to `task:` and `cmd:` that evaluates based on a go template to true or false to facilitate conditional task execution. ## Related Issue ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Other (security config, docs update, etc) ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/maru-runner/blob/main/CONTRIBUTING.md) followed --------- Co-authored-by: Eric Wyles <23637493+ericwyles@users.noreply.github.com> Co-authored-by: Wayne Starr --- .github/actions/zarf/action.yaml | 4 +- Makefile | 5 +- src/pkg/runner/actions.go | 30 ++++-- src/pkg/runner/actions_test.go | 6 +- src/pkg/runner/runner.go | 7 +- src/pkg/utils/template.go | 31 +++--- src/test/e2e/runner_test.go | 98 ++++++++++++++++++- src/test/tasks/conditionals/tasks.yaml | 129 +++++++++++++++++++++++++ src/types/tasks.go | 1 + tasks.schema.json | 4 + 10 files changed, 285 insertions(+), 30 deletions(-) create mode 100644 src/test/tasks/conditionals/tasks.yaml diff --git a/.github/actions/zarf/action.yaml b/.github/actions/zarf/action.yaml index 21255d7..ec7eb84 100644 --- a/.github/actions/zarf/action.yaml +++ b/.github/actions/zarf/action.yaml @@ -6,5 +6,5 @@ runs: steps: - uses: defenseunicorns/setup-zarf@main with: - # renovate: datasource=github-tags depName=defenseunicorns/zarf - version: v0.36.0 + # renovate: datasource=github-tags depName=zarf-dev/zarf + version: v0.39.0 diff --git a/Makefile b/Makefile index 3a050fe..f0915e5 100644 --- a/Makefile +++ b/Makefile @@ -53,8 +53,9 @@ test-unit: ## Run unit tests .PHONY: test-e2e -test-e2e: ## Run End to End (e2e) tests - cd src/test/e2e && go test -failfast -v -timeout 30m + +test-e2e: build ## Run End to End (e2e) tests + cd src/test/e2e && go test -failfast -v -timeout 30m -count=1 schema: ## Update JSON schema for maru tasks ./hack/generate-schema.sh diff --git a/src/pkg/runner/actions.go b/src/pkg/runner/actions.go index a17b3a4..723d62a 100644 --- a/src/pkg/runner/actions.go +++ b/src/pkg/runner/actions.go @@ -22,24 +22,32 @@ import ( "github.com/defenseunicorns/maru-runner/src/types" ) -func (r *Runner) performAction(action types.Action) error { +func (r *Runner) performAction(action types.Action, withs map[string]string, inputs map[string]types.InputParameter) error { + + message.SLog.Debug(fmt.Sprintf("Evaluating action conditional %s", action.If)) + + action, _ = utils.TemplateTaskAction(action, withs, inputs, r.variableConfig.GetSetVariables()) + if action.If == "false" && action.TaskReference != "" { + message.SLog.Info(fmt.Sprintf("Skipping action %s", action.TaskReference)) + return nil + } else if action.If == "false" && action.Description != "" { + message.SLog.Info(fmt.Sprintf("Skipping action %s", action.Description)) + return nil + } else if action.If == "false" && action.Cmd != "" { + cmdEscaped := helpers.Truncate(action.Cmd, 60, false) + message.SLog.Info(fmt.Sprintf("Skipping action %q", cmdEscaped)) + return nil + } + if action.TaskReference != "" { // todo: much of this logic is duplicated in Run, consider refactoring referencedTask, err := r.getTask(action.TaskReference) if err != nil { return err } - - // template the withs with variables for k, v := range action.With { action.With[k] = utils.TemplateString(r.variableConfig.GetSetVariables(), v) } - - referencedTask.Actions, err = utils.TemplateTaskActionsWithInputs(referencedTask, action.With) - if err != nil { - return err - } - withEnv := []string{} for name := range action.With { withEnv = append(withEnv, utils.FormatEnvVar(name, action.With[name])) @@ -50,7 +58,8 @@ func (r *Runner) performAction(action types.Action) error { for _, a := range referencedTask.Actions { a.Env = utils.MergeEnv(withEnv, a.Env) } - if err := r.executeTask(referencedTask); err != nil { + + if err := r.executeTask(referencedTask, action.With); err != nil { return err } } else { @@ -58,6 +67,7 @@ func (r *Runner) performAction(action types.Action) error { if err != nil { return err } + } return nil } diff --git a/src/pkg/runner/actions_test.go b/src/pkg/runner/actions_test.go index 4d08de0..a82e467 100644 --- a/src/pkg/runner/actions_test.go +++ b/src/pkg/runner/actions_test.go @@ -199,6 +199,7 @@ func Test_validateActionableTaskCall(t *testing.T) { name: "Valid task call with default value for missing input", args: args{ inputTaskName: "testTask", + inputs: map[string]types.InputParameter{ "input1": {Required: true, Default: "defaultValue"}, "input2": {Required: true, Default: ""}, @@ -228,6 +229,8 @@ func TestRunner_performAction(t *testing.T) { } type args struct { action types.Action + inputs map[string]types.InputParameter + withs map[string]string } tests := []struct { name string @@ -236,6 +239,7 @@ func TestRunner_performAction(t *testing.T) { wantErr bool }{ // TODO: Add more test cases + // https://github.com/defenseunicorns/maru-runner/issues/143 { name: "failed action processing due to invalid command", fields: fields{ @@ -294,7 +298,7 @@ func TestRunner_performAction(t *testing.T) { envFilePath: tt.fields.envFilePath, variableConfig: tt.fields.variableConfig, } - err := r.performAction(tt.args.action) + err := r.performAction(tt.args.action, tt.args.withs, tt.args.inputs) if (err != nil) != tt.wantErr { t.Errorf("performAction() error = %v, wantErr %v", err, tt.wantErr) } diff --git a/src/pkg/runner/runner.go b/src/pkg/runner/runner.go index 8068867..ebd1f43 100644 --- a/src/pkg/runner/runner.go +++ b/src/pkg/runner/runner.go @@ -66,7 +66,7 @@ func Run(tasksFile types.TasksFile, taskName string, setVariables map[string]str return err } - err = runner.executeTask(task) + err = runner.executeTask(task, nil) return err } @@ -267,7 +267,7 @@ func (r *Runner) getTask(taskName string) (types.Task, error) { return types.Task{}, fmt.Errorf("task name %s not found", taskName) } -func (r *Runner) executeTask(task types.Task) error { +func (r *Runner) executeTask(task types.Task, withs map[string]string) error { defaultEnv := []string{} for name, inputParam := range task.Inputs { d := inputParam.Default @@ -284,7 +284,8 @@ func (r *Runner) executeTask(task types.Task) error { for _, action := range task.Actions { action.Env = utils.MergeEnv(action.Env, defaultEnv) - if err := r.performAction(action); err != nil { + + if err := r.performAction(action, withs, task.Inputs); err != nil { return err } } diff --git a/src/pkg/utils/template.go b/src/pkg/utils/template.go index dda1707..fc14437 100644 --- a/src/pkg/utils/template.go +++ b/src/pkg/utils/template.go @@ -16,10 +16,11 @@ import ( goyaml "github.com/goccy/go-yaml" ) -// TemplateTaskActionsWithInputs templates a task's actions with the given inputs -func TemplateTaskActionsWithInputs(task types.Task, withs map[string]string) ([]types.Action, error) { +// TemplateTaskAction templates a task's actions with the given inputs and variables +func TemplateTaskAction[T any](action types.Action, withs map[string]string, inputs map[string]types.InputParameter, setVarMap variables.SetVariableMap[T]) (types.Action, error) { data := map[string]map[string]string{ - "inputs": {}, + "inputs": {}, + "variables": {}, } // get inputs from "with" map @@ -27,34 +28,42 @@ func TemplateTaskActionsWithInputs(task types.Task, withs map[string]string) ([] data["inputs"][name] = withs[name] } + // get vars from "vms" map + for name := range setVarMap { + data["variables"][name] = setVarMap[name].Value + } + // use default if not populated in data - for name := range task.Inputs { + for name := range inputs { if current, ok := data["inputs"][name]; !ok || current == "" { - data["inputs"][name] = task.Inputs[name].Default + data["inputs"][name] = inputs[name].Default } } - b, err := goyaml.Marshal(task.Actions) + b, err := goyaml.Marshal(action) if err != nil { - return nil, err + return action, err } t, err := template.New("template task actions").Option("missingkey=error").Delims("${{", "}}").Parse(string(b)) if err != nil { - return nil, err + return action, err } var templated strings.Builder if err := t.Execute(&templated, data); err != nil { - return nil, err + return action, err } result := templated.String() - var templatedActions []types.Action + var templatedAction types.Action + if err := goyaml.Unmarshal([]byte(result), &templatedAction); err != nil { + return action, err + } - return templatedActions, goyaml.Unmarshal([]byte(result), &templatedActions) + return templatedAction, nil } // TemplateString replaces ${...} with the value from the template map diff --git a/src/test/e2e/runner_test.go b/src/test/e2e/runner_test.go index d91daca..e24acf8 100644 --- a/src/test/e2e/runner_test.go +++ b/src/test/e2e/runner_test.go @@ -232,7 +232,7 @@ func TestTaskRunner(t *testing.T) { t.Parallel() _, stdErr, err := e2e.Maru("run", "wait-fail", "--file", "src/test/tasks/tasks.yaml") require.Error(t, err) - require.Contains(t, stdErr, "Waiting for") + require.Contains(t, stdErr, "timed out after 1 seconds") }) t.Run("test successful call to zarf tools wait-for (requires Zarf on path)", func(t *testing.T) { @@ -298,4 +298,100 @@ func TestTaskRunner(t *testing.T) { require.NoError(t, err, stdOut, stdErr) require.Contains(t, stdErr, "defenseunicorns is a pretty ok company") }) + + // Conditional Tests + t.Run("test calling a task with false conditional cmd comparing variables", func(t *testing.T) { + t.Parallel() + stdOut, stdErr, err := e2e.Maru("run", "false-conditional-with-var-cmd", "--file", "src/test/tasks/conditionals/tasks.yaml") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "Skipping action false-conditional-with-var-cmd") + }) + + t.Run("test calling a task with true conditional cmd comparing variables", func(t *testing.T) { + t.Parallel() + stdOut, stdErr, err := e2e.Maru("run", "true-conditional-with-var-cmd", "--file", "src/test/tasks/conditionals/tasks.yaml") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "This should run because .variables.BAR = default-value") + }) + + t.Run("test calling a task with cmd no conditional", func(t *testing.T) { + t.Parallel() + stdOut, stdErr, err := e2e.Maru("run", "empty-conditional-cmd", "--file", "src/test/tasks/conditionals/tasks.yaml") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "This should run because there is no condition") + }) + + t.Run("test calling a task with false conditional comparing variables", func(t *testing.T) { + t.Parallel() + stdOut, stdErr, err := e2e.Maru("run", "false-conditional-task", "--file", "src/test/tasks/conditionals/tasks.yaml") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "Skipping action included-task") + }) + + t.Run("test calling a task with true conditional comparing variables", func(t *testing.T) { + t.Parallel() + stdOut, stdErr, err := e2e.Maru("run", "true-conditional-task", "--file", "src/test/tasks/conditionals/tasks.yaml") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "Task called successfully") + }) + + t.Run("test calling a task with no conditional comparing variables", func(t *testing.T) { + t.Parallel() + stdOut, stdErr, err := e2e.Maru("run", "empty-conditional-task", "--file", "src/test/tasks/conditionals/tasks.yaml") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "Task called successfully") + }) + + t.Run("test calling a task with nested true conditional comparing variables and inputs", func(t *testing.T) { + t.Parallel() + stdOut, stdErr, err := e2e.Maru("run", "true-conditional-nested-task-comp-var-inputs", "--file", "src/test/tasks/conditionals/tasks.yaml") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "input val equals 5 and variable VAL1 equals 5") + }) + t.Run("test calling a task with nested false conditional comparing variables and inputs", func(t *testing.T) { + t.Parallel() + stdOut, stdErr, err := e2e.Maru("run", "false-conditional-nested-task-comp-var-inputs", "--file", "src/test/tasks/conditionals/tasks.yaml") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "Skipping action included-task-with-inputs") + }) + + t.Run("test calling a task with nested task true conditional comparing variables and inputs", func(t *testing.T) { + t.Parallel() + stdOut, stdErr, err := e2e.Maru("run", "true-conditional-nested-nested-task-comp-var-inputs", "--file", "src/test/tasks/conditionals/tasks.yaml") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "Task called successfully") + }) + t.Run("test calling a task with nested task false conditional comparing variables and inputs", func(t *testing.T) { + t.Parallel() + stdOut, stdErr, err := e2e.Maru("run", "false-conditional-nested-nested-task-comp-var-inputs", "--file", "src/test/tasks/conditionals/tasks.yaml") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "Skipping action included-task") + }) + + t.Run("test calling a task with nested task calling a task with true conditional comparing variables and inputs", func(t *testing.T) { + t.Parallel() + stdOut, stdErr, err := e2e.Maru("run", "true-conditional-nested-nested-nested-task-comp-var-inputs", "--file", "src/test/tasks/conditionals/tasks.yaml") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "\"input val2 equals 5 and variable VAL1 equals 5\"") + }) + t.Run("test calling a task with nested task calling a task with false conditional comparing variables and inputs", func(t *testing.T) { + t.Parallel() + stdOut, stdErr, err := e2e.Maru("run", "false-conditional-nested-nested-nested-task-comp-var-inputs", "--file", "src/test/tasks/conditionals/tasks.yaml") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "Skipping action \"echo \\\"input val2 equals 7 and variable VAL1 equals 5\\\"\"") + }) + + t.Run("test calling a task with nested task calling a task with old style var as input true conditional comparing variables and inputs", func(t *testing.T) { + t.Parallel() + stdOut, stdErr, err := e2e.Maru("run", "true-condition-var-as-input-original-syntax-nested-nested-with-comp", "--file", "src/test/tasks/conditionals/tasks.yaml") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "\"input val2 equals 5 and variable VAL1 equals 5\"") + }) + + t.Run("test calling a task with nested task calling a task with new style var as input true conditional comparing variables and inputs", func(t *testing.T) { + t.Parallel() + stdOut, stdErr, err := e2e.Maru("run", "true-condition-var-as-input-new-syntax-nested-nested-with-comp", "--file", "src/test/tasks/conditionals/tasks.yaml") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "\"input val2 equals 5 and variable VAL1 equals 5\"") + }) } diff --git a/src/test/tasks/conditionals/tasks.yaml b/src/test/tasks/conditionals/tasks.yaml new file mode 100644 index 0000000..47fa3b1 --- /dev/null +++ b/src/test/tasks/conditionals/tasks.yaml @@ -0,0 +1,129 @@ +variables: + - name: FOO + default: default-value + - name: BAR + default: default-value + - name: VAL1 + default: "5" + - name: VAL2 + default: "10" + +tasks: + + - name: false-conditional-with-var-cmd + actions: + - cmd: echo "This should not run because .variables.BAR != default-value" + description: false-conditional-with-var-cmd + if: ${{ eq .variables.BAR "default-value1" }} + + - name: true-conditional-with-var-cmd + actions: + - cmd: echo "This should run because .variables.BAR = default-value" + description: true-conditional-with-var-cmd + if: ${{ eq .variables.BAR "default-value" }} + + - name: empty-conditional-cmd + actions: + - cmd: echo "This should run because there is no condition" + description: empty-conditional-cmd + + - name: empty-conditional-task + actions: + - task: included-task + + - name: true-conditional-task + actions: + - task: included-task + if: ${{ eq .variables.BAR "default-value" }} + + - name: false-conditional-task + actions: + - task: included-task + if: ${{ eq .variables.BAR "default-value1" }} + + - name: true-conditional-nested-task-comp-var-inputs + actions: + - task: included-task-with-inputs + with: + val: "5" + + - name: false-conditional-nested-task-comp-var-inputs + actions: + - task: included-task-with-inputs + with: + val: "7" + + - name: true-conditional-nested-nested-task-comp-var-inputs + actions: + - task: included-task-with-inputs-and-nested-task + with: + val: "5" + + - name: false-conditional-nested-nested-task-comp-var-inputs + actions: + - task: included-task-with-inputs-and-nested-task + with: + val: "7" + + - name: true-conditional-nested-nested-nested-task-comp-var-inputs + actions: + - task: included-task-with-inputs-and-nested-nested-task + with: + val: "5" + + - name: false-conditional-nested-nested-nested-task-comp-var-inputs + actions: + - task: included-task-with-inputs-and-nested-nested-task + with: + val: "7" + + - name: true-condition-var-as-input-original-syntax-nested-nested-with-comp + actions: + - task: included-task-with-inputs-and-nested-nested-task + with: + val: ${VAL1} + + - name: true-condition-var-as-input-new-syntax-nested-nested-with-comp + actions: + - task: included-task-with-inputs-and-nested-nested-task + with: + val: ${{ .variables.VAL1 }} + + - name: included-task + actions: + - cmd: echo "Task called successfully" + + - name: included-task-with-inputs + inputs: + val: + description: has no default + actions: + - cmd: echo "input val equals ${{ .inputs.val }} and variable VAL1 equals ${{ .variables.VAL1 }}" + description: "included-task-with-inputs" + if: ${{ eq .inputs.val .variables.VAL1 }} + + - name: included-task-with-inputs-and-nested-task + inputs: + val: + description: has no default + actions: + - task: included-task + if: ${{ eq .inputs.val .variables.VAL1 }} + + + - name: included-task-with-inputs-and-nested-nested-task + inputs: + val: + description: has no default + actions: + - task: included-task-nested + with: + val2: ${{ .inputs.val }} + + - name: included-task-nested + inputs: + val2: + description: has no default + actions: + - cmd: echo "input val2 equals ${{ .inputs.val2 }} and variable VAL1 equals ${{ .variables.VAL1 }}" + if: ${{ eq .inputs.val2 .variables.VAL1 }} diff --git a/src/types/tasks.go b/src/types/tasks.go index c6c0b28..b97f30c 100644 --- a/src/types/tasks.go +++ b/src/types/tasks.go @@ -37,6 +37,7 @@ type Action struct { *BaseAction[variables.ExtraVariableInfo] `json:",inline"` TaskReference string `json:"task,omitempty" jsonschema:"description=The task to run, mutually exclusive with cmd and wait"` With map[string]string `json:"with,omitempty" jsonschema:"description=Input parameters to pass to the task,type=object"` + If string `json:"if,omitempty" jsonschema:"description=Conditional to determine if the action should run"` } // TaskReference references the name of a task diff --git a/tasks.schema.json b/tasks.schema.json index 0eedb91..2a1c2a1 100644 --- a/tasks.schema.json +++ b/tasks.schema.json @@ -61,6 +61,10 @@ }, "type": "object", "description": "Input parameters to pass to the task" + }, + "if": { + "type": "string", + "description": "Conditional to determine if the action should run" } }, "additionalProperties": false,