package configs import ( "fmt" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/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}, } }