mirror of https://github.com/hashicorp/terraform
This covers all of the expression node types in HIL's AST, and also includes initial support for some of our top-level blocks so that we can easily test that. The initial implementations of the "variable" and "output" blocks are pretty redundant and messy, so we can hopefully improve on these in a later pass.f-hcl2-planstate
parent
1c5d05b1a0
commit
ca4875128e
@ -0,0 +1,3 @@
|
||||
output "foo" {
|
||||
value = "${path.module}"
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
output "foo" {
|
||||
value = path.module
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
locals {
|
||||
# Arithmetic
|
||||
add = "${1 + 2}"
|
||||
sub = "${1 - 2}"
|
||||
mul = "${1 * 2}"
|
||||
mod = "${4 % 2}"
|
||||
and = "${true && true}"
|
||||
or = "${true || true}"
|
||||
equal = "${1 == 2}"
|
||||
not_equal = "${1 != 2}"
|
||||
less_than = "${1 < 2}"
|
||||
greater_than = "${1 > 2}"
|
||||
less_than_eq = "${1 <= 2}"
|
||||
greater_than_eq = "${1 >= 2}"
|
||||
neg = "${- local.add}"
|
||||
|
||||
# Call
|
||||
call_no_args = "${foo()}"
|
||||
call_one_arg = "${foo(1)}"
|
||||
call_two_args = "${foo(1, 2)}"
|
||||
|
||||
# Conditional
|
||||
cond = "${true ? 1 : 2}"
|
||||
|
||||
# Index
|
||||
index_str = "${foo["a"]}"
|
||||
index_num = "${foo[1]}"
|
||||
|
||||
# Variable Access
|
||||
var_access_single = "${foo}"
|
||||
var_access_dot = "${foo.bar}"
|
||||
var_access_splat = "${foo.bar.*.baz}"
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
locals {
|
||||
# Arithmetic
|
||||
add = 1 + 2
|
||||
sub = 1 - 2
|
||||
mul = 1 * 2
|
||||
mod = 4 % 2
|
||||
and = true && true
|
||||
or = true || true
|
||||
equal = 1 == 2
|
||||
not_equal = 1 != 2
|
||||
less_than = 1 < 2
|
||||
greater_than = 1 > 2
|
||||
less_than_eq = 1 <= 2
|
||||
greater_than_eq = 1 >= 2
|
||||
neg = -local.add
|
||||
|
||||
# Call
|
||||
call_no_args = foo()
|
||||
call_one_arg = foo(1)
|
||||
call_two_args = foo(1, 2)
|
||||
|
||||
# Conditional
|
||||
cond = true ? 1 : 2
|
||||
|
||||
# Index
|
||||
index_str = foo["a"]
|
||||
index_num = foo[1]
|
||||
|
||||
# Variable Access
|
||||
var_access_single = foo
|
||||
var_access_dot = foo.bar
|
||||
var_access_splat = foo.bar.*.baz
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
variable "s" {
|
||||
type = "string"
|
||||
}
|
||||
|
||||
variable "l" {
|
||||
type = "list"
|
||||
}
|
||||
|
||||
variable "m" {
|
||||
type = "map"
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
variable "s" {
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "l" {
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "m" {
|
||||
type = map(string)
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
||||
@ -0,0 +1,234 @@
|
||||
package configupgrade
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
hcl2 "github.com/hashicorp/hcl2/hcl"
|
||||
|
||||
hcl1ast "github.com/hashicorp/hcl/hcl/ast"
|
||||
hcl1printer "github.com/hashicorp/hcl/hcl/printer"
|
||||
hcl1token "github.com/hashicorp/hcl/hcl/token"
|
||||
|
||||
"github.com/hashicorp/hil"
|
||||
hilast "github.com/hashicorp/hil/ast"
|
||||
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
func upgradeExpr(val interface{}, filename string, interp bool) ([]byte, tfdiags.Diagnostics) {
|
||||
var buf bytes.Buffer
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
// "val" here can be either a hcl1ast.Node or a hilast.Node, since both
|
||||
// of these correspond to expressions in HCL2. Therefore we need to
|
||||
// comprehensively handle every possible HCL1 *and* HIL AST node type
|
||||
// and, at minimum, print it out as-is in HCL2 syntax.
|
||||
switch tv := val.(type) {
|
||||
|
||||
case *hcl1ast.LiteralType:
|
||||
litVal := tv.Token.Value()
|
||||
switch tv.Token.Type {
|
||||
case hcl1token.STRING:
|
||||
if !interp {
|
||||
// Easy case, then.
|
||||
printQuotedString(&buf, litVal.(string))
|
||||
break
|
||||
}
|
||||
|
||||
hilNode, err := hil.Parse(litVal.(string))
|
||||
if err != nil {
|
||||
diags = diags.Append(&hcl2.Diagnostic{
|
||||
Severity: hcl2.DiagError,
|
||||
Summary: "Invalid interpolated string",
|
||||
Detail: fmt.Sprintf("Interpolation parsing failed: %s", err),
|
||||
Subject: hcl1PosRange(filename, tv.Pos()).Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
interpSrc, interpDiags := upgradeExpr(hilNode, filename, interp)
|
||||
buf.Write(interpSrc)
|
||||
diags = diags.Append(interpDiags)
|
||||
|
||||
case hcl1token.HEREDOC:
|
||||
// TODO: Implement
|
||||
panic("HEREDOC not supported yet")
|
||||
|
||||
case hcl1token.BOOL:
|
||||
if litVal.(bool) {
|
||||
buf.WriteString("true")
|
||||
} else {
|
||||
buf.WriteString("false")
|
||||
}
|
||||
|
||||
default:
|
||||
// For everything else (NUMBER, FLOAT) we'll just pass through the given bytes verbatim.
|
||||
buf.WriteString(tv.Token.Text)
|
||||
|
||||
}
|
||||
|
||||
case hcl1ast.Node:
|
||||
// If our more-specific cases above didn't match this then we'll
|
||||
// ask the hcl1printer package to print the expression out
|
||||
// itself, and assume it'll still be valid in HCL2.
|
||||
// (We should rarely end up here, since our cases above should
|
||||
// be comprehensive.)
|
||||
hcl1printer.Fprint(&buf, tv)
|
||||
|
||||
case *hilast.LiteralNode:
|
||||
switch tl := tv.Value.(type) {
|
||||
case string:
|
||||
// This shouldn't generally happen because literal strings are
|
||||
// always wrapped in hilast.Output in HIL, but we'll allow it anyway.
|
||||
printQuotedString(&buf, tl)
|
||||
case int:
|
||||
buf.WriteString(strconv.Itoa(tl))
|
||||
case float64:
|
||||
buf.WriteString(strconv.FormatFloat(tl, 'f', 64, 64))
|
||||
case bool:
|
||||
if tl {
|
||||
buf.WriteString("true")
|
||||
} else {
|
||||
buf.WriteString("false")
|
||||
}
|
||||
}
|
||||
|
||||
case *hilast.VariableAccess:
|
||||
buf.WriteString(tv.Name)
|
||||
|
||||
case *hilast.Arithmetic:
|
||||
op, exists := hilArithmeticOpSyms[tv.Op]
|
||||
if !exists {
|
||||
panic(fmt.Errorf("arithmetic node with unsupported operator %#v", tv.Op))
|
||||
}
|
||||
|
||||
lhsExpr := tv.Exprs[0]
|
||||
rhsExpr := tv.Exprs[1]
|
||||
lhsSrc, exprDiags := upgradeExpr(lhsExpr, filename, true)
|
||||
diags = diags.Append(exprDiags)
|
||||
rhsSrc, exprDiags := upgradeExpr(rhsExpr, filename, true)
|
||||
diags = diags.Append(exprDiags)
|
||||
|
||||
// HIL's AST represents -foo as (0 - foo), so we'll recognize
|
||||
// that here and normalize it back.
|
||||
if tv.Op == hilast.ArithmeticOpSub && len(lhsSrc) == 1 && lhsSrc[0] == '0' {
|
||||
buf.WriteString("-")
|
||||
buf.Write(rhsSrc)
|
||||
break
|
||||
}
|
||||
|
||||
buf.Write(lhsSrc)
|
||||
buf.WriteString(op)
|
||||
buf.Write(rhsSrc)
|
||||
|
||||
case *hilast.Call:
|
||||
name := tv.Func
|
||||
args := tv.Args
|
||||
|
||||
buf.WriteString(name)
|
||||
buf.WriteByte('(')
|
||||
for i, arg := range args {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
|
||||
exprSrc, exprDiags := upgradeExpr(arg, filename, true)
|
||||
diags = diags.Append(exprDiags)
|
||||
buf.Write(exprSrc)
|
||||
}
|
||||
buf.WriteByte(')')
|
||||
|
||||
case *hilast.Conditional:
|
||||
condSrc, exprDiags := upgradeExpr(tv.CondExpr, filename, true)
|
||||
diags = diags.Append(exprDiags)
|
||||
trueSrc, exprDiags := upgradeExpr(tv.TrueExpr, filename, true)
|
||||
diags = diags.Append(exprDiags)
|
||||
falseSrc, exprDiags := upgradeExpr(tv.FalseExpr, filename, true)
|
||||
diags = diags.Append(exprDiags)
|
||||
|
||||
buf.Write(condSrc)
|
||||
buf.WriteString(" ? ")
|
||||
buf.Write(trueSrc)
|
||||
buf.WriteString(" : ")
|
||||
buf.Write(falseSrc)
|
||||
|
||||
case *hilast.Index:
|
||||
targetSrc, exprDiags := upgradeExpr(tv.Target, filename, true)
|
||||
diags = diags.Append(exprDiags)
|
||||
keySrc, exprDiags := upgradeExpr(tv.Key, filename, true)
|
||||
diags = diags.Append(exprDiags)
|
||||
buf.Write(targetSrc)
|
||||
buf.WriteString("[")
|
||||
buf.Write(keySrc)
|
||||
buf.WriteString("]")
|
||||
|
||||
case *hilast.Output:
|
||||
if len(tv.Exprs) == 1 {
|
||||
item := tv.Exprs[0]
|
||||
naked := true
|
||||
if lit, ok := item.(*hilast.LiteralNode); ok {
|
||||
if _, ok := lit.Value.(string); ok {
|
||||
naked = false
|
||||
}
|
||||
}
|
||||
if naked {
|
||||
// If there's only one expression and it isn't a literal string
|
||||
// then we'll just output it naked, since wrapping a single
|
||||
// expression in interpolation is no longer idiomatic.
|
||||
interped, interpDiags := upgradeExpr(item, filename, true)
|
||||
diags = diags.Append(interpDiags)
|
||||
buf.Write(interped)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString(`"`)
|
||||
for _, item := range tv.Exprs {
|
||||
if lit, ok := item.(*hilast.LiteralNode); ok {
|
||||
if litStr, ok := lit.Value.(string); ok {
|
||||
printStringLiteralFromHILOutput(&buf, litStr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
interped, interpDiags := upgradeExpr(item, filename, true)
|
||||
diags = diags.Append(interpDiags)
|
||||
|
||||
buf.WriteString("${")
|
||||
buf.Write(interped)
|
||||
buf.WriteString("}")
|
||||
}
|
||||
buf.WriteString(`"`)
|
||||
|
||||
case hilast.Node:
|
||||
// Nothing reasonable we can do here, so we should've handled all of
|
||||
// the possibilities above.
|
||||
panic(fmt.Errorf("upgradeExpr doesn't handle HIL node type %T", tv))
|
||||
|
||||
default:
|
||||
// If we end up in here then the caller gave us something completely invalid.
|
||||
panic(fmt.Errorf("upgradeExpr on unsupported type %T", val))
|
||||
|
||||
}
|
||||
|
||||
return buf.Bytes(), diags
|
||||
}
|
||||
|
||||
var hilArithmeticOpSyms = map[hilast.ArithmeticOp]string{
|
||||
hilast.ArithmeticOpAdd: " + ",
|
||||
hilast.ArithmeticOpSub: " - ",
|
||||
hilast.ArithmeticOpMul: " * ",
|
||||
hilast.ArithmeticOpDiv: " / ",
|
||||
hilast.ArithmeticOpMod: " % ",
|
||||
|
||||
hilast.ArithmeticOpLogicalAnd: " && ",
|
||||
hilast.ArithmeticOpLogicalOr: " || ",
|
||||
|
||||
hilast.ArithmeticOpEqual: " == ",
|
||||
hilast.ArithmeticOpNotEqual: " != ",
|
||||
hilast.ArithmeticOpLessThan: " < ",
|
||||
hilast.ArithmeticOpLessThanOrEqual: " <= ",
|
||||
hilast.ArithmeticOpGreaterThan: " > ",
|
||||
hilast.ArithmeticOpGreaterThanOrEqual: " >= ",
|
||||
}
|
||||
@ -0,0 +1,436 @@
|
||||
package configupgrade
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
|
||||
hcl1ast "github.com/hashicorp/hcl/hcl/ast"
|
||||
hcl1parser "github.com/hashicorp/hcl/hcl/parser"
|
||||
hcl1printer "github.com/hashicorp/hcl/hcl/printer"
|
||||
hcl1token "github.com/hashicorp/hcl/hcl/token"
|
||||
|
||||
hcl2 "github.com/hashicorp/hcl2/hcl"
|
||||
hcl2syntax "github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
type upgradeFileResult struct {
|
||||
Content []byte
|
||||
ProviderRequirements map[string]version.Constraints
|
||||
}
|
||||
|
||||
func upgradeNativeSyntaxFile(filename string, src []byte) (upgradeFileResult, tfdiags.Diagnostics) {
|
||||
var result upgradeFileResult
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
f, err := hcl1parser.Parse(src)
|
||||
if err != nil {
|
||||
return result, diags.Append(&hcl2.Diagnostic{
|
||||
Severity: hcl2.DiagError,
|
||||
Summary: "Syntax error in configuration file",
|
||||
Detail: fmt.Sprintf("Error while parsing: %s", err),
|
||||
Subject: hcl1ErrSubjectRange(filename, err),
|
||||
})
|
||||
}
|
||||
|
||||
rootList := f.Node.(*hcl1ast.ObjectList)
|
||||
rootItems := rootList.Items
|
||||
adhocComments := collectAdhocComments(f)
|
||||
|
||||
for _, item := range rootItems {
|
||||
comments := adhocComments.TakeBefore(item)
|
||||
for _, group := range comments {
|
||||
printComments(&buf, group)
|
||||
buf.WriteByte('\n') // Extra separator after each group
|
||||
}
|
||||
|
||||
blockType := item.Keys[0].Token.Value().(string)
|
||||
labels := make([]string, len(item.Keys)-1)
|
||||
for i, key := range item.Keys[1:] {
|
||||
labels[i] = key.Token.Value().(string)
|
||||
}
|
||||
body, isObject := item.Val.(*hcl1ast.ObjectType)
|
||||
if !isObject {
|
||||
// Should never happen for valid input, since we don't expect
|
||||
// any non-block items at our top level.
|
||||
diags = diags.Append(&hcl2.Diagnostic{
|
||||
Severity: hcl2.DiagWarning,
|
||||
Summary: "Unsupported top-level attribute",
|
||||
Detail: fmt.Sprintf("Attribute %q is not expected here, so its expression was not migrated.", blockType),
|
||||
Subject: hcl1PosRange(filename, item.Keys[0].Pos()).Ptr(),
|
||||
})
|
||||
// Preserve the item as-is, using the hcl1printer package.
|
||||
buf.WriteString("# TF-UPGRADE-TODO: Top-level attributes are not valid, so this was not automatically migrated.\n")
|
||||
hcl1printer.Fprint(&buf, item)
|
||||
buf.WriteString("\n\n")
|
||||
continue
|
||||
}
|
||||
|
||||
switch blockType {
|
||||
|
||||
case "variable":
|
||||
printComments(&buf, item.LeadComment)
|
||||
printBlockOpen(&buf, blockType, labels, item.LineComment)
|
||||
args := body.List.Items
|
||||
for i, arg := range args {
|
||||
if len(arg.Keys) != 1 {
|
||||
// Should never happen for valid input, since there are no nested blocks expected here.
|
||||
diags = diags.Append(&hcl2.Diagnostic{
|
||||
Severity: hcl2.DiagWarning,
|
||||
Summary: "Invalid nested block",
|
||||
Detail: fmt.Sprintf("Blocks of type %q are not expected here, so this was not automatically migrated.", arg.Keys[0].Token.Value().(string)),
|
||||
Subject: hcl1PosRange(filename, arg.Keys[0].Pos()).Ptr(),
|
||||
})
|
||||
// Preserve the item as-is, using the hcl1printer package.
|
||||
buf.WriteString("\n# TF-UPGRADE-TODO: Blocks are not expected here, so this was not automatically migrated.\n")
|
||||
hcl1printer.Fprint(&buf, arg)
|
||||
buf.WriteString("\n\n")
|
||||
continue
|
||||
}
|
||||
|
||||
comments := adhocComments.TakeBefore(arg)
|
||||
for _, group := range comments {
|
||||
printComments(&buf, group)
|
||||
buf.WriteByte('\n') // Extra separator after each group
|
||||
}
|
||||
|
||||
printComments(&buf, arg.LeadComment)
|
||||
|
||||
switch arg.Keys[0].Token.Value() {
|
||||
case "type":
|
||||
// It is no longer idiomatic to place the type keyword in quotes,
|
||||
// so we'll unquote it here as long as it looks like the result
|
||||
// will be valid.
|
||||
if lit, isLit := arg.Val.(*hcl1ast.LiteralType); isLit {
|
||||
if lit.Token.Type == hcl1token.STRING {
|
||||
kw := lit.Token.Value().(string)
|
||||
if hcl2syntax.ValidIdentifier(kw) {
|
||||
|
||||
// "list" and "map" in older versions really meant
|
||||
// list and map of strings, so we'll migrate to
|
||||
// that and let the user adjust to "any" as
|
||||
// the element type if desired.
|
||||
switch strings.TrimSpace(kw) {
|
||||
case "list":
|
||||
kw = "list(string)"
|
||||
case "map":
|
||||
kw = "map(string)"
|
||||
}
|
||||
|
||||
printAttribute(&buf, "type", []byte(kw), arg.LineComment)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we got something invalid there then we'll just fall through
|
||||
// into the default case and migrate it as a normal expression.
|
||||
fallthrough
|
||||
default:
|
||||
valSrc, valDiags := upgradeExpr(arg.Val, filename, false)
|
||||
diags = diags.Append(valDiags)
|
||||
printAttribute(&buf, arg.Keys[0].Token.Value().(string), valSrc, arg.LineComment)
|
||||
}
|
||||
|
||||
// If we have another item and it's more than one line away
|
||||
// from the current one then we'll print an extra blank line
|
||||
// to retain that separation.
|
||||
if (i + 1) < len(args) {
|
||||
next := args[i+1]
|
||||
thisPos := arg.Pos()
|
||||
nextPos := next.Pos()
|
||||
if nextPos.Line-thisPos.Line > 1 {
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
}
|
||||
buf.WriteString("}\n\n")
|
||||
|
||||
case "output":
|
||||
printComments(&buf, item.LeadComment)
|
||||
printBlockOpen(&buf, blockType, labels, item.LineComment)
|
||||
args := body.List.Items
|
||||
for i, arg := range args {
|
||||
if len(arg.Keys) != 1 {
|
||||
// Should never happen for valid input, since there are no nested blocks expected here.
|
||||
diags = diags.Append(&hcl2.Diagnostic{
|
||||
Severity: hcl2.DiagWarning,
|
||||
Summary: "Invalid nested block",
|
||||
Detail: fmt.Sprintf("Blocks of type %q are not expected here, so this was not automatically migrated.", arg.Keys[0].Token.Value().(string)),
|
||||
Subject: hcl1PosRange(filename, arg.Keys[0].Pos()).Ptr(),
|
||||
})
|
||||
// Preserve the item as-is, using the hcl1printer package.
|
||||
buf.WriteString("\n# TF-UPGRADE-TODO: Blocks are not expected here, so this was not automatically migrated.\n")
|
||||
hcl1printer.Fprint(&buf, arg)
|
||||
buf.WriteString("\n\n")
|
||||
continue
|
||||
}
|
||||
|
||||
comments := adhocComments.TakeBefore(arg)
|
||||
for _, group := range comments {
|
||||
printComments(&buf, group)
|
||||
buf.WriteByte('\n') // Extra separator after each group
|
||||
}
|
||||
|
||||
printComments(&buf, arg.LeadComment)
|
||||
|
||||
interp := false
|
||||
switch arg.Keys[0].Token.Value() {
|
||||
case "value":
|
||||
interp = true
|
||||
}
|
||||
|
||||
valSrc, valDiags := upgradeExpr(arg.Val, filename, interp)
|
||||
diags = diags.Append(valDiags)
|
||||
printAttribute(&buf, arg.Keys[0].Token.Value().(string), valSrc, arg.LineComment)
|
||||
|
||||
// If we have another item and it's more than one line away
|
||||
// from the current one then we'll print an extra blank line
|
||||
// to retain that separation.
|
||||
if (i + 1) < len(args) {
|
||||
next := args[i+1]
|
||||
thisPos := arg.Pos()
|
||||
nextPos := next.Pos()
|
||||
if nextPos.Line-thisPos.Line > 1 {
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
}
|
||||
buf.WriteString("}\n\n")
|
||||
|
||||
case "locals":
|
||||
printComments(&buf, item.LeadComment)
|
||||
printBlockOpen(&buf, blockType, labels, item.LineComment)
|
||||
|
||||
args := body.List.Items
|
||||
for i, arg := range args {
|
||||
if len(arg.Keys) != 1 {
|
||||
// Should never happen for valid input, since there are no nested blocks expected here.
|
||||
diags = diags.Append(&hcl2.Diagnostic{
|
||||
Severity: hcl2.DiagWarning,
|
||||
Summary: "Invalid nested block",
|
||||
Detail: fmt.Sprintf("Blocks of type %q are not expected here, so this was not automatically migrated.", arg.Keys[0].Token.Value().(string)),
|
||||
Subject: hcl1PosRange(filename, arg.Keys[0].Pos()).Ptr(),
|
||||
})
|
||||
// Preserve the item as-is, using the hcl1printer package.
|
||||
buf.WriteString("\n# TF-UPGRADE-TODO: Blocks are not expected here, so this was not automatically migrated.\n")
|
||||
hcl1printer.Fprint(&buf, arg)
|
||||
buf.WriteString("\n\n")
|
||||
continue
|
||||
}
|
||||
|
||||
comments := adhocComments.TakeBefore(arg)
|
||||
for _, group := range comments {
|
||||
printComments(&buf, group)
|
||||
buf.WriteByte('\n') // Extra separator after each group
|
||||
}
|
||||
|
||||
printComments(&buf, arg.LeadComment)
|
||||
|
||||
name := arg.Keys[0].Token.Value().(string)
|
||||
expr := arg.Val
|
||||
exprSrc, exprDiags := upgradeExpr(expr, filename, true)
|
||||
diags = diags.Append(exprDiags)
|
||||
printAttribute(&buf, name, exprSrc, arg.LineComment)
|
||||
|
||||
// If we have another item and it's more than one line away
|
||||
// from the current one then we'll print an extra blank line
|
||||
// to retain that separation.
|
||||
if (i + 1) < len(args) {
|
||||
next := args[i+1]
|
||||
thisPos := arg.Pos()
|
||||
nextPos := next.Pos()
|
||||
if nextPos.Line-thisPos.Line > 1 {
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
}
|
||||
buf.WriteString("}\n\n")
|
||||
|
||||
default:
|
||||
// Should never happen for valid input, because the above cases
|
||||
// are exhaustive for valid blocks as of Terraform 0.11.
|
||||
diags = diags.Append(&hcl2.Diagnostic{
|
||||
Severity: hcl2.DiagWarning,
|
||||
Summary: "Unsupported root block type",
|
||||
Detail: fmt.Sprintf("The block type %q is not expected here, so its content was not migrated.", blockType),
|
||||
Subject: hcl1PosRange(filename, item.Keys[0].Pos()).Ptr(),
|
||||
})
|
||||
|
||||
// Preserve the block as-is, using the hcl1printer package.
|
||||
buf.WriteString("# TF-UPGRADE-TODO: Block type was not recognized, so this block and its contents were not automatically migrated.\n")
|
||||
hcl1printer.Fprint(&buf, item)
|
||||
buf.WriteString("\n\n")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Print out any leftover comments
|
||||
for _, group := range *adhocComments {
|
||||
printComments(&buf, group)
|
||||
}
|
||||
|
||||
result.Content = buf.Bytes()
|
||||
|
||||
return result, diags
|
||||
}
|
||||
|
||||
func printComments(buf *bytes.Buffer, group *hcl1ast.CommentGroup) {
|
||||
if group == nil {
|
||||
return
|
||||
}
|
||||
for _, comment := range group.List {
|
||||
buf.WriteString(comment.Text)
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
func printBlockOpen(buf *bytes.Buffer, blockType string, labels []string, commentGroup *hcl1ast.CommentGroup) {
|
||||
buf.WriteString(blockType)
|
||||
for _, label := range labels {
|
||||
buf.WriteByte(' ')
|
||||
printQuotedString(buf, label)
|
||||
}
|
||||
buf.WriteString(" {")
|
||||
if commentGroup != nil {
|
||||
for _, c := range commentGroup.List {
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(c.Text)
|
||||
}
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
|
||||
func printAttribute(buf *bytes.Buffer, name string, valSrc []byte, commentGroup *hcl1ast.CommentGroup) {
|
||||
buf.WriteString(name)
|
||||
buf.WriteString(" = ")
|
||||
buf.Write(valSrc)
|
||||
if commentGroup != nil {
|
||||
for _, c := range commentGroup.List {
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(c.Text)
|
||||
}
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
|
||||
func printQuotedString(buf *bytes.Buffer, val string) {
|
||||
buf.WriteByte('"')
|
||||
printStringLiteralFromHILOutput(buf, val)
|
||||
buf.WriteByte('"')
|
||||
}
|
||||
|
||||
func printStringLiteralFromHILOutput(buf *bytes.Buffer, val string) {
|
||||
val = strings.Replace(val, `\`, `\\`, -1)
|
||||
val = strings.Replace(val, `"`, `\"`, -1)
|
||||
val = strings.Replace(val, "\n", `\n`, -1)
|
||||
val = strings.Replace(val, "\r", `\r`, -1)
|
||||
val = strings.Replace(val, `${`, `$${`, -1)
|
||||
val = strings.Replace(val, `%{`, `%%{`, -1)
|
||||
buf.WriteString(val)
|
||||
}
|
||||
|
||||
func collectAdhocComments(f *hcl1ast.File) *commentQueue {
|
||||
comments := make(map[hcl1token.Pos]*hcl1ast.CommentGroup)
|
||||
for _, c := range f.Comments {
|
||||
comments[c.Pos()] = c
|
||||
}
|
||||
|
||||
// We'll remove from our map any comments that are attached to specific
|
||||
// nodes as lead or line comments, since we'll find those during our
|
||||
// walk anyway.
|
||||
hcl1ast.Walk(f, func(nn hcl1ast.Node) (hcl1ast.Node, bool) {
|
||||
switch t := nn.(type) {
|
||||
case *hcl1ast.LiteralType:
|
||||
if t.LeadComment != nil {
|
||||
for _, comment := range t.LeadComment.List {
|
||||
delete(comments, comment.Pos())
|
||||
}
|
||||
}
|
||||
|
||||
if t.LineComment != nil {
|
||||
for _, comment := range t.LineComment.List {
|
||||
delete(comments, comment.Pos())
|
||||
}
|
||||
}
|
||||
case *hcl1ast.ObjectItem:
|
||||
if t.LeadComment != nil {
|
||||
for _, comment := range t.LeadComment.List {
|
||||
delete(comments, comment.Pos())
|
||||
}
|
||||
}
|
||||
|
||||
if t.LineComment != nil {
|
||||
for _, comment := range t.LineComment.List {
|
||||
delete(comments, comment.Pos())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nn, true
|
||||
})
|
||||
|
||||
if len(comments) == 0 {
|
||||
var ret commentQueue
|
||||
return &ret
|
||||
}
|
||||
|
||||
ret := make([]*hcl1ast.CommentGroup, 0, len(comments))
|
||||
for _, c := range comments {
|
||||
ret = append(ret, c)
|
||||
}
|
||||
sort.Slice(ret, func(i, j int) bool {
|
||||
return ret[i].Pos().Before(ret[j].Pos())
|
||||
})
|
||||
queue := commentQueue(ret)
|
||||
return &queue
|
||||
}
|
||||
|
||||
type commentQueue []*hcl1ast.CommentGroup
|
||||
|
||||
func (q *commentQueue) TakeBefore(node hcl1ast.Node) []*hcl1ast.CommentGroup {
|
||||
toPos := node.Pos()
|
||||
var i int
|
||||
for i = 0; i < len(*q); i++ {
|
||||
if (*q)[i].Pos().After(toPos) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret := (*q)[:i]
|
||||
*q = (*q)[i:]
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func hcl1ErrSubjectRange(filename string, err error) *hcl2.Range {
|
||||
if pe, isPos := err.(*hcl1parser.PosError); isPos {
|
||||
return hcl1PosRange(filename, pe.Pos)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hcl1PosRange(filename string, pos hcl1token.Pos) *hcl2.Range {
|
||||
return &hcl2.Range{
|
||||
Filename: filename,
|
||||
Start: hcl2.Pos{
|
||||
Line: pos.Line,
|
||||
Column: pos.Column,
|
||||
Byte: pos.Offset,
|
||||
},
|
||||
End: hcl2.Pos{
|
||||
Line: pos.Line,
|
||||
Column: pos.Column,
|
||||
Byte: pos.Offset,
|
||||
},
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue