From a57337327de3a10df5101da6e790e26768ad3284 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 12 Dec 2019 12:47:08 -0500 Subject: [PATCH] check resource-level connections block for refs References from a resource-level connection blocks were not returned from NodeAbstractResource.References, causing the provisioner connection attributes to sometimes be evaluated too early. --- terraform/context_apply_test.go | 57 +++++++++++++++++++ terraform/node_resource_abstract.go | 5 ++ terraform/provisioner_mock.go | 1 - .../apply-plan-connection-refs/main.tf | 18 ++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 terraform/testdata/apply-plan-connection-refs/main.tf diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index 6c2062572..1de9e380b 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -11131,6 +11131,63 @@ func TestContext2Apply_taintedDestroyFailure(t *testing.T) { } } +func TestContext2Apply_plannedConnectionRefs(t *testing.T) { + m := testModule(t, "apply-plan-connection-refs") + p := testProvider("test") + p.DiffFn = testDiffFn + p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { + s := req.PlannedState.AsValueMap() + // delay "a" slightly, so if the reference edge is missing the "b" + // provisioner will see an unknown value. + if s["foo"].AsString() == "a" { + time.Sleep(500 * time.Millisecond) + } + + s["id"] = cty.StringVal("ID") + resp.NewState = cty.ObjectVal(s) + return resp + } + + pr := testProvisioner() + pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { + host := req.Connection.GetAttr("host") + if host.IsNull() || !host.IsKnown() { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("invalid host value: %#v", host)) + } + + return resp + } + + providerResolver := providers.ResolverFixed( + map[addrs.Provider]providers.Factory{ + addrs.NewLegacyProvider("test"): testProviderFuncFixed(p), + }, + ) + + provisioners := map[string]ProvisionerFactory{ + "shell": testProvisionerFuncFixed(pr), + } + + hook := &testHook{} + ctx := testContext2(t, &ContextOpts{ + Config: m, + ProviderResolver: providerResolver, + Provisioners: provisioners, + Hooks: []Hook{hook}, + }) + + _, diags := ctx.Plan() + diags.HasErrors() + if diags.HasErrors() { + t.Fatalf("diags: %s", diags.Err()) + } + + _, diags = ctx.Apply() + if diags.HasErrors() { + t.Fatalf("diags: %s", diags.Err()) + } +} + func TestContext2Apply_cbdCycle(t *testing.T) { m, snap := testModuleWithSnapshot(t, "apply-cbd-cycle") p := testProvider("test") diff --git a/terraform/node_resource_abstract.go b/terraform/node_resource_abstract.go index 8d6f01832..fd2cfc8e5 100644 --- a/terraform/node_resource_abstract.go +++ b/terraform/node_resource_abstract.go @@ -195,6 +195,11 @@ func (n *NodeAbstractResource) References() []*addrs.Reference { refs, _ = lang.ReferencesInBlock(c.Config, n.Schema) result = append(result, refs...) if c.Managed != nil { + if c.Managed.Connection != nil { + refs, _ = lang.ReferencesInBlock(c.Managed.Connection.Config, connectionBlockSupersetSchema) + result = append(result, refs...) + } + for _, p := range c.Managed.Provisioners { if p.When != configs.ProvisionerWhenCreate { continue diff --git a/terraform/provisioner_mock.go b/terraform/provisioner_mock.go index f59589164..d476e4ea7 100644 --- a/terraform/provisioner_mock.go +++ b/terraform/provisioner_mock.go @@ -120,7 +120,6 @@ func (p *MockProvisioner) ProvisionResource(r provisioners.ProvisionResourceRequ } if p.ProvisionResourceFn != nil { fn := p.ProvisionResourceFn - p.Unlock() return fn(r) } diff --git a/terraform/testdata/apply-plan-connection-refs/main.tf b/terraform/testdata/apply-plan-connection-refs/main.tf new file mode 100644 index 000000000..d20191f33 --- /dev/null +++ b/terraform/testdata/apply-plan-connection-refs/main.tf @@ -0,0 +1,18 @@ +variable "msg" { + default = "ok" +} + +resource "test_instance" "a" { + foo = "a" +} + + +resource "test_instance" "b" { + foo = "b" + provisioner "shell" { + command = "echo ${var.msg}" + } + connection { + host = test_instance.a.id + } +}