mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-27 09:21:14 -06:00
67ca067910
When a system-wide shared plugin cache is configured, we'll want to make use of entries already in the shared cache when populating a local (configuration-specific) cache. This new method LinkFromOtherCache encapsulates the work of placing a link from one cache to another. If possible it will create a symlink, therefore retaining a key advantage of configuring a shared plugin cache, but otherwise we'll do a deep copy of the package directory from one cache to the other. Our old provider installer would always skip trying to create symlinks on Windows because Go standard library support for os.Symlink on Windows was inconsistent in older versions. However, os.Symlink can now create symlinks using a new API introduced in a Windows 10 update and cleanly fail if symlink creation is impossible, so it's safe for us to just try to create the symlink and react if that produces an error, just as we used to do on non-Windows systems when possibly creating symlinks on filesystems that cannot support them.
96 lines
3.6 KiB
Go
96 lines
3.6 KiB
Go
package providercache
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/hashicorp/terraform/internal/copydir"
|
|
"github.com/hashicorp/terraform/internal/getproviders"
|
|
)
|
|
|
|
// LinkFromOtherCache takes a CachedProvider value produced from another Dir
|
|
// and links it into the cache represented by the receiver Dir.
|
|
//
|
|
// This is used to implement tiered caching, where new providers are first
|
|
// populated into a system-wide shared cache and then linked from there into
|
|
// a configuration-specific local cache.
|
|
//
|
|
// It's invalid to link a CachedProvider from a particular Dir into that same
|
|
// Dir, because that would otherwise potentially replace a real package
|
|
// directory with a circular link back to itself.
|
|
func (d *Dir) LinkFromOtherCache(entry *CachedProvider) error {
|
|
newPath := getproviders.UnpackedDirectoryPathForPackage(
|
|
d.baseDir, entry.Provider, entry.Version, d.targetPlatform,
|
|
)
|
|
currentPath := entry.PackageDir
|
|
|
|
absNew, err := filepath.Abs(newPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to make new path %s absolute: %s", newPath, err)
|
|
}
|
|
absCurrent, err := filepath.Abs(currentPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to make existing cache path %s absolute: %s", currentPath, err)
|
|
}
|
|
|
|
// Before we do anything else, we'll do a quick check to make sure that
|
|
// these two paths are not pointing at the same physical directory on
|
|
// disk. This compares the files by their OS-level device and directory
|
|
// entry identifiers, not by their virtual filesystem paths.
|
|
if same, err := copydir.SameFile(absNew, absCurrent); same {
|
|
return fmt.Errorf("cannot link existing cache path %s to itself", newPath)
|
|
} else if err != nil {
|
|
return fmt.Errorf("failed to determine if %s and %s are the same: %s", currentPath, newPath, err)
|
|
}
|
|
|
|
// Invalidate our metaCache so that subsequent read calls will re-scan to
|
|
// incorporate any changes we make here.
|
|
d.metaCache = nil
|
|
|
|
// Delete anything that's already present at this path first.
|
|
err = os.RemoveAll(currentPath)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("failed to remove existing %s before linking it to %s: %s", currentPath, newPath, err)
|
|
}
|
|
|
|
// We'll prefer to create a symlink if possible, but we'll fall back to
|
|
// a recursive copy if symlink creation fails. It could fail for a number
|
|
// of reasons, including being on Windows 8 without administrator
|
|
// privileges or being on a legacy filesystem like FAT that has no way
|
|
// to represent a symlink. (Generalized symlink support for Windows was
|
|
// introduced in a Windows 10 minor update.)
|
|
//
|
|
// We'd prefer to use a relative path for the symlink to reduce the risk
|
|
// of it being broken by moving things around later, but we'll fall back
|
|
// on the absolute path we already calculated if that isn't possible
|
|
// (e.g. because the two paths are on different "volumes" on an OS with
|
|
// that concept, like Windows with drive letters and UNC host/share names.)
|
|
linkTarget, err := filepath.Rel(newPath, absCurrent)
|
|
if err != nil {
|
|
linkTarget = absCurrent
|
|
}
|
|
|
|
parentDir := filepath.Dir(absNew)
|
|
err = os.MkdirAll(parentDir, 0755)
|
|
if err != nil && os.IsExist(err) {
|
|
return fmt.Errorf("failed to create parent directories leading to %s: %s", newPath, err)
|
|
}
|
|
|
|
err = os.Symlink(linkTarget, absNew)
|
|
if err == nil {
|
|
// Success, then!
|
|
return nil
|
|
}
|
|
|
|
// If we get down here then symlinking failed and we need a deep copy
|
|
// instead.
|
|
err = copydir.CopyDir(absNew, absCurrent)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to either symlink or copy %s to %s: %s", absCurrent, absNew, err)
|
|
}
|
|
|
|
// If we got here then apparently our copy succeeded, so we're done.
|
|
return nil
|
|
}
|