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