github.com/cbroglie/terraform@v0.7.0-rc3.0.20170410193827-735dfc416d46/builtin/providers/aws/validators.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "net" 6 "net/url" 7 "regexp" 8 "strings" 9 "time" 10 11 "github.com/aws/aws-sdk-go/service/apigateway" 12 "github.com/aws/aws-sdk-go/service/s3" 13 "github.com/hashicorp/terraform/helper/schema" 14 ) 15 16 func validateRdsIdentifier(v interface{}, k string) (ws []string, errors []error) { 17 value := v.(string) 18 if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) { 19 errors = append(errors, fmt.Errorf( 20 "only lowercase alphanumeric characters and hyphens allowed in %q", k)) 21 } 22 if !regexp.MustCompile(`^[a-z]`).MatchString(value) { 23 errors = append(errors, fmt.Errorf( 24 "first character of %q must be a letter", k)) 25 } 26 if regexp.MustCompile(`--`).MatchString(value) { 27 errors = append(errors, fmt.Errorf( 28 "%q cannot contain two consecutive hyphens", k)) 29 } 30 if regexp.MustCompile(`-$`).MatchString(value) { 31 errors = append(errors, fmt.Errorf( 32 "%q cannot end with a hyphen", k)) 33 } 34 return 35 } 36 37 func validateRdsIdentifierPrefix(v interface{}, k string) (ws []string, errors []error) { 38 value := v.(string) 39 if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) { 40 errors = append(errors, fmt.Errorf( 41 "only lowercase alphanumeric characters and hyphens allowed in %q", k)) 42 } 43 if !regexp.MustCompile(`^[a-z]`).MatchString(value) { 44 errors = append(errors, fmt.Errorf( 45 "first character of %q must be a letter", k)) 46 } 47 if regexp.MustCompile(`--`).MatchString(value) { 48 errors = append(errors, fmt.Errorf( 49 "%q cannot contain two consecutive hyphens", k)) 50 } 51 return 52 } 53 54 func validateElastiCacheClusterId(v interface{}, k string) (ws []string, errors []error) { 55 value := v.(string) 56 if (len(value) < 1) || (len(value) > 20) { 57 errors = append(errors, fmt.Errorf( 58 "%q (%q) must contain from 1 to 20 alphanumeric characters or hyphens", k, value)) 59 } 60 if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) { 61 errors = append(errors, fmt.Errorf( 62 "only lowercase alphanumeric characters and hyphens allowed in %q (%q)", k, value)) 63 } 64 if !regexp.MustCompile(`^[a-z]`).MatchString(value) { 65 errors = append(errors, fmt.Errorf( 66 "first character of %q (%q) must be a letter", k, value)) 67 } 68 if regexp.MustCompile(`--`).MatchString(value) { 69 errors = append(errors, fmt.Errorf( 70 "%q (%q) cannot contain two consecutive hyphens", k, value)) 71 } 72 if regexp.MustCompile(`-$`).MatchString(value) { 73 errors = append(errors, fmt.Errorf( 74 "%q (%q) cannot end with a hyphen", k, value)) 75 } 76 return 77 } 78 79 func validateASGScheduleTimestamp(v interface{}, k string) (ws []string, errors []error) { 80 value := v.(string) 81 _, err := time.Parse(awsAutoscalingScheduleTimeLayout, value) 82 if err != nil { 83 errors = append(errors, fmt.Errorf( 84 "%q cannot be parsed as iso8601 Timestamp Format", value)) 85 } 86 87 return 88 } 89 90 // validateTagFilters confirms the "value" component of a tag filter is one of 91 // AWS's three allowed types. 92 func validateTagFilters(v interface{}, k string) (ws []string, errors []error) { 93 value := v.(string) 94 if value != "KEY_ONLY" && value != "VALUE_ONLY" && value != "KEY_AND_VALUE" { 95 errors = append(errors, fmt.Errorf( 96 "%q must be one of \"KEY_ONLY\", \"VALUE_ONLY\", or \"KEY_AND_VALUE\"", k)) 97 } 98 return 99 } 100 101 func validateDbParamGroupName(v interface{}, k string) (ws []string, errors []error) { 102 value := v.(string) 103 if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) { 104 errors = append(errors, fmt.Errorf( 105 "only lowercase alphanumeric characters and hyphens allowed in %q", k)) 106 } 107 if !regexp.MustCompile(`^[a-z]`).MatchString(value) { 108 errors = append(errors, fmt.Errorf( 109 "first character of %q must be a letter", k)) 110 } 111 if regexp.MustCompile(`--`).MatchString(value) { 112 errors = append(errors, fmt.Errorf( 113 "%q cannot contain two consecutive hyphens", k)) 114 } 115 if regexp.MustCompile(`-$`).MatchString(value) { 116 errors = append(errors, fmt.Errorf( 117 "%q cannot end with a hyphen", k)) 118 } 119 if len(value) > 255 { 120 errors = append(errors, fmt.Errorf( 121 "%q cannot be greater than 255 characters", k)) 122 } 123 return 124 } 125 126 func validateDbParamGroupNamePrefix(v interface{}, k string) (ws []string, errors []error) { 127 value := v.(string) 128 if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) { 129 errors = append(errors, fmt.Errorf( 130 "only lowercase alphanumeric characters and hyphens allowed in %q", k)) 131 } 132 if !regexp.MustCompile(`^[a-z]`).MatchString(value) { 133 errors = append(errors, fmt.Errorf( 134 "first character of %q must be a letter", k)) 135 } 136 if regexp.MustCompile(`--`).MatchString(value) { 137 errors = append(errors, fmt.Errorf( 138 "%q cannot contain two consecutive hyphens", k)) 139 } 140 if len(value) > 255 { 141 errors = append(errors, fmt.Errorf( 142 "%q cannot be greater than 226 characters", k)) 143 } 144 return 145 } 146 147 func validateStreamViewType(v interface{}, k string) (ws []string, errors []error) { 148 value := v.(string) 149 viewTypes := map[string]bool{ 150 "KEYS_ONLY": true, 151 "NEW_IMAGE": true, 152 "OLD_IMAGE": true, 153 "NEW_AND_OLD_IMAGES": true, 154 } 155 156 if !viewTypes[value] { 157 errors = append(errors, fmt.Errorf("%q be a valid DynamoDB StreamViewType", k)) 158 } 159 return 160 } 161 162 func validateElbName(v interface{}, k string) (ws []string, errors []error) { 163 value := v.(string) 164 if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { 165 errors = append(errors, fmt.Errorf( 166 "only alphanumeric characters and hyphens allowed in %q: %q", 167 k, value)) 168 } 169 if len(value) > 32 { 170 errors = append(errors, fmt.Errorf( 171 "%q cannot be longer than 32 characters: %q", k, value)) 172 } 173 if regexp.MustCompile(`^-`).MatchString(value) { 174 errors = append(errors, fmt.Errorf( 175 "%q cannot begin with a hyphen: %q", k, value)) 176 } 177 if regexp.MustCompile(`-$`).MatchString(value) { 178 errors = append(errors, fmt.Errorf( 179 "%q cannot end with a hyphen: %q", k, value)) 180 } 181 return 182 } 183 184 func validateElbNamePrefix(v interface{}, k string) (ws []string, errors []error) { 185 value := v.(string) 186 if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { 187 errors = append(errors, fmt.Errorf( 188 "only alphanumeric characters and hyphens allowed in %q: %q", 189 k, value)) 190 } 191 if len(value) > 6 { 192 errors = append(errors, fmt.Errorf( 193 "%q cannot be longer than 6 characters: %q", k, value)) 194 } 195 if regexp.MustCompile(`^-`).MatchString(value) { 196 errors = append(errors, fmt.Errorf( 197 "%q cannot begin with a hyphen: %q", k, value)) 198 } 199 return 200 } 201 202 func validateEcrRepositoryName(v interface{}, k string) (ws []string, errors []error) { 203 value := v.(string) 204 if len(value) < 2 { 205 errors = append(errors, fmt.Errorf( 206 "%q must be at least 2 characters long: %q", k, value)) 207 } 208 if len(value) > 256 { 209 errors = append(errors, fmt.Errorf( 210 "%q cannot be longer than 256 characters: %q", k, value)) 211 } 212 213 // http://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_CreateRepository.html 214 pattern := `^(?:[a-z0-9]+(?:[._-][a-z0-9]+)*/)*[a-z0-9]+(?:[._-][a-z0-9]+)*$` 215 if !regexp.MustCompile(pattern).MatchString(value) { 216 errors = append(errors, fmt.Errorf( 217 "%q doesn't comply with restrictions (%q): %q", 218 k, pattern, value)) 219 } 220 221 return 222 } 223 224 func validateCloudWatchEventRuleName(v interface{}, k string) (ws []string, errors []error) { 225 value := v.(string) 226 if len(value) > 64 { 227 errors = append(errors, fmt.Errorf( 228 "%q cannot be longer than 64 characters: %q", k, value)) 229 } 230 231 // http://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_PutRule.html 232 pattern := `^[\.\-_A-Za-z0-9]+$` 233 if !regexp.MustCompile(pattern).MatchString(value) { 234 errors = append(errors, fmt.Errorf( 235 "%q doesn't comply with restrictions (%q): %q", 236 k, pattern, value)) 237 } 238 239 return 240 } 241 242 func validateMaxLength(length int) schema.SchemaValidateFunc { 243 return func(v interface{}, k string) (ws []string, errors []error) { 244 value := v.(string) 245 if len(value) > length { 246 errors = append(errors, fmt.Errorf( 247 "%q cannot be longer than %d characters: %q", k, length, value)) 248 } 249 return 250 } 251 } 252 253 func validateIntegerInRange(min, max int) schema.SchemaValidateFunc { 254 return func(v interface{}, k string) (ws []string, errors []error) { 255 value := v.(int) 256 if value < min { 257 errors = append(errors, fmt.Errorf( 258 "%q cannot be lower than %d: %d", k, min, value)) 259 } 260 if value > max { 261 errors = append(errors, fmt.Errorf( 262 "%q cannot be higher than %d: %d", k, max, value)) 263 } 264 return 265 } 266 } 267 268 func validateCloudWatchEventTargetId(v interface{}, k string) (ws []string, errors []error) { 269 value := v.(string) 270 if len(value) > 64 { 271 errors = append(errors, fmt.Errorf( 272 "%q cannot be longer than 64 characters: %q", k, value)) 273 } 274 275 // http://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_Target.html 276 pattern := `^[\.\-_A-Za-z0-9]+$` 277 if !regexp.MustCompile(pattern).MatchString(value) { 278 errors = append(errors, fmt.Errorf( 279 "%q doesn't comply with restrictions (%q): %q", 280 k, pattern, value)) 281 } 282 283 return 284 } 285 286 func validateLambdaFunctionName(v interface{}, k string) (ws []string, errors []error) { 287 value := v.(string) 288 if len(value) > 140 { 289 errors = append(errors, fmt.Errorf( 290 "%q cannot be longer than 140 characters: %q", k, value)) 291 } 292 // http://docs.aws.amazon.com/lambda/latest/dg/API_AddPermission.html 293 pattern := `^(arn:[\w-]+:lambda:)?([a-z]{2}-[a-z]+-\d{1}:)?(\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\$LATEST|[a-zA-Z0-9-_]+))?$` 294 if !regexp.MustCompile(pattern).MatchString(value) { 295 errors = append(errors, fmt.Errorf( 296 "%q doesn't comply with restrictions (%q): %q", 297 k, pattern, value)) 298 } 299 300 return 301 } 302 303 func validateLambdaQualifier(v interface{}, k string) (ws []string, errors []error) { 304 value := v.(string) 305 if len(value) > 128 { 306 errors = append(errors, fmt.Errorf( 307 "%q cannot be longer than 128 characters: %q", k, value)) 308 } 309 // http://docs.aws.amazon.com/lambda/latest/dg/API_AddPermission.html 310 pattern := `^[a-zA-Z0-9$_-]+$` 311 if !regexp.MustCompile(pattern).MatchString(value) { 312 errors = append(errors, fmt.Errorf( 313 "%q doesn't comply with restrictions (%q): %q", 314 k, pattern, value)) 315 } 316 317 return 318 } 319 320 func validateLambdaPermissionAction(v interface{}, k string) (ws []string, errors []error) { 321 value := v.(string) 322 323 // http://docs.aws.amazon.com/lambda/latest/dg/API_AddPermission.html 324 pattern := `^(lambda:[*]|lambda:[a-zA-Z]+|[*])$` 325 if !regexp.MustCompile(pattern).MatchString(value) { 326 errors = append(errors, fmt.Errorf( 327 "%q doesn't comply with restrictions (%q): %q", 328 k, pattern, value)) 329 } 330 331 return 332 } 333 334 func validateAwsAccountId(v interface{}, k string) (ws []string, errors []error) { 335 value := v.(string) 336 337 // http://docs.aws.amazon.com/lambda/latest/dg/API_AddPermission.html 338 pattern := `^\d{12}$` 339 if !regexp.MustCompile(pattern).MatchString(value) { 340 errors = append(errors, fmt.Errorf( 341 "%q doesn't look like AWS Account ID (exactly 12 digits): %q", 342 k, value)) 343 } 344 345 return 346 } 347 348 func validateArn(v interface{}, k string) (ws []string, errors []error) { 349 value := v.(string) 350 351 if value == "" { 352 return 353 } 354 355 // http://docs.aws.amazon.com/lambda/latest/dg/API_AddPermission.html 356 pattern := `^arn:[\w-]+:([a-zA-Z0-9\-])+:([a-z]{2}-[a-z]+-\d{1})?:(\d{12})?:(.*)$` 357 if !regexp.MustCompile(pattern).MatchString(value) { 358 errors = append(errors, fmt.Errorf( 359 "%q doesn't look like a valid ARN (%q): %q", 360 k, pattern, value)) 361 } 362 363 return 364 } 365 366 func validatePolicyStatementId(v interface{}, k string) (ws []string, errors []error) { 367 value := v.(string) 368 369 if len(value) > 100 { 370 errors = append(errors, fmt.Errorf( 371 "%q cannot be longer than 100 characters: %q", k, value)) 372 } 373 374 // http://docs.aws.amazon.com/lambda/latest/dg/API_AddPermission.html 375 pattern := `^[a-zA-Z0-9-_]+$` 376 if !regexp.MustCompile(pattern).MatchString(value) { 377 errors = append(errors, fmt.Errorf( 378 "%q doesn't look like a valid statement ID (%q): %q", 379 k, pattern, value)) 380 } 381 382 return 383 } 384 385 // validateCIDRNetworkAddress ensures that the string value is a valid CIDR that 386 // represents a network address - it adds an error otherwise 387 func validateCIDRNetworkAddress(v interface{}, k string) (ws []string, errors []error) { 388 value := v.(string) 389 _, ipnet, err := net.ParseCIDR(value) 390 if err != nil { 391 errors = append(errors, fmt.Errorf( 392 "%q must contain a valid CIDR, got error parsing: %s", k, err)) 393 return 394 } 395 396 if ipnet == nil || value != ipnet.String() { 397 errors = append(errors, fmt.Errorf( 398 "%q must contain a valid network CIDR, expected %q, got %q", 399 k, ipnet, value)) 400 } 401 402 return 403 } 404 405 func validateHTTPMethod(v interface{}, k string) (ws []string, errors []error) { 406 value := v.(string) 407 408 validMethods := map[string]bool{ 409 "ANY": true, 410 "DELETE": true, 411 "GET": true, 412 "HEAD": true, 413 "OPTIONS": true, 414 "PATCH": true, 415 "POST": true, 416 "PUT": true, 417 } 418 419 if _, ok := validMethods[value]; !ok { 420 errors = append(errors, fmt.Errorf( 421 "%q contains an invalid method %q. Valid methods are either %q, %q, %q, %q, %q, %q, %q, or %q.", 422 k, value, "ANY", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT")) 423 } 424 return 425 } 426 427 func validateLogMetricFilterName(v interface{}, k string) (ws []string, errors []error) { 428 value := v.(string) 429 430 if len(value) > 512 { 431 errors = append(errors, fmt.Errorf( 432 "%q cannot be longer than 512 characters: %q", k, value)) 433 } 434 435 // http://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutMetricFilter.html 436 pattern := `^[^:*]+$` 437 if !regexp.MustCompile(pattern).MatchString(value) { 438 errors = append(errors, fmt.Errorf( 439 "%q isn't a valid log metric name (must not contain colon nor asterisk): %q", 440 k, value)) 441 } 442 443 return 444 } 445 446 func validateLogMetricFilterTransformationName(v interface{}, k string) (ws []string, errors []error) { 447 value := v.(string) 448 449 if len(value) > 255 { 450 errors = append(errors, fmt.Errorf( 451 "%q cannot be longer than 255 characters: %q", k, value)) 452 } 453 454 // http://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_MetricTransformation.html 455 pattern := `^[^:*$]*$` 456 if !regexp.MustCompile(pattern).MatchString(value) { 457 errors = append(errors, fmt.Errorf( 458 "%q isn't a valid log metric transformation name (must not contain"+ 459 " colon, asterisk nor dollar sign): %q", 460 k, value)) 461 } 462 463 return 464 } 465 466 func validateLogGroupName(v interface{}, k string) (ws []string, errors []error) { 467 value := v.(string) 468 469 if len(value) > 512 { 470 errors = append(errors, fmt.Errorf( 471 "%q cannot be longer than 512 characters: %q", k, value)) 472 } 473 474 // http://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_CreateLogGroup.html 475 pattern := `^[\.\-_/#A-Za-z0-9]+$` 476 if !regexp.MustCompile(pattern).MatchString(value) { 477 errors = append(errors, fmt.Errorf( 478 "%q isn't a valid log group name (alphanumeric characters, underscores,"+ 479 " hyphens, slashes, hash signs and dots are allowed): %q", 480 k, value)) 481 } 482 483 return 484 } 485 486 func validateS3BucketLifecycleTimestamp(v interface{}, k string) (ws []string, errors []error) { 487 value := v.(string) 488 _, err := time.Parse(time.RFC3339, fmt.Sprintf("%sT00:00:00Z", value)) 489 if err != nil { 490 errors = append(errors, fmt.Errorf( 491 "%q cannot be parsed as RFC3339 Timestamp Format", value)) 492 } 493 494 return 495 } 496 497 func validateS3BucketLifecycleStorageClass(v interface{}, k string) (ws []string, errors []error) { 498 value := v.(string) 499 if value != s3.TransitionStorageClassStandardIa && value != s3.TransitionStorageClassGlacier { 500 errors = append(errors, fmt.Errorf( 501 "%q must be one of '%q', '%q'", k, s3.TransitionStorageClassStandardIa, s3.TransitionStorageClassGlacier)) 502 } 503 504 return 505 } 506 507 func validateS3BucketReplicationRuleId(v interface{}, k string) (ws []string, errors []error) { 508 value := v.(string) 509 if len(value) > 255 { 510 errors = append(errors, fmt.Errorf( 511 "%q cannot be longer than 255 characters: %q", k, value)) 512 } 513 514 return 515 } 516 517 func validateS3BucketReplicationRulePrefix(v interface{}, k string) (ws []string, errors []error) { 518 value := v.(string) 519 if len(value) > 1024 { 520 errors = append(errors, fmt.Errorf( 521 "%q cannot be longer than 1024 characters: %q", k, value)) 522 } 523 524 return 525 } 526 527 func validateS3BucketReplicationDestinationStorageClass(v interface{}, k string) (ws []string, errors []error) { 528 value := v.(string) 529 if value != s3.StorageClassStandard && value != s3.StorageClassStandardIa && value != s3.StorageClassReducedRedundancy { 530 errors = append(errors, fmt.Errorf( 531 "%q must be one of '%q', '%q' or '%q'", k, s3.StorageClassStandard, s3.StorageClassStandardIa, s3.StorageClassReducedRedundancy)) 532 } 533 534 return 535 } 536 537 func validateS3BucketReplicationRuleStatus(v interface{}, k string) (ws []string, errors []error) { 538 value := v.(string) 539 if value != s3.ReplicationRuleStatusEnabled && value != s3.ReplicationRuleStatusDisabled { 540 errors = append(errors, fmt.Errorf( 541 "%q must be one of '%q' or '%q'", k, s3.ReplicationRuleStatusEnabled, s3.ReplicationRuleStatusDisabled)) 542 } 543 544 return 545 } 546 547 func validateS3BucketLifecycleRuleId(v interface{}, k string) (ws []string, errors []error) { 548 value := v.(string) 549 if len(value) > 255 { 550 errors = append(errors, fmt.Errorf( 551 "%q cannot exceed 255 characters", k)) 552 } 553 return 554 } 555 556 func validateDbEventSubscriptionName(v interface{}, k string) (ws []string, errors []error) { 557 value := v.(string) 558 if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { 559 errors = append(errors, fmt.Errorf( 560 "only alphanumeric characters and hyphens allowed in %q", k)) 561 } 562 if len(value) > 255 { 563 errors = append(errors, fmt.Errorf( 564 "%q cannot be longer than 255 characters", k)) 565 } 566 return 567 } 568 569 func validateApiGatewayIntegrationPassthroughBehavior(v interface{}, k string) (ws []string, errors []error) { 570 value := v.(string) 571 if value != "WHEN_NO_MATCH" && value != "WHEN_NO_TEMPLATES" && value != "NEVER" { 572 errors = append(errors, fmt.Errorf( 573 "%q must be one of 'WHEN_NO_MATCH', 'WHEN_NO_TEMPLATES', 'NEVER'", k)) 574 } 575 return 576 } 577 578 func validateJsonString(v interface{}, k string) (ws []string, errors []error) { 579 if _, err := normalizeJsonString(v); err != nil { 580 errors = append(errors, fmt.Errorf("%q contains an invalid JSON: %s", k, err)) 581 } 582 return 583 } 584 585 func validateCloudFormationTemplate(v interface{}, k string) (ws []string, errors []error) { 586 if looksLikeJsonString(v) { 587 if _, err := normalizeJsonString(v); err != nil { 588 errors = append(errors, fmt.Errorf("%q contains an invalid JSON: %s", k, err)) 589 } 590 } else { 591 if _, err := checkYamlString(v); err != nil { 592 errors = append(errors, fmt.Errorf("%q contains an invalid YAML: %s", k, err)) 593 } 594 } 595 return 596 } 597 598 func validateApiGatewayIntegrationType(v interface{}, k string) (ws []string, errors []error) { 599 value := v.(string) 600 601 validTypes := map[string]bool{ 602 "AWS": true, 603 "AWS_PROXY": true, 604 "HTTP": true, 605 "HTTP_PROXY": true, 606 "MOCK": true, 607 } 608 609 if _, ok := validTypes[value]; !ok { 610 errors = append(errors, fmt.Errorf( 611 "%q contains an invalid integration type %q. Valid types are either %q, %q, %q, %q, or %q.", 612 k, value, "AWS", "AWS_PROXY", "HTTP", "HTTP_PROXY", "MOCK")) 613 } 614 return 615 } 616 617 func validateApiGatewayIntegrationContentHandling(v interface{}, k string) (ws []string, errors []error) { 618 value := v.(string) 619 620 validTypes := map[string]bool{ 621 "CONVERT_TO_BINARY": true, 622 "CONVERT_TO_TEXT": true, 623 } 624 625 if _, ok := validTypes[value]; !ok { 626 errors = append(errors, fmt.Errorf( 627 "%q contains an invalid integration type %q. Valid types are either %q or %q.", 628 k, value, "CONVERT_TO_BINARY", "CONVERT_TO_TEXT")) 629 } 630 return 631 } 632 633 func validateSQSQueueName(v interface{}, k string) (errors []error) { 634 value := v.(string) 635 if len(value) > 80 { 636 errors = append(errors, fmt.Errorf("%q cannot be longer than 80 characters", k)) 637 } 638 639 if !regexp.MustCompile(`^[0-9A-Za-z-_]+$`).MatchString(value) { 640 errors = append(errors, fmt.Errorf("only alphanumeric characters and hyphens allowed in %q", k)) 641 } 642 return 643 } 644 645 func validateSQSFifoQueueName(v interface{}, k string) (errors []error) { 646 value := v.(string) 647 648 if len(value) > 80 { 649 errors = append(errors, fmt.Errorf("%q cannot be longer than 80 characters", k)) 650 } 651 652 if !regexp.MustCompile(`^[0-9A-Za-z-_.]+$`).MatchString(value) { 653 errors = append(errors, fmt.Errorf("only alphanumeric characters and hyphens allowed in %q", k)) 654 } 655 656 if regexp.MustCompile(`^[^a-zA-Z0-9-_]`).MatchString(value) { 657 errors = append(errors, fmt.Errorf("FIFO queue name must start with one of these characters [a-zA-Z0-9-_]: %v", value)) 658 } 659 660 if !regexp.MustCompile(`\.fifo$`).MatchString(value) { 661 errors = append(errors, fmt.Errorf("FIFO queue name should ends with \".fifo\": %v", value)) 662 } 663 664 return 665 } 666 667 func validateSNSSubscriptionProtocol(v interface{}, k string) (ws []string, errors []error) { 668 value := strings.ToLower(v.(string)) 669 forbidden := []string{"email", "sms"} 670 for _, f := range forbidden { 671 if strings.Contains(value, f) { 672 errors = append( 673 errors, 674 fmt.Errorf("Unsupported protocol (%s) for SNS Topic", value), 675 ) 676 } 677 } 678 return 679 } 680 681 func validateSecurityRuleType(v interface{}, k string) (ws []string, errors []error) { 682 value := strings.ToLower(v.(string)) 683 684 validTypes := map[string]bool{ 685 "ingress": true, 686 "egress": true, 687 } 688 689 if _, ok := validTypes[value]; !ok { 690 errors = append(errors, fmt.Errorf( 691 "%q contains an invalid Security Group Rule type %q. Valid types are either %q or %q.", 692 k, value, "ingress", "egress")) 693 } 694 return 695 } 696 697 func validateOnceAWeekWindowFormat(v interface{}, k string) (ws []string, errors []error) { 698 // valid time format is "ddd:hh24:mi" 699 validTimeFormat := "(sun|mon|tue|wed|thu|fri|sat):([0-1][0-9]|2[0-3]):([0-5][0-9])" 700 validTimeFormatConsolidated := "^(" + validTimeFormat + "-" + validTimeFormat + "|)$" 701 702 value := strings.ToLower(v.(string)) 703 if !regexp.MustCompile(validTimeFormatConsolidated).MatchString(value) { 704 errors = append(errors, fmt.Errorf( 705 "%q must satisfy the format of \"ddd:hh24:mi-ddd:hh24:mi\".", k)) 706 } 707 return 708 } 709 710 func validateOnceADayWindowFormat(v interface{}, k string) (ws []string, errors []error) { 711 // valid time format is "hh24:mi" 712 validTimeFormat := "([0-1][0-9]|2[0-3]):([0-5][0-9])" 713 validTimeFormatConsolidated := "^(" + validTimeFormat + "-" + validTimeFormat + "|)$" 714 715 value := v.(string) 716 if !regexp.MustCompile(validTimeFormatConsolidated).MatchString(value) { 717 errors = append(errors, fmt.Errorf( 718 "%q must satisfy the format of \"hh24:mi-hh24:mi\".", k)) 719 } 720 return 721 } 722 723 func validateRoute53RecordType(v interface{}, k string) (ws []string, errors []error) { 724 // Valid Record types 725 // SOA, A, TXT, NS, CNAME, MX, NAPTR, PTR, SRV, SPF, AAAA 726 validTypes := map[string]struct{}{ 727 "SOA": {}, 728 "A": {}, 729 "TXT": {}, 730 "NS": {}, 731 "CNAME": {}, 732 "MX": {}, 733 "NAPTR": {}, 734 "PTR": {}, 735 "SRV": {}, 736 "SPF": {}, 737 "AAAA": {}, 738 } 739 740 value := v.(string) 741 if _, ok := validTypes[value]; !ok { 742 errors = append(errors, fmt.Errorf( 743 "%q must be one of [SOA, A, TXT, NS, CNAME, MX, NAPTR, PTR, SRV, SPF, AAAA]", k)) 744 } 745 return 746 } 747 748 // Validates that ECS Placement Constraints are set correctly 749 // Takes type, and expression as strings 750 func validateAwsEcsPlacementConstraint(constType, constExpr string) error { 751 switch constType { 752 case "distinctInstance": 753 // Expression can be nil for distinctInstance 754 return nil 755 case "memberOf": 756 if constExpr == "" { 757 return fmt.Errorf("Expression cannot be nil for 'memberOf' type") 758 } 759 default: 760 return fmt.Errorf("Unknown type provided: %q", constType) 761 } 762 return nil 763 } 764 765 // Validates that an Ecs placement strategy is set correctly 766 // Takes type, and field as strings 767 func validateAwsEcsPlacementStrategy(stratType, stratField string) error { 768 switch stratType { 769 case "random": 770 // random does not need the field attribute set, could error, but it isn't read at the API level 771 return nil 772 case "spread": 773 // For the spread placement strategy, valid values are instanceId 774 // (or host, which has the same effect), or any platform or custom attribute 775 // that is applied to a container instance 776 // stratField is already cased to a string 777 return nil 778 case "binpack": 779 if stratField != "cpu" && stratField != "memory" { 780 return fmt.Errorf("Binpack type requires the field attribute to be either 'cpu' or 'memory'. Got: %s", 781 stratField) 782 } 783 default: 784 return fmt.Errorf("Unknown type %s. Must be one of 'random', 'spread', or 'binpack'.", stratType) 785 } 786 return nil 787 } 788 789 func validateAwsEmrEbsVolumeType(v interface{}, k string) (ws []string, errors []error) { 790 validTypes := map[string]struct{}{ 791 "gp2": {}, 792 "io1": {}, 793 "standard": {}, 794 } 795 796 value := v.(string) 797 798 if _, ok := validTypes[value]; !ok { 799 errors = append(errors, fmt.Errorf( 800 "%q must be one of ['gp2', 'io1', 'standard']", k)) 801 } 802 return 803 } 804 805 func validateSfnActivityName(v interface{}, k string) (ws []string, errors []error) { 806 value := v.(string) 807 if len(value) > 80 { 808 errors = append(errors, fmt.Errorf("%q cannot be longer than 80 characters", k)) 809 } 810 811 return 812 } 813 814 func validateSfnStateMachineDefinition(v interface{}, k string) (ws []string, errors []error) { 815 value := v.(string) 816 if len(value) > 1048576 { 817 errors = append(errors, fmt.Errorf("%q cannot be longer than 1048576 characters", k)) 818 } 819 return 820 } 821 822 func validateSfnStateMachineName(v interface{}, k string) (ws []string, errors []error) { 823 value := v.(string) 824 if len(value) > 80 { 825 errors = append(errors, fmt.Errorf("%q cannot be longer than 80 characters", k)) 826 } 827 828 if !regexp.MustCompile(`^[a-zA-Z0-9-_]+$`).MatchString(value) { 829 errors = append(errors, fmt.Errorf( 830 "%q must be composed with only these characters [a-zA-Z0-9-_]: %v", k, value)) 831 } 832 return 833 } 834 835 func validateDmsCertificateId(v interface{}, k string) (ws []string, es []error) { 836 val := v.(string) 837 838 if len(val) > 255 { 839 es = append(es, fmt.Errorf("%q must not be longer than 255 characters", k)) 840 } 841 if !regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9-]+$").MatchString(val) { 842 es = append(es, fmt.Errorf("%q must start with a letter, only contain alphanumeric characters and hyphens", k)) 843 } 844 if strings.Contains(val, "--") { 845 es = append(es, fmt.Errorf("%q must not contain consecutive hyphens", k)) 846 } 847 if strings.HasSuffix(val, "-") { 848 es = append(es, fmt.Errorf("%q must not end in a hyphen", k)) 849 } 850 851 return 852 } 853 854 func validateDmsEndpointId(v interface{}, k string) (ws []string, es []error) { 855 val := v.(string) 856 857 if len(val) > 255 { 858 es = append(es, fmt.Errorf("%q must not be longer than 255 characters", k)) 859 } 860 if !regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9-]+$").MatchString(val) { 861 es = append(es, fmt.Errorf("%q must start with a letter, only contain alphanumeric characters and hyphens", k)) 862 } 863 if strings.Contains(val, "--") { 864 es = append(es, fmt.Errorf("%q must not contain consecutive hyphens", k)) 865 } 866 if strings.HasSuffix(val, "-") { 867 es = append(es, fmt.Errorf("%q must not end in a hyphen", k)) 868 } 869 870 return 871 } 872 873 func validateDmsReplicationInstanceId(v interface{}, k string) (ws []string, es []error) { 874 val := v.(string) 875 876 if len(val) > 63 { 877 es = append(es, fmt.Errorf("%q must not be longer than 63 characters", k)) 878 } 879 if !regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9-]+$").MatchString(val) { 880 es = append(es, fmt.Errorf("%q must start with a letter, only contain alphanumeric characters and hyphens", k)) 881 } 882 if strings.Contains(val, "--") { 883 es = append(es, fmt.Errorf("%q must not contain consecutive hyphens", k)) 884 } 885 if strings.HasSuffix(val, "-") { 886 es = append(es, fmt.Errorf("%q must not end in a hyphen", k)) 887 } 888 889 return 890 } 891 892 func validateDmsReplicationSubnetGroupId(v interface{}, k string) (ws []string, es []error) { 893 val := v.(string) 894 895 if val == "default" { 896 es = append(es, fmt.Errorf("%q must not be default", k)) 897 } 898 if len(val) > 255 { 899 es = append(es, fmt.Errorf("%q must not be longer than 255 characters", k)) 900 } 901 if !regexp.MustCompile(`^[a-zA-Z0-9. _-]+$`).MatchString(val) { 902 es = append(es, fmt.Errorf("%q must only contain alphanumeric characters, periods, spaces, underscores and hyphens", k)) 903 } 904 905 return 906 } 907 908 func validateDmsReplicationTaskId(v interface{}, k string) (ws []string, es []error) { 909 val := v.(string) 910 911 if len(val) > 255 { 912 es = append(es, fmt.Errorf("%q must not be longer than 255 characters", k)) 913 } 914 if !regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9-]+$").MatchString(val) { 915 es = append(es, fmt.Errorf("%q must start with a letter, only contain alphanumeric characters and hyphens", k)) 916 } 917 if strings.Contains(val, "--") { 918 es = append(es, fmt.Errorf("%q must not contain consecutive hyphens", k)) 919 } 920 if strings.HasSuffix(val, "-") { 921 es = append(es, fmt.Errorf("%q must not end in a hyphen", k)) 922 } 923 924 return 925 } 926 927 func validateAppautoscalingScalableDimension(v interface{}, k string) (ws []string, errors []error) { 928 value := v.(string) 929 dimensions := map[string]bool{ 930 "ecs:service:DesiredCount": true, 931 "ec2:spot-fleet-request:TargetCapacity": true, 932 "elasticmapreduce:instancegroup:InstanceCount": true, 933 } 934 935 if !dimensions[value] { 936 errors = append(errors, fmt.Errorf("%q must be a valid scalable dimension value: %q", k, value)) 937 } 938 return 939 } 940 941 func validateAppautoscalingServiceNamespace(v interface{}, k string) (ws []string, errors []error) { 942 value := v.(string) 943 namespaces := map[string]bool{ 944 "ecs": true, 945 "ec2": true, 946 "elasticmapreduce": true, 947 } 948 949 if !namespaces[value] { 950 errors = append(errors, fmt.Errorf("%q must be a valid service namespace value: %q", k, value)) 951 } 952 return 953 } 954 955 func validateConfigRuleSourceOwner(v interface{}, k string) (ws []string, errors []error) { 956 validOwners := []string{ 957 "CUSTOM_LAMBDA", 958 "AWS", 959 } 960 owner := v.(string) 961 for _, o := range validOwners { 962 if owner == o { 963 return 964 } 965 } 966 errors = append(errors, fmt.Errorf( 967 "%q contains an invalid owner %q. Valid owners are %q.", 968 k, owner, validOwners)) 969 return 970 } 971 972 func validateConfigExecutionFrequency(v interface{}, k string) (ws []string, errors []error) { 973 validFrequencies := []string{ 974 "One_Hour", 975 "Three_Hours", 976 "Six_Hours", 977 "Twelve_Hours", 978 "TwentyFour_Hours", 979 } 980 frequency := v.(string) 981 for _, f := range validFrequencies { 982 if frequency == f { 983 return 984 } 985 } 986 errors = append(errors, fmt.Errorf( 987 "%q contains an invalid frequency %q. Valid frequencies are %q.", 988 k, frequency, validFrequencies)) 989 return 990 } 991 992 func validateAccountAlias(v interface{}, k string) (ws []string, es []error) { 993 val := v.(string) 994 995 if (len(val) < 3) || (len(val) > 63) { 996 es = append(es, fmt.Errorf("%q must contain from 3 to 63 alphanumeric characters or hyphens", k)) 997 } 998 if !regexp.MustCompile("^[a-z0-9][a-z0-9-]+$").MatchString(val) { 999 es = append(es, fmt.Errorf("%q must start with an alphanumeric character and only contain lowercase alphanumeric characters and hyphens", k)) 1000 } 1001 if strings.Contains(val, "--") { 1002 es = append(es, fmt.Errorf("%q must not contain consecutive hyphens", k)) 1003 } 1004 if strings.HasSuffix(val, "-") { 1005 es = append(es, fmt.Errorf("%q must not end in a hyphen", k)) 1006 } 1007 return 1008 } 1009 1010 func validateApiGatewayApiKeyValue(v interface{}, k string) (ws []string, errors []error) { 1011 value := v.(string) 1012 if len(value) < 30 { 1013 errors = append(errors, fmt.Errorf( 1014 "%q must be at least 30 characters long", k)) 1015 } 1016 if len(value) > 128 { 1017 errors = append(errors, fmt.Errorf( 1018 "%q cannot be longer than 128 characters", k)) 1019 } 1020 return 1021 } 1022 1023 func validateIamRolePolicyName(v interface{}, k string) (ws []string, errors []error) { 1024 // https://github.com/boto/botocore/blob/2485f5c/botocore/data/iam/2010-05-08/service-2.json#L8291-L8296 1025 value := v.(string) 1026 if len(value) > 128 { 1027 errors = append(errors, fmt.Errorf( 1028 "%q cannot be longer than 128 characters", k)) 1029 } 1030 if !regexp.MustCompile("^[\\w+=,.@-]+$").MatchString(value) { 1031 errors = append(errors, fmt.Errorf("%q must match [\\w+=,.@-]", k)) 1032 } 1033 return 1034 } 1035 1036 func validateIamRolePolicyNamePrefix(v interface{}, k string) (ws []string, errors []error) { 1037 value := v.(string) 1038 if len(value) > 100 { 1039 errors = append(errors, fmt.Errorf( 1040 "%q cannot be longer than 100 characters", k)) 1041 } 1042 if !regexp.MustCompile("^[\\w+=,.@-]+$").MatchString(value) { 1043 errors = append(errors, fmt.Errorf("%q must match [\\w+=,.@-]", k)) 1044 } 1045 return 1046 } 1047 1048 func validateApiGatewayUsagePlanQuotaSettingsPeriod(v interface{}, k string) (ws []string, errors []error) { 1049 validPeriods := []string{ 1050 apigateway.QuotaPeriodTypeDay, 1051 apigateway.QuotaPeriodTypeWeek, 1052 apigateway.QuotaPeriodTypeMonth, 1053 } 1054 period := v.(string) 1055 for _, f := range validPeriods { 1056 if period == f { 1057 return 1058 } 1059 } 1060 errors = append(errors, fmt.Errorf( 1061 "%q contains an invalid period %q. Valid period are %q.", 1062 k, period, validPeriods)) 1063 return 1064 } 1065 1066 func validateApiGatewayUsagePlanQuotaSettings(v map[string]interface{}) (errors []error) { 1067 period := v["period"].(string) 1068 offset := v["offset"].(int) 1069 1070 if period == apigateway.QuotaPeriodTypeDay && offset != 0 { 1071 errors = append(errors, fmt.Errorf("Usage Plan quota offset must be zero in the DAY period")) 1072 } 1073 1074 if period == apigateway.QuotaPeriodTypeWeek && (offset < 0 || offset > 6) { 1075 errors = append(errors, fmt.Errorf("Usage Plan quota offset must be between 0 and 6 inclusive in the WEEK period")) 1076 } 1077 1078 if period == apigateway.QuotaPeriodTypeMonth && (offset < 0 || offset > 27) { 1079 errors = append(errors, fmt.Errorf("Usage Plan quota offset must be between 0 and 27 inclusive in the MONTH period")) 1080 } 1081 1082 return 1083 } 1084 1085 func validateDbSubnetGroupName(v interface{}, k string) (ws []string, errors []error) { 1086 value := v.(string) 1087 if !regexp.MustCompile(`^[ .0-9a-z-_]+$`).MatchString(value) { 1088 errors = append(errors, fmt.Errorf( 1089 "only lowercase alphanumeric characters, hyphens, underscores, periods, and spaces allowed in %q", k)) 1090 } 1091 if len(value) > 255 { 1092 errors = append(errors, fmt.Errorf( 1093 "%q cannot be longer than 255 characters", k)) 1094 } 1095 if regexp.MustCompile(`(?i)^default$`).MatchString(value) { 1096 errors = append(errors, fmt.Errorf( 1097 "%q is not allowed as %q", "Default", k)) 1098 } 1099 return 1100 } 1101 1102 func validateDbSubnetGroupNamePrefix(v interface{}, k string) (ws []string, errors []error) { 1103 value := v.(string) 1104 if !regexp.MustCompile(`^[ .0-9a-z-_]+$`).MatchString(value) { 1105 errors = append(errors, fmt.Errorf( 1106 "only lowercase alphanumeric characters, hyphens, underscores, periods, and spaces allowed in %q", k)) 1107 } 1108 if len(value) > 229 { 1109 errors = append(errors, fmt.Errorf( 1110 "%q cannot be longer than 229 characters", k)) 1111 } 1112 return 1113 } 1114 1115 func validateDbOptionGroupName(v interface{}, k string) (ws []string, errors []error) { 1116 value := v.(string) 1117 if !regexp.MustCompile(`^[a-z]`).MatchString(value) { 1118 errors = append(errors, fmt.Errorf( 1119 "first character of %q must be a letter", k)) 1120 } 1121 if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { 1122 errors = append(errors, fmt.Errorf( 1123 "only alphanumeric characters and hyphens allowed in %q", k)) 1124 } 1125 if regexp.MustCompile(`--`).MatchString(value) { 1126 errors = append(errors, fmt.Errorf( 1127 "%q cannot contain two consecutive hyphens", k)) 1128 } 1129 if regexp.MustCompile(`-$`).MatchString(value) { 1130 errors = append(errors, fmt.Errorf( 1131 "%q cannot end with a hyphen", k)) 1132 } 1133 if len(value) > 255 { 1134 errors = append(errors, fmt.Errorf( 1135 "%q cannot be greater than 255 characters", k)) 1136 } 1137 return 1138 } 1139 1140 func validateDbOptionGroupNamePrefix(v interface{}, k string) (ws []string, errors []error) { 1141 value := v.(string) 1142 if !regexp.MustCompile(`^[a-z]`).MatchString(value) { 1143 errors = append(errors, fmt.Errorf( 1144 "first character of %q must be a letter", k)) 1145 } 1146 if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { 1147 errors = append(errors, fmt.Errorf( 1148 "only alphanumeric characters and hyphens allowed in %q", k)) 1149 } 1150 if regexp.MustCompile(`--`).MatchString(value) { 1151 errors = append(errors, fmt.Errorf( 1152 "%q cannot contain two consecutive hyphens", k)) 1153 } 1154 if len(value) > 229 { 1155 errors = append(errors, fmt.Errorf( 1156 "%q cannot be greater than 229 characters", k)) 1157 } 1158 return 1159 } 1160 1161 func validateAwsAlbTargetGroupName(v interface{}, k string) (ws []string, errors []error) { 1162 name := v.(string) 1163 if len(name) > 32 { 1164 errors = append(errors, fmt.Errorf("%q (%q) cannot be longer than '32' characters", k, name)) 1165 } 1166 return 1167 } 1168 1169 func validateAwsAlbTargetGroupNamePrefix(v interface{}, k string) (ws []string, errors []error) { 1170 name := v.(string) 1171 if len(name) > 32 { 1172 errors = append(errors, fmt.Errorf("%q (%q) cannot be longer than '6' characters", k, name)) 1173 } 1174 return 1175 } 1176 1177 func validateOpenIdURL(v interface{}, k string) (ws []string, errors []error) { 1178 value := v.(string) 1179 u, err := url.Parse(value) 1180 if err != nil { 1181 errors = append(errors, fmt.Errorf("%q has to be a valid URL", k)) 1182 return 1183 } 1184 if u.Scheme != "https" { 1185 errors = append(errors, fmt.Errorf("%q has to use HTTPS scheme (i.e. begin with https://)", k)) 1186 } 1187 if len(u.Query()) > 0 { 1188 errors = append(errors, fmt.Errorf("%q cannot contain query parameters per the OIDC standard", k)) 1189 } 1190 return 1191 }