2017-10-03 03:46:30 +02:00
|
|
|
package configschema
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
|
|
|
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
|
|
|
|
multierror "github.com/hashicorp/go-multierror"
|
|
|
|
)
|
|
|
|
|
|
|
|
var validName = regexp.MustCompile(`^[a-z0-9_]+$`)
|
|
|
|
|
2021-03-22 18:17:50 +01:00
|
|
|
// InternalValidate returns an error if the receiving block and its child schema
|
|
|
|
// definitions have any inconsistencies with the documented rules for valid
|
|
|
|
// schema.
|
2017-10-03 03:46:30 +02:00
|
|
|
//
|
2021-03-22 18:17:50 +01:00
|
|
|
// 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.
|
2017-10-03 03:46:30 +02:00
|
|
|
func (b *Block) InternalValidate() error {
|
|
|
|
if b == nil {
|
|
|
|
return fmt.Errorf("top-level block schema is nil")
|
|
|
|
}
|
2021-03-22 18:17:50 +01:00
|
|
|
return b.internalValidate("")
|
2017-10-03 03:46:30 +02:00
|
|
|
}
|
|
|
|
|
2021-03-22 18:17:50 +01:00
|
|
|
func (b *Block) internalValidate(prefix string) error {
|
|
|
|
var multiErr *multierror.Error
|
|
|
|
|
2017-10-03 03:46:30 +02:00
|
|
|
for name, attrS := range b.Attributes {
|
|
|
|
if attrS == nil {
|
2021-03-22 18:17:50 +01:00
|
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: attribute schema is nil", prefix, name))
|
2017-10-03 03:46:30 +02:00
|
|
|
continue
|
|
|
|
}
|
2021-03-22 18:17:50 +01:00
|
|
|
multiErr = multierror.Append(multiErr, attrS.internalValidate(name, prefix))
|
2017-10-03 03:46:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for name, blockS := range b.BlockTypes {
|
|
|
|
if blockS == nil {
|
2021-03-22 18:17:50 +01:00
|
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: block schema is nil", prefix, name))
|
2017-10-03 03:46:30 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, isAttr := b.Attributes[name]; isAttr {
|
2021-03-22 18:17:50 +01:00
|
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: name defined as both attribute and child block type", prefix, name))
|
2017-10-03 03:46:30 +02:00
|
|
|
} else if !validName.MatchString(name) {
|
2021-03-22 18:17:50 +01:00
|
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: name may contain only lowercase letters, digits and underscores", prefix, name))
|
2017-10-03 03:46:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if blockS.MinItems < 0 || blockS.MaxItems < 0 {
|
2021-03-22 18:17:50 +01:00
|
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: MinItems and MaxItems must both be greater than zero", prefix, name))
|
2017-10-03 03:46:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
switch blockS.Nesting {
|
|
|
|
case NestingSingle:
|
|
|
|
switch {
|
|
|
|
case blockS.MinItems != blockS.MaxItems:
|
2021-03-22 18:17:50 +01:00
|
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: MinItems and MaxItems must match in NestingSingle mode", prefix, name))
|
2017-10-03 03:46:30 +02:00
|
|
|
case blockS.MinItems < 0 || blockS.MinItems > 1:
|
2021-03-22 18:17:50 +01:00
|
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: MinItems and MaxItems must be set to either 0 or 1 in NestingSingle mode", prefix, name))
|
2017-10-03 03:46:30 +02:00
|
|
|
}
|
2019-04-09 00:32:53 +02:00
|
|
|
case NestingGroup:
|
|
|
|
if blockS.MinItems != 0 || blockS.MaxItems != 0 {
|
2021-03-22 18:17:50 +01:00
|
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: MinItems and MaxItems cannot be used in NestingGroup mode", prefix, name))
|
2019-04-09 00:32:53 +02:00
|
|
|
}
|
2017-10-03 03:46:30 +02:00
|
|
|
case NestingList, NestingSet:
|
|
|
|
if blockS.MinItems > blockS.MaxItems && blockS.MaxItems != 0 {
|
2021-03-22 18:17:50 +01:00
|
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: MinItems must be less than or equal to MaxItems in %s mode", prefix, name, blockS.Nesting))
|
2017-10-03 03:46:30 +02:00
|
|
|
}
|
2018-08-21 03:45:34 +02:00
|
|
|
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.
|
2021-03-22 18:17:50 +01:00
|
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: NestingSet blocks may not contain attributes of cty.DynamicPseudoType", prefix, name))
|
2018-08-21 03:45:34 +02:00
|
|
|
}
|
|
|
|
}
|
2017-10-03 03:46:30 +02:00
|
|
|
case NestingMap:
|
|
|
|
if blockS.MinItems != 0 || blockS.MaxItems != 0 {
|
2021-03-22 18:17:50 +01:00
|
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: MinItems and MaxItems must both be 0 in NestingMap mode", prefix, name))
|
2017-10-03 03:46:30 +02:00
|
|
|
}
|
|
|
|
default:
|
2021-03-22 18:17:50 +01:00
|
|
|
multiErr = multierror.Append(multiErr, fmt.Errorf("%s%s: invalid nesting mode %s", prefix, name, blockS.Nesting))
|
2017-10-03 03:46:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
subPrefix := prefix + name + "."
|
2021-03-22 18:17:50 +01:00
|
|
|
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))
|
|
|
|
}
|
2017-10-03 03:46:30 +02:00
|
|
|
}
|
|
|
|
|
2021-03-22 18:17:50 +01:00
|
|
|
return err.ErrorOrNil()
|
2017-10-03 03:46:30 +02:00
|
|
|
}
|