119 lines
3.1 KiB
Go
119 lines
3.1 KiB
Go
|
package configs
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
|
||
|
"github.com/hashicorp/hcl2/hcl"
|
||
|
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||
|
"github.com/zclconf/go-cty/cty"
|
||
|
)
|
||
|
|
||
|
// SynthBody produces a synthetic hcl.Body that behaves as if it had attributes
|
||
|
// corresponding to the elements given in the values map.
|
||
|
//
|
||
|
// This is useful in situations where, for example, values provided on the
|
||
|
// command line can override values given in configuration, using MergeBodies.
|
||
|
//
|
||
|
// The given filename is used in case any diagnostics are returned. Since
|
||
|
// the created body is synthetic, it is likely that this will not be a "real"
|
||
|
// filename. For example, if from a command line argument it could be
|
||
|
// a representation of that argument's name, such as "-var=...".
|
||
|
func SynthBody(filename string, values map[string]cty.Value) hcl.Body {
|
||
|
return synthBody{
|
||
|
Filename: filename,
|
||
|
Values: values,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type synthBody struct {
|
||
|
Filename string
|
||
|
Values map[string]cty.Value
|
||
|
}
|
||
|
|
||
|
func (b synthBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
|
||
|
content, remain, diags := b.PartialContent(schema)
|
||
|
remainS := remain.(synthBody)
|
||
|
for name := range remainS.Values {
|
||
|
diags = append(diags, &hcl.Diagnostic{
|
||
|
Severity: hcl.DiagError,
|
||
|
Summary: "Unsupported attribute",
|
||
|
Detail: fmt.Sprintf("An attribute named %q is not expected here.", name),
|
||
|
Subject: b.synthRange().Ptr(),
|
||
|
})
|
||
|
}
|
||
|
return content, diags
|
||
|
}
|
||
|
|
||
|
func (b synthBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
|
||
|
var diags hcl.Diagnostics
|
||
|
content := &hcl.BodyContent{
|
||
|
Attributes: make(hcl.Attributes),
|
||
|
MissingItemRange: b.synthRange(),
|
||
|
}
|
||
|
|
||
|
remainValues := make(map[string]cty.Value)
|
||
|
for attrName, val := range b.Values {
|
||
|
remainValues[attrName] = val
|
||
|
}
|
||
|
|
||
|
for _, attrS := range schema.Attributes {
|
||
|
delete(remainValues, attrS.Name)
|
||
|
val, defined := b.Values[attrS.Name]
|
||
|
if !defined {
|
||
|
if attrS.Required {
|
||
|
diags = append(diags, &hcl.Diagnostic{
|
||
|
Severity: hcl.DiagError,
|
||
|
Summary: "Missing required attribute",
|
||
|
Detail: fmt.Sprintf("The attribute %q is required, but no definition was found.", attrS.Name),
|
||
|
Subject: b.synthRange().Ptr(),
|
||
|
})
|
||
|
}
|
||
|
continue
|
||
|
}
|
||
|
content.Attributes[attrS.Name] = b.synthAttribute(attrS.Name, val)
|
||
|
}
|
||
|
|
||
|
// We just ignore blocks altogether, because this body type never has
|
||
|
// nested blocks.
|
||
|
|
||
|
remain := synthBody{
|
||
|
Filename: b.Filename,
|
||
|
Values: remainValues,
|
||
|
}
|
||
|
|
||
|
return content, remain, diags
|
||
|
}
|
||
|
|
||
|
func (b synthBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
|
||
|
ret := make(hcl.Attributes)
|
||
|
for name, val := range b.Values {
|
||
|
ret[name] = b.synthAttribute(name, val)
|
||
|
}
|
||
|
return ret, nil
|
||
|
}
|
||
|
|
||
|
func (b synthBody) MissingItemRange() hcl.Range {
|
||
|
return b.synthRange()
|
||
|
}
|
||
|
|
||
|
func (b synthBody) synthAttribute(name string, val cty.Value) *hcl.Attribute {
|
||
|
rng := b.synthRange()
|
||
|
return &hcl.Attribute{
|
||
|
Name: name,
|
||
|
Expr: &hclsyntax.LiteralValueExpr{
|
||
|
Val: val,
|
||
|
SrcRange: rng,
|
||
|
},
|
||
|
NameRange: rng,
|
||
|
Range: rng,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (b synthBody) synthRange() hcl.Range {
|
||
|
return hcl.Range{
|
||
|
Filename: b.Filename,
|
||
|
Start: hcl.Pos{Line: 1, Column: 1},
|
||
|
End: hcl.Pos{Line: 1, Column: 1},
|
||
|
}
|
||
|
}
|