Skip to content

Commit 5ce9161

Browse files
authored
Merge pull request #218 from hashicorp/cherry-pick-22846
Cherry pick hashicorp/terraform#22846: Always evaluate resources in their entirety
2 parents 8ca46a8 + fae8d2d commit 5ce9161

File tree

10 files changed

+193
-232
lines changed

10 files changed

+193
-232
lines changed

internal/addrs/parse_ref.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ func parseResourceRef(mode ResourceMode, startRange hcl.Range, traversal hcl.Tra
290290
// of the resource, but we don't have enough context here to decide
291291
// so we'll let the caller resolve that ambiguity.
292292
return &Reference{
293-
Subject: resourceInstAddr,
293+
Subject: resourceAddr,
294294
SourceRange: tfdiags.SourceRangeFromHCL(rng),
295295
}, diags
296296
}

internal/addrs/parse_ref_test.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,10 @@ func TestParseRef(t *testing.T) {
114114
{
115115
`data.external.foo`,
116116
&Reference{
117-
Subject: ResourceInstance{
118-
Resource: Resource{
119-
Mode: DataResourceMode,
120-
Type: "external",
121-
Name: "foo",
122-
},
117+
Subject: Resource{
118+
Mode: DataResourceMode,
119+
Type: "external",
120+
Name: "foo",
123121
},
124122
SourceRange: tfdiags.SourceRange{
125123
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
@@ -592,12 +590,10 @@ func TestParseRef(t *testing.T) {
592590
{
593591
`boop_instance.foo`,
594592
&Reference{
595-
Subject: ResourceInstance{
596-
Resource: Resource{
597-
Mode: ManagedResourceMode,
598-
Type: "boop_instance",
599-
Name: "foo",
600-
},
593+
Subject: Resource{
594+
Mode: ManagedResourceMode,
595+
Type: "boop_instance",
596+
Name: "foo",
601597
},
602598
SourceRange: tfdiags.SourceRange{
603599
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},

internal/lang/data.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type Data interface {
2424

2525
GetCountAttr(addrs.CountAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
2626
GetForEachAttr(addrs.ForEachAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
27-
GetResourceInstance(addrs.ResourceInstance, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
27+
GetResource(addrs.Resource, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
2828
GetLocalValue(addrs.LocalValue, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
2929
GetModuleInstance(addrs.ModuleCallInstance, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)
3030
GetModuleInstanceOutput(addrs.ModuleCallOutput, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)

internal/lang/data_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ import (
77
)
88

99
type dataForTests struct {
10-
CountAttrs map[string]cty.Value
11-
ForEachAttrs map[string]cty.Value
12-
ResourceInstances map[string]cty.Value
13-
LocalValues map[string]cty.Value
14-
Modules map[string]cty.Value
15-
PathAttrs map[string]cty.Value
16-
TerraformAttrs map[string]cty.Value
17-
InputVariables map[string]cty.Value
10+
CountAttrs map[string]cty.Value
11+
ForEachAttrs map[string]cty.Value
12+
Resources map[string]cty.Value
13+
LocalValues map[string]cty.Value
14+
Modules map[string]cty.Value
15+
PathAttrs map[string]cty.Value
16+
TerraformAttrs map[string]cty.Value
17+
InputVariables map[string]cty.Value
1818
}
1919

2020
var _ Data = &dataForTests{}
@@ -31,8 +31,8 @@ func (d *dataForTests) GetForEachAttr(addr addrs.ForEachAttr, rng tfdiags.Source
3131
return d.ForEachAttrs[addr.Name], nil
3232
}
3333

34-
func (d *dataForTests) GetResourceInstance(addr addrs.ResourceInstance, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
35-
return d.ResourceInstances[addr.String()], nil
34+
func (d *dataForTests) GetResource(addr addrs.Resource, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
35+
return d.Resources[addr.String()], nil
3636
}
3737

3838
func (d *dataForTests) GetInputVariable(addr addrs.InputVariable, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {

internal/lang/eval.go

Lines changed: 40 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,8 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl
194194
// it, since that allows us to gather a full set of any errors and
195195
// warnings, but once we've gathered all the data we'll then skip anything
196196
// that's redundant in the process of populating our values map.
197-
dataResources := map[string]map[string]map[addrs.InstanceKey]cty.Value{}
198-
managedResources := map[string]map[string]map[addrs.InstanceKey]cty.Value{}
197+
dataResources := map[string]map[string]cty.Value{}
198+
managedResources := map[string]map[string]cty.Value{}
199199
wholeModules := map[string]map[addrs.InstanceKey]cty.Value{}
200200
moduleOutputs := map[string]map[addrs.InstanceKey]map[string]cty.Value{}
201201
inputVariables := map[string]cty.Value{}
@@ -208,7 +208,6 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl
208208

209209
for _, ref := range refs {
210210
rng := ref.SourceRange
211-
isSelf := false
212211

213212
rawSubj := ref.Subject
214213
if rawSubj == addrs.Self {
@@ -226,45 +225,60 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl
226225
continue
227226
}
228227

229-
// Treat "self" as an alias for the configured self address.
230-
rawSubj = selfAddr
231-
isSelf = true
232-
233-
if rawSubj == addrs.Self {
228+
if selfAddr == addrs.Self {
234229
// Programming error: the self address cannot alias itself.
235230
panic("scope SelfAddr attempting to alias itself")
236231
}
232+
233+
// self can only be used within a resource instance
234+
subj := selfAddr.(addrs.ResourceInstance)
235+
236+
val, valDiags := normalizeRefValue(s.Data.GetResource(subj.ContainingResource(), rng))
237+
238+
diags = diags.Append(valDiags)
239+
240+
// Self is an exception in that it must always resolve to a
241+
// particular instance. We will still insert the full resource into
242+
// the context below.
243+
switch k := subj.Key.(type) {
244+
case addrs.IntKey:
245+
self = val.Index(cty.NumberIntVal(int64(k)))
246+
case addrs.StringKey:
247+
self = val.Index(cty.StringVal(string(k)))
248+
default:
249+
self = val
250+
}
251+
252+
continue
237253
}
238254

239255
// This type switch must cover all of the "Referenceable" implementations
240-
// in package addrs.
241-
switch subj := rawSubj.(type) {
256+
// in package addrs, however we are removing the possibility of
257+
// ResourceInstance beforehand.
258+
if addr, ok := rawSubj.(addrs.ResourceInstance); ok {
259+
rawSubj = addr.ContainingResource()
260+
}
242261

243-
case addrs.ResourceInstance:
244-
var into map[string]map[string]map[addrs.InstanceKey]cty.Value
245-
switch subj.Resource.Mode {
262+
switch subj := rawSubj.(type) {
263+
case addrs.Resource:
264+
var into map[string]map[string]cty.Value
265+
switch subj.Mode {
246266
case addrs.ManagedResourceMode:
247267
into = managedResources
248268
case addrs.DataResourceMode:
249269
into = dataResources
250270
default:
251-
panic(fmt.Errorf("unsupported ResourceMode %s", subj.Resource.Mode))
271+
panic(fmt.Errorf("unsupported ResourceMode %s", subj.Mode))
252272
}
253273

254-
val, valDiags := normalizeRefValue(s.Data.GetResourceInstance(subj, rng))
274+
val, valDiags := normalizeRefValue(s.Data.GetResource(subj, rng))
255275
diags = diags.Append(valDiags)
256276

257-
r := subj.Resource
277+
r := subj
258278
if into[r.Type] == nil {
259-
into[r.Type] = make(map[string]map[addrs.InstanceKey]cty.Value)
260-
}
261-
if into[r.Type][r.Name] == nil {
262-
into[r.Type][r.Name] = make(map[addrs.InstanceKey]cty.Value)
263-
}
264-
into[r.Type][r.Name][subj.Key] = val
265-
if isSelf {
266-
self = val
279+
into[r.Type] = make(map[string]cty.Value)
267280
}
281+
into[r.Type][r.Name] = val
268282

269283
case addrs.ModuleCallInstance:
270284
val, valDiags := normalizeRefValue(s.Data.GetModuleInstance(subj, rng))
@@ -274,9 +288,6 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl
274288
wholeModules[subj.Call.Name] = make(map[addrs.InstanceKey]cty.Value)
275289
}
276290
wholeModules[subj.Call.Name][subj.Key] = val
277-
if isSelf {
278-
self = val
279-
}
280291

281292
case addrs.ModuleCallOutput:
282293
val, valDiags := normalizeRefValue(s.Data.GetModuleInstanceOutput(subj, rng))
@@ -291,57 +302,36 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl
291302
moduleOutputs[callName][callKey] = make(map[string]cty.Value)
292303
}
293304
moduleOutputs[callName][callKey][subj.Name] = val
294-
if isSelf {
295-
self = val
296-
}
297305

298306
case addrs.InputVariable:
299307
val, valDiags := normalizeRefValue(s.Data.GetInputVariable(subj, rng))
300308
diags = diags.Append(valDiags)
301309
inputVariables[subj.Name] = val
302-
if isSelf {
303-
self = val
304-
}
305310

306311
case addrs.LocalValue:
307312
val, valDiags := normalizeRefValue(s.Data.GetLocalValue(subj, rng))
308313
diags = diags.Append(valDiags)
309314
localValues[subj.Name] = val
310-
if isSelf {
311-
self = val
312-
}
313315

314316
case addrs.PathAttr:
315317
val, valDiags := normalizeRefValue(s.Data.GetPathAttr(subj, rng))
316318
diags = diags.Append(valDiags)
317319
pathAttrs[subj.Name] = val
318-
if isSelf {
319-
self = val
320-
}
321320

322321
case addrs.TerraformAttr:
323322
val, valDiags := normalizeRefValue(s.Data.GetTerraformAttr(subj, rng))
324323
diags = diags.Append(valDiags)
325324
terraformAttrs[subj.Name] = val
326-
if isSelf {
327-
self = val
328-
}
329325

330326
case addrs.CountAttr:
331327
val, valDiags := normalizeRefValue(s.Data.GetCountAttr(subj, rng))
332328
diags = diags.Append(valDiags)
333329
countAttrs[subj.Name] = val
334-
if isSelf {
335-
self = val
336-
}
337330

338331
case addrs.ForEachAttr:
339332
val, valDiags := normalizeRefValue(s.Data.GetForEachAttr(subj, rng))
340333
diags = diags.Append(valDiags)
341334
forEachAttrs[subj.Name] = val
342-
if isSelf {
343-
self = val
344-
}
345335

346336
default:
347337
// Should never happen
@@ -367,13 +357,9 @@ func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceabl
367357
return ctx, diags
368358
}
369359

370-
func buildResourceObjects(resources map[string]map[string]map[addrs.InstanceKey]cty.Value) map[string]cty.Value {
360+
func buildResourceObjects(resources map[string]map[string]cty.Value) map[string]cty.Value {
371361
vals := make(map[string]cty.Value)
372-
for typeName, names := range resources {
373-
nameVals := make(map[string]cty.Value)
374-
for name, keys := range names {
375-
nameVals[name] = buildInstanceObjects(keys)
376-
}
362+
for typeName, nameVals := range resources {
377363
vals[typeName] = cty.ObjectVal(nameVals)
378364
}
379365
return vals

internal/lang/eval_test.go

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func TestScopeEvalContext(t *testing.T) {
2424
"key": cty.StringVal("a"),
2525
"value": cty.NumberIntVal(1),
2626
},
27-
ResourceInstances: map[string]cty.Value{
27+
Resources: map[string]cty.Value{
2828
"null_resource.foo": cty.ObjectVal(map[string]cty.Value{
2929
"attr": cty.StringVal("bar"),
3030
}),
@@ -39,6 +39,14 @@ func TestScopeEvalContext(t *testing.T) {
3939
"attr": cty.StringVal("multi1"),
4040
}),
4141
}),
42+
"null_resource.each": cty.ObjectVal(map[string]cty.Value{
43+
"each0": cty.ObjectVal(map[string]cty.Value{
44+
"attr": cty.StringVal("each0"),
45+
}),
46+
"each1": cty.ObjectVal(map[string]cty.Value{
47+
"attr": cty.StringVal("each1"),
48+
}),
49+
}),
4250
"null_resource.multi[1]": cty.ObjectVal(map[string]cty.Value{
4351
"attr": cty.StringVal("multi1"),
4452
}),
@@ -139,18 +147,37 @@ func TestScopeEvalContext(t *testing.T) {
139147
},
140148
},
141149
{
150+
// at this level, all instance references return the entire resource
142151
`null_resource.multi[1]`,
143152
map[string]cty.Value{
144153
"null_resource": cty.ObjectVal(map[string]cty.Value{
145154
"multi": cty.TupleVal([]cty.Value{
146-
cty.DynamicVal,
155+
cty.ObjectVal(map[string]cty.Value{
156+
"attr": cty.StringVal("multi0"),
157+
}),
147158
cty.ObjectVal(map[string]cty.Value{
148159
"attr": cty.StringVal("multi1"),
149160
}),
150161
}),
151162
}),
152163
},
153164
},
165+
{
166+
// at this level, all instance references return the entire resource
167+
`null_resource.each["each1"]`,
168+
map[string]cty.Value{
169+
"null_resource": cty.ObjectVal(map[string]cty.Value{
170+
"each": cty.ObjectVal(map[string]cty.Value{
171+
"each0": cty.ObjectVal(map[string]cty.Value{
172+
"attr": cty.StringVal("each0"),
173+
}),
174+
"each1": cty.ObjectVal(map[string]cty.Value{
175+
"attr": cty.StringVal("each1"),
176+
}),
177+
}),
178+
}),
179+
},
180+
},
154181
{
155182
`foo(null_resource.multi, null_resource.multi[1])`,
156183
map[string]cty.Value{
@@ -210,17 +237,6 @@ func TestScopeEvalContext(t *testing.T) {
210237
{
211238
`self.baz`,
212239
map[string]cty.Value{
213-
// In the test function below we set "SelfAddr" to be
214-
// one of the resources in our dataset, causing it to get
215-
// expanded here and then copied into "self".
216-
"null_resource": cty.ObjectVal(map[string]cty.Value{
217-
"multi": cty.TupleVal([]cty.Value{
218-
cty.DynamicVal,
219-
cty.ObjectVal(map[string]cty.Value{
220-
"attr": cty.StringVal("multi1"),
221-
}),
222-
}),
223-
}),
224240
"self": cty.ObjectVal(map[string]cty.Value{
225241
"attr": cty.StringVal("multi1"),
226242
}),

terraform/context_apply_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10472,3 +10472,44 @@ func TestContext2Apply_issue19908(t *testing.T) {
1047210472
t.Errorf("test.foo attributes JSON doesn't contain %s after apply\ngot: %s", want, got)
1047310473
}
1047410474
}
10475+
10476+
func TestContext2Apply_invalidIndexRef(t *testing.T) {
10477+
p := testProvider("test")
10478+
p.GetSchemaReturn = &ProviderSchema{
10479+
ResourceTypes: map[string]*configschema.Block{
10480+
"test_instance": {
10481+
Attributes: map[string]*configschema.Attribute{
10482+
"value": {Type: cty.String, Optional: true, Computed: true},
10483+
},
10484+
},
10485+
},
10486+
}
10487+
p.DiffFn = testDiffFn
10488+
10489+
m := testModule(t, "apply-invalid-index")
10490+
c := testContext2(t, &ContextOpts{
10491+
Config: m,
10492+
ProviderResolver: providers.ResolverFixed(
10493+
map[string]providers.Factory{
10494+
"test": testProviderFuncFixed(p),
10495+
},
10496+
),
10497+
})
10498+
10499+
diags := c.Validate()
10500+
if diags.HasErrors() {
10501+
t.Fatalf("unexpected validation failure: %s", diags.Err())
10502+
}
10503+
10504+
wantErr := `The given key does not identify an element in this collection value`
10505+
_, diags = c.Plan()
10506+
10507+
if !diags.HasErrors() {
10508+
t.Fatalf("plan succeeded; want error")
10509+
}
10510+
gotErr := diags.Err().Error()
10511+
10512+
if !strings.Contains(gotErr, wantErr) {
10513+
t.Fatalf("missing expected error\ngot: %s\n\nwant: error containing %q", gotErr, wantErr)
10514+
}
10515+
}

0 commit comments

Comments
 (0)