Skip to content

Commit d2e581e

Browse files
authored
[fix] Windows junction handling (#557)
* Windows checks * Try old Go * Try again * Add debug * Windows * New winsymlink * Debug copyDir * Windows fallback * Try file getter * Try file getter * Add windows debuggery * Handle windows differently * Windows fixes? * Remove debuggery * Refactor to return early * Closing in * Tweak fixes * windows not working right * Fix logic around junctions * It was never working * More robust junction check * Robuster junction point check * Cleaner * Fallback more generously * Debug more * Be more like EvalSymlinks * Tighten up code * Further cleaning * Reasonable .gitignore * Fix resolving junction points on Windows * Add windows/unix unit tests * Fix linter error * Windows test failures * Another try at windows test failures * Probably not fix windows test failures
1 parent 928ae39 commit d2e581e

File tree

11 files changed

+1076
-21
lines changed

11 files changed

+1076
-21
lines changed

.gitignore

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,75 @@
1+
# Binaries for programs and plugins
2+
*.exe
3+
*.exe~
4+
*.dll
5+
*.so
6+
*.dylib
7+
8+
# Test binary, built with `go test -c`
9+
*.test
10+
11+
# Output of the go coverage tool, specifically when used with LiteIDE
12+
*.out
13+
14+
# Dependency directories (remove the comment below to include it)
15+
# vendor/
16+
17+
# Go workspace file
18+
go.work
19+
20+
# Build artifacts
21+
/bin/
22+
/build/
23+
/dist/
24+
25+
# Project-specific binaries
126
cmd/go-getter/go-getter
27+
go-getter
28+
29+
# IDE files
30+
.vscode/
31+
.idea/
32+
*.swp
33+
*.swo
34+
*~
35+
36+
# OS generated files
37+
.DS_Store
38+
.DS_Store?
39+
._*
40+
.Spotlight-V100
41+
.Trashes
42+
ehthumbs.db
43+
Thumbs.db
44+
45+
# Test coverage
46+
*.coverprofile
47+
coverage.html
48+
coverage.out
49+
50+
# Temporary files
51+
*.tmp
52+
*.temp
53+
54+
# Log files
55+
*.log
56+
57+
# Environment files
58+
.env
59+
.env.local
60+
.env.*.local
61+
62+
# Profiling data
63+
*.prof
64+
*.pprof
65+
66+
# Air live reload tool
67+
tmp/
68+
69+
# Archives
70+
*.tar
71+
*.tar.gz
72+
*.zip
73+
74+
# Local test fixtures (if any)
75+
/test-fixtures-local/

copy_dir.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ func mode(mode, umask os.FileMode) os.FileMode {
2323
func copyDir(ctx context.Context, dst string, src string, ignoreDot bool, disableSymlinks bool, umask os.FileMode) error {
2424
// We can safely evaluate the symlinks here, even if disabled, because they
2525
// will be checked before actual use in walkFn and copyFile
26-
var err error
27-
resolved, err := filepath.EvalSymlinks(src)
26+
resolved, err := resolveSymlinks(src)
2827
if err != nil {
2928
return err
3029
}

get_file_test.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,26 @@ func TestFileGetter(t *testing.T) {
2929
t.Fatalf("err: %s", err)
3030
}
3131

32-
// On Unix, verify it's a symlink; on Windows, just verify it works
33-
if runtime.GOOS != "windows" {
34-
fi, err := os.Lstat(dst)
35-
if err != nil {
36-
t.Fatalf("err: %s", err)
32+
// Verify it's a symlink on Unix or junction point on Windows
33+
fi, err := os.Lstat(dst)
34+
if err != nil {
35+
t.Fatalf("err: %s", err)
36+
}
37+
38+
if runtime.GOOS == "windows" {
39+
isJunction, junctionErr := isWindowsJunctionPoint(dst)
40+
if junctionErr != nil {
41+
t.Fatalf("failed to check if destination is a junction point: %s", junctionErr)
42+
}
43+
if !isJunction {
44+
t.Fatal("destination is not a junction point")
45+
}
46+
// Additional verification: should be accessible as a directory
47+
if dirInfo, err := os.Stat(dst); err != nil || !dirInfo.IsDir() {
48+
t.Fatal("destination junction point is not accessible as a directory")
3749
}
50+
} else {
51+
// On Unix, verify it's a traditional symlink
3852
if fi.Mode()&os.ModeSymlink == 0 {
3953
t.Fatal("destination is not a symlink")
4054
}

get_file_windows.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) HashiCorp, Inc.
22
// SPDX-License-Identifier: MPL-2.0
33

4+
//go:build windows
45
// +build windows
56

67
package getter

get_http.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -533,8 +533,7 @@ func (g *HttpGetter) getSubdir(ctx context.Context, dst, source, subDir string,
533533

534534
// Make sure the subdir path actually exists
535535
if _, err := os.Stat(sourcePath); err != nil {
536-
return fmt.Errorf(
537-
"Error downloading %s: %s", source, err)
536+
return fmt.Errorf("downloading %s: %s", source, err)
538537
}
539538

540539
// Copy the subdirectory into our actual destination.

get_http_test.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import (
1515
"net/url"
1616
"os"
1717
"path/filepath"
18-
"runtime"
1918
"strconv"
2019
"strings"
2120
"testing"
@@ -142,11 +141,6 @@ func TestHttpGetter_meta(t *testing.T) {
142141
}
143142

144143
func TestHttpGetter_metaSubdir(t *testing.T) {
145-
// Skip this test on Windows due to file:// URL subdirectory resolution issues
146-
if runtime.GOOS == "windows" {
147-
t.Skip("Skipping meta subdir test on Windows due to file:// URL path resolution issues")
148-
}
149-
150144
ln := testHttpServer(t)
151145
defer func() { _ = ln.Close() }()
152146

@@ -171,11 +165,6 @@ func TestHttpGetter_metaSubdir(t *testing.T) {
171165
}
172166

173167
func TestHttpGetter_metaSubdirGlob(t *testing.T) {
174-
// Skip this test on Windows due to file:// URL subdirectory resolution issues
175-
if runtime.GOOS == "windows" {
176-
t.Skip("Skipping meta subdir glob test on Windows due to file:// URL path resolution issues")
177-
}
178-
179168
ln := testHttpServer(t)
180169
defer func() { _ = ln.Close() }()
181170

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ require (
1717
github.com/mitchellh/go-homedir v1.1.0
1818
github.com/ulikunitz/xz v0.5.15
1919
golang.org/x/oauth2 v0.27.0
20+
golang.org/x/sys v0.35.0
2021
google.golang.org/api v0.114.0
2122
)
2223

@@ -46,7 +47,6 @@ require (
4647
github.com/mattn/go-runewidth v0.0.4 // indirect
4748
go.opencensus.io v0.24.0 // indirect
4849
golang.org/x/net v0.43.0 // indirect
49-
golang.org/x/sys v0.35.0 // indirect
5050
golang.org/x/text v0.28.0 // indirect
5151
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
5252
google.golang.org/appengine v1.6.7 // indirect

symlinks_unix.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//go:build !windows
2+
// +build !windows
3+
4+
// Copyright (c) HashiCorp, Inc.
5+
// SPDX-License-Identifier: MPL-2.0
6+
7+
package getter
8+
9+
import (
10+
"path/filepath"
11+
)
12+
13+
// resolveSymlinks resolves symlinks
14+
func resolveSymlinks(src string) (string, error) {
15+
resolved, err := filepath.EvalSymlinks(src)
16+
if err != nil {
17+
return "", err
18+
}
19+
return resolved, nil
20+
}
21+
22+
// isWindowsJunctionPoint is a no-op on non-Windows platforms
23+
func isWindowsJunctionPoint(_ string) (bool, error) {
24+
return false, nil
25+
}

0 commit comments

Comments
 (0)