diff --git a/terraform/context.go b/terraform/context.go index a91c05325..2dbbc4663 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -416,7 +416,7 @@ func (c *Context) Eval(path addrs.ModuleInstance) (*lang.Scope, tfdiags.Diagnost // caches its contexts, so we should get hold of the context that was // previously used for evaluation here, unless we skipped walking. evalCtx := walker.EnterPath(path) - return evalCtx.EvaluationScope(nil, addrs.NoKey), diags + return evalCtx.EvaluationScope(nil, EvalDataForNoInstanceKey), diags } // Interpolater is no longer used. Use Evaluator instead. diff --git a/terraform/eval_apply.go b/terraform/eval_apply.go index 6bd1e611b..4108ab9de 100644 --- a/terraform/eval_apply.go +++ b/terraform/eval_apply.go @@ -313,13 +313,15 @@ func (n *EvalApplyProvisioners) apply(ctx EvalContext, provs []*configs.Provisio provisioner := ctx.Provisioner(prov.Type) schema := ctx.ProvisionerSchema(prov.Type) + keyData := EvalDataForInstanceKey(instanceAddr.Key) + // Evaluate the main provisioner configuration. - config, _, configDiags := ctx.EvaluateBlock(prov.Config, schema, instanceAddr, instanceAddr.Key) + config, _, configDiags := ctx.EvaluateBlock(prov.Config, schema, instanceAddr, keyData) diags = diags.Append(configDiags) // A provisioner may not have a connection block if prov.Connection != nil { - connInfo, _, connInfoDiags := ctx.EvaluateBlock(prov.Connection.Config, connectionBlockSupersetSchema, instanceAddr, instanceAddr.Key) + connInfo, _, connInfoDiags := ctx.EvaluateBlock(prov.Connection.Config, connectionBlockSupersetSchema, instanceAddr, keyData) diags = diags.Append(connInfoDiags) if configDiags.HasErrors() || connInfoDiags.HasErrors() { diff --git a/terraform/eval_context.go b/terraform/eval_context.go index 8f0a10e2f..660d6bb79 100644 --- a/terraform/eval_context.go +++ b/terraform/eval_context.go @@ -100,7 +100,7 @@ type EvalContext interface { // "dynamic" blocks replaced with zero or more static blocks. This can be // used to extract correct source location information about attributes of // the returned object value. - EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, key addrs.InstanceKey) (cty.Value, hcl.Body, tfdiags.Diagnostics) + EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, keyData InstanceKeyEvalData) (cty.Value, hcl.Body, tfdiags.Diagnostics) // EvaluateExpr takes the given HCL expression and evaluates it to produce // a value. @@ -112,7 +112,7 @@ type EvalContext interface { // EvaluationScope returns a scope that can be used to evaluate reference // addresses in this context. - EvaluationScope(self addrs.Referenceable, key addrs.InstanceKey) *lang.Scope + EvaluationScope(self addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope // SetModuleCallArguments defines values for the variables of a particular // child module call. diff --git a/terraform/eval_context_builtin.go b/terraform/eval_context_builtin.go index 4d0cc590b..cbbe9b377 100644 --- a/terraform/eval_context_builtin.go +++ b/terraform/eval_context_builtin.go @@ -271,9 +271,9 @@ func (ctx *BuiltinEvalContext) CloseProvisioner(n string) error { return nil } -func (ctx *BuiltinEvalContext) EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, key addrs.InstanceKey) (cty.Value, hcl.Body, tfdiags.Diagnostics) { +func (ctx *BuiltinEvalContext) EvaluateBlock(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, key) + scope := ctx.EvaluationScope(self, keyData) body, evalDiags := scope.ExpandBlock(body, schema) diags = diags.Append(evalDiags) val, evalDiags := scope.EvalBlock(body, schema) @@ -282,15 +282,15 @@ func (ctx *BuiltinEvalContext) EvaluateBlock(body hcl.Body, schema *configschema } func (ctx *BuiltinEvalContext) EvaluateExpr(expr hcl.Expression, wantType cty.Type, self addrs.Referenceable) (cty.Value, tfdiags.Diagnostics) { - scope := ctx.EvaluationScope(self, addrs.NoKey) + scope := ctx.EvaluationScope(self, EvalDataForNoInstanceKey) return scope.EvalExpr(expr, wantType) } -func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, key addrs.InstanceKey) *lang.Scope { +func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope { data := &evaluationStateData{ - Evaluator: ctx.Evaluator, - ModulePath: ctx.PathValue, - InstanceKey: key, + Evaluator: ctx.Evaluator, + ModulePath: ctx.PathValue, + InstanceKeyData: keyData, } return ctx.Evaluator.Scope(data, self) } diff --git a/terraform/eval_context_mock.go b/terraform/eval_context_mock.go index 49dbc688d..c31ad62a0 100644 --- a/terraform/eval_context_mock.go +++ b/terraform/eval_context_mock.go @@ -80,12 +80,12 @@ type MockEvalContext struct { EvaluateBlockBody hcl.Body EvaluateBlockSchema *configschema.Block EvaluateBlockSelf addrs.Referenceable - EvaluateBlockKey addrs.InstanceKey + EvaluateBlockKeyData InstanceKeyEvalData EvaluateBlockResultFunc func( body hcl.Body, schema *configschema.Block, self addrs.Referenceable, - key addrs.InstanceKey, + keyData InstanceKeyEvalData, ) (cty.Value, hcl.Body, tfdiags.Diagnostics) // overrides the other values below, if set EvaluateBlockResult cty.Value EvaluateBlockExpandedBody hcl.Body @@ -103,10 +103,10 @@ type MockEvalContext struct { EvaluateExprResult cty.Value EvaluateExprDiags tfdiags.Diagnostics - EvaluationScopeCalled bool - EvaluationScopeSelf addrs.Referenceable - EvaluationScopeKey addrs.InstanceKey - EvaluationScopeScope *lang.Scope + EvaluationScopeCalled bool + EvaluationScopeSelf addrs.Referenceable + EvaluationScopeKeyData InstanceKeyEvalData + EvaluationScopeScope *lang.Scope InterpolateCalled bool InterpolateConfig *config.RawConfig @@ -228,14 +228,14 @@ func (c *MockEvalContext) CloseProvisioner(n string) error { return nil } -func (c *MockEvalContext) EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, key addrs.InstanceKey) (cty.Value, hcl.Body, tfdiags.Diagnostics) { +func (c *MockEvalContext) EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, keyData InstanceKeyEvalData) (cty.Value, hcl.Body, tfdiags.Diagnostics) { c.EvaluateBlockCalled = true c.EvaluateBlockBody = body c.EvaluateBlockSchema = schema c.EvaluateBlockSelf = self - c.EvaluateBlockKey = key + c.EvaluateBlockKeyData = keyData if c.EvaluateBlockResultFunc != nil { - return c.EvaluateBlockResultFunc(body, schema, self, key) + return c.EvaluateBlockResultFunc(body, schema, self, keyData) } return c.EvaluateBlockResult, c.EvaluateBlockExpandedBody, c.EvaluateBlockDiags } @@ -261,7 +261,7 @@ func (c *MockEvalContext) EvaluateExpr(expr hcl.Expression, wantType cty.Type, s // This function overwrites any existing functions installed in fields // EvaluateBlockResultFunc and EvaluateExprResultFunc. func (c *MockEvalContext) installSimpleEval() { - c.EvaluateBlockResultFunc = func(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, key addrs.InstanceKey) (cty.Value, hcl.Body, tfdiags.Diagnostics) { + c.EvaluateBlockResultFunc = func(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, keyData InstanceKeyEvalData) (cty.Value, hcl.Body, tfdiags.Diagnostics) { if scope := c.EvaluationScopeScope; scope != nil { // Fully-functional codepath. var diags tfdiags.Diagnostics @@ -304,10 +304,10 @@ func (c *MockEvalContext) installSimpleEval() { } } -func (c *MockEvalContext) EvaluationScope(self addrs.Referenceable, key addrs.InstanceKey) *lang.Scope { +func (c *MockEvalContext) EvaluationScope(self addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope { c.EvaluationScopeCalled = true c.EvaluationScopeSelf = self - c.EvaluationScopeKey = key + c.EvaluationScopeKeyData = keyData return c.EvaluationScopeScope } diff --git a/terraform/eval_diff.go b/terraform/eval_diff.go index 891e7a3d6..ced760df2 100644 --- a/terraform/eval_diff.go +++ b/terraform/eval_diff.go @@ -133,7 +133,8 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) { // Should be caught during validation, so we don't bother with a pretty error here return nil, fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type) } - configVal, _, configDiags := ctx.EvaluateBlock(config.Config, schema, nil, n.Addr.Key) + keyData := EvalDataForInstanceKey(n.Addr.Key) + configVal, _, configDiags := ctx.EvaluateBlock(config.Config, schema, nil, keyData) diags = diags.Append(configDiags) if configDiags.HasErrors() { return nil, diags.Err() diff --git a/terraform/eval_lang.go b/terraform/eval_lang.go index d309622ed..f99e4c4bf 100644 --- a/terraform/eval_lang.go +++ b/terraform/eval_lang.go @@ -26,7 +26,7 @@ type EvalConfigBlock struct { } func (n *EvalConfigBlock) Eval(ctx EvalContext) (interface{}, error) { - val, body, diags := ctx.EvaluateBlock(*n.Config, n.Schema, n.SelfAddr, addrs.NoKey) + val, body, diags := ctx.EvaluateBlock(*n.Config, n.Schema, n.SelfAddr, EvalDataForNoInstanceKey) if diags.HasErrors() && n.ContinueOnErr { log.Printf("[WARN] Block evaluation failed: %s", diags.Err()) return nil, EvalEarlyExitError{} diff --git a/terraform/eval_provider.go b/terraform/eval_provider.go index be695b2f5..59d5d3b42 100644 --- a/terraform/eval_provider.go +++ b/terraform/eval_provider.go @@ -74,7 +74,7 @@ func (n *EvalConfigProvider) Eval(ctx EvalContext) (interface{}, error) { } configSchema := schema.Provider - configVal, configBody, evalDiags := ctx.EvaluateBlock(configBody, configSchema, nil, addrs.NoKey) + configVal, configBody, evalDiags := ctx.EvaluateBlock(configBody, configSchema, nil, EvalDataForNoInstanceKey) diags = diags.Append(evalDiags) if evalDiags.HasErrors() { return nil, diags.NonFatalErr() diff --git a/terraform/eval_read_data.go b/terraform/eval_read_data.go index ec50bfe82..c0c37d0e0 100644 --- a/terraform/eval_read_data.go +++ b/terraform/eval_read_data.go @@ -63,8 +63,10 @@ func (n *EvalReadDataDiff) Eval(ctx EvalContext) (interface{}, error) { return nil, fmt.Errorf("provider does not support data source %q", n.Addr.Resource.Type) } + keyData := EvalDataForInstanceKey(n.Addr.Key) + var configDiags tfdiags.Diagnostics - configVal, _, configDiags = ctx.EvaluateBlock(config.Config, schema, nil, n.Addr.Key) + configVal, _, configDiags = ctx.EvaluateBlock(config.Config, schema, nil, keyData) diags = diags.Append(configDiags) if configDiags.HasErrors() { return nil, diags.Err() diff --git a/terraform/eval_validate.go b/terraform/eval_validate.go index 88712af97..b7b73e741 100644 --- a/terraform/eval_validate.go +++ b/terraform/eval_validate.go @@ -92,7 +92,7 @@ func (n *EvalValidateProvider) Eval(ctx EvalContext) (interface{}, error) { configSchema = &configschema.Block{} } - configVal, configBody, evalDiags := ctx.EvaluateBlock(configBody, configSchema, nil, addrs.NoKey) + configVal, configBody, evalDiags := ctx.EvaluateBlock(configBody, configSchema, nil, EvalDataForNoInstanceKey) diags = diags.Append(evalDiags) if evalDiags.HasErrors() { return nil, diags.NonFatalErr() @@ -142,7 +142,9 @@ func (n *EvalValidateProvisioner) Eval(ctx EvalContext) (interface{}, error) { { // Validate the provisioner's own config first - configVal, _, configDiags := ctx.EvaluateBlock(config.Config, schema, n.ResourceAddr, n.ResourceAddr.Key) + keyData := EvalDataForInstanceKey(n.ResourceAddr.Key) + + configVal, _, configDiags := ctx.EvaluateBlock(config.Config, schema, n.ResourceAddr, keyData) diags = diags.Append(configDiags) if configDiags.HasErrors() { return nil, diags.Err() @@ -197,10 +199,12 @@ func (n *EvalValidateProvisioner) validateConnConfig(ctx EvalContext, config *co return diags } + keyData := EvalDataForInstanceKey(n.ResourceAddr.Key) + // We evaluate here just by evaluating the block and returning any // diagnostics we get, since evaluation alone is enough to check for // extraneous arguments and incorrectly-typed arguments. - _, _, configDiags := ctx.EvaluateBlock(config.Config, connectionBlockSupersetSchema, self, n.ResourceAddr.Key) + _, _, configDiags := ctx.EvaluateBlock(config.Config, connectionBlockSupersetSchema, self, keyData) diags = diags.Append(configDiags) return diags @@ -361,7 +365,9 @@ func (n *EvalValidateResource) Eval(ctx EvalContext) (interface{}, error) { return nil, diags.Err() } - configVal, _, valDiags := ctx.EvaluateBlock(cfg.Config, schema, nil, n.Addr.Key) + keyData := EvalDataForInstanceKey(n.Addr.Key) + + configVal, _, valDiags := ctx.EvaluateBlock(cfg.Config, schema, nil, keyData) diags = diags.Append(valDiags) if valDiags.HasErrors() { return nil, diags.Err() @@ -388,7 +394,9 @@ func (n *EvalValidateResource) Eval(ctx EvalContext) (interface{}, error) { return nil, diags.Err() } - configVal, _, valDiags := ctx.EvaluateBlock(cfg.Config, schema, nil, n.Addr.Key) + keyData := EvalDataForInstanceKey(n.Addr.Key) + + configVal, _, valDiags := ctx.EvaluateBlock(cfg.Config, schema, nil, keyData) diags = diags.Append(valDiags) if valDiags.HasErrors() { return nil, diags.Err() diff --git a/terraform/evaluate.go b/terraform/evaluate.go index ce622b53c..32064b8d6 100644 --- a/terraform/evaluate.go +++ b/terraform/evaluate.go @@ -83,11 +83,58 @@ type evaluationStateData struct { // that references will be resolved relative to. ModulePath addrs.ModuleInstance - // InstanceKey is the instance key for the object being evaluated, if any. - // Set to addrs.NoKey if no object repetition is in progress. - InstanceKey addrs.InstanceKey + // InstanceKeyData describes the values, if any, that are accessible due + // to repetition of a containing object using "count" or "for_each" + // arguments. (It is _not_ used for the for_each inside "dynamic" blocks, + // since the user specifies in that case which variable name to locally + // shadow.) + InstanceKeyData InstanceKeyEvalData } +// InstanceKeyEvalData is used during evaluation to specify which values, +// if any, should be produced for count.index, each.key, and each.value. +type InstanceKeyEvalData struct { + // CountIndex is the value for count.index, or cty.NilVal if evaluating + // in a context where the "count" argument is not active. + // + // For correct operation, this should always be of type cty.Number if not + // nil. + CountIndex cty.Value + + // EachKey and EachValue are the values for each.key and each.value + // respectively, or cty.NilVal if evaluating in a context where the + // "for_each" argument is not active. These must either both be set + // or neither set. + // + // For correct operation, EachKey must always be either of type cty.String + // or cty.Number if not nil. + EachKey, EachValue cty.Value +} + +// EvalDataForInstanceKey constructs a suitable InstanceKeyEvalData for +// evaluating in a context that has the given instance key. +func EvalDataForInstanceKey(key addrs.InstanceKey) InstanceKeyEvalData { + // At the moment we don't actually implement for_each, so we only + // ever populate CountIndex. + // (When we implement for_each later we may need to reorganize this some, + // so that we can resolve the ambiguity that an int key may either be + // a count.index or an each.key where for_each is over a list.) + + var countIdx cty.Value + if intKey, ok := key.(addrs.IntKey); ok { + countIdx = cty.NumberIntVal(int64(intKey)) + } + + return InstanceKeyEvalData{ + CountIndex: countIdx, + } +} + +// EvalDataForNoInstanceKey is a value of InstanceKeyData that sets no instance +// key values at all, suitable for use in contexts where no keyed instance +// is relevant. +var EvalDataForNoInstanceKey = InstanceKeyEvalData{} + // evaluationStateData must implement lang.Data var _ lang.Data = (*evaluationStateData)(nil) @@ -96,12 +143,8 @@ func (d *evaluationStateData) GetCountAttr(addr addrs.CountAttr, rng tfdiags.Sou switch addr.Name { case "index": - key := d.InstanceKey - // key might not be set at all (addrs.NoKey) or it might be a string - // if we're actually in a for_each block, so we'll check first and - // produce a nice error if this is being used in the wrong context. - intKey, ok := key.(addrs.IntKey) - if !ok { + idxVal := d.InstanceKeyData.CountIndex + if idxVal == cty.NilVal { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: `Reference to "count" in non-counted context`, @@ -110,7 +153,7 @@ func (d *evaluationStateData) GetCountAttr(addr addrs.CountAttr, rng tfdiags.Sou }) return cty.UnknownVal(cty.Number), diags } - return cty.NumberIntVal(int64(intKey)), diags + return idxVal, diags default: diags = diags.Append(&hcl.Diagnostic{