github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/helper/validation/validation.go (about)

     1  package validation
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net"
     7  	"reflect"
     8  	"regexp"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  	"github.com/hashicorp/terraform/helper/structure"
    14  )
    15  
    16  // All returns a SchemaValidateFunc which tests if the provided value
    17  // passes all provided SchemaValidateFunc
    18  func All(validators ...schema.SchemaValidateFunc) schema.SchemaValidateFunc {
    19  	return func(i interface{}, k string) ([]string, []error) {
    20  		var allErrors []error
    21  		var allWarnings []string
    22  		for _, validator := range validators {
    23  			validatorWarnings, validatorErrors := validator(i, k)
    24  			allWarnings = append(allWarnings, validatorWarnings...)
    25  			allErrors = append(allErrors, validatorErrors...)
    26  		}
    27  		return allWarnings, allErrors
    28  	}
    29  }
    30  
    31  // Any returns a SchemaValidateFunc which tests if the provided value
    32  // passes any of the provided SchemaValidateFunc
    33  func Any(validators ...schema.SchemaValidateFunc) schema.SchemaValidateFunc {
    34  	return func(i interface{}, k string) ([]string, []error) {
    35  		var allErrors []error
    36  		var allWarnings []string
    37  		for _, validator := range validators {
    38  			validatorWarnings, validatorErrors := validator(i, k)
    39  			if len(validatorWarnings) == 0 && len(validatorErrors) == 0 {
    40  				return []string{}, []error{}
    41  			}
    42  			allWarnings = append(allWarnings, validatorWarnings...)
    43  			allErrors = append(allErrors, validatorErrors...)
    44  		}
    45  		return allWarnings, allErrors
    46  	}
    47  }
    48  
    49  // IntBetween returns a SchemaValidateFunc which tests if the provided value
    50  // is of type int and is between min and max (inclusive)
    51  func IntBetween(min, max int) schema.SchemaValidateFunc {
    52  	return func(i interface{}, k string) (s []string, es []error) {
    53  		v, ok := i.(int)
    54  		if !ok {
    55  			es = append(es, fmt.Errorf("expected type of %s to be int", k))
    56  			return
    57  		}
    58  
    59  		if v < min || v > max {
    60  			es = append(es, fmt.Errorf("expected %s to be in the range (%d - %d), got %d", k, min, max, v))
    61  			return
    62  		}
    63  
    64  		return
    65  	}
    66  }
    67  
    68  // IntAtLeast returns a SchemaValidateFunc which tests if the provided value
    69  // is of type int and is at least min (inclusive)
    70  func IntAtLeast(min int) schema.SchemaValidateFunc {
    71  	return func(i interface{}, k string) (s []string, es []error) {
    72  		v, ok := i.(int)
    73  		if !ok {
    74  			es = append(es, fmt.Errorf("expected type of %s to be int", k))
    75  			return
    76  		}
    77  
    78  		if v < min {
    79  			es = append(es, fmt.Errorf("expected %s to be at least (%d), got %d", k, min, v))
    80  			return
    81  		}
    82  
    83  		return
    84  	}
    85  }
    86  
    87  // IntAtMost returns a SchemaValidateFunc which tests if the provided value
    88  // is of type int and is at most max (inclusive)
    89  func IntAtMost(max int) schema.SchemaValidateFunc {
    90  	return func(i interface{}, k string) (s []string, es []error) {
    91  		v, ok := i.(int)
    92  		if !ok {
    93  			es = append(es, fmt.Errorf("expected type of %s to be int", k))
    94  			return
    95  		}
    96  
    97  		if v > max {
    98  			es = append(es, fmt.Errorf("expected %s to be at most (%d), got %d", k, max, v))
    99  			return
   100  		}
   101  
   102  		return
   103  	}
   104  }
   105  
   106  // IntInSlice returns a SchemaValidateFunc which tests if the provided value
   107  // is of type int and matches the value of an element in the valid slice
   108  func IntInSlice(valid []int) schema.SchemaValidateFunc {
   109  	return func(i interface{}, k string) (s []string, es []error) {
   110  		v, ok := i.(int)
   111  		if !ok {
   112  			es = append(es, fmt.Errorf("expected type of %s to be an integer", k))
   113  			return
   114  		}
   115  
   116  		for _, validInt := range valid {
   117  			if v == validInt {
   118  				return
   119  			}
   120  		}
   121  
   122  		es = append(es, fmt.Errorf("expected %s to be one of %v, got %d", k, valid, v))
   123  		return
   124  	}
   125  }
   126  
   127  // StringInSlice returns a SchemaValidateFunc which tests if the provided value
   128  // is of type string and matches the value of an element in the valid slice
   129  // will test with in lower case if ignoreCase is true
   130  func StringInSlice(valid []string, ignoreCase bool) schema.SchemaValidateFunc {
   131  	return func(i interface{}, k string) (s []string, es []error) {
   132  		v, ok := i.(string)
   133  		if !ok {
   134  			es = append(es, fmt.Errorf("expected type of %s to be string", k))
   135  			return
   136  		}
   137  
   138  		for _, str := range valid {
   139  			if v == str || (ignoreCase && strings.ToLower(v) == strings.ToLower(str)) {
   140  				return
   141  			}
   142  		}
   143  
   144  		es = append(es, fmt.Errorf("expected %s to be one of %v, got %s", k, valid, v))
   145  		return
   146  	}
   147  }
   148  
   149  // StringLenBetween returns a SchemaValidateFunc which tests if the provided value
   150  // is of type string and has length between min and max (inclusive)
   151  func StringLenBetween(min, max int) schema.SchemaValidateFunc {
   152  	return func(i interface{}, k string) (s []string, es []error) {
   153  		v, ok := i.(string)
   154  		if !ok {
   155  			es = append(es, fmt.Errorf("expected type of %s to be string", k))
   156  			return
   157  		}
   158  		if len(v) < min || len(v) > max {
   159  			es = append(es, fmt.Errorf("expected length of %s to be in the range (%d - %d), got %s", k, min, max, v))
   160  		}
   161  		return
   162  	}
   163  }
   164  
   165  // StringMatch returns a SchemaValidateFunc which tests if the provided value
   166  // matches a given regexp. Optionally an error message can be provided to
   167  // return something friendlier than "must match some globby regexp".
   168  func StringMatch(r *regexp.Regexp, message string) schema.SchemaValidateFunc {
   169  	return func(i interface{}, k string) ([]string, []error) {
   170  		v, ok := i.(string)
   171  		if !ok {
   172  			return nil, []error{fmt.Errorf("expected type of %s to be string", k)}
   173  		}
   174  
   175  		if ok := r.MatchString(v); !ok {
   176  			if message != "" {
   177  				return nil, []error{fmt.Errorf("invalid value for %s (%s)", k, message)}
   178  
   179  			}
   180  			return nil, []error{fmt.Errorf("expected value of %s to match regular expression %q", k, r)}
   181  		}
   182  		return nil, nil
   183  	}
   184  }
   185  
   186  // NoZeroValues is a SchemaValidateFunc which tests if the provided value is
   187  // not a zero value. It's useful in situations where you want to catch
   188  // explicit zero values on things like required fields during validation.
   189  func NoZeroValues(i interface{}, k string) (s []string, es []error) {
   190  	if reflect.ValueOf(i).Interface() == reflect.Zero(reflect.TypeOf(i)).Interface() {
   191  		switch reflect.TypeOf(i).Kind() {
   192  		case reflect.String:
   193  			es = append(es, fmt.Errorf("%s must not be empty", k))
   194  		case reflect.Int, reflect.Float64:
   195  			es = append(es, fmt.Errorf("%s must not be zero", k))
   196  		default:
   197  			// this validator should only ever be applied to TypeString, TypeInt and TypeFloat
   198  			panic(fmt.Errorf("can't use NoZeroValues with %T attribute %s", i, k))
   199  		}
   200  	}
   201  	return
   202  }
   203  
   204  // CIDRNetwork returns a SchemaValidateFunc which tests if the provided value
   205  // is of type string, is in valid CIDR network notation, and has significant bits between min and max (inclusive)
   206  func CIDRNetwork(min, max int) schema.SchemaValidateFunc {
   207  	return func(i interface{}, k string) (s []string, es []error) {
   208  		v, ok := i.(string)
   209  		if !ok {
   210  			es = append(es, fmt.Errorf("expected type of %s to be string", k))
   211  			return
   212  		}
   213  
   214  		_, ipnet, err := net.ParseCIDR(v)
   215  		if err != nil {
   216  			es = append(es, fmt.Errorf(
   217  				"expected %s to contain a valid CIDR, got: %s with err: %s", k, v, err))
   218  			return
   219  		}
   220  
   221  		if ipnet == nil || v != ipnet.String() {
   222  			es = append(es, fmt.Errorf(
   223  				"expected %s to contain a valid network CIDR, expected %s, got %s",
   224  				k, ipnet, v))
   225  		}
   226  
   227  		sigbits, _ := ipnet.Mask.Size()
   228  		if sigbits < min || sigbits > max {
   229  			es = append(es, fmt.Errorf(
   230  				"expected %q to contain a network CIDR with between %d and %d significant bits, got: %d",
   231  				k, min, max, sigbits))
   232  		}
   233  
   234  		return
   235  	}
   236  }
   237  
   238  // SingleIP returns a SchemaValidateFunc which tests if the provided value
   239  // is of type string, and in valid single IP notation
   240  func SingleIP() schema.SchemaValidateFunc {
   241  	return func(i interface{}, k string) (s []string, es []error) {
   242  		v, ok := i.(string)
   243  		if !ok {
   244  			es = append(es, fmt.Errorf("expected type of %s to be string", k))
   245  			return
   246  		}
   247  
   248  		ip := net.ParseIP(v)
   249  		if ip == nil {
   250  			es = append(es, fmt.Errorf(
   251  				"expected %s to contain a valid IP, got: %s", k, v))
   252  		}
   253  		return
   254  	}
   255  }
   256  
   257  // IPRange returns a SchemaValidateFunc which tests if the provided value
   258  // is of type string, and in valid IP range notation
   259  func IPRange() schema.SchemaValidateFunc {
   260  	return func(i interface{}, k string) (s []string, es []error) {
   261  		v, ok := i.(string)
   262  		if !ok {
   263  			es = append(es, fmt.Errorf("expected type of %s to be string", k))
   264  			return
   265  		}
   266  
   267  		ips := strings.Split(v, "-")
   268  		if len(ips) != 2 {
   269  			es = append(es, fmt.Errorf(
   270  				"expected %s to contain a valid IP range, got: %s", k, v))
   271  			return
   272  		}
   273  		ip1 := net.ParseIP(ips[0])
   274  		ip2 := net.ParseIP(ips[1])
   275  		if ip1 == nil || ip2 == nil || bytes.Compare(ip1, ip2) > 0 {
   276  			es = append(es, fmt.Errorf(
   277  				"expected %s to contain a valid IP range, got: %s", k, v))
   278  		}
   279  		return
   280  	}
   281  }
   282  
   283  // ValidateJsonString is a SchemaValidateFunc which tests to make sure the
   284  // supplied string is valid JSON.
   285  func ValidateJsonString(v interface{}, k string) (ws []string, errors []error) {
   286  	if _, err := structure.NormalizeJsonString(v); err != nil {
   287  		errors = append(errors, fmt.Errorf("%q contains an invalid JSON: %s", k, err))
   288  	}
   289  	return
   290  }
   291  
   292  // ValidateListUniqueStrings is a ValidateFunc that ensures a list has no
   293  // duplicate items in it. It's useful for when a list is needed over a set
   294  // because order matters, yet the items still need to be unique.
   295  func ValidateListUniqueStrings(v interface{}, k string) (ws []string, errors []error) {
   296  	for n1, v1 := range v.([]interface{}) {
   297  		for n2, v2 := range v.([]interface{}) {
   298  			if v1.(string) == v2.(string) && n1 != n2 {
   299  				errors = append(errors, fmt.Errorf("%q: duplicate entry - %s", k, v1.(string)))
   300  			}
   301  		}
   302  	}
   303  	return
   304  }
   305  
   306  // ValidateRegexp returns a SchemaValidateFunc which tests to make sure the
   307  // supplied string is a valid regular expression.
   308  func ValidateRegexp(v interface{}, k string) (ws []string, errors []error) {
   309  	if _, err := regexp.Compile(v.(string)); err != nil {
   310  		errors = append(errors, fmt.Errorf("%q: %s", k, err))
   311  	}
   312  	return
   313  }
   314  
   315  // ValidateRFC3339TimeString is a ValidateFunc that ensures a string parses
   316  // as time.RFC3339 format
   317  func ValidateRFC3339TimeString(v interface{}, k string) (ws []string, errors []error) {
   318  	if _, err := time.Parse(time.RFC3339, v.(string)); err != nil {
   319  		errors = append(errors, fmt.Errorf("%q: invalid RFC3339 timestamp", k))
   320  	}
   321  	return
   322  }
   323  
   324  // FloatBetween returns a SchemaValidateFunc which tests if the provided value
   325  // is of type float64 and is between min and max (inclusive).
   326  func FloatBetween(min, max float64) schema.SchemaValidateFunc {
   327  	return func(i interface{}, k string) (s []string, es []error) {
   328  		v, ok := i.(float64)
   329  		if !ok {
   330  			es = append(es, fmt.Errorf("expected type of %s to be float64", k))
   331  			return
   332  		}
   333  
   334  		if v < min || v > max {
   335  			es = append(es, fmt.Errorf("expected %s to be in the range (%f - %f), got %f", k, min, max, v))
   336  			return
   337  		}
   338  
   339  		return
   340  	}
   341  }