getproviders: Add a real type Hash for package hashes

The logic for what constitutes a valid hash and how different hash schemes
are represented was starting to get sprawled over many different files and
packages.

Consistently with other cases where we've used named types to gather the
definition of a particular string into a single place and have the Go
compiler help us use it properly, this introduces both getproviders.Hash
representing a hash value and getproviders.HashScheme representing the
idea of a particular hash scheme.

Most of this changeset is updating existing uses of primitive strings to
uses of getproviders.Hash. The new type definitions are in
internal/getproviders/hash.go.
This commit is contained in:
Martin Atkins 2020-09-23 14:27:09 -07:00
parent 264a3cf031
commit 6694cfaa0e
15 changed files with 306 additions and 73 deletions

View File

@ -269,8 +269,8 @@ func (c *ProvidersMirrorCommand) Run(args []string) int {
indexArchives[version] = map[string]interface{}{}
}
indexArchives[version][platform.String()] = map[string]interface{}{
"url": archiveFilename, // a relative URL from the index file's URL
"hashes": []string{hash}, // an array to allow for additional hash formats in future
"url": archiveFilename, // a relative URL from the index file's URL
"hashes": []string{hash.String()}, // an array to allow for additional hash formats in future
}
}
mainIndex := map[string]interface{}{

View File

@ -2,7 +2,6 @@ package depsfile
import (
"fmt"
"sort"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/internal/getproviders"
@ -58,14 +57,11 @@ func (l *Locks) Provider(addr addrs.Provider) *ProviderLock {
// non-lockable provider address then this function will panic. Use
// function ProviderIsLockable to determine whether a particular provider
// should participate in the version locking mechanism.
func (l *Locks) SetProvider(addr addrs.Provider, version getproviders.Version, constraints getproviders.VersionConstraints, hashes []string) *ProviderLock {
func (l *Locks) SetProvider(addr addrs.Provider, version getproviders.Version, constraints getproviders.VersionConstraints, hashes []getproviders.Hash) *ProviderLock {
if !ProviderIsLockable(addr) {
panic(fmt.Sprintf("Locks.SetProvider with non-lockable provider %s", addr))
}
// Normalize the hash lists into a consistent order.
sort.Strings(hashes)
new := &ProviderLock{
addr: addr,
version: version,
@ -137,7 +133,7 @@ type ProviderLock struct {
// means we can only populate the hash for the current platform, and so
// it won't be possible to verify a subsequent installation of the same
// provider on a different platform.
hashes []string
hashes []getproviders.Hash
}
// Provider returns the address of the provider this lock applies to.
@ -172,7 +168,7 @@ func (l *ProviderLock) VersionConstraints() getproviders.VersionConstraints {
// of which must match in order for verification to be considered successful.
//
// Do not modify the backing array of the returned slice.
func (l *ProviderLock) AllHashes() []string {
func (l *ProviderLock) AllHashes() []getproviders.Hash {
return l.hashes
}
@ -183,6 +179,6 @@ func (l *ProviderLock) AllHashes() []string {
//
// At least one of the given hashes must match for a package to be considered
// valud.
func (l *ProviderLock) PreferredHashes() []string {
func (l *ProviderLock) PreferredHashes() []getproviders.Hash {
return getproviders.PreferredHashes(l.hashes)
}

View File

@ -102,8 +102,8 @@ func SaveLocksToFile(locks *Locks, filename string) tfdiags.Diagnostics {
}
if len(lock.hashes) != 0 {
hashVals := make([]cty.Value, 0, len(lock.hashes))
for _, str := range lock.hashes {
hashVals = append(hashVals, cty.StringVal(str))
for _, hash := range lock.hashes {
hashVals = append(hashVals, cty.StringVal(hash.String()))
}
// We're using a set rather than a list here because the order
// isn't significant and SetAttributeValue will automatically
@ -369,7 +369,7 @@ func decodeProviderVersionConstraintsArgument(provider addrs.Provider, attr *hcl
return constraints, diags
}
func decodeProviderHashesArgument(provider addrs.Provider, attr *hcl.Attribute) ([]string, tfdiags.Diagnostics) {
func decodeProviderHashesArgument(provider addrs.Provider, attr *hcl.Attribute) ([]getproviders.Hash, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
if attr == nil {
// It's okay to omit this argument.
@ -396,7 +396,7 @@ func decodeProviderHashesArgument(provider addrs.Provider, attr *hcl.Attribute)
return nil, diags
}
ret := make([]string, 0, len(hashExprs))
ret := make([]getproviders.Hash, 0, len(hashExprs))
for _, hashExpr := range hashExprs {
var raw string
hclDiags := gohcl.DecodeExpression(hashExpr, nil, &raw)
@ -404,10 +404,19 @@ func decodeProviderHashesArgument(provider addrs.Provider, attr *hcl.Attribute)
if hclDiags.HasErrors() {
continue
}
// TODO: Validate the hash syntax, but not the actual hash schemes
// because we expect to support different hash formats over time and
// will silently ignore ones that we no longer prefer.
ret = append(ret, raw)
hash, err := getproviders.ParseHash(raw)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider hash string",
Detail: fmt.Sprintf("Cannot interpret %q as a provider hash: %s.", raw, err),
Subject: expr.Range().Ptr(),
})
continue
}
ret = append(ret, hash)
}
return ret, diags

View File

@ -144,10 +144,10 @@ func TestLoadLocksFromFile(t *testing.T) {
if got, want := getproviders.VersionConstraintsString(lock.VersionConstraints()), ">= 3.0.2"; got != want {
t.Errorf("wrong version constraints\ngot: %s\nwant: %s", got, want)
}
wantHashes := []string{
"test:placeholder-hash-1",
"test:placeholder-hash-2",
"test:placeholder-hash-3",
wantHashes := []getproviders.Hash{
getproviders.MustParseHash("test:placeholder-hash-1"),
getproviders.MustParseHash("test:placeholder-hash-2"),
getproviders.MustParseHash("test:placeholder-hash-3"),
}
if diff := cmp.Diff(wantHashes, lock.hashes); diff != "" {
t.Errorf("wrong hashes\n%s", diff)
@ -169,10 +169,10 @@ func TestSaveLocksToFile(t *testing.T) {
oneDotTwo := getproviders.MustParseVersion("1.2.0")
atLeastOneDotOh := getproviders.MustParseVersionConstraints(">= 1.0.0")
pessimisticOneDotOh := getproviders.MustParseVersionConstraints("~> 1")
hashes := []string{
"test:cccccccccccccccccccccccccccccccccccccccccccccccc",
"test:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"test:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
hashes := []getproviders.Hash{
getproviders.MustParseHash("test:cccccccccccccccccccccccccccccccccccccccccccccccc"),
getproviders.MustParseHash("test:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
getproviders.MustParseHash("test:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
}
locks.SetProvider(fooProvider, oneDotOh, atLeastOneDotOh, hashes)
locks.SetProvider(barProvider, oneDotTwo, pessimisticOneDotOh, nil)

View File

@ -11,8 +11,142 @@ import (
"golang.org/x/mod/sumdb/dirhash"
)
const h1Prefix = "h1:"
const zipHashPrefix = "zh:"
// Hash is a specially-formatted string representing a checksum of a package
// or the contents of the package.
//
// A Hash string is always starts with a scheme, which is a short series of
// alphanumeric characters followed by a colon, and then the remainder of the
// string has a different meaning depending on the scheme prefix.
//
// The currently-valid schemes are defined as the constants of type HashScheme
// in this package.
//
// Callers outside of this package must not create Hash values via direct
// conversion. Instead, use either the HashScheme.New method on one of the
// HashScheme contents (for a hash of a particular scheme) or the ParseHash
// function (if hashes of any scheme are acceptable).
type Hash string
// NilHash is the zero value of Hash. It isn't a valid hash, so all of its
// methods will panic.
const NilHash = Hash("")
// ParseHash parses the string representation of a Hash into a Hash value.
//
// A particular version of Terraform only supports a fixed set of hash schemes,
// but this function intentionally allows unrecognized schemes so that we can
// silently ignore other schemes that may be introduced in the future. For
// that reason, the Scheme method of the returned Hash may return a value that
// isn't in one of the HashScheme constants in this package.
//
// This function doesn't verify that the value portion of the given hash makes
// sense for the given scheme. Invalid values are just considered to not match
// any packages.
//
// If this function returns an error then the returned Hash is invalid and
// must not be used.
func ParseHash(s string) (Hash, error) {
colon := strings.Index(s, ":")
if colon < 1 { // 1 because a zero-length scheme is not allowed
return NilHash, fmt.Errorf("hash string must start with a scheme keyword followed by a colon")
}
return Hash(s), nil
}
// MustParseHash is a wrapper around ParseHash that panics if it returns an
// error.
func MustParseHash(s string) Hash {
hash, err := ParseHash(s)
if err != nil {
panic(err.Error())
}
return hash
}
// Scheme returns the scheme of the recieving hash. If the receiver is not
// using valid syntax then this method will panic.
func (h Hash) Scheme() HashScheme {
colon := strings.Index(string(h), ":")
if colon < 0 {
panic(fmt.Sprintf("invalid hash string %q", h))
}
return HashScheme(h[:colon+1])
}
// HasScheme returns true if the given scheme matches the receiver's scheme,
// or false otherwise.
//
// If the receiver is not using valid syntax then this method will panic.
func (h Hash) HasScheme(want HashScheme) bool {
return h.Scheme() == want
}
// Value returns the scheme-specific value from the recieving hash. The
// meaning of this value depends on the scheme.
//
// If the receiver is not using valid syntax then this method will panic.
func (h Hash) Value() string {
colon := strings.Index(string(h), ":")
if colon < 0 {
panic(fmt.Sprintf("invalid hash string %q", h))
}
return string(h[colon+1:])
}
// String returns a string representation of the receiving hash.
func (h Hash) String() string {
return string(h)
}
// GoString returns a Go syntax representation of the receiving hash.
//
// This is here primarily to help with producing descriptive test failure
// output; these results are not particularly useful at runtime.
func (h Hash) GoString() string {
if h == NilHash {
return "getproviders.NilHash"
}
switch scheme := h.Scheme(); scheme {
case HashScheme1:
return fmt.Sprintf("getproviders.HashScheme1.New(%q)", h.Value())
case HashSchemeZip:
return fmt.Sprintf("getproviders.HashSchemeZip.New(%q)", h.Value())
default:
// This fallback is for when we encounter lock files or API responses
// with hash schemes that the current version of Terraform isn't
// familiar with. They were presumably introduced in a later version.
return fmt.Sprintf("getproviders.HashScheme(%q).New(%q)", scheme, h.Value())
}
}
// HashScheme is an enumeration of schemes that are allowed for values of type
// Hash.
type HashScheme string
const (
// HashScheme1 is the scheme identifier for the first hash scheme.
//
// Use HashV1 (or one of its wrapper functions) to calculate hashes with
// this scheme.
HashScheme1 HashScheme = HashScheme("h1:")
// HashSchemeZip is the scheme identifier for the legacy hash scheme that
// applies to distribution archives (.zip files) rather than package
// contents, and can therefore only be verified against the original
// distribution .zip file, not an extracted directory.
//
// Use PackageHashLegacyZipSHA to calculate hashes with this scheme.
HashSchemeZip HashScheme = HashScheme("zh:")
)
// New creates a new Hash value with the receiver as its scheme and the given
// raw string as its value.
//
// It's the caller's responsibility to make sure that the given value makes
// sense for the selected scheme.
func (hs HashScheme) New(value string) Hash {
return Hash(string(hs) + value)
}
// PackageHash computes a hash of the contents of the package at the given
// location, using whichever hash algorithm is the current default.
@ -26,7 +160,7 @@ const zipHashPrefix = "zh:"
// PackageLocalDir and PackageLocalArchive, because it needs to access the
// contents of the indicated package in order to compute the hash. If given
// a non-local location this function will always return an error.
func PackageHash(loc PackageLocation) (string, error) {
func PackageHash(loc PackageLocation) (Hash, error) {
return PackageHashV1(loc)
}
@ -44,9 +178,9 @@ func PackageHash(loc PackageLocation) (string, error) {
// PackageLocalDir and PackageLocalArchive, because it needs to access the
// contents of the indicated package in order to compute the hash. If given
// a non-local location this function will always return an error.
func PackageMatchesHash(loc PackageLocation, want string) (bool, error) {
switch {
case strings.HasPrefix(want, h1Prefix):
func PackageMatchesHash(loc PackageLocation, want Hash) (bool, error) {
switch want.Scheme() {
case HashScheme1:
got, err := PackageHashV1(loc)
if err != nil {
return false, err
@ -65,11 +199,11 @@ func PackageMatchesHash(loc PackageLocation, want string) (bool, error) {
// format. If PreferredHash returns a non-empty string then it will be one
// of the hash strings in "given", and that hash is the one that must pass
// verification in order for a package to be considered valid.
func PreferredHashes(given []string) []string {
var ret []string
for _, s := range given {
if strings.HasPrefix(s, h1Prefix) {
return append(ret, s)
func PreferredHashes(given []Hash) []Hash {
var ret []Hash
for _, hash := range given {
if hash.Scheme() == HashScheme1 {
return append(ret, hash)
}
}
return ret
@ -87,7 +221,7 @@ func PreferredHashes(given []string) []string {
//
// Because this hashing scheme uses the official provider .zip file as its
// input, it accepts only PackageLocalArchive locations.
func PackageHashLegacyZipSHA(loc PackageLocalArchive) (string, error) {
func PackageHashLegacyZipSHA(loc PackageLocalArchive) (Hash, error) {
archivePath, err := filepath.EvalSymlinks(string(loc))
if err != nil {
return "", err
@ -106,7 +240,7 @@ func PackageHashLegacyZipSHA(loc PackageLocalArchive) (string, error) {
}
gotHash := h.Sum(nil)
return fmt.Sprintf("%s%x", zipHashPrefix, gotHash), nil
return HashSchemeZip.New(fmt.Sprintf("%x", gotHash)), nil
}
// HashLegacyZipSHAFromSHA is a convenience method to produce the schemed-string
@ -114,12 +248,13 @@ func PackageHashLegacyZipSHA(loc PackageLocalArchive) (string, error) {
//
// This just adds the "zh:" prefix and encodes the string in hex, so that the
// result is in the same format as PackageHashLegacyZipSHA.
func HashLegacyZipSHAFromSHA(sum [sha256.Size]byte) string {
return fmt.Sprintf("%s%x", zipHashPrefix, sum[:])
func HashLegacyZipSHAFromSHA(sum [sha256.Size]byte) Hash {
return HashSchemeZip.New(fmt.Sprintf("%x", sum[:]))
}
// PackageHashV1 computes a hash of the contents of the package at the given
// location using hash algorithm 1.
// location using hash algorithm 1. The resulting Hash is guaranteed to have
// the scheme HashScheme1.
//
// 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
@ -135,7 +270,7 @@ func HashLegacyZipSHAFromSHA(sum [sha256.Size]byte) string {
// PackageLocalDir and PackageLocalArchive, because it needs to access the
// contents of the indicated package in order to compute the hash. If given
// a non-local location this function will always return an error.
func PackageHashV1(loc PackageLocation) (string, error) {
func PackageHashV1(loc PackageLocation) (Hash, 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
@ -163,7 +298,10 @@ func PackageHashV1(loc PackageLocation) (string, error) {
return "", err
}
return dirhash.HashDir(packageDir, "", dirhash.Hash1)
// The dirhash.HashDir result is already in our expected h1:...
// format, so we can just convert directly to Hash.
s, err := dirhash.HashDir(packageDir, "", dirhash.Hash1)
return Hash(s), err
case PackageLocalArchive:
archivePath, err := filepath.EvalSymlinks(string(loc))
@ -171,7 +309,10 @@ func PackageHashV1(loc PackageLocation) (string, error) {
return "", err
}
return dirhash.HashZip(archivePath, dirhash.Hash1)
// The dirhash.HashDir result is already in our expected h1:...
// format, so we can just convert directly to Hash.
s, err := dirhash.HashZip(archivePath, dirhash.Hash1)
return Hash(s), err
default:
return "", fmt.Errorf("cannot hash package at %s", loc.String())
@ -190,7 +331,7 @@ func PackageHashV1(loc PackageLocation) (string, error) {
// PackageLocalDir and PackageLocalArchive, because it needs to access the
// contents of the indicated package in order to compute the hash. If given
// a non-local location this function will always return an error.
func (m PackageMeta) Hash() (string, error) {
func (m PackageMeta) Hash() (Hash, error) {
return PackageHash(m.Location)
}
@ -204,7 +345,7 @@ func (m PackageMeta) Hash() (string, error) {
// PackageLocalDir and PackageLocalArchive, because it needs to access the
// contents of the indicated package in order to compute the hash. If given
// a non-local location this function will always return an error.
func (m PackageMeta) MatchesHash(want string) (bool, error) {
func (m PackageMeta) MatchesHash(want Hash) (bool, error) {
return PackageMatchesHash(m.Location, want)
}
@ -219,6 +360,6 @@ func (m PackageMeta) MatchesHash(want string) (bool, error) {
// PackageLocalDir and PackageLocalArchive, because it needs to access the
// contents of the indicated package in order to compute the hash. If given
// a non-local location this function will always return an error.
func (m PackageMeta) HashV1() (string, error) {
func (m PackageMeta) HashV1() (Hash, error) {
return PackageHashV1(m.Location)
}

View File

@ -0,0 +1,70 @@
package getproviders
import (
"testing"
)
func TestParseHash(t *testing.T) {
tests := []struct {
Input string
Want Hash
WantErr string
}{
{
Input: "h1:foo",
Want: HashScheme1.New("foo"),
},
{
Input: "zh:bar",
Want: HashSchemeZip.New("bar"),
},
{
// A scheme we don't know is considered valid syntax, it just won't match anything.
Input: "unknown:baz",
Want: HashScheme("unknown:").New("baz"),
},
{
// A scheme with an empty value is weird, but allowed.
Input: "unknown:",
Want: HashScheme("unknown:").New(""),
},
{
Input: "",
WantErr: "hash string must start with a scheme keyword followed by a colon",
},
{
// A naked SHA256 hash in hex format is not sufficient
Input: "1e5f7a5f3ade7b8b1d1d59c5cea2e1a2f8d2f8c3f41962dbbe8647e222be8239",
WantErr: "hash string must start with a scheme keyword followed by a colon",
},
{
// An empty scheme is not allowed
Input: ":blah",
WantErr: "hash string must start with a scheme keyword followed by a colon",
},
}
for _, test := range tests {
t.Run(test.Input, func(t *testing.T) {
got, err := ParseHash(test.Input)
if test.WantErr != "" {
if err == nil {
t.Fatalf("want error: %s", test.WantErr)
}
if got, want := err.Error(), test.WantErr; got != want {
t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %s", err.Error())
}
if got != test.Want {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}

View File

@ -225,7 +225,18 @@ func (s *HTTPMirrorSource) PackageMeta(provider addrs.Provider, version Version,
// A network mirror might not provide any hashes at all, in which case
// the package has no source-defined authentication whatsoever.
if len(archiveMeta.Hashes) > 0 {
ret.Authentication = NewPackageHashAuthentication(target, archiveMeta.Hashes)
hashes := make([]Hash, 0, len(archiveMeta.Hashes))
for _, hashStr := range archiveMeta.Hashes {
hash, err := ParseHash(hashStr)
if err != nil {
return PackageMeta{}, s.errQueryFailed(
provider,
fmt.Errorf("provider mirror returned invalid provider hash %q: %s", hashStr, err),
)
}
hashes = append(hashes, hash)
}
ret.Authentication = NewPackageHashAuthentication(target, hashes)
}
return ret, nil

View File

@ -124,7 +124,7 @@ func TestHTTPMirrorSource(t *testing.T) {
Location: PackageHTTPURL(httpServer.URL + "/terraform.io/test/exists/terraform-provider-test_v1.0.0_tos_m68k.zip"),
Authentication: packageHashAuthentication{
RequiredHash: "h1:placeholder-hash",
ValidHashes: []string{"h1:placeholder-hash", "h0:unacceptable-hash"},
ValidHashes: []Hash{"h1:placeholder-hash", "h0:unacceptable-hash"},
Platform: Platform{"tos", "m68k"},
},
}
@ -133,7 +133,7 @@ func TestHTTPMirrorSource(t *testing.T) {
}
gotHashes := got.AcceptableHashes()
wantHashes := map[Platform][]string{
wantHashes := map[Platform][]Hash{
tosPlatform: {"h1:placeholder-hash", "h0:unacceptable-hash"},
}
if diff := cmp.Diff(wantHashes, gotHashes); diff != "" {

View File

@ -147,7 +147,7 @@ type PackageAuthenticationHashes interface {
// Authenticators that don't use hashes as their authentication procedure
// will either not implement this interface or will have an implementation
// that returns an empty result.
AcceptableHashes() map[Platform][]string
AcceptableHashes() map[Platform][]Hash
}
type packageAuthenticationAll []PackageAuthentication
@ -183,7 +183,7 @@ func (checks packageAuthenticationAll) AuthenticatePackage(localLocation Package
return authResult, nil
}
func (checks packageAuthenticationAll) AcceptableHashes() map[Platform][]string {
func (checks packageAuthenticationAll) AcceptableHashes() map[Platform][]Hash {
// The elements of checks are expected to be ordered so that the strongest
// one is later in the list, so we'll visit them in reverse order and
// take the first one that implements the interface and returns a non-empty
@ -202,8 +202,8 @@ func (checks packageAuthenticationAll) AcceptableHashes() map[Platform][]string
}
type packageHashAuthentication struct {
RequiredHash string
ValidHashes []string
RequiredHash Hash
ValidHashes []Hash
Platform Platform
}
@ -215,10 +215,10 @@ type packageHashAuthentication struct {
// The PreferredHash function will select which of the given hashes is
// considered by Terraform to be the strongest verification, and authentication
// succeeds as long as that chosen hash matches.
func NewPackageHashAuthentication(platform Platform, validHashes []string) PackageAuthentication {
func NewPackageHashAuthentication(platform Platform, validHashes []Hash) PackageAuthentication {
requiredHashes := PreferredHashes(validHashes)
// TODO: Update to support multiple hashes
var requiredHash string
var requiredHash Hash
if len(requiredHashes) > 0 {
requiredHash = requiredHashes[0]
}
@ -248,8 +248,8 @@ func (a packageHashAuthentication) AuthenticatePackage(localLocation PackageLoca
return nil, fmt.Errorf("provider package doesn't match the expected checksum %q", a.RequiredHash)
}
func (a packageHashAuthentication) AcceptableHashes() map[Platform][]string {
return map[Platform][]string{
func (a packageHashAuthentication) AcceptableHashes() map[Platform][]Hash {
return map[Platform][]Hash{
a.Platform: a.ValidHashes,
}
}
@ -295,8 +295,8 @@ func (a archiveHashAuthentication) AuthenticatePackage(localLocation PackageLoca
return &PackageAuthenticationResult{result: verifiedChecksum}, nil
}
func (a archiveHashAuthentication) AcceptableHashes() map[Platform][]string {
return map[Platform][]string{
func (a archiveHashAuthentication) AcceptableHashes() map[Platform][]Hash {
return map[Platform][]Hash{
a.Platform: {HashLegacyZipSHAFromSHA(a.WantSHA256Sum)},
}
}

View File

@ -104,9 +104,9 @@ func TestPackageHashAuthentication_success(t *testing.T) {
// Location must be a PackageLocalArchive path
location := PackageLocalDir("testdata/filesystem-mirror/registry.terraform.io/hashicorp/null/2.0.0/linux_amd64")
wantHashes := []string{
wantHashes := []Hash{
// Known-good HashV1 result for this directory
"h1:qjsREM4DqEWECD43FcPqddZ9oxCG+IaMTxvWPciS05g=",
Hash("h1:qjsREM4DqEWECD43FcPqddZ9oxCG+IaMTxvWPciS05g="),
}
auth := NewPackageHashAuthentication(Platform{"linux", "amd64"}, wantHashes)
@ -145,7 +145,7 @@ func TestPackageHashAuthentication_failure(t *testing.T) {
t.Run(name, func(t *testing.T) {
// Invalid expected hash, either because we'll error before we
// reach it, or we want to force a checksum mismatch.
auth := NewPackageHashAuthentication(Platform{"linux", "amd64"}, []string{"h1:invalid"})
auth := NewPackageHashAuthentication(Platform{"linux", "amd64"}, []Hash{"h1:invalid"})
result, err := auth.AuthenticatePackage(test.location)
if result != nil {

View File

@ -115,7 +115,7 @@ func TestSourcePackageMeta(t *testing.T) {
version string
os, arch string
want PackageMeta
wantHashes map[Platform][]string
wantHashes map[Platform][]Hash
wantErr string
}{
// These test cases are relying on behaviors of the fake provider
@ -149,7 +149,7 @@ func TestSourcePackageMeta(t *testing.T) {
),
),
},
map[Platform][]string{
map[Platform][]Hash{
{"linux", "amd64"}: {
"zh:000000000000000000000000000000000000000000000000000000000000f00d",
},

View File

@ -259,7 +259,7 @@ func (m PackageMeta) PackedFilePath(baseDir string) string {
// Authentication field. AcceptableHashes therefore returns an empty result
// for a PackageMeta that has no authentication object, or has one that does
// not make use of hashes.
func (m PackageMeta) AcceptableHashes() map[Platform][]string {
func (m PackageMeta) AcceptableHashes() map[Platform][]Hash {
auth, ok := m.Authentication.(PackageAuthenticationHashes)
if !ok {
return nil

View File

@ -43,7 +43,7 @@ func (cp *CachedProvider) PackageLocation() getproviders.PackageLocalDir {
// If you need a specific version of hash rather than just whichever one is
// current default, call that version's corresponding method (e.g. HashV1)
// directly instead.
func (cp *CachedProvider) Hash() (string, error) {
func (cp *CachedProvider) Hash() (getproviders.Hash, error) {
return getproviders.PackageHash(cp.PackageLocation())
}
@ -54,7 +54,7 @@ func (cp *CachedProvider) Hash() (string, error) {
//
// MatchesHash may accept hashes in a number of different formats. Over time
// the set of supported formats may grow and shrink.
func (cp *CachedProvider) MatchesHash(want string) (bool, error) {
func (cp *CachedProvider) MatchesHash(want getproviders.Hash) (bool, error) {
return getproviders.PackageMatchesHash(cp.PackageLocation(), want)
}
@ -69,7 +69,7 @@ func (cp *CachedProvider) MatchesHash(want string) (bool, error) {
// 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) {
func (cp *CachedProvider) HashV1() (getproviders.Hash, error) {
return getproviders.PackageHashV1(cp.PackageLocation())
}

View File

@ -18,7 +18,7 @@ func TestCachedProviderHash(t *testing.T) {
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/darwin_amd64",
}
want := "h1:qjsREM4DqEWECD43FcPqddZ9oxCG+IaMTxvWPciS05g="
want := getproviders.MustParseHash("h1:qjsREM4DqEWECD43FcPqddZ9oxCG+IaMTxvWPciS05g=")
got, err := cp.Hash()
if err != nil {
t.Fatalf("unexpected error: %s", err)

View File

@ -418,7 +418,7 @@ NeedProvider:
}
lockEntries[provider] = lockFileEntry{
SelectedVersion: version,
PackageHash: hash,
PackageHash: hash.String(),
}
}
err := i.lockFile().Write(lockEntries)
@ -471,7 +471,13 @@ func (i *Installer) SelectedPackages() (map[addrs.Provider]*CachedProvider, erro
continue
}
ok, err := cached.MatchesHash(entry.PackageHash)
hash, err := getproviders.ParseHash(entry.PackageHash)
if err != nil {
errs[provider] = fmt.Errorf("local cache for %s has invalid hash %q: %s", provider, entry.PackageHash, err)
continue
}
ok, err := cached.MatchesHash(hash)
if err != nil {
errs[provider] = fmt.Errorf("failed to verify checksum for v%s package: %s", entry.SelectedVersion, err)
continue