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