github.com/kwoods/terraform@v0.6.11-0.20160809170336-13497db7138e/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 "enable_logging": { 211 Type: schema.TypeBool, 212 Optional: true, 213 Default: false, 214 }, 215 216 "bucket_name": { 217 Type: schema.TypeString, 218 Optional: true, 219 Computed: true, 220 }, 221 222 "s3_key_prefix": { 223 Type: schema.TypeString, 224 Optional: true, 225 Computed: true, 226 }, 227 228 "tags": tagsSchema(), 229 }, 230 } 231 } 232 233 func resourceAwsRedshiftClusterImport( 234 d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { 235 // Neither skip_final_snapshot nor final_snapshot_identifier can be fetched 236 // from any API call, so we need to default skip_final_snapshot to true so 237 // that final_snapshot_identifier is not required 238 d.Set("skip_final_snapshot", true) 239 return []*schema.ResourceData{d}, nil 240 } 241 242 func resourceAwsRedshiftClusterCreate(d *schema.ResourceData, meta interface{}) error { 243 conn := meta.(*AWSClient).redshiftconn 244 245 log.Printf("[INFO] Building Redshift Cluster Options") 246 tags := tagsFromMapRedshift(d.Get("tags").(map[string]interface{})) 247 createOpts := &redshift.CreateClusterInput{ 248 ClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)), 249 Port: aws.Int64(int64(d.Get("port").(int))), 250 MasterUserPassword: aws.String(d.Get("master_password").(string)), 251 MasterUsername: aws.String(d.Get("master_username").(string)), 252 ClusterVersion: aws.String(d.Get("cluster_version").(string)), 253 NodeType: aws.String(d.Get("node_type").(string)), 254 DBName: aws.String(d.Get("database_name").(string)), 255 AllowVersionUpgrade: aws.Bool(d.Get("allow_version_upgrade").(bool)), 256 PubliclyAccessible: aws.Bool(d.Get("publicly_accessible").(bool)), 257 AutomatedSnapshotRetentionPeriod: aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))), 258 Tags: tags, 259 } 260 261 if v := d.Get("number_of_nodes").(int); v > 1 { 262 createOpts.ClusterType = aws.String("multi-node") 263 createOpts.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int))) 264 } else { 265 createOpts.ClusterType = aws.String("single-node") 266 } 267 268 if v := d.Get("cluster_security_groups").(*schema.Set); v.Len() > 0 { 269 createOpts.ClusterSecurityGroups = expandStringList(v.List()) 270 } 271 272 if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { 273 createOpts.VpcSecurityGroupIds = expandStringList(v.List()) 274 } 275 276 if v, ok := d.GetOk("cluster_subnet_group_name"); ok { 277 createOpts.ClusterSubnetGroupName = aws.String(v.(string)) 278 } 279 280 if v, ok := d.GetOk("availability_zone"); ok { 281 createOpts.AvailabilityZone = aws.String(v.(string)) 282 } 283 284 if v, ok := d.GetOk("preferred_maintenance_window"); ok { 285 createOpts.PreferredMaintenanceWindow = aws.String(v.(string)) 286 } 287 288 if v, ok := d.GetOk("cluster_parameter_group_name"); ok { 289 createOpts.ClusterParameterGroupName = aws.String(v.(string)) 290 } 291 292 if v, ok := d.GetOk("encrypted"); ok { 293 createOpts.Encrypted = aws.Bool(v.(bool)) 294 } 295 296 if v, ok := d.GetOk("kms_key_id"); ok { 297 createOpts.KmsKeyId = aws.String(v.(string)) 298 } 299 300 if v, ok := d.GetOk("elastic_ip"); ok { 301 createOpts.ElasticIp = aws.String(v.(string)) 302 } 303 304 if v, ok := d.GetOk("iam_roles"); ok { 305 createOpts.IamRoles = expandStringList(v.(*schema.Set).List()) 306 } 307 308 log.Printf("[DEBUG] Redshift Cluster create options: %s", createOpts) 309 resp, err := conn.CreateCluster(createOpts) 310 if err != nil { 311 log.Printf("[ERROR] Error creating Redshift Cluster: %s", err) 312 return err 313 } 314 315 log.Printf("[DEBUG]: Cluster create response: %s", resp) 316 d.SetId(*resp.Cluster.ClusterIdentifier) 317 318 stateConf := &resource.StateChangeConf{ 319 Pending: []string{"creating", "backing-up", "modifying"}, 320 Target: []string{"available"}, 321 Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta), 322 Timeout: 40 * time.Minute, 323 MinTimeout: 10 * time.Second, 324 } 325 326 _, err = stateConf.WaitForState() 327 if err != nil { 328 return fmt.Errorf("[WARN] Error waiting for Redshift Cluster state to be \"available\": %s", err) 329 } 330 331 if _, ok := d.GetOk("enable_logging"); ok { 332 333 loggingErr := enableRedshiftClusterLogging(d, conn) 334 if loggingErr != nil { 335 log.Printf("[ERROR] Error Enabling Logging on Redshift Cluster: %s", err) 336 return loggingErr 337 } 338 339 } 340 341 return resourceAwsRedshiftClusterRead(d, meta) 342 } 343 344 func resourceAwsRedshiftClusterRead(d *schema.ResourceData, meta interface{}) error { 345 conn := meta.(*AWSClient).redshiftconn 346 347 log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id()) 348 resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{ 349 ClusterIdentifier: aws.String(d.Id()), 350 }) 351 352 if err != nil { 353 if awsErr, ok := err.(awserr.Error); ok { 354 if "ClusterNotFound" == awsErr.Code() { 355 d.SetId("") 356 log.Printf("[DEBUG] Redshift Cluster (%s) not found", d.Id()) 357 return nil 358 } 359 } 360 log.Printf("[DEBUG] Error describing Redshift Cluster (%s)", d.Id()) 361 return err 362 } 363 364 var rsc *redshift.Cluster 365 for _, c := range resp.Clusters { 366 if *c.ClusterIdentifier == d.Id() { 367 rsc = c 368 } 369 } 370 371 if rsc == nil { 372 log.Printf("[WARN] Redshift Cluster (%s) not found", d.Id()) 373 d.SetId("") 374 return nil 375 } 376 377 log.Printf("[INFO] Reading Redshift Cluster Logging Status: %s", d.Id()) 378 loggingStatus, loggingErr := conn.DescribeLoggingStatus(&redshift.DescribeLoggingStatusInput{ 379 ClusterIdentifier: aws.String(d.Id()), 380 }) 381 382 if loggingErr != nil { 383 return loggingErr 384 } 385 386 d.Set("master_username", rsc.MasterUsername) 387 d.Set("node_type", rsc.NodeType) 388 d.Set("allow_version_upgrade", rsc.AllowVersionUpgrade) 389 d.Set("database_name", rsc.DBName) 390 d.Set("cluster_identifier", rsc.ClusterIdentifier) 391 d.Set("cluster_version", rsc.ClusterVersion) 392 393 d.Set("cluster_subnet_group_name", rsc.ClusterSubnetGroupName) 394 d.Set("availability_zone", rsc.AvailabilityZone) 395 d.Set("encrypted", rsc.Encrypted) 396 d.Set("kms_key_id", rsc.KmsKeyId) 397 d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod) 398 d.Set("preferred_maintenance_window", rsc.PreferredMaintenanceWindow) 399 if rsc.Endpoint != nil && rsc.Endpoint.Address != nil { 400 endpoint := *rsc.Endpoint.Address 401 if rsc.Endpoint.Port != nil { 402 endpoint = fmt.Sprintf("%s:%d", endpoint, *rsc.Endpoint.Port) 403 } 404 d.Set("port", rsc.Endpoint.Port) 405 d.Set("endpoint", endpoint) 406 } 407 d.Set("cluster_parameter_group_name", rsc.ClusterParameterGroups[0].ParameterGroupName) 408 if len(rsc.ClusterNodes) > 1 { 409 d.Set("cluster_type", "multi-node") 410 } else { 411 d.Set("cluster_type", "single-node") 412 } 413 d.Set("number_of_nodes", rsc.NumberOfNodes) 414 d.Set("publicly_accessible", rsc.PubliclyAccessible) 415 416 var vpcg []string 417 for _, g := range rsc.VpcSecurityGroups { 418 vpcg = append(vpcg, *g.VpcSecurityGroupId) 419 } 420 if err := d.Set("vpc_security_group_ids", vpcg); err != nil { 421 return fmt.Errorf("[DEBUG] Error saving VPC Security Group IDs to state for Redshift Cluster (%s): %s", d.Id(), err) 422 } 423 424 var csg []string 425 for _, g := range rsc.ClusterSecurityGroups { 426 csg = append(csg, *g.ClusterSecurityGroupName) 427 } 428 if err := d.Set("cluster_security_groups", csg); err != nil { 429 return fmt.Errorf("[DEBUG] Error saving Cluster Security Group Names to state for Redshift Cluster (%s): %s", d.Id(), err) 430 } 431 432 var iamRoles []string 433 for _, i := range rsc.IamRoles { 434 iamRoles = append(iamRoles, *i.IamRoleArn) 435 } 436 if err := d.Set("iam_roles", iamRoles); err != nil { 437 return fmt.Errorf("[DEBUG] Error saving IAM Roles to state for Redshift Cluster (%s): %s", d.Id(), err) 438 } 439 440 d.Set("cluster_public_key", rsc.ClusterPublicKey) 441 d.Set("cluster_revision_number", rsc.ClusterRevisionNumber) 442 d.Set("tags", tagsToMapRedshift(rsc.Tags)) 443 444 d.Set("bucket_name", loggingStatus.BucketName) 445 d.Set("enable_logging", loggingStatus.LoggingEnabled) 446 d.Set("s3_key_prefix", loggingStatus.S3KeyPrefix) 447 448 return nil 449 } 450 451 func resourceAwsRedshiftClusterUpdate(d *schema.ResourceData, meta interface{}) error { 452 conn := meta.(*AWSClient).redshiftconn 453 d.Partial(true) 454 455 arn, tagErr := buildRedshiftARN(d.Id(), meta.(*AWSClient).accountid, meta.(*AWSClient).region) 456 if tagErr != nil { 457 return fmt.Errorf("Error building ARN for Redshift Cluster, not updating Tags for cluster %s", d.Id()) 458 } else { 459 if tagErr := setTagsRedshift(conn, d, arn); tagErr != nil { 460 return tagErr 461 } else { 462 d.SetPartial("tags") 463 } 464 } 465 466 requestUpdate := false 467 log.Printf("[INFO] Building Redshift Modify Cluster Options") 468 req := &redshift.ModifyClusterInput{ 469 ClusterIdentifier: aws.String(d.Id()), 470 } 471 472 if d.HasChange("cluster_type") { 473 req.ClusterType = aws.String(d.Get("cluster_type").(string)) 474 requestUpdate = true 475 } 476 477 if d.HasChange("node_type") { 478 req.NodeType = aws.String(d.Get("node_type").(string)) 479 requestUpdate = true 480 } 481 482 if d.HasChange("number_of_nodes") { 483 if v := d.Get("number_of_nodes").(int); v > 1 { 484 req.ClusterType = aws.String("multi-node") 485 req.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int))) 486 } else { 487 req.ClusterType = aws.String("single-node") 488 } 489 490 req.NodeType = aws.String(d.Get("node_type").(string)) 491 requestUpdate = true 492 } 493 494 if d.HasChange("cluster_security_groups") { 495 req.ClusterSecurityGroups = expandStringList(d.Get("cluster_security_groups").(*schema.Set).List()) 496 requestUpdate = true 497 } 498 499 if d.HasChange("vpc_security_group_ips") { 500 req.VpcSecurityGroupIds = expandStringList(d.Get("vpc_security_group_ips").(*schema.Set).List()) 501 requestUpdate = true 502 } 503 504 if d.HasChange("master_password") { 505 req.MasterUserPassword = aws.String(d.Get("master_password").(string)) 506 requestUpdate = true 507 } 508 509 if d.HasChange("cluster_parameter_group_name") { 510 req.ClusterParameterGroupName = aws.String(d.Get("cluster_parameter_group_name").(string)) 511 requestUpdate = true 512 } 513 514 if d.HasChange("automated_snapshot_retention_period") { 515 req.AutomatedSnapshotRetentionPeriod = aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))) 516 requestUpdate = true 517 } 518 519 if d.HasChange("preferred_maintenance_window") { 520 req.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintenance_window").(string)) 521 requestUpdate = true 522 } 523 524 if d.HasChange("cluster_version") { 525 req.ClusterVersion = aws.String(d.Get("cluster_version").(string)) 526 requestUpdate = true 527 } 528 529 if d.HasChange("allow_version_upgrade") { 530 req.AllowVersionUpgrade = aws.Bool(d.Get("allow_version_upgrade").(bool)) 531 requestUpdate = true 532 } 533 534 if d.HasChange("publicly_accessible") { 535 req.PubliclyAccessible = aws.Bool(d.Get("publicly_accessible").(bool)) 536 requestUpdate = true 537 } 538 539 if requestUpdate { 540 log.Printf("[INFO] Modifying Redshift Cluster: %s", d.Id()) 541 log.Printf("[DEBUG] Redshift Cluster Modify options: %s", req) 542 _, err := conn.ModifyCluster(req) 543 if err != nil { 544 return fmt.Errorf("[WARN] Error modifying Redshift Cluster (%s): %s", d.Id(), err) 545 } 546 } 547 548 if d.HasChange("iam_roles") { 549 o, n := d.GetChange("iam_roles") 550 if o == nil { 551 o = new(schema.Set) 552 } 553 if n == nil { 554 n = new(schema.Set) 555 } 556 557 os := o.(*schema.Set) 558 ns := n.(*schema.Set) 559 560 removeIams := os.Difference(ns).List() 561 addIams := ns.Difference(os).List() 562 563 log.Printf("[INFO] Building Redshift Modify Cluster IAM Role Options") 564 req := &redshift.ModifyClusterIamRolesInput{ 565 ClusterIdentifier: aws.String(d.Id()), 566 AddIamRoles: expandStringList(addIams), 567 RemoveIamRoles: expandStringList(removeIams), 568 } 569 570 log.Printf("[INFO] Modifying Redshift Cluster IAM Roles: %s", d.Id()) 571 log.Printf("[DEBUG] Redshift Cluster Modify IAM Role options: %s", req) 572 _, err := conn.ModifyClusterIamRoles(req) 573 if err != nil { 574 return fmt.Errorf("[WARN] Error modifying Redshift Cluster IAM Roles (%s): %s", d.Id(), err) 575 } 576 577 d.SetPartial("iam_roles") 578 } 579 580 if requestUpdate || d.HasChange("iam_roles") { 581 582 stateConf := &resource.StateChangeConf{ 583 Pending: []string{"creating", "deleting", "rebooting", "resizing", "renaming", "modifying"}, 584 Target: []string{"available"}, 585 Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta), 586 Timeout: 40 * time.Minute, 587 MinTimeout: 10 * time.Second, 588 } 589 590 // Wait, catching any errors 591 _, err := stateConf.WaitForState() 592 if err != nil { 593 return fmt.Errorf("[WARN] Error Modifying Redshift Cluster (%s): %s", d.Id(), err) 594 } 595 } 596 597 if d.HasChange("enable_logging") || d.HasChange("bucket_name") || d.HasChange("s3_key_prefix") { 598 var loggingErr error 599 if _, ok := d.GetOk("enable_logging"); ok { 600 601 log.Printf("[INFO] Enabling Logging for Redshift Cluster %q", d.Id()) 602 loggingErr = enableRedshiftClusterLogging(d, conn) 603 if loggingErr != nil { 604 return loggingErr 605 } 606 } else { 607 608 log.Printf("[INFO] Disabling Logging for Redshift Cluster %q", d.Id()) 609 _, loggingErr = conn.DisableLogging(&redshift.DisableLoggingInput{ 610 ClusterIdentifier: aws.String(d.Id()), 611 }) 612 if loggingErr != nil { 613 return loggingErr 614 } 615 } 616 617 d.SetPartial("enable_logging") 618 } 619 620 d.Partial(false) 621 622 return resourceAwsRedshiftClusterRead(d, meta) 623 } 624 625 func enableRedshiftClusterLogging(d *schema.ResourceData, conn *redshift.Redshift) error { 626 if _, ok := d.GetOk("bucket_name"); !ok { 627 return fmt.Errorf("bucket_name must be set when enabling logging for Redshift Clusters") 628 } 629 630 params := &redshift.EnableLoggingInput{ 631 ClusterIdentifier: aws.String(d.Id()), 632 BucketName: aws.String(d.Get("bucket_name").(string)), 633 } 634 635 if v, ok := d.GetOk("s3_key_prefix"); ok { 636 params.S3KeyPrefix = aws.String(v.(string)) 637 } 638 639 _, loggingErr := conn.EnableLogging(params) 640 if loggingErr != nil { 641 log.Printf("[ERROR] Error Enabling Logging on Redshift Cluster: %s", loggingErr) 642 return loggingErr 643 } 644 return nil 645 } 646 647 func resourceAwsRedshiftClusterDelete(d *schema.ResourceData, meta interface{}) error { 648 conn := meta.(*AWSClient).redshiftconn 649 log.Printf("[DEBUG] Destroying Redshift Cluster (%s)", d.Id()) 650 651 deleteOpts := redshift.DeleteClusterInput{ 652 ClusterIdentifier: aws.String(d.Id()), 653 } 654 655 skipFinalSnapshot := d.Get("skip_final_snapshot").(bool) 656 deleteOpts.SkipFinalClusterSnapshot = aws.Bool(skipFinalSnapshot) 657 658 if skipFinalSnapshot == false { 659 if name, present := d.GetOk("final_snapshot_identifier"); present { 660 deleteOpts.FinalClusterSnapshotIdentifier = aws.String(name.(string)) 661 } else { 662 return fmt.Errorf("Redshift Cluster Instance FinalSnapshotIdentifier is required when a final snapshot is required") 663 } 664 } 665 666 log.Printf("[DEBUG] Redshift Cluster delete options: %s", deleteOpts) 667 _, err := conn.DeleteCluster(&deleteOpts) 668 if err != nil { 669 return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err) 670 } 671 672 stateConf := &resource.StateChangeConf{ 673 Pending: []string{"available", "creating", "deleting", "rebooting", "resizing", "renaming"}, 674 Target: []string{"destroyed"}, 675 Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta), 676 Timeout: 40 * time.Minute, 677 MinTimeout: 5 * time.Second, 678 } 679 680 // Wait, catching any errors 681 _, err = stateConf.WaitForState() 682 if err != nil { 683 return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err) 684 } 685 686 log.Printf("[INFO] Redshift Cluster %s successfully deleted", d.Id()) 687 688 return nil 689 } 690 691 func resourceAwsRedshiftClusterStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { 692 return func() (interface{}, string, error) { 693 conn := meta.(*AWSClient).redshiftconn 694 695 log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id()) 696 resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{ 697 ClusterIdentifier: aws.String(d.Id()), 698 }) 699 700 if err != nil { 701 if awsErr, ok := err.(awserr.Error); ok { 702 if "ClusterNotFound" == awsErr.Code() { 703 return 42, "destroyed", nil 704 } 705 } 706 log.Printf("[WARN] Error on retrieving Redshift Cluster (%s) when waiting: %s", d.Id(), err) 707 return nil, "", err 708 } 709 710 var rsc *redshift.Cluster 711 712 for _, c := range resp.Clusters { 713 if *c.ClusterIdentifier == d.Id() { 714 rsc = c 715 } 716 } 717 718 if rsc == nil { 719 return 42, "destroyed", nil 720 } 721 722 if rsc.ClusterStatus != nil { 723 log.Printf("[DEBUG] Redshift Cluster status (%s): %s", d.Id(), *rsc.ClusterStatus) 724 } 725 726 return rsc, *rsc.ClusterStatus, nil 727 } 728 } 729 730 func validateRedshiftClusterIdentifier(v interface{}, k string) (ws []string, errors []error) { 731 value := v.(string) 732 if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) { 733 errors = append(errors, fmt.Errorf( 734 "only lowercase alphanumeric characters and hyphens allowed in %q", k)) 735 } 736 if !regexp.MustCompile(`^[a-z]`).MatchString(value) { 737 errors = append(errors, fmt.Errorf( 738 "first character of %q must be a letter", k)) 739 } 740 if regexp.MustCompile(`--`).MatchString(value) { 741 errors = append(errors, fmt.Errorf( 742 "%q cannot contain two consecutive hyphens", k)) 743 } 744 if regexp.MustCompile(`-$`).MatchString(value) { 745 errors = append(errors, fmt.Errorf( 746 "%q cannot end with a hyphen", k)) 747 } 748 return 749 } 750 751 func validateRedshiftClusterDbName(v interface{}, k string) (ws []string, errors []error) { 752 value := v.(string) 753 if !regexp.MustCompile(`^[a-z]+$`).MatchString(value) { 754 errors = append(errors, fmt.Errorf( 755 "only lowercase letters characters allowed in %q", k)) 756 } 757 if len(value) > 64 { 758 errors = append(errors, fmt.Errorf( 759 "%q cannot be longer than 64 characters: %q", k, value)) 760 } 761 if value == "" { 762 errors = append(errors, fmt.Errorf( 763 "%q cannot be an empty string", k)) 764 } 765 766 return 767 } 768 769 func validateRedshiftClusterFinalSnapshotIdentifier(v interface{}, k string) (ws []string, errors []error) { 770 value := v.(string) 771 if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { 772 errors = append(errors, fmt.Errorf( 773 "only alphanumeric characters and hyphens allowed in %q", k)) 774 } 775 if regexp.MustCompile(`--`).MatchString(value) { 776 errors = append(errors, fmt.Errorf("%q cannot contain two consecutive hyphens", k)) 777 } 778 if regexp.MustCompile(`-$`).MatchString(value) { 779 errors = append(errors, fmt.Errorf("%q cannot end in a hyphen", k)) 780 } 781 if len(value) > 255 { 782 errors = append(errors, fmt.Errorf("%q cannot be more than 255 characters", k)) 783 } 784 return 785 } 786 787 func validateRedshiftClusterMasterUsername(v interface{}, k string) (ws []string, errors []error) { 788 value := v.(string) 789 if !regexp.MustCompile(`^\w+$`).MatchString(value) { 790 errors = append(errors, fmt.Errorf( 791 "only alphanumeric characters in %q", k)) 792 } 793 if !regexp.MustCompile(`^[A-Za-z]`).MatchString(value) { 794 errors = append(errors, fmt.Errorf( 795 "first character of %q must be a letter", k)) 796 } 797 if len(value) > 128 { 798 errors = append(errors, fmt.Errorf("%q cannot be more than 128 characters", k)) 799 } 800 return 801 } 802 803 func buildRedshiftARN(identifier, accountid, region string) (string, error) { 804 if accountid == "" { 805 return "", fmt.Errorf("Unable to construct cluster ARN because of missing AWS Account ID") 806 } 807 arn := fmt.Sprintf("arn:aws:redshift:%s:%s:cluster:%s", region, accountid, identifier) 808 return arn, nil 809 810 }