diff --git a/internal/lang/eval.go b/internal/lang/eval.go index b1481a3641..1ca8fe7829 100644 --- a/internal/lang/eval.go +++ b/internal/lang/eval.go @@ -75,7 +75,7 @@ func (s *Scope) EvalBlock(body hcl.Body, schema *configschema.Block) (cty.Value, body = blocktoattr.FixUpBlockAttrs(body, schema) val, evalDiags := hcldec.Decode(body, spec, ctx) - diags = diags.Append(checkForUnknownFunctionDiags(evalDiags)) + diags = diags.Append(checkForUnknownFunctionDiags(evalDiags, s.IgnoreUnknownProviderFunctions)) return val, diags } @@ -153,7 +153,7 @@ func (s *Scope) EvalSelfBlock(body hcl.Body, self cty.Value, schema *configschem } val, decDiags := hcldec.Decode(body, schema.DecoderSpec(), ctx) - diags = diags.Append(checkForUnknownFunctionDiags(decDiags)) + diags = diags.Append(checkForUnknownFunctionDiags(decDiags, s.IgnoreUnknownProviderFunctions)) return val, diags } @@ -179,7 +179,7 @@ func (s *Scope) EvalExpr(expr hcl.Expression, wantType cty.Type) (cty.Value, tfd } val, evalDiags := expr.Value(ctx) - diags = diags.Append(checkForUnknownFunctionDiags(evalDiags)) + diags = diags.Append(checkForUnknownFunctionDiags(evalDiags, s.IgnoreUnknownProviderFunctions)) if wantType != cty.DynamicPseudoType { var convErr error @@ -524,16 +524,22 @@ func normalizeRefValue(val cty.Value, diags tfdiags.Diagnostics) (cty.Value, tfd // namespace. The generic unknown function diagnostic from hcl does not direct // the user on how to remedy the situation in Terraform, and we can give more // useful information in a few Terraform specific cases here. -func checkForUnknownFunctionDiags(diags hcl.Diagnostics) hcl.Diagnostics { +func checkForUnknownFunctionDiags(diags hcl.Diagnostics, ignoreUnknownProviderFunctions bool) hcl.Diagnostics { + var filteredDiags hcl.Diagnostics for _, d := range diags { extra, ok := hcl.DiagnosticExtra[hclsyntax.FunctionCallUnknownDiagExtra](d) if !ok { + filteredDiags = filteredDiags.Append(d) // we always want to include unrelated diags continue } + name := extra.CalledFunctionName() namespace := extra.CalledFunctionNamespace() namespaceParts := strings.Split(namespace, "::") if len(namespaceParts) < 2 { + // we always include diags for unknown regular function calls + filteredDiags = filteredDiags.Append(d) + // no namespace (namespace includes ::, so will have at least 2 // parts), but check if there is a matching name in a provider // namspace. @@ -550,6 +556,17 @@ func checkForUnknownFunctionDiags(diags hcl.Diagnostics) hcl.Diagnostics { continue } + // We might run into provider-defined functions during init. We can't filter out all + // places where they might be used, so we'll just filter out the diagnostics for the + // unknown function calls if we're configured to ignore them. This means that the user will + // just get an unknown value result for any provider function calls, which is fine because + // we won't have any provider functions available at this point anyway. + if ignoreUnknownProviderFunctions { + continue + } else { + filteredDiags = filteredDiags.Append(d) + } + // the diagnostic isn't really shared with anything, and copying would // still retain the internal pointers, so we're going to modify the // diagnostic in-place if we want to change the output. Log the original @@ -598,5 +615,5 @@ func checkForUnknownFunctionDiags(diags hcl.Diagnostics) hcl.Diagnostics { d.Detail = fmt.Sprintf(`There is no function named "%s%s". Ensure that provider name %q is declared in this module's required_providers block, and that this provider offers a function named %q.`, namespace, name, namespaceParts[1], name) } - return diags + return filteredDiags } diff --git a/internal/lang/scope.go b/internal/lang/scope.go index 4416e65f02..41334a51f3 100644 --- a/internal/lang/scope.go +++ b/internal/lang/scope.go @@ -84,6 +84,12 @@ type Scope struct { // marked as ephemeral. // FIXME: plan to officially deprecate this workaround. ForProvider bool + + // IgnoreUnknownProviderFunctions can be set to true to request that any unknown + // functions produce unknown value results rather than an error. This is + // important during a plan phase to avoid generating errors for functions that + // are only available in provider versions that have not yet been selected. + IgnoreUnknownProviderFunctions bool } // SetActiveExperiments allows a caller to declare that a set of experiments diff --git a/internal/terraform/evaluate.go b/internal/terraform/evaluate.go index ee1374427a..5e4b90c255 100644 --- a/internal/terraform/evaluate.go +++ b/internal/terraform/evaluate.go @@ -93,15 +93,16 @@ type Evaluator struct { // address. func (e *Evaluator) Scope(data lang.Data, self addrs.Referenceable, source addrs.Referenceable, extFuncs lang.ExternalFuncs) *lang.Scope { return &lang.Scope{ - Data: data, - ParseRef: addrs.ParseRef, - SelfAddr: self, - SourceAddr: source, - PureOnly: e.Operation != walkApply && e.Operation != walkDestroy && e.Operation != walkEval, - BaseDir: ".", // Always current working directory for now. - PlanTimestamp: e.PlanTimestamp, - ExternalFuncs: extFuncs, - FunctionResults: e.FunctionResults, + Data: data, + ParseRef: addrs.ParseRef, + SelfAddr: self, + SourceAddr: source, + PureOnly: e.Operation != walkApply && e.Operation != walkDestroy && e.Operation != walkEval, + BaseDir: ".", // Always current working directory for now. + PlanTimestamp: e.PlanTimestamp, + ExternalFuncs: extFuncs, + FunctionResults: e.FunctionResults, + IgnoreUnknownProviderFunctions: e.Operation == walkInit, } }