mirror of https://github.com/hashicorp/terraform
We need to abstract the function results verification to use internally too, so start by moving it out of the providers code.pull/37001/head
parent
49e8b56b32
commit
d016070564
@ -0,0 +1,129 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package lang
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
type priorResult struct {
|
||||
hash [sha256.Size]byte
|
||||
// when the result was from a current run, we keep a record of the result
|
||||
// value to aid in debugging. Results stored in the plan will only have the
|
||||
// hash to avoid bloating the plan with what could be many very large
|
||||
// values.
|
||||
value cty.Value
|
||||
}
|
||||
|
||||
type FunctionResults struct {
|
||||
mu sync.Mutex
|
||||
// results stores the prior result from a function call, keyed by
|
||||
// the hash of the function name and arguments.
|
||||
results map[[sha256.Size]byte]priorResult
|
||||
}
|
||||
|
||||
// NewFunctionResultsTable initializes a mapping of function calls to prior
|
||||
// results used to validate function calls. The hashes argument is an
|
||||
// optional slice of prior result hashes used to preload the cache.
|
||||
func NewFunctionResultsTable(hashes []FunctionHash) *FunctionResults {
|
||||
res := &FunctionResults{
|
||||
results: make(map[[sha256.Size]byte]priorResult),
|
||||
}
|
||||
|
||||
res.insertHashes(hashes)
|
||||
return res
|
||||
}
|
||||
|
||||
// CheckPrior compares the function call against any cached results, and returns
|
||||
// an error if the result does not match a prior call. A zero-value provider
|
||||
// address can be used for internal functions which need this validation.
|
||||
func (f *FunctionResults) CheckPrior(provider addrs.Provider, name string, args []cty.Value, result cty.Value) error {
|
||||
argSum := sha256.New()
|
||||
|
||||
if !provider.IsZero() {
|
||||
io.WriteString(argSum, provider.String()+"|")
|
||||
}
|
||||
io.WriteString(argSum, name)
|
||||
|
||||
for _, arg := range args {
|
||||
// cty.Values have a Hash method, but it is not collision resistant. We
|
||||
// are going to rely on the GoString formatting instead, which gives
|
||||
// detailed results for all values.
|
||||
io.WriteString(argSum, "|"+arg.GoString())
|
||||
}
|
||||
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
argHash := [sha256.Size]byte(argSum.Sum(nil))
|
||||
resHash := sha256.Sum256([]byte(result.GoString()))
|
||||
|
||||
res, ok := f.results[argHash]
|
||||
if !ok {
|
||||
f.results[argHash] = priorResult{
|
||||
hash: resHash,
|
||||
value: result,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if resHash != res.hash {
|
||||
provPrefix := ""
|
||||
if !provider.IsZero() {
|
||||
provPrefix = fmt.Sprintf("provider %s ", provider)
|
||||
}
|
||||
// Log the args for debugging in case the hcl context is
|
||||
// insufficient. The error should be adequate most of the time, and
|
||||
// could already be quite long, so we don't want to add all
|
||||
// arguments too.
|
||||
log.Printf("[ERROR] %sfunction %s returned an inconsistent result with args: %#v\n", provPrefix, name, args)
|
||||
// The hcl package will add the necessary context around the error in
|
||||
// the diagnostic, but we add the differing results when we can.
|
||||
if res.value != cty.NilVal {
|
||||
return fmt.Errorf("function returned an inconsistent result,\nwas: %#v,\nnow: %#v", res.value, result)
|
||||
}
|
||||
return fmt.Errorf("function returned an inconsistent result")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// insertHashes insert key-value pairs to the functionResults map. This is used
|
||||
// to preload stored values before any Verify calls are made.
|
||||
func (f *FunctionResults) insertHashes(hashes []FunctionHash) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
for _, res := range hashes {
|
||||
f.results[[sha256.Size]byte(res.Key)] = priorResult{
|
||||
hash: [sha256.Size]byte(res.Result),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FunctionHash contains the key and result hash values from a prior function
|
||||
// call.
|
||||
type FunctionHash struct {
|
||||
Key []byte
|
||||
Result []byte
|
||||
}
|
||||
|
||||
// copy the hash values into a struct which can be recorded in the plan.
|
||||
func (f *FunctionResults) GetHashes() []FunctionHash {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
var res []FunctionHash
|
||||
for k, r := range f.results {
|
||||
res = append(res, FunctionHash{Key: k[:], Result: r.hash[:]})
|
||||
}
|
||||
return res
|
||||
}
|
||||
Loading…
Reference in new issue