blob: 9d4c944ccf8de4e6293c74f6003cbd85972cacdd [file] [log] [blame] [edit]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package backendbase
import (
"fmt"
"math/big"
"os"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
)
// GetPathDefault traverses the steps of the given path through the given
// value, and then returns either that value or the value given in def,
// if the found value was null.
//
// This function expects the given path to be valid for the given value, and
// will panic if not. This should be used only for values that have already
// been coerced into a known-good data type, which is typically achieved by
// passing the value that was returned by [Base.PrepareConfig], which is also
// the value passed to [Backend.Configure].
func GetPathDefault(v cty.Value, path cty.Path, def cty.Value) cty.Value {
v, err := path.Apply(v)
if err != nil {
panic(fmt.Sprintf("invalid path: %s", tfdiags.FormatError(err)))
}
if v.IsNull() {
return def
}
return v
}
// GetAttrDefault is like [GetPathDefault] but more convenient for the common
// case of looking up a single top-level attribute.
func GetAttrDefault(v cty.Value, attrName string, def cty.Value) cty.Value {
return GetPathDefault(v, cty.GetAttrPath(attrName), def)
}
// GetPathEnvDefault is like [GetPathDefault] except that the default value
// is taken from an environment variable of the name given in defEnv, returned
// as a string value.
//
// If that environment variable is unset or has an empty-string value then
// the result is null, as a convenience to callers so that they don't need to
// handle both null-ness and empty-string-ness as variants of "unset".
//
// This function panics in the same situations as [GetPathDefault].
func GetPathEnvDefault(v cty.Value, path cty.Path, defEnv string) cty.Value {
v, err := path.Apply(v)
if err != nil {
panic(fmt.Sprintf("invalid path: %s", tfdiags.FormatError(err)))
}
if v.IsNull() {
if defStr := os.Getenv(defEnv); defStr != "" {
return cty.StringVal(defStr)
}
}
return v
}
// GetAttrEnvDefault is like [GetPathEnvDefault] but more convenient for the
// common case of looking up a single top-level attribute.
func GetAttrEnvDefault(v cty.Value, attrName string, defEnv string) cty.Value {
return GetPathEnvDefault(v, cty.GetAttrPath(attrName), defEnv)
}
// GetPathEnvDefaultFallback is like [GetPathEnvDefault] except that if
// neither the attribute nor the environment variable are set then instead
// of returning null it will return the given fallback value.
//
// Unless the fallback value is null itself, this function guarantees to never
// return null.
func GetPathEnvDefaultFallback(v cty.Value, path cty.Path, defEnv string, fallback cty.Value) cty.Value {
ret := GetPathEnvDefault(v, path, defEnv)
if ret.IsNull() {
return fallback
}
return ret
}
// GetAttrEnvDefaultFallback is like [GetPathEnvDefault] except that if
// neither the attribute nor the environment variable are set then instead
// of returning null it will return the given fallback value.
//
// Unless the fallback value is null itself, this function guarantees to never
// return null.
func GetAttrEnvDefaultFallback(v cty.Value, attrName string, defEnv string, fallback cty.Value) cty.Value {
ret := GetAttrEnvDefault(v, attrName, defEnv)
if ret.IsNull() {
return fallback
}
return ret
}
// IntValue converts a cty value into a Go int64, or returns an error if that's
// not possible.
func IntValue(v cty.Value) (int64, error) {
v, err := convert.Convert(v, cty.Number)
if err != nil {
return 0, err
}
if v.IsNull() {
return 0, fmt.Errorf("must not be null")
}
bf := v.AsBigFloat()
ret, acc := bf.Int64()
if acc != big.Exact {
return 0, fmt.Errorf("must not be a whole number")
}
return ret, nil
}
// BoolValue converts a cty value Go bool, or returns an error if that's not
// possible.
func BoolValue(v cty.Value) (bool, error) {
v, err := convert.Convert(v, cty.Bool)
if err != nil {
return false, err
}
if v.IsNull() {
return false, fmt.Errorf("must not be null")
}
return v.True(), nil
}
// MustBoolValue converts a cty value Go bool, or panics if that's not possible.
func MustBoolValue(v cty.Value) bool {
ret, err := BoolValue(v)
if err != nil {
panic(fmt.Sprintf("MustBoolValue: %s", err))
}
return ret
}
// ErrorAsDiagnostics wraps a non-nil error as a tfdiags.Diagnostics.
//
// Panics if the given error is nil, since the caller should only be using
// this if they've encountered a non-nil error.
//
// This is here just as a temporary measure to preserve the old treatment of
// errors returned from legacy helper/schema-based backend implementations,
// so that we can minimize the churn caused in the first iteration of adopting
// backendbase.
//
// In the long run backends should produce higher-quality diagnostics directly
// themselves, but we wanted to first complete the deprecation of the
// legacy/helper/schema package with only mechanical code updates and then
// save diagnostic quality improvements for a later time.
func ErrorAsDiagnostics(err error) tfdiags.Diagnostics {
if err == nil {
panic("ErrorAsDiagnostics with nil error")
}
var diags tfdiags.Diagnostics
// This produces a very low-quality diagnostic message, but it matches
// how legacy helper/schema dealt with the same situation.
diags = diags.Append(err)
return diags
}