Initial creation of the work for AWS RedShift Support

Finalising the schema and acceptance tests for the Redshift Security Group's
This commit is contained in:
stack72 2015-11-11 20:51:46 +00:00
parent 27008ae898
commit 85afc7d614
4 changed files with 532 additions and 0 deletions

View File

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

View File

@ -170,6 +170,7 @@ func Provider() terraform.ResourceProvider {
"aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(), "aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(),
"aws_rds_cluster": resourceAwsRDSCluster(), "aws_rds_cluster": resourceAwsRDSCluster(),
"aws_rds_cluster_instance": resourceAwsRDSClusterInstance(), "aws_rds_cluster_instance": resourceAwsRDSClusterInstance(),
"aws_redshift_security_group": resourceAwsRedshiftSecurityGroup(),
"aws_route53_delegation_set": resourceAwsRoute53DelegationSet(), "aws_route53_delegation_set": resourceAwsRoute53DelegationSet(),
"aws_route53_record": resourceAwsRoute53Record(), "aws_route53_record": resourceAwsRoute53Record(),
"aws_route53_zone_association": resourceAwsRoute53ZoneAssociation(), "aws_route53_zone_association": resourceAwsRoute53ZoneAssociation(),

View File

@ -0,0 +1,320 @@
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,
},
"tags": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
ForceNew: true,
},
},
}
}
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)
tags := tagsFromMapRedshift(d.Get("tags").(map[string]interface{}))
sgInput := &redshift.CreateClusterSecurityGroupInput{
ClusterSecurityGroupName: aws.String(name),
Description: aws.String(desc),
Tags: tags,
}
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 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
}
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 TestResourceAWSRedshiftSecurityGroupName_validation(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}"
}
}`