Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support recursive targeting on modules #9236

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions terraform/context_apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, `
<no 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")
Expand Down
27 changes: 24 additions & 3 deletions terraform/resource_address.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<path>(?:module\.[^.]+\.?)*)` +
`(?P<path>(?:module\.(?P<module_name>[^.]+)\.?)*)` +
// possibly "data.", if targeting is a data resource
`(?P<data_prefix>(?:data\.)?)` +
// "aws_instance.web" (optional when module path specified)
Expand Down
4 changes: 2 additions & 2 deletions terraform/resource_address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func TestParseResourceAddress(t *testing.T) {
"module.a",
&ResourceAddress{
Path: []string{"a"},
Type: "",
Type: "module",
Name: "",
InstanceType: TypePrimary,
Index: -1,
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions terraform/state_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module "subchild" {
source = "./subchild"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
resource "aws_instance" "foo" {
num = "2"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module "child" {
source = "./child"
}
4 changes: 3 additions & 1 deletion terraform/transform_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
4 changes: 3 additions & 1 deletion terraform/transform_targets.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down