Skip to content

Commit d9f7fff

Browse files
authored
fix(symlink): Improve symlinking (#648)
* fix(symlink): Improve symlinking Resolves #553 Use absolute paths for symlink creation to allow `--install <path_dir>` and `--bin <path_file>` to be used together.
1 parent f842e48 commit d9f7fff

File tree

3 files changed

+68
-24
lines changed

3 files changed

+68
-24
lines changed

lib/symlink.go

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,36 +11,56 @@ import (
1111
)
1212

1313
// CreateSymlink : create symlink or copy file to bin directory if windows
14-
func CreateSymlink(cwd string, dir string) error {
14+
func CreateSymlink(target string, link string) error {
1515
// If we are on windows the symlink is not working correctly.
1616
// Copy the desired terraform binary to the path environment.
1717
if runtime.GOOS == windows {
18-
r, err := os.Open(cwd)
18+
r, err := os.Open(target)
1919
if err != nil {
20-
return fmt.Errorf("Unable to open source binary: %q", cwd)
20+
return fmt.Errorf("Unable to open source binary %q: %v", target, err)
2121
}
2222
defer r.Close()
2323

24-
w, err := os.Create(dir)
24+
w, err := os.Create(link)
2525
if err != nil {
26-
return fmt.Errorf("Could not create target binary: %q", dir)
26+
return fmt.Errorf("Could not create target binary %q: %v", link, err)
2727
}
2828
defer func() {
2929
if c := w.Close(); err == nil {
3030
err = c
3131
}
3232
}()
33+
logger.Infof("Copying binary from %q to %q", target, link)
3334
_, err = io.Copy(w, r)
35+
if err != nil {
36+
return fmt.Errorf("Failed to copy binary from %q to %q: %v", target, link, err)
37+
}
3438
} else {
35-
err := os.Symlink(cwd, dir)
39+
// Get absolute path of target
40+
target, errTarget := GetAbsolutePath(target) //nolint:govet
41+
if errTarget != nil {
42+
return fmt.Errorf("Unable to get absolute path of %q: %v", target, errTarget)
43+
}
44+
45+
// Get absolute path of link
46+
link, errLink := GetAbsolutePath(link) //nolint:govet
47+
if errLink != nil {
48+
return fmt.Errorf("Unable to get absolute path of %q: %v", link, errLink)
49+
}
50+
51+
// Use absolute paths for symlink creation to allow
52+
// `--install <path_dir>` and `--bin <path_file>` to be used together
53+
// (don't mess with relative paths — it's complicated and error-prone)
54+
logger.Debugf("Symlinking %q to %q", link, target)
55+
err := os.Symlink(target, link)
3656
if err != nil {
3757
return fmt.Errorf(`
3858
Unable to create new symlink.
3959
Maybe symlink already exist. Try removing existing symlink manually.
4060
Try running "unlink %q" to remove existing symlink.
4161
If error persist, you may not have the permission to create a symlink at %q.
4262
Error: %v
43-
`, dir, dir, err)
63+
`, link, link, err)
4464
}
4565
}
4666
return nil
@@ -59,6 +79,7 @@ func RemoveSymlink(symlinkPath string) error {
5979
`, symlinkPath, symlinkPath, err)
6080
}
6181

82+
logger.Debugf("Removing existing symlink at %q", symlinkPath)
6283
if errRemove := os.Remove(symlinkPath); errRemove != nil {
6384
return fmt.Errorf(`
6485
Unable to remove symlink.
@@ -141,14 +162,15 @@ func ChangeProductSymlink(product Product, binVersionPath string, userBinPath st
141162
// If directory does not exist, check if we should create it, otherwise skip
142163
if !CheckDirExist(dirPath) {
143164
logger.Warnf("Installation directory %q doesn't exist!", dirPath)
144-
if location.create {
145-
logger.Infof("Creating %q directory", dirPath)
146-
err = os.MkdirAll(dirPath, 0o755)
147-
if err != nil {
148-
logger.Errorf("Unable to create %q directory: %v", dirPath, err)
149-
continue
150-
}
151-
} else {
165+
166+
if !location.create {
167+
continue
168+
}
169+
170+
logger.Infof("Creating %q directory", dirPath)
171+
err = os.MkdirAll(dirPath, 0o755)
172+
if err != nil {
173+
logger.Errorf("Unable to create %q directory: %v", dirPath, err)
152174
continue
153175
}
154176
} else if !CheckIsDir(dirPath) {
@@ -175,17 +197,23 @@ func ChangeProductSymlink(product Product, binVersionPath string, userBinPath st
175197
if runtime.GOOS != windows {
176198
isDirInPath := false
177199

178-
for _, envPathElement := range strings.Split(os.Getenv("PATH"), ":") {
200+
absDirPath, errAbs := GetAbsolutePath(dirPath)
201+
if errAbs != nil {
202+
return fmt.Errorf("Could not derive absolute path to %q: %v", dirPath, errAbs)
203+
}
204+
absDirPath = strings.TrimRight(absDirPath, "/")
205+
206+
for envPathElement := range strings.SplitSeq(os.Getenv("PATH"), ":") {
179207
expandedEnvPathElement := strings.TrimRight(strings.Replace(envPathElement, "~", homedir, 1), "/")
180208

181-
if expandedEnvPathElement == strings.TrimRight(dirPath, "/") {
209+
if expandedEnvPathElement == absDirPath {
182210
isDirInPath = true
183211
break
184212
}
185213
}
186214

187215
if !isDirInPath {
188-
logger.Warnf("Run `export PATH=\"$PATH:%s\"` to append %q to $PATH", dirPath, location.path)
216+
logger.Warnf("Run `export PATH=\"$PATH:%s\"` to append %q to $PATH", absDirPath, location.path)
189217
}
190218
}
191219

lib/symlink_test.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,8 @@ func TestCreateSymlink(t *testing.T) {
1818
testSymlinkSrc += ".exe"
1919
}
2020

21-
home, err := homedir.Dir()
22-
if err != nil {
23-
t.Errorf("Could not detect home directory.")
24-
}
25-
symlinkPathSrc := filepath.Join(home, testSymlinkSrc)
26-
symlinkPathDest := filepath.Join(home, testSymlinkDest)
21+
symlinkPathSrc := filepath.Join(t.TempDir(), testSymlinkSrc)
22+
symlinkPathDest := filepath.Join(t.TempDir(), testSymlinkDest)
2723

2824
// Create file for test as windows does not like no source
2925
create, err := os.Create(symlinkPathDest)
@@ -44,6 +40,7 @@ func TestCreateSymlink(t *testing.T) {
4440
}
4541
}
4642

43+
logger = InitLogger("DEBUG")
4744
lnCreateErr := CreateSymlink(symlinkPathDest, symlinkPathSrc)
4845
if lnCreateErr != nil {
4946
t.Errorf("Could not create symlink at %q to %q: %v", symlinkPathSrc, symlinkPathDest, lnCreateErr)

lib/utils.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,25 @@ func GetRelativePath(absPath string) (string, error) {
5757
return relPath, nil
5858
}
5959

60+
// GetAbsolutePath : get absolute path from path
61+
func GetAbsolutePath(path string) (string, error) {
62+
// Windows is tricky, so skip it
63+
if runtime.GOOS == windows {
64+
return path, nil
65+
}
66+
67+
if filepath.IsAbs(path) {
68+
return path, nil
69+
}
70+
71+
absPath, err := filepath.Abs(path)
72+
if err != nil {
73+
return path, fmt.Errorf("Unable to get absolute path of %q: %v", path, err)
74+
}
75+
76+
return absPath, nil
77+
}
78+
6079
// RemoveDuplicateStrings : deduplicate slice of strings
6180
func RemoveDuplicateStrings(slice []string) []string {
6281
seen := map[string]bool{}

0 commit comments

Comments
 (0)