2019-06-12 10:07:32 -05:00
package terraform
import (
"fmt"
2019-09-09 17:58:44 -05:00
"github.com/hashicorp/hcl/v2"
2019-06-12 10:07:32 -05:00
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
)
// evaluateResourceForEachExpression interprets a "for_each" argument on a resource.
//
// Returns a cty.Value map, and diagnostics if necessary. It will return nil if
// the expression is nil, and is used to distinguish between an unset for_each and an
// empty map
func evaluateResourceForEachExpression ( expr hcl . Expression , ctx EvalContext ) ( forEach map [ string ] cty . Value , diags tfdiags . Diagnostics ) {
2019-07-25 10:51:55 -05:00
forEachMap , known , diags := evaluateResourceForEachExpressionKnown ( expr , ctx )
if ! known {
2019-07-25 16:10:13 -05:00
// Attach a diag as we do with count, with the same downsides
2019-07-25 10:51:55 -05:00
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
2019-08-26 14:25:03 -05:00
Summary : "Invalid for_each argument" ,
2019-07-25 10:51:55 -05:00
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. ` ,
2019-09-10 13:56:11 -05:00
Subject : expr . Range ( ) . Ptr ( ) ,
2019-07-25 10:51:55 -05:00
} )
}
return forEachMap , diags
}
// evaluateResourceForEachExpressionKnown is like evaluateResourceForEachExpression
// except that it handles an unknown result by returning an empty map and
// a known = false, rather than by reporting the unknown value as an error
// diagnostic.
func evaluateResourceForEachExpressionKnown ( expr hcl . Expression , ctx EvalContext ) ( forEach map [ string ] cty . Value , known bool , diags tfdiags . Diagnostics ) {
2019-06-12 10:07:32 -05:00
if expr == nil {
2019-07-25 10:51:55 -05:00
return nil , true , nil
2019-06-12 10:07:32 -05:00
}
forEachVal , forEachDiags := ctx . EvaluateExpr ( expr , cty . DynamicPseudoType , nil )
diags = diags . Append ( forEachDiags )
if diags . HasErrors ( ) {
2019-07-25 10:51:55 -05:00
return nil , true , diags
2019-06-12 10:07:32 -05:00
}
2019-07-25 10:51:55 -05:00
switch {
case forEachVal . IsNull ( ) :
2019-06-12 10:07:32 -05:00
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid for_each argument" ,
Detail : ` The given "for_each" argument value is unsuitable: the given "for_each" argument value is null. A map, or set of strings is allowed. ` ,
Subject : expr . Range ( ) . Ptr ( ) ,
} )
2019-07-25 10:51:55 -05:00
return nil , true , diags
2019-08-28 13:02:11 -05:00
case ! forEachVal . IsKnown ( ) :
2019-07-25 10:51:55 -05:00
return map [ string ] cty . Value { } , false , diags
2019-06-12 10:07:32 -05:00
}
2019-08-01 09:33:46 -05:00
if ! forEachVal . CanIterateElements ( ) || forEachVal . Type ( ) . IsListType ( ) || forEachVal . Type ( ) . IsTupleType ( ) {
2019-06-12 10:07:32 -05:00
diags = diags . Append ( & hcl . Diagnostic {
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. ` , forEachVal . Type ( ) . FriendlyName ( ) ) ,
Subject : expr . Range ( ) . Ptr ( ) ,
} )
2019-07-25 10:51:55 -05:00
return nil , true , diags
2019-06-12 10:07:32 -05:00
}
2019-09-10 09:37:54 -05:00
// If the map is empty ({}), return an empty map, because cty will return nil when representing {} AsValueMap
// This also covers an empty set (toset([]))
if forEachVal . LengthInt ( ) == 0 {
return map [ string ] cty . Value { } , true , diags
}
2019-06-12 10:07:32 -05:00
if forEachVal . Type ( ) . IsSetType ( ) {
if forEachVal . Type ( ) . 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 ( ) ,
} )
2019-07-25 10:51:55 -05:00
return nil , true , diags
2019-06-12 10:07:32 -05:00
}
2019-08-28 13:02:11 -05:00
2019-08-28 14:13:36 -05:00
// A set may contain unknown values that must be
// discovered by checking with IsWhollyKnown (which iterates through the
// structure), while for maps in cty, keys can never be unknown or null,
// thus the earlier IsKnown check suffices for maps
2019-08-28 13:02:11 -05:00
if ! forEachVal . IsWhollyKnown ( ) {
return map [ string ] cty . Value { } , false , diags
}
2019-06-12 10:07:32 -05:00
}
2019-07-25 10:51:55 -05:00
return forEachVal . AsValueMap ( ) , true , nil
2019-06-12 10:07:32 -05:00
}