Skip to content

Commit d0cb329

Browse files
authored
fix: prepending PATH logic [IDE-1314] (#402)
Previously it would not re-prioritise to the front an entry prepended to the PATH. Plus a bit of a code refactor for clarity.
2 parents c3de6bd + 71eef51 commit d0cb329

File tree

3 files changed

+116
-24
lines changed

3 files changed

+116
-24
lines changed

pkg/envvars/environment.go

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ import (
1010
"time"
1111

1212
"github.com/subosito/gotenv"
13+
14+
"github.com/snyk/go-application-framework/pkg/utils"
15+
)
16+
17+
// Environment variable names
18+
const (
19+
PathEnvVarName = "PATH"
20+
ShellEnvVarName = "SHELL"
1321
)
1422

1523
// LoadConfiguredEnvironment updates the environment with local configuration. Precedence as follows:
@@ -24,7 +32,7 @@ func LoadConfiguredEnvironment(customConfigFiles []string, workingDirectory stri
2432
defer func() { _ = gotenv.Apply(strings.NewReader(bashOutput)) }() //nolint:errcheck // we can't do anything with the error
2533

2634
env := gotenv.Parse(strings.NewReader(bashOutput))
27-
specificShell, ok := env["SHELL"]
35+
specificShell, ok := env[ShellEnvVarName]
2836
if ok {
2937
fromSpecificShell := getEnvFromShell(specificShell)
3038
_ = gotenv.Apply(strings.NewReader(fromSpecificShell)) //nolint:errcheck // we can't do anything with the error
@@ -41,7 +49,7 @@ func LoadConfiguredEnvironment(customConfigFiles []string, workingDirectory stri
4149

4250
func loadFile(fileName string) {
4351
// preserve path
44-
path := os.Getenv("PATH")
52+
previousPath := os.Getenv(PathEnvVarName)
4553

4654
// overwrite existing variables with file config
4755
err := gotenv.OverLoad(fileName)
@@ -50,7 +58,7 @@ func loadFile(fileName string) {
5058
}
5159

5260
// add previous path to the end of the new
53-
UpdatePath(path, false)
61+
UpdatePath(previousPath, false)
5462
}
5563

5664
// guard against command injection
@@ -92,39 +100,38 @@ func getEnvFromShell(shell string) string {
92100
return string(env)
93101
}
94102

95-
// UpdatePath prepends or appends the extension to the current path. If the entry is already there, it skips it. The
96-
// result is set into the process environment with os.Setenv.
103+
// UpdatePath prepends or appends the extension to the current path.
104+
// For append, if the entry is already there, it will not be re-added / moved.
105+
// For prepend, if the entry is already there, it will be correctly re-prioritized to the front.
106+
// The result is set into the process environment with os.Setenv.
97107
//
98108
// pathExtension string the path component to be added.
99109
// prepend bool whether to pre- or append
100110
func UpdatePath(pathExtension string, prepend bool) string {
101-
pathVarName := "PATH"
111+
currentPath := os.Getenv(PathEnvVarName)
102112

103113
if pathExtension == "" {
104-
return os.Getenv(pathVarName)
114+
return currentPath
105115
}
106116

107-
currentPath := os.Getenv(pathVarName)
108-
currentPathEntries := strings.Split(currentPath, string(os.PathListSeparator))
109-
110-
pathEntries := map[string]bool{}
111-
for _, entry := range currentPathEntries {
112-
pathEntries[entry] = true
117+
if currentPath == "" {
118+
_ = os.Setenv(PathEnvVarName, pathExtension)
119+
return pathExtension
113120
}
114121

122+
currentPathEntries := strings.Split(currentPath, string(os.PathListSeparator))
115123
addPathEntries := strings.Split(pathExtension, string(os.PathListSeparator))
116-
var newPathSlice []string
117-
for _, entry := range addPathEntries {
118-
if !pathEntries[entry] {
119-
newPathSlice = append(newPathSlice, entry)
120-
}
121-
}
122124

123-
resultSlice := append(newPathSlice, currentPathEntries...)
124-
if !prepend {
125-
resultSlice = append(currentPathEntries, newPathSlice...)
125+
var combinedSliceWithDuplicates []string
126+
if prepend {
127+
combinedSliceWithDuplicates = append(addPathEntries, currentPathEntries...)
128+
} else {
129+
combinedSliceWithDuplicates = append(currentPathEntries, addPathEntries...)
126130
}
127-
newPath := strings.Join(resultSlice, string(os.PathListSeparator))
128-
_ = os.Setenv(pathVarName, newPath)
131+
132+
newPathSlice := utils.Dedupe(combinedSliceWithDuplicates)
133+
134+
newPath := strings.Join(newPathSlice, string(os.PathListSeparator))
135+
_ = os.Setenv(PathEnvVarName, newPath)
129136
return newPath
130137
}

pkg/envvars/environment_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,71 @@ func TestUpdatePathWithDefaults(t *testing.T) {
4242
require.Equal(t, "b"+pathListSep+pathFromEnv, os.Getenv("PATH"))
4343
})
4444

45+
t.Run("prepend re-prioritizes existing path segment to front", func(t *testing.T) {
46+
pathFromEnv := "a" + pathListSep + "b" + pathListSep + "c"
47+
t.Setenv("PATH", pathFromEnv)
48+
49+
UpdatePath("b", true)
50+
51+
require.Equal(t, "b"+pathListSep+"a"+pathListSep+"c", os.Getenv("PATH"))
52+
})
53+
54+
t.Run("add multiple entries at once (prepend)", func(t *testing.T) {
55+
pathFromEnv := "d" + pathListSep + "e"
56+
t.Setenv("PATH", pathFromEnv)
57+
58+
pathToPrepend := "a" + pathListSep + "b" + pathListSep + "c"
59+
UpdatePath(pathToPrepend, true)
60+
61+
require.Equal(t, pathToPrepend+pathListSep+pathFromEnv, os.Getenv("PATH"))
62+
})
63+
64+
t.Run("add multiple entries at once (append)", func(t *testing.T) {
65+
pathFromEnv := "a" + pathListSep + "b"
66+
t.Setenv("PATH", pathFromEnv)
67+
68+
pathToAppend := "c" + pathListSep + "d" + pathListSep + "e"
69+
UpdatePath(pathToAppend, false)
70+
71+
require.Equal(t, pathFromEnv+pathListSep+pathToAppend, os.Getenv("PATH"))
72+
})
73+
74+
t.Run("add multiple entries with duplicates of existing entries (prepend)", func(t *testing.T) {
75+
pathFromEnv := "b" + pathListSep + "d"
76+
t.Setenv("PATH", pathFromEnv)
77+
78+
UpdatePath("a"+pathListSep+"b"+pathListSep+"c", true)
79+
80+
require.Equal(t, "a"+pathListSep+"b"+pathListSep+"c"+pathListSep+"d", os.Getenv("PATH"))
81+
})
82+
83+
t.Run("add multiple entries with duplicates of existing entries (append)", func(t *testing.T) {
84+
pathFromEnv := "a" + pathListSep + "b" + pathListSep + "c"
85+
t.Setenv("PATH", pathFromEnv)
86+
87+
UpdatePath("b"+pathListSep+"c"+pathListSep+"d", false)
88+
89+
require.Equal(t, "a"+pathListSep+"b"+pathListSep+"c"+pathListSep+"d", os.Getenv("PATH"))
90+
})
91+
92+
t.Run("add multiple entries with duplicates of new entries (prepend)", func(t *testing.T) {
93+
pathFromEnv := "b" + pathListSep + "d"
94+
t.Setenv("PATH", pathFromEnv)
95+
96+
UpdatePath("a"+pathListSep+"b"+pathListSep+"c"+pathListSep+"b", true)
97+
98+
require.Equal(t, "a"+pathListSep+"b"+pathListSep+"c"+pathListSep+"d", os.Getenv("PATH"))
99+
})
100+
101+
t.Run("add multiple entries with duplicates of new entries (append)", func(t *testing.T) {
102+
pathFromEnv := "a" + pathListSep + "b" + pathListSep + "c"
103+
t.Setenv("PATH", pathFromEnv)
104+
105+
UpdatePath("b"+pathListSep+"c"+pathListSep+"d"+pathListSep+"c", false)
106+
107+
require.Equal(t, "a"+pathListSep+"b"+pathListSep+"c"+pathListSep+"d", os.Getenv("PATH"))
108+
})
109+
45110
t.Run("add to path from environment only if not blank", func(t *testing.T) {
46111
pathFromEnv := "a"
47112
t.Setenv("PATH", pathFromEnv)

pkg/utils/array.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,26 @@ func Merge(input1 []string, input2 []string) []string {
7474
return result
7575
}
7676

77+
// Dedupe removes duplicate entries from a given slice.
78+
// Returns a new, deduplicated slice.
79+
//
80+
// Example:
81+
//
82+
// mySlice := []string{"apple", "banana", "apple", "cherry", "banana", "date"}
83+
// dedupedSlice := dedupe(mySlice)
84+
// fmt.Println(dedupedSlice) // Output: [apple banana cherry date]
85+
func Dedupe(s []string) []string {
86+
seen := make(map[string]bool)
87+
var result []string
88+
for _, str := range s {
89+
if _, ok := seen[str]; !ok {
90+
seen[str] = true
91+
result = append(result, str)
92+
}
93+
}
94+
return result
95+
}
96+
7797
// ToKeyValueMap converts a list of strings to a map of strings.
7898
// The input list will be converted based on the delimiter, 'splitBy'.
7999
// The resulting map will contain the keys and values of the input list.

0 commit comments

Comments
 (0)