Merge pull request #3862 from stack72/aws-redshift

provider/aws: AWS Redshift
This commit is contained in:
Clint 2016-01-13 16:52:47 -06:00
commit 7f6624e926
18 changed files with 2561 additions and 2 deletions

View File

@ -40,6 +40,7 @@ import (
"github.com/aws/aws-sdk-go/service/lambda"
"github.com/aws/aws-sdk-go/service/opsworks"
"github.com/aws/aws-sdk-go/service/rds"
"github.com/aws/aws-sdk-go/service/redshift"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/sns"
@ -77,6 +78,7 @@ type AWSClient struct {
s3conn *s3.S3
sqsconn *sqs.SQS
snsconn *sns.SNS
redshiftconn *redshift.Redshift
r53conn *route53.Route53
region string
rdsconn *rds.RDS
@ -238,6 +240,10 @@ func (c *Config) Client() (interface{}, error) {
log.Println("[INFO] Initializing CodeCommit SDK connection")
client.codecommitconn = codecommit.New(usEast1Sess)
log.Println("[INFO] Initializing Redshift SDK connection")
client.redshiftconn = redshift.New(sess)
}
if len(errs) > 0 {

View File

@ -173,6 +173,10 @@ func Provider() terraform.ResourceProvider {
"aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(),
"aws_rds_cluster": resourceAwsRDSCluster(),
"aws_rds_cluster_instance": resourceAwsRDSClusterInstance(),
"aws_redshift_cluster": resourceAwsRedshiftCluster(),
"aws_redshift_security_group": resourceAwsRedshiftSecurityGroup(),
"aws_redshift_parameter_group": resourceAwsRedshiftParameterGroup(),
"aws_redshift_subnet_group": resourceAwsRedshiftSubnetGroup(),
"aws_route53_delegation_set": resourceAwsRoute53DelegationSet(),
"aws_route53_record": resourceAwsRoute53Record(),
"aws_route53_zone_association": resourceAwsRoute53ZoneAssociation(),

View File

@ -0,0 +1,574 @@
package aws
import (
"fmt"
"log"
"regexp"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/redshift"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsRedshiftCluster() *schema.Resource {
return &schema.Resource{
Create: resourceAwsRedshiftClusterCreate,
Read: resourceAwsRedshiftClusterRead,
Update: resourceAwsRedshiftClusterUpdate,
Delete: resourceAwsRedshiftClusterDelete,
Schema: map[string]*schema.Schema{
"database_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validateRedshiftClusterDbName,
},
"cluster_identifier": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateRedshiftClusterIdentifier,
},
"cluster_type": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"node_type": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"master_username": &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateRedshiftClusterMasterUsername,
},
"master_password": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"cluster_security_groups": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"vpc_security_group_ids": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"cluster_subnet_group_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
"availability_zone": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"preferred_maintenance_window": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
StateFunc: func(val interface{}) string {
if val == nil {
return ""
}
return strings.ToLower(val.(string))
},
},
"cluster_parameter_group_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"automated_snapshot_retention_period": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Default: 1,
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
value := v.(int)
if value > 35 {
es = append(es, fmt.Errorf(
"backup retention period cannot be more than 35 days"))
}
return
},
},
"port": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Default: 5439,
},
"cluster_version": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "1.0",
},
"allow_version_upgrade": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"number_of_nodes": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Default: 1,
},
"publicly_accessible": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
},
"encrypted": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"elastic_ip": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"final_snapshot_identifier": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRedshiftClusterFinalSnapshotIdentifier,
},
"skip_final_snapshot": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"endpoint": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"cluster_public_key": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"cluster_revision_number": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
}
func resourceAwsRedshiftClusterCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).redshiftconn
log.Printf("[INFO] Building Redshift Cluster Options")
createOpts := &redshift.CreateClusterInput{
ClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)),
Port: aws.Int64(int64(d.Get("port").(int))),
MasterUserPassword: aws.String(d.Get("master_password").(string)),
MasterUsername: aws.String(d.Get("master_username").(string)),
ClusterType: aws.String(d.Get("cluster_type").(string)),
ClusterVersion: aws.String(d.Get("cluster_version").(string)),
NodeType: aws.String(d.Get("node_type").(string)),
DBName: aws.String(d.Get("database_name").(string)),
AllowVersionUpgrade: aws.Bool(d.Get("allow_version_upgrade").(bool)),
}
if d.Get("cluster_type") == "multi-node" {
createOpts.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int)))
}
if v := d.Get("cluster_security_groups").(*schema.Set); v.Len() > 0 {
createOpts.ClusterSecurityGroups = expandStringList(v.List())
}
if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 {
createOpts.VpcSecurityGroupIds = expandStringList(v.List())
}
if v, ok := d.GetOk("cluster_subnet_group_name"); ok {
createOpts.ClusterSubnetGroupName = aws.String(v.(string))
}
if v, ok := d.GetOk("availability_zone"); ok {
createOpts.AvailabilityZone = aws.String(v.(string))
}
if v, ok := d.GetOk("preferred_maintenance_window"); ok {
createOpts.PreferredMaintenanceWindow = aws.String(v.(string))
}
if v, ok := d.GetOk("cluster_parameter_group_name"); ok {
createOpts.ClusterParameterGroupName = aws.String(v.(string))
}
if v, ok := d.GetOk("automated_snapshot_retention_period"); ok {
createOpts.AutomatedSnapshotRetentionPeriod = aws.Int64(int64(v.(int)))
}
if v, ok := d.GetOk("publicly_accessible"); ok {
createOpts.PubliclyAccessible = aws.Bool(v.(bool))
}
if v, ok := d.GetOk("encrypted"); ok {
createOpts.Encrypted = aws.Bool(v.(bool))
}
if v, ok := d.GetOk("elastic_ip"); ok {
createOpts.ElasticIp = aws.String(v.(string))
}
log.Printf("[DEBUG] Redshift Cluster create options: %s", createOpts)
resp, err := conn.CreateCluster(createOpts)
if err != nil {
log.Printf("[ERROR] Error creating Redshift Cluster: %s", err)
return err
}
log.Printf("[DEBUG]: Cluster create response: %s", resp)
d.SetId(*resp.Cluster.ClusterIdentifier)
stateConf := &resource.StateChangeConf{
Pending: []string{"creating", "backing-up", "modifying"},
Target: "available",
Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta),
Timeout: 5 * time.Minute,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("[WARN] Error waiting for Redshift Cluster state to be \"available\": %s", err)
}
return resourceAwsRedshiftClusterRead(d, meta)
}
func resourceAwsRedshiftClusterRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).redshiftconn
log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id())
resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{
ClusterIdentifier: aws.String(d.Id()),
})
if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
if "ClusterNotFound" == awsErr.Code() {
d.SetId("")
log.Printf("[DEBUG] Redshift Cluster (%s) not found", d.Id())
return nil
}
}
log.Printf("[DEBUG] Error describing Redshift Cluster (%s)", d.Id())
return err
}
var rsc *redshift.Cluster
for _, c := range resp.Clusters {
if *c.ClusterIdentifier == d.Id() {
rsc = c
}
}
if rsc == nil {
log.Printf("[WARN] Redshift Cluster (%s) not found", d.Id())
d.SetId("")
return nil
}
d.Set("database_name", rsc.DBName)
d.Set("cluster_subnet_group_name", rsc.ClusterSubnetGroupName)
d.Set("availability_zone", rsc.AvailabilityZone)
d.Set("encrypted", rsc.Encrypted)
d.Set("automated_snapshot_retention_period", rsc.AutomatedSnapshotRetentionPeriod)
d.Set("preferred_maintenance_window", rsc.PreferredMaintenanceWindow)
d.Set("endpoint", aws.String(fmt.Sprintf("%s:%d", *rsc.Endpoint.Address, *rsc.Endpoint.Port)))
d.Set("cluster_parameter_group_name", rsc.ClusterParameterGroups[0].ParameterGroupName)
var vpcg []string
for _, g := range rsc.VpcSecurityGroups {
vpcg = append(vpcg, *g.VpcSecurityGroupId)
}
if err := d.Set("vpc_security_group_ids", vpcg); err != nil {
return fmt.Errorf("[DEBUG] Error saving VPC Security Group IDs to state for Redshift Cluster (%s): %s", d.Id(), err)
}
var csg []string
for _, g := range rsc.ClusterSecurityGroups {
csg = append(csg, *g.ClusterSecurityGroupName)
}
if err := d.Set("cluster_security_groups", csg); err != nil {
return fmt.Errorf("[DEBUG] Error saving Cluster Security Group Names to state for Redshift Cluster (%s): %s", d.Id(), err)
}
d.Set("cluster_public_key", rsc.ClusterPublicKey)
d.Set("cluster_revision_number", rsc.ClusterRevisionNumber)
return nil
}
func resourceAwsRedshiftClusterUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).redshiftconn
log.Printf("[INFO] Building Redshift Modify Cluster Options")
req := &redshift.ModifyClusterInput{
ClusterIdentifier: aws.String(d.Id()),
}
if d.HasChange("cluster_type") {
req.ClusterType = aws.String(d.Get("cluster_type").(string))
}
if d.HasChange("node_type") {
req.NodeType = aws.String(d.Get("node_type").(string))
}
if d.HasChange("number_of_nodes") {
log.Printf("[INFO] When changing the NumberOfNodes in a Redshift Cluster, NodeType is required")
req.NumberOfNodes = aws.Int64(int64(d.Get("number_of_nodes").(int)))
req.NodeType = aws.String(d.Get("node_type").(string))
}
if d.HasChange("cluster_security_groups") {
req.ClusterSecurityGroups = expandStringList(d.Get("cluster_security_groups").(*schema.Set).List())
}
if d.HasChange("vpc_security_group_ips") {
req.VpcSecurityGroupIds = expandStringList(d.Get("vpc_security_group_ips").(*schema.Set).List())
}
if d.HasChange("master_password") {
req.MasterUserPassword = aws.String(d.Get("master_password").(string))
}
if d.HasChange("cluster_parameter_group_name") {
req.ClusterParameterGroupName = aws.String(d.Get("cluster_parameter_group_name").(string))
}
if d.HasChange("automated_snapshot_retention_period") {
req.AutomatedSnapshotRetentionPeriod = aws.Int64(int64(d.Get("automated_snapshot_retention_period").(int)))
}
if d.HasChange("preferred_maintenance_window") {
req.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintenance_window").(string))
}
if d.HasChange("cluster_version") {
req.ClusterVersion = aws.String(d.Get("cluster_version").(string))
}
if d.HasChange("allow_version_upgrade") {
req.AllowVersionUpgrade = aws.Bool(d.Get("allow_version_upgrade").(bool))
}
log.Printf("[INFO] Modifying Redshift Cluster: %s", d.Id())
log.Printf("[DEBUG] Redshift Cluster Modify options: %s", req)
_, err := conn.ModifyCluster(req)
if err != nil {
return fmt.Errorf("[WARN] Error modifying Redshift Cluster (%s): %s", d.Id(), err)
}
stateConf := &resource.StateChangeConf{
Pending: []string{"creating", "deleting", "rebooting", "resizing", "renaming"},
Target: "available",
Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta),
Timeout: 10 * time.Minute,
MinTimeout: 5 * time.Second,
}
// Wait, catching any errors
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("[WARN] Error Modifying Redshift Cluster (%s): %s", d.Id(), err)
}
return resourceAwsRedshiftClusterRead(d, meta)
}
func resourceAwsRedshiftClusterDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).redshiftconn
log.Printf("[DEBUG] Destroying Redshift Cluster (%s)", d.Id())
deleteOpts := redshift.DeleteClusterInput{
ClusterIdentifier: aws.String(d.Id()),
}
skipFinalSnapshot := d.Get("skip_final_snapshot").(bool)
deleteOpts.SkipFinalClusterSnapshot = aws.Bool(skipFinalSnapshot)
if !skipFinalSnapshot {
if name, present := d.GetOk("final_snapshot_identifier"); present {
deleteOpts.FinalClusterSnapshotIdentifier = aws.String(name.(string))
} else {
return fmt.Errorf("Redshift Cluster Instance FinalSnapshotIdentifier is required when a final snapshot is required")
}
}
log.Printf("[DEBUG] Redshift Cluster delete options: %s", deleteOpts)
_, err := conn.DeleteCluster(&deleteOpts)
if err != nil {
return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err)
}
stateConf := &resource.StateChangeConf{
Pending: []string{"available", "creating", "deleting", "rebooting", "resizing", "renaming"},
Target: "destroyed",
Refresh: resourceAwsRedshiftClusterStateRefreshFunc(d, meta),
Timeout: 40 * time.Minute,
MinTimeout: 5 * time.Second,
}
// Wait, catching any errors
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("[ERROR] Error deleting Redshift Cluster (%s): %s", d.Id(), err)
}
log.Printf("[INFO] Redshift Cluster %s successfully deleted", d.Id())
return nil
}
func resourceAwsRedshiftClusterStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
conn := meta.(*AWSClient).redshiftconn
log.Printf("[INFO] Reading Redshift Cluster Information: %s", d.Id())
resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{
ClusterIdentifier: aws.String(d.Id()),
})
if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
if "ClusterNotFound" == awsErr.Code() {
return 42, "destroyed", nil
}
}
log.Printf("[WARN] Error on retrieving Redshift Cluster (%s) when waiting: %s", d.Id(), err)
return nil, "", err
}
var rsc *redshift.Cluster
for _, c := range resp.Clusters {
if *c.ClusterIdentifier == d.Id() {
rsc = c
}
}
if rsc == nil {
return 42, "destroyed", nil
}
if rsc.ClusterStatus != nil {
log.Printf("[DEBUG] Redshift Cluster status (%s): %s", d.Id(), *rsc.ClusterStatus)
}
return rsc, *rsc.ClusterStatus, nil
}
}
func validateRedshiftClusterIdentifier(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"only lowercase alphanumeric characters and hyphens allowed in %q", k))
}
if !regexp.MustCompile(`^[a-z]`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"first character of %q must be a letter", k))
}
if regexp.MustCompile(`--`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q cannot contain two consecutive hyphens", k))
}
if regexp.MustCompile(`-$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q cannot end with a hyphen", k))
}
return
}
func validateRedshiftClusterDbName(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^[a-z]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"only lowercase letters characters allowed in %q", k))
}
if len(value) > 64 {
errors = append(errors, fmt.Errorf(
"%q cannot be longer than 64 characters: %q", k, value))
}
if value == "" {
errors = append(errors, fmt.Errorf(
"%q cannot be an empty string", k))
}
return
}
func validateRedshiftClusterFinalSnapshotIdentifier(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"only alphanumeric characters and hyphens allowed in %q", k))
}
if regexp.MustCompile(`--`).MatchString(value) {
errors = append(errors, fmt.Errorf("%q cannot contain two consecutive hyphens", k))
}
if regexp.MustCompile(`-$`).MatchString(value) {
errors = append(errors, fmt.Errorf("%q cannot end in a hyphen", k))
}
if len(value) > 255 {
errors = append(errors, fmt.Errorf("%q cannot be more than 255 characters", k))
}
return
}
func validateRedshiftClusterMasterUsername(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^[A-Za-z0-9]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"only alphanumeric characters in %q", k))
}
if !regexp.MustCompile(`^[A-Za-z]`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"first character of %q must be a letter", k))
}
if len(value) > 128 {
errors = append(errors, fmt.Errorf("%q cannot be more than 128 characters", k))
}
return
}

View File

@ -0,0 +1,249 @@
package aws
import (
"fmt"
"math/rand"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/redshift"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSRedshiftCluster_basic(t *testing.T) {
var v redshift.Cluster
ri := rand.New(rand.NewSource(time.Now().UnixNano())).Int()
config := fmt.Sprintf(testAccAWSRedshiftClusterConfig_basic, ri)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSRedshiftClusterDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: config,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSRedshiftClusterExists("aws_redshift_cluster.default", &v),
),
},
},
})
}
func testAccCheckAWSRedshiftClusterDestroy(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_redshift_cluster" {
continue
}
// Try to find the Group
conn := testAccProvider.Meta().(*AWSClient).redshiftconn
var err error
resp, err := conn.DescribeClusters(
&redshift.DescribeClustersInput{
ClusterIdentifier: aws.String(rs.Primary.ID),
})
if err == nil {
if len(resp.Clusters) != 0 &&
*resp.Clusters[0].ClusterIdentifier == rs.Primary.ID {
return fmt.Errorf("Redshift Cluster %s still exists", rs.Primary.ID)
}
}
// Return nil if the cluster is already destroyed
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "ClusterNotFound" {
return nil
}
}
return err
}
return nil
}
func testAccCheckAWSRedshiftClusterExists(n string, v *redshift.Cluster) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No Redshift Cluster Instance ID is set")
}
conn := testAccProvider.Meta().(*AWSClient).redshiftconn
resp, err := conn.DescribeClusters(&redshift.DescribeClustersInput{
ClusterIdentifier: aws.String(rs.Primary.ID),
})
if err != nil {
return err
}
for _, c := range resp.Clusters {
if *c.ClusterIdentifier == rs.Primary.ID {
*v = *c
return nil
}
}
return fmt.Errorf("Redshift Cluster (%s) not found", rs.Primary.ID)
}
}
func TestResourceAWSRedshiftClusterIdentifierValidation(t *testing.T) {
cases := []struct {
Value string
ErrCount int
}{
{
Value: "tEsting",
ErrCount: 1,
},
{
Value: "1testing",
ErrCount: 1,
},
{
Value: "testing--123",
ErrCount: 1,
},
{
Value: "testing!",
ErrCount: 1,
},
{
Value: "testing-",
ErrCount: 1,
},
}
for _, tc := range cases {
_, errors := validateRedshiftClusterIdentifier(tc.Value, "aws_redshift_cluster_identifier")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected the Redshift Cluster cluster_identifier to trigger a validation error")
}
}
}
func TestResourceAWSRedshiftClusterDbNameValidation(t *testing.T) {
cases := []struct {
Value string
ErrCount int
}{
{
Value: "tEsting",
ErrCount: 1,
},
{
Value: "testing1",
ErrCount: 1,
},
{
Value: "testing-",
ErrCount: 1,
},
{
Value: "",
ErrCount: 2,
},
{
Value: randomString(65),
ErrCount: 1,
},
}
for _, tc := range cases {
_, errors := validateRedshiftClusterDbName(tc.Value, "aws_redshift_cluster_database_name")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected the Redshift Cluster database_name to trigger a validation error")
}
}
}
func TestResourceAWSRedshiftClusterFinalSnapshotIdentifierValidation(t *testing.T) {
cases := []struct {
Value string
ErrCount int
}{
{
Value: "testing--123",
ErrCount: 1,
},
{
Value: "testing-",
ErrCount: 1,
},
{
Value: "Testingq123!",
ErrCount: 1,
},
{
Value: randomString(256),
ErrCount: 1,
},
}
for _, tc := range cases {
_, errors := validateRedshiftClusterFinalSnapshotIdentifier(tc.Value, "aws_redshift_cluster_final_snapshot_identifier")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected the Redshift Cluster final_snapshot_identifier to trigger a validation error")
}
}
}
func TestResourceAWSRedshiftClusterMasterUsernameValidation(t *testing.T) {
cases := []struct {
Value string
ErrCount int
}{
{
Value: "1Testing",
ErrCount: 1,
},
{
Value: "Testing!!",
ErrCount: 1,
},
{
Value: randomString(129),
ErrCount: 1,
},
}
for _, tc := range cases {
_, errors := validateRedshiftClusterMasterUsername(tc.Value, "aws_redshift_cluster_master_username")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected the Redshift Cluster master_username to trigger a validation error")
}
}
}
var testAccAWSRedshiftClusterConfig_basic = `
provider "aws" {
region = "us-west-2"
}
resource "aws_redshift_cluster" "default" {
cluster_identifier = "tf-redshift-cluster-%d"
availability_zone = "us-west-2a"
database_name = "mydb"
master_username = "foo"
master_password = "Mustbe8characters"
node_type = "dc1.large"
cluster_type = "single-node"
automated_snapshot_retention_period = 7
allow_version_upgrade = false
}`

View File

@ -0,0 +1,238 @@
package aws
import (
"bytes"
"fmt"
"log"
"regexp"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/redshift"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsRedshiftParameterGroup() *schema.Resource {
return &schema.Resource{
Create: resourceAwsRedshiftParameterGroupCreate,
Read: resourceAwsRedshiftParameterGroupRead,
Update: resourceAwsRedshiftParameterGroupUpdate,
Delete: resourceAwsRedshiftParameterGroupDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
ForceNew: true,
Required: true,
ValidateFunc: validateRedshiftParamGroupName,
},
"family": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"description": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"parameter": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
ForceNew: false,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"value": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
},
Set: resourceAwsRedshiftParameterHash,
},
},
}
}
func resourceAwsRedshiftParameterGroupCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).redshiftconn
createOpts := redshift.CreateClusterParameterGroupInput{
ParameterGroupName: aws.String(d.Get("name").(string)),
ParameterGroupFamily: aws.String(d.Get("family").(string)),
Description: aws.String(d.Get("description").(string)),
}
log.Printf("[DEBUG] Create Redshift Parameter Group: %#v", createOpts)
_, err := conn.CreateClusterParameterGroup(&createOpts)
if err != nil {
return fmt.Errorf("Error creating Redshift Parameter Group: %s", err)
}
d.SetId(*createOpts.ParameterGroupName)
log.Printf("[INFO] Redshift Parameter Group ID: %s", d.Id())
return resourceAwsRedshiftParameterGroupUpdate(d, meta)
}
func resourceAwsRedshiftParameterGroupRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).redshiftconn
describeOpts := redshift.DescribeClusterParameterGroupsInput{
ParameterGroupName: aws.String(d.Id()),
}
describeResp, err := conn.DescribeClusterParameterGroups(&describeOpts)
if err != nil {
return err
}
if len(describeResp.ParameterGroups) != 1 ||
*describeResp.ParameterGroups[0].ParameterGroupName != d.Id() {
d.SetId("")
return fmt.Errorf("Unable to find Parameter Group: %#v", describeResp.ParameterGroups)
}
d.Set("name", describeResp.ParameterGroups[0].ParameterGroupName)
d.Set("family", describeResp.ParameterGroups[0].ParameterGroupFamily)
d.Set("description", describeResp.ParameterGroups[0].Description)
describeParametersOpts := redshift.DescribeClusterParametersInput{
ParameterGroupName: aws.String(d.Id()),
Source: aws.String("user"),
}
describeParametersResp, err := conn.DescribeClusterParameters(&describeParametersOpts)
if err != nil {
return err
}
d.Set("parameter", flattenRedshiftParameters(describeParametersResp.Parameters))
return nil
}
func resourceAwsRedshiftParameterGroupUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).redshiftconn
d.Partial(true)
if d.HasChange("parameter") {
o, n := d.GetChange("parameter")
if o == nil {
o = new(schema.Set)
}
if n == nil {
n = new(schema.Set)
}
os := o.(*schema.Set)
ns := n.(*schema.Set)
// Expand the "parameter" set to aws-sdk-go compat []redshift.Parameter
parameters, err := expandRedshiftParameters(ns.Difference(os).List())
if err != nil {
return err
}
if len(parameters) > 0 {
modifyOpts := redshift.ModifyClusterParameterGroupInput{
ParameterGroupName: aws.String(d.Get("name").(string)),
Parameters: parameters,
}
log.Printf("[DEBUG] Modify Redshift Parameter Group: %s", modifyOpts)
_, err = conn.ModifyClusterParameterGroup(&modifyOpts)
if err != nil {
return fmt.Errorf("Error modifying Redshift Parameter Group: %s", err)
}
}
d.SetPartial("parameter")
}
d.Partial(false)
return resourceAwsRedshiftParameterGroupRead(d, meta)
}
func resourceAwsRedshiftParameterGroupDelete(d *schema.ResourceData, meta interface{}) error {
stateConf := &resource.StateChangeConf{
Pending: []string{"pending"},
Target: "destroyed",
Refresh: resourceAwsRedshiftParameterGroupDeleteRefreshFunc(d, meta),
Timeout: 3 * time.Minute,
MinTimeout: 1 * time.Second,
}
_, err := stateConf.WaitForState()
return err
}
func resourceAwsRedshiftParameterGroupDeleteRefreshFunc(
d *schema.ResourceData,
meta interface{}) resource.StateRefreshFunc {
conn := meta.(*AWSClient).redshiftconn
return func() (interface{}, string, error) {
deleteOpts := redshift.DeleteClusterParameterGroupInput{
ParameterGroupName: aws.String(d.Id()),
}
if _, err := conn.DeleteClusterParameterGroup(&deleteOpts); err != nil {
redshiftErr, ok := err.(awserr.Error)
if !ok {
return d, "error", err
}
if redshiftErr.Code() != "RedshiftParameterGroupNotFoundFault" {
return d, "error", err
}
}
return d, "destroyed", nil
}
}
func resourceAwsRedshiftParameterHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%s-", m["name"].(string)))
// Store the value as a lower case string, to match how we store them in flattenParameters
buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["value"].(string))))
return hashcode.String(buf.String())
}
func validateRedshiftParamGroupName(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"only lowercase alphanumeric characters and hyphens allowed in %q", k))
}
if !regexp.MustCompile(`^[a-z]`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"first character of %q must be a letter", k))
}
if regexp.MustCompile(`--`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q cannot contain two consecutive hyphens", k))
}
if regexp.MustCompile(`-$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q cannot end with a hyphen", k))
}
if len(value) > 255 {
errors = append(errors, fmt.Errorf(
"%q cannot be greater than 255 characters", k))
}
return
}

