From a8c58b081c963d1faed3b5fdb547722561f278b9 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 15 Jun 2017 18:15:41 -0700 Subject: [PATCH] core: -target option to also select resources in descendant modules Previously the behavior for -target when given a module address was to target only resources directly within that module, ignoring any resources defined in child modules. This behavior turned out to be counter-intuitive, since users expected the -target address to be interpreted hierarchically. We'll now use the new "Contains" function for addresses, which provides a hierarchical "containment" concept that is more consistent with user expectations. In particular, it allows module.foo to match module.foo.module.bar.aws_instance.baz, where before that would not have been true. Since Contains isn't commutative (unlike Equals) this requires some special handling for targeting specific indices. When given an argument like -target=aws_instance.foo[0], the initial graph construction (for both plan and refresh) is for the resource nodes from configuration, which have not yet been expanded to separate indexed instances. Thus we need to do the first pass of TargetsTransformer in mode where indices are ignored, with the work then completed by the DynamicExpand method which re-applies the TargetsTransformer in index-sensitive mode. This is a breaking change for anyone depending on the previous behavior of -target, since it will now select more resources than before. There is no way provided to obtain the previous behavior. Eventually we may support negative targeting, which could then combine with positive targets to regain the previous behavior as an explicit choice. --- terraform/context_apply_test.go | 42 +++++++++++++++++++ terraform/graph_builder_plan.go | 10 ++++- terraform/graph_builder_refresh.go | 10 ++++- .../child/main.tf | 3 ++ .../child/subchild/main.tf | 3 ++ .../apply-targeted-module-recursive/main.tf | 3 ++ terraform/transform_targets.go | 13 +++++- website/docs/commands/apply.html.markdown | 6 +-- website/docs/commands/plan.html.markdown | 36 ++++++++++++++-- 9 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 terraform/test-fixtures/apply-targeted-module-recursive/child/main.tf create mode 100644 terraform/test-fixtures/apply-targeted-module-recursive/child/subchild/main.tf create mode 100644 terraform/test-fixtures/apply-targeted-module-recursive/main.tf diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index 044e6666d..4ee704e45 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -8719,3 +8719,45 @@ func TestContext2Apply_multiRef(t *testing.T) { t.Fatalf("expected 1 depends_on entry for aws_instance.create, got %q", deps) } } + +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, + ProviderResolver: ResourceProviderResolverFixed( + 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 + `) +} diff --git a/terraform/graph_builder_plan.go b/terraform/graph_builder_plan.go index a6a3a90d4..4b29bbb4b 100644 --- a/terraform/graph_builder_plan.go +++ b/terraform/graph_builder_plan.go @@ -117,7 +117,15 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer { &CountBoundaryTransformer{}, // Target - &TargetsTransformer{Targets: b.Targets}, + &TargetsTransformer{ + Targets: b.Targets, + + // Resource nodes from config have not yet been expanded for + // "count", so we must apply targeting without indices. Exact + // targeting will be dealt with later when these resources + // DynamicExpand. + IgnoreIndices: true, + }, // Close opened plugin connections &CloseProviderTransformer{}, diff --git a/terraform/graph_builder_refresh.go b/terraform/graph_builder_refresh.go index 0634f9698..3d3e968fa 100644 --- a/terraform/graph_builder_refresh.go +++ b/terraform/graph_builder_refresh.go @@ -144,7 +144,15 @@ func (b *RefreshGraphBuilder) Steps() []GraphTransformer { &ReferenceTransformer{}, // Target - &TargetsTransformer{Targets: b.Targets}, + &TargetsTransformer{ + Targets: b.Targets, + + // Resource nodes from config have not yet been expanded for + // "count", so we must apply targeting without indices. Exact + // targeting will be dealt with later when these resources + // DynamicExpand. + IgnoreIndices: true, + }, // Close opened plugin connections &CloseProviderTransformer{}, 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 000000000..852bce8b9 --- /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 000000000..98f5ee87e --- /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 000000000..0f6991c53 --- /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_targets.go b/terraform/transform_targets.go index 125f9e302..4f117b4f7 100644 --- a/terraform/transform_targets.go +++ b/terraform/transform_targets.go @@ -41,6 +41,12 @@ type TargetsTransformer struct { // that already have the targets parsed ParsedTargets []ResourceAddress + // If set, the index portions of resource addresses will be ignored + // for comparison. This is used when transforming a graph where + // counted resources have not yet been expanded, since otherwise + // the unexpanded nodes (which never have indices) would not match. + IgnoreIndices bool + // Set to true when we're in a `terraform destroy` or a // `terraform plan -destroy` Destroy bool @@ -199,7 +205,12 @@ func (t *TargetsTransformer) nodeIsTarget( addr := r.ResourceAddr() for _, targetAddr := range addrs { - if targetAddr.Equals(addr) { + if t.IgnoreIndices { + // targetAddr is not a pointer, so we can safely mutate it without + // interfering with references elsewhere. + targetAddr.Index = -1 + } + if targetAddr.Contains(addr) { return true } } diff --git a/website/docs/commands/apply.html.markdown b/website/docs/commands/apply.html.markdown index 3c00e7c05..55c26c7ad 100644 --- a/website/docs/commands/apply.html.markdown +++ b/website/docs/commands/apply.html.markdown @@ -54,9 +54,9 @@ The command-line flags are all optional. The list of available flags are: [remote state](/docs/state/remote.html) is used. * `-target=resource` - A [Resource - Address](/docs/internals/resource-addressing.html) to target. Operation will - be limited to this resource and its dependencies. This flag can be used - multiple times. + Address](/docs/internals/resource-addressing.html) to target. For more + information, see + [the targeting docs from `terraform plan`](/docs/commands/plan.html#resource-targeting). * `-var 'foo=bar'` - Set a variable in the Terraform configuration. This flag can be set multiple times. Variable values are interpreted as diff --git a/website/docs/commands/plan.html.markdown b/website/docs/commands/plan.html.markdown index 5d4c91039..de4f91594 100644 --- a/website/docs/commands/plan.html.markdown +++ b/website/docs/commands/plan.html.markdown @@ -63,9 +63,8 @@ The command-line flags are all optional. The list of available flags are: Ignored when [remote state](/docs/state/remote.html) is used. * `-target=resource` - A [Resource - Address](/docs/internals/resource-addressing.html) to target. Operation will - be limited to this resource and its dependencies. This flag can be used - multiple times. + Address](/docs/internals/resource-addressing.html) to target. This flag can + be used multiple times. See below for more information. * `-var 'foo=bar'` - Set a variable in the Terraform configuration. This flag can be set multiple times. Variable values are interpreted as @@ -78,6 +77,37 @@ The command-line flags are all optional. The list of available flags are: files specified by `-var-file` override any values in a "terraform.tfvars". This flag can be used multiple times. +## Resource Targeting + +The `-target` option can be used to focus Terraform's attention on only a +subset of resources. +[Resource Address](/docs/internals/resource-addressing.html) syntax is used +to specify the constraint. The resource address is interpreted as follows: + +* If the given address has a _resource spec_, only the specified resource + is targeted. If the named resource uses `count` and no explicit index + is specified in the address, all of the instances sharing the given + resource name are targeted. + +* The the given address _does not_ have a resource spec, and instead just + specifies a module path, the target applies to all resources in the + specified module _and_ all of the descendent modules of the specified + module. + +This targeting capability is provided for exceptional circumstances, such +as recovering from mistakes or working around Terraform limitations. It +is *not recommended* to use `-target` for routine operations, since this can +lead to undetected configuration drift and confusion about how the true state +of resources relates to configuration. + +Instead of using `-target` as a means to operate on isolated portions of very +large configurations, prefer instead to break large configurations into +several smaller configurations that can each be independently applied. +[Data sources](/docs/configuration/data-sources.html) can be used to access +information about resources created in other configurations, allowing +a complex system architecture to be broken down into more managable parts +that can be updated independently. + ## Security Warning Saved plan files (with the `-out` flag) encode the configuration,