1
0
mirror of https://github.com/go-task/task.git synced 2025-05-13 22:16:31 +02:00

#324 implement dotenv

This commit is contained in:
Chris Garrett 2020-08-03 16:18:38 -06:00
parent e5a3c861cb
commit 8b962fb8e8
13 changed files with 144 additions and 1 deletions

View File

@ -33,8 +33,10 @@ executable called must be available by the OS or in PATH.
If you omit a task name, "default" will be assumed. If you omit a task name, "default" will be assumed.
## Environment ## Environment variables
### Task
You can use `env` to set custom environment variables for a specific task: You can use `env` to set custom environment variables for a specific task:
```yaml ```yaml
@ -66,6 +68,39 @@ tasks:
> NOTE: `env` supports expansion and retrieving output from a shell command > NOTE: `env` supports expansion and retrieving output from a shell command
> just like variables, as you can see on the [Variables](#variables) section. > just like variables, as you can see on the [Variables](#variables) section.
### Operating System
Environment variables from the OS are accessible using `$VARNAME`:
```yaml
version: '2'
tasks:
greet:
cmds:
- echo "Hello $USER"
```
### .env
*.env* files are supported in v3 using the `dotenv` declaration:
.env
```
KEYNAME=VALUE
```
Taskfile.yml
```yaml
version: '3'
dotenv: ['.env']
tasks:
greet:
cmds:
- echo "Using $KEYNAME"
```
## Operating System specific tasks ## Operating System specific tasks
If you add a `Taskfile_{{GOOS}}.yml` you can override or amend your Taskfile If you add a `Taskfile_{{GOOS}}.yml` you can override or amend your Taskfile

1
go.mod
View File

@ -3,6 +3,7 @@ module github.com/go-task/task/v2
require ( require (
github.com/fatih/color v1.7.0 github.com/fatih/color v1.7.0
github.com/go-task/slim-sprig v0.0.0-20200516131648-f9bac4e523eb github.com/go-task/slim-sprig v0.0.0-20200516131648-f9bac4e523eb
github.com/joho/godotenv v1.3.0
github.com/mattn/go-colorable v0.1.2 // indirect github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-zglob v0.0.1 github.com/mattn/go-zglob v0.0.1
github.com/radovskyb/watcher v1.0.5 github.com/radovskyb/watcher v1.0.5

2
go.sum
View File

@ -11,6 +11,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
github.com/go-task/slim-sprig v0.0.0-20200516131648-f9bac4e523eb h1:/qbv1F67s6ehqX9mG23cJOeca3FWpOVKgtPfPUMAi0k= github.com/go-task/slim-sprig v0.0.0-20200516131648-f9bac4e523eb h1:/qbv1F67s6ehqX9mG23cJOeca3FWpOVKgtPfPUMAi0k=
github.com/go-task/slim-sprig v0.0.0-20200516131648-f9bac4e523eb/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20200516131648-f9bac4e523eb/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=

View File

@ -3,6 +3,7 @@ package read
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/joho/godotenv"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -16,6 +17,7 @@ import (
var ( var (
// ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes // ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes
ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main Taskfile") ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main Taskfile")
ErrIncludedTaskfilesCantHaveDotenvs = errors.New("task: Included Taskfiles can't have dotenv declarations. Please, move the dotenv declaration to the main Taskfile")
) )
// Taskfile reads a Taskfile for a given directory // Taskfile reads a Taskfile for a given directory
@ -34,6 +36,22 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
return nil, err return nil, err
} }
if v >= 3.0 {
if len(t.Dotenv) > 0 {
for _, envFile := range t.Dotenv {
var envFilePath string
if filepath.IsAbs(envFile) {
envFilePath = envFile
} else {
envFilePath = filepath.Join(dir, envFile)
}
if err = godotenv.Load(envFilePath); err != nil {
return nil, err
}
}
}
}
for namespace, includedTask := range t.Includes { for namespace, includedTask := range t.Includes {
if v >= 3.0 { if v >= 3.0 {
tr := templater.Templater{Vars: &taskfile.Vars{}, RemoveNoValue: true} tr := templater.Templater{Vars: &taskfile.Vars{}, RemoveNoValue: true}
@ -68,6 +86,12 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
return nil, ErrIncludedTaskfilesCantHaveIncludes return nil, ErrIncludedTaskfilesCantHaveIncludes
} }
if v >= 3.0 {
if len(includedTaskfile.Dotenv) > 0 {
return nil, ErrIncludedTaskfilesCantHaveDotenvs
}
}
if includedTask.AdvancedImport { if includedTask.AdvancedImport {
for _, task := range includedTaskfile.Tasks { for _, task := range includedTaskfile.Tasks {
if !filepath.IsAbs(task.Dir) { if !filepath.IsAbs(task.Dir) {

View File

@ -16,6 +16,7 @@ type Taskfile struct {
Env *Vars Env *Vars
Tasks Tasks Tasks Tasks
Silent bool Silent bool
Dotenv []string
} }
// UnmarshalYAML implements yaml.Unmarshaler interface // UnmarshalYAML implements yaml.Unmarshaler interface
@ -30,6 +31,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
Env *Vars Env *Vars
Tasks Tasks Tasks Tasks
Silent bool Silent bool
Dotenv []string
} }
if err := unmarshal(&taskfile); err != nil { if err := unmarshal(&taskfile); err != nil {
return err return err
@ -43,6 +45,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
tf.Env = taskfile.Env tf.Env = taskfile.Env
tf.Tasks = taskfile.Tasks tf.Tasks = taskfile.Tasks
tf.Silent = taskfile.Silent tf.Silent = taskfile.Silent
tf.Dotenv = taskfile.Dotenv
if tf.Expansions <= 0 { if tf.Expansions <= 0 {
tf.Expansions = 2 tf.Expansions = 2
} }

View File

@ -816,3 +816,50 @@ func TestShortTaskNotation(t *testing.T) {
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"})) assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
assert.Equal(t, "string-slice-1\nstring-slice-2\nstring\n", buff.String()) assert.Equal(t, "string-slice-1\nstring-slice-2\nstring\n", buff.String())
} }
func TestDotenvShouldIncludeAllEnvFiles(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/dotenv",
Target: "default",
TrimSpace: false,
Files: map[string]string{
"include.txt": "INCLUDE1='from_include1' INCLUDE2='from_include2'\n",
},
}
tt.Run(t)
}
func TestDotenvShouldErrorWithIncludeEnvPath(t *testing.T) {
const dir = "testdata/dotenv"
const entry = "Taskfile-errors1.yml"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Entrypoint: entry,
Summary: true,
Stdout: &buff,
Stderr: &buff,
}
err := e.Setup()
assert.Error(t, err)
assert.Contains(t, err.Error(), "no such file")
}
func TestDotenvShouldErrorWhenIncludingDependantDotenvs(t *testing.T) {
const dir = "testdata/dotenv"
const entry = "Taskfile-errors2.yml"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Entrypoint: entry,
Summary: true,
Stdout: &buff,
Stderr: &buff,
}
err := e.Setup()
assert.Error(t, err)
assert.Contains(t, err.Error(), "move the dotenv")
}

1
testdata/dotenv/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.txt

8
testdata/dotenv/Taskfile-errors1.yml vendored Normal file
View File

@ -0,0 +1,8 @@
version: '3'
dotenv: ['include1/.env', 'include1/envs/.env', 'file-does-not-exist']
tasks:
default:
cmds:
- echo "INCLUDE1='$INCLUDE1' INCLUDE2='$INCLUDE2'" > include-errors1.txt

9
testdata/dotenv/Taskfile-errors2.yml vendored Normal file
View File

@ -0,0 +1,9 @@
version: '3'
includes:
include1: './include1'
tasks:
default:
cmds:
- echo "INCLUDE1='$INCLUDE1' INCLUDE2='$INCLUDE2'" > include-errors2.txt

8
testdata/dotenv/Taskfile.yml vendored Normal file
View File

@ -0,0 +1,8 @@
version: '3'
dotenv: ['include1/.env', 'include1/envs/.env']
tasks:
default:
cmds:
- echo "INCLUDE1='$INCLUDE1' INCLUDE2='$INCLUDE2'" > include.txt

1
testdata/dotenv/include1/.env vendored Normal file
View File

@ -0,0 +1 @@
INCLUDE1=from_include1

3
testdata/dotenv/include1/Taskfile.yml vendored Normal file
View File

@ -0,0 +1,3 @@
version: '3'
dotenv: ['.env']

1
testdata/dotenv/include1/envs/.env vendored Normal file
View File

@ -0,0 +1 @@
INCLUDE2=from_include2