From 7fa4c00d1ae6d67b6a28e8bc5dfc17244c970f6e Mon Sep 17 00:00:00 2001 From: James Bardin Date: Fri, 25 Sep 2020 16:59:58 -0400 Subject: [PATCH] add validation for ignore_changes references Ensure that ignore_changes only refers to arguments set in the configuration. --- terraform/context_validate_test.go | 44 ++++++++++++++++++++++++++++++ terraform/eval_validate.go | 31 +++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/terraform/context_validate_test.go b/terraform/context_validate_test.go index 23eeab878..75415f58b 100644 --- a/terraform/context_validate_test.go +++ b/terraform/context_validate_test.go @@ -1712,3 +1712,47 @@ output "out" { } } } + +func TestContext2Validate_invalidIgnoreChanges(t *testing.T) { + // validate module and output depends_on + m := testModuleInline(t, map[string]string{ + "main.tf": ` +resource "test_instance" "a" { + lifecycle { + ignore_changes = [foo] + } +} + +`, + }) + + p := testProvider("test") + p.GetSchemaReturn = &ProviderSchema{ + ResourceTypes: map[string]*configschema.Block{ + "test_instance": { + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Computed: true}, + "foo": {Type: cty.String, Computed: true, Optional: true}, + }, + }, + }, + } + + ctx := testContext2(t, &ContextOpts{ + Config: m, + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + diags := ctx.Validate() + if !diags.HasErrors() { + t.Fatal("succeeded; want errors") + } + + for _, d := range diags { + des := d.Description().Summary + if !strings.Contains(des, "Cannot ignore") { + t.Fatalf(`expected "Invalid depends_on reference", got %q`, des) + } + } +} diff --git a/terraform/eval_validate.go b/terraform/eval_validate.go index 5483c09a6..c9f47b1b7 100644 --- a/terraform/eval_validate.go +++ b/terraform/eval_validate.go @@ -410,8 +410,39 @@ func (n *EvalValidateResource) Validate(ctx EvalContext) error { if cfg.Managed != nil { // can be nil only in tests with poorly-configured mocks for _, traversal := range cfg.Managed.IgnoreChanges { + // This will error out if the traversal contains an invalid + // index step. That is OK if we want users to be able to ignore + // a key that is no longer specified in the config. moreDiags := schema.StaticValidateTraversal(traversal) diags = diags.Append(moreDiags) + if diags.HasErrors() { + continue + } + + // first check to see if this assigned in the config + v, _ := traversal.TraverseRel(configVal) + if !v.IsNull() { + // it's assigned, so we can also assume it's not computed-only + continue + } + + // We can't ignore changes that don't exist in the configuration. + // We're not checking specifically if the traversal resolves to + // a computed-only value, but we can hint to the user that it + // might also be the case. + sourceRange := traversal.SourceRange() + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Cannot ignore argument not set in the configuration", + Detail: fmt.Sprintf("The ignore_changes argument is not set in the configuration.\n" + + "The ignore_changes mechanism only applies to changes " + + "within the configuration, and must be used with " + + "arguments set in the configuration and not computed by " + + "the provider.", + ), + Subject: &sourceRange, + }) + return diags.Err() } }