diff --git a/examples/Runfile.yml b/examples/Runfile.yml index 8c632d7..7e49d37 100644 --- a/examples/Runfile.yml +++ b/examples/Runfile.yml @@ -8,6 +8,9 @@ includes: run2: runfile: ./run2/Runfile +env: + global_k1: "v1" + tasks: cook: env: diff --git a/pkg/runfile/runfile.go b/pkg/runfile/runfile.go index 8354793..8353048 100644 --- a/pkg/runfile/runfile.go +++ b/pkg/runfile/runfile.go @@ -18,6 +18,8 @@ type Runfile struct { Version string `json:"version,omitempty"` Includes map[string]IncludeSpec `json:"includes"` + Env EnvVar `json:"env,omitempty"` + DotEnv []string `json:"dotEnv,omitempty"` Tasks map[string]Task `json:"tasks"` } diff --git a/pkg/runfile/task-parser.go b/pkg/runfile/task-parser.go index b3708ad..30f1d0c 100644 --- a/pkg/runfile/task-parser.go +++ b/pkg/runfile/task-parser.go @@ -21,6 +21,21 @@ type ParsedTask struct { // func ParseTask(ctx Context, rf *Runfile, taskName string) (*ParsedTask, error) { func ParseTask(ctx Context, rf *Runfile, task Task) (*ParsedTask, *Error) { + globalEnv := make(map[string]string) + + if rf.Env != nil { + genv, err := parseEnvVars(ctx, rf.Env, EvaluationArgs{ + Shell: nil, + Env: nil, + }) + if err != nil { + return nil, err + } + for k, v := range genv { + globalEnv[k] = v + } + } + for _, requirement := range task.Requires { if requirement == nil { continue @@ -29,7 +44,7 @@ func ParseTask(ctx Context, rf *Runfile, task Task) (*ParsedTask, *Error) { if requirement.Sh != nil { cmd := createCommand(ctx, cmdArgs{ shell: []string{"sh", "-c"}, - env: nil, + env: ToEnviron(globalEnv), workingDir: filepath.Dir(rf.attrs.RunfilePath), cmd: *requirement.Sh, stdout: fn.Must(os.OpenFile(os.DevNull, os.O_WRONLY, 0o755)), @@ -79,20 +94,20 @@ func ParseTask(ctx Context, rf *Runfile, task Task) (*ParsedTask, *Error) { return nil, TaskWorkingDirectoryInvalid.WithErr(fmt.Errorf("path is not a directory")).WithMetadata("working-dir", *task.Dir) } - dotenvPaths, err := resolveDotEnvFiles(filepath.Dir(rf.attrs.RunfilePath), task.DotEnv...) + taskDotEnvPaths, err := resolveDotEnvFiles(filepath.Dir(rf.attrs.RunfilePath), task.DotEnv...) if err != nil { return nil, err } - dotenvVars, err := parseDotEnvFiles(dotenvPaths...) + taskDotenvVars, err := parseDotEnvFiles(taskDotEnvPaths...) if err != nil { return nil, err } // INFO: keys from task.Env will override those coming from dotenv files, when duplicated - envVars, err := parseEnvVars(ctx, task.Env, EvaluationArgs{ + taskEnvVars, err := parseEnvVars(ctx, task.Env, EvaluationArgs{ Shell: task.Shell, - Env: dotenvVars, + Env: fn.MapMerge(globalEnv, taskDotenvVars), }) if err != nil { return nil, err @@ -110,7 +125,7 @@ func ParseTask(ctx Context, rf *Runfile, task Task) (*ParsedTask, *Error) { return &ParsedTask{ Shell: task.Shell, WorkingDir: *task.Dir, - Env: fn.MapMerge(dotenvVars, envVars), + Env: fn.MapMerge(globalEnv, taskDotenvVars, taskEnvVars), Commands: commands, }, nil } diff --git a/pkg/runfile/task-parser_test.go b/pkg/runfile/task-parser_test.go index ddefa75..40a75da 100644 --- a/pkg/runfile/task-parser_test.go +++ b/pkg/runfile/task-parser_test.go @@ -631,8 +631,170 @@ echo "hi" }, } + testGlobalEnvVars := []test{ + { + name: "1. testing global env key-value item", + args: args{ + ctx: nil, + rf: &Runfile{ + Env: map[string]any{ + "k1": "v1", + }, + Tasks: map[string]Task{ + "test": { + ignoreSystemEnv: true, + Commands: []any{ + "echo hi", + }, + }, + }, + }, + taskName: "test", + }, + want: &ParsedTask{ + Shell: []string{"sh", "-c"}, + WorkingDir: fn.Must(os.Getwd()), + Env: map[string]string{ + "k1": "v1", + }, + Commands: []CommandJson{ + {Command: "echo hi"}, + }, + }, + wantErr: false, + }, + { + name: "2. testing global env key-shell value", + args: args{ + ctx: nil, + rf: &Runfile{ + Env: map[string]any{ + "k1": map[string]any{ + "sh": "echo hi", + }, + }, + Tasks: map[string]Task{ + "test": { + ignoreSystemEnv: true, + Commands: []any{ + "echo hi", + }, + }, + }, + }, + taskName: "test", + }, + want: &ParsedTask{ + Shell: []string{"sh", "-c"}, + WorkingDir: fn.Must(os.Getwd()), + Env: map[string]string{ + "k1": "hi", + }, + Commands: []CommandJson{ + {Command: "echo hi"}, + }, + }, + wantErr: false, + }, + { + name: "3. testing global env-var default value", + args: args{ + ctx: nil, + rf: &Runfile{ + Env: map[string]any{ + "k1": map[string]any{ + "default": map[string]any{ + "value": "default-value", + }, + }, + }, + Tasks: map[string]Task{ + "test": { + ignoreSystemEnv: true, + Commands: []any{ + "echo hi", + }, + }, + }, + }, + taskName: "test", + }, + want: &ParsedTask{ + Shell: []string{"sh", "-c"}, + WorkingDir: fn.Must(os.Getwd()), + Env: map[string]string{ + "k1": "default-value", + }, + Commands: []CommandJson{ + {Command: "echo hi"}, + }, + }, + wantErr: false, + }, + { + name: "4. overriding global env var at task level", + args: args{ + ctx: nil, + rf: &Runfile{ + Env: map[string]any{ + "k1": "v1", + }, + Tasks: map[string]Task{ + "test": { + ignoreSystemEnv: true, + Env: EnvVar{ + "k1": "task-level-v1", + }, + Commands: []any{ + "echo hi", + }, + }, + }, + }, + taskName: "test", + }, + want: &ParsedTask{ + Shell: []string{"sh", "-c"}, + WorkingDir: fn.Must(os.Getwd()), + Env: map[string]string{ + "k1": "task-level-v1", + }, + Commands: []CommandJson{ + {Command: "echo hi"}, + }, + }, + wantErr: false, + }, + + { + name: "5. required global env var", + args: args{ + ctx: nil, + rf: &Runfile{ + Env: map[string]any{ + "k1": map[string]any{ + "required": true, + }, + }, + Tasks: map[string]Task{ + "test": { + ignoreSystemEnv: true, + Commands: []any{ + "echo hi", + }, + }, + }, + }, + taskName: "test", + }, + want: nil, + wantErr: true, + }, + } + tests = append(tests, testRequires...) tests = append(tests, testEnviroments...) + tests = append(tests, testGlobalEnvVars...) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {