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 }