kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/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 define
    96  			// both an attribute and a block type of the same name. We assume
    97  			// that the provider has already used something like
    98  			// InternalValidate to validate their schema.
    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.specType().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 that
   142  			// here. (There is no set analog to tuple and object types, because
   143  			// cty's set implementation depends on knowing the static type in
   144  			// order to properly compute its internal hashes.)  We assume that
   145  			// the provider has already used something like InternalValidate to
   146  			// validate their schema.
   147  			ret[name] = &hcldec.BlockSetSpec{
   148  				TypeName: name,
   149  				Nested:   childSpec,
   150  				MinItems: blockS.MinItems,
   151  				MaxItems: blockS.MaxItems,
   152  			}
   153  		case NestingMap:
   154  			// We prefer to use a list where possible, since it makes our
   155  			// implied type more complete, but if there are any
   156  			// dynamically-typed attributes inside we must use a tuple
   157  			// instead, at the expense of our type then not being predictable.
   158  			if blockS.Block.specType().HasDynamicTypes() {
   159  				ret[name] = &hcldec.BlockObjectSpec{
   160  					TypeName:   name,
   161  					Nested:     childSpec,
   162  					LabelNames: mapLabelNames,
   163  				}
   164  			} else {
   165  				ret[name] = &hcldec.BlockMapSpec{
   166  					TypeName:   name,
   167  					Nested:     childSpec,
   168  					LabelNames: mapLabelNames,
   169  				}
   170  			}
   171  		default:
   172  			// Invalid nesting type is just ignored. It's checked by
   173  			// InternalValidate.  We assume that the provider has already used
   174  			// something like InternalValidate to validate their schema.
   175  			continue
   176  		}
   177  	}
   178  
   179  	decoderSpecCache.set(b, ret)
   180  	return ret
   181  }
   182  
   183  func (a *Attribute) decoderSpec(name string) hcldec.Spec {
   184  	ret := &hcldec.AttrSpec{Name: name}
   185  	if a == nil {
   186  		return ret
   187  	}
   188  
   189  	if a.NestedType != nil {
   190  		// FIXME: a panic() is a bad UX. InternalValidate() can check Attribute
   191  		// schemas as well so a fix might be to call it when we get the schema
   192  		// from the provider in Context(). Since this could be a breaking
   193  		// change, we'd need to communicate well before adding that call.
   194  		if a.Type != cty.NilType {
   195  			panic("Invalid attribute schema: NestedType and Type cannot both be set. This is a bug in the provider.")
   196  		}
   197  
   198  		ty := a.NestedType.specType()
   199  		ret.Type = ty
   200  		ret.Required = a.Required || a.NestedType.MinItems > 0
   201  		return ret
   202  	}
   203  
   204  	ret.Type = a.Type
   205  	ret.Required = a.Required
   206  	return ret
   207  }
   208  
   209  // listOptionalAttrsFromObject is a helper function which does *not* recurse
   210  // into NestedType Attributes, because the optional types for each of those will
   211  // belong to their own cty.Object definitions. It is used in other functions
   212  // which themselves handle that recursion.
   213  func listOptionalAttrsFromObject(o *Object) []string {
   214  	ret := make([]string, 0)
   215  
   216  	// This is unlikely to happen outside of tests.
   217  	if o == nil {
   218  		return ret
   219  	}
   220  
   221  	for name, attr := range o.Attributes {
   222  		if attr.Optional || attr.Computed {
   223  			ret = append(ret, name)
   224  		}
   225  	}
   226  	return ret
   227  }