Skip to content

Commit a384d2d

Browse files
authored
stacks: fix deferred data sources and unknown component applies (#35876)
1 parent c7ef162 commit a384d2d

File tree

10 files changed

+419
-9
lines changed

10 files changed

+419
-9
lines changed

internal/stacks/stackplan/from_proto.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func (l *Loader) AddRaw(rawMsg *anypb.Any) error {
142142
}
143143

144144
case *tfstackdata1.PlanComponentInstance:
145-
addr, diags := stackaddrs.ParseAbsComponentInstanceStr(msg.ComponentInstanceAddr)
145+
addr, diags := stackaddrs.ParsePartialComponentInstanceStr(msg.ComponentInstanceAddr)
146146
if diags.HasErrors() {
147147
// Should not get here because the address we're parsing
148148
// should've been produced by this same version of Terraform.

internal/stacks/stackruntime/apply_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,6 +1361,134 @@ After applying this plan, Terraform will no longer manage these objects. You wil
13611361
},
13621362
},
13631363
},
1364+
"deferred-components": {
1365+
path: path.Join("with-data-source", "deferred-provider-for-each"),
1366+
store: stacks_testing_provider.NewResourceStoreBuilder().
1367+
AddResource("data_known", cty.ObjectVal(map[string]cty.Value{
1368+
"id": cty.StringVal("data_known"),
1369+
"value": cty.StringVal("known"),
1370+
})).
1371+
Build(),
1372+
cycles: []TestCycle{
1373+
{
1374+
planMode: plans.NormalMode,
1375+
planInputs: map[string]cty.Value{
1376+
"providers": cty.UnknownVal(cty.Set(cty.String)),
1377+
},
1378+
wantAppliedChanges: []stackstate.AppliedChange{
1379+
&stackstate.AppliedChangeComponentInstance{
1380+
ComponentAddr: mustAbsComponent("component.const"),
1381+
ComponentInstanceAddr: mustAbsComponentInstance("component.const"),
1382+
Dependencies: collections.NewSet[stackaddrs.AbsComponent](),
1383+
Dependents: collections.NewSet[stackaddrs.AbsComponent](),
1384+
InputVariables: map[addrs.InputVariable]cty.Value{
1385+
mustInputVariable("id"): cty.StringVal("data_known"),
1386+
mustInputVariable("resource"): cty.StringVal("resource_known"),
1387+
},
1388+
OutputValues: make(map[addrs.OutputValue]cty.Value),
1389+
},
1390+
&stackstate.AppliedChangeResourceInstanceObject{
1391+
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.const.data.testing_data_source.data"),
1392+
NewStateSrc: &states.ResourceInstanceObjectSrc{
1393+
AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{
1394+
"id": "data_known",
1395+
"value": "known",
1396+
}),
1397+
AttrSensitivePaths: make([]cty.Path, 0),
1398+
Status: states.ObjectReady,
1399+
},
1400+
ProviderConfigAddr: mustDefaultRootProvider("testing"),
1401+
Schema: stacks_testing_provider.TestingDataSourceSchema,
1402+
},
1403+
&stackstate.AppliedChangeResourceInstanceObject{
1404+
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.const.testing_resource.data"),
1405+
NewStateSrc: &states.ResourceInstanceObjectSrc{
1406+
AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{
1407+
"id": "resource_known",
1408+
"value": "known",
1409+
}),
1410+
Dependencies: []addrs.ConfigResource{
1411+
mustAbsResourceInstance("data.testing_data_source.data").ConfigResource(),
1412+
},
1413+
Status: states.ObjectReady,
1414+
},
1415+
ProviderConfigAddr: mustDefaultRootProvider("testing"),
1416+
Schema: stacks_testing_provider.TestingResourceSchema,
1417+
},
1418+
&stackstate.AppliedChangeInputVariable{
1419+
Addr: mustStackInputVariable("providers"),
1420+
Value: cty.UnknownVal(cty.Set(cty.String)),
1421+
},
1422+
},
1423+
},
1424+
{
1425+
planMode: plans.DestroyMode,
1426+
planInputs: map[string]cty.Value{
1427+
"providers": cty.UnknownVal(cty.Set(cty.String)),
1428+
},
1429+
wantAppliedChanges: []stackstate.AppliedChange{
1430+
&stackstate.AppliedChangeComponentInstanceRemoved{
1431+
ComponentAddr: mustAbsComponent("component.const"),
1432+
ComponentInstanceAddr: mustAbsComponentInstance("component.const"),
1433+
},
1434+
&stackstate.AppliedChangeResourceInstanceObject{
1435+
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.const.data.testing_data_source.data"),
1436+
ProviderConfigAddr: mustDefaultRootProvider("testing"),
1437+
NewStateSrc: nil,
1438+
},
1439+
&stackstate.AppliedChangeResourceInstanceObject{
1440+
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.const.testing_resource.data"),
1441+
ProviderConfigAddr: mustDefaultRootProvider("testing"),
1442+
NewStateSrc: nil,
1443+
},
1444+
&stackstate.AppliedChangeInputVariable{
1445+
Addr: mustStackInputVariable("providers"),
1446+
Value: cty.NilVal, // destroyed
1447+
},
1448+
},
1449+
},
1450+
},
1451+
},
1452+
"unknown-component-input": {
1453+
path: path.Join("map-object-input", "for-each-input"),
1454+
cycles: []TestCycle{
1455+
{
1456+
planMode: plans.NormalMode,
1457+
planInputs: map[string]cty.Value{
1458+
"inputs": cty.UnknownVal(cty.Map(cty.String)),
1459+
},
1460+
wantAppliedChanges: []stackstate.AppliedChange{
1461+
&stackstate.AppliedChangeComponentInstance{
1462+
ComponentAddr: mustAbsComponent("component.main"),
1463+
ComponentInstanceAddr: mustAbsComponentInstance("component.main"),
1464+
Dependencies: collections.NewSet(mustAbsComponent("component.self")),
1465+
OutputValues: make(map[addrs.OutputValue]cty.Value),
1466+
InputVariables: make(map[addrs.InputVariable]cty.Value),
1467+
},
1468+
&stackstate.AppliedChangeInputVariable{
1469+
Addr: mustStackInputVariable("inputs"),
1470+
Value: cty.UnknownVal(cty.Map(cty.String)),
1471+
},
1472+
},
1473+
},
1474+
{
1475+
planMode: plans.DestroyMode,
1476+
planInputs: map[string]cty.Value{
1477+
"inputs": cty.MapValEmpty(cty.String),
1478+
},
1479+
wantAppliedChanges: []stackstate.AppliedChange{
1480+
&stackstate.AppliedChangeComponentInstanceRemoved{
1481+
ComponentAddr: mustAbsComponent("component.main"),
1482+
ComponentInstanceAddr: mustAbsComponentInstance("component.main"),
1483+
},
1484+
&stackstate.AppliedChangeInputVariable{
1485+
Addr: mustStackInputVariable("inputs"),
1486+
Value: cty.NilVal, // destroyed
1487+
},
1488+
},
1489+
},
1490+
},
1491+
},
13641492
}
13651493

