You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
terraform/projects/projectconfigs/context.go

112 lines
3.7 KiB

package projectconfigs
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/ext/typeexpr"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/tfdiags"
)
// ContextValue represents a "context" block in the configuration, describing
// a value that can (or possibly must) be set to allow the workspace
// configuration to tailor itself to a specific execution context.
//
// For example, a context value might select the location of a service that
// the workspace configurations depend on but that might be accessed
// differently during development than in production.
//
// Best to keep use of this feature to a minimum, but it's here for pragmatic
// reasons knowing that sometimes it's not possible to make a fully-portable
// Terraform project configuration.
type ContextValue struct {
// Name is the name given for this context value in the configuration.
//
// It is guaranteed to be a valid HCL identifier.
Name string
// Type is the type constraint that given values must conform to.
Type cty.Type
// Default is a default value for this context value, or nil if
// this is a required context value.
Default hcl.Expression
// Description is a human-oriented description of the purpose of this
// context value, written in full sentences in a natural language.
Description string
// DeclRange is the source range of the block header of this block,
// for use in diagnostic messages. NameRange is the range of the
// Name string specifically.
DeclRange, NameRange tfdiags.SourceRange
}
func decodeContextBlock(block *hcl.Block) (*ContextValue, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
cv := &ContextValue{
Name: block.Labels[0],
DeclRange: tfdiags.SourceRangeFromHCL(block.DefRange),
NameRange: tfdiags.SourceRangeFromHCL(block.LabelRanges[0]),
}
if !hclsyntax.ValidIdentifier(cv.Name) {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid name for context value",
Detail: fmt.Sprintf("The name %q is not a valid name for a context value. Must start with a letter, followed by zero or more letters, digits, and underscores.", cv.Name),
Subject: block.LabelRanges[0].Ptr(),
})
}
content, hclDiags := block.Body.Content(contextSchema)
diags = diags.Append(hclDiags)
if attr, ok := content.Attributes["type"]; ok {
ty, hclDiags := typeexpr.TypeConstraint(attr.Expr)
diags = diags.Append(hclDiags)
cv.Type = ty
} else {
cv.Type = cty.DynamicPseudoType
}
if attr, ok := content.Attributes["default"]; ok {
cv.Default = attr.Expr
}
if attr, ok := content.Attributes["description"]; ok {
// We don't allow variables/functions in the description because
// we want to be able to display it in a UI that might be prompting
// for data that would be needed to evaluate expressions, so we'll
// just evaluate this right now and HCL will generate "variables cannot
// be used here" errors in case a user tries.
val, hclDiags := attr.Expr.Value(nil)
diags = diags.Append(hclDiags)
if !diags.HasErrors() {
if val.Type().Equals(cty.String) && !val.IsNull() {
cv.Description = val.AsString()
} else {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid description for context value",
Detail: "The description of a context value must be a string and should contain a natural language description of the meaning of this context value using full sentences.",
Subject: attr.Expr.StartRange().Ptr(),
})
}
}
}
return cv, diags
}
var contextSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: "type"},
{Name: "default"},
{Name: "description"},
},
}