github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/lang/blocktoattr/schema.go (about) 1 package blocktoattr 2 3 import ( 4 "github.com/hashicorp/hcl/v2" 5 "github.com/muratcelep/terraform/not-internal/configs/configschema" 6 "github.com/zclconf/go-cty/cty" 7 ) 8 9 func ambiguousNames(schema *configschema.Block) map[string]struct{} { 10 if schema == nil { 11 return nil 12 } 13 ambiguousNames := make(map[string]struct{}) 14 for name, attrS := range schema.Attributes { 15 aty := attrS.Type 16 if (aty.IsListType() || aty.IsSetType()) && aty.ElementType().IsObjectType() { 17 ambiguousNames[name] = struct{}{} 18 } 19 } 20 return ambiguousNames 21 } 22 23 func effectiveSchema(given *hcl.BodySchema, body hcl.Body, ambiguousNames map[string]struct{}, dynamicExpanded bool) *hcl.BodySchema { 24 ret := &hcl.BodySchema{} 25 26 appearsAsBlock := make(map[string]struct{}) 27 { 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 34 // in that case. 35 var probeSchema hcl.BodySchema 36 37 for name := range ambiguousNames { 38 probeSchema = hcl.BodySchema{ 39 Attributes: []hcl.AttributeSchema{ 40 { 41 Name: name, 42 }, 43 }, 44 } 45 content, _, _ := body.PartialContent(&probeSchema) 46 if _, exists := content.Attributes[name]; exists { 47 // Can decode as an attribute, so we'll go with that. 48 continue 49 } 50 probeSchema = hcl.BodySchema{ 51 Blocks: []hcl.BlockHeaderSchema{ 52 { 53 Type: name, 54 }, 55 }, 56 } 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{}{} 64 } 65 } 66 if !dynamicExpanded { 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{ 71 { 72 Type: "dynamic", 73 LabelNames: []string{"type"}, 74 }, 75 }, 76 } 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{}{} 81 } 82 } 83 } 84 } 85 86 for _, attrS := range given.Attributes { 87 if _, exists := appearsAsBlock[attrS.Name]; exists { 88 ret.Blocks = append(ret.Blocks, hcl.BlockHeaderSchema{ 89 Type: attrS.Name, 90 }) 91 } else { 92 ret.Attributes = append(ret.Attributes, attrS) 93 } 94 } 95 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...) 99 100 return ret 101 } 102 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)), 111 } 112 for name, aty := range atys { 113 ret.Attributes[name] = &configschema.Attribute{ 114 Type: aty, 115 Optional: true, 116 } 117 } 118 return ret 119 } 120 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 126 switch { 127 case ty.IsListType(): 128 nesting = configschema.NestingList 129 case ty.IsSetType(): 130 nesting = configschema.NestingSet 131 default: 132 panic("unsuitable type") 133 } 134 nested := SchemaForCtyElementType(ty.ElementType()) 135 return &configschema.NestedBlock{ 136 Nesting: nesting, 137 Block: *nested, 138 } 139 } 140 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() 146 }