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