diff --git a/configs/compat_shim.go b/configs/compat_shim.go index 69d8b0d0f..2f803b3fa 100644 --- a/configs/compat_shim.go +++ b/configs/compat_shim.go @@ -3,6 +3,7 @@ package configs import ( "github.com/hashicorp/hcl2/hcl" "github.com/hashicorp/hcl2/hcl/hclsyntax" + "github.com/zclconf/go-cty/cty" ) // ------------------------------------------------------------------------- @@ -79,3 +80,20 @@ func shimTraversalInString(expr hcl.Expression, wantKeyword bool) (hcl.Expressio SrcRange: srcRange, }, diags } + +// shimIsIgnoreChangesStar returns true if the given expression seems to be +// a string literal whose value is "*". This is used to support a legacy +// form of ignore_changes = all . +// +// This function does not itself emit any diagnostics, so it's the caller's +// responsibility to emit a warning diagnostic when this function returns true. +func shimIsIgnoreChangesStar(expr hcl.Expression) bool { + val, valDiags := expr.Value(nil) + if valDiags.HasErrors() { + return false + } + if val.Type() != cty.String || val.IsNull() || !val.IsKnown() { + return false + } + return val.AsString() == "*" +} diff --git a/configs/parser_config_test.go b/configs/parser_config_test.go index ae7c8856d..ff90d5eba 100644 --- a/configs/parser_config_test.go +++ b/configs/parser_config_test.go @@ -129,6 +129,16 @@ func TestParserLoadConfigFileFailureMessages(t *testing.T) { hcl.DiagWarning, "Quoted references are deprecated", }, + { + "valid-files/resources-ignorechanges-all-legacy.tf", + hcl.DiagWarning, + "Deprecated ignore_changes wildcard", + }, + { + "valid-files/resources-ignorechanges-all-legacy.tf.json", + hcl.DiagWarning, + "Deprecated ignore_changes wildcard", + }, { "valid-files/resources-provisioner-when-quoted.tf", hcl.DiagWarning, diff --git a/configs/resource.go b/configs/resource.go index 1fa16553f..e6c9ca2af 100644 --- a/configs/resource.go +++ b/configs/resource.go @@ -26,6 +26,7 @@ type ManagedResource struct { CreateBeforeDestroy bool PreventDestroy bool IgnoreChanges []hcl.Traversal + IgnoreAllChanges bool CreateBeforeDestroySet bool PreventDestroySet bool @@ -118,19 +119,66 @@ func decodeResourceBlock(block *hcl.Block) (*ManagedResource, hcl.Diagnostics) { } if attr, exists := lcContent.Attributes["ignore_changes"]; exists { - exprs, listDiags := hcl.ExprList(attr.Expr) - diags = append(diags, listDiags...) - for _, expr := range exprs { - expr, shimDiags := shimTraversalInString(expr, false) - diags = append(diags, shimDiags...) + // ignore_changes can either be a list of relative traversals + // or it can be just the keyword "all" to ignore changes to this + // resource entirely. + // ignore_changes = [ami, instance_type] + // ignore_changes = all + // We also allow two legacy forms for compatibility with earlier + // versions: + // ignore_changes = ["ami", "instance_type"] + // ignore_changes = ["*"] - traversal, travDiags := hcl.RelTraversalForExpr(expr) - diags = append(diags, travDiags...) - if len(traversal) != 0 { - r.IgnoreChanges = append(r.IgnoreChanges, traversal) + kw := hcl.ExprAsKeyword(attr.Expr) + + switch { + case kw == "all": + r.IgnoreAllChanges = true + default: + exprs, listDiags := hcl.ExprList(attr.Expr) + diags = append(diags, listDiags...) + + var ignoreAllRange hcl.Range + + for _, expr := range exprs { + + // our expr might be the literal string "*", which + // we accept as a deprecated way of saying "all". + if shimIsIgnoreChangesStar(expr) { + r.IgnoreAllChanges = true + ignoreAllRange = expr.Range() + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Deprecated ignore_changes wildcard", + Detail: "The [\"*\"] form of ignore_changes wildcard is reprecated. Use \"ignore_changes = all\" to ignore changes to all attributes.", + Subject: attr.Expr.Range().Ptr(), + }) + continue + } + + expr, shimDiags := shimTraversalInString(expr, false) + diags = append(diags, shimDiags...) + + traversal, travDiags := hcl.RelTraversalForExpr(expr) + diags = append(diags, travDiags...) + if len(traversal) != 0 { + r.IgnoreChanges = append(r.IgnoreChanges, traversal) + } } + + if r.IgnoreAllChanges && len(r.IgnoreChanges) != 0 { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid ignore_changes ruleset", + Detail: "Cannot mix wildcard string \"*\" with non-wildcard references.", + Subject: &ignoreAllRange, + Context: attr.Expr.Range().Ptr(), + }) + } + } + } case "connection": diff --git a/configs/test-fixtures/invalid-files/resources-ignorechanges-all-legacymix.tf b/configs/test-fixtures/invalid-files/resources-ignorechanges-all-legacymix.tf new file mode 100644 index 000000000..9557379aa --- /dev/null +++ b/configs/test-fixtures/invalid-files/resources-ignorechanges-all-legacymix.tf @@ -0,0 +1,5 @@ +resource "aws_instance" "web" { + lifecycle { + ignore_changes = ["*", "foo"] + } +} diff --git a/configs/test-fixtures/valid-files/resources-ignorechanges-all-legacy.tf b/configs/test-fixtures/valid-files/resources-ignorechanges-all-legacy.tf new file mode 100644 index 000000000..6b5e61a9c --- /dev/null +++ b/configs/test-fixtures/valid-files/resources-ignorechanges-all-legacy.tf @@ -0,0 +1,5 @@ +resource "aws_instance" "web" { + lifecycle { + ignore_changes = ["*"] + } +} diff --git a/configs/test-fixtures/valid-files/resources-ignorechanges-all-legacy.tf.json b/configs/test-fixtures/valid-files/resources-ignorechanges-all-legacy.tf.json new file mode 100644 index 000000000..5502dcd50 --- /dev/null +++ b/configs/test-fixtures/valid-files/resources-ignorechanges-all-legacy.tf.json @@ -0,0 +1,11 @@ +{ + "resource": { + "aws_instance": { + "web": { + "lifecycle": { + "ignore_changes": ["*"] + } + } + } + } +} diff --git a/configs/test-fixtures/valid-files/resources-ignorechanges-all.tf b/configs/test-fixtures/valid-files/resources-ignorechanges-all.tf new file mode 100644 index 000000000..32cd23288 --- /dev/null +++ b/configs/test-fixtures/valid-files/resources-ignorechanges-all.tf @@ -0,0 +1,5 @@ +resource "aws_instance" "web" { + lifecycle { + ignore_changes = all + } +} diff --git a/configs/test-fixtures/valid-files/resources-ignorechanges-all.tf.json b/configs/test-fixtures/valid-files/resources-ignorechanges-all.tf.json new file mode 100644 index 000000000..c22020826 --- /dev/null +++ b/configs/test-fixtures/valid-files/resources-ignorechanges-all.tf.json @@ -0,0 +1,11 @@ +{ + "resource": { + "aws_instance": { + "web": { + "lifecycle": { + "ignore_changes": "all" + } + } + } + } +}