github.com/ottenhoff/terraform@v0.7.0-rc1.0.20160607213102-ac2d195cc560/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 24 Schema: map[string]*schema.Schema{ 25 "database_name": &schema.Schema{ 26 Type: schema.TypeString, 27 Optional: true, 28 Computed: true, 29 ValidateFunc: validateRedshiftClusterDbName, 30 }, 31 32 "cluster_identifier": &schema.Schema{ 33 Type: schema.TypeString, 34 Required: true, 35 ForceNew: true, 36 ValidateFunc: validateRedshiftClusterIdentifier, 37 }, 38 "cluster_type": &schema.Schema{ 39 Type: schema.TypeString, 40 Optional: true, 41 Computed: true, 42 }, 43 44 "node_type": &schema.Schema{ 45 Type: schema.TypeString, 46 Required: true, 47 }, 48 49 "master_username": &schema.Schema{ 50 Type: schema.TypeString, 51 Required: true, 52 ValidateFunc: validateRedshiftClusterMasterUsername, 53 }, 54 55 "master_password": &schema.Schema{ 56 Type: schema.TypeString, 57 Required: true, 58 }, 59 60 "cluster_security_groups": &schema.Schema{ 61 Type: schema.TypeSet, 62 Optional: true, 63 Computed: true, 64 Elem: &schema.Schema{Type: schema.TypeString}, 65 Set: schema.HashString, 66 }, 67 68 "vpc_security_group_ids": &schema.Schema{ 69 Type: schema.TypeSet, 70 Optional: true, 71 Computed: true, 72 Elem: &schema.Schema{Type: schema.TypeString}, 73 Set: schema.HashString, 74 }, 75 76 "cluster_subnet_group_name": &schema.Schema{ 77 Type: schema.TypeString, 78 Optional: true, 79 ForceNew: true, 80 Computed: true, 81 }, 82 83 "availability_zone": &schema.Schema{ 84 Type: schema.TypeString, 85 Optional: true, 86 Computed: true, 87 }, 88 89 "preferred_maintenance_window": &schema.Schema{ 90 Type: schema.TypeString, 91 Optional: true, 92 Computed: true, 93 StateFunc: func(val interface{}) string { 94 if val == nil { 95 return "" 96 } 97 return strings.ToLower(val.(string)) 98 }, 99 }, 100 101 "cluster_parameter_group_name": &schema.Schema{ 102 Type: schema.TypeString, 103 Optional: true, 104 Computed: true, 105 }, 106 107 "automated_snapshot_retention_period": &schema.Schema{ 108 Type: schema.TypeInt, 109 Optional: true, 110 Default: 1, 111 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 112 value := v.(int) 113 if value > 35 { 114 es = append(es, fmt.Errorf( 115 "backup retention period cannot be more than 35 days")) 116 } 117 return 118 }, 119 }, 120 121 "port": &schema.Schema{ 122 Type: schema.TypeInt, 123 Optional: true, 124 Default: 5439, 125 }, 126 127 "cluster_version": &schema.Schema{ 128 Type: schema.TypeString, 129 Optional: true, 130 Default: "1.0", 131 }, 132 133 "allow_version_upgrade": &schema.Schema{ 134 Type: schema.TypeBool, 135 Optional: true, 136 Default: true, 137 }, 138 139 "number_of_nodes": &schema.Schema{ 140 Type: schema.TypeInt, 141 Optional: true, 142 Default: 1, 143 }, 144 145 "publicly_accessible": &schema.Schema{ 146 Type: schema.TypeBool, 147 Optional: true, 148 Default: true, 149 }, 150 151 "encrypted": &schema.Schema{ 152 Type: schema.TypeBool, 153 Optional: true, 154 Computed: true, 155 }, 156 157 "kms_key_id": &schema.Schema{ 158 Type: schema.TypeString, 159 Optional: true, 160 Computed: true, 161 ForceNew: true, 162 }, 163 164 "elastic_ip": &schema.Schema{ 165 Type: schema.TypeString, 166 Optional: true, 167 }, 168 169 "final_snapshot_identifier": &schema.Schema{ 170 Type: schema.TypeString, 171 Optional: true, 172 ValidateFunc: validateRedshiftClusterFinalSnapshotIdentifier, 173 }, 174 175 "skip_final_snapshot": &schema.Schema{ 176 Type: schema.TypeBool, 177 Optional: true, 178 Default: true, 179 }, 180 181 "endpoint": &schema.Schema{ 182 Type: schema.TypeString, 183 Optional: true, 184 Computed: true, 185 }, 186 187 "cluster_public_key": &schema.Schema{ 188 Type: schema.TypeString, 189 Optional: true, 190 Computed: true, 191 }, 192 193 "cluster_revision_number": &schema.Schema{ 194 Type: schema.TypeString, 195 Optional: true, 196 Computed: true, 197 }, 198 199 "iam_roles": &schema.Schema{ 200 Type: schema.TypeSet, 201 Optional: true, 202 Computed: true, 203 Elem: &schema.Schema{Type: schema.TypeString}, 204 Set: schema.HashString, 205 }, 206 207 "tags": tagsSchema(), 208 }, 209 } 210 } 211 212 func resourceAwsRedshiftClusterCreate(d *schema.ResourceData, meta interface{}) error { 213 conn := meta.(*AWSClient).redshiftconn 214 215 log.Printf("[INFO] Building Redshift Cluster Options") 216 tags := tagsFromMapRedshift(d.Get("tags").(map[string]interface{})) 217 createOpts := &redshift.CreateClusterInput{ 218 ClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)), 219 Port: aws.Int64(int64(d.Get("port").(int))), 220 MasterUserPassword: aws.String(d.Get("master_password").(string)), 221 MasterUsername: aws.String(d.Get("master_username").(string)), 222 ClusterVersion: aws.String(d.Get("cluster_version").(string)), 223 NodeType: aws.String(d.Get("node_type").(string)), 224 DBName: aws.String(d.Get("database_name").(string)), 225 AllowVersionUpgrade: aws.Bool(d.Get("allow_version_upgrade").(bool)), 226 PubliclyAccessible: aws.Bool(d.Get("publicly_accessible").(bool)), 227 AutomatedSnapshotRetentionPeriod: aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))), 228 Tags: tags, 229 } 230 231 if v := d.Get("number_of_nodes").(int); v > 1 { 232 createOpts.ClusterType = aws.String("multi-node") 233 createOpts.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int))) 234 } else { 235 createOpts.ClusterType = aws.String("single-node") 236 } 237 238 if v := d.Get("cluster_security_groups").(*schema.Set); v.Len() > 0 { 239 createOpts.ClusterSecurityGroups = expandStringList(v.List()) 240 } 241 242 if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 { 243 createOpts.VpcSecurityGroupIds = expandStringList(v.List()) 244 } 245 246 if v, ok := d.GetOk("cluster_subnet_group_name"); ok { 247 createOpts.ClusterSubnetGroupName = aws.String(v.(string)) 248 } 249 250 if v, ok := d.GetOk("availability_zone"); ok { 251 createOpts.AvailabilityZone = aws.String(v.(string)) 252 } 253 254 if v, ok := d.GetOk("preferred_maintenance_window"); ok { 255 createOpts.PreferredMaintenanceWindow = aws.String(v.(string)) 256 } 257 258 if v, ok := d.GetOk("cluster_parameter_group_name"); ok { 259 createOpts.ClusterParameterGroupName = aws.String(v.(string)) 260 } 261 262 if v, ok := d.GetOk("encrypted"); ok { 263 createOpts.Encrypted = aws.Bool(v.(bool)) 264 } 265 266 if v, ok := d.GetOk("kms_key_id"); ok { 267 createOpts.KmsKeyId = aws.String(v.(string)) 268 } 269 270 if v, ok := d.GetOk("elastic_ip"); ok { 271 createOpts.ElasticIp = aws.String(v.(string)) 272 } 273 274 if v, ok := d.GetOk("iam_roles"); ok { 275 createOpts.IamRoles = expandStringList(v.(*schema.Set).List()) 276 } 277 278 log.Printf("[DEBUG] Redshift Cluster create options: %s", createOpts) 279 resp, err := conn.CreateCluster(createOpts) 280 if err != nil { 281 log.Printf("[ERROR] Error creating Redshift Cluster: %s", err) 282 return err 283 } 284 285 log.Printf("[DEBUG]: Cluster create response: %s", resp) 286 d.SetId(*resp.Cluster.ClusterIdentifier) 287 288 stateConf := &resource.StateChangeConf{ 289 Pending: []string{"creating", "backing-up", "modifying"}, 290 Target: []string{"available"}, 291 Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta), 292 Timeout: 40 * time.Minute, 293 MinTimeout: 10 * time.Second, 294 } 295 296 _, err = stateConf.WaitForState() 297 if err != nil { 298 return fmt.Errorf("[WARN] Error waiting for Redshift Cluster state to be \"available\": %s", err) 299 } 300 301 return resourceAwsRedshiftClusterRead(d, meta) 302 } 303 304 func resourceAwsRedshiftClusterRead(d *schema.ResourceData, meta interface{}) error { 305 conn := meta.(*AWSClient).redshiftconn 306 307 log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id()) 308 resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{ 309 ClusterIdentifier: aws.String(d.Id()), 310 }) 311 312 if err != nil { 313 if awsErr, ok := err.(awserr.Error); ok { 314 if "ClusterNotFound" == awsErr.Code() { 315 d.SetId("") 316 log.Printf("[DEBUG] Redshift Cluster (%s) not found", d.Id()) 317 return nil 318 } 319 } 320 log.Printf("[DEBUG] Error describing Redshift Cluster (%s)", d.Id()) 321 return err 322 } 323 324 var rsc *redshift.Cluster 325 for _, c := range resp.Clusters { 326 if *c.ClusterIdentifier == d.Id() { 327 rsc = c 328 } 329 } 330 331 if rsc == nil { 332 log.Printf("[WARN] Redshift Cluster (%s) not found", d.Id()) 333 d.SetId("") 334 return nil 335 } 336 337 d.Set("database_name", rsc.DBName) 338 d.Set("cluster_subnet_group_name", rsc.ClusterSubnetGroupName) 339 d.Set("availability_zone", rsc.AvailabilityZone) 340 d.Set("encrypted", rsc.Encrypted) 341 d.Set("kms_key_id", rsc.KmsKeyId) 342 d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod) 343 d.Set("preferred_maintenance_window", rsc.PreferredMaintenanceWindow) 344 if rsc.Endpoint != nil && rsc.Endpoint.Address != nil { 345 endpoint := *rsc.Endpoint.Address 346 if rsc.Endpoint.Port != nil { 347 endpoint = fmt.Sprintf("%s:%d", endpoint, *rsc.Endpoint.Port) 348 } 349 d.Set("endpoint", endpoint) 350 } 351 d.Set("cluster_parameter_group_name", rsc.ClusterParameterGroups[0].ParameterGroupName) 352 if len(rsc.ClusterNodes) > 1 { 353 d.Set("cluster_type", "multi-node") 354 } else { 355 d.Set("cluster_type", "single-node") 356 } 357 358 var vpcg []string 359 for _, g := range rsc.VpcSecurityGroups { 360 vpcg = append(vpcg, *g.VpcSecurityGroupId) 361 } 362 if err := d.Set("vpc_security_group_ids", vpcg); err != nil { 363 return fmt.Errorf("[DEBUG] Error saving VPC Security Group IDs to state for Redshift Cluster (%s): %s", d.Id(), err) 364 } 365 366 var csg []string 367 for _, g := range rsc.ClusterSecurityGroups { 368 csg = append(csg, *g.ClusterSecurityGroupName) 369 } 370 if err := d.Set("cluster_security_groups", csg); err != nil { 371 return fmt.Errorf("[DEBUG] Error saving Cluster Security Group Names to state for Redshift Cluster (%s): %s", d.Id(), err) 372 } 373 374 var iamRoles []string 375 for _, i := range rsc.IamRoles { 376 iamRoles = append(iamRoles, *i.IamRoleArn) 377 } 378 if err := d.Set("iam_roles", iamRoles); err != nil { 379 return fmt.Errorf("[DEBUG] Error saving IAM Roles to state for Redshift Cluster (%s): %s", d.Id(), err) 380 } 381 382 d.Set("cluster_public_key", rsc.ClusterPublicKey) 383 d.Set("cluster_revision_number", rsc.ClusterRevisionNumber) 384 d.Set("tags", tagsToMapRedshift(rsc.Tags)) 385 386 return nil 387 } 388 389 func resourceAwsRedshiftClusterUpdate(d *schema.ResourceData, meta interface{}) error { 390 conn := meta.(*AWSClient).redshiftconn 391 d.Partial(true) 392 393 arn, tagErr := buildRedshiftARN(d.Id(), meta.(*AWSClient).accountid, meta.(*AWSClient).region) 394 if tagErr != nil { 395 return fmt.Errorf("Error building ARN for Redshift Cluster, not updating Tags for cluster %s", d.Id()) 396 } else { 397 if tagErr := setTagsRedshift(conn, d, arn); tagErr != nil { 398 return tagErr 399 } else { 400 d.SetPartial("tags") 401 } 402 } 403 404 requestUpdate := false 405 log.Printf("[INFO] Building Redshift Modify Cluster Options") 406 req := &redshift.ModifyClusterInput{ 407 ClusterIdentifier: aws.String(d.Id()), 408 } 409 410 if d.HasChange("cluster_type") { 411 req.ClusterType = aws.String(d.Get("cluster_type").(string)) 412 requestUpdate = true 413 } 414 415 if d.HasChange("node_type") { 416 req.NodeType = aws.String(d.Get("node_type").(string)) 417 requestUpdate = true 418 } 419 420 if d.HasChange("number_of_nodes") { 421 if v := d.Get("number_of_nodes").(int); v > 1 { 422 req.ClusterType = aws.String("multi-node") 423 req.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int))) 424 } else { 425 req.ClusterType = aws.String("single-node") 426 } 427 428 req.NodeType = aws.String(d.Get("node_type").(string)) 429 requestUpdate = true 430 } 431 432 if d.HasChange("cluster_security_groups") { 433 req.ClusterSecurityGroups = expandStringList(d.Get("cluster_security_groups").(*schema.Set).List()) 434 requestUpdate = true 435 } 436 437 if d.HasChange("vpc_security_group_ips") { 438 req.VpcSecurityGroupIds = expandStringList(d.Get("vpc_security_group_ips").(*schema.Set).List()) 439 requestUpdate = true 440 } 441 442 if d.HasChange("master_password") { 443 req.MasterUserPassword = aws.String(d.Get("master_password").(string)) 444 requestUpdate = true 445 } 446 447 if d.HasChange("cluster_parameter_group_name") { 448 req.ClusterParameterGroupName = aws.String(d.Get("cluster_parameter_group_name").(string)) 449 requestUpdate = true 450 } 451 452 if d.HasChange("automated_snapshot_retention_period") { 453 req.AutomatedSnapshotRetentionPeriod = aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int))) 454 requestUpdate = true 455 } 456 457 if d.HasChange("preferred_maintenance_window") { 458 req.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintenance_window").(string)) 459 requestUpdate = true 460 } 461 462 if d.HasChange("cluster_version") { 463 req.ClusterVersion = aws.String(d.Get("cluster_version").(string)) 464 requestUpdate = true 465 } 466 467 if d.HasChange("allow_version_upgrade") { 468 req.AllowVersionUpgrade = aws.Bool(d.Get("allow_version_upgrade").(bool)) 469 requestUpdate = true 470 } 471 472 if d.HasChange("publicly_accessible") { 473 req.PubliclyAccessible = aws.Bool(d.Get("publicly_accessible").(bool)) 474 requestUpdate = true 475 } 476 477 if requestUpdate { 478 log.Printf("[INFO] Modifying Redshift Cluster: %s", d.Id()) 479 log.Printf("[DEBUG] Redshift Cluster Modify options: %s", req) 480 _, err := conn.ModifyCluster(req) 481 if err != nil { 482 return fmt.Errorf("[WARN] Error modifying Redshift Cluster (%s): %s", d.Id(), err) 483 } 484 } 485 486 if d.HasChange("iam_roles") { 487 o, n := d.GetChange("iam_roles") 488 if o == nil { 489 o = new(schema.Set) 490 } 491 if n == nil { 492 n = new(schema.Set) 493 } 494 495 os := o.(*schema.Set) 496 ns := n.(*schema.Set) 497 498 removeIams := os.Difference(ns).List() 499 addIams := ns.Difference(os).List() 500 501 log.Printf("[INFO] Building Redshift Modify Cluster IAM Role Options") 502 req := &redshift.ModifyClusterIamRolesInput{ 503 ClusterIdentifier: aws.String(d.Id()), 504 AddIamRoles: expandStringList(addIams), 505 RemoveIamRoles: expandStringList(removeIams), 506 } 507 508 log.Printf("[INFO] Modifying Redshift Cluster IAM Roles: %s", d.Id()) 509 log.Printf("[DEBUG] Redshift Cluster Modify IAM Role options: %s", req) 510 _, err := conn.ModifyClusterIamRoles(req) 511 if err != nil { 512 return fmt.Errorf("[WARN] Error modifying Redshift Cluster IAM Roles (%s): %s", d.Id(), err) 513 } 514 515 d.SetPartial("iam_roles") 516 } 517 518 if requestUpdate || d.HasChange("iam_roles") { 519 520 stateConf := &resource.StateChangeConf{ 521 Pending: []string{"creating", "deleting", "rebooting", "resizing", "renaming", "modifying"}, 522 Target: []string{"available"}, 523 Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta), 524 Timeout: 40 * time.Minute, 525 MinTimeout: 10 * time.Second, 526 } 527 528 // Wait, catching any errors 529 _, err := stateConf.WaitForState() 530 if err != nil { 531 return fmt.Errorf("[WARN] Error Modifying Redshift Cluster (%s): %s", d.Id(), err) 532 } 533 } 534 535 d.Partial(false) 536 537 return resourceAwsRedshiftClusterRead(d, meta) 538 } 539 540 func resourceAwsRedshiftClusterDelete(d *schema.ResourceData, meta interface{}) error { 541 conn := meta.(*AWSClient).redshiftconn 542 log.Printf("[DEBUG] Destroying Redshift Cluster (%s)", d.Id()) 543 544 deleteOpts := redshift.DeleteClusterInput{ 545 ClusterIdentifier: aws.String(d.Id()), 546 } 547 548 skipFinalSnapshot := d.Get("skip_final_snapshot").(bool) 549 deleteOpts.SkipFinalClusterSnapshot = aws.Bool(skipFinalSnapshot) 550 551 if !skipFinalSnapshot { 552 if name, present := d.GetOk("final_snapshot_identifier"); present { 553 deleteOpts.FinalClusterSnapshotIdentifier = aws.String(name.(string)) 554 } else { 555 return fmt.Errorf("Redshift Cluster Instance FinalSnapshotIdentifier is required when a final snapshot is required") 556 } 557 } 558 559 log.Printf("[DEBUG] Redshift Cluster delete options: %s", deleteOpts) 560 _, err := conn.DeleteCluster(&deleteOpts) 561 if err != nil { 562 return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err) 563 } 564 565 stateConf := &resource.StateChangeConf{ 566 Pending: []string{"available", "creating", "deleting", "rebooting", "resizing", "renaming"}, 567 Target: []string{"destroyed"}, 568 Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta), 569 Timeout: 40 * time.Minute, 570 MinTimeout: 5 * time.Second, 571 } 572 573 // Wait, catching any errors 574 _, err = stateConf.WaitForState() 575 if err != nil { 576 return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err) 577 } 578 579 log.Printf("[INFO] Redshift Cluster %s successfully deleted", d.Id()) 580 581 return nil 582 } 583 584 func resourceAwsRedshiftClusterStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { 585 return func() (interface{}, string, error) { 586 conn := meta.(*AWSClient).redshiftconn 587 588 log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id()) 589 resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{ 590 ClusterIdentifier: aws.String(d.Id()), 591 }) 592 593 if err != nil { 594 if awsErr, ok := err.(awserr.Error); ok { 595 if "ClusterNotFound" == awsErr.Code() { 596 return 42, "destroyed", nil 597 } 598 } 599 log.Printf("[WARN] Error on retrieving Redshift Cluster (%s) when waiting: %s", d.Id(), err) 600 return nil, "", err 601 } 602 603 var rsc *redshift.Cluster 604 605 for _, c := range resp.Clusters { 606 if *c.ClusterIdentifier == d.Id() { 607 rsc = c 608 } 609 } 610 611 if rsc == nil { 612 return 42, "destroyed", nil 613 } 614 615 if rsc.ClusterStatus != nil { 616 log.Printf("[DEBUG] Redshift Cluster status (%s): %s", d.Id(), *rsc.ClusterStatus) 617 } 618 619 return rsc, *rsc.ClusterStatus, nil 620 } 621 } 622 623 func validateRedshiftClusterIdentifier(v interface{}, k string) (ws []string, errors []error) { 624 value := v.(string) 625 if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) { 626 errors = append(errors, fmt.Errorf( 627 "only lowercase alphanumeric characters and hyphens allowed in %q", k)) 628 } 629 if !regexp.MustCompile(`^[a-z]`).MatchString(value) { 630 errors = append(errors, fmt.Errorf( 631 "first character of %q must be a letter", k)) 632 } 633 if regexp.MustCompile(`--`).MatchString(value) { 634 errors = append(errors, fmt.Errorf( 635 "%q cannot contain two consecutive hyphens", k)) 636 } 637 if regexp.MustCompile(`-$`).MatchString(value) { 638 errors = append(errors, fmt.Errorf( 639 "%q cannot end with a hyphen", k)) 640 } 641 return 642 } 643 644 func validateRedshiftClusterDbName(v interface{}, k string) (ws []string, errors []error) { 645 value := v.(string) 646 if !regexp.MustCompile(`^[a-z]+$`).MatchString(value) { 647 errors = append(errors, fmt.Errorf( 648 "only lowercase letters characters allowed in %q", k)) 649 } 650 if len(value) > 64 { 651 errors = append(errors, fmt.Errorf( 652 "%q cannot be longer than 64 characters: %q", k, value)) 653 } 654 if value == "" { 655 errors = append(errors, fmt.Errorf( 656 "%q cannot be an empty string", k)) 657 } 658 659 return 660 } 661 662 func validateRedshiftClusterFinalSnapshotIdentifier(v interface{}, k string) (ws []string, errors []error) { 663 value := v.(string) 664 if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { 665 errors = append(errors, fmt.Errorf( 666 "only alphanumeric characters and hyphens allowed in %q", k)) 667 } 668 if regexp.MustCompile(`--`).MatchString(value) { 669 errors = append(errors, fmt.Errorf("%q cannot contain two consecutive hyphens", k)) 670 } 671 if regexp.MustCompile(`-$`).MatchString(value) { 672 errors = append(errors, fmt.Errorf("%q cannot end in a hyphen", k)) 673 } 674 if len(value) > 255 { 675 errors = append(errors, fmt.Errorf("%q cannot be more than 255 characters", k)) 676 } 677 return 678 } 679 680 func validateRedshiftClusterMasterUsername(v interface{}, k string) (ws []string, errors []error) { 681 value := v.(string) 682 if !regexp.MustCompile(`^\w+$`).MatchString(value) { 683 errors = append(errors, fmt.Errorf( 684 "only alphanumeric characters in %q", k)) 685 } 686 if !regexp.MustCompile(`^[A-Za-z]`).MatchString(value) { 687 errors = append(errors, fmt.Errorf( 688 "first character of %q must be a letter", k)) 689 } 690 if len(value) > 128 { 691 errors = append(errors, fmt.Errorf("%q cannot be more than 128 characters", k)) 692 } 693 return 694 } 695 696 func buildRedshiftARN(identifier, accountid, region string) (string, error) { 697 if accountid == "" { 698 return "", fmt.Errorf("Unable to construct cluster ARN because of missing AWS Account ID") 699 } 700 arn := fmt.Sprintf("arn:aws:redshift:%s:%s:cluster:%s", region, accountid, identifier) 701 return arn, nil 702 703 }