Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add a dry run flag for validating tasks #158

Merged
merged 11 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ func (i *listFlag) Set(value string) error {
return nil
}

// dryRun is a flag to only load / validate tasks without running commands
var dryRun bool

// setRunnerVariables provides a map of set variables from the command line
var setRunnerVariables map[string]string

Expand Down Expand Up @@ -140,7 +143,7 @@ var runCmd = &cobra.Command{
if len(args) > 0 {
taskName = args[0]
}
if err := runner.Run(tasksFile, taskName, setRunnerVariables); err != nil {
if err := runner.Run(tasksFile, taskName, setRunnerVariables, dryRun); err != nil {
message.Fatalf(err, "Failed to run action: %s", err.Error())
}
},
Expand Down Expand Up @@ -241,6 +244,7 @@ func init() {
rootCmd.AddCommand(runCmd)
runFlags := runCmd.Flags()
runFlags.StringVarP(&config.TaskFileLocation, "file", "f", config.TasksYAML, lang.CmdRunFlag)
runFlags.BoolVar(&dryRun, "dry-run", false, lang.CmdRunDryRun)

// Setup the --list flag
flag.Var(&listTasks, "list", lang.CmdRunList)
Expand Down
1 change: 1 addition & 0 deletions src/config/lang/english.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const (
CmdRunWithVarFlag = "Set the inputs for a task from the command line (KEY=value)"
CmdRunList = "List available tasks in a task file"
CmdRunListAll = "List all available tasks in a task file, including tasks from included files"
CmdRunDryRun = "Validate the task without actually running any commands"
)

// Common Errors
Expand Down
28 changes: 17 additions & 11 deletions src/pkg/runner/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,10 @@ func (r *Runner) performAction(action types.Action, withs map[string]string, inp
return err
}
} else {
err := RunAction(action.BaseAction, r.envFilePath, r.variableConfig)
err := RunAction(action.BaseAction, r.envFilePath, r.variableConfig, r.dryRun)
if err != nil {
return err
}

}
return nil
}
Expand Down Expand Up @@ -106,7 +105,7 @@ func getUniqueTaskActions(actions []types.Action) []types.Action {
}

// RunAction executes a specific action command, either wait or cmd. It handles variable loading environment variables and manages retries and timeouts
func RunAction[T any](action *types.BaseAction[T], envFilePath string, variableConfig *variables.VariableConfig[T]) error {
func RunAction[T any](action *types.BaseAction[T], envFilePath string, variableConfig *variables.VariableConfig[T], dryRun bool) error {
var (
ctx context.Context
cancel context.CancelFunc
Expand Down Expand Up @@ -145,6 +144,19 @@ func RunAction[T any](action *types.BaseAction[T], envFilePath string, variableC
action.SetVariables = []variables.Variable[T]{}
}

if action.Description != "" {
cmdEscaped = action.Description
} else {
cmdEscaped = helpers.Truncate(cmd, 60, false)
}

// if this is a dry run, print the command that would run and return
if dryRun {
message.SLog.Info(fmt.Sprintf("Dry-running %q", cmdEscaped))
fmt.Println(cmd)
return nil
}

// load the contents of the env file into the Action + the MARU_ARCH
if envFilePath != "" {
envFilePath := filepath.Join(filepath.Dir(config.TaskFileLocation), envFilePath)
Expand All @@ -155,19 +167,13 @@ func RunAction[T any](action *types.BaseAction[T], envFilePath string, variableC
action.Env = append(action.Env, strings.Split(string(envFileContents), "\n")...)
}

if action.Description != "" {
cmdEscaped = action.Description
} else {
cmdEscaped = helpers.Truncate(cmd, 60, false)
}

spinner := message.NewProgressSpinner("Running \"%s\"", cmdEscaped)
spinner := message.NewProgressSpinner("Running %q", cmdEscaped)

cfg := GetBaseActionCfg(types.ActionDefaults{}, *action, variableConfig.GetSetVariables())

if cmd = exec.MutateCommand(cmd, cfg.Shell); err != nil {
message.SLog.Debug(err.Error())
spinner.Failf("Error mutating command: %s", cmdEscaped)
spinner.Failf("Error mutating command: %q", cmdEscaped)
}

// Template dir string
Expand Down
7 changes: 6 additions & 1 deletion src/pkg/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ type Runner struct {
TaskNameMap map[string]bool
envFilePath string
variableConfig *variables.VariableConfig[variables.ExtraVariableInfo]
dryRun bool
}

// Run runs a task from tasks file
func Run(tasksFile types.TasksFile, taskName string, setVariables map[string]string) error {
func Run(tasksFile types.TasksFile, taskName string, setVariables map[string]string, dryRun bool) error {
if dryRun {
message.SLog.Info("Dry-run has been set - only printing the commands that would run:")
}

// Populate the variables loaded in the root task file
rootVariables := tasksFile.Variables
Expand Down Expand Up @@ -61,6 +65,7 @@ func Run(tasksFile types.TasksFile, taskName string, setVariables map[string]str
TasksFile: tasksFile,
TaskNameMap: map[string]bool{},
variableConfig: combinedVariableConfig,
dryRun: dryRun,
}

task, err := runner.getTask(taskName)
Expand Down
15 changes: 12 additions & 3 deletions src/test/e2e/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ func TestTaskRunner(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\"")
require.Contains(t, stdErr, "Completed \"echo \\\"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()
Expand All @@ -436,14 +436,14 @@ func TestTaskRunner(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\"")
require.Contains(t, stdErr, "Completed \"echo \\\"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\"")
require.Contains(t, stdErr, "Completed \"echo \\\"input val2 equals 5 and variable VAL1 equals 5\\\"\"")
})

t.Run("run successful pattern", func(t *testing.T) {
Expand All @@ -461,4 +461,13 @@ func TestTaskRunner(t *testing.T) {
require.Error(t, err, stdOut, stdErr)
require.Contains(t, stdErr, "\"HELLO\" does not match pattern \"^HELLO$\"")
})

t.Run("dry run", func(t *testing.T) {
t.Parallel()

stdOut, stdErr, err := e2e.Maru("run", "--dry-run", "--file", "src/test/tasks/tasks.yaml", "env-from-file")
require.NoError(t, err, stdOut, stdErr)
require.Contains(t, stdErr, "Dry-running \"echo $MARU_ARCH\"")
require.Contains(t, stdOut, "echo env var from calling task - $SECRET_KEY")
})
}
Loading