mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-21 10:47:34 -05:00
Other parts of the language allow deciding the sensitivity of a value based on results that won't be known until the apply phase, which means that in practice we cannot predict the final sensitivity of an unknown value. Previously this function assumed that an unknown value would always be a placeholder for a final value of the same sensitivity, which is not a valid assumption in practice and so using the results of this function could cause downstream value consistency checks to fail. This does unfortunately create a situation where a new version of OpenTofu will return an unknown value in a situation that was previously always known, which could therefore begin causing a plan-time error if the result is then used to populate something that is required to be known at plan time. However, the previous behavior caused OpenTofu to produce confusing errors (blaming a provider for OpenTofu's mistake) during the apply phase, and so the potential new plan-time errors are arguably better than the previous behavior. Any unknown result is refined as definitely not null to shrink the potential impact: other parts of the language will still be able to assume that the result of this function is not null even if it's not yet known. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
112 lines
3.5 KiB
Go
112 lines
3.5 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package funcs
|
|
|
|
import (
|
|
"github.com/opentofu/opentofu/internal/lang/marks"
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/function"
|
|
)
|
|
|
|
// SensitiveFunc returns a value identical to its argument except that
|
|
// OpenTofu will consider it to be sensitive.
|
|
var SensitiveFunc = function.New(&function.Spec{
|
|
Params: []function.Parameter{
|
|
{
|
|
Name: "value",
|
|
Type: cty.DynamicPseudoType,
|
|
AllowUnknown: true,
|
|
AllowNull: true,
|
|
AllowMarked: true,
|
|
AllowDynamicType: true,
|
|
},
|
|
},
|
|
Type: func(args []cty.Value) (cty.Type, error) {
|
|
// This function only affects the value's marks, so the result
|
|
// type is always the same as the argument type.
|
|
return args[0].Type(), nil
|
|
},
|
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
|
return args[0].Mark(marks.Sensitive), nil
|
|
},
|
|
})
|
|
|
|
// NonsensitiveFunc takes a sensitive value and returns the same value without
|
|
// the sensitive marking, effectively exposing the value.
|
|
var NonsensitiveFunc = function.New(&function.Spec{
|
|
Params: []function.Parameter{
|
|
{
|
|
Name: "value",
|
|
Type: cty.DynamicPseudoType,
|
|
AllowUnknown: true,
|
|
AllowNull: true,
|
|
AllowMarked: true,
|
|
AllowDynamicType: true,
|
|
},
|
|
},
|
|
Type: func(args []cty.Value) (cty.Type, error) {
|
|
// This function only affects the value's marks, so the result
|
|
// type is always the same as the argument type.
|
|
return args[0].Type(), nil
|
|
},
|
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
|
v, m := args[0].Unmark()
|
|
delete(m, marks.Sensitive) // remove the sensitive marking
|
|
return v.WithMarks(m), nil
|
|
},
|
|
})
|
|
|
|
// IsSensitiveFunc returns whether or not the value is sensitive.
|
|
var IsSensitiveFunc = function.New(&function.Spec{
|
|
Params: []function.Parameter{
|
|
{
|
|
Name: "value",
|
|
Type: cty.DynamicPseudoType,
|
|
AllowUnknown: true,
|
|
AllowNull: true,
|
|
AllowMarked: true,
|
|
AllowDynamicType: true,
|
|
},
|
|
},
|
|
RefineResult: refineNotNull,
|
|
Type: func(args []cty.Value) (cty.Type, error) {
|
|
return cty.Bool, nil
|
|
},
|
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
|
if !args[0].IsKnown() {
|
|
// When a value is unknown its sensitivity is also not yet
|
|
// finalized, because authors can write expressions where the
|
|
// sensitivity of the result is decided based on some other
|
|
// value that isn't yet known itself. As a simple example:
|
|
// var.unknown_bool ? sensitive("a") : "b"
|
|
//
|
|
// The above would conservatively return a sensitive unknown
|
|
// string when var.unknown_bool is not known, but then that
|
|
// variable could become false once finalized and cause the
|
|
// sensitivity to change.
|
|
//
|
|
// Therefore we must report that we can't predict whether an
|
|
// unknown value will be sensitive or not. For more information,
|
|
// refer to:
|
|
// https://github.com/opentofu/opentofu/issues/2415
|
|
return cty.UnknownVal(cty.Bool), nil
|
|
}
|
|
return cty.BoolVal(args[0].HasMark(marks.Sensitive)), nil
|
|
},
|
|
})
|
|
|
|
func Sensitive(v cty.Value) (cty.Value, error) {
|
|
return SensitiveFunc.Call([]cty.Value{v})
|
|
}
|
|
|
|
func Nonsensitive(v cty.Value) (cty.Value, error) {
|
|
return NonsensitiveFunc.Call([]cty.Value{v})
|
|
}
|
|
|
|
func IsSensitive(v cty.Value) (cty.Value, error) {
|
|
return IsSensitiveFunc.Call([]cty.Value{v})
|
|
}
|