forked from hashicorp/go-getter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use junction points instead of symlinks on Windows
This resolves a problem observed on domain joined machines where the user has no Administrative priviledges, which is a prerequisite for creating a symbolic link in Windows. This breaks local Terraform modules on such machines. Instead for directories we shell to `mklink /J` which creates a junction point - this is inspired by the Go standard library test suite.
- Loading branch information
Showing
3 changed files
with
203 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,98 +1,8 @@ | ||
package getter | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"net/url" | ||
"os" | ||
"path/filepath" | ||
) | ||
|
||
// FileGetter is a Getter implementation that will download a module from | ||
// a file scheme. | ||
type FileGetter struct { | ||
// Copy, if set to true, will copy data instead of using a symlink | ||
Copy bool | ||
} | ||
|
||
func (g *FileGetter) Get(dst string, u *url.URL) error { | ||
// The source path must exist and be a directory to be usable. | ||
if fi, err := os.Stat(u.Path); err != nil { | ||
return fmt.Errorf("source path error: %s", err) | ||
} else if !fi.IsDir() { | ||
return fmt.Errorf("source path must be a directory") | ||
} | ||
|
||
fi, err := os.Lstat(dst) | ||
if err != nil && !os.IsNotExist(err) { | ||
return err | ||
} | ||
|
||
// If the destination already exists, it must be a symlink | ||
if err == nil { | ||
mode := fi.Mode() | ||
if mode&os.ModeSymlink == 0 { | ||
return fmt.Errorf("destination exists and is not a symlink") | ||
} | ||
|
||
// Remove the destination | ||
if err := os.Remove(dst); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
// Create all the parent directories | ||
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { | ||
return err | ||
} | ||
|
||
return os.Symlink(u.Path, dst) | ||
} | ||
|
||
func (g *FileGetter) GetFile(dst string, u *url.URL) error { | ||
// The source path must exist and be a directory to be usable. | ||
if fi, err := os.Stat(u.Path); err != nil { | ||
return fmt.Errorf("source path error: %s", err) | ||
} else if fi.IsDir() { | ||
return fmt.Errorf("source path must be a file") | ||
} | ||
|
||
_, err := os.Lstat(dst) | ||
if err != nil && !os.IsNotExist(err) { | ||
return err | ||
} | ||
|
||
// If the destination already exists, it must be a symlink | ||
if err == nil { | ||
// Remove the destination | ||
if err := os.Remove(dst); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
// Create all the parent directories | ||
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { | ||
return err | ||
} | ||
|
||
// If we're not copying, just symlink and we're done | ||
if !g.Copy { | ||
return os.Symlink(u.Path, dst) | ||
} | ||
|
||
// Copy | ||
srcF, err := os.Open(u.Path) | ||
if err != nil { | ||
return err | ||
} | ||
defer srcF.Close() | ||
|
||
dstF, err := os.Create(dst) | ||
if err != nil { | ||
return err | ||
} | ||
defer dstF.Close() | ||
|
||
_, err = io.Copy(dstF, srcF) | ||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// +build !windows | ||
|
||
package getter | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"net/url" | ||
"os" | ||
"path/filepath" | ||
) | ||
|
||
func (g *FileGetter) Get(dst string, u *url.URL) error { | ||
// The source path must exist and be a directory to be usable. | ||
if fi, err := os.Stat(u.Path); err != nil { | ||
return fmt.Errorf("source path error: %s", err) | ||
} else if !fi.IsDir() { | ||
return fmt.Errorf("source path must be a directory") | ||
} | ||
|
||
fi, err := os.Lstat(dst) | ||
if err != nil && !os.IsNotExist(err) { | ||
return err | ||
} | ||
|
||
// If the destination already exists, it must be a symlink | ||
if err == nil { | ||
mode := fi.Mode() | ||
if mode&os.ModeSymlink == 0 { | ||
return fmt.Errorf("destination exists and is not a symlink") | ||
} | ||
|
||
// Remove the destination | ||
if err := os.Remove(dst); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
// Create all the parent directories | ||
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { | ||
return err | ||
} | ||
|
||
return os.Symlink(u.Path, dst) | ||
} | ||
|
||
func (g *FileGetter) GetFile(dst string, u *url.URL) error { | ||
// The source path must exist and be a directory to be usable. | ||
if fi, err := os.Stat(u.Path); err != nil { | ||
return fmt.Errorf("source path error: %s", err) | ||
} else if fi.IsDir() { | ||
return fmt.Errorf("source path must be a file") | ||
} | ||
|
||
_, err := os.Lstat(dst) | ||
if err != nil && !os.IsNotExist(err) { | ||
return err | ||
} | ||
|
||
// If the destination already exists, it must be a symlink | ||
if err == nil { | ||
// Remove the destination | ||
if err := os.Remove(dst); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
// Create all the parent directories | ||
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { | ||
return err | ||
} | ||
|
||
// If we're not copying, just symlink and we're done | ||
if !g.Copy { | ||
return os.Symlink(u.Path, dst) | ||
} | ||
|
||
// Copy | ||
srcF, err := os.Open(u.Path) | ||
if err != nil { | ||
return err | ||
} | ||
defer srcF.Close() | ||
|
||
dstF, err := os.Create(dst) | ||
if err != nil { | ||
return err | ||
} | ||
defer dstF.Close() | ||
|
||
_, err = io.Copy(dstF, srcF) | ||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// +build windows | ||
|
||
package getter | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"net/url" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
func (g *FileGetter) Get(dst string, u *url.URL) error { | ||
// The source path must exist and be a directory to be usable. | ||
if fi, err := os.Stat(u.Path); err != nil { | ||
return fmt.Errorf("source path error: %s", err) | ||
} else if !fi.IsDir() { | ||
return fmt.Errorf("source path must be a directory") | ||
} | ||
|
||
fi, err := os.Lstat(dst) | ||
if err != nil && !os.IsNotExist(err) { | ||
return err | ||
} | ||
|
||
// If the destination already exists, it must be a symlink | ||
if err == nil { | ||
mode := fi.Mode() | ||
if mode&os.ModeSymlink == 0 { | ||
return fmt.Errorf("destination exists and is not a symlink") | ||
} | ||
|
||
// Remove the destination | ||
if err := os.Remove(dst); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
// Create all the parent directories | ||
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { | ||
return err | ||
} | ||
|
||
sourcePath := toBackslash(u.Path) | ||
|
||
// Use mklink to create a junction point | ||
output, err := exec.Command("cmd", "/c", "mklink", "/J", dst, sourcePath).CombinedOutput() | ||
if err != nil { | ||
return fmt.Errorf("failed to run mklink %v %v: %v %q", dst, sourcePath, err, output) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (g *FileGetter) GetFile(dst string, u *url.URL) error { | ||
// The source path must exist and be a directory to be usable. | ||
if fi, err := os.Stat(u.Path); err != nil { | ||
return fmt.Errorf("source path error: %s", err) | ||
} else if fi.IsDir() { | ||
return fmt.Errorf("source path must be a file") | ||
} | ||
|
||
_, err := os.Lstat(dst) | ||
if err != nil && !os.IsNotExist(err) { | ||
return err | ||
} | ||
|
||
// If the destination already exists, it must be a symlink | ||
if err == nil { | ||
// Remove the destination | ||
if err := os.Remove(dst); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
// Create all the parent directories | ||
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { | ||
return err | ||
} | ||
|
||
// If we're not copying, just symlink and we're done | ||
if !g.Copy { | ||
return os.Symlink(u.Path, dst) | ||
} | ||
|
||
// Copy | ||
srcF, err := os.Open(u.Path) | ||
if err != nil { | ||
return err | ||
} | ||
defer srcF.Close() | ||
|
||
dstF, err := os.Create(dst) | ||
if err != nil { | ||
return err | ||
} | ||
defer dstF.Close() | ||
|
||
_, err = io.Copy(dstF, srcF) | ||
return err | ||
} | ||
|
||
// toBackslash returns the result of replacing each slash character | ||
// in path with a backslash ('\') character. Multiple separators are | ||
// replaced by multiple backslashes. | ||
func toBackslash(path string) string { | ||
return strings.Replace(path, "/", "\\", -1) | ||
} |