Skip to content

Commit de74720

Browse files
author
The Terraform Team
authored
Merge 4e9ae3e into backport/liamcervante/34976/frankly-assuring-boxer
2 parents a881573 + 4e9ae3e commit de74720

File tree

4 files changed

+132
-15
lines changed

4 files changed

+132
-15
lines changed

internal/terraform/context_apply2_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3377,3 +3377,35 @@ resource "test_object" "a" {
33773377
t.Errorf("Unexpected %s change for %s", c.Action, c.Addr)
33783378
}
33793379
}
3380+
3381+
// This test explicitly reproduces the issue described in #34976.
3382+
func TestContext2Apply_34976(t *testing.T) {
3383+
m := testModuleInline(t, map[string]string{
3384+
"main.tf": `
3385+
module "a" {
3386+
source = "./mod"
3387+
count = 1
3388+
}
3389+
3390+
resource "test_object" "obj" {
3391+
test_number = length(module.a)
3392+
}
3393+
`,
3394+
"mod/main.tf": ``, // just an empty module
3395+
})
3396+
3397+
p := simpleMockProvider()
3398+
3399+
ctx := testContext2(t, &ContextOpts{
3400+
Providers: map[addrs.Provider]providers.Factory{
3401+
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
3402+
},
3403+
})
3404+
3405+
plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
3406+
assertNoErrors(t, diags)
3407+
3408+
// Just don't crash.
3409+
_, diags = ctx.Apply(plan, m, nil)
3410+
assertNoErrors(t, diags)
3411+
}

internal/terraform/context_plan2_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strings"
1111
"sync"
1212
"testing"
13+
"time"
1314

1415
"github.com/davecgh/go-spew/spew"
1516
"github.com/google/go-cmp/cmp"
@@ -5493,3 +5494,49 @@ resource "test_object" "obj" {
54935494
t.Fatal("expected no change in plan")
54945495
}
54955496
}
5497+
5498+
// This test explicitly reproduces the issue described in #34976.
5499+
func TestContext2Plan_34976(t *testing.T) {
5500+
m := testModuleInline(t, map[string]string{
5501+
"main.tf": `
5502+
data "test_object" "obj" {}
5503+
5504+
module "a" {
5505+
depends_on = [data.test_object.obj]
5506+
source = "./mod"
5507+
}
5508+
5509+
output "value" {
5510+
value = try(module.a.notreal, null)
5511+
}
5512+
`,
5513+
"mod/main.tf": ``,
5514+
})
5515+
5516+
p := new(testing_provider.MockProvider)
5517+
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{
5518+
DataSources: map[string]*configschema.Block{
5519+
"test_object": {},
5520+
},
5521+
})
5522+
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
5523+
5524+
// Just very small delay that means the output will be processed before
5525+
// the module.
5526+
time.Sleep(50 * time.Millisecond)
5527+
5528+
return providers.ReadDataSourceResponse{
5529+
State: req.Config,
5530+
}
5531+
}
5532+
5533+
ctx := testContext2(t, &ContextOpts{
5534+
Providers: map[addrs.Provider]providers.Factory{
5535+
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
5536+
},
5537+
})
5538+
5539+
// Just shouldn't crash.
5540+
_, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
5541+
assertNoErrors(t, diags)
5542+
}

internal/terraform/node_module_expand.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type nodeExpandModule struct {
2727

2828
var (
2929
_ GraphNodeExecutable = (*nodeExpandModule)(nil)
30+
_ GraphNodeReferenceable = (*nodeExpandModule)(nil)
3031
_ GraphNodeReferencer = (*nodeExpandModule)(nil)
3132
_ GraphNodeReferenceOutside = (*nodeExpandModule)(nil)
3233
_ graphNodeExpandsInstances = (*nodeExpandModule)(nil)
@@ -75,6 +76,14 @@ func (n *nodeExpandModule) References() []*addrs.Reference {
7576
return refs
7677
}
7778

79+
func (n *nodeExpandModule) ReferenceableAddrs() []addrs.Referenceable {
80+
// Anything referencing this module must do so after the ExpandModule call
81+
// has been made to the expander, so we return the module call address as
82+
// the only referenceable address.
83+
_, call := n.Addr.Call()
84+
return []addrs.Referenceable{call}
85+
}
86+
7887
func (n *nodeExpandModule) DependsOn() []*addrs.Reference {
7988
if n.ModuleCall == nil {
8089
return nil
@@ -99,7 +108,7 @@ func (n *nodeExpandModule) DependsOn() []*addrs.Reference {
99108

100109
// GraphNodeReferenceOutside
101110
func (n *nodeExpandModule) ReferenceOutside() (selfPath, referencePath addrs.Module) {
102-
return n.Addr, n.Addr.Parent()
111+
return n.Addr.Parent(), n.Addr.Parent()
103112
}
104113

105114
// GraphNodeExecutable

internal/terraform/transform_reference.go

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -522,21 +522,50 @@ func (m ReferenceMap) referenceMapKey(path addrs.Module, addr addrs.Referenceabl
522522
// might be in a resource-oriented graph rather than an
523523
// instance-oriented graph, and so we'll see if we have the
524524
// resource itself instead.
525-
switch ri := addr.(type) {
526-
case addrs.ResourceInstance:
527-
addr = ri.ContainingResource()
528-
case addrs.ResourceInstancePhase:
529-
addr = ri.ContainingResource()
530-
case addrs.ModuleCallInstanceOutput:
531-
addr = ri.ModuleCallOutput()
532-
case addrs.ModuleCallInstance:
533-
addr = ri.Call
534-
default:
535-
return key
525+
526+
if ri, ok := addr.(addrs.ResourceInstance); ok {
527+
return m.mapKey(path, ri.ContainingResource())
528+
}
529+
530+
if rip, ok := addr.(addrs.ResourceInstancePhase); ok {
531+
return m.mapKey(path, rip.ContainingResource())
536532
}
537-
// if we matched any of the resource node types above, generate a new
538-
// key
539-
key = m.mapKey(path, addr)
533+
534+
if mcio, ok := addr.(addrs.ModuleCallInstanceOutput); ok {
535+
536+
// A module call instance output is a reference to an output of a
537+
// specific module call. If we can't find that, we'll look first
538+
// for the general non-instanced output.
539+
540+
key = m.mapKey(path, mcio.ModuleCallOutput())
541+
if _, exists := m[key]; exists {
542+
// We found it, so we can just use that.
543+
return key
544+
}
545+
546+
// Otherwise we'll look just for the instanced module call itself.
547+
548+
key = m.mapKey(path, mcio.Call)
549+
if _, exists := m[key]; exists {
550+
// We found it, so we can just use that.
551+
return key
552+
}
553+
554+
// If we still can't find it, then we'll look for the non-instanced
555+
// module call. This is the same as we'd do if the original call had
556+
// just been for a ModuleCallInstance, so we'll let that fall
557+
// through.
558+
559+
addr = mcio.Call
560+
561+
}
562+
563+
if mci, ok := addr.(addrs.ModuleCallInstance); ok {
564+
return m.mapKey(path, mci.Call)
565+
}
566+
567+
// If nothing matched, then we'll just return the original key
568+
// unchanged.
540569
}
541570
return key
542571
}

0 commit comments

Comments
 (0)