Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #24 from eko/added-env-file
Added support for an environment variable file on local apps
  • Loading branch information
eko committed Sep 21, 2019
2 parents f71fea9 + fd5e89f commit 9685673
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 22 deletions.
2 changes: 2 additions & 0 deletions example.yaml
Expand Up @@ -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
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions 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
42 changes: 25 additions & 17 deletions pkg/config/model.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
51 changes: 51 additions & 0 deletions 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
}
}
89 changes: 89 additions & 0 deletions 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",
},
},
}
}
6 changes: 2 additions & 4 deletions pkg/runner/runner.go
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion pkg/runner/runner_test.go
Expand Up @@ -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"
)
Expand Down
4 changes: 4 additions & 0 deletions 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

0 comments on commit 9685673

Please sign in to comment.