13661494
for name, tc := range tcs {

internal/stacks/stackruntime/helper_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,15 +510,15 @@ func mustAbsResourceInstanceObjectPtr(addr string) *stackaddrs.AbsResourceInstan
510510
}
511511

512512
func mustAbsComponentInstance(addr string) stackaddrs.AbsComponentInstance {
513-
ret, diags := stackaddrs.ParseAbsComponentInstanceStr(addr)
513+
ret, diags := stackaddrs.ParsePartialComponentInstanceStr(addr)
514514
if len(diags) > 0 {
515515
panic(fmt.Sprintf("failed to parse component instance address %q: %s", addr, diags))
516516
}
517517
return ret
518518
}
519519

520520
func mustAbsComponent(addr string) stackaddrs.AbsComponent {
521-
ret, diags := stackaddrs.ParseAbsComponentInstanceStr(addr)
521+
ret, diags := stackaddrs.ParsePartialComponentInstanceStr(addr)
522522
if len(diags) > 0 {
523523
panic(fmt.Sprintf("failed to parse component instance address %q: %s", addr, diags))
524524
}

internal/stacks/stackruntime/internal/stackeval/stubs/unknown.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ func (u *unknownProvider) ReadDataSource(request providers.ReadDataSourceRequest
212212
// unknown values. This isn't the original use case for the mocking
213213
// library, but it is doing exactly what we need it to do.
214214

215-
schema := u.GetProviderSchema().ResourceTypes[request.TypeName]
215+
schema := u.GetProviderSchema().DataSources[request.TypeName]
216216
val, diags := mocking.PlanComputedValuesForResource(request.Config, schema.Block)
217217
if diags.HasErrors() {
218218
// All the potential errors we get back from this function are

internal/stacks/stackruntime/internal/stackeval/walk_dynamic.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/zclconf/go-cty/cty"
1010

11+
"github.com/hashicorp/terraform/internal/addrs"
1112
"github.com/hashicorp/terraform/internal/collections"
1213
"github.com/hashicorp/terraform/internal/instances"
1314
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
@@ -166,11 +167,23 @@ func walkDynamicObjectsInStack[Output any](
166167
// This instance is not claimed by the removed block, so
167168
// we'll mark it as being deferred until the foreach is
168169
// known.
169-
inst := newComponentInstance(component, inst.Key, instances.RepetitionData{
170-
EachKey: inst.Key.Value(),
171-
EachValue: cty.UnknownVal(cty.DynamicPseudoType),
172-
}, true)
173-
visit(ctx, walk, inst)
170+
171+
if inst.Key == addrs.WildcardKey {
172+
// If the key we retrieved is a wildcard key, then we'll
173+
// recreate a "proper" unknown instance as we'll
174+
// recompute a properly typed each value with this
175+
// function.
176+
inst := component.UnknownInstance(ctx, phase)
177+
visit(ctx, walk, inst)
178+
} else {
179+
// Otherwise, the key is a known key and the instance
180+
// actually does exist.
181+
inst := newComponentInstance(component, inst.Key, instances.RepetitionData{
182+
EachKey: inst.Key.Value(),
183+
EachValue: cty.UnknownVal(cty.DynamicPseudoType),
184+
}, true)
185+
visit(ctx, walk, inst)
186+
}
174187
}
175188

176189
return

internal/stacks/stackruntime/plan_test.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,173 @@ func TestPlan(t *testing.T) {
242242
},
243243
},
244244
},
245+
"deferred-provider-with-data-sources": {
246+
path: path.Join("with-data-source", "deferred-provider-for-each"),
247+
store: stacks_testing_provider.NewResourceStoreBuilder().
248+
AddResource("data_known", cty.ObjectVal(map[string]cty.Value{
249+
"id": cty.StringVal("data_known"),
250+
"value": cty.StringVal("known"),
251+
})).
252+
Build(),
253+
cycle: TestCycle{
254+
planInputs: map[string]cty.Value{
255+
"providers": cty.UnknownVal(cty.Set(cty.String)),
256+
},
257+
wantPlannedChanges: []stackplan.PlannedChange{
258+
&stackplan.PlannedChangeApplyable{
259+
Applyable: true,
260+
},
261+
&stackplan.PlannedChangeComponentInstance{
262+
Addr: mustAbsComponentInstance("component.const"),
263+
PlanApplyable: true,
264+
PlanComplete: true,
265+
Action: plans.Create,
266+
PlannedInputValues: map[string]plans.DynamicValue{
267+
"id": mustPlanDynamicValueDynamicType(cty.StringVal("data_known")),
268+
"resource": mustPlanDynamicValueDynamicType(cty.StringVal("resource_known")),
269+
},
270+
PlannedInputValueMarks: map[string][]cty.PathValueMarks{
271+
"id": nil,
272+
"resource": nil,
273+
},
274+
PlannedOutputValues: make(map[string]cty.Value),
275+
PlannedCheckResults: &states.CheckResults{},
276+
PlanTimestamp: fakePlanTimestamp,
277+
},
278+
&stackplan.PlannedChangeResourceInstancePlanned{
279+
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.const.data.testing_data_source.data"),
280+
ChangeSrc: nil,
281+
PriorStateSrc: &states.ResourceInstanceObjectSrc{
282+
AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{
283+
"id": "data_known",
284+
"value": "known",
285+
}),
286+
Status: states.ObjectReady,
287+
Dependencies: make([]addrs.ConfigResource, 0),
288+
},
289+
ProviderConfigAddr: mustDefaultRootProvider("testing"),
290+
Schema: stacks_testing_provider.TestingDataSourceSchema,
291+
},
292+
&stackplan.PlannedChangeResourceInstancePlanned{
293+
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.const.testing_resource.data"),
294+
ChangeSrc: &plans.ResourceInstanceChangeSrc{
295+
Addr: mustAbsResourceInstance("testing_resource.data"),
296+
PrevRunAddr: mustAbsResourceInstance("testing_resource.data"),
297+
ProviderAddr: mustDefaultRootProvider("testing"),
298+
ChangeSrc: plans.ChangeSrc{
299+
Action: plans.Create,
300+
Before: mustPlanDynamicValue(cty.NullVal(cty.DynamicPseudoType)),
301+
After: mustPlanDynamicValue(cty.ObjectVal(map[string]cty.Value{
302+
"id": cty.StringVal("resource_known"),
303+
"value": cty.StringVal("known"),
304+
})),
305+
},
306+
},
307+
PriorStateSrc: nil,
308+
ProviderConfigAddr: mustDefaultRootProvider("testing"),
309+
Schema: stacks_testing_provider.TestingResourceSchema,
310+
},
311+
&stackplan.PlannedChangeComponentInstance{
312+
Addr: mustAbsComponentInstance("component.main[*]"),
313+
PlanApplyable: false, // only deferred changes
314+
PlanComplete: false, // deferred
315+
Action: plans.Create,
316+
PlannedInputValues: map[string]plans.DynamicValue{
317+
"id": mustPlanDynamicValueDynamicType(cty.StringVal("data_unknown")),
318+
"resource": mustPlanDynamicValueDynamicType(cty.StringVal("resource_unknown")),
319+
},
320+
PlannedInputValueMarks: map[string][]cty.PathValueMarks{
321+
"id": nil,
322+
"resource": nil,
323+
},
324+
PlannedOutputValues: make(map[string]cty.Value),
325+
PlannedCheckResults: &states.CheckResults{},
326+
PlanTimestamp: fakePlanTimestamp,
327+
},
328+
&stackplan.PlannedChangeDeferredResourceInstancePlanned{
329+
ResourceInstancePlanned: stackplan.PlannedChangeResourceInstancePlanned{
330+
ResourceInstanceObjectAddr: stackaddrs.AbsResourceInstanceObject{
331+
Component: stackaddrs.AbsComponentInstance{
332+
Item: stackaddrs.ComponentInstance{
333+
Component: stackaddrs.Component{
334+
Name: "main",
335+
},
336+
Key: addrs.WildcardKey,
337+
},
338+
},
339+
Item: addrs.AbsResourceInstanceObject{
340+
ResourceInstance: mustAbsResourceInstance("data.testing_data_source.data"),
341+
},
342+
},
343+
ChangeSrc: &plans.ResourceInstanceChangeSrc{
344+
Addr: mustAbsResourceInstance("data.testing_data_source.data"),
345+
PrevRunAddr: mustAbsResourceInstance("data.testing_data_source.data"),
346+
ProviderAddr: mustDefaultRootProvider("testing"),
347+
ChangeSrc: plans.ChangeSrc{
348+
Action: plans.Read,
349+
Before: mustPlanDynamicValue(cty.NullVal(cty.DynamicPseudoType)),
350+
After: mustPlanDynamicValue(cty.ObjectVal(map[string]cty.Value{
351+
"id": cty.StringVal("data_unknown"),
352+
"value": cty.UnknownVal(cty.String),
353+
})),
354+
},
355+
ActionReason: plans.ResourceInstanceReadBecauseDependencyPending,
356+
},
357+
PriorStateSrc: nil,
358+
ProviderConfigAddr: mustDefaultRootProvider("testing"),
359+
Schema: stacks_testing_provider.TestingDataSourceSchema,
360+
},
361+
DeferredReason: providers.DeferredReasonProviderConfigUnknown,
362+
},
363+
&stackplan.PlannedChangeDeferredResourceInstancePlanned{
364+
ResourceInstancePlanned: stackplan.PlannedChangeResourceInstancePlanned{
365+
ResourceInstanceObjectAddr: stackaddrs.AbsResourceInstanceObject{
366+
Component: stackaddrs.AbsComponentInstance{
367+
Item: stackaddrs.ComponentInstance{
368+
Component: stackaddrs.Component{
369+
Name: "main",
370+
},
371+
Key: addrs.WildcardKey,
372+
},
373+
},
374+
Item: addrs.AbsResourceInstanceObject{
375+
ResourceInstance: mustAbsResourceInstance("testing_resource.data"),
376+
},
377+
},
378+
ChangeSrc: &plans.ResourceInstanceChangeSrc{
379+
Addr: mustAbsResourceInstance("testing_resource.data"),
380+
PrevRunAddr: mustAbsResourceInstance("testing_resource.data"),
381+
ProviderAddr: mustDefaultRootProvider("testing"),
382+
ChangeSrc: plans.ChangeSrc{
383+
Action: plans.Create,
384+
Before: mustPlanDynamicValue(cty.NullVal(cty.DynamicPseudoType)),
385+
After: mustPlanDynamicValue(cty.ObjectVal(map[string]cty.Value{
386+
"id": cty.StringVal("resource_unknown"),
387+
"value": cty.UnknownVal(cty.String),
388+
})),
389+
},
390+
},
391+
PriorStateSrc: nil,
392+
ProviderConfigAddr: mustDefaultRootProvider("testing"),
393+
Schema: stacks_testing_provider.TestingResourceSchema,
394+
},
395+
DeferredReason: providers.DeferredReasonProviderConfigUnknown,
396+
},
397+
&stackplan.PlannedChangeHeader{
398+
TerraformVersion: version.SemVer,
399+
},
400+
&stackplan.PlannedChangePlannedTimestamp{
401+
PlannedTimestamp: fakePlanTimestamp,
402+
},
403+
&stackplan.PlannedChangeRootInputValue{
404+
Addr: mustStackInputVariable("providers"),
405+
Action: plans.Create,
406+
Before: cty.NullVal(cty.DynamicPseudoType),
407+
After: cty.UnknownVal(cty.Set(cty.String)),
408+
},
409+
},
410+
},
411+
},
245412
}
246413
for name, tc := range tcs {
247414
t.Run(name, func(t *testing.T) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
variable "input" {
2+
type = string
3+
}
4+
5+
output "value" {
6+
value = var.input
7+
}

0 commit comments

Comments
 (0)