github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/configs/configschema/decoder_spec.go (about) 1 package configschema 2 3 import ( 4 "runtime" 5 "sync" 6 "unsafe" 7 8 "github.com/hashicorp/hcl/v2/hcldec" 9 "github.com/zclconf/go-cty/cty" 10 ) 11 12 var mapLabelNames = []string{"key"} 13 14 // specCache is a global cache of all the generated hcldec.Spec values for 15 // Blocks. This cache is used by the Block.DecoderSpec method to memoize calls 16 // and prevent unnecessary regeneration of the spec, especially when they are 17 // large and deeply nested. 18 // Caching these externally rather than within the struct is required because 19 // Blocks are used by value and copied when working with NestedBlocks, and the 20 // copying of the value prevents any safe synchronisation of the struct itself. 21 // 22 // While we are using the *Block pointer as the cache key, and the Block 23 // contents are mutable, once a Block is created it is treated as immutable for 24 // the duration of its life. Because a Block is a representation of a logical 25 // schema, which cannot change while it's being used, any modifications to the 26 // schema during execution would be an error. 27 type specCache struct { 28 sync.Mutex 29 specs map[uintptr]hcldec.Spec 30 } 31 32 var decoderSpecCache = specCache{ 33 specs: map[uintptr]hcldec.Spec{}, 34 } 35 36 // get returns the Spec associated with eth given Block, or nil if non is 37 // found. 38 func (s *specCache) get(b *Block) hcldec.Spec { 39 s.Lock() 40 defer s.Unlock() 41 k := uintptr(unsafe.Pointer(b)) 42 return s.specs[k] 43 } 44 45 // set stores the given Spec as being the result of b.DecoderSpec(). 46 func (s *specCache) set(b *Block, spec hcldec.Spec) { 47 s.Lock() 48 defer s.Unlock() 49 50 // the uintptr value gets us a unique identifier for each block, without 51 // tying this to the block value itself. 52 k := uintptr(unsafe.Pointer(b)) 53 if _, ok := s.specs[k]; ok { 54 return 55 } 56 57 s.specs[k] = spec 58 59 // This must use a finalizer tied to the Block, otherwise we'll continue to 60 // build up Spec values as the Blocks are recycled. 61 runtime.SetFinalizer(b, s.delete) 62 } 63 64 // delete removes the spec associated with the given Block. 65 func (s *specCache) delete(b *Block) { 66 s.Lock() 67 defer s.Unlock() 68 69 k := uintptr(unsafe.Pointer(b)) 70 delete(s.specs, k) 71 } 72 73 // DecoderSpec returns a hcldec.Spec that can be used to decode a HCL Body 74 // using the facilities in the hcldec package. 75 // 76 // The returned specification is guaranteed to return a value of the same type 77 // returned by method ImpliedType, but it may contain null values if any of the 78 // block attributes are defined as optional and/or computed respectively. 79 func (b *Block) DecoderSpec() hcldec.Spec { 80 ret := hcldec.ObjectSpec{} 81 if b == nil { 82 return ret 83 } 84 85 if spec := decoderSpecCache.get(b); spec != nil { 86 return spec 87 } 88 89 for name, attrS := range b.Attributes { 90 ret[name] = attrS.decoderSpec(name) 91 } 92 93 for name, blockS := range b.BlockTypes { 94 if _, exists := ret[name]; exists { 95 // This indicates an invalid schema, since it's not valid to 96 // define both an attribute and a block type of the same name. 97 // However, we don't raise this here since it's checked by 98 // InternalValidate. 99 continue 100 } 101 102 childSpec := blockS.Block.DecoderSpec() 103 104 switch blockS.Nesting { 105 case NestingSingle, NestingGroup: 106 ret[name] = &hcldec.BlockSpec{ 107 TypeName: name, 108 Nested: childSpec, 109 Required: blockS.MinItems == 1, 110 } 111 if blockS.Nesting == NestingGroup { 112 ret[name] = &hcldec.DefaultSpec{ 113 Primary: ret[name], 114 Default: &hcldec.LiteralSpec{ 115 Value: blockS.EmptyValue(), 116 }, 117 } 118 } 119 case NestingList: 120 // We prefer to use a list where possible, since it makes our 121 // implied type more complete, but if there are any 122 // dynamically-typed attributes inside we must use a tuple 123 // instead, at the expense of our type then not being predictable. 124 if blockS.Block.ImpliedType().HasDynamicTypes() { 125 ret[name] = &hcldec.BlockTupleSpec{ 126 TypeName: name, 127 Nested: childSpec, 128 MinItems: blockS.MinItems, 129 MaxItems: blockS.MaxItems, 130 } 131 } else { 132 ret[name] = &hcldec.BlockListSpec{ 133 TypeName: name, 134 Nested: childSpec, 135 MinItems: blockS.MinItems, 136 MaxItems: blockS.MaxItems, 137 } 138 } 139 case NestingSet: 140 // We forbid dynamically-typed attributes inside NestingSet in 141 // InternalValidate, so we don't do anything special to handle 142 // that here. (There is no set analog to tuple and object types, 143 // because cty's set implementation depends on knowing the static 144 // type in order to properly compute its internal hashes.) 145 ret[name] = &hcldec.BlockSetSpec{ 146 TypeName: name, 147 Nested: childSpec, 148 MinItems: blockS.MinItems, 149 MaxItems: blockS.MaxItems, 150 } 151 case NestingMap: 152 // We prefer to use a list where possible, since it makes our 153 // implied type more complete, but if there are any 154 // dynamically-typed attributes inside we must use a tuple 155 // instead, at the expense of our type then not being predictable. 156 if blockS.Block.ImpliedType().HasDynamicTypes() { 157 ret[name] = &hcldec.BlockObjectSpec{ 158 TypeName: name, 159 Nested: childSpec, 160 LabelNames: mapLabelNames, 161 } 162 } else { 163 ret[name] = &hcldec.BlockMapSpec{ 164 TypeName: name, 165 Nested: childSpec, 166 LabelNames: mapLabelNames, 167 } 168 } 169 default: 170 // Invalid nesting type is just ignored. It's checked by 171 // InternalValidate. 172 continue 173 } 174 } 175 176 decoderSpecCache.set(b, ret) 177 return ret 178 } 179 180 func (a *Attribute) decoderSpec(name string) hcldec.Spec { 181 ret := &hcldec.AttrSpec{Name: name} 182 if a == nil { 183 return ret 184 } 185 186 if a.NestedType != nil { 187 // FIXME: a panic() is a bad UX. Fix this, probably by extending 188 // InternalValidate() to check Attribute schemas as well and calling it 189 // when we get the schema from the provider in Context(). 190 if a.Type != cty.NilType { 191 panic("Invalid attribute schema: NestedType and Type cannot both be set. This is a bug in the provider.") 192 } 193 194 ty := a.NestedType.ImpliedType() 195 ret.Type = ty 196 ret.Required = a.Required || a.NestedType.MinItems > 0 197 return ret 198 } 199 200 ret.Type = a.Type 201 ret.Required = a.Required 202 return ret 203 } 204 205 // listOptionalAttrsFromObject is a helper function which does *not* recurse 206 // into NestedType Attributes, because the optional types for each of those will 207 // belong to their own cty.Object definitions. It is used in other functions 208 // which themselves handle that recursion. 209 func listOptionalAttrsFromObject(o *Object) []string { 210 ret := make([]string, 0) 211 212 // This is unlikely to happen outside of tests. 213 if o == nil { 214 return ret 215 } 216 217 for name, attr := range o.Attributes { 218 if attr.Optional || attr.Computed { 219 ret = append(ret, name) 220 } 221 } 222 return ret 223 }