github.com/grange74/terraform@v0.7.0-rc3.0.20160722171430-8c8803864753/builtin/providers/aws/resource_aws_redshift_cluster.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "regexp" 7 "strings" 8 "time" 9 10 "github.com/aws/aws-sdk-go/aws" 11 "github.com/aws/aws-sdk-go/aws/awserr" 12 "github.com/aws/aws-sdk-go/service/redshift" 13 "github.com/hashicorp/terraform/helper/resource" 14 "github.com/hashicorp/terraform/helper/schema" 15 ) 16 17 func resourceAwsRedshiftCluster() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceAwsRedshiftClusterCreate, 20 Read: resourceAwsRedshiftClusterRead, 21 Update: resourceAwsRedshiftClusterUpdate, 22 Delete: resourceAwsRedshiftClusterDelete, 23 Importer: &schema.ResourceImporter{ 24 State: resourceAwsRedshiftClusterImport, 25 }, 26 27 Schema: map[string]*schema.Schema{ 28 "database_name": &schema.Schema{ 29 Type: schema.TypeString, 30 Optional: true, 31 Computed: true, 32 ValidateFunc: validateRedshiftClusterDbName, 33 }, 34 35 "cluster_identifier": &schema.Schema{ 36 Type: schema.TypeString, 37 Required: true, 38 ForceNew: true, 39 ValidateFunc: validateRedshiftClusterIdentifier, 40 }, 41 "cluster_type": &schema.Schema{ 42 Type: schema.TypeString, 43 Optional: true, 44 Computed: true, 45 }, 46 47 "node_type": &schema.Schema{ 48 Type: schema.TypeString, 49 Required: true, 50 }, 51 52 "master_username": &schema.Schema{ 53 Type: schema.TypeString, 54 Required: true, 55 ValidateFunc: validateRedshiftClusterMasterUsername, 56 }, 57 58 "master_password": &schema.Schema{ 59 Type: schema.TypeString, 60 Required: true, 61 }, 62 63 "cluster_security_groups": &schema.Schema{ 64 Type: schema.TypeSet, 65 Optional: true, 66 Computed: true, 67 Elem: &schema.Schema{Type: schema.TypeString}, 68 Set: schema.HashString, 69 }, 70 71 "vpc_security_group_ids": &schema.Schema{ 72 Type: schema.TypeSet, 73 Optional: true, 74 Computed: true, 75 Elem: &schema.Schema{Type: schema.TypeString}, 76 Set: schema.HashString, 77 }, 78 79 "cluster_subnet_group_name": &schema.Schema{ 80 Type: schema.TypeString, 81 Optional: true, 82 ForceNew: true, 83 Computed: true, 84 }, 85 86 "availability_zone": &schema.Schema{ 87 Type: schema.TypeString, 88 Optional: true, 89 Computed: true, 90 }, 91 92 "preferred_maintenance_window": &schema.Schema{ 93 Type: schema.TypeString, 94 Optional: true, 95 Computed: true, 96 StateFunc: func(val interface{}) string { 97 if val == nil { 98 return "" 99 } 100 return strings.ToLower(val.(string)) 101 }, 102 }, 103 104 "cluster_parameter_group_name": &schema.Schema{ 105 Type: schema.TypeString, 106 Optional: true, 107 Computed: true, 108 }, 109 110 "automated_snapshot_retention_period": &schema.Schema{ 111 Type: schema.TypeInt, 112 Optional: true, 113 Default: 1, 114 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 115 value := v.(int) 116 if value > 35 { 117 es = append(es, fmt.Errorf( 118 "backup retention period cannot be more than 35 days")) 119 } 120 return 121 }, 122 }, 123 124 "port": &schema.Schema{ 125 Type: schema.TypeInt, 126 Optional: true, 127 Default: 5439, 128 }, 129 130 "cluster_version": &schema.Schema{ 131 Type: schema.TypeString, 132 Optional: true, 133 Default: "1.0", 134 }, 135 136 "allow_version_upgrade": &schema.Schema{ 137 Type: schema.TypeBool, 138 Optional: true, 139 Default: true, 140 }, 141 142 "number_of_nodes": &schema.Schema{ 143 Type: schema.TypeInt, 144 Optional: true, 145 Default: 1, 146 }, 147 148 "publicly_accessible": &schema.Schema{ 149 Type: schema.TypeBool, 150 Optional: true, 151 Default: true, 152 }, 153 154 "encrypted": &schema.Schema{ 155 Type: schema.TypeBool, 156 Optional: true, 157 Computed: true, 158 }, 159 160 "kms_key_id": &schema.Schema{ 161 Type: schema.TypeString, 162 Optional: true, 163 Computed: true, 164 ForceNew: true, 165 }, 166 167 "elastic_ip": &schema.Schema{ 168 Type: schema.TypeString, 169 Optional: true, 170 }, 171 172 "final_snapshot_identifier": &schema.Schema{ 173 Type: schema.TypeString, 174 Optional: true, 175 ValidateFunc: validateRedshiftClusterFinalSnapshotIdentifier, 176 }, 177 178 "skip_final_snapshot": &schema.Schema{ 179 Type: schema.TypeBool, 180 Optional: true, 181 Default: true, 182 }, 183 184 "endpoint": &schema.Schema{ 185 Type: schema.TypeString, 186 Optional: true, 187 Computed: true, 188 }, 189 190 "cluster_public_key": &schema.Schema{ 191 Type: schema.TypeString, 192 Optional: true, 193 Computed: true, 194 }, 195 196 "cluster_revision_number": &schema.Schema{ 197 Type: schema.TypeString, 198 Optional: true, 199 Computed: true, 200 }, 201 202 "iam_roles": &schema.Schema{ 203 Type: schema.TypeSet, 204 Optional: true, 205 Computed: true, 206 Elem: &schema.Schema{Type: schema.TypeString}, 207 Set: schema.HashString, 208 }, 209 210 "tags": tagsSchema(), 211 }, 212 } 213 } 214 215 func resourceAwsRedshiftClusterImport( 216 d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { 217 // Neither skip_final_snapshot nor final_snapshot_identifier can be fetched 218 // from any API call, so we need to default skip_final_snapshot to true so 219 // that final_snapshot_identifier is not required 220 d.Set("skip_final_snapshot", true) 221 return []*schema.ResourceData{d}, nil 222 } 223 224 func resourceAwsRedshiftClusterCreate(d *schema.ResourceData, meta interface{}) error { 225 conn := meta.(*AWSClient).redshiftconn 226 227 log.Printf("[INFO] Building Redshift Cluster Options") 228 tags := tagsFromMapRedshift(d.Get("tags").(map[string]interface{})) 229 createOpts := &redshift.CreateClusterInput{ 230 ClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)), 231 Port: aws.Int64(int64(d.Get("port").(int))), 232 MasterUserPassword: aws.String(d.Get("master_password").(string)), 233 MasterUsername: aws.String(d.Get("master_username").(string)), 234 ClusterVersion: aws.String(d.Get("cluster_version").(string)), 235 NodeType: aws.String(d.Get("node_type").(string)), 236 DBName: aws.String(d.Get("database_name").(string)), 237 AllowVersionUpgrade: aws.Bool(d.Get("allow_version_upgrade").(bool)), 238 PubliclyAccessible: aws.Bool(d.Get("publicly_accessible").(bool)), 239 AutomatedSnapshotRetentionPeriod: aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))), 240 Tags: tags, 241 } 242 243 if v := d.Get("number_of_nodes").(int); v > 1 { 244 createOpts.ClusterType = aws.String("multi-node") 245 createOpts.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int))) 246 } else { 247 createOpts.ClusterType = aws.String("single-node") 248 } 249 250 if v := d.Get("cluster_security_groups").(*schema.Set); v.Len() > 0 { 251 createOpts.ClusterSecurityGroups = expandStringList(v.List()) 252 } 253 254 if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { 255 createOpts.VpcSecurityGroupIds = expandStringList(v.List()) 256 } 257 258 if v, ok := d.GetOk("cluster_subnet_group_name"); ok { 259 createOpts.ClusterSubnetGroupName = aws.String(v.(string)) 260 } 261 262 if v, ok := d.GetOk("availability_zone"); ok { 263 createOpts.AvailabilityZone = aws.String(v.(string)) 264 } 265 266 if v, ok := d.GetOk("preferred_maintenance_window"); ok { 267 createOpts.PreferredMaintenanceWindow = aws.String(v.(string)) 268 } 269 270 if v, ok := d.GetOk("cluster_parameter_group_name"); ok { 271 createOpts.ClusterParameterGroupName = aws.String(v.(string)) 272 } 273 274 if v, ok := d.GetOk("encrypted"); ok { 275 createOpts.Encrypted = aws.Bool(v.(bool)) 276 } 277 278 if v, ok := d.GetOk("kms_key_id"); ok { 279 createOpts.KmsKeyId = aws.String(v.(string)) 280 } 281 282 if v, ok := d.GetOk("elastic_ip"); ok { 283 createOpts.ElasticIp = aws.String(v.(string)) 284 } 285 286 if v, ok := d.GetOk("iam_roles"); ok { 287 createOpts.IamRoles = expandStringList(v.(*schema.Set).List()) 288 } 289 290 log.Printf("[DEBUG] Redshift Cluster create options: %s", createOpts) 291 resp, err := conn.CreateCluster(createOpts) 292 if err != nil { 293 log.Printf("[ERROR] Error creating Redshift Cluster: %s", err) 294 return err 295 } 296 297 log.Printf("[DEBUG]: Cluster create response: %s", resp) 298 d.SetId(*resp.Cluster.ClusterIdentifier) 299 300 stateConf := &resource.StateChangeConf{ 301 Pending: []string{"creating", "backing-up", "modifying"}, 302 Target: []string{"available"}, 303 Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta), 304 Timeout: 40 * time.Minute, 305 MinTimeout: 10 * time.Second, 306 } 307 308 _, err = stateConf.WaitForState() 309 if err != nil { 310 return fmt.Errorf("[WARN] Error waiting for Redshift Cluster state to be \"available\": %s", err) 311 } 312 313 return resourceAwsRedshiftClusterRead(d, meta) 314 } 315 316 func resourceAwsRedshiftClusterRead(d *schema.ResourceData, meta interface{}) error { 317 conn := meta.(*AWSClient).redshiftconn 318 319 log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id()) 320 resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{ 321 ClusterIdentifier: aws.String(d.Id()), 322 }) 323 324 if err != nil { 325 if awsErr, ok := err.(awserr.Error); ok { 326 if "ClusterNotFound" == awsErr.Code() { 327 d.SetId("") 328 log.Printf("[DEBUG] Redshift Cluster (%s) not found", d.Id()) 329 return nil 330 } 331 } 332 log.Printf("[DEBUG] Error describing Redshift Cluster (%s)", d.Id()) 333 return err 334 } 335 336 var rsc *redshift.Cluster 337 for _, c := range resp.Clusters { 338 if *c.ClusterIdentifier == d.Id() { 339 rsc = c 340 } 341 } 342 343 if rsc == nil { 344 log.Printf("[WARN] Redshift Cluster (%s) not found", d.Id()) 345 d.SetId("") 346 return nil 347 } 348 349 d.Set("master_username", rsc.MasterUsername) 350 d.Set("node_type", rsc.NodeType) 351 d.Set("allow_version_upgrade", rsc.AllowVersionUpgrade) 352 d.Set("database_name", rsc.DBName) 353 d.Set("cluster_identifier", rsc.ClusterIdentifier) 354 d.Set("cluster_version", rsc.ClusterVersion) 355 356 d.Set("cluster_subnet_group_name", rsc.ClusterSubnetGroupName) 357 d.Set("availability_zone", rsc.AvailabilityZone) 358 d.Set("encrypted", rsc.Encrypted) 359 d.Set("kms_key_id", rsc.KmsKeyId) 360 d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod) 361 d.Set("preferred_maintenance_window", rsc.PreferredMaintenanceWindow) 362 if rsc.Endpoint != nil && rsc.Endpoint.Address != nil { 363 endpoint := *rsc.Endpoint.Address 364 if rsc.Endpoint.Port != nil { 365 endpoint = fmt.Sprintf("%s:%d", endpoint, *rsc.Endpoint.Port) 366 } 367 d.Set("port", rsc.Endpoint.Port) 368 d.Set("endpoint", endpoint) 369 } 370 d.Set("cluster_parameter_group_name", rsc.ClusterParameterGroups[0].ParameterGroupName) 371 if len(rsc.ClusterNodes) > 1 { 372 d.Set("cluster_type", "multi-node") 373 } else { 374 d.Set("cluster_type", "single-node") 375 } 376 d.Set("number_of_nodes", rsc.NumberOfNodes) 377 d.Set("publicly_accessible", rsc.PubliclyAccessible) 378 379 var vpcg []string 380 for _, g := range rsc.VpcSecurityGroups { 381 vpcg = append(vpcg, *g.VpcSecurityGroupId) 382 } 383 if err := d.Set("vpc_security_group_ids", vpcg); err != nil { 384 return fmt.Errorf("[DEBUG] Error saving VPC Security Group IDs to state for Redshift Cluster (%s): %s", d.Id(), err) 385 } 386 387 var csg []string 388 for _, g := range rsc.ClusterSecurityGroups { 389 csg = append(csg, *g.ClusterSecurityGroupName) 390 } 391 if err := d.Set("cluster_security_groups", csg); err != nil { 392 return fmt.Errorf("[DEBUG] Error saving Cluster Security Group Names to state for Redshift Cluster (%s): %s", d.Id(), err) 393 } 394 395 var iamRoles []string 396 for _, i := range rsc.IamRoles { 397 iamRoles = append(iamRoles, *i.IamRoleArn) 398 } 399 if err := d.Set("iam_roles", iamRoles); err != nil { 400 return fmt.Errorf("[DEBUG] Error saving IAM Roles to state for Redshift Cluster (%s): %s", d.Id(), err) 401 } 402 403 d.Set("cluster_public_key", rsc.ClusterPublicKey) 404 d.Set("cluster_revision_number", rsc.ClusterRevisionNumber) 405 d.Set("tags", tagsToMapRedshift(rsc.Tags)) 406 407 return nil 408 } 409 410 func resourceAwsRedshiftClusterUpdate(d *schema.ResourceData, meta interface{}) error { 411 conn := meta.(*AWSClient).redshiftconn 412 d.Partial(true) 413 414 arn, tagErr := buildRedshiftARN(d.Id(), meta.(*AWSClient).accountid, meta.(*AWSClient).region) 415 if tagErr != nil { 416 return fmt.Errorf("Error building ARN for Redshift Cluster, not updating Tags for cluster %s", d.Id()) 417 } else { 418 if tagErr := setTagsRedshift(conn, d, arn); tagErr != nil { 419 return tagErr 420 } else { 421 d.SetPartial("tags") 422 } 423 } 424 425 requestUpdate := false 426 log.Printf("[INFO] Building Redshift Modify Cluster Options") 427 req := &redshift.ModifyClusterInput{ 428 ClusterIdentifier: aws.String(d.Id()), 429 } 430 431 if d.HasChange("cluster_type") { 432 req.ClusterType = aws.String(d.Get("cluster_type").(string)) 433 requestUpdate = true 434 } 435 436 if d.HasChange("node_type") { 437 req.NodeType = aws.String(d.Get("node_type").(string)) 438 requestUpdate = true 439 } 440 441 if d.HasChange("number_of_nodes") { 442 if v := d.Get("number_of_nodes").(int); v > 1 { 443 req.ClusterType = aws.String("multi-node") 444 req.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int))) 445 } else { 446 req.ClusterType = aws.String("single-node") 447 } 448 449 req.NodeType = aws.String(d.Get("node_type").(string)) 450 requestUpdate = true 451 } 452 453 if d.HasChange("cluster_security_groups") { 454 req.ClusterSecurityGroups = expandStringList(d.Get("cluster_security_groups").(*schema.Set).List()) 455 requestUpdate = true 456 } 457 458 if d.HasChange("vpc_security_group_ips") { 459 req.VpcSecurityGroupIds = expandStringList(d.Get("vpc_security_group_ips").(*schema.Set).List()) 460 requestUpdate = true 461 } 462 463 if d.HasChange("master_password") { 464 req.MasterUserPassword = aws.String(d.Get("master_password").(string)) 465 requestUpdate = true 466 } 467 468 if d.HasChange("cluster_parameter_group_name") { 469 req.ClusterParameterGroupName = aws.String(d.Get("cluster_parameter_group_name").(string)) 470 requestUpdate = true 471 } 472 473 if d.HasChange("automated_snapshot_retention_period") { 474 req.AutomatedSnapshotRetentionPeriod = aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))) 475 requestUpdate = true 476 } 477 478 if d.HasChange("preferred_maintenance_window") { 479 req.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintenance_window").(string)) 480 requestUpdate = true 481 } 482 483 if d.HasChange("cluster_version") { 484 req.ClusterVersion = aws.String(d.Get("cluster_version").(string)) 485 requestUpdate = true 486 } 487 488 if d.HasChange("allow_version_upgrade") { 489 req.AllowVersionUpgrade = aws.Bool(d.Get("allow_version_upgrade").(bool)) 490 requestUpdate = true 491 } 492 493 if d.HasChange("publicly_accessible") { 494 req.PubliclyAccessible = aws.Bool(d.Get("publicly_accessible").(bool)) 495 requestUpdate = true 496 } 497 498 if requestUpdate { 499 log.Printf("[INFO] Modifying Redshift Cluster: %s", d.Id()) 500 log.Printf("[DEBUG] Redshift Cluster Modify options: %s", req) 501 _, err := conn.ModifyCluster(req) 502 if err != nil { 503 return fmt.Errorf("[WARN] Error modifying Redshift Cluster (%s): %s", d.Id(), err) 504 } 505 } 506 507 if d.HasChange("iam_roles") { 508 o, n := d.GetChange("iam_roles") 509 if o == nil { 510 o = new(schema.Set) 511 } 512 if n == nil { 513 n = new(schema.Set) 514 } 515 516 os := o.(*schema.Set) 517 ns := n.(*schema.Set) 518 519 removeIams := os.Difference(ns).List() 520 addIams := ns.Difference(os).List() 521 522 log.Printf("[INFO] Building Redshift Modify Cluster IAM Role Options") 523 req := &redshift.ModifyClusterIamRolesInput{ 524 ClusterIdentifier: aws.String(d.Id()), 525 AddIamRoles: expandStringList(addIams), 526 RemoveIamRoles: expandStringList(removeIams), 527 } 528 529 log.Printf("[INFO] Modifying Redshift Cluster IAM Roles: %s", d.Id()) 530 log.Printf("[DEBUG] Redshift Cluster Modify IAM Role options: %s", req) 531 _, err := conn.ModifyClusterIamRoles(req) 532 if err != nil { 533 return fmt.Errorf("[WARN] Error modifying Redshift Cluster IAM Roles (%s): %s", d.Id(), err) 534 } 535 536 d.SetPartial("iam_roles") 537 } 538 539 if requestUpdate || d.HasChange("iam_roles") { 540 541 stateConf := &resource.StateChangeConf{ 542 Pending: []string{"creating", "deleting", "rebooting", "resizing", "renaming", "modifying"}, 543 Target: []string{"available"}, 544 Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta), 545 Timeout: 40 * time.Minute, 546 MinTimeout: 10 * time.Second, 547 } 548 549 // Wait, catching any errors 550 _, err := stateConf.WaitForState() 551 if err != nil { 552 return fmt.Errorf("[WARN] Error Modifying Redshift Cluster (%s): %s", d.Id(), err) 553 } 554 } 555 556 d.Partial(false) 557 558 return resourceAwsRedshiftClusterRead(d, meta) 559 } 560 561 func resourceAwsRedshiftClusterDelete(d *schema.ResourceData, meta interface{}) error { 562 conn := meta.(*AWSClient).redshiftconn 563 log.Printf("[DEBUG] Destroying Redshift Cluster (%s)", d.Id()) 564 565 deleteOpts := redshift.DeleteClusterInput{ 566 ClusterIdentifier: aws.String(d.Id()), 567 } 568 569 skipFinalSnapshot := d.Get("skip_final_snapshot").(bool) 570 deleteOpts.SkipFinalClusterSnapshot = aws.Bool(skipFinalSnapshot) 571 572 if skipFinalSnapshot == false { 573 if name, present := d.GetOk("final_snapshot_identifier"); present { 574 deleteOpts.FinalClusterSnapshotIdentifier = aws.String(name.(string)) 575 } else { 576 return fmt.Errorf("Redshift Cluster Instance FinalSnapshotIdentifier is required when a final snapshot is required") 577 } 578 } 579 580 log.Printf("[DEBUG] Redshift Cluster delete options: %s", deleteOpts) 581 _, err := conn.DeleteCluster(&deleteOpts) 582 if err != nil { 583 return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err) 584 } 585 586 stateConf := &resource.StateChangeConf{ 587 Pending: []string{"available", "creating", "deleting", "rebooting", "resizing", "renaming"}, 588 Target: []string{"destroyed"}, 589 Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta), 590 Timeout: 40 * time.Minute, 591 MinTimeout: 5 * time.Second, 592 } 593 594 // Wait, catching any errors 595 _, err = stateConf.WaitForState() 596 if err != nil { 597 return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err) 598 } 599 600 log.Printf("[INFO] Redshift Cluster %s successfully deleted", d.Id()) 601 602 return nil 603 } 604 605 func resourceAwsRedshiftClusterStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { 606 return func() (interface{}, string, error) { 607 conn := meta.(*AWSClient).redshiftconn 608 609 log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id()) 610 resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{ 611 ClusterIdentifier: aws.String(d.Id()), 612 }) 613 614 if err != nil { 615 if awsErr, ok := err.(awserr.Error); ok { 616 if "ClusterNotFound" == awsErr.Code() { 617 return 42, "destroyed", nil 618 } 619 } 620 log.Printf("[WARN] Error on retrieving Redshift Cluster (%s) when waiting: %s", d.Id(), err) 621 return nil, "", err 622 } 623 624 var rsc *redshift.Cluster 625 626 for _, c := range resp.Clusters { 627 if *c.ClusterIdentifier == d.Id() { 628 rsc = c 629 } 630 } 631 632 if rsc == nil { 633 return 42, "destroyed", nil 634 } 635 636 if rsc.ClusterStatus != nil { 637 log.Printf("[DEBUG] Redshift Cluster status (%s): %s", d.Id(), *rsc.ClusterStatus) 638 } 639 640 return rsc, *rsc.ClusterStatus, nil 641 } 642 } 643 644 func validateRedshiftClusterIdentifier(v interface{}, k string) (ws []string, errors []error) { 645 value := v.(string) 646 if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) { 647 errors = append(errors, fmt.Errorf( 648 "only lowercase alphanumeric characters and hyphens allowed in %q", k)) 649 } 650 if !regexp.MustCompile(`^[a-z]`).MatchString(value) { 651 errors = append(errors, fmt.Errorf( 652 "first character of %q must be a letter", k)) 653 } 654 if regexp.MustCompile(`--`).MatchString(value) { 655 errors = append(errors, fmt.Errorf( 656 "%q cannot contain two consecutive hyphens", k)) 657 } 658 if regexp.MustCompile(`-$`).MatchString(value) { 659 errors = append(errors, fmt.Errorf( 660 "%q cannot end with a hyphen", k)) 661 } 662 return 663 } 664 665 func validateRedshiftClusterDbName(v interface{}, k string) (ws []string, errors []error) { 666 value := v.(string) 667 if !regexp.MustCompile(`^[a-z]+$`).MatchString(value) { 668 errors = append(errors, fmt.Errorf( 669 "only lowercase letters characters allowed in %q", k)) 670 } 671 if len(value) > 64 { 672 errors = append(errors, fmt.Errorf( 673 "%q cannot be longer than 64 characters: %q", k, value)) 674 } 675 if value == "" { 676 errors = append(errors, fmt.Errorf( 677 "%q cannot be an empty string", k)) 678 } 679 680 return 681 } 682 683 func validateRedshiftClusterFinalSnapshotIdentifier(v interface{}, k string) (ws []string, errors []error) { 684 value := v.(string) 685 if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { 686 errors = append(errors, fmt.Errorf( 687 "only alphanumeric characters and hyphens allowed in %q", k)) 688 } 689 if regexp.MustCompile(`--`).MatchString(value) { 690 errors = append(errors, fmt.Errorf("%q cannot contain two consecutive hyphens", k)) 691 } 692 if regexp.MustCompile(`-$`).MatchString(value) { 693 errors = append(errors, fmt.Errorf("%q cannot end in a hyphen", k)) 694 } 695 if len(value) > 255 { 696 errors = append(errors, fmt.Errorf("%q cannot be more than 255 characters", k)) 697 } 698 return 699 } 700 701 func validateRedshiftClusterMasterUsername(v interface{}, k string) (ws []string, errors []error) { 702 value := v.(string) 703 if !regexp.MustCompile(`^\w+$`).MatchString(value) { 704 errors = append(errors, fmt.Errorf( 705 "only alphanumeric characters in %q", k)) 706 } 707 if !regexp.MustCompile(`^[A-Za-z]`).MatchString(value) { 708 errors = append(errors, fmt.Errorf( 709 "first character of %q must be a letter", k)) 710 } 711 if len(value) > 128 { 712 errors = append(errors, fmt.Errorf("%q cannot be more than 128 characters", k)) 713 } 714 return 715 } 716 717 func buildRedshiftARN(identifier, accountid, region string) (string, error) { 718 if accountid == "" { 719 return "", fmt.Errorf("Unable to construct cluster ARN because of missing AWS Account ID") 720 } 721 arn := fmt.Sprintf("arn:aws:redshift:%s:%s:cluster:%s", region, accountid, identifier) 722 return arn, nil 723 724 }