diff --git a/configs/configupgrade/test-fixtures/valid/lifecycle/input/lifecycle.tf b/configs/configupgrade/test-fixtures/valid/lifecycle/input/lifecycle.tf new file mode 100644 index 0000000000..df085738a2 --- /dev/null +++ b/configs/configupgrade/test-fixtures/valid/lifecycle/input/lifecycle.tf @@ -0,0 +1,29 @@ +resource "test_instance" "foo" { + lifecycle { + create_before_destroy = true + prevent_destroy = true + } +} + +resource "test_instance" "bar" { + lifecycle { + ignore_changes = ["*"] + } +} + +resource "test_instance" "baz" { + lifecycle { + ignore_changes = [ + "image", + "tags.name", + ] + } +} + +resource "test_instance" "boop" { + lifecycle { + ignore_changes = [ + "image", + ] + } +} diff --git a/configs/configupgrade/test-fixtures/valid/lifecycle/want/lifecycle.tf b/configs/configupgrade/test-fixtures/valid/lifecycle/want/lifecycle.tf new file mode 100644 index 0000000000..4b8fc33bec --- /dev/null +++ b/configs/configupgrade/test-fixtures/valid/lifecycle/want/lifecycle.tf @@ -0,0 +1,27 @@ +resource "test_instance" "foo" { + lifecycle { + create_before_destroy = true + prevent_destroy = true + } +} + +resource "test_instance" "bar" { + lifecycle { + ignore_changes = all + } +} + +resource "test_instance" "baz" { + lifecycle { + ignore_changes = [ + image, + tags.name, + ] + } +} + +resource "test_instance" "boop" { + lifecycle { + ignore_changes = [image] + } +} diff --git a/configs/configupgrade/test-fixtures/valid/lifecycle/want/versions.tf b/configs/configupgrade/test-fixtures/valid/lifecycle/want/versions.tf new file mode 100644 index 0000000000..d9b6f790b9 --- /dev/null +++ b/configs/configupgrade/test-fixtures/valid/lifecycle/want/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.12" +} diff --git a/configs/configupgrade/upgrade_body.go b/configs/configupgrade/upgrade_body.go index ab9827a3af..fa49c39d94 100644 --- a/configs/configupgrade/upgrade_body.go +++ b/configs/configupgrade/upgrade_body.go @@ -65,20 +65,7 @@ func maybeBareKeywordAttributeRule(filename string, an *analysis, specials map[s func maybeBareTraversalAttributeRule(filename string, an *analysis) bodyItemRule { exprRule := func(val interface{}) ([]byte, tfdiags.Diagnostics) { - // If the expression is a literal that would be valid as a naked - // absolute traversal then we'll turn it into one. - if lit, isLit := val.(*hcl1ast.LiteralType); isLit { - if lit.Token.Type == hcl1token.STRING { - trStr := lit.Token.Value().(string) - trSrc := []byte(trStr) - _, trDiags := hcl2syntax.ParseTraversalAbs(trSrc, "", hcl2.Pos{}) - if !trDiags.HasErrors() { - return trSrc, nil - } - } - } - - return upgradeExpr(val, filename, false, an) + return upgradeTraversalExpr(val, filename, an) } return attributeRule(filename, cty.String, an, exprRule) } @@ -373,3 +360,54 @@ func justAttributesBodyRules(filename string, body *hcl1ast.ObjectType, an *anal } return rules } + +func lifecycleBlockBodyRules(filename string, an *analysis) bodyContentRules { + return bodyContentRules{ + "create_before_destroy": noInterpAttributeRule(filename, cty.Bool, an), + "prevent_destroy": noInterpAttributeRule(filename, cty.Bool, an), + "ignore_changes": func(buf *bytes.Buffer, blockAddr string, item *hcl1ast.ObjectItem) tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + val, ok := item.Val.(*hcl1ast.ListType) + if !ok { + diags = diags.Append(&hcl2.Diagnostic{ + Severity: hcl2.DiagError, + Summary: "Invalid providers argument", + Detail: `The "providers" argument must be a map from provider addresses in the child module to corresponding provider addresses in this module.`, + Subject: hcl1PosRange(filename, item.Keys[0].Pos()).Ptr(), + }) + return diags + } + + // As a special case, we'll map the single-element list ["*"] to + // the new keyword "all". + if len(val.List) == 1 { + if lit, ok := val.List[0].(*hcl1ast.LiteralType); ok { + if lit.Token.Value() == "*" { + printAttribute(buf, item.Keys[0].Token.Value().(string), []byte("all"), item.LineComment) + return diags + } + } + } + + var exprBuf bytes.Buffer + multiline := len(val.List) > 1 + exprBuf.WriteByte('[') + if multiline { + exprBuf.WriteByte('\n') + } + for _, node := range val.List { + itemSrc, moreDiags := upgradeTraversalExpr(node, filename, an) + diags = diags.Append(moreDiags) + exprBuf.Write(itemSrc) + if multiline { + exprBuf.WriteString(",\n") + } + } + exprBuf.WriteByte(']') + + printAttribute(buf, item.Keys[0].Token.Value().(string), exprBuf.Bytes(), item.LineComment) + + return diags + }, + } +} diff --git a/configs/configupgrade/upgrade_expr.go b/configs/configupgrade/upgrade_expr.go index 770a264115..fb4464731c 100644 --- a/configs/configupgrade/upgrade_expr.go +++ b/configs/configupgrade/upgrade_expr.go @@ -392,6 +392,18 @@ Value: return buf.Bytes(), diags } +func upgradeTraversalExpr(val interface{}, filename string, an *analysis) ([]byte, tfdiags.Diagnostics) { + if lit, ok := val.(*hcl1ast.LiteralType); ok && lit.Token.Type == hcl1token.STRING { + trStr := lit.Token.Value().(string) + trSrc := []byte(trStr) + _, trDiags := hcl2syntax.ParseTraversalAbs(trSrc, "", hcl2.Pos{}) + if !trDiags.HasErrors() { + return trSrc, nil + } + } + return upgradeExpr(val, filename, false, an) +} + var hilArithmeticOpSyms = map[hilast.ArithmeticOp]string{ hilast.ArithmeticOpAdd: " + ", hilast.ArithmeticOpSub: " - ", diff --git a/configs/configupgrade/upgrade_native.go b/configs/configupgrade/upgrade_native.go index 096250fb0b..30913c14b3 100644 --- a/configs/configupgrade/upgrade_native.go +++ b/configs/configupgrade/upgrade_native.go @@ -327,6 +327,7 @@ func (u *Upgrader) upgradeNativeSyntaxResource(filename string, buf *bytes.Buffe rules := schemaDefaultBodyRules(filename, schema, an, adhocComments) rules["count"] = normalAttributeRule(filename, cty.Number, an) rules["provider"] = maybeBareTraversalAttributeRule(filename, an) + rules["lifecycle"] = nestedBlockRule(filename, lifecycleBlockBodyRules(filename, an), an, adhocComments) printComments(buf, item.LeadComment) printBlockOpen(buf, blockType, labels, item.LineComment)