mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-18 20:52:58 -06:00
31349a9c3a
This is part of a general effort to move all of Terraform's non-library package surface under internal in order to reinforce that these are for internal use within Terraform only. If you were previously importing packages under this prefix into an external codebase, you could pin to an earlier release tag as an interim solution until you've make a plan to achieve the same functionality some other way.
172 lines
6.8 KiB
Go
172 lines
6.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 inconsistencies with the documented rules for valid
|
|
// schema.
|
|
//
|
|
// This can be used within unit tests to detect when a given schema is invalid,
|
|
// and is run when terraform loads provider schemas during NewContext.
|
|
func (b *Block) InternalValidate() error {
|
|
if b == nil {
|
|
return fmt.Errorf("top-level block schema is nil")
|
|
}
|
|
return b.internalValidate("")
|
|
}
|
|
|
|
func (b *Block) internalValidate(prefix string) error {
|
|
var multiErr *multierror.Error
|
|
|
|
for name, attrS := range b.Attributes {
|
|
if attrS == nil {
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: attribute schema is nil", prefix, name))
|
|
continue
|
|
}
|
|
multiErr = multierror.Append(multiErr, attrS.internalValidate(name, prefix))
|
|
}
|
|
|
|
for name, blockS := range b.BlockTypes {
|
|
if blockS == nil {
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: block schema is nil", prefix, name))
|
|
continue
|
|
}
|
|
|
|
if _, isAttr := b.Attributes[name]; isAttr {
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: name defined as both attribute and child block type", prefix, name))
|
|
} else if !validName.MatchString(name) {
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: name may contain only lowercase letters, digits and underscores", prefix, name))
|
|
}
|
|
|
|
if blockS.MinItems < 0 || blockS.MaxItems < 0 {
|
|
multiErr = multierror.Append(multiErr, 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:
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: MinItems and MaxItems must match in NestingSingle mode", prefix, name))
|
|
case blockS.MinItems < 0 || blockS.MinItems > 1:
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: MinItems and MaxItems must be set to either 0 or 1 in NestingSingle mode", prefix, name))
|
|
}
|
|
case NestingGroup:
|
|
if blockS.MinItems != 0 || blockS.MaxItems != 0 {
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: MinItems and MaxItems cannot be used in NestingGroup mode", prefix, name))
|
|
}
|
|
case NestingList, NestingSet:
|
|
if blockS.MinItems > blockS.MaxItems && blockS.MaxItems != 0 {
|
|
multiErr = multierror.Append(multiErr, 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.
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: NestingSet blocks may not contain attributes of cty.DynamicPseudoType", prefix, name))
|
|
}
|
|
}
|
|
case NestingMap:
|
|
if blockS.MinItems != 0 || blockS.MaxItems != 0 {
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: MinItems and MaxItems must both be 0 in NestingMap mode", prefix, name))
|
|
}
|
|
default:
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: invalid nesting mode %s", prefix, name, blockS.Nesting))
|
|
}
|
|
|
|
subPrefix := prefix + name + "."
|
|
multiErr = multierror.Append(multiErr, blockS.Block.internalValidate(subPrefix))
|
|
}
|
|
|
|
return multiErr.ErrorOrNil()
|
|
}
|
|
|
|
// InternalValidate returns an error if the receiving attribute and its child
|
|
// schema definitions have any inconsistencies with the documented rules for
|
|
// valid schema.
|
|
func (a *Attribute) InternalValidate(name string) error {
|
|
if a == nil {
|
|
return fmt.Errorf("attribute schema is nil")
|
|
}
|
|
return a.internalValidate(name, "")
|
|
}
|
|
|
|
func (a *Attribute) internalValidate(name, prefix string) error {
|
|
var err *multierror.Error
|
|
|
|
/* FIXME: this validation breaks certain existing providers and cannot be enforced without coordination.
|
|
if !validName.MatchString(name) {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: name may contain only lowercase letters, digits and underscores", prefix, name))
|
|
}
|
|
*/
|
|
if !a.Optional && !a.Required && !a.Computed {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: must set Optional, Required or Computed", prefix, name))
|
|
}
|
|
if a.Optional && a.Required {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: cannot set both Optional and Required", prefix, name))
|
|
}
|
|
if a.Computed && a.Required {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: cannot set both Computed and Required", prefix, name))
|
|
}
|
|
|
|
if a.Type == cty.NilType && a.NestedType == nil {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: either Type or NestedType must be defined", prefix, name))
|
|
}
|
|
|
|
if a.Type != cty.NilType {
|
|
if a.NestedType != nil {
|
|
err = multierror.Append(fmt.Errorf("%s: Type and NestedType cannot both be set", name))
|
|
}
|
|
}
|
|
|
|
if a.NestedType != nil {
|
|
switch a.NestedType.Nesting {
|
|
case NestingSingle:
|
|
switch {
|
|
case a.NestedType.MinItems != a.NestedType.MaxItems:
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must match in NestingSingle mode", prefix, name))
|
|
case a.NestedType.MinItems < 0 || a.NestedType.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 a.NestedType.MinItems > a.NestedType.MaxItems && a.NestedType.MaxItems != 0 {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: MinItems must be less than or equal to MaxItems in %s mode", prefix, name, a.NestedType.Nesting))
|
|
}
|
|
if a.NestedType.Nesting == NestingSet {
|
|
ety := a.NestedType.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 a.NestedType.MinItems != 0 || a.NestedType.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, a.NestedType.Nesting))
|
|
}
|
|
for name, attrS := range a.NestedType.Attributes {
|
|
if attrS == nil {
|
|
err = multierror.Append(err, fmt.Errorf("%s%s: attribute schema is nil", prefix, name))
|
|
continue
|
|
}
|
|
err = multierror.Append(err, attrS.internalValidate(name, prefix))
|
|
}
|
|
}
|
|
|
|
return err.ErrorOrNil()
|
|
}
|