mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-15 19:22:46 -06:00
537c1bedcf
This was incorrectly removing the _source_ entry prior to creating the symlink, therefore ending up with a dangling symlink and no source file. This wasn't obvious before because the test case for LinkFromOtherCache was also incorrectly named and therefore wasn't running. Fixing the name of that test made this problem apparent. The TestLinkFromOtherCache test case now ends up seeing the final resolved directory rather than the symlink target, because of upstream changes to the internal/getproviders filesystem scanning logic to handle symlinks properly.
125 lines
4.9 KiB
Go
125 lines
4.9 KiB
Go
package providercache
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/hashicorp/terraform/internal/copydir"
|
|
"github.com/hashicorp/terraform/internal/getproviders"
|
|
)
|
|
|
|
// InstallPackage takes a metadata object describing a package available for
|
|
// installation, retrieves that package, and installs it into the receiving
|
|
// cache directory.
|
|
func (d *Dir) InstallPackage(ctx context.Context, meta getproviders.PackageMeta) error {
|
|
if meta.TargetPlatform != d.targetPlatform {
|
|
return fmt.Errorf("can't install %s package into cache directory expecting %s", meta.TargetPlatform, d.targetPlatform)
|
|
}
|
|
newPath := getproviders.UnpackedDirectoryPathForPackage(
|
|
d.baseDir, meta.Provider, meta.Version, d.targetPlatform,
|
|
)
|
|
|
|
log.Printf("[TRACE] providercache.Dir.InstallPackage: installing %s v%s from %s", meta.Provider, meta.Version, meta.Location)
|
|
switch location := meta.Location.(type) {
|
|
case getproviders.PackageHTTPURL:
|
|
return installFromHTTPURL(ctx, string(location), newPath)
|
|
case getproviders.PackageLocalArchive:
|
|
return installFromLocalArchive(ctx, string(location), newPath)
|
|
case getproviders.PackageLocalDir:
|
|
return installFromLocalDir(ctx, string(location), newPath)
|
|
default:
|
|
// Should not get here, because the above should be exhaustive for
|
|
// all implementations of getproviders.Location.
|
|
return fmt.Errorf("don't know how to install from a %T location", location)
|
|
}
|
|
}
|
|
|
|
// 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
|
|
log.Printf("[TRACE] providercache.Dir.LinkFromOtherCache: linking %s v%s from existing cache %s to %s", entry.Provider, entry.Version, currentPath, newPath)
|
|
|
|
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(newPath)
|
|
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
|
|
}
|