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