Our diagnostics model allows for optionally annotating an error or warning
with information about the expression and eval context it was generated
from, which the diagnostic renderer for the UI will then use to give the
user some additional hints about what values may have contributed to the
error.
We previously didn't have those annotations on the results of evaluating
for_each expressions though, because in that case we were using the helper
function to evaluate an expression in one shot and thus we didn't ever
have a reference to the EvalContext in order to include it in the
diagnostic values.
Now, at the expense of having to handle the evaluation at a slightly lower
level of abstraction, we'll annotate all of the for_each error messages
with source expression information. This is valuable because we see users
often confused as to how their complex for_each expressions ended up being
invalid, and hopefully giving some information about what the inputs were
will allow more users to self-solve.
// Attach a diag as we do with count, with the same downsides
diags=diags.Append(&hcl.Diagnostic{
Severity:hcl.DiagError,
Summary:"Invalid for_each argument",
Detail:`The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the for_each depends on.`,
// This shouldn't happen in real code, but it can unfortunately arise
// in unit tests due to incompletely-implemented mocks. :(
hclCtx=&hcl.EvalContext{}
}
diags=diags.Append(moreDiags)
ifdiags.HasErrors(){// Can't continue if we don't even have a valid scope
returnnullMap,diags
}
forEachVal,forEachDiags:=expr.Value(hclCtx)
diags=diags.Append(forEachDiags)
ifforEachVal.ContainsMarked(){
diags=diags.Append(&hcl.Diagnostic{
Severity:hcl.DiagError,
Summary:"Invalid for_each argument",
Detail:"Sensitive variable, or values derived from sensitive variables, cannot be used as for_each arguments. If used, the sensitive value could be exposed as a resource instance key.",
Subject:expr.Range().Ptr(),
Severity:hcl.DiagError,
Summary:"Invalid for_each argument",
Detail:"Sensitive variable, or values derived from sensitive variables, cannot be used as for_each arguments. If used, the sensitive value could be exposed as a resource instance key.",
Detail:fmt.Sprintf(`The given "for_each" argument value is unsuitable: the "for_each" argument must be a map, or set of strings, and you have provided a value of type %s.`,ty.FriendlyName()),
Subject:expr.Range().Ptr(),
Severity:hcl.DiagError,
Summary:"Invalid for_each argument",
Detail:fmt.Sprintf(`The given "for_each" argument value is unsuitable: the "for_each" argument must be a map, or set of strings, and you have provided a value of type %s.`,ty.FriendlyName()),
// since we can't use a set values that are unknown, we treat the
// entire set as unknown
if!forEachVal.IsWhollyKnown(){
if!allowUnknown{
diags=diags.Append(&hcl.Diagnostic{
Severity:hcl.DiagError,
Summary:"Invalid for_each argument",
Detail:errInvalidForEachUnknownDetail,
Subject:expr.Range().Ptr(),
Expression:expr,
EvalContext:hclCtx,
})
}
returncty.UnknownVal(ty),diags
}
ifty.ElementType()!=cty.String{
diags=diags.Append(&hcl.Diagnostic{
Severity:hcl.DiagError,
Summary:"Invalid for_each set argument",
Detail:fmt.Sprintf(`The given "for_each" argument value is unsuitable: "for_each" supports maps and sets of strings, but you have provided a set containing type %s.`,forEachVal.Type().ElementType().FriendlyName()),
Subject:expr.Range().Ptr(),
Severity:hcl.DiagError,
Summary:"Invalid for_each set argument",
Detail:fmt.Sprintf(`The given "for_each" argument value is unsuitable: "for_each" supports maps and sets of strings, but you have provided a set containing type %s.`,forEachVal.Type().ElementType().FriendlyName()),
consterrInvalidForEachUnknownDetail=`The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the for_each depends on.`