diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index 465695c96..b81f730af 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -5948,6 +5948,61 @@ resource "aws_instance" "foo" { } } +func TestContext2Plan_targetResourceInModuleInstance(t *testing.T) { + m := testModuleInline(t, map[string]string{ + "main.tf": ` +module "mod" { + count = 3 + source = "./mod" +} +`, + "mod/main.tf": ` +resource "aws_instance" "foo" { +} +`, + }) + + p := testProvider("aws") + p.DiffFn = testDiffFn + + target, diags := addrs.ParseTargetStr("module.mod[1].aws_instance.foo") + if diags.HasErrors() { + t.Fatal(diags.ErrWithWarnings()) + } + + targets := []addrs.Targetable{target.Subject} + + ctx := testContext2(t, &ContextOpts{ + Config: m, + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), + }, + Targets: targets, + }) + + plan, diags := ctx.Plan() + if diags.HasErrors() { + t.Fatal(diags.ErrWithWarnings()) + } + + expected := map[string]plans.Action{ + // the single targeted mod[1] instance + `module.mod[1].aws_instance.foo`: plans.Create, + } + + for _, res := range plan.Changes.Resources { + want := expected[res.Addr.String()] + if res.Action != want { + t.Fatalf("expected %s action, got: %q %s", want, res.Addr, res.Action) + } + delete(expected, res.Addr.String()) + } + + for res, action := range expected { + t.Errorf("missing %s change for %s", action, res) + } +} + func TestContext2Plan_moduleRefIndex(t *testing.T) { m := testModuleInline(t, map[string]string{ "main.tf": ` @@ -6109,3 +6164,50 @@ resource "test_instance" "b" { _, diags := ctx.Plan() assertNoErrors(t, diags) } + +func TestContext2Plan_targetedModuleInstance(t *testing.T) { + m := testModule(t, "plan-targeted") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext2(t, &ContextOpts{ + Config: m, + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), + }, + Targets: []addrs.Targetable{ + addrs.RootModuleInstance.Child("mod", addrs.IntKey(0)), + }, + }) + + plan, diags := ctx.Plan() + if diags.HasErrors() { + t.Fatalf("unexpected errors: %s", diags.Err()) + } + schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] + ty := schema.ImpliedType() + + if len(plan.Changes.Resources) != 1 { + t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) + } + + for _, res := range plan.Changes.Resources { + ric, err := res.Decode(ty) + if err != nil { + t.Fatal(err) + } + + switch i := ric.Addr.String(); i { + case "module.mod[0].aws_instance.foo": + if res.Action != plans.Create { + t.Fatalf("resource %s should be created", i) + } + checkVals(t, objectVal(t, schema, map[string]cty.Value{ + "id": cty.UnknownVal(cty.String), + "num": cty.NumberIntVal(2), + "type": cty.StringVal("aws_instance"), + }), ric.After) + default: + t.Fatal("unknown instance:", i) + } + } +} diff --git a/terraform/graph_builder_plan.go b/terraform/graph_builder_plan.go index 1cb0e5ee2..0354ce238 100644 --- a/terraform/graph_builder_plan.go +++ b/terraform/graph_builder_plan.go @@ -157,12 +157,6 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer { // Target &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, }, // Detect when create_before_destroy must be forced on for a particular diff --git a/terraform/graph_builder_refresh.go b/terraform/graph_builder_refresh.go index 546fdff63..e91123a1c 100644 --- a/terraform/graph_builder_refresh.go +++ b/terraform/graph_builder_refresh.go @@ -176,12 +176,6 @@ func (b *RefreshGraphBuilder) Steps() []GraphTransformer { // Target &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 diff --git a/terraform/testdata/plan-targeted/main.tf b/terraform/testdata/plan-targeted/main.tf index 1b6cdae67..ab00a845f 100644 --- a/terraform/testdata/plan-targeted/main.tf +++ b/terraform/testdata/plan-targeted/main.tf @@ -3,5 +3,10 @@ resource "aws_instance" "foo" { } resource "aws_instance" "bar" { - foo = "${aws_instance.foo.num}" + foo = aws_instance.foo.num +} + +module "mod" { + source = "./mod" + count = 1 } diff --git a/terraform/testdata/plan-targeted/mod/main.tf b/terraform/testdata/plan-targeted/mod/main.tf new file mode 100644 index 000000000..98f5ee87e --- /dev/null +++ b/terraform/testdata/plan-targeted/mod/main.tf @@ -0,0 +1,3 @@ +resource "aws_instance" "foo" { + num = "2" +} diff --git a/terraform/transform_targets.go b/terraform/transform_targets.go index e2b2cf525..0989390e4 100644 --- a/terraform/transform_targets.go +++ b/terraform/transform_targets.go @@ -22,12 +22,6 @@ type GraphNodeTargetable interface { type TargetsTransformer struct { // List of targeted resource names specified by the user Targets []addrs.Targetable - - // 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 } func (t *TargetsTransformer) Transform(g *Graph) error { @@ -133,25 +127,29 @@ func (t *TargetsTransformer) nodeIsTarget(v dag.Vertex, targets []addrs.Targetab vertexAddr = r.ResourceInstanceAddr() case GraphNodeConfigResource: vertexAddr = r.ResourceAddr() + default: // Only resource and resource instance nodes can be targeted. return false } for _, targetAddr := range targets { - if t.IgnoreIndices { - // If we're ignoring indices then we'll convert any resource instance - // addresses into resource addresses. We don't need to convert - // vertexAddr because instance addresses are contained within - // their associated resources, and so .TargetContains will take - // care of this for us. - switch instance := targetAddr.(type) { + switch vertexAddr.(type) { + case addrs.ConfigResource: + // Before expansion happens, we only have nodes that know their + // ConfigResource address. We need to take the more specific + // target addresses and generalize them in order to compare with a + // ConfigResource. + switch target := targetAddr.(type) { case addrs.AbsResourceInstance: - targetAddr = instance.ContainingResource().Config() + targetAddr = target.ContainingResource().Config() + case addrs.AbsResource: + targetAddr = target.Config() case addrs.ModuleInstance: - targetAddr = instance.Module() + targetAddr = target.Module() } } + if targetAddr.TargetContains(vertexAddr) { return true }