View File

@ -0,0 +1,207 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/redshift"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSRedshiftParameterGroup_withParameters(t *testing.T) {
var v redshift.ClusterParameterGroup
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSRedshiftParameterGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSRedshiftParameterGroupConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSRedshiftParameterGroupExists("aws_redshift_parameter_group.bar", &v),
resource.TestCheckResourceAttr(
"aws_redshift_parameter_group.bar", "name", "parameter-group-test-terraform"),
resource.TestCheckResourceAttr(
"aws_redshift_parameter_group.bar", "family", "redshift-1.0"),
resource.TestCheckResourceAttr(
"aws_redshift_parameter_group.bar", "description", "Test parameter group for terraform"),
resource.TestCheckResourceAttr(
"aws_redshift_parameter_group.bar", "parameter.490804664.name", "require_ssl"),
resource.TestCheckResourceAttr(
"aws_redshift_parameter_group.bar", "parameter.490804664.value", "true"),
resource.TestCheckResourceAttr(
"aws_redshift_parameter_group.bar", "parameter.2036118857.name", "query_group"),
resource.TestCheckResourceAttr(
"aws_redshift_parameter_group.bar", "parameter.2036118857.value", "example"),
resource.TestCheckResourceAttr(
"aws_redshift_parameter_group.bar", "parameter.484080973.name", "enable_user_activity_logging"),
resource.TestCheckResourceAttr(
"aws_redshift_parameter_group.bar", "parameter.484080973.value", "true"),
),
},
},
})
}
func TestAccAWSRedshiftParameterGroup_withoutParameters(t *testing.T) {
var v redshift.ClusterParameterGroup
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSRedshiftParameterGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSRedshiftParameterGroupOnlyConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSRedshiftParameterGroupExists("aws_redshift_parameter_group.bar", &v),
resource.TestCheckResourceAttr(
"aws_redshift_parameter_group.bar", "name", "parameter-group-test-terraform"),
resource.TestCheckResourceAttr(
"aws_redshift_parameter_group.bar", "family", "redshift-1.0"),
resource.TestCheckResourceAttr(
"aws_redshift_parameter_group.bar", "description", "Test parameter group for terraform"),
),
},
},
})
}
func TestResourceAWSRedshiftParameterGroupNameValidation(t *testing.T) {
cases := []struct {
Value string
ErrCount int
}{
{
Value: "tEsting123",
ErrCount: 1,
},
{
Value: "testing123!",
ErrCount: 1,
},
{
Value: "1testing123",
ErrCount: 1,
},
{
Value: "testing--123",
ErrCount: 1,
},
{
Value: "testing123-",
ErrCount: 1,
},
{
Value: randomString(256),
ErrCount: 1,
},
}
for _, tc := range cases {
_, errors := validateRedshiftParamGroupName(tc.Value, "aws_redshift_parameter_group_name")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected the Redshift Parameter Group Name to trigger a validation error")
}
}
}
func testAccCheckAWSRedshiftParameterGroupDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).redshiftconn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_redshift_parameter_group" {
continue
}
// Try to find the Group
resp, err := conn.DescribeClusterParameterGroups(
&redshift.DescribeClusterParameterGroupsInput{
ParameterGroupName: aws.String(rs.Primary.ID),
})
if err == nil {
if len(resp.ParameterGroups) != 0 &&
*resp.ParameterGroups[0].ParameterGroupName == rs.Primary.ID {
return fmt.Errorf("Redshift Parameter Group still exists")
}
}
// Verify the error
newerr, ok := err.(awserr.Error)
if !ok {
return err
}
if newerr.Code() != "InvalidRedshiftParameterGroup.NotFound" {
return err
}
}
return nil
}
func testAccCheckAWSRedshiftParameterGroupExists(n string, v *redshift.ClusterParameterGroup) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No Redshift Parameter Group ID is set")
}
conn := testAccProvider.Meta().(*AWSClient).redshiftconn
opts := redshift.DescribeClusterParameterGroupsInput{
ParameterGroupName: aws.String(rs.Primary.ID),
}
resp, err := conn.DescribeClusterParameterGroups(&opts)
if err != nil {
return err
}
if len(resp.ParameterGroups) != 1 ||
*resp.ParameterGroups[0].ParameterGroupName != rs.Primary.ID {
return fmt.Errorf("Redshift Parameter Group not found")
}
*v = *resp.ParameterGroups[0]
return nil
}
}
const testAccAWSRedshiftParameterGroupOnlyConfig = `
resource "aws_redshift_parameter_group" "bar" {
name = "parameter-group-test-terraform"
family = "redshift-1.0"
description = "Test parameter group for terraform"
}`
const testAccAWSRedshiftParameterGroupConfig = `
resource "aws_redshift_parameter_group" "bar" {
name = "parameter-group-test-terraform"
family = "redshift-1.0"
description = "Test parameter group for terraform"
parameter {
name = "require_ssl"
value = "true"
}
parameter {
name = "query_group"
value = "example"
}
parameter{
name = "enable_user_activity_logging"
value = "true"
}
}
`

