mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-02 12:17:39 -06:00
0317da9911
This produces a "proposed new state", which already has prior computed values propagated into it (since that behavior is standard for all resource types) but could be customized further by the provider to make the "_planned_ new state". In the process of implementing this it became clear that our configschema DecoderSpec behavior is incorrect, since it's producing list values for NestingList and map values for NestingMap. While that seems like it should be right, we should actually be using tuple and object types respectively to allow each block to have a different runtime type in situations where an attribute is given the type cty.DynamicPseudoType. That's not fixed here, and so without a further fix list and map blocks will panic here. The DecoderSpec implementation will be fixed in a subsequent commit.
102 lines
3.8 KiB
Go
102 lines
3.8 KiB
Go
package configschema
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
multierror "github.com/hashicorp/go-multierror"
|
|
)
|
|
|
|
var validName = regexp.MustCompile(`^[a-z0-9_]+$`)
|
|
|
|
// InternalValidate returns an error if the receiving block and its child
|
|
// schema definitions have any consistencies with the documented rules for
|
|
// valid schema.
|
|
//
|
|
// This is intended to be used within unit tests to detect when a given
|
|
// schema is invalid.
|
|
func (b *Block) InternalValidate() error {
|
|
if b == nil {
|
|
return fmt.Errorf("top-level block schema is nil")
|
|
}
|
|
return b.internalValidate("", nil)
|
|
|
|
}
|
|
|
|
func (b *Block) internalValidate(prefix string, err error) error {
|
|
for name, attrS := range b.Attributes {
|
|
if attrS == nil {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: attribute schema is nil", prefix, name))
|
|
continue
|
|
}
|
|
if !validName.MatchString(name) {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: name may contain only lowercase letters, digits and underscores", prefix, name))
|
|
}
|
|
if attrS.Optional == false && attrS.Required == false && attrS.Computed == false {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: must set Optional, Required or Computed", prefix, name))
|
|
}
|
|
if attrS.Optional && attrS.Required {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: cannot set both Optional and Required", prefix, name))
|
|
}
|
|
if attrS.Computed && attrS.Required {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: cannot set both Computed and Required", prefix, name))
|
|
}
|
|
if attrS.Type == cty.NilType {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: Type must be set to something other than cty.NilType", prefix, name))
|
|
}
|
|
}
|
|
|
|
for name, blockS := range b.BlockTypes {
|
|
if blockS == nil {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: block schema is nil", prefix, name))
|
|
continue
|
|
}
|
|
|
|
if _, isAttr := b.Attributes[name]; isAttr {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: name defined as both attribute and child block type", prefix, name))
|
|
} else if !validName.MatchString(name) {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: name may contain only lowercase letters, digits and underscores", prefix, name))
|
|
}
|
|
|
|
if blockS.MinItems < 0 || blockS.MaxItems < 0 {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must both be greater than zero", prefix, name))
|
|
}
|
|
|
|
switch blockS.Nesting {
|
|
case NestingSingle:
|
|
switch {
|
|
case blockS.MinItems != blockS.MaxItems:
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must match in NestingSingle mode", prefix, name))
|
|
case blockS.MinItems < 0 || blockS.MinItems > 1:
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must be set to either 0 or 1 in NestingSingle mode", prefix, name))
|
|
}
|
|
case NestingList, NestingSet:
|
|
if blockS.MinItems > blockS.MaxItems && blockS.MaxItems != 0 {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: MinItems must be less than or equal to MaxItems in %s mode", prefix, name, blockS.Nesting))
|
|
}
|
|
if blockS.Nesting == NestingSet {
|
|
ety := blockS.Block.ImpliedType()
|
|
if ety.HasDynamicTypes() {
|
|
// This is not permitted because the HCL (cty) set implementation
|
|
// needs to know the exact type of set elements in order to
|
|
// properly hash them, and so can't support mixed types.
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: NestingSet blocks may not contain attributes of cty.DynamicPseudoType", prefix, name))
|
|
}
|
|
}
|
|
case NestingMap:
|
|
if blockS.MinItems != 0 || blockS.MaxItems != 0 {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must both be 0 in NestingMap mode", prefix, name))
|
|
}
|
|
default:
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: invalid nesting mode %s", prefix, name, blockS.Nesting))
|
|
}
|
|
|
|
subPrefix := prefix + name + "."
|
|
err = blockS.Block.internalValidate(subPrefix, err)
|
|
}
|
|
|
|
return err
|
|
}
|