219 lines
5.4 KiB
Go
219 lines
5.4 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
|
|
|
|
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...)
|
|
}
|
|
|
|
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 {
|
|
switch hcl.ExprAsKeyword(attr.Expr) {
|
|
case "string":
|
|
v.TypeHint = TypeHintString
|
|
case "list":
|
|
v.TypeHint = TypeHintList
|
|
case "map":
|
|
v.TypeHint = TypeHintMap
|
|
default:
|
|
// In our legacy configuration format these keywords would've been
|
|
// provided as quoted strings, so we'll generate a special error
|
|
// message for that to help those who find outdated examples and
|
|
// would otherwise be confused.
|
|
if exprIsNativeQuotedString(attr.Expr) {
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid variable type hint",
|
|
Detail: "The type hint keyword must not be given in quotes.",
|
|
Subject: attr.Expr.Range().Ptr(),
|
|
})
|
|
} else {
|
|
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: attr.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
|
|
|
|
DeclRange hcl.Range
|
|
}
|
|
|
|
func decodeOutputBlock(block *hcl.Block) (*Output, hcl.Diagnostics) {
|
|
o := &Output{
|
|
Name: block.Labels[0],
|
|
DeclRange: block.DefRange,
|
|
}
|
|
|
|
content, diags := block.Body.Content(outputBlockSchema)
|
|
|
|
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...)
|
|
}
|
|
|
|
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...)
|
|
}
|
|
|
|
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",
|
|
},
|
|
},
|
|
}
|