mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-18 18:29:44 -05:00
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).
This commit is contained in:
parent
f8ae45cfc8
commit
eaf225a871
4 changed files with 47 additions and 1 deletions
|
|
@ -52,6 +52,17 @@ var templateFunctions = collections.NewSetCmp[string](
|
|||
// Functions returns the set of functions that should be used to when evaluating
|
||||
// expressions in the receiving scope.
|
||||
func (s *Scope) Functions() map[string]function.Function {
|
||||
// For backwards compatibility, filesystem functions are allowed to return
|
||||
// inconsistent results when called from within a provider configuration, so
|
||||
// here we override the checks with a noop wrapper. This misbehavior 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.
|
||||
immutableResults := immutableResults
|
||||
if s.ForProvider {
|
||||
immutableResults = filesystemNoopWrapper
|
||||
}
|
||||
|
||||
s.funcsLock.Lock()
|
||||
if s.funcs == nil {
|
||||
s.funcs = baseFunctions(s.BaseDir)
|
||||
|
|
@ -468,6 +479,10 @@ func immutableResults(name string, priorResults *FunctionResults) func(fn functi
|
|||
}
|
||||
}
|
||||
|
||||
func filesystemNoopWrapper(name string, priorResults *FunctionResults) func(fn function.ImplFunc) function.ImplFunc {
|
||||
return noopWrapper
|
||||
}
|
||||
|
||||
func noopWrapper(fn function.ImplFunc) function.ImplFunc {
|
||||
return fn
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,12 @@ type Scope struct {
|
|||
// PlanTimestamp is a timestamp representing when the plan was made. It will
|
||||
// either have been generated during this operation or read from the plan.
|
||||
PlanTimestamp time.Time
|
||||
|
||||
// ForProvider indicates a special case where a provider configuration is
|
||||
// being evaluated and can tolerate inconsistent results which are not
|
||||
// marked as ephemeral.
|
||||
// FIXME: plan to officially deprecate this workaround.
|
||||
ForProvider bool
|
||||
}
|
||||
|
||||
// SetActiveExperiments allows a caller to declare that a set of experiments
|
||||
|
|
|
|||
|
|
@ -329,6 +329,23 @@ func (ctx *BuiltinEvalContext) EvaluateBlock(body hcl.Body, schema *configschema
|
|||
return val, body, diags
|
||||
}
|
||||
|
||||
// EvaluateBlockForProvider is a workaround to allow providers to access a more
|
||||
// ephemeral context, where filesystem functions can return inconsistent
|
||||
// results. Prior to ephemeral values, some configurations were using this
|
||||
// loophole to inject different credentials between plan and apply. This
|
||||
// exception is not added to the EvalContext interface, so in order to access
|
||||
// this workaround the context type must be asserted as BuiltinEvalContext.
|
||||
func (ctx *BuiltinEvalContext) EvaluateBlockForProvider(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, keyData InstanceKeyEvalData) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
scope := ctx.EvaluationScope(self, nil, keyData)
|
||||
scope.ForProvider = true
|
||||
body, evalDiags := scope.ExpandBlock(body, schema)
|
||||
diags = diags.Append(evalDiags)
|
||||
val, evalDiags := scope.EvalBlock(body, schema)
|
||||
diags = diags.Append(evalDiags)
|
||||
return val, body, diags
|
||||
}
|
||||
|
||||
func (ctx *BuiltinEvalContext) EvaluateExpr(expr hcl.Expression, wantType cty.Type, self addrs.Referenceable) (cty.Value, tfdiags.Diagnostics) {
|
||||
scope := ctx.EvaluationScope(self, nil, EvalDataForNoInstanceKey)
|
||||
return scope.EvalExpr(expr, wantType)
|
||||
|
|
|
|||
|
|
@ -111,8 +111,16 @@ func (n *NodeApplyableProvider) ConfigureProvider(ctx EvalContext, provider prov
|
|||
return diags
|
||||
}
|
||||
|
||||
// BuiltinEvalContext contains a workaround for providers to allow
|
||||
// inconsistent filesystem function results, which can be accepted due to
|
||||
// the ephemeral nature of a provider configuration.
|
||||
eval := ctx.EvaluateBlock
|
||||
if ctx, ok := ctx.(*BuiltinEvalContext); ok {
|
||||
eval = ctx.EvaluateBlockForProvider
|
||||
}
|
||||
|
||||
configSchema := resp.Provider.Body
|
||||
configVal, configBody, evalDiags := ctx.EvaluateBlock(configBody, configSchema, nil, EvalDataForNoInstanceKey)
|
||||
configVal, configBody, evalDiags := eval(configBody, configSchema, nil, EvalDataForNoInstanceKey)
|
||||
diags = diags.Append(evalDiags)
|
||||
if evalDiags.HasErrors() {
|
||||
if config == nil {
|
||||
|
|
|
|||
Loading…
Reference in a new issue