View File

@ -0,0 +1,291 @@
package aws
import (
"bytes"
"fmt"
"log"
"regexp"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/redshift"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsRedshiftSecurityGroup() *schema.Resource {
return &schema.Resource{
Create: resourceAwsRedshiftSecurityGroupCreate,
Read: resourceAwsRedshiftSecurityGroupRead,
Delete: resourceAwsRedshiftSecurityGroupDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateRedshiftSecurityGroupName,
},
"description": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"ingress": &schema.Schema{
Type: schema.TypeSet,
Required: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"cidr": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"security_group_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"security_group_owner_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
},
Set: resourceAwsRedshiftSecurityGroupIngressHash,
},
},
}
}
func resourceAwsRedshiftSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).redshiftconn
var err error
var errs []error
name := d.Get("name").(string)
desc := d.Get("description").(string)
sgInput := &redshift.CreateClusterSecurityGroupInput{
ClusterSecurityGroupName: aws.String(name),
Description: aws.String(desc),
}
log.Printf("[DEBUG] Redshift security group create: name: %s, description: %s", name, desc)
_, err = conn.CreateClusterSecurityGroup(sgInput)
if err != nil {
return fmt.Errorf("Error creating RedshiftSecurityGroup: %s", err)
}
d.SetId(d.Get("name").(string))
log.Printf("[INFO] Redshift Security Group ID: %s", d.Id())
sg, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta)
if err != nil {
return err
}
ingresses := d.Get("ingress").(*schema.Set)
for _, ing := range ingresses.List() {
err := resourceAwsRedshiftSecurityGroupAuthorizeRule(ing, *sg.ClusterSecurityGroupName, conn)
if err != nil {
errs = append(errs, err)
}
}
if len(errs) > 0 {
return &multierror.Error{Errors: errs}
}
log.Println("[INFO] Waiting for Redshift Security Group Ingress Authorizations to be authorized")
stateConf := &resource.StateChangeConf{
Pending: []string{"authorizing"},
Target: "authorized",
Refresh: resourceAwsRedshiftSecurityGroupStateRefreshFunc(d, meta),
Timeout: 10 * time.Minute,
}
_, err = stateConf.WaitForState()
if err != nil {
return err
}
return resourceAwsRedshiftSecurityGroupRead(d, meta)
}
func resourceAwsRedshiftSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
sg, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta)
if err != nil {
return err
}
rules := &schema.Set{
F: resourceAwsRedshiftSecurityGroupIngressHash,
}
for _, v := range sg.IPRanges {
rule := map[string]interface{}{"cidr": *v.CIDRIP}
rules.Add(rule)
}
for _, g := range sg.EC2SecurityGroups {
rule := map[string]interface{}{
"security_group_name": *g.EC2SecurityGroupName,
"security_group_owner_id": *g.EC2SecurityGroupOwnerId,
}
rules.Add(rule)
}
d.Set("ingress", rules)
d.Set("name", *sg.ClusterSecurityGroupName)
d.Set("description", *sg.Description)
return nil
}
func resourceAwsRedshiftSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).redshiftconn
log.Printf("[DEBUG] Redshift Security Group destroy: %v", d.Id())
opts := redshift.DeleteClusterSecurityGroupInput{
ClusterSecurityGroupName: aws.String(d.Id()),
}
log.Printf("[DEBUG] Redshift Security Group destroy configuration: %v", opts)
_, err := conn.DeleteClusterSecurityGroup(&opts)
if err != nil {
newerr, ok := err.(awserr.Error)
if ok && newerr.Code() == "InvalidRedshiftSecurityGroup.NotFound" {
return nil
}
return err
}
return nil
}
func resourceAwsRedshiftSecurityGroupRetrieve(d *schema.ResourceData, meta interface{}) (*redshift.ClusterSecurityGroup, error) {
conn := meta.(*AWSClient).redshiftconn
opts := redshift.DescribeClusterSecurityGroupsInput{
ClusterSecurityGroupName: aws.String(d.Id()),
}
log.Printf("[DEBUG] Redshift Security Group describe configuration: %#v", opts)
resp, err := conn.DescribeClusterSecurityGroups(&opts)
if err != nil {
return nil, fmt.Errorf("Error retrieving Redshift Security Groups: %s", err)
}
if len(resp.ClusterSecurityGroups) != 1 ||
*resp.ClusterSecurityGroups[0].ClusterSecurityGroupName != d.Id() {
return nil, fmt.Errorf("Unable to find Redshift Security Group: %#v", resp.ClusterSecurityGroups)
}
return resp.ClusterSecurityGroups[0], nil
}
func validateRedshiftSecurityGroupName(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if value == "default" {
errors = append(errors, fmt.Errorf("the Redshift Security Group name cannot be %q", value))
}
if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"only lowercase alphanumeric characters and hyphens allowed in %q: %q",
k, value))
}
if len(value) > 255 {
errors = append(errors, fmt.Errorf(
"%q cannot be longer than 32 characters: %q", k, value))
}
return
}
func resourceAwsRedshiftSecurityGroupIngressHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
if v, ok := m["cidr"]; ok {
buf.WriteString(fmt.Sprintf("%s-", v.(string)))
}
if v, ok := m["security_group_name"]; ok {
buf.WriteString(fmt.Sprintf("%s-", v.(string)))
}
if v, ok := m["security_group_owner_id"]; ok {
buf.WriteString(fmt.Sprintf("%s-", v.(string)))
}
return hashcode.String(buf.String())
}
func resourceAwsRedshiftSecurityGroupAuthorizeRule(ingress interface{}, redshiftSecurityGroupName string, conn *redshift.Redshift) error {
ing := ingress.(map[string]interface{})
opts := redshift.AuthorizeClusterSecurityGroupIngressInput{
ClusterSecurityGroupName: aws.String(redshiftSecurityGroupName),
}
if attr, ok := ing["cidr"]; ok && attr != "" {
opts.CIDRIP = aws.String(attr.(string))
}
if attr, ok := ing["security_group_name"]; ok && attr != "" {
opts.EC2SecurityGroupName = aws.String(attr.(string))
}
if attr, ok := ing["security_group_owner_id"]; ok && attr != "" {
opts.EC2SecurityGroupOwnerId = aws.String(attr.(string))
}
log.Printf("[DEBUG] Authorize ingress rule configuration: %#v", opts)
_, err := conn.AuthorizeClusterSecurityGroupIngress(&opts)
if err != nil {
return fmt.Errorf("Error authorizing security group ingress: %s", err)
}
return nil
}
func resourceAwsRedshiftSecurityGroupStateRefreshFunc(
d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
v, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta)
if err != nil {
log.Printf("Error on retrieving Redshift Security Group when waiting: %s", err)
return nil, "", err
}
statuses := make([]string, 0, len(v.EC2SecurityGroups)+len(v.IPRanges))
for _, ec2g := range v.EC2SecurityGroups {
statuses = append(statuses, *ec2g.Status)
}
for _, ips := range v.IPRanges {
statuses = append(statuses, *ips.Status)
}
for _, stat := range statuses {
// Not done
if stat != "authorized" {
return nil, "authorizing", nil
}
}
return v, "authorized", nil
}
}

