From 1bba574fe93076d7f406adfcbb67247c205bb6ba Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 18 Jun 2019 16:03:33 -0700 Subject: [PATCH] website: Document ignore_changes for individual map elements This also includes a previously-missing test that verifies the behavior described here, implemented as a planning context test for consistency with how the other ignore_changes tests are handled. --- terraform/context_plan_test.go | 81 +++++++++++++++++++ .../ignore-changes-in-map.tf | 13 +++ website/docs/configuration/resources.html.md | 29 +++++++ 3 files changed, 123 insertions(+) create mode 100644 terraform/test-fixtures/plan-ignore-changes-in-map/ignore-changes-in-map.tf diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index d1a45d62e..bf30539f6 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -4870,6 +4870,87 @@ func TestContext2Plan_ignoreChangesWildcard(t *testing.T) { } } +func TestContext2Plan_ignoreChangesInMap(t *testing.T) { + p := testProvider("test") + + p.GetSchemaReturn = &ProviderSchema{ + ResourceTypes: map[string]*configschema.Block{ + "test_ignore_changes_map": { + Attributes: map[string]*configschema.Attribute{ + "tags": {Type: cty.Map(cty.String), Optional: true}, + }, + }, + }, + } + p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { + return providers.PlanResourceChangeResponse{ + PlannedState: req.ProposedNewState, + } + } + + p.DiffFn = testDiffFn + + s := states.BuildState(func(ss *states.SyncState) { + ss.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_ignore_changes_map", + Name: "foo", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"tags":{"ignored":"from state","other":"from state"}}`), + }, + addrs.ProviderConfig{ + Type: "test", + }.Absolute(addrs.RootModuleInstance), + ) + }) + m := testModule(t, "plan-ignore-changes-in-map") + + ctx := testContext2(t, &ContextOpts{ + Config: m, + ProviderResolver: providers.ResolverFixed( + map[string]providers.Factory{ + "test": testProviderFuncFixed(p), + }, + ), + State: s, + }) + + plan, diags := ctx.Plan() + if diags.HasErrors() { + t.Fatalf("unexpected errors: %s", diags.Err()) + } + + schema := p.GetSchemaReturn.ResourceTypes["test_ignore_changes_map"] + ty := schema.ImpliedType() + + if got, want := len(plan.Changes.Resources), 1; got != want { + t.Fatalf("wrong number of changes %d; want %d", got, want) + } + + res := plan.Changes.Resources[0] + ric, err := res.Decode(ty) + if err != nil { + t.Fatal(err) + } + if res.Action != plans.Update { + t.Fatalf("resource %s should be updated, got %s", ric.Addr, res.Action) + } + + if got, want := ric.Addr.String(), "test_ignore_changes_map.foo"; got != want { + t.Fatalf("unexpected resource address %s; want %s", got, want) + } + + checkVals(t, objectVal(t, schema, map[string]cty.Value{ + "tags": cty.MapVal(map[string]cty.Value{ + "ignored": cty.StringVal("from state"), + "other": cty.StringVal("from config"), + }), + }), ric.After) +} + func TestContext2Plan_moduleMapLiteral(t *testing.T) { m := testModule(t, "plan-module-map-literal") p := testProvider("aws") diff --git a/terraform/test-fixtures/plan-ignore-changes-in-map/ignore-changes-in-map.tf b/terraform/test-fixtures/plan-ignore-changes-in-map/ignore-changes-in-map.tf new file mode 100644 index 000000000..75adcac5c --- /dev/null +++ b/terraform/test-fixtures/plan-ignore-changes-in-map/ignore-changes-in-map.tf @@ -0,0 +1,13 @@ + +resource "test_ignore_changes_map" "foo" { + tags = { + ignored = "from config" + other = "from config" + } + + lifecycle { + ignore_changes = [ + tags["ignored"], + ] + } +} diff --git a/website/docs/configuration/resources.html.md b/website/docs/configuration/resources.html.md index 4cb618440..1744a8d62 100644 --- a/website/docs/configuration/resources.html.md +++ b/website/docs/configuration/resources.html.md @@ -436,6 +436,35 @@ meta-arguments are supported: } ``` + You can also ignore specific map elements by writing references like + `tags["Name"]` in the `ignore_changes` list, though with an important + caveat: the ignoring applies only to in-place updates to an existing + key. Adding or removing a key is treated by Terraform as a change to the + containing map itself rather than to the individual key, and so if you + wish to ignore changes to a particular tag made by an external system + you must ensure that the Terraform configuration creates a placeholder + element for that tag name so that the external system changes will be + understood as an in-place edit of that key: + + ```hcl + resource "aws_instance" "example" { + # ... + + tags = { + # Initial value for Name is overridden by our automatic scheduled + # re-tagging process; changes to this are ignored by ignore_changes + # below. + Name = "placeholder" + } + + lifecycle { + ignore_changes = [ + tags["Name"], + ] + } + } + ``` + Instead of a list, the special keyword `all` may be used to instruct Terraform to ignore _all_ attributes, which means that Terraform can create and destroy the remote object but will never propose updates to it.