github.com/danrjohnson/terraform@v0.7.0-rc2.0.20160627135212-d0fc1fa086ff/builtin/providers/aws/resource_aws_redshift_security_group.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"regexp"
     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/go-multierror"
    14  	"github.com/hashicorp/terraform/helper/hashcode"
    15  	"github.com/hashicorp/terraform/helper/resource"
    16  	"github.com/hashicorp/terraform/helper/schema"
    17  )
    18  
    19  func resourceAwsRedshiftSecurityGroup() *schema.Resource {
    20  	return &schema.Resource{
    21  		Create: resourceAwsRedshiftSecurityGroupCreate,
    22  		Read:   resourceAwsRedshiftSecurityGroupRead,
    23  		Delete: resourceAwsRedshiftSecurityGroupDelete,
    24  
    25  		Schema: map[string]*schema.Schema{
    26  			"name": &schema.Schema{
    27  				Type:         schema.TypeString,
    28  				Required:     true,
    29  				ForceNew:     true,
    30  				ValidateFunc: validateRedshiftSecurityGroupName,
    31  			},
    32  
    33  			"description": &schema.Schema{
    34  				Type:     schema.TypeString,
    35  				Optional: true,
    36  				ForceNew: true,
    37  				Default:  "Managed by Terraform",
    38  			},
    39  
    40  			"ingress": &schema.Schema{
    41  				Type:     schema.TypeSet,
    42  				Required: true,
    43  				ForceNew: true,
    44  				Elem: &schema.Resource{
    45  					Schema: map[string]*schema.Schema{
    46  						"cidr": &schema.Schema{
    47  							Type:     schema.TypeString,
    48  							Optional: true,
    49  						},
    50  
    51  						"security_group_name": &schema.Schema{
    52  							Type:     schema.TypeString,
    53  							Optional: true,
    54  							Computed: true,
    55  						},
    56  
    57  						"security_group_owner_id": &schema.Schema{
    58  							Type:     schema.TypeString,
    59  							Optional: true,
    60  							Computed: true,
    61  						},
    62  					},
    63  				},
    64  				Set: resourceAwsRedshiftSecurityGroupIngressHash,
    65  			},
    66  		},
    67  	}
    68  }
    69  
    70  func resourceAwsRedshiftSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
    71  	conn := meta.(*AWSClient).redshiftconn
    72  
    73  	var err error
    74  	var errs []error
    75  
    76  	name := d.Get("name").(string)
    77  	desc := d.Get("description").(string)
    78  	sgInput := &redshift.CreateClusterSecurityGroupInput{
    79  		ClusterSecurityGroupName: aws.String(name),
    80  		Description:              aws.String(desc),
    81  	}
    82  	log.Printf("[DEBUG] Redshift security group create: name: %s, description: %s", name, desc)
    83  	_, err = conn.CreateClusterSecurityGroup(sgInput)
    84  	if err != nil {
    85  		return fmt.Errorf("Error creating RedshiftSecurityGroup: %s", err)
    86  	}
    87  
    88  	d.SetId(d.Get("name").(string))
    89  
    90  	log.Printf("[INFO] Redshift Security Group ID: %s", d.Id())
    91  	sg, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta)
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	ingresses := d.Get("ingress").(*schema.Set)
    97  	for _, ing := range ingresses.List() {
    98  		err := resourceAwsRedshiftSecurityGroupAuthorizeRule(ing, *sg.ClusterSecurityGroupName, conn)
    99  		if err != nil {
   100  			errs = append(errs, err)
   101  		}
   102  	}
   103  
   104  	if len(errs) > 0 {
   105  		return &multierror.Error{Errors: errs}
   106  	}
   107  
   108  	log.Println("[INFO] Waiting for Redshift Security Group Ingress Authorizations to be authorized")
   109  	stateConf := &resource.StateChangeConf{
   110  		Pending: []string{"authorizing"},
   111  		Target:  []string{"authorized"},
   112  		Refresh: resourceAwsRedshiftSecurityGroupStateRefreshFunc(d, meta),
   113  		Timeout: 10 * time.Minute,
   114  	}
   115  
   116  	_, err = stateConf.WaitForState()
   117  	if err != nil {
   118  		return err
   119  	}
   120  
   121  	return resourceAwsRedshiftSecurityGroupRead(d, meta)
   122  }
   123  
   124  func resourceAwsRedshiftSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
   125  	sg, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta)
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	rules := &schema.Set{
   131  		F: resourceAwsRedshiftSecurityGroupIngressHash,
   132  	}
   133  
   134  	for _, v := range sg.IPRanges {
   135  		rule := map[string]interface{}{"cidr": *v.CIDRIP}
   136  		rules.Add(rule)
   137  	}
   138  
   139  	for _, g := range sg.EC2SecurityGroups {
   140  		rule := map[string]interface{}{
   141  			"security_group_name":     *g.EC2SecurityGroupName,
   142  			"security_group_owner_id": *g.EC2SecurityGroupOwnerId,
   143  		}
   144  		rules.Add(rule)
   145  	}
   146  
   147  	d.Set("ingress", rules)
   148  	d.Set("name", *sg.ClusterSecurityGroupName)
   149  	d.Set("description", *sg.Description)
   150  
   151  	return nil
   152  }
   153  
   154  func resourceAwsRedshiftSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
   155  	conn := meta.(*AWSClient).redshiftconn
   156  
   157  	log.Printf("[DEBUG] Redshift Security Group destroy: %v", d.Id())
   158  	opts := redshift.DeleteClusterSecurityGroupInput{
   159  		ClusterSecurityGroupName: aws.String(d.Id()),
   160  	}
   161  
   162  	log.Printf("[DEBUG] Redshift Security Group destroy configuration: %v", opts)
   163  	_, err := conn.DeleteClusterSecurityGroup(&opts)
   164  
   165  	if err != nil {
   166  		newerr, ok := err.(awserr.Error)
   167  		if ok && newerr.Code() == "InvalidRedshiftSecurityGroup.NotFound" {
   168  			return nil
   169  		}
   170  		return err
   171  	}
   172  
   173  	return nil
   174  }
   175  
   176  func resourceAwsRedshiftSecurityGroupRetrieve(d *schema.ResourceData, meta interface{}) (*redshift.ClusterSecurityGroup, error) {
   177  	conn := meta.(*AWSClient).redshiftconn
   178  
   179  	opts := redshift.DescribeClusterSecurityGroupsInput{
   180  		ClusterSecurityGroupName: aws.String(d.Id()),
   181  	}
   182  
   183  	log.Printf("[DEBUG] Redshift Security Group describe configuration: %#v", opts)
   184  
   185  	resp, err := conn.DescribeClusterSecurityGroups(&opts)
   186  
   187  	if err != nil {
   188  		return nil, fmt.Errorf("Error retrieving Redshift Security Groups: %s", err)
   189  	}
   190  
   191  	if len(resp.ClusterSecurityGroups) != 1 ||
   192  		*resp.ClusterSecurityGroups[0].ClusterSecurityGroupName != d.Id() {
   193  		return nil, fmt.Errorf("Unable to find Redshift Security Group: %#v", resp.ClusterSecurityGroups)
   194  	}
   195  
   196  	return resp.ClusterSecurityGroups[0], nil
   197  }
   198  
   199  func validateRedshiftSecurityGroupName(v interface{}, k string) (ws []string, errors []error) {
   200  	value := v.(string)
   201  	if value == "default" {
   202  		errors = append(errors, fmt.Errorf("the Redshift Security Group name cannot be %q", value))
   203  	}
   204  	if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) {
   205  		errors = append(errors, fmt.Errorf(
   206  			"only lowercase alphanumeric characters and hyphens allowed in %q: %q",
   207  			k, value))
   208  	}
   209  	if len(value) > 255 {
   210  		errors = append(errors, fmt.Errorf(
   211  			"%q cannot be longer than 32 characters: %q", k, value))
   212  	}
   213  	return
   214  
   215  }
   216  
   217  func resourceAwsRedshiftSecurityGroupIngressHash(v interface{}) int {
   218  	var buf bytes.Buffer
   219  	m := v.(map[string]interface{})
   220  
   221  	if v, ok := m["cidr"]; ok {
   222  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   223  	}
   224  
   225  	if v, ok := m["security_group_name"]; ok {
   226  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   227  	}
   228  
   229  	if v, ok := m["security_group_owner_id"]; ok {
   230  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   231  	}
   232  
   233  	return hashcode.String(buf.String())
   234  }
   235  
   236  func resourceAwsRedshiftSecurityGroupAuthorizeRule(ingress interface{}, redshiftSecurityGroupName string, conn *redshift.Redshift) error {
   237  	ing := ingress.(map[string]interface{})
   238  
   239  	opts := redshift.AuthorizeClusterSecurityGroupIngressInput{
   240  		ClusterSecurityGroupName: aws.String(redshiftSecurityGroupName),
   241  	}
   242  
   243  	if attr, ok := ing["cidr"]; ok && attr != "" {
   244  		opts.CIDRIP = aws.String(attr.(string))
   245  	}
   246  
   247  	if attr, ok := ing["security_group_name"]; ok && attr != "" {
   248  		opts.EC2SecurityGroupName = aws.String(attr.(string))
   249  	}
   250  
   251  	if attr, ok := ing["security_group_owner_id"]; ok && attr != "" {
   252  		opts.EC2SecurityGroupOwnerId = aws.String(attr.(string))
   253  	}
   254  
   255  	log.Printf("[DEBUG] Authorize ingress rule configuration: %#v", opts)
   256  	_, err := conn.AuthorizeClusterSecurityGroupIngress(&opts)
   257  
   258  	if err != nil {
   259  		return fmt.Errorf("Error authorizing security group ingress: %s", err)
   260  	}
   261  
   262  	return nil
   263  }
   264  
   265  func resourceAwsRedshiftSecurityGroupStateRefreshFunc(
   266  	d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
   267  	return func() (interface{}, string, error) {
   268  		v, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta)
   269  
   270  		if err != nil {
   271  			log.Printf("Error on retrieving Redshift Security Group when waiting: %s", err)
   272  			return nil, "", err
   273  		}
   274  
   275  		statuses := make([]string, 0, len(v.EC2SecurityGroups)+len(v.IPRanges))
   276  		for _, ec2g := range v.EC2SecurityGroups {
   277  			statuses = append(statuses, *ec2g.Status)
   278  		}
   279  		for _, ips := range v.IPRanges {
   280  			statuses = append(statuses, *ips.Status)
   281  		}
   282  
   283  		for _, stat := range statuses {
   284  			// Not done
   285  			if stat != "authorized" {
   286  				return nil, "authorizing", nil
   287  			}
   288  		}
   289  
   290  		return v, "authorized", nil
   291  	}
   292  }