mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-07 14:44:11 -06:00
171e7ef6d9
Our original messaging here was largely just lifted from the equivalent message for unknown values in "count", and it didn't really include any specific advice on how to update a configuration to make for_each valid, instead focusing only on the workaround of using the -target planning option. It's tough to pack in a fully-actionable suggestion here since unknown values in for_each keys tends to be a gnarly architectural problem rather than a local quirk -- when data flows between modules it can sometimes be unclear whether it'll end up being used in a context which allows unknown values. I did my best to summarize the advice we've been giving in community forum though, in the hope that more people will be able to address this for themselves without asking for help, until we're one day able to smooth this out better with a mechanism such as "partial apply".
213 lines
6.9 KiB
Go
213 lines
6.9 KiB
Go
package terraform
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/hcltest"
|
|
"github.com/hashicorp/terraform/internal/lang/marks"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
func TestEvaluateForEachExpression_valid(t *testing.T) {
|
|
tests := map[string]struct {
|
|
Expr hcl.Expression
|
|
ForEachMap map[string]cty.Value
|
|
}{
|
|
"empty set": {
|
|
hcltest.MockExprLiteral(cty.SetValEmpty(cty.String)),
|
|
map[string]cty.Value{},
|
|
},
|
|
"multi-value string set": {
|
|
hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")})),
|
|
map[string]cty.Value{
|
|
"a": cty.StringVal("a"),
|
|
"b": cty.StringVal("b"),
|
|
},
|
|
},
|
|
"empty map": {
|
|
hcltest.MockExprLiteral(cty.MapValEmpty(cty.Bool)),
|
|
map[string]cty.Value{},
|
|
},
|
|
"map": {
|
|
hcltest.MockExprLiteral(cty.MapVal(map[string]cty.Value{
|
|
"a": cty.BoolVal(true),
|
|
"b": cty.BoolVal(false),
|
|
})),
|
|
map[string]cty.Value{
|
|
"a": cty.BoolVal(true),
|
|
"b": cty.BoolVal(false),
|
|
},
|
|
},
|
|
"map containing unknown values": {
|
|
hcltest.MockExprLiteral(cty.MapVal(map[string]cty.Value{
|
|
"a": cty.UnknownVal(cty.Bool),
|
|
"b": cty.UnknownVal(cty.Bool),
|
|
})),
|
|
map[string]cty.Value{
|
|
"a": cty.UnknownVal(cty.Bool),
|
|
"b": cty.UnknownVal(cty.Bool),
|
|
},
|
|
},
|
|
"map containing sensitive values, but strings are literal": {
|
|
hcltest.MockExprLiteral(cty.MapVal(map[string]cty.Value{
|
|
"a": cty.BoolVal(true).Mark(marks.Sensitive),
|
|
"b": cty.BoolVal(false),
|
|
})),
|
|
map[string]cty.Value{
|
|
"a": cty.BoolVal(true).Mark(marks.Sensitive),
|
|
"b": cty.BoolVal(false),
|
|
},
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
ctx := &MockEvalContext{}
|
|
ctx.installSimpleEval()
|
|
forEachMap, diags := evaluateForEachExpression(test.Expr, ctx)
|
|
|
|
if len(diags) != 0 {
|
|
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
|
|
}
|
|
|
|
if !reflect.DeepEqual(forEachMap, test.ForEachMap) {
|
|
t.Errorf(
|
|
"wrong map value\ngot: %swant: %s",
|
|
spew.Sdump(forEachMap), spew.Sdump(test.ForEachMap),
|
|
)
|
|
}
|
|
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEvaluateForEachExpression_errors(t *testing.T) {
|
|
tests := map[string]struct {
|
|
Expr hcl.Expression
|
|
Summary, DetailSubstring string
|
|
}{
|
|
"null set": {
|
|
hcltest.MockExprLiteral(cty.NullVal(cty.Set(cty.String))),
|
|
"Invalid for_each argument",
|
|
`the given "for_each" argument value is null`,
|
|
},
|
|
"string": {
|
|
hcltest.MockExprLiteral(cty.StringVal("i am definitely a set")),
|
|
"Invalid for_each argument",
|
|
"must be a map, or set of strings, and you have provided a value of type string",
|
|
},
|
|
"list": {
|
|
hcltest.MockExprLiteral(cty.ListVal([]cty.Value{cty.StringVal("a"), cty.StringVal("a")})),
|
|
"Invalid for_each argument",
|
|
"must be a map, or set of strings, and you have provided a value of type list",
|
|
},
|
|
"tuple": {
|
|
hcltest.MockExprLiteral(cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")})),
|
|
"Invalid for_each argument",
|
|
"must be a map, or set of strings, and you have provided a value of type tuple",
|
|
},
|
|
"unknown string set": {
|
|
hcltest.MockExprLiteral(cty.UnknownVal(cty.Set(cty.String))),
|
|
"Invalid for_each argument",
|
|
"set includes values derived from resource attributes that cannot be determined until apply",
|
|
},
|
|
"unknown map": {
|
|
hcltest.MockExprLiteral(cty.UnknownVal(cty.Map(cty.Bool))),
|
|
"Invalid for_each argument",
|
|
"map includes keys derived from resource attributes that cannot be determined until apply",
|
|
},
|
|
"marked map": {
|
|
hcltest.MockExprLiteral(cty.MapVal(map[string]cty.Value{
|
|
"a": cty.BoolVal(true),
|
|
"b": cty.BoolVal(false),
|
|
}).Mark(marks.Sensitive)),
|
|
"Invalid for_each argument",
|
|
"Sensitive values, or values derived from sensitive values, cannot be used as for_each arguments. If used, the sensitive value could be exposed as a resource instance key.",
|
|
},
|
|
"set containing booleans": {
|
|
hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.BoolVal(true)})),
|
|
"Invalid for_each set argument",
|
|
"supports maps and sets of strings, but you have provided a set containing type bool",
|
|
},
|
|
"set containing null": {
|
|
hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.NullVal(cty.String)})),
|
|
"Invalid for_each set argument",
|
|
"must not contain null values",
|
|
},
|
|
"set containing unknown value": {
|
|
hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.UnknownVal(cty.String)})),
|
|
"Invalid for_each argument",
|
|
"set includes values derived from resource attributes that cannot be determined until apply",
|
|
},
|
|
"set containing dynamic unknown value": {
|
|
hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.UnknownVal(cty.DynamicPseudoType)})),
|
|
"Invalid for_each argument",
|
|
"set includes values derived from resource attributes that cannot be determined until apply",
|
|
},
|
|
"set containing marked values": {
|
|
hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.StringVal("beep").Mark(marks.Sensitive), cty.StringVal("boop")})),
|
|
"Invalid for_each argument",
|
|
"Sensitive values, or values derived from sensitive values, cannot be used as for_each arguments. If used, the sensitive value could be exposed as a resource instance key.",
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
ctx := &MockEvalContext{}
|
|
ctx.installSimpleEval()
|
|
_, diags := evaluateForEachExpression(test.Expr, ctx)
|
|
|
|
if len(diags) != 1 {
|
|
t.Fatalf("got %d diagnostics; want 1", diags)
|
|
}
|
|
if got, want := diags[0].Severity(), tfdiags.Error; got != want {
|
|
t.Errorf("wrong diagnostic severity %#v; want %#v", got, want)
|
|
}
|
|
if got, want := diags[0].Description().Summary, test.Summary; got != want {
|
|
t.Errorf("wrong diagnostic summary\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
if got, want := diags[0].Description().Detail, test.DetailSubstring; !strings.Contains(got, want) {
|
|
t.Errorf("wrong diagnostic detail\ngot: %s\nwant substring: %s", got, want)
|
|
}
|
|
if fromExpr := diags[0].FromExpr(); fromExpr != nil {
|
|
if fromExpr.Expression == nil {
|
|
t.Errorf("diagnostic does not refer to an expression")
|
|
}
|
|
if fromExpr.EvalContext == nil {
|
|
t.Errorf("diagnostic does not refer to an EvalContext")
|
|
}
|
|
} else {
|
|
t.Errorf("diagnostic does not support FromExpr\ngot: %s", spew.Sdump(diags[0]))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEvaluateForEachExpressionKnown(t *testing.T) {
|
|
tests := map[string]hcl.Expression{
|
|
"unknown string set": hcltest.MockExprLiteral(cty.UnknownVal(cty.Set(cty.String))),
|
|
"unknown map": hcltest.MockExprLiteral(cty.UnknownVal(cty.Map(cty.Bool))),
|
|
}
|
|
|
|
for name, expr := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
ctx := &MockEvalContext{}
|
|
ctx.installSimpleEval()
|
|
forEachVal, diags := evaluateForEachExpressionValue(expr, ctx, true)
|
|
|
|
if len(diags) != 0 {
|
|
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
|
|
}
|
|
|
|
if forEachVal.IsKnown() {
|
|
t.Error("got known, want unknown")
|
|
}
|
|
})
|
|
}
|
|
}
|