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", }, }, }