diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index 6c2a493c58e1..2109ce7026ca 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -4180,6 +4180,46 @@ module.child: `) } +func TestContext2Apply_targetedModuleRecursive(t *testing.T) { + m := testModule(t, "apply-targeted-module-recursive") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Targets: []string{"module.child"}, + }) + + if _, err := ctx.Plan(); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + mod := state.ModuleByPath([]string{"root", "child", "subchild"}) + if mod == nil { + t.Fatalf("no subchild module found in the state!\n\n%#v", state) + } + if len(mod.Resources) != 1 { + t.Fatalf("expected 1 resources, got: %#v", mod.Resources) + } + + checkStateString(t, state, ` + +module.child.subchild: + aws_instance.foo: + ID = foo + num = 2 + type = aws_instance + `) +} + func TestContext2Apply_targetedModuleResource(t *testing.T) { m := testModule(t, "apply-targeted-module-resource") p := testProvider("aws") diff --git a/terraform/resource_address.go b/terraform/resource_address.go index da22b23219e7..0bae5ec1923e 100644 --- a/terraform/resource_address.go +++ b/terraform/resource_address.go @@ -59,7 +59,7 @@ func (r *ResourceAddress) String() string { panic(fmt.Errorf("unsupported resource mode %s", r.Mode)) } - if r.Type != "" { + if r.Type != "" && r.Type != "module" { result = append(result, r.Type) } @@ -109,17 +109,38 @@ func ParseResourceAddress(s string) (*ResourceAddress, error) { return nil, fmt.Errorf("must target specific data instance") } + var resourceType string + if matches["type"] == "" && matches["module_name"] != "" { + resourceType = "module" + } else { + resourceType = matches["type"] + } + return &ResourceAddress{ Path: path, Index: resourceIndex, InstanceType: instanceType, InstanceTypeSet: matches["instance_type"] != "", Name: matches["name"], - Type: matches["type"], + Type: resourceType, Mode: mode, }, nil } +func (addr *ResourceAddress) ContainedInHierarchy(node *ResourceAddress) bool { + path := addr.Path + nodePath := node.Path + if len(nodePath) < len(path) { + return false + } + for i, component := range path { + if val := nodePath[i]; val != component { + return false + } + } + return true +} + func (addr *ResourceAddress) Equals(raw interface{}) bool { other, ok := raw.(*ResourceAddress) if !ok { @@ -196,7 +217,7 @@ func tokenizeResourceAddress(s string) (map[string]string, error) { // string "aws_instance.web.tainted[1]" re := regexp.MustCompile(`\A` + // "module.foo.module.bar" (optional) - `(?P(?:module\.[^.]+\.?)*)` + + `(?P(?:module\.(?P[^.]+)\.?)*)` + // possibly "data.", if targeting is a data resource `(?P(?:data\.)?)` + // "aws_instance.web" (optional when module path specified) diff --git a/terraform/resource_address_test.go b/terraform/resource_address_test.go index 144d7a9ec05e..1f434913ebe2 100644 --- a/terraform/resource_address_test.go +++ b/terraform/resource_address_test.go @@ -144,7 +144,7 @@ func TestParseResourceAddress(t *testing.T) { "module.a", &ResourceAddress{ Path: []string{"a"}, - Type: "", + Type: "module", Name: "", InstanceType: TypePrimary, Index: -1, @@ -155,7 +155,7 @@ func TestParseResourceAddress(t *testing.T) { "module.a.module.b", &ResourceAddress{ Path: []string{"a", "b"}, - Type: "", + Type: "module", Name: "", InstanceType: TypePrimary, Index: -1, diff --git a/terraform/state_filter.go b/terraform/state_filter.go index 89cf0d898947..82d1874a6d89 100644 --- a/terraform/state_filter.go +++ b/terraform/state_filter.go @@ -71,7 +71,7 @@ func (f *StateFilter) filterSingle(a *ResourceAddress) []*StateFilterResult { // Only add the module to the results if we haven't specified a type. // We also ignore the root module. - if a.Type == "" && len(m.Path) > 1 { + if (a.Type == "" || a.Type == "module") && len(m.Path) > 1 { results = append(results, &StateFilterResult{ Path: m.Path[1:], Address: (&ResourceAddress{Path: m.Path[1:]}).String(), @@ -173,7 +173,7 @@ func (f *StateFilter) relevant(addr *ResourceAddress, raw interface{}) bool { return true case *ResourceState: - if addr.Type == "" { + if addr.Type == "" || addr.Type == "module" { // If we have no resource type, then we're interested in all! return true } diff --git a/terraform/test-fixtures/apply-targeted-module-recursive/child/main.tf b/terraform/test-fixtures/apply-targeted-module-recursive/child/main.tf new file mode 100644 index 000000000000..852bce8b9f39 --- /dev/null +++ b/terraform/test-fixtures/apply-targeted-module-recursive/child/main.tf @@ -0,0 +1,3 @@ +module "subchild" { + source = "./subchild" +} diff --git a/terraform/test-fixtures/apply-targeted-module-recursive/child/subchild/main.tf b/terraform/test-fixtures/apply-targeted-module-recursive/child/subchild/main.tf new file mode 100644 index 000000000000..98f5ee87e9f0 --- /dev/null +++ b/terraform/test-fixtures/apply-targeted-module-recursive/child/subchild/main.tf @@ -0,0 +1,3 @@ +resource "aws_instance" "foo" { + num = "2" +} diff --git a/terraform/test-fixtures/apply-targeted-module-recursive/main.tf b/terraform/test-fixtures/apply-targeted-module-recursive/main.tf new file mode 100644 index 000000000000..0f6991c536ca --- /dev/null +++ b/terraform/test-fixtures/apply-targeted-module-recursive/main.tf @@ -0,0 +1,3 @@ +module "child" { + source = "./child" +} diff --git a/terraform/transform_resource.go b/terraform/transform_resource.go index b877a6051836..f002f13fa5ce 100644 --- a/terraform/transform_resource.go +++ b/terraform/transform_resource.go @@ -84,7 +84,9 @@ func (t *ResourceCountTransformer) nodeIsTargeted(node dag.Vertex) bool { addr := addressable.ResourceAddress() for _, targetAddr := range t.Targets { - if targetAddr.Equals(addr) { + if targetAddr.Type == "module" && targetAddr.ContainedInHierarchy(addr) { + return true + } else if targetAddr.Equals(addr) { return true } } diff --git a/terraform/transform_targets.go b/terraform/transform_targets.go index 4e99baddad9c..663b7349f33c 100644 --- a/terraform/transform_targets.go +++ b/terraform/transform_targets.go @@ -109,7 +109,9 @@ func (t *TargetsTransformer) nodeIsTarget( } addr := r.ResourceAddress() for _, targetAddr := range addrs { - if targetAddr.Equals(addr) { + if targetAddr.Type == "module" && targetAddr.ContainedInHierarchy(addr) { + return true + } else if targetAddr.Equals(addr) { return true } }