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.
This commit is contained in:
Martin Atkins 2017-06-15 18:15:41 -07:00
parent d3eb2b2d28
commit a8c58b081c
9 changed files with 117 additions and 9 deletions

View File

@ -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, `
<no state>
module.child.subchild:
aws_instance.foo:
ID = foo
num = 2
type = aws_instance
`)
}

View File

@ -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{},

View File

@ -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{},

View File

@ -0,0 +1,3 @@
module "subchild" {
source = "./subchild"
}

View File

@ -0,0 +1,3 @@
resource "aws_instance" "foo" {
num = "2"
}

View File

@ -0,0 +1,3 @@
module "child" {
source = "./child"
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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,