mirror of
https://github.com/go-task/task.git
synced 2025-08-10 22:42:19 +02:00
Allow vars in dotenv paths, including environment variables
Closes #453 Closes #434 Ref #433 Co-authored-by: Andrey Nering <andrey@nering.com.br>
This commit is contained in:
committed by
Andrey Nering
parent
cded9af90f
commit
08265ed1d7
@@ -78,18 +78,25 @@ setting:
|
|||||||
KEYNAME=VALUE
|
KEYNAME=VALUE
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
# testing/.env
|
||||||
|
ENDPOINT=testing.com
|
||||||
|
```
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# Taskfile.yml
|
# Taskfile.yml
|
||||||
|
|
||||||
version: '3'
|
version: '3'
|
||||||
|
|
||||||
dotenv: ['.env']
|
env:
|
||||||
|
ENV: testing
|
||||||
|
|
||||||
|
dotenv: ['.env', '{{.ENV}}/.env.', '{{.HOME}}/.env']
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
greet:
|
greet:
|
||||||
cmds:
|
cmds:
|
||||||
- echo "Using $KEYNAME"
|
- echo "Using $KEYNAME and endpoint $ENDPOINT"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Including other Taskfiles
|
## Including other Taskfiles
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
// Compiler handles compilation of a task before its execution.
|
// Compiler handles compilation of a task before its execution.
|
||||||
// E.g. variable merger, template processing, etc.
|
// E.g. variable merger, template processing, etc.
|
||||||
type Compiler interface {
|
type Compiler interface {
|
||||||
|
GetTaskfileVariables() (*taskfile.Vars, error)
|
||||||
GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error)
|
GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error)
|
||||||
FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error)
|
FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error)
|
||||||
HandleDynamicVar(v taskfile.Var, dir string) (string, error)
|
HandleDynamicVar(v taskfile.Var, dir string) (string, error)
|
||||||
|
@@ -30,6 +30,10 @@ type CompilerV2 struct {
|
|||||||
muDynamicCache sync.Mutex
|
muDynamicCache sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *CompilerV2) GetTaskfileVariables() (*taskfile.Vars, error) {
|
||||||
|
return &taskfile.Vars{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// FastGetVariables is a no-op on v2
|
// FastGetVariables is a no-op on v2
|
||||||
func (c *CompilerV2) FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
func (c *CompilerV2) FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
||||||
return c.GetVariables(t, call)
|
return c.GetVariables(t, call)
|
||||||
|
@@ -29,17 +29,23 @@ type CompilerV3 struct {
|
|||||||
muDynamicCache sync.Mutex
|
muDynamicCache sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *CompilerV3) GetTaskfileVariables() (*taskfile.Vars, error) {
|
||||||
|
return c.getVariables(nil, nil, true)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CompilerV3) GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
func (c *CompilerV3) GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
||||||
return c.getVariables(t, call, true)
|
return c.getVariables(t, &call, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CompilerV3) FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
func (c *CompilerV3) FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
||||||
return c.getVariables(t, call, false)
|
return c.getVariables(t, &call, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CompilerV3) getVariables(t *taskfile.Task, call taskfile.Call, evaluateShVars bool) (*taskfile.Vars, error) {
|
func (c *CompilerV3) getVariables(t *taskfile.Task, call *taskfile.Call, evaluateShVars bool) (*taskfile.Vars, error) {
|
||||||
result := compiler.GetEnviron()
|
result := compiler.GetEnviron()
|
||||||
|
if t != nil {
|
||||||
result.Set("TASK", taskfile.Var{Static: t.Task})
|
result.Set("TASK", taskfile.Var{Static: t.Task})
|
||||||
|
}
|
||||||
|
|
||||||
getRangeFunc := func(dir string) func(k string, v taskfile.Var) error {
|
getRangeFunc := func(dir string) func(k string, v taskfile.Var) error {
|
||||||
return func(k string, v taskfile.Var) error {
|
return func(k string, v taskfile.Var) error {
|
||||||
@@ -74,6 +80,11 @@ func (c *CompilerV3) getVariables(t *taskfile.Task, call taskfile.Call, evaluate
|
|||||||
if err := c.TaskfileVars.Range(rangeFunc); err != nil {
|
if err := c.TaskfileVars.Range(rangeFunc); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if t == nil || call == nil {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
if err := call.Vars.Range(rangeFunc); err != nil {
|
if err := call.Vars.Range(rangeFunc); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
17
task.go
17
task.go
@@ -176,6 +176,23 @@ func (e *Executor) Setup() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v >= 3.0 {
|
||||||
|
env, err := read.Dotenv(e.Compiler, e.Taskfile, e.Dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = env.Range(func(key string, value taskfile.Var) error {
|
||||||
|
if _, ok := e.Taskfile.Env.Mapping[key]; !ok {
|
||||||
|
e.Taskfile.Env.Set(key, value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if v < 2.1 && e.Taskfile.Output != "" {
|
if v < 2.1 && e.Taskfile.Output != "" {
|
||||||
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
|
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
|
||||||
}
|
}
|
||||||
|
38
task_test.go
38
task_test.go
@@ -904,6 +904,44 @@ func TestDotenvShouldAllowMissingEnv(t *testing.T) {
|
|||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDotenvHasLocalEnvInPath(t *testing.T) {
|
||||||
|
tt := fileContentTest{
|
||||||
|
Dir: "testdata/dotenv/local_env_in_path",
|
||||||
|
Target: "default",
|
||||||
|
TrimSpace: false,
|
||||||
|
Files: map[string]string{
|
||||||
|
"var.txt": "VAR='var_in_dot_env_1'\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tt.Run(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDotenvHasLocalVarInPath(t *testing.T) {
|
||||||
|
tt := fileContentTest{
|
||||||
|
Dir: "testdata/dotenv/local_var_in_path",
|
||||||
|
Target: "default",
|
||||||
|
TrimSpace: false,
|
||||||
|
Files: map[string]string{
|
||||||
|
"var.txt": "VAR='var_in_dot_env_3'\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tt.Run(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDotenvHasEnvVarInPath(t *testing.T) {
|
||||||
|
os.Setenv("ENV_VAR", "testing")
|
||||||
|
|
||||||
|
tt := fileContentTest{
|
||||||
|
Dir: "testdata/dotenv/env_var_in_path",
|
||||||
|
Target: "default",
|
||||||
|
TrimSpace: false,
|
||||||
|
Files: map[string]string{
|
||||||
|
"var.txt": "VAR='var_in_dot_env_2'\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tt.Run(t)
|
||||||
|
}
|
||||||
|
|
||||||
func TestExitImmediately(t *testing.T) {
|
func TestExitImmediately(t *testing.T) {
|
||||||
const dir = "testdata/exit_immediately"
|
const dir = "testdata/exit_immediately"
|
||||||
|
|
||||||
|
46
taskfile/read/dotenv.go
Normal file
46
taskfile/read/dotenv.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package read
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/compiler"
|
||||||
|
"github.com/go-task/task/v3/internal/templater"
|
||||||
|
"github.com/go-task/task/v3/taskfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Dotenv(c compiler.Compiler, tf *taskfile.Taskfile, dir string) (*taskfile.Vars, error) {
|
||||||
|
vars, err := c.GetTaskfileVariables()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
env := &taskfile.Vars{}
|
||||||
|
|
||||||
|
tr := templater.Templater{Vars: vars, RemoveNoValue: true}
|
||||||
|
|
||||||
|
for _, dotEnvPath := range tf.Dotenv {
|
||||||
|
dotEnvPath = tr.Replace(dotEnvPath)
|
||||||
|
|
||||||
|
if !filepath.IsAbs(dotEnvPath) {
|
||||||
|
dotEnvPath = filepath.Join(dir, dotEnvPath)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(dotEnvPath); os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
envs, err := godotenv.Read(dotEnvPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for key, value := range envs {
|
||||||
|
if _, ok := env.Mapping[key]; !ok {
|
||||||
|
env.Set(key, taskfile.Var{Static: value})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return env, nil
|
||||||
|
}
|
@@ -7,7 +7,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/templater"
|
"github.com/go-task/task/v3/internal/templater"
|
||||||
@@ -37,27 +36,6 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if v >= 3.0 {
|
|
||||||
for _, dotEnvPath := range t.Dotenv {
|
|
||||||
if !filepath.IsAbs(dotEnvPath) {
|
|
||||||
dotEnvPath = filepath.Join(dir, dotEnvPath)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(dotEnvPath); os.IsNotExist(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
envs, err := godotenv.Read(dotEnvPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for key, value := range envs {
|
|
||||||
if _, ok := t.Env.Mapping[key]; !ok {
|
|
||||||
t.Env.Set(key, taskfile.Var{Static: value})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = t.Includes.Range(func(namespace string, includedTask taskfile.IncludedTaskfile) error {
|
err = t.Includes.Range(func(namespace string, includedTask taskfile.IncludedTaskfile) error {
|
||||||
if v >= 3.0 {
|
if v >= 3.0 {
|
||||||
tr := templater.Templater{Vars: &taskfile.Vars{}, RemoveNoValue: true}
|
tr := templater.Templater{Vars: &taskfile.Vars{}, RemoveNoValue: true}
|
||||||
|
1
testdata/dotenv/env_var_in_path/.env.testing
vendored
Normal file
1
testdata/dotenv/env_var_in_path/.env.testing
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
VAR_IN_DOTENV=var_in_dot_env_2
|
8
testdata/dotenv/env_var_in_path/Taskfile.yml
vendored
Normal file
8
testdata/dotenv/env_var_in_path/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
dotenv: [".env.{{.ENV_VAR}}"]
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
cmds:
|
||||||
|
- echo "VAR='$VAR_IN_DOTENV'" > var.txt
|
1
testdata/dotenv/local_env_in_path/.env.testing
vendored
Normal file
1
testdata/dotenv/local_env_in_path/.env.testing
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
VAR_IN_DOTENV=var_in_dot_env_1
|
11
testdata/dotenv/local_env_in_path/Taskfile.yml
vendored
Normal file
11
testdata/dotenv/local_env_in_path/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
env:
|
||||||
|
LOCAL_ENV: testing
|
||||||
|
|
||||||
|
dotenv: [".env.{{.LOCAL_ENV}}"]
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
cmds:
|
||||||
|
- echo "VAR='$VAR_IN_DOTENV'" > var.txt
|
1
testdata/dotenv/local_var_in_path/.env.testing
vendored
Normal file
1
testdata/dotenv/local_var_in_path/.env.testing
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
VAR_IN_DOTENV=var_in_dot_env_3
|
13
testdata/dotenv/local_var_in_path/Taskfile.yml
vendored
Normal file
13
testdata/dotenv/local_var_in_path/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
vars:
|
||||||
|
PART_1: test
|
||||||
|
PART_2: ing
|
||||||
|
LOCAL_VAR: "{{.PART_1}}{{.PART_2}}"
|
||||||
|
|
||||||
|
dotenv: [".env.{{.LOCAL_VAR}}"]
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
cmds:
|
||||||
|
- echo "VAR='$VAR_IN_DOTENV'" > var.txt
|
Reference in New Issue
Block a user