Skip to content

Commit

Permalink
feat: support shell cmd substitution (#81)
Browse files Browse the repository at this point in the history
* feat: support shell cmd subst
* supporting shell command substitution in the value. e.g. MY_ENV="$(echo hello)"
* refactoring: move Env and Envs to env.go file
* refactoring: Envs to have pointer of Env
* fix: use /bin/sh if default shell is empty
  • Loading branch information
sunggun-yu authored Oct 16, 2023
1 parent 1c92017 commit 9e9ea7f
Show file tree
Hide file tree
Showing 13 changed files with 383 additions and 238 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.19
go-version: 1.21
- name: Generate token
id: generate_token
uses: tibdex/github-app-token@v1
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
name: test

on:
push:
pull_request:
branches: [ main ]

Expand All @@ -16,7 +15,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.19
go-version: 1.21
- name: Run go mod tidy
run: go mod tidy
- name: Run Test
Expand Down
2 changes: 1 addition & 1 deletion cmd/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func addCommand() *cobra.Command {
name := args[0]
profile := config.Profile{
Desc: flags.desc,
Env: []config.Env{},
Env: []*config.Env{},
}
profile.Env = config.ParseEnvFlagToEnv(flags.env)

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/sunggun-yu/envp

go 1.19
go 1.21

require (
github.com/fatih/color v1.15.0
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
Expand Down Expand Up @@ -50,6 +51,7 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand All @@ -62,6 +64,7 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
70 changes: 0 additions & 70 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package config

import (
"sort"
"strings"
"sync"
)

Expand Down Expand Up @@ -105,71 +103,3 @@ func (c *Config) ProfileNames() []string {
defer c.mu.Unlock()
return c.Profiles.ProfileNames()
}

// ParseEnvFlagToMap parse string format "env=val" to map "env: val". it can be used fo dup check from slice of Env
func ParseEnvFlagToMap(envs []string) map[string]string {

if len(envs) == 0 {
return nil
}

r := map[string]string{}

for _, s := range envs {
ev := strings.Split(s, "=")
if len(ev) != 2 {
// TODO: handle unexpected format
continue
} else {
r[ev[0]] = ev[1]
}
}
return r
}

// ParseEnvFlagToEnv parse slice of string "var=val" to []ENV
func ParseEnvFlagToEnv(args []string) Envs {
if len(args) == 0 {
return nil
}

r := []Env{}

for _, s := range args {
ev := strings.Split(s, "=")
if len(ev) != 2 {
// TODO: handle unexpected format
//fmt.Println("WARN: wrong format of env item. it must be var=val.", ev, "will be ignored")
continue
} else {
r = append(r, Env{
Name: ev[0],
Value: ev[1],
})
}
}
SortEnv(r)
return r
}

// MapToEnv parse string map to slice of Env
func MapToEnv(m map[string]string) Envs {
r := []Env{}
for k, v := range m {
r = append(r, Env{
Name: k,
Value: v,
})
}
// sort it by env name
SortEnv(r)
return r
}

// SortEnv sort []Env by name asc
func SortEnv(e []Env) {
// sort it by env name
sort.Slice(e, func(i, j int) bool {
return e[i].Name < e[j].Name
})
}
11 changes: 5 additions & 6 deletions internal/config/config_file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package config

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
Expand All @@ -14,7 +13,7 @@ import (
"github.com/stretchr/testify/assert"
)

// perform test whithin single process but multi thread operation
// perform test within single process but multi thread operation
func TestConfigFile(t *testing.T) {

// assert
Expand Down Expand Up @@ -111,7 +110,7 @@ func TestRead(t *testing.T) {
- wrong
- 1
`
ioutil.WriteFile(testFile, []byte(wrongData), 0600)
os.WriteFile(testFile, []byte(wrongData), 0600)

cf.config = nil
_, err := cf.Read()
Expand Down Expand Up @@ -147,7 +146,7 @@ func TestWrite(t *testing.T) {
// Ginkgo test suite
// ---------------------------------------------------------------------------
var _ = Describe("NewConfigFile", func() {
When("set exisiting directory as config file", func() {
When("set existing directory as config file", func() {
testFile := fmt.Sprintf("/tmp/%v/%v", GinkgoRandomSeed(), GinkgoRandomSeed())
testDir := filepath.Dir(testFile)
os.Create(testDir)
Expand Down Expand Up @@ -186,7 +185,7 @@ var _ = Describe("NewConfigFile", func() {
testFile := "../../testdata/config.yaml"

// copy test config file
original, _ := ioutil.ReadFile("../../testdata/config.yaml")
original, _ := os.ReadFile("../../testdata/config.yaml")

_, err := NewConfigFile(testFile)

Expand All @@ -195,7 +194,7 @@ var _ = Describe("NewConfigFile", func() {
})

It("should not change existing config content", func() {
actual, err := ioutil.ReadFile(testFile)
actual, err := os.ReadFile(testFile)
Expect(err).NotTo(HaveOccurred())
Expect(actual).To(Equal(original))
})
Expand Down
103 changes: 2 additions & 101 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,16 @@ package config_test

import (
"fmt"
"io/ioutil"
"reflect"
"os"
"testing"

"github.com/sunggun-yu/envp/internal/config"
"gopkg.in/yaml.v2"
)

var (
testDataEnvs = config.Envs{
config.Env{Name: "VAR_C", Value: "VAL_C"},
config.Env{Name: "VAR_A", Value: "VAL_A"},
config.Env{Name: "VAR_D", Value: "VAL_D"},
config.Env{Name: "VAR_B", Value: "VAL_B"},
}

testDataEnvMap = map[string]string{
"VAR_A": "VAL_A",
"VAR_B": "VAL_B",
"VAR_C": "VAL_C",
"VAR_D": "VAL_D",
}

testDataArrStringFromFlag = []string{
"VAR_A=VAL_A",
"something_not_valid", // should be ignored
"VAR_B=VAL_B",
"VAR_C=VAL_C",
"VAR_D=VAL_D",
"not:valid", // should be ignored
" ", // should be ignored
"how=about=this", // should be ignored
}

testDataConfig = func() config.Config {
file, _ := ioutil.ReadFile("../../testdata/config.yaml")
file, _ := os.ReadFile("../../testdata/config.yaml")

var cfg config.Config
err := yaml.Unmarshal(file, &cfg)
Expand All @@ -48,79 +22,6 @@ var (
}
)

// test String() method and SortEnv
func TestEnv(t *testing.T) {
envs := testDataEnvs
// sort
config.SortEnv(envs)

// data must be sorted in key
// Env should return string in VAR=VAL format
// Envs should return comma separated string
expected := "VAR_A=VAL_A,VAR_B=VAL_B,VAR_C=VAL_C,VAR_D=VAL_D"
actual := envs.String()
if expected != actual {
t.Error("Not meet expectation", expected, "-", actual)
}
}

// test ParseEnvFlagToMap func
// ParseEnvFlagToMap should parse string format "env=val" to map "env: val"
func TestParseEnvFlagToMap(t *testing.T) {

t.Run("when set empty data", func(t *testing.T) {
// nil data test
if config.ParseEnvFlagToMap([]string{}) != nil {
t.Error("Not meet expectation. empty slice should return nil")
}
})

t.Run("when data exist", func(t *testing.T) {
testData := testDataArrStringFromFlag
expected := testDataEnvMap
actual := config.ParseEnvFlagToMap(testData)

if !reflect.DeepEqual(expected, actual) {
t.Error("Not meet expectation", expected, "-", actual)
}
})
}

// ParseEnvFlagToEnv should parse slice of string "var=val" to []ENV
func TestParseEnvFlagToEnv(t *testing.T) {

t.Run("when set empty data", func(t *testing.T) {
// nil data test
if config.ParseEnvFlagToEnv([]string{}) != nil {
t.Error("Not meet expectation. empty slice should return nil")
}
})

t.Run("when data exist", func(t *testing.T) {
testData := testDataArrStringFromFlag
// invalid format should be ignored without error
actual := config.ParseEnvFlagToEnv(testData)
expected := testDataEnvs
// ParseEnvFlagToEnv sort the result. so expected should be sorted
config.SortEnv(expected)
if !reflect.DeepEqual(expected, actual) {
t.Error("Not meet expectation", expected, "-", actual)
}
})
}

// test MapToEnv func
func TestMapToEnv(t *testing.T) {
testData := testDataEnvMap
expected := testDataEnvs
// sort. MapToEnv sort the result. so expected should be sorted
config.SortEnv(expected)
actual := config.MapToEnv(testData)
if !reflect.DeepEqual(expected, actual) {
t.Error("Not meet expectation", expected, "-", actual)
}
}

func TestDefaultProfile(t *testing.T) {
cfg := testDataConfig()

Expand Down
Loading

0 comments on commit 9e9ea7f

Please sign in to comment.