4 "github.com/hashicorp/hcl2/hcl"
5 "github.com/hashicorp/terraform/configs/configschema"
6 "github.com/zclconf/go-cty/cty"
9 func ambiguousNames(schema *configschema.Block) map[string]struct{} {
13 ambiguousNames := make(map[string]struct{})
14 for name, attrS := range schema.Attributes {
16 if (aty.IsListType() || aty.IsSetType()) && aty.ElementType().IsObjectType() {
17 ambiguousNames[name] = struct{}{}
23 func effectiveSchema(given *hcl.BodySchema, body hcl.Body, ambiguousNames map[string]struct{}, dynamicExpanded bool) *hcl.BodySchema {
24 ret := &hcl.BodySchema{}
26 appearsAsBlock := make(map[string]struct{})
28 // We'll construct some throwaway schemas here just to probe for
29 // whether each of our ambiguous names seems to be being used as
30 // an attribute or a block. We need to check both because in JSON
31 // syntax we rely on the schema to decide between attribute or block
32 // interpretation and so JSON will always answer yes to both of
33 // these questions and we want to prefer the attribute interpretation
35 var probeSchema hcl.BodySchema
37 for name := range ambiguousNames {
38 probeSchema = hcl.BodySchema{
39 Attributes: []hcl.AttributeSchema{
45 content, _, _ := body.PartialContent(&probeSchema)
46 if _, exists := content.Attributes[name]; exists {
47 // Can decode as an attribute, so we'll go with that.
50 probeSchema = hcl.BodySchema{
51 Blocks: []hcl.BlockHeaderSchema{
57 content, _, _ = body.PartialContent(&probeSchema)
58 if len(content.Blocks) > 0 {
59 // No attribute present and at least one block present, so
60 // we'll need to rewrite this one as a block for a successful
62 appearsAsBlock[name] = struct{}{}
66 // If we're deciding for a context where dynamic blocks haven't
67 // been expanded yet then we need to probe for those too.
68 probeSchema = hcl.BodySchema{
69 Blocks: []hcl.BlockHeaderSchema{
72 LabelNames: []string{"type"},
76 content, _, _ := body.PartialContent(&probeSchema)
77 for _, block := range content.Blocks {
78 if _, exists := ambiguousNames[block.Labels[0]]; exists {
79 appearsAsBlock[block.Labels[0]] = struct{}{}
85 for _, attrS := range given.Attributes {
86 if _, exists := appearsAsBlock[attrS.Name]; exists {
87 ret.Blocks = append(ret.Blocks, hcl.BlockHeaderSchema{
91 ret.Attributes = append(ret.Attributes, attrS)
95 // Anything that is specified as a block type in the input schema remains
96 // that way by just passing through verbatim.
97 ret.Blocks = append(ret.Blocks, given.Blocks...)
102 // SchemaForCtyElementType converts a cty object type into an
103 // approximately-equivalent configschema.Block representing the element of
104 // a list or set. If the given type is not an object type then this
105 // function will panic.
106 func SchemaForCtyElementType(ty cty.Type) *configschema.Block {
107 atys := ty.AttributeTypes()
108 ret := &configschema.Block{
109 Attributes: make(map[string]*configschema.Attribute, len(atys)),
111 for name, aty := range atys {
112 ret.Attributes[name] = &configschema.Attribute{
120 // SchemaForCtyContainerType converts a cty list-of-object or set-of-object type
121 // into an approximately-equivalent configschema.NestedBlock. If the given type
122 // is not of the expected kind then this function will panic.
123 func SchemaForCtyContainerType(ty cty.Type) *configschema.NestedBlock {
124 var nesting configschema.NestingMode
126 case ty.IsListType():
127 nesting = configschema.NestingList
129 nesting = configschema.NestingSet
131 panic("unsuitable type")
133 nested := SchemaForCtyElementType(ty.ElementType())
134 return &configschema.NestedBlock{
140 // TypeCanBeBlocks returns true if the given type is a list-of-object or
141 // set-of-object type, and would thus be subject to the blocktoattr fixup
142 // if used as an attribute type.
143 func TypeCanBeBlocks(ty cty.Type) bool {
144 return (ty.IsListType() || ty.IsSetType()) && ty.ElementType().IsObjectType()