View File

@ -0,0 +1,205 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/redshift"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSRedshiftSecurityGroup_ingressCidr(t *testing.T) {
var v redshift.ClusterSecurityGroup
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSRedshiftSecurityGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSRedshiftSecurityGroupConfig_ingressCidr,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSRedshiftSecurityGroupExists("aws_redshift_security_group.bar", &v),
resource.TestCheckResourceAttr(
"aws_redshift_security_group.bar", "name", "redshift-sg-terraform"),
resource.TestCheckResourceAttr(
"aws_redshift_security_group.bar", "description", "this is a description"),
resource.TestCheckResourceAttr(
"aws_redshift_security_group.bar", "ingress.2735652665.cidr", "10.0.0.1/24"),
resource.TestCheckResourceAttr(
"aws_redshift_security_group.bar", "ingress.#", "1"),
),
},
},
})
}
func TestAccAWSRedshiftSecurityGroup_ingressSecurityGroup(t *testing.T) {
var v redshift.ClusterSecurityGroup
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSRedshiftSecurityGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSRedshiftSecurityGroupConfig_ingressSgId,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSRedshiftSecurityGroupExists("aws_redshift_security_group.bar", &v),
resource.TestCheckResourceAttr(
"aws_redshift_security_group.bar", "name", "redshift-sg-terraform"),
resource.TestCheckResourceAttr(
"aws_redshift_security_group.bar", "description", "this is a description"),
resource.TestCheckResourceAttr(
"aws_redshift_security_group.bar", "ingress.#", "1"),
resource.TestCheckResourceAttr(
"aws_redshift_security_group.bar", "ingress.220863.security_group_name", "terraform_redshift_acceptance_test"),
),
},
},
})
}
func testAccCheckAWSRedshiftSecurityGroupExists(n string, v *redshift.ClusterSecurityGroup) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No Redshift Security Group ID is set")
}
conn := testAccProvider.Meta().(*AWSClient).redshiftconn
opts := redshift.DescribeClusterSecurityGroupsInput{
ClusterSecurityGroupName: aws.String(rs.Primary.ID),
}
resp, err := conn.DescribeClusterSecurityGroups(&opts)
if err != nil {
return err
}
if len(resp.ClusterSecurityGroups) != 1 ||
*resp.ClusterSecurityGroups[0].ClusterSecurityGroupName != rs.Primary.ID {
return fmt.Errorf("Redshift Security Group not found")
}
*v = *resp.ClusterSecurityGroups[0]
return nil
}
}
func testAccCheckAWSRedshiftSecurityGroupDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).redshiftconn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_redshift_security_group" {
continue
}
// Try to find the Group
resp, err := conn.DescribeClusterSecurityGroups(
&redshift.DescribeClusterSecurityGroupsInput{
ClusterSecurityGroupName: aws.String(rs.Primary.ID),
})
if err == nil {
if len(resp.ClusterSecurityGroups) != 0 &&
*resp.ClusterSecurityGroups[0].ClusterSecurityGroupName == rs.Primary.ID {
return fmt.Errorf("Redshift Security Group still exists")
}
}
// Verify the error
newerr, ok := err.(awserr.Error)
if !ok {
return err
}
if newerr.Code() != "InvalidRedshiftSecurityGroup.NotFound" {
return err
}
}
return nil
}
func TestResourceAWSRedshiftSecurityGroupNameValidation(t *testing.T) {
cases := []struct {
Value string
ErrCount int
}{
{
Value: "default",
ErrCount: 1,
},
{
Value: "testing123%%",
ErrCount: 1,
},
{
Value: "TestingSG",
ErrCount: 1,
},
{
Value: randomString(256),
ErrCount: 1,
},
}
for _, tc := range cases {
_, errors := validateRedshiftSecurityGroupName(tc.Value, "aws_redshift_security_group_name")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected the Redshift Security Group Name to trigger a validation error")
}
}
}
const testAccAWSRedshiftSecurityGroupConfig_ingressCidr = `
provider "aws" {
region = "us-east-1"
}
resource "aws_redshift_security_group" "bar" {
name = "redshift-sg-terraform"
description = "this is a description"
ingress {
cidr = "10.0.0.1/24"
}
}`
const testAccAWSRedshiftSecurityGroupConfig_ingressSgId = `
provider "aws" {
region = "us-east-1"
}
resource "aws_security_group" "redshift" {
name = "terraform_redshift_acceptance_test"
description = "Used in the redshift acceptance tests"
ingress {
protocol = "tcp"
from_port = 22
to_port = 22
cidr_blocks = ["10.0.0.0/8"]
}
}
resource "aws_redshift_security_group" "bar" {
name = "redshift-sg-terraform"
description = "this is a description"
ingress {
security_group_name = "${aws_security_group.redshift.name}"
security_group_owner_id = "${aws_security_group.redshift.owner_id}"
}
}`

