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:
parent
264a3cf031
commit
6694cfaa0e
|
@ -270,7 +270,7 @@ func (c *ProvidersMirrorCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
indexArchives[version][platform.String()] = map[string]interface{}{
|
indexArchives[version][platform.String()] = map[string]interface{}{
|
||||||
"url": archiveFilename, // a relative URL from the index file's URL
|
"url": archiveFilename, // a relative URL from the index file's URL
|
||||||
"hashes": []string{hash}, // an array to allow for additional hash formats in future
|
"hashes": []string{hash.String()}, // an array to allow for additional hash formats in future
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mainIndex := map[string]interface{}{
|
mainIndex := map[string]interface{}{
|
||||||
|
|
|
@ -2,7 +2,6 @@ package depsfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/internal/getproviders"
|
"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
|
// non-lockable provider address then this function will panic. Use
|
||||||
// function ProviderIsLockable to determine whether a particular provider
|
// function ProviderIsLockable to determine whether a particular provider
|
||||||
// should participate in the version locking mechanism.
|
// 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) {
|
if !ProviderIsLockable(addr) {
|
||||||
panic(fmt.Sprintf("Locks.SetProvider with non-lockable provider %s", 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{
|
new := &ProviderLock{
|
||||||
addr: addr,
|
addr: addr,
|
||||||
version: version,
|
version: version,
|
||||||
|
@ -137,7 +133,7 @@ type ProviderLock struct {
|
||||||
// means we can only populate the hash for the current platform, and so
|
// 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
|
// it won't be possible to verify a subsequent installation of the same
|
||||||
// provider on a different platform.
|
// provider on a different platform.
|
||||||
hashes []string
|
hashes []getproviders.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provider returns the address of the provider this lock applies to.
|
// 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.
|
// of which must match in order for verification to be considered successful.
|
||||||
//
|
//
|
||||||
// Do not modify the backing array of the returned slice.
|
// Do not modify the backing array of the returned slice.
|
||||||
func (l *ProviderLock) AllHashes() []string {
|
func (l *ProviderLock) AllHashes() []getproviders.Hash {
|
||||||
return l.hashes
|
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
|
// At least one of the given hashes must match for a package to be considered
|
||||||
// valud.
|
// valud.
|
||||||
func (l *ProviderLock) PreferredHashes() []string {
|
func (l *ProviderLock) PreferredHashes() []getproviders.Hash {
|
||||||
return getproviders.PreferredHashes(l.hashes)
|
return getproviders.PreferredHashes(l.hashes)
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,8 +102,8 @@ func SaveLocksToFile(locks *Locks, filename string) tfdiags.Diagnostics {
|
||||||
}
|
}
|
||||||
if len(lock.hashes) != 0 {
|
if len(lock.hashes) != 0 {
|
||||||
hashVals := make([]cty.Value, 0, len(lock.hashes))
|
hashVals := make([]cty.Value, 0, len(lock.hashes))
|
||||||
for _, str := range lock.hashes {
|
for _, hash := range lock.hashes {
|
||||||
hashVals = append(hashVals, cty.StringVal(str))
|
hashVals = append(hashVals, cty.StringVal(hash.String()))
|
||||||
}
|
}
|
||||||
// We're using a set rather than a list here because the order
|
// We're using a set rather than a list here because the order
|
||||||
// isn't significant and SetAttributeValue will automatically
|
// isn't significant and SetAttributeValue will automatically
|
||||||
|
@ -369,7 +369,7 @@ func decodeProviderVersionConstraintsArgument(provider addrs.Provider, attr *hcl
|
||||||
return constraints, diags
|
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
|
var diags tfdiags.Diagnostics
|
||||||
if attr == nil {
|
if attr == nil {
|
||||||
// It's okay to omit this argument.
|
// It's okay to omit this argument.
|
||||||
|
@ -396,7 +396,7 @@ func decodeProviderHashesArgument(provider addrs.Provider, attr *hcl.Attribute)
|
||||||
return nil, diags
|
return nil, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := make([]string, 0, len(hashExprs))
|
ret := make([]getproviders.Hash, 0, len(hashExprs))
|
||||||
for _, hashExpr := range hashExprs {
|
for _, hashExpr := range hashExprs {
|
||||||
var raw string
|
var raw string
|
||||||
hclDiags := gohcl.DecodeExpression(hashExpr, nil, &raw)
|
hclDiags := gohcl.DecodeExpression(hashExpr, nil, &raw)
|
||||||
|
@ -404,10 +404,19 @@ func decodeProviderHashesArgument(provider addrs.Provider, attr *hcl.Attribute)
|
||||||
if hclDiags.HasErrors() {
|
if hclDiags.HasErrors() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// TODO: Validate the hash syntax, but not the actual hash schemes
|
|
||||||
// because we expect to support different hash formats over time and
|
hash, err := getproviders.ParseHash(raw)
|
||||||
// will silently ignore ones that we no longer prefer.
|
if err != nil {
|
||||||
ret = append(ret, raw)
|
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
|
return ret, diags
|
||||||
|
|
|
@ -144,10 +144,10 @@ func TestLoadLocksFromFile(t *testing.T) {
|
||||||
if got, want := getproviders.VersionConstraintsString(lock.VersionConstraints()), ">= 3.0.2"; got != want {
|
if got, want := getproviders.VersionConstraintsString(lock.VersionConstraints()), ">= 3.0.2"; got != want {
|
||||||
t.Errorf("wrong version constraints\ngot: %s\nwant: %s", got, want)
|
t.Errorf("wrong version constraints\ngot: %s\nwant: %s", got, want)
|
||||||
}
|
}
|
||||||
wantHashes := []string{
|
wantHashes := []getproviders.Hash{
|
||||||
"test:placeholder-hash-1",
|
getproviders.MustParseHash("test:placeholder-hash-1"),
|
||||||
"test:placeholder-hash-2",
|
getproviders.MustParseHash("test:placeholder-hash-2"),
|
||||||
"test:placeholder-hash-3",
|
getproviders.MustParseHash("test:placeholder-hash-3"),
|
||||||
}
|
}
|
||||||
if diff := cmp.Diff(wantHashes, lock.hashes); diff != "" {
|
if diff := cmp.Diff(wantHashes, lock.hashes); diff != "" {
|
||||||
t.Errorf("wrong hashes\n%s", diff)
|
t.Errorf("wrong hashes\n%s", diff)
|
||||||
|
@ -169,10 +169,10 @@ func TestSaveLocksToFile(t *testing.T) {
|
||||||
oneDotTwo := getproviders.MustParseVersion("1.2.0")
|
oneDotTwo := getproviders.MustParseVersion("1.2.0")
|
||||||
atLeastOneDotOh := getproviders.MustParseVersionConstraints(">= 1.0.0")
|
atLeastOneDotOh := getproviders.MustParseVersionConstraints(">= 1.0.0")
|
||||||
pessimisticOneDotOh := getproviders.MustParseVersionConstraints("~> 1")
|
pessimisticOneDotOh := getproviders.MustParseVersionConstraints("~> 1")
|
||||||
hashes := []string{
|
hashes := []getproviders.Hash{
|
||||||
"test:cccccccccccccccccccccccccccccccccccccccccccccccc",
|
getproviders.MustParseHash("test:cccccccccccccccccccccccccccccccccccccccccccccccc"),
|
||||||
"test:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
getproviders.MustParseHash("test:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
|
||||||
"test:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
getproviders.MustParseHash("test:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
|
||||||
}
|
}
|
||||||
locks.SetProvider(fooProvider, oneDotOh, atLeastOneDotOh, hashes)
|
locks.SetProvider(fooProvider, oneDotOh, atLeastOneDotOh, hashes)
|
||||||
locks.SetProvider(barProvider, oneDotTwo, pessimisticOneDotOh, nil)
|
locks.SetProvider(barProvider, oneDotTwo, pessimisticOneDotOh, nil)
|
||||||
|
|
|
@ -11,8 +11,142 @@ import (
|
||||||
"golang.org/x/mod/sumdb/dirhash"
|
"golang.org/x/mod/sumdb/dirhash"
|
||||||
)
|
)
|
||||||
|
|
||||||
const h1Prefix = "h1:"
|
// Hash is a specially-formatted string representing a checksum of a package
|
||||||
const zipHashPrefix = "zh:"
|
// 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
|
// PackageHash computes a hash of the contents of the package at the given
|
||||||
// location, using whichever hash algorithm is the current default.
|
// 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
|
// PackageLocalDir and PackageLocalArchive, because it needs to access the
|
||||||
// contents of the indicated package in order to compute the hash. If given
|
// contents of the indicated package in order to compute the hash. If given
|
||||||
// a non-local location this function will always return an error.
|
// 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)
|
return PackageHashV1(loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,9 +178,9 @@ func PackageHash(loc PackageLocation) (string, error) {
|
||||||
// PackageLocalDir and PackageLocalArchive, because it needs to access the
|
// PackageLocalDir and PackageLocalArchive, because it needs to access the
|
||||||
// contents of the indicated package in order to compute the hash. If given
|
// contents of the indicated package in order to compute the hash. If given
|
||||||
// a non-local location this function will always return an error.
|
// a non-local location this function will always return an error.
|
||||||
func PackageMatchesHash(loc PackageLocation, want string) (bool, error) {
|
func PackageMatchesHash(loc PackageLocation, want Hash) (bool, error) {
|
||||||
switch {
|
switch want.Scheme() {
|
||||||
case strings.HasPrefix(want, h1Prefix):
|
case HashScheme1:
|
||||||
got, err := PackageHashV1(loc)
|
got, err := PackageHashV1(loc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
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
|
// 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
|
// 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.
|
// verification in order for a package to be considered valid.
|
||||||
func PreferredHashes(given []string) []string {
|
func PreferredHashes(given []Hash) []Hash {
|
||||||
var ret []string
|
var ret []Hash
|
||||||
for _, s := range given {
|
for _, hash := range given {
|
||||||
if strings.HasPrefix(s, h1Prefix) {
|
if hash.Scheme() == HashScheme1 {
|
||||||
return append(ret, s)
|
return append(ret, hash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
|
@ -87,7 +221,7 @@ func PreferredHashes(given []string) []string {
|
||||||
//
|
//
|
||||||
// Because this hashing scheme uses the official provider .zip file as its
|
// Because this hashing scheme uses the official provider .zip file as its
|
||||||
// input, it accepts only PackageLocalArchive locations.
|
// input, it accepts only PackageLocalArchive locations.
|
||||||
func PackageHashLegacyZipSHA(loc PackageLocalArchive) (string, error) {
|
func PackageHashLegacyZipSHA(loc PackageLocalArchive) (Hash, error) {
|
||||||
archivePath, err := filepath.EvalSymlinks(string(loc))
|
archivePath, err := filepath.EvalSymlinks(string(loc))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -106,7 +240,7 @@ func PackageHashLegacyZipSHA(loc PackageLocalArchive) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
gotHash := h.Sum(nil)
|
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
|
// 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
|
// This just adds the "zh:" prefix and encodes the string in hex, so that the
|
||||||
// result is in the same format as PackageHashLegacyZipSHA.
|
// result is in the same format as PackageHashLegacyZipSHA.
|
||||||
func HashLegacyZipSHAFromSHA(sum [sha256.Size]byte) string {
|
func HashLegacyZipSHAFromSHA(sum [sha256.Size]byte) Hash {
|
||||||
return fmt.Sprintf("%s%x", zipHashPrefix, sum[:])
|
return HashSchemeZip.New(fmt.Sprintf("%x", sum[:]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// PackageHashV1 computes a hash of the contents of the package at the given
|
// 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
|
// 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
|
// 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
|
// PackageLocalDir and PackageLocalArchive, because it needs to access the
|
||||||
// contents of the indicated package in order to compute the hash. If given
|
// contents of the indicated package in order to compute the hash. If given
|
||||||
// a non-local location this function will always return an error.
|
// 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
|
// Our HashV1 is really just the Go Modules hash version 1, which is
|
||||||
// sufficient for our needs and already well-used for identity of
|
// sufficient for our needs and already well-used for identity of
|
||||||
// Go Modules distribution packages. It is also blocked from incompatible
|
// Go Modules distribution packages. It is also blocked from incompatible
|
||||||
|
@ -163,7 +298,10 @@ func PackageHashV1(loc PackageLocation) (string, error) {
|
||||||
return "", err
|
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:
|
case PackageLocalArchive:
|
||||||
archivePath, err := filepath.EvalSymlinks(string(loc))
|
archivePath, err := filepath.EvalSymlinks(string(loc))
|
||||||
|
@ -171,7 +309,10 @@ func PackageHashV1(loc PackageLocation) (string, error) {
|
||||||
return "", err
|
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:
|
default:
|
||||||
return "", fmt.Errorf("cannot hash package at %s", loc.String())
|
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
|
// PackageLocalDir and PackageLocalArchive, because it needs to access the
|
||||||
// contents of the indicated package in order to compute the hash. If given
|
// contents of the indicated package in order to compute the hash. If given
|
||||||
// a non-local location this function will always return an error.
|
// 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)
|
return PackageHash(m.Location)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +345,7 @@ func (m PackageMeta) Hash() (string, error) {
|
||||||
// PackageLocalDir and PackageLocalArchive, because it needs to access the
|
// PackageLocalDir and PackageLocalArchive, because it needs to access the
|
||||||
// contents of the indicated package in order to compute the hash. If given
|
// contents of the indicated package in order to compute the hash. If given
|
||||||
// a non-local location this function will always return an error.
|
// 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)
|
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
|
// PackageLocalDir and PackageLocalArchive, because it needs to access the
|
||||||
// contents of the indicated package in order to compute the hash. If given
|
// contents of the indicated package in order to compute the hash. If given
|
||||||
// a non-local location this function will always return an error.
|
// 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)
|
return PackageHashV1(m.Location)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
// A network mirror might not provide any hashes at all, in which case
|
||||||
// the package has no source-defined authentication whatsoever.
|
// the package has no source-defined authentication whatsoever.
|
||||||
if len(archiveMeta.Hashes) > 0 {
|
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
|
return ret, nil
|
||||||
|
|
|
@ -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"),
|
Location: PackageHTTPURL(httpServer.URL + "/terraform.io/test/exists/terraform-provider-test_v1.0.0_tos_m68k.zip"),
|
||||||
Authentication: packageHashAuthentication{
|
Authentication: packageHashAuthentication{
|
||||||
RequiredHash: "h1:placeholder-hash",
|
RequiredHash: "h1:placeholder-hash",
|
||||||
ValidHashes: []string{"h1:placeholder-hash", "h0:unacceptable-hash"},
|
ValidHashes: []Hash{"h1:placeholder-hash", "h0:unacceptable-hash"},
|
||||||
Platform: Platform{"tos", "m68k"},
|
Platform: Platform{"tos", "m68k"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -133,7 +133,7 @@ func TestHTTPMirrorSource(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
gotHashes := got.AcceptableHashes()
|
gotHashes := got.AcceptableHashes()
|
||||||
wantHashes := map[Platform][]string{
|
wantHashes := map[Platform][]Hash{
|
||||||
tosPlatform: {"h1:placeholder-hash", "h0:unacceptable-hash"},
|
tosPlatform: {"h1:placeholder-hash", "h0:unacceptable-hash"},
|
||||||
}
|
}
|
||||||
if diff := cmp.Diff(wantHashes, gotHashes); diff != "" {
|
if diff := cmp.Diff(wantHashes, gotHashes); diff != "" {
|
||||||
|
|
|
@ -147,7 +147,7 @@ type PackageAuthenticationHashes interface {
|
||||||
// Authenticators that don't use hashes as their authentication procedure
|
// Authenticators that don't use hashes as their authentication procedure
|
||||||
// will either not implement this interface or will have an implementation
|
// will either not implement this interface or will have an implementation
|
||||||
// that returns an empty result.
|
// that returns an empty result.
|
||||||
AcceptableHashes() map[Platform][]string
|
AcceptableHashes() map[Platform][]Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
type packageAuthenticationAll []PackageAuthentication
|
type packageAuthenticationAll []PackageAuthentication
|
||||||
|
@ -183,7 +183,7 @@ func (checks packageAuthenticationAll) AuthenticatePackage(localLocation Package
|
||||||
return authResult, nil
|
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
|
// 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
|
// 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
|
// 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 {
|
type packageHashAuthentication struct {
|
||||||
RequiredHash string
|
RequiredHash Hash
|
||||||
ValidHashes []string
|
ValidHashes []Hash
|
||||||
Platform Platform
|
Platform Platform
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,10 +215,10 @@ type packageHashAuthentication struct {
|
||||||
// The PreferredHash function will select which of the given hashes is
|
// The PreferredHash function will select which of the given hashes is
|
||||||
// considered by Terraform to be the strongest verification, and authentication
|
// considered by Terraform to be the strongest verification, and authentication
|
||||||
// succeeds as long as that chosen hash matches.
|
// 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)
|
requiredHashes := PreferredHashes(validHashes)
|
||||||
// TODO: Update to support multiple hashes
|
// TODO: Update to support multiple hashes
|
||||||
var requiredHash string
|
var requiredHash Hash
|
||||||
if len(requiredHashes) > 0 {
|
if len(requiredHashes) > 0 {
|
||||||
requiredHash = 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)
|
return nil, fmt.Errorf("provider package doesn't match the expected checksum %q", a.RequiredHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a packageHashAuthentication) AcceptableHashes() map[Platform][]string {
|
func (a packageHashAuthentication) AcceptableHashes() map[Platform][]Hash {
|
||||||
return map[Platform][]string{
|
return map[Platform][]Hash{
|
||||||
a.Platform: a.ValidHashes,
|
a.Platform: a.ValidHashes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -295,8 +295,8 @@ func (a archiveHashAuthentication) AuthenticatePackage(localLocation PackageLoca
|
||||||
return &PackageAuthenticationResult{result: verifiedChecksum}, nil
|
return &PackageAuthenticationResult{result: verifiedChecksum}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a archiveHashAuthentication) AcceptableHashes() map[Platform][]string {
|
func (a archiveHashAuthentication) AcceptableHashes() map[Platform][]Hash {
|
||||||
return map[Platform][]string{
|
return map[Platform][]Hash{
|
||||||
a.Platform: {HashLegacyZipSHAFromSHA(a.WantSHA256Sum)},
|
a.Platform: {HashLegacyZipSHAFromSHA(a.WantSHA256Sum)},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,9 +104,9 @@ func TestPackageHashAuthentication_success(t *testing.T) {
|
||||||
// Location must be a PackageLocalArchive path
|
// Location must be a PackageLocalArchive path
|
||||||
location := PackageLocalDir("testdata/filesystem-mirror/registry.terraform.io/hashicorp/null/2.0.0/linux_amd64")
|
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
|
// Known-good HashV1 result for this directory
|
||||||
"h1:qjsREM4DqEWECD43FcPqddZ9oxCG+IaMTxvWPciS05g=",
|
Hash("h1:qjsREM4DqEWECD43FcPqddZ9oxCG+IaMTxvWPciS05g="),
|
||||||
}
|
}
|
||||||
|
|
||||||
auth := NewPackageHashAuthentication(Platform{"linux", "amd64"}, wantHashes)
|
auth := NewPackageHashAuthentication(Platform{"linux", "amd64"}, wantHashes)
|
||||||
|
@ -145,7 +145,7 @@ func TestPackageHashAuthentication_failure(t *testing.T) {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
// Invalid expected hash, either because we'll error before we
|
// Invalid expected hash, either because we'll error before we
|
||||||
// reach it, or we want to force a checksum mismatch.
|
// 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)
|
result, err := auth.AuthenticatePackage(test.location)
|
||||||
|
|
||||||
if result != nil {
|
if result != nil {
|
||||||
|
|
|
@ -115,7 +115,7 @@ func TestSourcePackageMeta(t *testing.T) {
|
||||||
version string
|
version string
|
||||||
os, arch string
|
os, arch string
|
||||||
want PackageMeta
|
want PackageMeta
|
||||||
wantHashes map[Platform][]string
|
wantHashes map[Platform][]Hash
|
||||||
wantErr string
|
wantErr string
|
||||||
}{
|
}{
|
||||||
// These test cases are relying on behaviors of the fake provider
|
// 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"}: {
|
{"linux", "amd64"}: {
|
||||||
"zh:000000000000000000000000000000000000000000000000000000000000f00d",
|
"zh:000000000000000000000000000000000000000000000000000000000000f00d",
|
||||||
},
|
},
|
||||||
|
|
|
@ -259,7 +259,7 @@ func (m PackageMeta) PackedFilePath(baseDir string) string {
|
||||||
// Authentication field. AcceptableHashes therefore returns an empty result
|
// Authentication field. AcceptableHashes therefore returns an empty result
|
||||||
// for a PackageMeta that has no authentication object, or has one that does
|
// for a PackageMeta that has no authentication object, or has one that does
|
||||||
// not make use of hashes.
|
// not make use of hashes.
|
||||||
func (m PackageMeta) AcceptableHashes() map[Platform][]string {
|
func (m PackageMeta) AcceptableHashes() map[Platform][]Hash {
|
||||||
auth, ok := m.Authentication.(PackageAuthenticationHashes)
|
auth, ok := m.Authentication.(PackageAuthenticationHashes)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -43,7 +43,7 @@ func (cp *CachedProvider) PackageLocation() getproviders.PackageLocalDir {
|
||||||
// If you need a specific version of hash rather than just whichever one is
|
// 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)
|
// current default, call that version's corresponding method (e.g. HashV1)
|
||||||
// directly instead.
|
// directly instead.
|
||||||
func (cp *CachedProvider) Hash() (string, error) {
|
func (cp *CachedProvider) Hash() (getproviders.Hash, error) {
|
||||||
return getproviders.PackageHash(cp.PackageLocation())
|
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
|
// MatchesHash may accept hashes in a number of different formats. Over time
|
||||||
// the set of supported formats may grow and shrink.
|
// 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)
|
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
|
// being added (in a backward-compatible way) in future. The result from
|
||||||
// HashV1 always begins with the prefix "h1:" so that callers can distinguish
|
// HashV1 always begins with the prefix "h1:" so that callers can distinguish
|
||||||
// the results of potentially multiple different hash algorithms in future.
|
// 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())
|
return getproviders.PackageHashV1(cp.PackageLocation())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ func TestCachedProviderHash(t *testing.T) {
|
||||||
PackageDir: "testdata/cachedir/registry.terraform.io/hashicorp/null/2.0.0/darwin_amd64",
|
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()
|
got, err := cp.Hash()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %s", err)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
|
|
@ -418,7 +418,7 @@ NeedProvider:
|
||||||
}
|
}
|
||||||
lockEntries[provider] = lockFileEntry{
|
lockEntries[provider] = lockFileEntry{
|
||||||
SelectedVersion: version,
|
SelectedVersion: version,
|
||||||
PackageHash: hash,
|
PackageHash: hash.String(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err := i.lockFile().Write(lockEntries)
|
err := i.lockFile().Write(lockEntries)
|
||||||
|
@ -471,7 +471,13 @@ func (i *Installer) SelectedPackages() (map[addrs.Provider]*CachedProvider, erro
|
||||||
continue
|
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 {
|
if err != nil {
|
||||||
errs[provider] = fmt.Errorf("failed to verify checksum for v%s package: %s", entry.SelectedVersion, err)
|
errs[provider] = fmt.Errorf("failed to verify checksum for v%s package: %s", entry.SelectedVersion, err)
|
||||||
continue
|
continue
|
||||||
|
|
Loading…
Reference in New Issue