228 lines
10 KiB
Go
228 lines
10 KiB
Go
package configs
|
|
|
|
import (
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Functions in this file are compatibility shims intended to ease conversion
|
|
// from the old configuration loader. Any use of these functions that makes
|
|
// a change should generate a deprecation warning explaining to the user how
|
|
// to update their code for new patterns.
|
|
//
|
|
// Shims are particularly important for any patterns that have been widely
|
|
// documented in books, tutorials, etc. Users will still be starting from
|
|
// these examples and we want to help them adopt the latest patterns rather
|
|
// than leave them stranded.
|
|
// -------------------------------------------------------------------------
|
|
|
|
// shimTraversalInString takes any arbitrary expression and checks if it is
|
|
// a quoted string in the native syntax. If it _is_, then it is parsed as a
|
|
// traversal and re-wrapped into a synthetic traversal expression and a
|
|
// warning is generated. Otherwise, the given expression is just returned
|
|
// verbatim.
|
|
//
|
|
// This function has no effect on expressions from the JSON syntax, since
|
|
// traversals in strings are the required pattern in that syntax.
|
|
//
|
|
// If wantKeyword is set, the generated warning diagnostic will talk about
|
|
// keywords rather than references. The behavior is otherwise unchanged, and
|
|
// the caller remains responsible for checking that the result is indeed
|
|
// a keyword, e.g. using hcl.ExprAsKeyword.
|
|
func shimTraversalInString(expr hcl.Expression, wantKeyword bool) (hcl.Expression, hcl.Diagnostics) {
|
|
// ObjectConsKeyExpr is a special wrapper type used for keys on object
|
|
// constructors to deal with the fact that naked identifiers are normally
|
|
// handled as "bareword" strings rather than as variable references. Since
|
|
// we know we're interpreting as a traversal anyway (and thus it won't
|
|
// matter whether it's a string or an identifier) we can safely just unwrap
|
|
// here and then process whatever we find inside as normal.
|
|
if ocke, ok := expr.(*hclsyntax.ObjectConsKeyExpr); ok {
|
|
expr = ocke.Wrapped
|
|
}
|
|
|
|
if !exprIsNativeQuotedString(expr) {
|
|
return expr, nil
|
|
}
|
|
|
|
strVal, diags := expr.Value(nil)
|
|
if diags.HasErrors() || strVal.IsNull() || !strVal.IsKnown() {
|
|
// Since we're not even able to attempt a shim here, we'll discard
|
|
// the diagnostics we saw so far and let the caller's own error
|
|
// handling take care of reporting the invalid expression.
|
|
return expr, nil
|
|
}
|
|
|
|
// The position handling here isn't _quite_ right because it won't
|
|
// take into account any escape sequences in the literal string, but
|
|
// it should be close enough for any error reporting to make sense.
|
|
srcRange := expr.Range()
|
|
startPos := srcRange.Start // copy
|
|
startPos.Column++ // skip initial quote
|
|
startPos.Byte++ // skip initial quote
|
|
|
|
traversal, tDiags := hclsyntax.ParseTraversalAbs(
|
|
[]byte(strVal.AsString()),
|
|
srcRange.Filename,
|
|
startPos,
|
|
)
|
|
diags = append(diags, tDiags...)
|
|
|
|
if wantKeyword {
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagWarning,
|
|
Summary: "Quoted keywords are deprecated",
|
|
Detail: "In this context, keywords are expected literally rather than in quotes. Terraform 0.11 and earlier required quotes, but quoted keywords are now deprecated and will be removed in a future version of Terraform. Remove the quotes surrounding this keyword to silence this warning.",
|
|
Subject: &srcRange,
|
|
})
|
|
} else {
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagWarning,
|
|
Summary: "Quoted references are deprecated",
|
|
Detail: "In this context, references are expected literally rather than in quotes. Terraform 0.11 and earlier required quotes, but quoted references are now deprecated and will be removed in a future version of Terraform. Remove the quotes surrounding this reference to silence this warning.",
|
|
Subject: &srcRange,
|
|
})
|
|
}
|
|
|
|
return &hclsyntax.ScopeTraversalExpr{
|
|
Traversal: traversal,
|
|
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() == "*"
|
|
}
|
|
|
|
// warnForDeprecatedInterpolations returns warning diagnostics if the given
|
|
// body can be proven to contain attributes whose expressions are native
|
|
// syntax expressions consisting entirely of a single template interpolation,
|
|
// which is a deprecated way to include a non-literal value in configuration.
|
|
//
|
|
// This is a best-effort sort of thing which relies on the physical HCL native
|
|
// syntax AST, so it might not catch everything. The main goal is to catch the
|
|
// "obvious" cases in order to help spread awareness that this old form is
|
|
// deprecated, when folks copy it from older examples they've found on the
|
|
// internet that were written for Terraform 0.11 or earlier.
|
|
func warnForDeprecatedInterpolationsInBody(body hcl.Body) hcl.Diagnostics {
|
|
var diags hcl.Diagnostics
|
|
|
|
nativeBody, ok := body.(*hclsyntax.Body)
|
|
if !ok {
|
|
// If it's not native syntax then we've nothing to do here.
|
|
return diags
|
|
}
|
|
|
|
for _, attr := range nativeBody.Attributes {
|
|
moreDiags := warnForDeprecatedInterpolationsInExpr(attr.Expr)
|
|
diags = append(diags, moreDiags...)
|
|
}
|
|
|
|
for _, block := range nativeBody.Blocks {
|
|
// We'll also go hunting in nested blocks
|
|
moreDiags := warnForDeprecatedInterpolationsInBody(block.Body)
|
|
diags = append(diags, moreDiags...)
|
|
}
|
|
|
|
return diags
|
|
}
|
|
|
|
func warnForDeprecatedInterpolationsInExpr(expr hcl.Expression) hcl.Diagnostics {
|
|
node, ok := expr.(hclsyntax.Node)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
walker := warnForDeprecatedInterpolationsWalker{
|
|
// create some capacity so that we can deal with simple expressions
|
|
// without any further allocation during our walk.
|
|
contextStack: make([]warnForDeprecatedInterpolationsContext, 0, 16),
|
|
}
|
|
return hclsyntax.Walk(node, &walker)
|
|
}
|
|
|
|
// warnForDeprecatedInterpolationsWalker is an implementation of
|
|
// hclsyntax.Walker that we use to generate deprecation warnings for template
|
|
// expressions that consist entirely of a single interpolation directive.
|
|
// That's always redundant in Terraform v0.12 and later, but tends to show up
|
|
// when people work from examples written for Terraform v0.11 or earlier.
|
|
type warnForDeprecatedInterpolationsWalker struct {
|
|
contextStack []warnForDeprecatedInterpolationsContext
|
|
}
|
|
|
|
var _ hclsyntax.Walker = (*warnForDeprecatedInterpolationsWalker)(nil)
|
|
|
|
type warnForDeprecatedInterpolationsContext int
|
|
|
|
const (
|
|
warnForDeprecatedInterpolationsNormal warnForDeprecatedInterpolationsContext = 0
|
|
warnForDeprecatedInterpolationsObjKey warnForDeprecatedInterpolationsContext = 1
|
|
)
|
|
|
|
func (w *warnForDeprecatedInterpolationsWalker) Enter(node hclsyntax.Node) hcl.Diagnostics {
|
|
var diags hcl.Diagnostics
|
|
|
|
context := warnForDeprecatedInterpolationsNormal
|
|
switch node := node.(type) {
|
|
case *hclsyntax.ObjectConsKeyExpr:
|
|
context = warnForDeprecatedInterpolationsObjKey
|
|
case *hclsyntax.TemplateWrapExpr:
|
|
// hclsyntax.TemplateWrapExpr is a special node type used by HCL only
|
|
// for the situation where a template is just a single interpolation,
|
|
// so we don't need to do anything further to distinguish that
|
|
// situation. ("normal" templates are *hclsyntax.TemplateExpr.)
|
|
|
|
const summary = "Interpolation-only expressions are deprecated"
|
|
switch w.currentContext() {
|
|
case warnForDeprecatedInterpolationsObjKey:
|
|
// This case requires a different resolution in order to retain
|
|
// the same meaning, so we have a different detail message for
|
|
// it.
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagWarning,
|
|
Summary: summary,
|
|
Detail: "Terraform 0.11 and earlier required all non-constant expressions to be provided via interpolation syntax, but this pattern is now deprecated.\n\nTo silence this warning, replace the \"${ opening sequence and the }\" closing sequence with opening and closing parentheses respectively. Parentheses are needed here to mark this as an expression to be evaluated, rather than as a literal string key.\n\nTemplate interpolation syntax is still used to construct strings from expressions when the template includes multiple interpolation sequences or a mixture of literal strings and interpolations. This deprecation applies only to templates that consist entirely of a single interpolation sequence.",
|
|
Subject: node.Range().Ptr(),
|
|
})
|
|
default:
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagWarning,
|
|
Summary: summary,
|
|
Detail: "Terraform 0.11 and earlier required all non-constant expressions to be provided via interpolation syntax, but this pattern is now deprecated. To silence this warning, remove the \"${ sequence from the start and the }\" sequence from the end of this expression, leaving just the inner expression.\n\nTemplate interpolation syntax is still used to construct strings from expressions when the template includes multiple interpolation sequences or a mixture of literal strings and interpolations. This deprecation applies only to templates that consist entirely of a single interpolation sequence.",
|
|
Subject: node.Range().Ptr(),
|
|
})
|
|
}
|
|
}
|
|
|
|
// Note the context of the current node for when we potentially visit
|
|
// child nodes.
|
|
w.contextStack = append(w.contextStack, context)
|
|
return diags
|
|
}
|
|
|
|
func (w *warnForDeprecatedInterpolationsWalker) Exit(node hclsyntax.Node) hcl.Diagnostics {
|
|
w.contextStack = w.contextStack[:len(w.contextStack)-1]
|
|
return nil
|
|
}
|
|
|
|
func (w *warnForDeprecatedInterpolationsWalker) currentContext() warnForDeprecatedInterpolationsContext {
|
|
if len(w.contextStack) == 0 {
|
|
return warnForDeprecatedInterpolationsNormal
|
|
}
|
|
return w.contextStack[len(w.contextStack)-1]
|
|
}
|