View File

@ -0,0 +1,186 @@
package aws
import (
"fmt"
"log"
"regexp"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/redshift"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsRedshiftSubnetGroup() *schema.Resource {
return &schema.Resource{
Create: resourceAwsRedshiftSubnetGroupCreate,
Read: resourceAwsRedshiftSubnetGroupRead,
Update: resourceAwsRedshiftSubnetGroupUpdate,
Delete: resourceAwsRedshiftSubnetGroupDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
ForceNew: true,
Required: true,
ValidateFunc: validateRedshiftSubnetGroupName,
},
"description": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"subnet_ids": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
}
}
func resourceAwsRedshiftSubnetGroupCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).redshiftconn
subnetIdsSet := d.Get("subnet_ids").(*schema.Set)
subnetIds := make([]*string, subnetIdsSet.Len())
for i, subnetId := range subnetIdsSet.List() {
subnetIds[i] = aws.String(subnetId.(string))
}
createOpts := redshift.CreateClusterSubnetGroupInput{
ClusterSubnetGroupName: aws.String(d.Get("name").(string)),
Description: aws.String(d.Get("description").(string)),
SubnetIds: subnetIds,
}
log.Printf("[DEBUG] Create Redshift Subnet Group: %#v", createOpts)
_, err := conn.CreateClusterSubnetGroup(&createOpts)
if err != nil {
return fmt.Errorf("Error creating Redshift Subnet Group: %s", err)
}
d.SetId(*createOpts.ClusterSubnetGroupName)
log.Printf("[INFO] Redshift Subnet Group ID: %s", d.Id())
return resourceAwsRedshiftSubnetGroupRead(d, meta)
}
func resourceAwsRedshiftSubnetGroupRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).redshiftconn
describeOpts := redshift.DescribeClusterSubnetGroupsInput{
ClusterSubnetGroupName: aws.String(d.Id()),
}
describeResp, err := conn.DescribeClusterSubnetGroups(&describeOpts)
if err != nil {
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "ClusterSubnetGroupNotFoundFault" {
log.Printf("[INFO] Redshift Subnet Group: %s was not found", d.Id())
d.SetId("")
return nil
}
return err
}
if len(describeResp.ClusterSubnetGroups) == 0 {
return fmt.Errorf("Unable to find Redshift Subnet Group: %#v", describeResp.ClusterSubnetGroups)
}
d.Set("name", d.Id())
d.Set("description", describeResp.ClusterSubnetGroups[0].Description)
d.Set("subnet_ids", subnetIdsToSlice(describeResp.ClusterSubnetGroups[0].Subnets))
return nil
}
func resourceAwsRedshiftSubnetGroupUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).redshiftconn
if d.HasChange("subnet_ids") {
_, n := d.GetChange("subnet_ids")
if n == nil {
n = new(schema.Set)
}
ns := n.(*schema.Set)
var sIds []*string
for _, s := range ns.List() {
sIds = append(sIds, aws.String(s.(string)))
}
_, err := conn.ModifyClusterSubnetGroup(&redshift.ModifyClusterSubnetGroupInput{
ClusterSubnetGroupName: aws.String(d.Id()),
SubnetIds: sIds,
})
if err != nil {
return err
}
}
return nil
}
func resourceAwsRedshiftSubnetGroupDelete(d *schema.ResourceData, meta interface{}) error {
stateConf := &resource.StateChangeConf{
Pending: []string{"pending"},
Target: "destroyed",
Refresh: resourceAwsRedshiftSubnetGroupDeleteRefreshFunc(d, meta),
Timeout: 3 * time.Minute,
MinTimeout: 1 * time.Second,
}
_, err := stateConf.WaitForState()
return err
}
func resourceAwsRedshiftSubnetGroupDeleteRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
conn := meta.(*AWSClient).redshiftconn
return func() (interface{}, string, error) {
deleteOpts := redshift.DeleteClusterSubnetGroupInput{
ClusterSubnetGroupName: aws.String(d.Id()),
}
if _, err := conn.DeleteClusterSubnetGroup(&deleteOpts); err != nil {
redshiftErr, ok := err.(awserr.Error)
if !ok {
return d, "error", err
}
if redshiftErr.Code() != "ClusterSubnetGroupNotFoundFault" {
return d, "error", err
}
}
return d, "destroyed", nil
}
}
func subnetIdsToSlice(subnetIds []*redshift.Subnet) []string {
subnetsSlice := make([]string, 0, len(subnetIds))
for _, s := range subnetIds {
subnetsSlice = append(subnetsSlice, *s.SubnetIdentifier)
}
return subnetsSlice
}
func validateRedshiftSubnetGroupName(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^[0-9a-z-_]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"only lowercase alphanumeric characters, hyphens, underscores, and periods allowed in %q", k))
}
if len(value) > 255 {
errors = append(errors, fmt.Errorf(
"%q cannot be longer than 255 characters", k))
}
if regexp.MustCompile(`(?i)^default$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q is not allowed as %q", "Default", k))
}
return
}

View File

@ -0,0 +1,220 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/redshift"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSRedshiftSubnetGroup_basic(t *testing.T) {
var v redshift.ClusterSubnetGroup
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckRedshiftSubnetGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccRedshiftSubnetGroupConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckRedshiftSubnetGroupExists("aws_redshift_subnet_group.foo", &v),
resource.TestCheckResourceAttr(
"aws_redshift_subnet_group.foo", "subnet_ids.#", "2"),
),
},
},
})
}
func TestAccAWSRedshiftSubnetGroup_updateSubnetIds(t *testing.T) {
var v redshift.ClusterSubnetGroup
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckRedshiftSubnetGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccRedshiftSubnetGroupConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckRedshiftSubnetGroupExists("aws_redshift_subnet_group.foo", &v),
resource.TestCheckResourceAttr(
"aws_redshift_subnet_group.foo", "subnet_ids.#", "2"),
),
},
resource.TestStep{
Config: testAccRedshiftSubnetGroupConfig_updateSubnetIds,
Check: resource.ComposeTestCheckFunc(
testAccCheckRedshiftSubnetGroupExists("aws_redshift_subnet_group.foo", &v),
resource.TestCheckResourceAttr(
"aws_redshift_subnet_group.foo", "subnet_ids.#", "3"),
),
},
},
})
}
func TestResourceAWSRedshiftSubnetGroupNameValidation(t *testing.T) {
cases := []struct {
Value string
ErrCount int
}{
{
Value: "default",
ErrCount: 1,
},
{
Value: "testing123%%",
ErrCount: 1,
},
{
Value: "TestingSG",
ErrCount: 1,
},
{
Value: randomString(256),
ErrCount: 1,
},
}
for _, tc := range cases {
_, errors := validateRedshiftSubnetGroupName(tc.Value, "aws_redshift_subnet_group_name")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected the Redshift Subnet Group Name to trigger a validation error")
}
}
}
func testAccCheckRedshiftSubnetGroupDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).redshiftconn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_redshift_subnet_group" {
continue
}
resp, err := conn.DescribeClusterSubnetGroups(
&redshift.DescribeClusterSubnetGroupsInput{
ClusterSubnetGroupName: aws.String(rs.Primary.ID)})
if err == nil {
if len(resp.ClusterSubnetGroups) > 0 {
return fmt.Errorf("still exist.")
}
return nil
}
redshiftErr, ok := err.(awserr.Error)
if !ok {
return err
}
if redshiftErr.Code() != "ClusterSubnetGroupNotFoundFault" {
return err
}
}
return nil
}
func testAccCheckRedshiftSubnetGroupExists(n string, v *redshift.ClusterSubnetGroup) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
conn := testAccProvider.Meta().(*AWSClient).redshiftconn
resp, err := conn.DescribeClusterSubnetGroups(
&redshift.DescribeClusterSubnetGroupsInput{ClusterSubnetGroupName: aws.String(rs.Primary.ID)})
if err != nil {
return err
}
if len(resp.ClusterSubnetGroups) == 0 {
return fmt.Errorf("ClusterSubnetGroup not found")
}
*v = *resp.ClusterSubnetGroups[0]
return nil
}
}
const testAccRedshiftSubnetGroupConfig = `
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
}
resource "aws_subnet" "foo" {
cidr_block = "10.1.1.0/24"
availability_zone = "us-west-2a"
vpc_id = "${aws_vpc.foo.id}"
tags {
Name = "tf-dbsubnet-test-1"
}
}
resource "aws_subnet" "bar" {
cidr_block = "10.1.2.0/24"
availability_zone = "us-west-2b"
vpc_id = "${aws_vpc.foo.id}"
tags {
Name = "tf-dbsubnet-test-2"
}
}
resource "aws_redshift_subnet_group" "foo" {
name = "foo"
description = "foo description"
subnet_ids = ["${aws_subnet.foo.id}", "${aws_subnet.bar.id}"]
}
`
const testAccRedshiftSubnetGroupConfig_updateSubnetIds = `
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
}
resource "aws_subnet" "foo" {
cidr_block = "10.1.1.0/24"
availability_zone = "us-west-2a"
vpc_id = "${aws_vpc.foo.id}"
tags {
Name = "tf-dbsubnet-test-1"
}
}
resource "aws_subnet" "bar" {
cidr_block = "10.1.2.0/24"
availability_zone = "us-west-2b"
vpc_id = "${aws_vpc.foo.id}"
tags {
Name = "tf-dbsubnet-test-2"
}
}
resource "aws_subnet" "foobar" {
cidr_block = "10.1.3.0/24"
availability_zone = "us-west-2c"
vpc_id = "${aws_vpc.foo.id}"
tags {
Name = "tf-dbsubnet-test-3"
}
}
resource "aws_redshift_subnet_group" "foo" {
name = "foo"
description = "foo description"
subnet_ids = ["${aws_subnet.foo.id}", "${aws_subnet.bar.id}", "${aws_subnet.foobar.id}"]
}
`

View File

@ -16,6 +16,7 @@ import (
elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice"
"github.com/aws/aws-sdk-go/service/elb"
"github.com/aws/aws-sdk-go/service/rds"
"github.com/aws/aws-sdk-go/service/redshift"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/hashicorp/terraform/helper/schema"
)
@ -232,6 +233,29 @@ func expandParameters(configured []interface{}) ([]*rds.Parameter, error) {
return parameters, nil
}
func expandRedshiftParameters(configured []interface{}) ([]*redshift.Parameter, error) {
var parameters []*redshift.Parameter
// Loop over our configured parameters and create
// an array of aws-sdk-go compatabile objects
for _, pRaw := range configured {
data := pRaw.(map[string]interface{})
if data["name"].(string) == "" {
continue
}
p := &redshift.Parameter{
ParameterName: aws.String(data["name"].(string)),
ParameterValue: aws.String(data["value"].(string)),
}
parameters = append(parameters, p)
}
return parameters, nil
}
// Takes the result of flatmap.Expand for an array of parameters and
// returns Parameter API compatible objects
func expandElastiCacheParameters(configured []interface{}) ([]*elasticache.ParameterNameValue, error) {
@ -412,6 +436,18 @@ func flattenParameters(list []*rds.Parameter) []map[string]interface{} {
return result
}
// Flattens an array of Redshift Parameters into a []map[string]interface{}
func flattenRedshiftParameters(list []*redshift.Parameter) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(list))
for _, i := range list {
result = append(result, map[string]interface{}{
"name": strings.ToLower(*i.ParameterName),
"value": strings.ToLower(*i.ParameterValue),
})
}
return result
}
// Flattens an array of Parameters into a []map[string]interface{}
func flattenElastiCacheParameters(list []*elasticache.Parameter) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(list))

View File

@ -10,6 +10,7 @@ import (
"github.com/aws/aws-sdk-go/service/elasticache"
"github.com/aws/aws-sdk-go/service/elb"
"github.com/aws/aws-sdk-go/service/rds"
"github.com/aws/aws-sdk-go/service/redshift"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/hashicorp/terraform/flatmap"
"github.com/hashicorp/terraform/helper/schema"
@ -426,7 +427,32 @@ func TestExpandParameters(t *testing.T) {
}
}
func TestExpandElasticacheParameters(t *testing.T) {
func TestexpandRedshiftParameters(t *testing.T) {
expanded := []interface{}{
map[string]interface{}{
"name": "character_set_client",
"value": "utf8",
},
}
parameters, err := expandRedshiftParameters(expanded)
if err != nil {
t.Fatalf("bad: %#v", err)
}
expected := &redshift.Parameter{
ParameterName: aws.String("character_set_client"),
ParameterValue: aws.String("utf8"),
}
if !reflect.DeepEqual(parameters[0], expected) {
t.Fatalf(
"Got:\n\n%#v\n\nExpected:\n\n%#v\n",
parameters[0],
expected)
}
}
func TestexpandElasticacheParameters(t *testing.T) {
expanded := []interface{}{
map[string]interface{}{
"name": "activerehashing",
@ -481,7 +507,36 @@ func TestFlattenParameters(t *testing.T) {
}
}
func TestFlattenElasticacheParameters(t *testing.T) {
func TestflattenRedshiftParameters(t *testing.T) {
cases := []struct {
Input []*redshift.Parameter
Output []map[string]interface{}
}{
{
Input: []*redshift.Parameter{
&redshift.Parameter{
ParameterName: aws.String("character_set_client"),
ParameterValue: aws.String("utf8"),
},
},
Output: []map[string]interface{}{
map[string]interface{}{
"name": "character_set_client",
"value": "utf8",
},
},
},
}
for _, tc := range cases {
output := flattenRedshiftParameters(tc.Input)
if !reflect.DeepEqual(output, tc.Output) {
t.Fatalf("Got:\n\n%#v\n\nExpected:\n\n%#v", output, tc.Output)
}
}
}
func TestflattenElasticacheParameters(t *testing.T) {
cases := []struct {
Input []*elasticache.Parameter
Output []map[string]interface{}

View File

@ -0,0 +1,27 @@
package aws
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/redshift"
)
func tagsFromMapRedshift(m map[string]interface{}) []*redshift.Tag {
result := make([]*redshift.Tag, 0, len(m))
for k, v := range m {
result = append(result, &redshift.Tag{
Key: aws.String(k),
Value: aws.String(v.(string)),
})
}
return result
}
func tagsToMapRedshift(ts []*redshift.Tag) map[string]string {
result := make(map[string]string)
for _, t := range ts {
result[*t.Key] = *t.Value
}
return result
}

View File

@ -0,0 +1,80 @@
---
layout: "aws"
page_title: "AWS: aws_redshift_cluster"
sidebar_current: "docs-aws-resource-redshift-cluster"
---
# aws\_redshift\_cluster
Provides a Redshift Cluster Resource.
## Example Usage
resource "aws_redshift_cluster" "default" {
cluster_identifier = "tf-redshift-cluster"
database_name = "mydb"
master_username = "foo"
master_password = "Mustbe8characters"
node_type = "dc1.large"
cluster_type = "single-node"
}
## Argument Reference
For more detailed documentation about each argument, refer to
the [AWS official documentation](http://docs.aws.amazon.com/cli/latest/reference/redshift/index.html#cli-aws-redshift).
The following arguments are supported:
* `cluster_identifier` - (Required) The Cluster Identifier. Must be a lower case
string.
* `database_name` - (Optional) The name of the first database to be created when the cluster is created.
If you do not provide a name, Amazon Redshift will create a default database called `dev`.
* `cluster_type` - (Required) The type of the cluster. Valid values are `multi-node` and `single-node`
* `node_type` - (Required) The node type to be provisioned for the cluster.
* `master_password` - (Required) Password for the master DB user. Note that this may
show up in logs, and it will be stored in the state file
* `master_username` - (Required) Username for the master DB user
* `cluster_security_groups` - (Optional) A list of security groups to be associated with this cluster.
* `vpc_security_group_ids` - (Optional) A list of Virtual Private Cloud (VPC) security groups to be associated with the cluster.
* `cluster_subnet_group_name` - (Optional) The name of a cluster subnet group to be associated with this cluster. If this parameter is not provided the resulting cluster will be deployed outside virtual private cloud (VPC).
* `availability_zone` - (Optional) The EC2 Availability Zone (AZ) in which you want Amazon Redshift to provision the cluster. For example, if you have several EC2 instances running in a specific Availability Zone, then you might want the cluster to be provisioned in the same zone in order to decrease network latency.
* `preferred_maintenance_window` - (Optional) The weekly time range (in UTC) during which automated cluster maintenance can occur.
Format: ddd:hh24:mi-ddd:hh24:mi
* `cluster_parameter_group_name` - (Optional) The name of the parameter group to be associated with this cluster.
* `automated_snapshot_retention_period` - (Optional) The number of days that automated snapshots are retained. If the value is 0, automated snapshots are disabled. Even if automated snapshots are disabled, you can still create manual snapshots when you want with create-cluster-snapshot.
* `port` - (Optional) The port number on which the cluster accepts incoming connections.
The cluster is accessible only via the JDBC and ODBC connection strings. Part of the connection string requires the port on which the cluster will listen for incoming connections. Default port is 5439.
* `cluster_version` - (Optional) The version of the Amazon Redshift engine software that you want to deploy on the cluster.
The version selected runs on all the nodes in the cluster.
* `allow_version_upgrade` - (Optional) If true , major version upgrades can be applied during the maintenance window to the Amazon Redshift engine that is running on the cluster. Default is true
* `number_of_nodes` - (Optional) The number of compute nodes in the cluster. This parameter is required when the ClusterType parameter is specified as multi-node. Default is 1.
* `publicly_accessible` - (Optional) If true , the cluster can be accessed from a public network.
* `encrypted` - (Optional) If true , the data in the cluster is encrypted at rest.
* `elastic_ip` - (Optional) The Elastic IP (EIP) address for the cluster.
* `skip_final_snapshot` - (Optional) Determines whether a final snapshot of the cluster is created before Amazon Redshift deletes the cluster. If true , a final cluster snapshot is not created. If false , a final cluster snapshot is created before the cluster is deleted. Default is false.
* `final_snapshot_identifier` - (Optional) The identifier of the final snapshot that is to be created immediately before deleting the cluster. If this parameter is provided, `skip_final_snapshot` must be false.
## Attributes Reference
The following attributes are exported:
* `id` - The Redshift Cluster ID.
* `cluster_identifier` - The Cluster Identifier
* `cluster_type` - The cluster type
* `node_type` - The type of nodes in the cluster
* `database_name` - The name of the default database in the Cluster
* `availability_zone` - The availability zone of the Cluster
* `automated_snapshot_retention_period` - The backup retention period
* `preferred_maintenance_window` - The backup window
* `endpoint` - The connection endpoint
* `encrypted` - Whether the data in the cluster is encrypted
* `cluster_security_groups` - The security groups associated with the cluster
* `vpc_security_group_ids` - The VPC security group Ids associated with the cluster
* `port` - The Port the cluster responds on
* `cluster_version` - The version of Redshift engine software
* `cluster_parameter_group_name` - The name of the parameter group to be associated with this cluster
* `cluster_subnet_group_name` - The name of a cluster subnet group to be associated with this cluster
* `cluster_public_key` - The public key for the cluster
* `cluster_revision_number` - The specific revision number of the database in the cluster

View File

@ -0,0 +1,53 @@
---
layout: "aws"
page_title: "AWS: aws_redshift_parameter_group"
sidebar_current: "docs-aws-resource-redshift-parameter-group"
---
# aws\_redshift\_parameter\_group
Provides a Redshift Cluster parameter group resource.
## Example Usage
```
resource "aws_redshift_parameter_group" "bar" {
name = "parameter-group-test-terraform"
family = "redshift-1.0"
description = "Test parameter group for terraform"
parameter {
name = "require_ssl"
value = "true"
}
parameter {
name = "query_group"
value = "example"
}
parameter{
name = "enable_user_activity_logging"
value = "true"
}
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the Redshift parameter group.
* `family` - (Required) The family of the Redshift parameter group.
* `description` - (Required) The description of the Redshift parameter group.
* `parameter` - (Optional) A list of Redshift parameters to apply.
Parameter blocks support the following:
* `name` - (Required) The name of the Redshift parameter.
* `value` - (Required) The value of the Redshift parameter.
You can read more about the parameters that Redshift supports in the [documentation](http://docs.aws.amazon.com/redshift/latest/mgmt/working-with-parameter-groups.html)
## Attributes Reference
The following attributes are exported:
* `id` - The Redshift parameter group name.

View File

@ -0,0 +1,46 @@
---
layout: "aws"
page_title: "AWS: aws_redshift_security_group"
sidebar_current: "docs-aws-resource-redshift-security-group"
description: |-
Provides a Redshift security group resource.
---
# aws\_redshift\_security\_group
Creates a new Amazon Redshift security group. You use security groups to control access to non-VPC clusters
## Example Usage
```
resource "aws_redshift_security_group" "default" {
name = "redshift_sg"
description = "Redshift Example security group"
ingress {
cidr = "10.0.0.0/24"
}
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the Redshift security group.
* `description` - (Required) The description of the Redshift security group.
* `ingress` - (Optional) A list of ingress rules.
Ingress blocks support the following:
* `cidr` - The CIDR block to accept
* `security_group_name` - The name of the security group to authorize
* `security_group_owner_id` - The owner Id of the security group provided
by `security_group_name`.
## Attributes Reference
The following attributes are exported:
* `id` - The Redshift security group ID.

View File

@ -0,0 +1,59 @@
---
layout: "aws"
page_title: "AWS: aws_redshift_subnet_group"
sidebar_current: "docs-aws-resource-redshift-subnet-group"
description: |-
Provides a Redshift Subnet Group resource.
---
# aws\_redshift\_subnet\_group
Creates a new Amazon Redshift subnet group. You must provide a list of one or more subnets in your existing Amazon Virtual Private Cloud (Amazon VPC) when creating Amazon Redshift subnet group.
## Example Usage
```
resource "aws_vpc" "foo" {
cidr_block = "10.1.0.0/16"
}
resource "aws_subnet" "foo" {
cidr_block = "10.1.1.0/24"
availability_zone = "us-west-2a"
vpc_id = "${aws_vpc.foo.id}"
tags {
Name = "tf-dbsubnet-test-1"
}
}
resource "aws_subnet" "bar" {
cidr_block = "10.1.2.0/24"
availability_zone = "us-west-2b"
vpc_id = "${aws_vpc.foo.id}"
tags {
Name = "tf-dbsubnet-test-2"
}
}
resource "aws_redshift_subnet_group" "foo" {
name = "foo"
description = "foo description"
subnet_ids = ["${aws_subnet.foo.id}", "${aws_subnet.bar.id}"]
}
`
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the Redshift Subnet group.
* `description` - (Required) The description of the Redshift Subnet group.
* `subnet_ids` - (Optional) An array of VPC subnet IDs..
## Attributes Reference
The following attributes are exported:
* `id` - The Redshift Subnet group ID.

View File

@ -441,6 +441,29 @@
</ul>
</li>
<li<%= sidebar_current(/^docs-aws-resource-redshift/) %>>
<a href="#">Redshift Resources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-aws-resource-redshift-cluster") %>>
<a href="/docs/providers/aws/r/redshift_cluster.html">aws_redshift_cluster</a>
</li>
<li<%= sidebar_current("docs-aws-resource-redshift-parameter-group") %>>
<a href="/docs/providers/aws/r/redshift_parameter_group.html">aws_redshift_parameter_group</a>
</li>
<li<%= sidebar_current("docs-aws-resource-redshift-security-group") %>>
<a href="/docs/providers/aws/r/redshift_security_group.html">aws_redshift_security_group</a>
</li>
<li<%= sidebar_current("docs-aws-resource-redshift-subnet-group") %>>
<a href="/docs/providers/aws/r/redshift_subnet_group.html">aws_redshift_subnet_group</a>
</li>
</ul>
</li>
<li<%= sidebar_current(/^docs-aws-resource-route53/) %>>
<a href="#">Route53 Resources</a>