From fd5e89f0317f037ea6127884b20b280226ff4018 Mon Sep 17 00:00:00 2001 From: Vincent Composieux Date: Sat, 21 Sep 2019 20:20:44 +0200 Subject: [PATCH] Added support for an environment variable file on local apps --- example.yaml | 2 + internal/tests/runner/test.env | 6 +++ pkg/config/model.go | 42 +++++++++------- pkg/runner/env.go | 51 +++++++++++++++++++ pkg/runner/env_test.go | 89 ++++++++++++++++++++++++++++++++++ pkg/runner/runner.go | 6 +-- pkg/runner/runner_test.go | 2 +- test.env | 4 ++ 8 files changed, 180 insertions(+), 22 deletions(-) create mode 100644 internal/tests/runner/test.env create mode 100644 pkg/runner/env.go create mode 100644 pkg/runner/env_test.go create mode 100644 test.env diff --git a/example.yaml b/example.yaml index 07189ad0..c5657903 100644 --- a/example.yaml +++ b/example.yaml @@ -22,6 +22,7 @@ watcher: # Optional - cmd/main.go env: # Optional, in case you want to specify some environment variables for this app HTTP_PORT: 8005 + env_file: "github.com/eko/graphql/.env" # Optional, in case you want to specify some environment variables from a file setup: # Optional, in case you want to setup the project first if directory does not exists - go get github.com/eko/graphql - echo You can use ~/path syntax and environment variables like $GOPATH in your commands @@ -37,6 +38,7 @@ watcher: # Optional - main.go env: # Optional, in case you want to specify some environment variables for this app GRPC_PORT: 8006 + env_file: "github.com/eko/grpc-api/.env" # Optional, in case you want to specify some environment variables from a file setup: # Optional, in case you want to setup the project first if directory does not exists - go get github.com/eko/grpc-api - echo You can use ~/path syntax and environment variables like $GOPATH in your commands diff --git a/internal/tests/runner/test.env b/internal/tests/runner/test.env new file mode 100644 index 00000000..6d416921 --- /dev/null +++ b/internal/tests/runner/test.env @@ -0,0 +1,6 @@ +# This is a comment, should be ignored +MY_ENVFILE_VAR_1=this is ok + +# This is a second comment, should be ignored too +MY_ENVFILE_VAR_2=this is really good +MY_ENVFILE_VAR_3=great diff --git a/pkg/config/model.go b/pkg/config/model.go index dfb9c90c..2074f757 100644 --- a/pkg/config/model.go +++ b/pkg/config/model.go @@ -61,28 +61,18 @@ type Application struct { Hostname string `yaml:"hostname"` Watch bool `yaml:"watch"` Env map[string]string `yaml:"env"` + EnvFile string `yaml:"env_file"` Setup []string `yaml:"setup"` } +// GetEnvFile returns the filename guessed with current application environment +func (a *Application) GetEnvFile() string { + return getValueByExecutionContext(a.EnvFile, a.Executable) +} + // GetPath returns the path dependending on overrided value or not func (a *Application) GetPath() string { - path := a.Path - - if strings.Contains(a.Path, "~") { - path = strings.Replace(a.Path, "~", "$HOME", -1) - } - - switch a.Executable { - case ExecutableGo: - // First try to use the given directory, else, add the Go's $GOPATH - if _, err := os.Stat(path); os.IsNotExist(err) { - path = fmt.Sprintf("$GOPATH/src/%s", a.Path) - } - } - - path = os.ExpandEnv(path) - - return path + return getValueByExecutionContext(a.Path, a.Executable) } type Forward struct { @@ -117,3 +107,21 @@ type ForwardValues struct { type Watcher struct { Exclude []string `yaml:"exclude"` } + +func getValueByExecutionContext(path, executable string) string { + if strings.Contains(path, "~") { + path = strings.Replace(path, "~", "$HOME", -1) + } + + switch executable { + case ExecutableGo: + // First try to use the given directory, else, add the Go's $GOPATH + if _, err := os.Stat(path); os.IsNotExist(err) { + path = fmt.Sprintf("$GOPATH/src/%s", path) + } + } + + path = os.ExpandEnv(path) + + return path +} diff --git a/pkg/runner/env.go b/pkg/runner/env.go new file mode 100644 index 00000000..52b4fc83 --- /dev/null +++ b/pkg/runner/env.go @@ -0,0 +1,51 @@ +package runner + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "regexp" +) + +// addEnvVariables adds environment variables given as key/value pair +func (r *Runner) addEnvVariables(cmd *exec.Cmd, envs map[string]string) { + for key, value := range envs { + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, value)) + } +} + +// addEnvVariablesFromFile adds environment variables given as a filename +func (r *Runner) addEnvVariablesFromFile(cmd *exec.Cmd, filename string) { + if filename == "" { + return + } + + filename = os.ExpandEnv(filename) + + file, err := os.OpenFile(filename, os.O_RDONLY, os.ModePerm) + if err != nil { + r.view.Writef("❌ Unable to open environment file '%s': %v\n", filename, err) + return + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + + r, _ := regexp.Compile("([a-zA-Z0-9_]+)=(.*)") + matches := r.FindStringSubmatch(line) + + if len(matches) < 3 { + continue + } + + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", matches[1], matches[2])) + } + + if err := scanner.Err(); err != nil { + r.view.Writef("❌ An error has occured while reading environment file '%s': %v\n", filename, err) + return + } +} diff --git a/pkg/runner/env_test.go b/pkg/runner/env_test.go new file mode 100644 index 00000000..b22de1d4 --- /dev/null +++ b/pkg/runner/env_test.go @@ -0,0 +1,89 @@ +package runner + +import ( + "os" + "testing" + + mocks "github.com/eko/monday/internal/tests/mocks/proxy" + uimocks "github.com/eko/monday/internal/tests/mocks/ui" + "github.com/eko/monday/pkg/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestAddEnvVariables(t *testing.T) { + // Given + view := &uimocks.ViewInterface{} + view.On("Write", mock.Anything) + view.On("Writef", mock.Anything, mock.Anything, mock.Anything) + + proxy := &mocks.ProxyInterface{} + + project := getMockedProjectWithApplicationEnv() + + runner := NewRunner(view, proxy, project) + + // When + runner.Run(project.Applications[0]) + + // Then + assert.IsType(t, new(Runner), runner) + assert.Len(t, runner.cmds, 1) + + cmd := runner.cmds["test-app"] + + assert.Contains(t, cmd.Env, "MY_ENVVAR_1=value") + assert.Contains(t, cmd.Env, "MY_ENVVAR_2=My custom second value") +} + +func TestAddEnvVariablesFromFile(t *testing.T) { + // Given + view := &uimocks.ViewInterface{} + view.On("Write", mock.Anything) + view.On("Writef", mock.Anything, mock.Anything, mock.Anything) + + proxy := &mocks.ProxyInterface{} + + project := getMockedProjectWithApplicationEnv() + + runner := NewRunner(view, proxy, project) + + // When + runner.Run(project.Applications[0]) + + // Then + assert.IsType(t, new(Runner), runner) + assert.Len(t, runner.cmds, 1) + + cmd := runner.cmds["test-app"] + + assert.Contains(t, cmd.Env, "MY_ENVFILE_VAR_1=this is ok") + assert.Contains(t, cmd.Env, "MY_ENVFILE_VAR_2=this is really good") + assert.Contains(t, cmd.Env, "MY_ENVFILE_VAR_3=great") +} + +func getMockedProjectWithApplicationEnv() *config.Project { + dir, _ := os.Getwd() + + return &config.Project{ + Name: "My project name", + Applications: []*config.Application{ + &config.Application{ + Name: "test-app", + Path: "/", + Executable: "echo", + Args: []string{ + "OK", + "Arguments", + "Seems", + "-to=work", + }, + Env: map[string]string{ + "MY_ENVVAR_1": "value", + "MY_ENVVAR_2": "My custom second value", + }, + EnvFile: dir + "/../../internal/tests/runner/test.env", + }, + }, + } +} diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index 194b78fa..96ce7fd0 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -95,10 +95,8 @@ func (r *Runner) Run(application *config.Application) { cmd.Stderr = stderrStream cmd.Env = os.Environ() - // Add environment variables - for key, value := range application.Env { - cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, value)) - } + r.addEnvVariables(cmd, application.Env) + r.addEnvVariablesFromFile(cmd, application.GetEnvFile()) r.cmds[application.Name] = cmd diff --git a/pkg/runner/runner_test.go b/pkg/runner/runner_test.go index 954cec25..232f53ad 100644 --- a/pkg/runner/runner_test.go +++ b/pkg/runner/runner_test.go @@ -6,9 +6,9 @@ import ( "testing" "time" - "github.com/eko/monday/pkg/config" mocks "github.com/eko/monday/internal/tests/mocks/proxy" uimocks "github.com/eko/monday/internal/tests/mocks/ui" + "github.com/eko/monday/pkg/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) diff --git a/test.env b/test.env new file mode 100644 index 00000000..b0478adc --- /dev/null +++ b/test.env @@ -0,0 +1,4 @@ +# Ok this is a test of comment +MY_TEST_1=ok +MY_TEST_2=alwaysok +MY_TEST_3=sureok