mirror of
				https://github.com/go-task/task.git
				synced 2025-10-30 23:58:01 +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
						Andrey Nering
					
				
			
			
				
	
			
			
			
						parent
						
							cded9af90f
						
					
				
				
					commit
					08265ed1d7
				
			| @@ -78,18 +78,25 @@ setting: | ||||
| KEYNAME=VALUE | ||||
| ``` | ||||
|  | ||||
| ``` | ||||
| # testing/.env | ||||
| ENDPOINT=testing.com | ||||
| ``` | ||||
|  | ||||
| ```yaml | ||||
| # Taskfile.yml | ||||
|  | ||||
| version: '3' | ||||
|  | ||||
| dotenv: ['.env'] | ||||
| env: | ||||
|   ENV: testing | ||||
|  | ||||
| dotenv: ['.env', '{{.ENV}}/.env.', '{{.HOME}}/.env'] | ||||
|  | ||||
| tasks: | ||||
|   greet: | ||||
|     cmds: | ||||
|       - echo "Using $KEYNAME" | ||||
|       - echo "Using $KEYNAME and endpoint $ENDPOINT" | ||||
| ``` | ||||
|  | ||||
| ## Including other Taskfiles | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import ( | ||||
| // Compiler handles compilation of a task before its execution. | ||||
| // E.g. variable merger, template processing, etc. | ||||
| type Compiler interface { | ||||
| 	GetTaskfileVariables() (*taskfile.Vars, error) | ||||
| 	GetVariables(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) | ||||
|   | ||||
| @@ -30,6 +30,10 @@ type CompilerV2 struct { | ||||
| 	muDynamicCache sync.Mutex | ||||
| } | ||||
|  | ||||
| func (c *CompilerV2) GetTaskfileVariables() (*taskfile.Vars, error) { | ||||
| 	return &taskfile.Vars{}, nil | ||||
| } | ||||
|  | ||||
| // FastGetVariables is a no-op on v2 | ||||
| func (c *CompilerV2) FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) { | ||||
| 	return c.GetVariables(t, call) | ||||
|   | ||||
| @@ -29,17 +29,23 @@ type CompilerV3 struct { | ||||
| 	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) { | ||||
| 	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) { | ||||
| 	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.Set("TASK", taskfile.Var{Static: t.Task}) | ||||
| 	if t != nil { | ||||
| 		result.Set("TASK", taskfile.Var{Static: t.Task}) | ||||
| 	} | ||||
|  | ||||
| 	getRangeFunc := func(dir string) 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 { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if t == nil || call == nil { | ||||
| 		return result, nil | ||||
| 	} | ||||
|  | ||||
| 	if err := call.Vars.Range(rangeFunc); err != nil { | ||||
| 		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 != "" { | ||||
| 		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) | ||||
| } | ||||
|  | ||||
| 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) { | ||||
| 	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" | ||||
| 	"runtime" | ||||
|  | ||||
| 	"github.com/joho/godotenv" | ||||
| 	"gopkg.in/yaml.v3" | ||||
|  | ||||
| 	"github.com/go-task/task/v3/internal/templater" | ||||
| @@ -37,27 +36,6 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) { | ||||
| 		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 { | ||||
| 		if v >= 3.0 { | ||||
| 			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