2016-08-10 14:14:25 +02:00
|
|
|
package validation
|
|
|
|
|
|
|
|
import (
|
2018-03-29 18:41:59 +02:00
|
|
|
"bytes"
|
2016-08-10 14:14:25 +02:00
|
|
|
"fmt"
|
2017-03-23 09:57:11 +01:00
|
|
|
"net"
|
2017-09-25 03:18:31 +02:00
|
|
|
"reflect"
|
2017-08-26 19:07:10 +02:00
|
|
|
"regexp"
|
2016-08-10 14:14:25 +02:00
|
|
|
"strings"
|
2018-03-02 05:19:07 +01:00
|
|
|
"time"
|
2016-08-10 14:14:25 +02:00
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
2017-03-27 16:35:40 +02:00
|
|
|
"github.com/hashicorp/terraform/helper/structure"
|
2016-08-10 14:14:25 +02:00
|
|
|
)
|
|
|
|
|
2018-10-17 20:22:29 +02:00
|
|
|
// All returns a SchemaValidateFunc which tests if the provided value
|
|
|
|
// passes all provided SchemaValidateFunc
|
|
|
|
func All(validators ...schema.SchemaValidateFunc) schema.SchemaValidateFunc {
|
|
|
|
return func(i interface{}, k string) ([]string, []error) {
|
|
|
|
var allErrors []error
|
|
|
|
var allWarnings []string
|
|
|
|
for _, validator := range validators {
|
|
|
|
validatorWarnings, validatorErrors := validator(i, k)
|
|
|
|
allWarnings = append(allWarnings, validatorWarnings...)
|
|
|
|
allErrors = append(allErrors, validatorErrors...)
|
|
|
|
}
|
|
|
|
return allWarnings, allErrors
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-10 14:14:25 +02:00
|
|
|
// IntBetween returns a SchemaValidateFunc which tests if the provided value
|
|
|
|
// is of type int and is between min and max (inclusive)
|
|
|
|
func IntBetween(min, max int) schema.SchemaValidateFunc {
|
|
|
|
return func(i interface{}, k string) (s []string, es []error) {
|
|
|
|
v, ok := i.(int)
|
|
|
|
if !ok {
|
|
|
|
es = append(es, fmt.Errorf("expected type of %s to be int", k))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if v < min || v > max {
|
|
|
|
es = append(es, fmt.Errorf("expected %s to be in the range (%d - %d), got %d", k, min, max, v))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-27 16:25:51 +02:00
|
|
|
// IntAtLeast returns a SchemaValidateFunc which tests if the provided value
|
|
|
|
// is of type int and is at least min (inclusive)
|
|
|
|
func IntAtLeast(min int) schema.SchemaValidateFunc {
|
|
|
|
return func(i interface{}, k string) (s []string, es []error) {
|
|
|
|
v, ok := i.(int)
|
|
|
|
if !ok {
|
|
|
|
es = append(es, fmt.Errorf("expected type of %s to be int", k))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if v < min {
|
|
|
|
es = append(es, fmt.Errorf("expected %s to be at least (%d), got %d", k, min, v))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// IntAtMost returns a SchemaValidateFunc which tests if the provided value
|
|
|
|
// is of type int and is at most max (inclusive)
|
|
|
|
func IntAtMost(max int) schema.SchemaValidateFunc {
|
|
|
|
return func(i interface{}, k string) (s []string, es []error) {
|
|
|
|
v, ok := i.(int)
|
|
|
|
if !ok {
|
|
|
|
es = append(es, fmt.Errorf("expected type of %s to be int", k))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if v > max {
|
|
|
|
es = append(es, fmt.Errorf("expected %s to be at most (%d), got %d", k, max, v))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-17 20:22:29 +02:00
|
|
|
// IntInSlice returns a SchemaValidateFunc which tests if the provided value
|
|
|
|
// is of type int and matches the value of an element in the valid slice
|
|
|
|
func IntInSlice(valid []int) schema.SchemaValidateFunc {
|
|
|
|
return func(i interface{}, k string) (s []string, es []error) {
|
|
|
|
v, ok := i.(int)
|
|
|
|
if !ok {
|
|
|
|
es = append(es, fmt.Errorf("expected type of %s to be an integer", k))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, validInt := range valid {
|
|
|
|
if v == validInt {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
es = append(es, fmt.Errorf("expected %s to be one of %v, got %d", k, valid, v))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-10 14:14:25 +02:00
|
|
|
// StringInSlice returns a SchemaValidateFunc which tests if the provided value
|
|
|
|
// is of type string and matches the value of an element in the valid slice
|
|
|
|
// will test with in lower case if ignoreCase is true
|
|
|
|
func StringInSlice(valid []string, ignoreCase bool) schema.SchemaValidateFunc {
|
|
|
|
return func(i interface{}, k string) (s []string, es []error) {
|
|
|
|
v, ok := i.(string)
|
|
|
|
if !ok {
|
|
|
|
es = append(es, fmt.Errorf("expected type of %s to be string", k))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, str := range valid {
|
|
|
|
if v == str || (ignoreCase && strings.ToLower(v) == strings.ToLower(str)) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
es = append(es, fmt.Errorf("expected %s to be one of %v, got %s", k, valid, v))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2017-03-23 09:57:11 +01:00
|
|
|
|
|
|
|
// StringLenBetween returns a SchemaValidateFunc which tests if the provided value
|
|
|
|
// is of type string and has length between min and max (inclusive)
|
|
|
|
func StringLenBetween(min, max int) schema.SchemaValidateFunc {
|
|
|
|
return func(i interface{}, k string) (s []string, es []error) {
|
|
|
|
v, ok := i.(string)
|
|
|
|
if !ok {
|
|
|
|
es = append(es, fmt.Errorf("expected type of %s to be string", k))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if len(v) < min || len(v) > max {
|
|
|
|
es = append(es, fmt.Errorf("expected length of %s to be in the range (%d - %d), got %s", k, min, max, v))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-28 02:13:25 +01:00
|
|
|
// StringMatch returns a SchemaValidateFunc which tests if the provided value
|
2017-12-01 19:48:25 +01:00
|
|
|
// matches a given regexp. Optionally an error message can be provided to
|
|
|
|
// return something friendlier than "must match some globby regexp".
|
2017-11-28 02:13:25 +01:00
|
|
|
func StringMatch(r *regexp.Regexp, message string) schema.SchemaValidateFunc {
|
|
|
|
return func(i interface{}, k string) ([]string, []error) {
|
|
|
|
v, ok := i.(string)
|
|
|
|
if !ok {
|
|
|
|
return nil, []error{fmt.Errorf("expected type of %s to be string", k)}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ok := r.MatchString(v); !ok {
|
|
|
|
if message != "" {
|
|
|
|
return nil, []error{fmt.Errorf("invalid value for %s (%s)", k, message)}
|
|
|
|
|
|
|
|
}
|
|
|
|
return nil, []error{fmt.Errorf("expected value of %s to match regular expression %q", k, r)}
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-25 03:18:31 +02:00
|
|
|
// NoZeroValues is a SchemaValidateFunc which tests if the provided value is
|
|
|
|
// not a zero value. It's useful in situations where you want to catch
|
|
|
|
// explicit zero values on things like required fields during validation.
|
|
|
|
func NoZeroValues(i interface{}, k string) (s []string, es []error) {
|
|
|
|
if reflect.ValueOf(i).Interface() == reflect.Zero(reflect.TypeOf(i)).Interface() {
|
|
|
|
switch reflect.TypeOf(i).Kind() {
|
|
|
|
case reflect.String:
|
|
|
|
es = append(es, fmt.Errorf("%s must not be empty", k))
|
|
|
|
case reflect.Int, reflect.Float64:
|
|
|
|
es = append(es, fmt.Errorf("%s must not be zero", k))
|
|
|
|
default:
|
|
|
|
// this validator should only ever be applied to TypeString, TypeInt and TypeFloat
|
|
|
|
panic(fmt.Errorf("can't use NoZeroValues with %T attribute %s", i, k))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-03-23 09:57:11 +01:00
|
|
|
// CIDRNetwork returns a SchemaValidateFunc which tests if the provided value
|
|
|
|
// is of type string, is in valid CIDR network notation, and has significant bits between min and max (inclusive)
|
|
|
|
func CIDRNetwork(min, max int) schema.SchemaValidateFunc {
|
|
|
|
return func(i interface{}, k string) (s []string, es []error) {
|
|
|
|
v, ok := i.(string)
|
|
|
|
if !ok {
|
|
|
|
es = append(es, fmt.Errorf("expected type of %s to be string", k))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
_, ipnet, err := net.ParseCIDR(v)
|
|
|
|
if err != nil {
|
|
|
|
es = append(es, fmt.Errorf(
|
|
|
|
"expected %s to contain a valid CIDR, got: %s with err: %s", k, v, err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if ipnet == nil || v != ipnet.String() {
|
|
|
|
es = append(es, fmt.Errorf(
|
|
|
|
"expected %s to contain a valid network CIDR, expected %s, got %s",
|
|
|
|
k, ipnet, v))
|
|
|
|
}
|
|
|
|
|
|
|
|
sigbits, _ := ipnet.Mask.Size()
|
|
|
|
if sigbits < min || sigbits > max {
|
|
|
|
es = append(es, fmt.Errorf(
|
|
|
|
"expected %q to contain a network CIDR with between %d and %d significant bits, got: %d",
|
|
|
|
k, min, max, sigbits))
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2017-03-27 16:35:40 +02:00
|
|
|
|
2018-03-29 18:41:59 +02:00
|
|
|
// SingleIP returns a SchemaValidateFunc which tests if the provided value
|
|
|
|
// is of type string, and in valid single IP notation
|
|
|
|
func SingleIP() schema.SchemaValidateFunc {
|
|
|
|
return func(i interface{}, k string) (s []string, es []error) {
|
|
|
|
v, ok := i.(string)
|
|
|
|
if !ok {
|
|
|
|
es = append(es, fmt.Errorf("expected type of %s to be string", k))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ip := net.ParseIP(v)
|
|
|
|
if ip == nil {
|
|
|
|
es = append(es, fmt.Errorf(
|
|
|
|
"expected %s to contain a valid IP, got: %s", k, v))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// IPRange returns a SchemaValidateFunc which tests if the provided value
|
|
|
|
// is of type string, and in valid IP range notation
|
|
|
|
func IPRange() schema.SchemaValidateFunc {
|
|
|
|
return func(i interface{}, k string) (s []string, es []error) {
|
|
|
|
v, ok := i.(string)
|
|
|
|
if !ok {
|
|
|
|
es = append(es, fmt.Errorf("expected type of %s to be string", k))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ips := strings.Split(v, "-")
|
|
|
|
if len(ips) != 2 {
|
|
|
|
es = append(es, fmt.Errorf(
|
|
|
|
"expected %s to contain a valid IP range, got: %s", k, v))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ip1 := net.ParseIP(ips[0])
|
|
|
|
ip2 := net.ParseIP(ips[1])
|
|
|
|
if ip1 == nil || ip2 == nil || bytes.Compare(ip1, ip2) > 0 {
|
|
|
|
es = append(es, fmt.Errorf(
|
|
|
|
"expected %s to contain a valid IP range, got: %s", k, v))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-26 19:07:10 +02:00
|
|
|
// ValidateJsonString is a SchemaValidateFunc which tests to make sure the
|
|
|
|
// supplied string is valid JSON.
|
2017-03-27 16:35:40 +02:00
|
|
|
func ValidateJsonString(v interface{}, k string) (ws []string, errors []error) {
|
|
|
|
if _, err := structure.NormalizeJsonString(v); err != nil {
|
|
|
|
errors = append(errors, fmt.Errorf("%q contains an invalid JSON: %s", k, err))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2017-08-27 18:50:21 +02:00
|
|
|
|
|
|
|
// ValidateListUniqueStrings is a ValidateFunc that ensures a list has no
|
|
|
|
// duplicate items in it. It's useful for when a list is needed over a set
|
|
|
|
// because order matters, yet the items still need to be unique.
|
|
|
|
func ValidateListUniqueStrings(v interface{}, k string) (ws []string, errors []error) {
|
|
|
|
for n1, v1 := range v.([]interface{}) {
|
|
|
|
for n2, v2 := range v.([]interface{}) {
|
|
|
|
if v1.(string) == v2.(string) && n1 != n2 {
|
|
|
|
errors = append(errors, fmt.Errorf("%q: duplicate entry - %s", k, v1.(string)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-08-28 23:41:01 +02:00
|
|
|
return
|
2017-08-27 18:50:21 +02:00
|
|
|
}
|
2017-08-28 22:45:16 +02:00
|
|
|
|
2017-08-26 19:07:10 +02:00
|
|
|
// ValidateRegexp returns a SchemaValidateFunc which tests to make sure the
|
|
|
|
// supplied string is a valid regular expression.
|
|
|
|
func ValidateRegexp(v interface{}, k string) (ws []string, errors []error) {
|
|
|
|
if _, err := regexp.Compile(v.(string)); err != nil {
|
|
|
|
errors = append(errors, fmt.Errorf("%q: %s", k, err))
|
|
|
|
}
|
|
|
|
return
|
2017-08-28 22:49:11 +02:00
|
|
|
}
|
2018-03-02 05:19:07 +01:00
|
|
|
|
|
|
|
// ValidateRFC3339TimeString is a ValidateFunc that ensures a string parses
|
|
|
|
// as time.RFC3339 format
|
|
|
|
func ValidateRFC3339TimeString(v interface{}, k string) (ws []string, errors []error) {
|
|
|
|
if _, err := time.Parse(time.RFC3339, v.(string)); err != nil {
|
2018-03-15 17:31:31 +01:00
|
|
|
errors = append(errors, fmt.Errorf("%q: invalid RFC3339 timestamp", k))
|
2018-03-02 05:19:07 +01:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|