Skip to content

Commit

Permalink
Merge branch 'main' into allow_direct_task_calls
Browse files Browse the repository at this point in the history
  • Loading branch information
zachariahmiller authored Sep 19, 2024
2 parents 2ffb2ce + 87c56e4 commit 5862fde
Show file tree
Hide file tree
Showing 10 changed files with 292 additions and 32 deletions.
4 changes: 2 additions & 2 deletions .github/actions/zarf/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 5 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,13 @@ 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
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
Expand Down
30 changes: 20 additions & 10 deletions src/pkg/runner/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]))
Expand All @@ -51,14 +59,16 @@ 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 {
err := RunAction(action.BaseAction, r.envFilePath, r.variableConfig)
if err != nil {
return err
}

}
return nil
}
Expand Down
10 changes: 8 additions & 2 deletions src/pkg/runner/actions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package runner

import (
"reflect"
"slices"
"testing"

"github.com/defenseunicorns/maru-runner/src/config"
Expand Down Expand Up @@ -200,6 +201,7 @@ func Test_validateActionableTaskCall(t *testing.T) {
args: args{
execContext: "internal",
inputTaskName: "testTask",

inputs: map[string]types.InputParameter{
"input1": {Required: true, Default: "defaultValue"},
"input2": {Required: true, Default: ""},
Expand Down Expand Up @@ -229,6 +231,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
Expand All @@ -237,6 +241,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{
Expand Down Expand Up @@ -295,7 +300,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)
}
Expand Down Expand Up @@ -500,7 +505,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")
})
}
Expand Down
9 changes: 6 additions & 3 deletions src/pkg/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ func Run(tasksFile types.TasksFile, taskName string, setVariables map[string]str
if err = runner.checkForTaskLoops(task, runner.TasksFile, setVariables); err != nil {
return err
}
err = runner.executeTask(task)

err = runner.executeTask(task, nil)

return err
}

Expand Down Expand Up @@ -266,7 +268,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
Expand All @@ -283,7 +285,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
}
}
Expand Down
31 changes: 20 additions & 11 deletions src/pkg/utils/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,45 +16,54 @@ 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
for name := range withs {
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
Expand Down
98 changes: 97 additions & 1 deletion src/test/e2e/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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\"")
})
}
Loading

0 comments on commit 5862fde

Please sign in to comment.