Skip to content

Commit eaf225a

Browse files
committed
allow inconsistent function results for providers
Due to the inherently ephemeral nature of provider configuration, inconsistent function results were tolerated while evaluating provider config. This loophole was found to be used by a number of configurations, which took advantage of it to create the equivalent of ephemeral values before they formally existed in the language. In order to work around this, we create a special evaluation scope just for providers, allowing us to override the results check for filesystem functions. I've opted to not further clutter the EvalContext interface since this is intended to be a temporary workaround, and does not contribute to the testing of that interface (the interface is solely for internal unit tests anyway).
1 parent f8ae45c commit eaf225a

File tree

4 files changed

+47
-1
lines changed

4 files changed

+47
-1
lines changed

internal/lang/functions.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ var templateFunctions = collections.NewSetCmp[string](
5252
// Functions returns the set of functions that should be used to when evaluating
5353
// expressions in the receiving scope.
5454
func (s *Scope) Functions() map[string]function.Function {
55+
// For backwards compatibility, filesystem functions are allowed to return
56+
// inconsistent results when called from within a provider configuration, so
57+
// here we override the checks with a noop wrapper. This misbehavior was
58+
// found to be used by a number of configurations, which took advantage of
59+
// it to create the equivalent of ephemeral values before they formally
60+
// existed in the language.
61+
immutableResults := immutableResults
62+
if s.ForProvider {
63+
immutableResults = filesystemNoopWrapper
64+
}
65+
5566
s.funcsLock.Lock()
5667
if s.funcs == nil {
5768
s.funcs = baseFunctions(s.BaseDir)
@@ -468,6 +479,10 @@ func immutableResults(name string, priorResults *FunctionResults) func(fn functi
468479
}
469480
}
470481

482+
func filesystemNoopWrapper(name string, priorResults *FunctionResults) func(fn function.ImplFunc) function.ImplFunc {
483+
return noopWrapper
484+
}
485+
471486
func noopWrapper(fn function.ImplFunc) function.ImplFunc {
472487
return fn
473488
}

internal/lang/scope.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ type Scope struct {
7878
// PlanTimestamp is a timestamp representing when the plan was made. It will
7979
// either have been generated during this operation or read from the plan.
8080
PlanTimestamp time.Time
81+
82+
// ForProvider indicates a special case where a provider configuration is
83+
// being evaluated and can tolerate inconsistent results which are not
84+
// marked as ephemeral.
85+
// FIXME: plan to officially deprecate this workaround.
86+
ForProvider bool
8187
}
8288

8389
// SetActiveExperiments allows a caller to declare that a set of experiments

internal/terraform/eval_context_builtin.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,23 @@ func (ctx *BuiltinEvalContext) EvaluateBlock(body hcl.Body, schema *configschema
329329
return val, body, diags
330330
}
331331

332+
// EvaluateBlockForProvider is a workaround to allow providers to access a more
333+
// ephemeral context, where filesystem functions can return inconsistent
334+
// results. Prior to ephemeral values, some configurations were using this
335+
// loophole to inject different credentials between plan and apply. This
336+
// exception is not added to the EvalContext interface, so in order to access
337+
// this workaround the context type must be asserted as BuiltinEvalContext.
338+
func (ctx *BuiltinEvalContext) EvaluateBlockForProvider(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, keyData InstanceKeyEvalData) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
339+
var diags tfdiags.Diagnostics
340+
scope := ctx.EvaluationScope(self, nil, keyData)
341+
scope.ForProvider = true
342+
body, evalDiags := scope.ExpandBlock(body, schema)
343+
diags = diags.Append(evalDiags)
344+
val, evalDiags := scope.EvalBlock(body, schema)
345+
diags = diags.Append(evalDiags)
346+
return val, body, diags
347+
}
348+
332349
func (ctx *BuiltinEvalContext) EvaluateExpr(expr hcl.Expression, wantType cty.Type, self addrs.Referenceable) (cty.Value, tfdiags.Diagnostics) {
333350
scope := ctx.EvaluationScope(self, nil, EvalDataForNoInstanceKey)
334351
return scope.EvalExpr(expr, wantType)

internal/terraform/node_provider.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,16 @@ func (n *NodeApplyableProvider) ConfigureProvider(ctx EvalContext, provider prov
111111
return diags
112112
}
113113

114+
// BuiltinEvalContext contains a workaround for providers to allow
115+
// inconsistent filesystem function results, which can be accepted due to
116+
// the ephemeral nature of a provider configuration.
117+
eval := ctx.EvaluateBlock
118+
if ctx, ok := ctx.(*BuiltinEvalContext); ok {
119+
eval = ctx.EvaluateBlockForProvider
120+
}
121+
114122
configSchema := resp.Provider.Body
115-
configVal, configBody, evalDiags := ctx.EvaluateBlock(configBody, configSchema, nil, EvalDataForNoInstanceKey)
123+
configVal, configBody, evalDiags := eval(configBody, configSchema, nil, EvalDataForNoInstanceKey)
116124
diags = diags.Append(evalDiags)
117125
if evalDiags.HasErrors() {
118126
if config == nil {

0 commit comments

Comments
 (0)