opentofu/configs/named_values.go
Martin Atkins 36fb5b52e7 configs: quoted keywords/references are warnings, not errors
In our new loader we are changing certain values in configuration to be
naked keywords or references rather than quoted strings as before. Since
many of these have been shown in books, tutorials, and our own
documentation we will make the old forms generate deprecation warnings
rather than errors so that newcomers starting from older documentation
can be eased into the new syntax, rather than getting blocked.

This will also avoid creating a hard compatibility wall for reusable
modules that are already published, allowing them to still be used in
spite of these warnings and then fixed when the maintainer is able.
2018-02-15 15:56:39 -08:00

222 lines
5.2 KiB
Go

package configs
import (
"fmt"
"github.com/hashicorp/hcl2/gohcl"
"github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hcl/hclsyntax"
"github.com/zclconf/go-cty/cty"
)
// A consistent detail message for all "not a valid identifier" diagnostics.
const badIdentifierDetail = "A name must start with a letter and may contain only letters, digits, underscores, and dashes."
// Variable represents a "variable" block in a module or file.
type Variable struct {
Name string
Description string
Default cty.Value
TypeHint VariableTypeHint
DescriptionSet bool
DeclRange hcl.Range
}
func decodeVariableBlock(block *hcl.Block) (*Variable, hcl.Diagnostics) {
v := &Variable{
Name: block.Labels[0],
DeclRange: block.DefRange,
}
content, diags := block.Body.Content(variableBlockSchema)
if !hclsyntax.ValidIdentifier(v.Name) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid variable name",
Detail: badIdentifierDetail,
Subject: &block.LabelRanges[0],
})
}
// Don't allow declaration of variables that would conflict with the
// reserved attribute and block type names in a "module" block, since
// these won't be usable for child modules.
for _, attr := range moduleBlockSchema.Attributes {
if attr.Name == v.Name {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid variable name",
Detail: fmt.Sprintf("The variable name %q is reserved due to its special meaning inside module blocks.", attr.Name),
Subject: &block.LabelRanges[0],
})
}
}
if attr, exists := content.Attributes["description"]; exists {
valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Description)
diags = append(diags, valDiags...)
v.DescriptionSet = true
}
if attr, exists := content.Attributes["default"]; exists {
val, valDiags := attr.Expr.Value(nil)
diags = append(diags, valDiags...)
v.Default = val
}
if attr, exists := content.Attributes["type"]; exists {
expr, shimDiags := shimTraversalInString(attr.Expr, true)
diags = append(diags, shimDiags...)
switch hcl.ExprAsKeyword(expr) {
case "string":
v.TypeHint = TypeHintString
case "list":
v.TypeHint = TypeHintList
case "map":
v.TypeHint = TypeHintMap
default:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid variable type hint",
Detail: "The type argument requires one of the following keywords: string, list, or map.",
Subject: expr.Range().Ptr(),
})
}
}
return v, diags
}
// Output represents an "output" block in a module or file.
type Output struct {
Name string
Description string
Expr hcl.Expression
DependsOn []hcl.Traversal
Sensitive bool
DescriptionSet bool
SensitiveSet bool
DeclRange hcl.Range
}
func decodeOutputBlock(block *hcl.Block, override bool) (*Output, hcl.Diagnostics) {
o := &Output{
Name: block.Labels[0],
DeclRange: block.DefRange,
}
schema := outputBlockSchema
if override {
schema = schemaForOverrides(schema)
}
content, diags := block.Body.Content(schema)
if !hclsyntax.ValidIdentifier(o.Name) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid output name",
Detail: badIdentifierDetail,
Subject: &block.LabelRanges[0],
})
}
if attr, exists := content.Attributes["description"]; exists {
valDiags := gohcl.DecodeExpression(attr.Expr, nil, &o.Description)
diags = append(diags, valDiags...)
o.DescriptionSet = true
}
if attr, exists := content.Attributes["value"]; exists {
o.Expr = attr.Expr
}
if attr, exists := content.Attributes["sensitive"]; exists {
valDiags := gohcl.DecodeExpression(attr.Expr, nil, &o.Sensitive)
diags = append(diags, valDiags...)
o.SensitiveSet = true
}
if attr, exists := content.Attributes["depends_on"]; exists {
deps, depsDiags := decodeDependsOn(attr)
diags = append(diags, depsDiags...)
o.DependsOn = append(o.DependsOn, deps...)
}
return o, diags
}
// Local represents a single entry from a "locals" block in a module or file.
// The "locals" block itself is not represented, because it serves only to
// provide context for us to interpret its contents.
type Local struct {
Name string
Expr hcl.Expression
DeclRange hcl.Range
}
func decodeLocalsBlock(block *hcl.Block) ([]*Local, hcl.Diagnostics) {
attrs, diags := block.Body.JustAttributes()
if len(attrs) == 0 {
return nil, diags
}
locals := make([]*Local, 0, len(attrs))
for name, attr := range attrs {
if !hclsyntax.ValidIdentifier(name) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid local value name",
Detail: badIdentifierDetail,
Subject: &attr.NameRange,
})
}
locals = append(locals, &Local{
Name: name,
Expr: attr.Expr,
DeclRange: attr.Range,
})
}
return locals, diags
}
var variableBlockSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "description",
},
{
Name: "default",
},
{
Name: "type",
},
},
}
var outputBlockSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "description",
},
{
Name: "value",
Required: true,
},
{
Name: "depends_on",
},
{
Name: "sensitive",
},
},
}