github.com/ves/terraform@v0.8.0-beta2/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 Optional: true, 55 ValidateFunc: validateRedshiftClusterMasterUsername, 56 }, 57 58 "master_password": &schema.Schema{ 59 Type: schema.TypeString, 60 Optional: true, 61 Sensitive: true, 62 ValidateFunc: validateRedshiftClusterMasterPassword, 63 }, 64 65 "cluster_security_groups": &schema.Schema{ 66 Type: schema.TypeSet, 67 Optional: true, 68 Computed: true, 69 Elem: &schema.Schema{Type: schema.TypeString}, 70 Set: schema.HashString, 71 }, 72 73 "vpc_security_group_ids": &schema.Schema{ 74 Type: schema.TypeSet, 75 Optional: true, 76 Computed: true, 77 Elem: &schema.Schema{Type: schema.TypeString}, 78 Set: schema.HashString, 79 }, 80 81 "cluster_subnet_group_name": &schema.Schema{ 82 Type: schema.TypeString, 83 Optional: true, 84 ForceNew: true, 85 Computed: true, 86 }, 87 88 "availability_zone": &schema.Schema{ 89 Type: schema.TypeString, 90 Optional: true, 91 Computed: true, 92 }, 93 94 "preferred_maintenance_window": &schema.Schema{ 95 Type: schema.TypeString, 96 Optional: true, 97 Computed: true, 98 StateFunc: func(val interface{}) string { 99 if val == nil { 100 return "" 101 } 102 return strings.ToLower(val.(string)) 103 }, 104 }, 105 106 "cluster_parameter_group_name": &schema.Schema{ 107 Type: schema.TypeString, 108 Optional: true, 109 Computed: true, 110 }, 111 112 "automated_snapshot_retention_period": &schema.Schema{ 113 Type: schema.TypeInt, 114 Optional: true, 115 Default: 1, 116 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 117 value := v.(int) 118 if value > 35 { 119 es = append(es, fmt.Errorf( 120 "backup retention period cannot be more than 35 days")) 121 } 122 return 123 }, 124 }, 125 126 "port": &schema.Schema{ 127 Type: schema.TypeInt, 128 Optional: true, 129 Default: 5439, 130 }, 131 132 "cluster_version": &schema.Schema{ 133 Type: schema.TypeString, 134 Optional: true, 135 Default: "1.0", 136 }, 137 138 "allow_version_upgrade": &schema.Schema{ 139 Type: schema.TypeBool, 140 Optional: true, 141 Default: true, 142 }, 143 144 "number_of_nodes": &schema.Schema{ 145 Type: schema.TypeInt, 146 Optional: true, 147 Default: 1, 148 }, 149 150 "publicly_accessible": &schema.Schema{ 151 Type: schema.TypeBool, 152 Optional: true, 153 Default: true, 154 }, 155 156 "encrypted": &schema.Schema{ 157 Type: schema.TypeBool, 158 Optional: true, 159 Computed: true, 160 }, 161 162 "enhanced_vpc_routing": &schema.Schema{ 163 Type: schema.TypeBool, 164 Optional: true, 165 Computed: true, 166 }, 167 168 "kms_key_id": &schema.Schema{ 169 Type: schema.TypeString, 170 Optional: true, 171 Computed: true, 172 ForceNew: true, 173 }, 174 175 "elastic_ip": &schema.Schema{ 176 Type: schema.TypeString, 177 Optional: true, 178 }, 179 180 "final_snapshot_identifier": &schema.Schema{ 181 Type: schema.TypeString, 182 Optional: true, 183 ValidateFunc: validateRedshiftClusterFinalSnapshotIdentifier, 184 }, 185 186 "skip_final_snapshot": &schema.Schema{ 187 Type: schema.TypeBool, 188 Optional: true, 189 Default: true, 190 }, 191 192 "endpoint": &schema.Schema{ 193 Type: schema.TypeString, 194 Optional: true, 195 Computed: true, 196 }, 197 198 "cluster_public_key": &schema.Schema{ 199 Type: schema.TypeString, 200 Optional: true, 201 Computed: true, 202 }, 203 204 "cluster_revision_number": &schema.Schema{ 205 Type: schema.TypeString, 206 Optional: true, 207 Computed: true, 208 }, 209 210 "iam_roles": &schema.Schema{ 211 Type: schema.TypeSet, 212 Optional: true, 213 Computed: true, 214 Elem: &schema.Schema{Type: schema.TypeString}, 215 Set: schema.HashString, 216 }, 217 218 "enable_logging": { 219 Type: schema.TypeBool, 220 Optional: true, 221 Default: false, 222 }, 223 224 "bucket_name": { 225 Type: schema.TypeString, 226 Optional: true, 227 Computed: true, 228 }, 229 230 "s3_key_prefix": { 231 Type: schema.TypeString, 232 Optional: true, 233 Computed: true, 234 }, 235 236 "snapshot_identifier": &schema.Schema{ 237 Type: schema.TypeString, 238 Optional: true, 239 }, 240 241 "snapshot_cluster_identifier": &schema.Schema{ 242 Type: schema.TypeString, 243 Optional: true, 244 }, 245 246 "tags": tagsSchema(), 247 }, 248 } 249 } 250 251 func resourceAwsRedshiftClusterImport( 252 d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { 253 // Neither skip_final_snapshot nor final_snapshot_identifier can be fetched 254 // from any API call, so we need to default skip_final_snapshot to true so 255 // that final_snapshot_identifier is not required 256 d.Set("skip_final_snapshot", true) 257 return []*schema.ResourceData{d}, nil 258 } 259 260 func resourceAwsRedshiftClusterCreate(d *schema.ResourceData, meta interface{}) error { 261 conn := meta.(*AWSClient).redshiftconn 262 tags := tagsFromMapRedshift(d.Get("tags").(map[string]interface{})) 263 264 if v, ok := d.GetOk("snapshot_identifier"); ok { 265 restoreOpts := &redshift.RestoreFromClusterSnapshotInput{ 266 ClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)), 267 SnapshotIdentifier: aws.String(v.(string)), 268 Port: aws.Int64(int64(d.Get("port").(int))), 269 AllowVersionUpgrade: aws.Bool(d.Get("allow_version_upgrade").(bool)), 270 NodeType: aws.String(d.Get("node_type").(string)), 271 PubliclyAccessible: aws.Bool(d.Get("publicly_accessible").(bool)), 272 AutomatedSnapshotRetentionPeriod: aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))), 273 } 274 275 if v, ok := d.GetOk("snapshot_cluster_identifier"); ok { 276 restoreOpts.SnapshotClusterIdentifier = aws.String(v.(string)) 277 } 278 279 if v, ok := d.GetOk("availability_zone"); ok { 280 restoreOpts.AvailabilityZone = aws.String(v.(string)) 281 } 282 283 if v, ok := d.GetOk("cluster_subnet_group_name"); ok { 284 restoreOpts.ClusterSubnetGroupName = aws.String(v.(string)) 285 } 286 287 if v, ok := d.GetOk("cluster_parameter_group_name"); ok { 288 restoreOpts.ClusterParameterGroupName = aws.String(v.(string)) 289 } 290 291 if v := d.Get("cluster_security_groups").(*schema.Set); v.Len() > 0 { 292 restoreOpts.ClusterSecurityGroups = expandStringList(v.List()) 293 } 294 295 if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { 296 restoreOpts.VpcSecurityGroupIds = expandStringList(v.List()) 297 } 298 299 if v, ok := d.GetOk("preferred_maintenance_window"); ok { 300 restoreOpts.PreferredMaintenanceWindow = aws.String(v.(string)) 301 } 302 303 if v, ok := d.GetOk("kms_key_id"); ok { 304 restoreOpts.KmsKeyId = aws.String(v.(string)) 305 } 306 307 if v, ok := d.GetOk("elastic_ip"); ok { 308 restoreOpts.ElasticIp = aws.String(v.(string)) 309 } 310 311 if v, ok := d.GetOk("enhanced_vpc_routing"); ok { 312 restoreOpts.EnhancedVpcRouting = aws.Bool(v.(bool)) 313 } 314 315 if v, ok := d.GetOk("iam_roles"); ok { 316 restoreOpts.IamRoles = expandStringList(v.(*schema.Set).List()) 317 } 318 319 log.Printf("[DEBUG] Redshift Cluster restore cluster options: %s", restoreOpts) 320 321 resp, err := conn.RestoreFromClusterSnapshot(restoreOpts) 322 if err != nil { 323 log.Printf("[ERROR] Error Restoring Redshift Cluster from Snapshot: %s", err) 324 return err 325 } 326 327 d.SetId(*resp.Cluster.ClusterIdentifier) 328 329 } else { 330 createOpts := &redshift.CreateClusterInput{ 331 ClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)), 332 Port: aws.Int64(int64(d.Get("port").(int))), 333 MasterUserPassword: aws.String(d.Get("master_password").(string)), 334 MasterUsername: aws.String(d.Get("master_username").(string)), 335 ClusterVersion: aws.String(d.Get("cluster_version").(string)), 336 NodeType: aws.String(d.Get("node_type").(string)), 337 DBName: aws.String(d.Get("database_name").(string)), 338 AllowVersionUpgrade: aws.Bool(d.Get("allow_version_upgrade").(bool)), 339 PubliclyAccessible: aws.Bool(d.Get("publicly_accessible").(bool)), 340 AutomatedSnapshotRetentionPeriod: aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))), 341 Tags: tags, 342 } 343 344 if v := d.Get("number_of_nodes").(int); v > 1 { 345 createOpts.ClusterType = aws.String("multi-node") 346 createOpts.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int))) 347 } else { 348 createOpts.ClusterType = aws.String("single-node") 349 } 350 351 if v := d.Get("cluster_security_groups").(*schema.Set); v.Len() > 0 { 352 createOpts.ClusterSecurityGroups = expandStringList(v.List()) 353 } 354 355 if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { 356 createOpts.VpcSecurityGroupIds = expandStringList(v.List()) 357 } 358 359 if v, ok := d.GetOk("cluster_subnet_group_name"); ok { 360 createOpts.ClusterSubnetGroupName = aws.String(v.(string)) 361 } 362 363 if v, ok := d.GetOk("availability_zone"); ok { 364 createOpts.AvailabilityZone = aws.String(v.(string)) 365 } 366 367 if v, ok := d.GetOk("preferred_maintenance_window"); ok { 368 createOpts.PreferredMaintenanceWindow = aws.String(v.(string)) 369 } 370 371 if v, ok := d.GetOk("cluster_parameter_group_name"); ok { 372 createOpts.ClusterParameterGroupName = aws.String(v.(string)) 373 } 374 375 if v, ok := d.GetOk("encrypted"); ok { 376 createOpts.Encrypted = aws.Bool(v.(bool)) 377 } 378 379 if v, ok := d.GetOk("enhanced_vpc_routing"); ok { 380 createOpts.EnhancedVpcRouting = aws.Bool(v.(bool)) 381 } 382 383 if v, ok := d.GetOk("kms_key_id"); ok { 384 createOpts.KmsKeyId = aws.String(v.(string)) 385 } 386 387 if v, ok := d.GetOk("elastic_ip"); ok { 388 createOpts.ElasticIp = aws.String(v.(string)) 389 } 390 391 if v, ok := d.GetOk("iam_roles"); ok { 392 createOpts.IamRoles = expandStringList(v.(*schema.Set).List()) 393 } 394 395 log.Printf("[DEBUG] Redshift Cluster create options: %s", createOpts) 396 resp, err := conn.CreateCluster(createOpts) 397 if err != nil { 398 log.Printf("[ERROR] Error creating Redshift Cluster: %s", err) 399 return err 400 } 401 402 log.Printf("[DEBUG]: Cluster create response: %s", resp) 403 d.SetId(*resp.Cluster.ClusterIdentifier) 404 } 405 406 stateConf := &resource.StateChangeConf{ 407 Pending: []string{"creating", "backing-up", "modifying", "restoring"}, 408 Target: []string{"available"}, 409 Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta), 410 Timeout: 40 * time.Minute, 411 MinTimeout: 10 * time.Second, 412 } 413 414 _, err := stateConf.WaitForState() 415 if err != nil { 416 return fmt.Errorf("[WARN] Error waiting for Redshift Cluster state to be \"available\": %s", err) 417 } 418 419 if _, ok := d.GetOk("enable_logging"); ok { 420 421 loggingErr := enableRedshiftClusterLogging(d, conn) 422 if loggingErr != nil { 423 log.Printf("[ERROR] Error Enabling Logging on Redshift Cluster: %s", err) 424 return loggingErr 425 } 426 427 } 428 429 return resourceAwsRedshiftClusterRead(d, meta) 430 } 431 432 func resourceAwsRedshiftClusterRead(d *schema.ResourceData, meta interface{}) error { 433 conn := meta.(*AWSClient).redshiftconn 434 435 log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id()) 436 resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{ 437 ClusterIdentifier: aws.String(d.Id()), 438 }) 439 440 if err != nil { 441 if awsErr, ok := err.(awserr.Error); ok { 442 if "ClusterNotFound" == awsErr.Code() { 443 d.SetId("") 444 log.Printf("[DEBUG] Redshift Cluster (%s) not found", d.Id()) 445 return nil 446 } 447 } 448 log.Printf("[DEBUG] Error describing Redshift Cluster (%s)", d.Id()) 449 return err 450 } 451 452 var rsc *redshift.Cluster 453 for _, c := range resp.Clusters { 454 if *c.ClusterIdentifier == d.Id() { 455 rsc = c 456 } 457 } 458 459 if rsc == nil { 460 log.Printf("[WARN] Redshift Cluster (%s) not found", d.Id()) 461 d.SetId("") 462 return nil 463 } 464 465 log.Printf("[INFO] Reading Redshift Cluster Logging Status: %s", d.Id()) 466 loggingStatus, loggingErr := conn.DescribeLoggingStatus(&redshift.DescribeLoggingStatusInput{ 467 ClusterIdentifier: aws.String(d.Id()), 468 }) 469 470 if loggingErr != nil { 471 return loggingErr 472 } 473 474 d.Set("master_username", rsc.MasterUsername) 475 d.Set("node_type", rsc.NodeType) 476 d.Set("allow_version_upgrade", rsc.AllowVersionUpgrade) 477 d.Set("database_name", rsc.DBName) 478 d.Set("cluster_identifier", rsc.ClusterIdentifier) 479 d.Set("cluster_version", rsc.ClusterVersion) 480 481 d.Set("cluster_subnet_group_name", rsc.ClusterSubnetGroupName) 482 d.Set("availability_zone", rsc.AvailabilityZone) 483 d.Set("encrypted", rsc.Encrypted) 484 d.Set("enhanced_vpc_routing", rsc.EnhancedVpcRouting) 485 d.Set("kms_key_id", rsc.KmsKeyId) 486 d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod) 487 d.Set("preferred_maintenance_window", rsc.PreferredMaintenanceWindow) 488 if rsc.Endpoint != nil && rsc.Endpoint.Address != nil { 489 endpoint := *rsc.Endpoint.Address 490 if rsc.Endpoint.Port != nil { 491 endpoint = fmt.Sprintf("%s:%d", endpoint, *rsc.Endpoint.Port) 492 } 493 d.Set("port", rsc.Endpoint.Port) 494 d.Set("endpoint", endpoint) 495 } 496 d.Set("cluster_parameter_group_name", rsc.ClusterParameterGroups[0].ParameterGroupName) 497 if len(rsc.ClusterNodes) > 1 { 498 d.Set("cluster_type", "multi-node") 499 } else { 500 d.Set("cluster_type", "single-node") 501 } 502 d.Set("number_of_nodes", rsc.NumberOfNodes) 503 d.Set("publicly_accessible", rsc.PubliclyAccessible) 504 505 var vpcg []string 506 for _, g := range rsc.VpcSecurityGroups { 507 vpcg = append(vpcg, *g.VpcSecurityGroupId) 508 } 509 if err := d.Set("vpc_security_group_ids", vpcg); err != nil { 510 return fmt.Errorf("[DEBUG] Error saving VPC Security Group IDs to state for Redshift Cluster (%s): %s", d.Id(), err) 511 } 512 513 var csg []string 514 for _, g := range rsc.ClusterSecurityGroups { 515 csg = append(csg, *g.ClusterSecurityGroupName) 516 } 517 if err := d.Set("cluster_security_groups", csg); err != nil { 518 return fmt.Errorf("[DEBUG] Error saving Cluster Security Group Names to state for Redshift Cluster (%s): %s", d.Id(), err) 519 } 520 521 var iamRoles []string 522 for _, i := range rsc.IamRoles { 523 iamRoles = append(iamRoles, *i.IamRoleArn) 524 } 525 if err := d.Set("iam_roles", iamRoles); err != nil { 526 return fmt.Errorf("[DEBUG] Error saving IAM Roles to state for Redshift Cluster (%s): %s", d.Id(), err) 527 } 528 529 d.Set("cluster_public_key", rsc.ClusterPublicKey) 530 d.Set("cluster_revision_number", rsc.ClusterRevisionNumber) 531 d.Set("tags", tagsToMapRedshift(rsc.Tags)) 532 533 d.Set("bucket_name", loggingStatus.BucketName) 534 d.Set("enable_logging", loggingStatus.LoggingEnabled) 535 d.Set("s3_key_prefix", loggingStatus.S3KeyPrefix) 536 537 return nil 538 } 539 540 func resourceAwsRedshiftClusterUpdate(d *schema.ResourceData, meta interface{}) error { 541 conn := meta.(*AWSClient).redshiftconn 542 d.Partial(true) 543 544 arn, tagErr := buildRedshiftARN(d.Id(), meta.(*AWSClient).partition, meta.(*AWSClient).accountid, meta.(*AWSClient).region) 545 if tagErr != nil { 546 return fmt.Errorf("Error building ARN for Redshift Cluster, not updating Tags for cluster %s", d.Id()) 547 } else { 548 if tagErr := setTagsRedshift(conn, d, arn); tagErr != nil { 549 return tagErr 550 } else { 551 d.SetPartial("tags") 552 } 553 } 554 555 requestUpdate := false 556 log.Printf("[INFO] Building Redshift Modify Cluster Options") 557 req := &redshift.ModifyClusterInput{ 558 ClusterIdentifier: aws.String(d.Id()), 559 } 560 561 if d.HasChange("cluster_type") { 562 req.ClusterType = aws.String(d.Get("cluster_type").(string)) 563 requestUpdate = true 564 } 565 566 if d.HasChange("node_type") { 567 req.NodeType = aws.String(d.Get("node_type").(string)) 568 requestUpdate = true 569 } 570 571 if d.HasChange("number_of_nodes") { 572 if v := d.Get("number_of_nodes").(int); v > 1 { 573 req.ClusterType = aws.String("multi-node") 574 req.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int))) 575 } else { 576 req.ClusterType = aws.String("single-node") 577 } 578 579 req.NodeType = aws.String(d.Get("node_type").(string)) 580 requestUpdate = true 581 } 582 583 if d.HasChange("cluster_security_groups") { 584 req.ClusterSecurityGroups = expandStringList(d.Get("cluster_security_groups").(*schema.Set).List()) 585 requestUpdate = true 586 } 587 588 if d.HasChange("vpc_security_group_ips") { 589 req.VpcSecurityGroupIds = expandStringList(d.Get("vpc_security_group_ips").(*schema.Set).List()) 590 requestUpdate = true 591 } 592 593 if d.HasChange("master_password") { 594 req.MasterUserPassword = aws.String(d.Get("master_password").(string)) 595 requestUpdate = true 596 } 597 598 if d.HasChange("cluster_parameter_group_name") { 599 req.ClusterParameterGroupName = aws.String(d.Get("cluster_parameter_group_name").(string)) 600 requestUpdate = true 601 } 602 603 if d.HasChange("automated_snapshot_retention_period") { 604 req.AutomatedSnapshotRetentionPeriod = aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))) 605 requestUpdate = true 606 } 607 608 if d.HasChange("preferred_maintenance_window") { 609 req.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintenance_window").(string)) 610 requestUpdate = true 611 } 612 613 if d.HasChange("cluster_version") { 614 req.ClusterVersion = aws.String(d.Get("cluster_version").(string)) 615 requestUpdate = true 616 } 617 618 if d.HasChange("allow_version_upgrade") { 619 req.AllowVersionUpgrade = aws.Bool(d.Get("allow_version_upgrade").(bool)) 620 requestUpdate = true 621 } 622 623 if d.HasChange("publicly_accessible") { 624 req.PubliclyAccessible = aws.Bool(d.Get("publicly_accessible").(bool)) 625 requestUpdate = true 626 } 627 628 if d.HasChange("enhanced_vpc_routing") { 629 req.EnhancedVpcRouting = aws.Bool(d.Get("enhanced_vpc_routing").(bool)) 630 requestUpdate = true 631 } 632 633 if requestUpdate { 634 log.Printf("[INFO] Modifying Redshift Cluster: %s", d.Id()) 635 log.Printf("[DEBUG] Redshift Cluster Modify options: %s", req) 636 _, err := conn.ModifyCluster(req) 637 if err != nil { 638 return fmt.Errorf("[WARN] Error modifying Redshift Cluster (%s): %s", d.Id(), err) 639 } 640 } 641 642 if d.HasChange("iam_roles") { 643 o, n := d.GetChange("iam_roles") 644 if o == nil { 645 o = new(schema.Set) 646 } 647 if n == nil { 648 n = new(schema.Set) 649 } 650 651 os := o.(*schema.Set) 652 ns := n.(*schema.Set) 653 654 removeIams := os.Difference(ns).List() 655 addIams := ns.Difference(os).List() 656 657 log.Printf("[INFO] Building Redshift Modify Cluster IAM Role Options") 658 req := &redshift.ModifyClusterIamRolesInput{ 659 ClusterIdentifier: aws.String(d.Id()), 660 AddIamRoles: expandStringList(addIams), 661 RemoveIamRoles: expandStringList(removeIams), 662 } 663 664 log.Printf("[INFO] Modifying Redshift Cluster IAM Roles: %s", d.Id()) 665 log.Printf("[DEBUG] Redshift Cluster Modify IAM Role options: %s", req) 666 _, err := conn.ModifyClusterIamRoles(req) 667 if err != nil { 668 return fmt.Errorf("[WARN] Error modifying Redshift Cluster IAM Roles (%s): %s", d.Id(), err) 669 } 670 671 d.SetPartial("iam_roles") 672 } 673 674 if requestUpdate || d.HasChange("iam_roles") { 675 676 stateConf := &resource.StateChangeConf{ 677 Pending: []string{"creating", "deleting", "rebooting", "resizing", "renaming", "modifying"}, 678 Target: []string{"available"}, 679 Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta), 680 Timeout: 40 * time.Minute, 681 MinTimeout: 10 * time.Second, 682 } 683 684 // Wait, catching any errors 685 _, err := stateConf.WaitForState() 686 if err != nil { 687 return fmt.Errorf("[WARN] Error Modifying Redshift Cluster (%s): %s", d.Id(), err) 688 } 689 } 690 691 if d.HasChange("enable_logging") || d.HasChange("bucket_name") || d.HasChange("s3_key_prefix") { 692 var loggingErr error 693 if _, ok := d.GetOk("enable_logging"); ok { 694 695 log.Printf("[INFO] Enabling Logging for Redshift Cluster %q", d.Id()) 696 loggingErr = enableRedshiftClusterLogging(d, conn) 697 if loggingErr != nil { 698 return loggingErr 699 } 700 } else { 701 702 log.Printf("[INFO] Disabling Logging for Redshift Cluster %q", d.Id()) 703 _, loggingErr = conn.DisableLogging(&redshift.DisableLoggingInput{ 704 ClusterIdentifier: aws.String(d.Id()), 705 }) 706 if loggingErr != nil { 707 return loggingErr 708 } 709 } 710 711 d.SetPartial("enable_logging") 712 } 713 714 d.Partial(false) 715 716 return resourceAwsRedshiftClusterRead(d, meta) 717 } 718 719 func enableRedshiftClusterLogging(d *schema.ResourceData, conn *redshift.Redshift) error { 720 if _, ok := d.GetOk("bucket_name"); !ok { 721 return fmt.Errorf("bucket_name must be set when enabling logging for Redshift Clusters") 722 } 723 724 params := &redshift.EnableLoggingInput{ 725 ClusterIdentifier: aws.String(d.Id()), 726 BucketName: aws.String(d.Get("bucket_name").(string)), 727 } 728 729 if v, ok := d.GetOk("s3_key_prefix"); ok { 730 params.S3KeyPrefix = aws.String(v.(string)) 731 } 732 733 _, loggingErr := conn.EnableLogging(params) 734 if loggingErr != nil { 735 log.Printf("[ERROR] Error Enabling Logging on Redshift Cluster: %s", loggingErr) 736 return loggingErr 737 } 738 return nil 739 } 740 741 func resourceAwsRedshiftClusterDelete(d *schema.ResourceData, meta interface{}) error { 742 conn := meta.(*AWSClient).redshiftconn 743 log.Printf("[DEBUG] Destroying Redshift Cluster (%s)", d.Id()) 744 745 deleteOpts := redshift.DeleteClusterInput{ 746 ClusterIdentifier: aws.String(d.Id()), 747 } 748 749 skipFinalSnapshot := d.Get("skip_final_snapshot").(bool) 750 deleteOpts.SkipFinalClusterSnapshot = aws.Bool(skipFinalSnapshot) 751 752 if skipFinalSnapshot == false { 753 if name, present := d.GetOk("final_snapshot_identifier"); present { 754 deleteOpts.FinalClusterSnapshotIdentifier = aws.String(name.(string)) 755 } else { 756 return fmt.Errorf("Redshift Cluster Instance FinalSnapshotIdentifier is required when a final snapshot is required") 757 } 758 } 759 760 log.Printf("[DEBUG] Redshift Cluster delete options: %s", deleteOpts) 761 _, err := conn.DeleteCluster(&deleteOpts) 762 if err != nil { 763 return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err) 764 } 765 766 stateConf := &resource.StateChangeConf{ 767 Pending: []string{"available", "creating", "deleting", "rebooting", "resizing", "renaming", "final-snapshot"}, 768 Target: []string{"destroyed"}, 769 Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta), 770 Timeout: 40 * time.Minute, 771 MinTimeout: 5 * time.Second, 772 } 773 774 // Wait, catching any errors 775 _, err = stateConf.WaitForState() 776 if err != nil { 777 return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err) 778 } 779 780 log.Printf("[INFO] Redshift Cluster %s successfully deleted", d.Id()) 781 782 return nil 783 } 784 785 func resourceAwsRedshiftClusterStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { 786 return func() (interface{}, string, error) { 787 conn := meta.(*AWSClient).redshiftconn 788 789 log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id()) 790 resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{ 791 ClusterIdentifier: aws.String(d.Id()), 792 }) 793 794 if err != nil { 795 if awsErr, ok := err.(awserr.Error); ok { 796 if "ClusterNotFound" == awsErr.Code() { 797 return 42, "destroyed", nil 798 } 799 } 800 log.Printf("[WARN] Error on retrieving Redshift Cluster (%s) when waiting: %s", d.Id(), err) 801 return nil, "", err 802 } 803 804 var rsc *redshift.Cluster 805 806 for _, c := range resp.Clusters { 807 if *c.ClusterIdentifier == d.Id() { 808 rsc = c 809 } 810 } 811 812 if rsc == nil { 813 return 42, "destroyed", nil 814 } 815 816 if rsc.ClusterStatus != nil { 817 log.Printf("[DEBUG] Redshift Cluster status (%s): %s", d.Id(), *rsc.ClusterStatus) 818 } 819 820 return rsc, *rsc.ClusterStatus, nil 821 } 822 } 823 824 func validateRedshiftClusterIdentifier(v interface{}, k string) (ws []string, errors []error) { 825 value := v.(string) 826 if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) { 827 errors = append(errors, fmt.Errorf( 828 "only lowercase alphanumeric characters and hyphens allowed in %q", k)) 829 } 830 if !regexp.MustCompile(`^[a-z]`).MatchString(value) { 831 errors = append(errors, fmt.Errorf( 832 "first character of %q must be a letter", k)) 833 } 834 if regexp.MustCompile(`--`).MatchString(value) { 835 errors = append(errors, fmt.Errorf( 836 "%q cannot contain two consecutive hyphens", k)) 837 } 838 if regexp.MustCompile(`-$`).MatchString(value) { 839 errors = append(errors, fmt.Errorf( 840 "%q cannot end with a hyphen", k)) 841 } 842 return 843 } 844 845 func validateRedshiftClusterDbName(v interface{}, k string) (ws []string, errors []error) { 846 value := v.(string) 847 if !regexp.MustCompile(`^[0-9A-Za-z_$]+$`).MatchString(value) { 848 errors = append(errors, fmt.Errorf( 849 "only alphanumeric characters, underscores, and dollar signs are allowed in %q", k)) 850 } 851 if !regexp.MustCompile(`^[a-zA-Z_]`).MatchString(value) { 852 errors = append(errors, fmt.Errorf( 853 "first character of %q must be a letter or underscore", k)) 854 } 855 if len(value) > 64 { 856 errors = append(errors, fmt.Errorf( 857 "%q cannot be longer than 64 characters: %q", k, value)) 858 } 859 if value == "" { 860 errors = append(errors, fmt.Errorf( 861 "%q cannot be an empty string", k)) 862 } 863 864 return 865 } 866 867 func validateRedshiftClusterFinalSnapshotIdentifier(v interface{}, k string) (ws []string, errors []error) { 868 value := v.(string) 869 if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { 870 errors = append(errors, fmt.Errorf( 871 "only alphanumeric characters and hyphens allowed in %q", k)) 872 } 873 if regexp.MustCompile(`--`).MatchString(value) { 874 errors = append(errors, fmt.Errorf("%q cannot contain two consecutive hyphens", k)) 875 } 876 if regexp.MustCompile(`-$`).MatchString(value) { 877 errors = append(errors, fmt.Errorf("%q cannot end in a hyphen", k)) 878 } 879 if len(value) > 255 { 880 errors = append(errors, fmt.Errorf("%q cannot be more than 255 characters", k)) 881 } 882 return 883 } 884 885 func validateRedshiftClusterMasterUsername(v interface{}, k string) (ws []string, errors []error) { 886 value := v.(string) 887 if !regexp.MustCompile(`^\w+$`).MatchString(value) { 888 errors = append(errors, fmt.Errorf( 889 "only alphanumeric characters in %q", k)) 890 } 891 if !regexp.MustCompile(`^[A-Za-z]`).MatchString(value) { 892 errors = append(errors, fmt.Errorf( 893 "first character of %q must be a letter", k)) 894 } 895 if len(value) > 128 { 896 errors = append(errors, fmt.Errorf("%q cannot be more than 128 characters", k)) 897 } 898 return 899 } 900 901 func validateRedshiftClusterMasterPassword(v interface{}, k string) (ws []string, errors []error) { 902 value := v.(string) 903 if !regexp.MustCompile(`^.*[a-z].*`).MatchString(value) { 904 errors = append(errors, fmt.Errorf( 905 "%q must contain at least one lowercase letter", k)) 906 } 907 if !regexp.MustCompile(`^.*[A-Z].*`).MatchString(value) { 908 errors = append(errors, fmt.Errorf( 909 "%q must contain at least one uppercase letter", k)) 910 } 911 if !regexp.MustCompile(`^.*[0-9].*`).MatchString(value) { 912 errors = append(errors, fmt.Errorf( 913 "%q must contain at least one number", k)) 914 } 915 if len(value) < 8 { 916 errors = append(errors, fmt.Errorf("%q must be at least 8 characters", k)) 917 } 918 return 919 } 920 921 func buildRedshiftARN(identifier, partition, accountid, region string) (string, error) { 922 if partition == "" { 923 return "", fmt.Errorf("Unable to construct cluster ARN because of missing AWS partition") 924 } 925 if accountid == "" { 926 return "", fmt.Errorf("Unable to construct cluster ARN because of missing AWS Account ID") 927 } 928 arn := fmt.Sprintf("arn:%s:redshift:%s:%s:cluster:%s", partition, region, accountid, identifier) 929 return arn, nil 930 931 }