// Copyright IBM Corp. 2014, 2026 // 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 }