From ff55e1a1cde49e2fea08b8f70098b75efceccca3 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 1 Apr 2020 10:25:48 -0700 Subject: [PATCH] internal/providercache: installFromLocalDir We previously skipped this one because it wasn't strictly necessary for replicating the old "terraform init" behavior, but we do need it to work so that things like the -plugin-dir option can behave correctly. Linking packages from other cache directories and installing from unpacked directories are fundamentally the same operation because a cache directory is really just a collection of unpacked packages, so here we refactor the LinkFromOtherCache functionality to actually be in installFromLocalDir, and LinkFromOtherCache becomes a wrapper for the installFromLocalDir function that just calculates the source and target directories automatically and invalidates the metaCache. --- internal/providercache/dir_modify.go | 70 ++--------------------- internal/providercache/package_install.go | 67 +++++++++++++++++++++- 2 files changed, 70 insertions(+), 67 deletions(-) diff --git a/internal/providercache/dir_modify.go b/internal/providercache/dir_modify.go index de9206d95a..9441f93fe5 100644 --- a/internal/providercache/dir_modify.go +++ b/internal/providercache/dir_modify.go @@ -4,10 +4,7 @@ import ( "context" "fmt" "log" - "os" - "path/filepath" - "github.com/hashicorp/terraform/internal/copydir" "github.com/hashicorp/terraform/internal/getproviders" ) @@ -58,71 +55,12 @@ func (d *Dir) LinkFromOtherCache(entry *CachedProvider) error { 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 + // We re-use the process of installing from a local directory here, because + // the two operations are fundamentally the same: symlink if possible, + // deep-copy otherwise. + return installFromLocalDir(context.TODO(), currentPath, newPath) } diff --git a/internal/providercache/package_install.go b/internal/providercache/package_install.go index ade52127b0..b80b72e79f 100644 --- a/internal/providercache/package_install.go +++ b/internal/providercache/package_install.go @@ -5,10 +5,13 @@ import ( "fmt" "io/ioutil" "net/http" + "os" + "path/filepath" getter "github.com/hashicorp/go-getter" "github.com/hashicorp/terraform/httpclient" + "github.com/hashicorp/terraform/internal/copydir" ) // We borrow the "unpack a zip file into a target directory" logic from @@ -68,6 +71,68 @@ func installFromLocalArchive(ctx context.Context, filename string, targetDir str return unzip.Decompress(targetDir, filename, true) } +// installFromLocalDir is the implementation of both installing a package from +// a local directory source _and_ of linking a package from another cache +// in LinkFromOtherCache, because they both do fundamentally the same +// operation: symlink if possible, or deep-copy otherwise. func installFromLocalDir(ctx context.Context, sourceDir string, targetDir string) error { - return fmt.Errorf("installFromLocalDir not yet implemented") + absNew, err := filepath.Abs(targetDir) + if err != nil { + return fmt.Errorf("failed to make target path %s absolute: %s", targetDir, err) + } + absCurrent, err := filepath.Abs(sourceDir) + if err != nil { + return fmt.Errorf("failed to make source path %s absolute: %s", sourceDir, 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 install existing provider directory %s to itself", targetDir) + } else if err != nil { + return fmt.Errorf("failed to determine if %s and %s are the same: %s", sourceDir, targetDir, err) + } + + // Delete anything that's already present at this path first. + err = os.RemoveAll(targetDir) + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to remove existing %s before linking it to %s: %s", sourceDir, targetDir, 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 use an absolute path for the symlink to reduce the risk of it being + // broken by moving things around later, since the source directory is + // likely to be a shared directory independent on any particular target + // and thus we can't assume that they will move around together. + 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", targetDir, 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 }