Skip to content
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
2 changes: 2 additions & 0 deletions examples/run1/Runfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ tasks:
echo:
cmd:
- echo "hello from run1"
- if: gt 1 0
cmd: echo "hello after condition check"

node:shell:
interactive: true
Expand Down
13 changes: 8 additions & 5 deletions pkg/runfile/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func processOutput(writer io.Writer, reader io.Reader, prefix *string) {
if err != nil {
// logger.Info("stdout", "msg", string(msg[:n]), "err", err)
if errors.Is(err, io.EOF) {
os.Stdout.Write(msg[:n])
writer.Write(msg[:n])
return
}
}
Expand All @@ -80,7 +80,7 @@ func processOutput(writer io.Writer, reader io.Reader, prefix *string) {

if prevByte == '\n' && prefix != nil {
// os.Stdout.WriteString(fmt.Sprintf("HERE... msg: '%s'", msg[:n]))
os.Stdout.WriteString(*prefix)
writer.Write([]byte(*prefix))
}

writer.Write(msg[:n])
Expand Down Expand Up @@ -124,16 +124,19 @@ func runTask(ctx Context, rf *Runfile, args runTaskArgs) *Error {
logger.Debug("debugging env", "pt.environ", pt.Env, "overrides", args.envOverrides)
for _, command := range pt.Commands {
logger.Debug("running command task", "command.run", command.Run, "parent.task", args.taskName)

if command.If != nil && !*command.If {
logger.Debug("skipping execution for failed `if`", "command", command.Run)
continue
}

if command.Run != "" {
if err := runTask(ctx, rf, runTaskArgs{
taskTrail: trail,
taskName: command.Run,
envOverrides: pt.Env,
}); err != nil {
return err
// return NewError("", "").WithTask(fmt.Sprintf("%s/%s", err.TaskName, command.Run)).WithRunfile(rf.attrs.RunfilePath).WithErr(err.WithMetadata())
// e := formatErr(err).WithTask(fmt.Sprintf("%s/%s", err.TaskName, command.Run))
// return e
}
continue
}
Expand Down
75 changes: 47 additions & 28 deletions pkg/runfile/task-parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,31 @@ import (
)

type ParsedTask struct {
Shell []string `json:"shell"`
WorkingDir string `json:"workingDir"`
Env map[string]string `json:"environ"`
Interactive bool `json:"interactive,omitempty"`
Commands []CommandJson `json:"commands"`
Shell []string `json:"shell"`
WorkingDir string `json:"workingDir"`
Env map[string]string `json:"environ"`
Interactive bool `json:"interactive,omitempty"`
Commands []ParsedCommandJson `json:"commands"`
}

func evalGoTemplateCondition(tpl string) (bool, *Error) {
t := template.New("requirement")
t = t.Funcs(sprig.FuncMap())
templateExpr := fmt.Sprintf(`{{ %s }}`, tpl)
t, err := t.Parse(templateExpr)
if err != nil {
return false, TaskRequirementIncorrect.WithErr(err).WithMetadata("requirement", tpl)
}
b := new(bytes.Buffer)
if err := t.ExecuteTemplate(b, "requirement", map[string]string{}); err != nil {
return false, TaskRequirementIncorrect.WithErr(err).WithMetadata("requirement", tpl)
}

if b.String() != "true" {
return false, TaskRequirementFailed.WithErr(fmt.Errorf("template must have evaluated to true")).WithMetadata("requirement", tpl)
}

return true, nil
}

func ParseTask(ctx Context, rf *Runfile, task Task) (*ParsedTask, *Error) {
Expand Down Expand Up @@ -71,22 +91,9 @@ func ParseTask(ctx Context, rf *Runfile, task Task) (*ParsedTask, *Error) {
}

if requirement.GoTmpl != nil {
t := template.New("requirement")
t = t.Funcs(sprig.FuncMap())
templateExpr := fmt.Sprintf(`{{ %s }}`, *requirement.GoTmpl)
t, err := t.Parse(templateExpr)
if err != nil {
return nil, TaskRequirementIncorrect.WithErr(err).WithMetadata("requirement", *requirement.GoTmpl)
}
b := new(bytes.Buffer)
if err := t.ExecuteTemplate(b, "requirement", map[string]string{}); err != nil {
return nil, TaskRequirementIncorrect.WithErr(err).WithMetadata("requirement", *requirement.GoTmpl)
}

if b.String() != "true" {
return nil, TaskRequirementFailed.WithErr(fmt.Errorf("template must have evaluated to true")).WithMetadata("requirement", *requirement.GoTmpl)
if _, err := evalGoTemplateCondition(*requirement.GoTmpl); err != nil {
return nil, err
}

continue
}
}
Expand Down Expand Up @@ -127,7 +134,7 @@ func ParseTask(ctx Context, rf *Runfile, task Task) (*ParsedTask, *Error) {
return nil, err
}

commands := make([]CommandJson, 0, len(task.Commands))
commands := make([]ParsedCommandJson, 0, len(task.Commands))
for i := range task.Commands {
c2, err := parseCommand(rf, task.Commands[i])
if err != nil {
Expand Down Expand Up @@ -169,11 +176,11 @@ func resolveDotEnvFiles(pwd string, dotEnvFiles ...string) ([]string, *Error) {
return paths, nil
}

func parseCommand(rf *Runfile, command any) (*CommandJson, *Error) {
func parseCommand(rf *Runfile, command any) (*ParsedCommandJson, *Error) {
switch c := command.(type) {
case string:
{
return &CommandJson{Command: c}, nil
return &ParsedCommandJson{Command: c}, nil
}
case map[string]any:
{
Expand All @@ -187,15 +194,27 @@ func parseCommand(rf *Runfile, command any) (*CommandJson, *Error) {
return nil, CommandInvalid.WithErr(err).WithMetadata("command", command)
}

if cj.Run == "" {
return nil, CommandInvalid.WithErr(fmt.Errorf("key: 'run', must be specified when setting command in json format")).WithMetadata("command", command)
if cj.Run == "" && cj.Command == "" {
return nil, CommandInvalid.WithErr(fmt.Errorf("key: 'run'/'cmd', must be specified when setting command in json format")).WithMetadata("command", cj)
}

if _, ok := rf.Tasks[cj.Run]; !ok {
return nil, CommandInvalid.WithErr(fmt.Errorf("run target, not found")).WithMetadata("command", command, "run-target", cj.Run)
var pcj ParsedCommandJson
pcj.Run = cj.Run
pcj.Command = cj.Command

if cj.If != nil {
ok, _ := evalGoTemplateCondition(*cj.If)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Error from evalGoTemplateCondition should not be ignored

Silently ignoring template evaluation errors could mask serious problems. Consider handling or propagating the error.

// if err != nil {
// return nil, err
// }
pcj.If = &ok
}

return &cj, nil
// if _, ok := rf.Tasks[cj.Run]; !ok {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Run target existence check should be retained

Removing this check could lead to runtime failures when trying to execute non-existent tasks.

// return nil, CommandInvalid.WithErr(fmt.Errorf("run target, not found")).WithMetadata("command", command, "run-target", cj.Run)
// }

return &pcj, nil
}
default:
{
Expand Down
19 changes: 17 additions & 2 deletions pkg/runfile/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,26 @@ type Task struct {
// List of commands to be executed in given shell (default: sh)
// can take multiple forms
// - simple string
// - a json object with key `run`, signifying other tasks to run
// - a json object with key
// `run`, signifying other tasks to run
// `if`, condition when to run this server
Commands []any `json:"cmd"`
}

type CommandJson struct {
Command string
Command string `json:"cmd"`
Run string `json:"run"`
Env string `json:"env"`

// If is a go template expression, which must evaluate to true, for task to run
If *string `json:"if,omitempty"`
}

type ParsedCommandJson struct {
Command string `json:"cmd"`
Run string `json:"run"`
Env string `json:"env"`

// If is a go template expression, which must evaluate to true, for task to run
If *bool `json:"if"`
}