opentofu/internal/providercache/cached_provider.go

110 lines
4.4 KiB
Go
Raw Normal View History

package providercache
import (
"fmt"
"path/filepath"
"strings"
"golang.org/x/mod/sumdb/dirhash"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/internal/getproviders"
)
// CachedProvider represents a provider package in a cache directory.
type CachedProvider struct {
// Provider and Version together identify the specific provider version
// this cache entry represents.
Provider addrs.Provider
Version getproviders.Version
// PackageDir is the local filesystem path to the root directory where
// the provider's distribution archive was unpacked.
//
// The path always uses slashes as path separators, even on Windows, so
// that the results are consistent between platforms. Windows accepts
// both slashes and backslashes as long as the separators are consistent
// within a particular path string.
PackageDir string
// ExecutableFile is the local filesystem path to the main plugin executable
// for the provider, which is always a file within the directory given
// in PackageDir.
//
// The path always uses slashes as path separators, even on Windows, so
// that the results are consistent between platforms. Windows accepts
// both slashes and backslashes as long as the separators are consistent
// within a particular path string.
ExecutableFile string
}
// Hash computes a hash of the contents of the package directory associated
// with the receiving cached provider, using whichever hash algorithm is
// the current default.
//
// Currently, this method returns version 1 hashes as produced by the
// method HashV1, but this function may switch to other versions in later
// releases. Call HashV1 directly if you specifically need a V1 hash.
func (cp *CachedProvider) Hash() (string, error) {
return cp.HashV1()
}
// MatchesHash returns true if the package on disk matches the given hash,
// or false otherwise. If it cannot traverse the package directory and read
// all of the files in it, or if the hash is in an unsupported format,
// CheckHash returns an error.
//
// There is currently only one hash format, as implemented by HashV1. However,
// if others are introduced in future MatchesHash may accept multiple formats,
// and may generate errors for any formats that become obsolete.
func (cp *CachedProvider) MatchesHash(want string) (bool, error) {
switch {
case strings.HasPrefix(want, "h1"):
got, err := cp.HashV1()
if err != nil {
return false, err
}
return got == want, nil
default:
return false, fmt.Errorf("unsupported hash format (this may require a newer version of Terraform)")
}
}
// HashV1 computes a hash of the contents of the package directory associated
// with the receiving cached provider using hash algorithm 1.
//
// The hash covers the paths to files in the directory and the contents of
// those files. It does not cover other metadata about the files, such as
// permissions.
//
// This function is named "HashV1" in anticipation of other hashing algorithms
// being added (in a backward-compatible way) in future. The result from
// HashV1 always begins with the prefix "h1:" so that callers can distinguish
// the results of potentially multiple different hash algorithms in future.
func (cp *CachedProvider) HashV1() (string, error) {
// Our HashV1 is really just the Go Modules hash version 1, which is
// sufficient for our needs and already well-used for identity of
// Go Modules distribution packages. It is also blocked from incompatible
// changes by being used in a wide array of go.sum files already.
//
// In particular, it also supports computing an equivalent hash from
// an unpacked zip file, which is not important for Terraform workflow
// today but is likely to become so in future if we adopt a top-level
// lockfile mechanism that is intended to be checked in to version control,
// rather than just a transient lock for a particular local cache directory.
// (In that case we'd need to check hashes of _packed_ packages, too.)
// We'll first dereference a possible symlink at our PackageDir location,
// as would be created if this package were linked in from another cache.
packageDir, err := filepath.EvalSymlinks(cp.PackageDir)
if err != nil {
return "", err
}
// Internally, dirhash.Hash1 produces a string containing a sequence of
// newline-separated path+filehash pairs for all of the files in the
// directory, and then finally produces a hash of that string to return.
// In both cases, the hash algorithm is SHA256.
return dirhash.HashDir(packageDir, "", dirhash.Hash1)
}