From 0b87ef82c3c07dd58c6471343f8afeb52ba30d97 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 11 Nov 2016 18:54:37 -0800 Subject: [PATCH] terraform: depends_on can reference entire modules --- terraform/context_apply_test.go | 136 ++++++++++++++++++ terraform/terraform_test.go | 12 ++ .../child/main.tf | 1 + .../apply-resource-depends-on-module/main.tf | 7 + terraform/transform_reference.go | 21 +++ 5 files changed, 177 insertions(+) create mode 100644 terraform/test-fixtures/apply-resource-depends-on-module/child/main.tf create mode 100644 terraform/test-fixtures/apply-resource-depends-on-module/main.tf diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index b6ac3b277..53c211807 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -110,6 +110,142 @@ test = []`) } } +func TestContext2Apply_resourceDependsOnModule(t *testing.T) { + m := testModule(t, "apply-resource-depends-on-module") + p := testProvider("aws") + p.DiffFn = testDiffFn + + { + // Wait for the dependency, sleep, and verify the graph never + // called a child. + var called int32 + var checked bool + p.ApplyFn = func( + info *InstanceInfo, + is *InstanceState, + id *InstanceDiff) (*InstanceState, error) { + if info.HumanId() == "module.child.aws_instance.child" { + checked = true + + // Sleep to allow parallel execution + time.Sleep(50 * time.Millisecond) + + // Verify that called is 0 (dep not called) + if atomic.LoadInt32(&called) != 0 { + return nil, fmt.Errorf("aws_instance.a should not be called") + } + } + + atomic.AddInt32(&called, 1) + return testApplyFn(info, is, id) + } + + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + if !checked { + t.Fatal("should check") + } + + checkStateString(t, state, testTerraformApplyResourceDependsOnModuleStr) + } +} + +func TestContext2Apply_resourceDependsOnModuleDestroy(t *testing.T) { + m := testModule(t, "apply-resource-depends-on-module") + p := testProvider("aws") + p.DiffFn = testDiffFn + + var globalState *State + { + p.ApplyFn = testApplyFn + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + globalState = state + } + + { + // Wait for the dependency, sleep, and verify the graph never + // called a child. + var called int32 + var checked bool + p.ApplyFn = func( + info *InstanceInfo, + is *InstanceState, + id *InstanceDiff) (*InstanceState, error) { + if info.HumanId() == "aws_instance.a" { + checked = true + + // Sleep to allow parallel execution + time.Sleep(50 * time.Millisecond) + + // Verify that called is 0 (dep not called) + if atomic.LoadInt32(&called) != 0 { + return nil, fmt.Errorf("module child should not be called") + } + } + + atomic.AddInt32(&called, 1) + return testApplyFn(info, is, id) + } + + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: globalState, + Destroy: true, + }) + + if _, err := ctx.Plan(); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + if !checked { + t.Fatal("should check") + } + + checkStateString(t, state, ` + +module.child: + + `) + } +} + func TestContext2Apply_mapVarBetweenModules(t *testing.T) { m := testModule(t, "apply-map-var-through-module") p := testProvider("null") diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index cf1f88924..cbe59abf1 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -653,6 +653,18 @@ aws_instance.foo: num = 2 ` +const testTerraformApplyResourceDependsOnModuleStr = ` +aws_instance.a: + ID = foo + + Dependencies: + module.child + +module.child: + aws_instance.child: + ID = foo +` + const testTerraformApplyTaintStr = ` aws_instance.bar: ID = foo diff --git a/terraform/test-fixtures/apply-resource-depends-on-module/child/main.tf b/terraform/test-fixtures/apply-resource-depends-on-module/child/main.tf new file mode 100644 index 000000000..33e4ced51 --- /dev/null +++ b/terraform/test-fixtures/apply-resource-depends-on-module/child/main.tf @@ -0,0 +1 @@ +resource "aws_instance" "child" {} diff --git a/terraform/test-fixtures/apply-resource-depends-on-module/main.tf b/terraform/test-fixtures/apply-resource-depends-on-module/main.tf new file mode 100644 index 000000000..a0859c0fd --- /dev/null +++ b/terraform/test-fixtures/apply-resource-depends-on-module/main.tf @@ -0,0 +1,7 @@ +module "child" { + source = "./child" +} + +resource "aws_instance" "a" { + depends_on = ["module.child"] +} diff --git a/terraform/transform_reference.go b/terraform/transform_reference.go index 1da835fbd..ac6703298 100644 --- a/terraform/transform_reference.go +++ b/terraform/transform_reference.go @@ -200,6 +200,15 @@ func NewReferenceMap(vs []dag.Vertex) *ReferenceMap { n = prefix + n refMap[n] = append(refMap[n], v) } + + // If there is a path, it is always referenceable by that. For + // example, if this is a referenceable thing at path []string{"foo"}, + // then it can be referenced at "module.foo" + if pn, ok := v.(GraphNodeSubPath); ok { + if p := ReferenceModulePath(pn.Path()); p != "" { + refMap[p] = append(refMap[p], v) + } + } } // Build the lookup table for referenced by @@ -224,6 +233,18 @@ func NewReferenceMap(vs []dag.Vertex) *ReferenceMap { return &m } +// Returns the reference name for a module path. The path "foo" would return +// "module.foo" +func ReferenceModulePath(p []string) string { + p = normalizeModulePath(p) + if len(p) == 1 { + // Root, no name + return "" + } + + return fmt.Sprintf("module.%s", strings.Join(p[1:], ".")) +} + // ReferencesFromConfig returns the references that a configuration has // based on the interpolated variables in a configuration. func ReferencesFromConfig(c *config.RawConfig) []string {