alicloud/validators.go (553 lines of code) (raw):
package alicloud
import (
"encoding/json"
"fmt"
"net"
"os"
"regexp"
"strconv"
"strings"
"time"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
"gopkg.in/yaml.v2"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)
// validateCIDRNetworkAddress ensures that the string value is a valid CIDR that
// represents a network address - it adds an error otherwise
func validateCIDRNetworkAddress(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
_, ipnet, err := net.ParseCIDR(value)
if err != nil {
errors = append(errors, fmt.Errorf(
"%q must contain a valid CIDR, got error parsing: %s", k, err))
return
}
if ipnet == nil || value != ipnet.String() {
errors = append(errors, fmt.Errorf(
"%q must contain a valid network CIDR, expected %q, got %q",
k, ipnet, value))
}
return
}
func validateVpnCIDRNetworkAddress(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
cidrs := strings.Split(value, ",")
for _, cidr := range cidrs {
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
errors = append(errors, fmt.Errorf(
"%q must contain a valid CIDR, got error parsing: %s", k, err))
return
}
if ipnet == nil || cidr != ipnet.String() {
errors = append(errors, fmt.Errorf(
"%q must contain a valid network CIDR, expected %q, got %q",
k, ipnet, cidr))
return
}
}
return
}
func validateSwitchCIDRNetworkAddress(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
_, ipnet, err := net.ParseCIDR(value)
if err != nil {
errors = append(errors, fmt.Errorf(
"%q must contain a valid CIDR, got error parsing: %s", k, err))
return
}
if ipnet == nil || value != ipnet.String() {
errors = append(errors, fmt.Errorf(
"%q must contain a valid network CIDR, expected %q, got %q",
k, ipnet, value))
return
}
mark, _ := strconv.Atoi(strings.Split(ipnet.String(), "/")[1])
if mark < 16 || mark > 29 {
errors = append(errors, fmt.Errorf(
"%q must contain a network CIDR which mark between 16 and 29",
k))
}
return
}
func validateAllowedSplitStringValue(ss []string, splitStr string) schema.SchemaValidateFunc {
return func(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
existed := false
tsList := strings.Split(value, splitStr)
for _, ts := range tsList {
existed = false
for _, s := range ss {
if ts == s {
existed = true
break
}
}
}
if !existed {
errors = append(errors, fmt.Errorf(
"%q must contain a valid string value should in %#v, got %q",
k, ss, value))
}
return
}
}
func validateStringConvertInt64() schema.SchemaValidateFunc {
return func(v interface{}, k string) (ws []string, errors []error) {
if value, ok := v.(string); ok {
_, err := strconv.ParseInt(value, 10, 64)
if err != nil {
errors = append(errors, fmt.Errorf(
"%q should be convert to int64, got %q", k, value))
}
} else {
errors = append(errors, fmt.Errorf(
"%q should be convert to string, got %q", k, value))
}
return
}
}
func validateOssBucketDateTimestamp(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
_, err := time.Parse("2006-01-02", value)
if err != nil {
errors = append(errors, fmt.Errorf(
"%q cannot be parsed as date YYYY-MM-DD Format", value))
}
return
}
func validateOnsGroupId(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !(strings.HasPrefix(value, "GID-") || strings.HasPrefix(value, "GID_")) {
errors = append(errors, fmt.Errorf("%q is invalid, it must start with 'GID-' or 'GID_'", k))
}
if reg := regexp.MustCompile(`^[\w\-]{7,64}$`); !reg.MatchString(value) {
errors = append(errors, fmt.Errorf("%q length is limited to 7-64 and only characters such as letters, digits, '_' and '-' are allowed", k))
}
return
}
func validateRR(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if strings.HasPrefix(value, "-") || strings.HasSuffix(value, "-") {
errors = append(errors, fmt.Errorf("RR is invalid, it can not starts or ends with '-'"))
}
if len(value) > 253 {
errors = append(errors, fmt.Errorf("RR can not longer than 253 characters."))
}
for _, part := range strings.Split(value, ".") {
if len(part) > 63 {
errors = append(errors, fmt.Errorf("Each part of RR split with . can not longer than 63 characters."))
return
}
}
return
}
// Takes a value containing JSON string and passes it through
// the JSON parser to normalize it, returns either a parsing
// error or normalized JSON string.
func normalizeYamlString(yamlString interface{}) (string, error) {
var j interface{}
if yamlString == nil || yamlString.(string) == "" {
return "", nil
}
s := yamlString.(string)
err := yaml.Unmarshal([]byte(s), &j)
if err != nil {
return s, err
}
// The error is intentionally ignored here to allow empty policies to passthrough validation.
// This covers any interpolated values
bytes, _ := yaml.Marshal(j)
return string(bytes[:]), nil
}
// Takes a value containing JSON string and passes it through
// the JSON parser to normalize it, returns either a parsing
// error or normalized JSON string.
func normalizeJsonString(jsonString interface{}) (string, error) {
var j interface{}
if jsonString == nil || jsonString.(string) == "" {
return "", nil
}
s := jsonString.(string)
err := json.Unmarshal([]byte(s), &j)
if err != nil {
return s, err
}
// The error is intentionally ignored here to allow empty policies to passthrough validation.
// This covers any interpolated values
bytes, _ := json.Marshal(j)
return string(bytes[:]), nil
}
func validateYamlString(v interface{}, k string) (ws []string, errors []error) {
if _, err := normalizeYamlString(v); err != nil {
errors = append(errors, fmt.Errorf("%q contains an invalid YAML: %s", k, err))
}
return
}
func validateDBConnectionPort(v interface{}, k string) (ws []string, errors []error) {
if value := v.(string); value != "" {
port, err := strconv.Atoi(value)
if err != nil {
errors = append(errors, err)
}
if port < 1000 || port > 5999 {
errors = append(errors, fmt.Errorf("%q cannot be less than 3001 and larger than 3999.", k))
}
}
return
}
func validateSslVpnPortValue(is []int) schema.SchemaValidateFunc {
return func(v interface{}, k string) (ws []string, errors []error) {
ws, errors = validation.IntBetween(1, 65535)(v, k)
if errors != nil {
return
}
value := v.(int)
for _, i := range is {
if i == value {
errors = append(errors, fmt.Errorf(
"%q must contain a valid int value should not be in array %#v, got %q",
k, is, value))
return
}
}
return
}
}
// below copy/pasta from https://github.com/hashicorp/terraform-plugin-sdk/blob/master/helper/validation/validation.go
// alicloud vendor contains very old version of Terraform which lacks this functions
// 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
}
}
// Validate length(2~128) and prefix of the name.
func validateNormalName(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if len(value) < 2 || len(value) > 128 {
errors = append(errors, fmt.Errorf("%s cannot be longer than 128 characters", k))
}
if strings.HasPrefix(value, "http://") || strings.HasPrefix(value, "https://") {
errors = append(errors, fmt.Errorf("%s cannot starts with http:// or https://", k))
}
return
}
// The instance name must be composed of a~z, A~Z, 0~9 and a hyphen (-),
// the first character must be a letter and the last character cannot be a hyphen (-),
// the legal length range is 3~16 bytes.
func validateOTSInstanceName(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
reg := regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9-]{1,14}[a-zA-Z0-9]$")
if !reg.MatchString(value) {
errors = append(errors, fmt.Errorf("the instance name must consist of a~z, A~Z, 0~9 and a hyphen (-), "+
"the first character must be a letter and the last character cannot be a hyphen (-), the legal length range is 3~16 bytes"))
}
return
}
// The table name must consist of a~z, A~Z, 0~9 and an underscore (_), the first character must be a letter or underscore (_),
// the legal length range is 1~255 bytes.
func validateOTSTableName(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
reg := regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]{0,254}$")
if !reg.MatchString(value) {
errors = append(errors, fmt.Errorf("the table name must consist of a~z, A~Z, 0~9 and an underscore (_), "+
"the first character must be a letter or underscore (_), the legal length range is 1~255 bytes"))
}
return
}
// The tunnel name must consist of a~z, A~Z, 0~9 and an underscore (_), the first character must be a letter or underscore (_),
// the legal length range is 1~255 bytes.
func validateOTSTunnelName(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
reg := regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]{0,254}$")
if !reg.MatchString(value) {
errors = append(errors, fmt.Errorf("the tunnel name must consist of a~z, A~Z, 0~9 and an underscore (_), "+
"the first character must be a letter or underscore (_), the legal length range is 1~255 bytes"))
}
return
}
// The index name must consist of a~z, A~Z, 0~9 and an underscore (_), the first character must be a letter or underscore (_),
// the legal length range is 1~255 bytes.
func validateOTSIndexName(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
reg := regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]{0,254}$")
if !reg.MatchString(value) {
errors = append(errors, fmt.Errorf("the index name must consist of a~z, A~Z, 0~9 and an underscore (_), "+
"the first character must be a letter or underscore (_), the legal length range is 1~255 bytes"))
}
return
}
var resourceSchemaValidationSkipped bool
func skipResourceSchemaValidation() bool {
if resourceSchemaValidationSkipped {
return resourceSchemaValidationSkipped
}
if os.Getenv("TF_SKIP_RESOURCE_SCHEMA_VALIDATION") == "true" {
resourceSchemaValidationSkipped = true
}
return resourceSchemaValidationSkipped
}
const skipResourceSchemaValidationWarning = "\n[NOTE] set env variable TF_SKIP_RESOURCE_SCHEMA_VALIDATION to true can skip the error and get a warning"
// 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 {
if skipResourceSchemaValidation() {
s = append(s, fmt.Sprintf("expected %s to be in the range (%d - %d), got %d", k, min, max, v))
} else {
es = append(es, fmt.Errorf("expected %s to be in the range (%d - %d), got %d %s", k, min, max, v, skipResourceSchemaValidationWarning))
}
return
}
return
}
}
// 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 {
if skipResourceSchemaValidation() {
s = append(s, fmt.Sprintf("expected %s to be at least (%d), got %d", k, min, v))
} else {
es = append(es, fmt.Errorf("expected %s to be at least (%d), got %d %s", k, min, v, skipResourceSchemaValidationWarning))
}
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 {
if skipResourceSchemaValidation() {
s = append(s, fmt.Sprintf("expected %s to be at most (%d), got %d", k, max, v))
} else {
es = append(es, fmt.Errorf("expected %s to be at most (%d), got %d %s", k, max, v, skipResourceSchemaValidationWarning))
}
return
}
return
}
}
// 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
}
}
if skipResourceSchemaValidation() {
s = append(s, fmt.Sprintf("expected %s to be one of %v, got %d", k, valid, v))
} else {
es = append(es, fmt.Errorf("expected %s to be one of %v, got %d %s", k, valid, v, skipResourceSchemaValidationWarning))
}
return
}
}
// 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
}
}
if skipResourceSchemaValidation() {
s = append(s, fmt.Sprintf("expected %s to be one of %v, got %s", k, valid, v))
} else {
es = append(es, fmt.Errorf("expected %s to be one of %v, got %s %s", k, valid, v, skipResourceSchemaValidationWarning))
}
return
}
}
// 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 {
if skipResourceSchemaValidation() {
s = append(s, fmt.Sprintf("expected length of %s to be in the range (%d - %d), got %s", k, min, max, v))
} else {
es = append(es, fmt.Errorf("expected length of %s to be in the range (%d - %d), got %s %s", k, min, max, v, skipResourceSchemaValidationWarning))
}
}
return
}
}
// StringMatch returns a SchemaValidateFunc which tests if the provided value
// matches a given regexp. Optionally an error message can be provided to
// return something friendlier than "must match some globby regexp".
func StringMatch(r *regexp.Regexp, message string) schema.SchemaValidateFunc {
return func(i interface{}, k string) (s []string, es []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 != "" {
if skipResourceSchemaValidation() {
s = append(s, fmt.Sprintf("invalid value for %s (%s)", k, message))
} else {
es = append(es, fmt.Errorf("invalid value for %s (%s) %s", k, message, skipResourceSchemaValidationWarning))
}
return
}
if skipResourceSchemaValidation() {
s = append(s, fmt.Sprintf("expected value of %s to match regular expression %q", k, r))
} else {
es = append(es, fmt.Errorf("expected value of %s to match regular expression %q %s", k, r, skipResourceSchemaValidationWarning))
}
return
}
return nil, nil
}
}
// StringDoesNotMatch returns a SchemaValidateFunc which tests if the provided value
// does not match a given regexp. Optionally an error message can be provided to
// return something friendlier than "must not match some globby regexp".
func StringDoesNotMatch(r *regexp.Regexp, message string) schema.SchemaValidateFunc {
return func(i interface{}, k string) (s []string, es []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 != "" {
if skipResourceSchemaValidation() {
s = append(s, fmt.Sprintf("invalid value for %s (%s)", k, message))
} else {
es = append(es, fmt.Errorf("invalid value for %s (%s) %s", k, message, skipResourceSchemaValidationWarning))
}
return
}
if skipResourceSchemaValidation() {
s = append(s, fmt.Sprintf("expected value of %s to not match regular expression %q", k, r))
} else {
es = append(es, fmt.Errorf("expected value of %s to not match regular expression %q %s", k, r, skipResourceSchemaValidationWarning))
}
return
}
return
}
}
// FloatBetween returns a SchemaValidateFunc which tests if the provided value
// is of type float64 and is between min and max (inclusive).
func FloatBetween(min, max float64) schema.SchemaValidateFunc {
return func(i interface{}, k string) (s []string, es []error) {
v, ok := i.(float64)
if !ok {
es = append(es, fmt.Errorf("expected type of %s to be float64", k))
return
}
if v < min || v > max {
if skipResourceSchemaValidation() {
s = append(s, fmt.Sprintf("expected %s to be in the range (%f - %f), got %f", k, min, max, v))
} else {
es = append(es, fmt.Errorf("expected %s to be in the range (%f - %f), got %f %s", k, min, max, v, skipResourceSchemaValidationWarning))
}
return
}
return
}
}
// FloatAtLeast returns a SchemaValidateFunc which tests if the provided value
// is of type float and is at least min (inclusive)
func FloatAtLeast(min float64) schema.SchemaValidateFunc {
return func(i interface{}, k string) (s []string, es []error) {
v, ok := i.(float64)
if !ok {
es = append(es, fmt.Errorf("expected type of %s to be float", k))
return
}
if v < min {
if skipResourceSchemaValidation() {
s = append(s, fmt.Sprintf("expected %s to be at least (%f), got %f", k, min, v))
} else {
es = append(es, fmt.Errorf("expected %s to be at least (%f), got %f %s", k, min, v, skipResourceSchemaValidationWarning))
}
return
}
return
}
}
// FloatAtMost returns a SchemaValidateFunc which tests if the provided value
// is of type float and is at most max (inclusive)
func FloatAtMost(max float64) schema.SchemaValidateFunc {
return func(i interface{}, k string) (s []string, es []error) {
v, ok := i.(float64)
if !ok {
es = append(es, fmt.Errorf("expected type of %s to be float", k))
return
}
if v > max {
if skipResourceSchemaValidation() {
s = append(s, fmt.Sprintf("expected %s to be at most (%f), got %f", k, max, v))
} else {
es = append(es, fmt.Errorf("expected %s to be at most (%f), got %f %s", k, max, v, skipResourceSchemaValidationWarning))
}
return
}
return
}
}
// StringDoesNotContainAny returns a SchemaValidateFunc which validates that the
// provided value does not contain any of the specified Unicode code points in chars.
func StringDoesNotContainAny(chars string) 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 strings.ContainsAny(v, chars) {
if skipResourceSchemaValidation() {
s = append(s, fmt.Sprintf("expected value of %s to not contain any of %q", k, chars))
} else {
es = append(es, fmt.Errorf("expected value of %s to not contain any of %q %s", k, chars, skipResourceSchemaValidationWarning))
}
return
}
return
}
}
// ValidateRFC3339TimeString is a ValidateFunc that ensures a string parses
// as time.RFC3339 format
func ValidateRFC3339TimeString(allowEmpty 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
}
if v == "" && allowEmpty {
return
}
if _, err := time.Parse(time.RFC3339, v); err != nil {
if skipResourceSchemaValidation() {
s = append(s, fmt.Sprintf("%q: invalid RFC3339 timestamp", k))
} else {
es = append(es, fmt.Errorf("%q: invalid RFC3339 timestamp %s", k, skipResourceSchemaValidationWarning))
}
}
return
}
}
// StringLenAtLeast returns a SchemaValidateFunc which tests if the provided value
// is of type string and has length at least min (inclusive)
func StringLenAtLeast(min 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
}
valueLen := len(strings.TrimSpace(v))
if valueLen < min {
if skipResourceSchemaValidation() {
s = append(s, fmt.Sprintf("expected length of %s to be at least (%d), got (%d)", k, min, valueLen))
} else {
es = append(es, fmt.Errorf("expected length of %s to be at least (%d), got (%d) %s", k, min, valueLen, skipResourceSchemaValidationWarning))
}
}
return
}
}
func validateRedisConfig(v interface{}, k string) (ws []string, errors []error) {
value, _ := v.(map[string]interface{})
if len(value) < 1 {
errors = append(errors, fmt.Errorf("invalid value for %s (%s)", k, value))
}
return
}