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 || dynamicExpanded {
59 // A dynamic block with an empty iterator returns nothing.
60 // If there's no attribute and we have either a block or a
61 // dynamic expansion, we need to rewrite this one as a
62 // block for a successful result.
63 appearsAsBlock[name] = struct{}{}
67 // If we're deciding for a context where dynamic blocks haven't
68 // been expanded yet then we need to probe for those too.
69 probeSchema = hcl.BodySchema{
70 Blocks: []hcl.BlockHeaderSchema{
73 LabelNames: []string{"type"},
77 content, _, _ := body.PartialContent(&probeSchema)
78 for _, block := range content.Blocks {
79 if _, exists := ambiguousNames[block.Labels[0]]; exists {
80 appearsAsBlock[block.Labels[0]] = struct{}{}
86 for _, attrS := range given.Attributes {
87 if _, exists := appearsAsBlock[attrS.Name]; exists {
88 ret.Blocks = append(ret.Blocks, hcl.BlockHeaderSchema{
92 ret.Attributes = append(ret.Attributes, attrS)
96 // Anything that is specified as a block type in the input schema remains
97 // that way by just passing through verbatim.
98 ret.Blocks = append(ret.Blocks, given.Blocks...)
103 // SchemaForCtyElementType converts a cty object type into an
104 // approximately-equivalent configschema.Block representing the element of
105 // a list or set. If the given type is not an object type then this
106 // function will panic.
107 func SchemaForCtyElementType(ty cty.Type) *configschema.Block {
108 atys := ty.AttributeTypes()
109 ret := &configschema.Block{
110 Attributes: make(map[string]*configschema.Attribute, len(atys)),
112 for name, aty := range atys {
113 ret.Attributes[name] = &configschema.Attribute{
121 // SchemaForCtyContainerType converts a cty list-of-object or set-of-object type
122 // into an approximately-equivalent configschema.NestedBlock. If the given type
123 // is not of the expected kind then this function will panic.
124 func SchemaForCtyContainerType(ty cty.Type) *configschema.NestedBlock {
125 var nesting configschema.NestingMode
127 case ty.IsListType():
128 nesting = configschema.NestingList
130 nesting = configschema.NestingSet
132 panic("unsuitable type")
134 nested := SchemaForCtyElementType(ty.ElementType())
135 return &configschema.NestedBlock{
141 // TypeCanBeBlocks returns true if the given type is a list-of-object or
142 // set-of-object type, and would thus be subject to the blocktoattr fixup
143 // if used as an attribute type.
144 func TypeCanBeBlocks(ty cty.Type) bool {
145 return (ty.IsListType() || ty.IsSetType()) && ty.ElementType().IsObjectType()