github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/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  		Update: resourceAwsRedshiftSecurityGroupUpdate,
    24  		Delete: resourceAwsRedshiftSecurityGroupDelete,
    25  		Importer: &schema.ResourceImporter{
    26  			State: resourceAwsRedshiftClusterImport,
    27  		},
    28  
    29  		Schema: map[string]*schema.Schema{
    30  			"name": &schema.Schema{
    31  				Type:         schema.TypeString,
    32  				Required:     true,
    33  				ForceNew:     true,
    34  				ValidateFunc: validateRedshiftSecurityGroupName,
    35  			},
    36  
    37  			"description": &schema.Schema{
    38  				Type:     schema.TypeString,
    39  				Optional: true,
    40  				ForceNew: true,
    41  				Default:  "Managed by Terraform",
    42  			},
    43  
    44  			"ingress": &schema.Schema{
    45  				Type:     schema.TypeSet,
    46  				Required: true,
    47  				Elem: &schema.Resource{
    48  					Schema: map[string]*schema.Schema{
    49  						"cidr": &schema.Schema{
    50  							Type:     schema.TypeString,
    51  							Optional: true,
    52  						},
    53  
    54  						"security_group_name": &schema.Schema{
    55  							Type:     schema.TypeString,
    56  							Optional: true,
    57  							Computed: true,
    58  						},
    59  
    60  						"security_group_owner_id": &schema.Schema{
    61  							Type:     schema.TypeString,
    62  							Optional: true,
    63  							Computed: true,
    64  						},
    65  					},
    66  				},
    67  				Set: resourceAwsRedshiftSecurityGroupIngressHash,
    68  			},
    69  		},
    70  	}
    71  }
    72  
    73  func resourceAwsRedshiftSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
    74  	conn := meta.(*AWSClient).redshiftconn
    75  
    76  	var err error
    77  	var errs []error
    78  
    79  	name := d.Get("name").(string)
    80  	desc := d.Get("description").(string)
    81  	sgInput := &redshift.CreateClusterSecurityGroupInput{
    82  		ClusterSecurityGroupName: aws.String(name),
    83  		Description:              aws.String(desc),
    84  	}
    85  	log.Printf("[DEBUG] Redshift security group create: name: %s, description: %s", name, desc)
    86  	_, err = conn.CreateClusterSecurityGroup(sgInput)
    87  	if err != nil {
    88  		return fmt.Errorf("Error creating RedshiftSecurityGroup: %s", err)
    89  	}
    90  
    91  	d.SetId(d.Get("name").(string))
    92  
    93  	log.Printf("[INFO] Redshift Security Group ID: %s", d.Id())
    94  	sg, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta)
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	ingresses := d.Get("ingress").(*schema.Set)
   100  	for _, ing := range ingresses.List() {
   101  		err := resourceAwsRedshiftSecurityGroupAuthorizeRule(ing, *sg.ClusterSecurityGroupName, conn)
   102  		if err != nil {
   103  			errs = append(errs, err)
   104  		}
   105  	}
   106  
   107  	if len(errs) > 0 {
   108  		return &multierror.Error{Errors: errs}
   109  	}
   110  
   111  	log.Println("[INFO] Waiting for Redshift Security Group Ingress Authorizations to be authorized")
   112  	stateConf := &resource.StateChangeConf{
   113  		Pending: []string{"authorizing"},
   114  		Target:  []string{"authorized"},
   115  		Refresh: resourceAwsRedshiftSecurityGroupStateRefreshFunc(d, meta),
   116  		Timeout: 10 * time.Minute,
   117  	}
   118  
   119  	_, err = stateConf.WaitForState()
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	return resourceAwsRedshiftSecurityGroupRead(d, meta)
   125  }
   126  
   127  func resourceAwsRedshiftSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
   128  	sg, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta)
   129  	if err != nil {
   130  		return err
   131  	}
   132  
   133  	rules := &schema.Set{
   134  		F: resourceAwsRedshiftSecurityGroupIngressHash,
   135  	}
   136  
   137  	for _, v := range sg.IPRanges {
   138  		rule := map[string]interface{}{"cidr": *v.CIDRIP}
   139  		rules.Add(rule)
   140  	}
   141  
   142  	for _, g := range sg.EC2SecurityGroups {
   143  		rule := map[string]interface{}{
   144  			"security_group_name":     *g.EC2SecurityGroupName,
   145  			"security_group_owner_id": *g.EC2SecurityGroupOwnerId,
   146  		}
   147  		rules.Add(rule)
   148  	}
   149  
   150  	d.Set("ingress", rules)
   151  	d.Set("name", *sg.ClusterSecurityGroupName)
   152  	d.Set("description", *sg.Description)
   153  
   154  	return nil
   155  }
   156  
   157  func resourceAwsRedshiftSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
   158  	conn := meta.(*AWSClient).redshiftconn
   159  
   160  	if d.HasChange("ingress") {
   161  		o, n := d.GetChange("ingress")
   162  		if o == nil {
   163  			o = new(schema.Set)
   164  		}
   165  		if n == nil {
   166  			n = new(schema.Set)
   167  		}
   168  
   169  		os := o.(*schema.Set)
   170  		ns := n.(*schema.Set)
   171  
   172  		removeIngressRules, err := expandRedshiftSGRevokeIngress(os.Difference(ns).List())
   173  		if err != nil {
   174  			return err
   175  		}
   176  		if len(removeIngressRules) > 0 {
   177  			for _, r := range removeIngressRules {
   178  				r.ClusterSecurityGroupName = aws.String(d.Id())
   179  
   180  				_, err := conn.RevokeClusterSecurityGroupIngress(&r)
   181  				if err != nil {
   182  					return err
   183  				}
   184  			}
   185  		}
   186  
   187  		addIngressRules, err := expandRedshiftSGAuthorizeIngress(ns.Difference(os).List())
   188  		if err != nil {
   189  			return err
   190  		}
   191  		if len(addIngressRules) > 0 {
   192  			for _, r := range addIngressRules {
   193  				r.ClusterSecurityGroupName = aws.String(d.Id())
   194  
   195  				_, err := conn.AuthorizeClusterSecurityGroupIngress(&r)
   196  				if err != nil {
   197  					return err
   198  				}
   199  			}
   200  		}
   201  
   202  	}
   203  	return resourceAwsRedshiftSecurityGroupRead(d, meta)
   204  }
   205  
   206  func resourceAwsRedshiftSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
   207  	conn := meta.(*AWSClient).redshiftconn
   208  
   209  	log.Printf("[DEBUG] Redshift Security Group destroy: %v", d.Id())
   210  	opts := redshift.DeleteClusterSecurityGroupInput{
   211  		ClusterSecurityGroupName: aws.String(d.Id()),
   212  	}
   213  
   214  	log.Printf("[DEBUG] Redshift Security Group destroy configuration: %v", opts)
   215  	_, err := conn.DeleteClusterSecurityGroup(&opts)
   216  
   217  	if err != nil {
   218  		newerr, ok := err.(awserr.Error)
   219  		if ok && newerr.Code() == "InvalidRedshiftSecurityGroup.NotFound" {
   220  			return nil
   221  		}
   222  		return err
   223  	}
   224  
   225  	return nil
   226  }
   227  
   228  func resourceAwsRedshiftSecurityGroupRetrieve(d *schema.ResourceData, meta interface{}) (*redshift.ClusterSecurityGroup, error) {
   229  	conn := meta.(*AWSClient).redshiftconn
   230  
   231  	opts := redshift.DescribeClusterSecurityGroupsInput{
   232  		ClusterSecurityGroupName: aws.String(d.Id()),
   233  	}
   234  
   235  	log.Printf("[DEBUG] Redshift Security Group describe configuration: %#v", opts)
   236  
   237  	resp, err := conn.DescribeClusterSecurityGroups(&opts)
   238  
   239  	if err != nil {
   240  		return nil, fmt.Errorf("Error retrieving Redshift Security Groups: %s", err)
   241  	}
   242  
   243  	if len(resp.ClusterSecurityGroups) != 1 ||
   244  		*resp.ClusterSecurityGroups[0].ClusterSecurityGroupName != d.Id() {
   245  		return nil, fmt.Errorf("Unable to find Redshift Security Group: %#v", resp.ClusterSecurityGroups)
   246  	}
   247  
   248  	return resp.ClusterSecurityGroups[0], nil
   249  }
   250  
   251  func validateRedshiftSecurityGroupName(v interface{}, k string) (ws []string, errors []error) {
   252  	value := v.(string)
   253  	if value == "default" {
   254  		errors = append(errors, fmt.Errorf("the Redshift Security Group name cannot be %q", value))
   255  	}
   256  	if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) {
   257  		errors = append(errors, fmt.Errorf(
   258  			"only lowercase alphanumeric characters and hyphens allowed in %q: %q",
   259  			k, value))
   260  	}
   261  	if len(value) > 255 {
   262  		errors = append(errors, fmt.Errorf(
   263  			"%q cannot be longer than 32 characters: %q", k, value))
   264  	}
   265  	return
   266  
   267  }
   268  
   269  func resourceAwsRedshiftSecurityGroupIngressHash(v interface{}) int {
   270  	var buf bytes.Buffer
   271  	m := v.(map[string]interface{})
   272  
   273  	if v, ok := m["cidr"]; ok {
   274  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   275  	}
   276  
   277  	if v, ok := m["security_group_name"]; ok {
   278  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   279  	}
   280  
   281  	if v, ok := m["security_group_owner_id"]; ok {
   282  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   283  	}
   284  
   285  	return hashcode.String(buf.String())
   286  }
   287  
   288  func resourceAwsRedshiftSecurityGroupAuthorizeRule(ingress interface{}, redshiftSecurityGroupName string, conn *redshift.Redshift) error {
   289  	ing := ingress.(map[string]interface{})
   290  
   291  	opts := redshift.AuthorizeClusterSecurityGroupIngressInput{
   292  		ClusterSecurityGroupName: aws.String(redshiftSecurityGroupName),
   293  	}
   294  
   295  	if attr, ok := ing["cidr"]; ok && attr != "" {
   296  		opts.CIDRIP = aws.String(attr.(string))
   297  	}
   298  
   299  	if attr, ok := ing["security_group_name"]; ok && attr != "" {
   300  		opts.EC2SecurityGroupName = aws.String(attr.(string))
   301  	}
   302  
   303  	if attr, ok := ing["security_group_owner_id"]; ok && attr != "" {
   304  		opts.EC2SecurityGroupOwnerId = aws.String(attr.(string))
   305  	}
   306  
   307  	log.Printf("[DEBUG] Authorize ingress rule configuration: %#v", opts)
   308  	_, err := conn.AuthorizeClusterSecurityGroupIngress(&opts)
   309  
   310  	if err != nil {
   311  		return fmt.Errorf("Error authorizing security group ingress: %s", err)
   312  	}
   313  
   314  	return nil
   315  }
   316  
   317  func resourceAwsRedshiftSecurityGroupStateRefreshFunc(
   318  	d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
   319  	return func() (interface{}, string, error) {
   320  		v, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta)
   321  
   322  		if err != nil {
   323  			log.Printf("Error on retrieving Redshift Security Group when waiting: %s", err)
   324  			return nil, "", err
   325  		}
   326  
   327  		statuses := make([]string, 0, len(v.EC2SecurityGroups)+len(v.IPRanges))
   328  		for _, ec2g := range v.EC2SecurityGroups {
   329  			statuses = append(statuses, *ec2g.Status)
   330  		}
   331  		for _, ips := range v.IPRanges {
   332  			statuses = append(statuses, *ips.Status)
   333  		}
   334  
   335  		for _, stat := range statuses {
   336  			// Not done
   337  			if stat != "authorized" {
   338  				return nil, "authorizing", nil
   339  			}
   340  		}
   341  
   342  		return v, "authorized", nil
   343  	}
   344  }
   345  
   346  func expandRedshiftSGAuthorizeIngress(configured []interface{}) ([]redshift.AuthorizeClusterSecurityGroupIngressInput, error) {
   347  	var ingress []redshift.AuthorizeClusterSecurityGroupIngressInput
   348  
   349  	// Loop over our configured parameters and create
   350  	// an array of aws-sdk-go compatible objects
   351  	for _, pRaw := range configured {
   352  		data := pRaw.(map[string]interface{})
   353  
   354  		i := redshift.AuthorizeClusterSecurityGroupIngressInput{}
   355  
   356  		if v, ok := data["cidr"]; ok {
   357  			i.CIDRIP = aws.String(v.(string))
   358  		}
   359  
   360  		if v, ok := data["security_group_name"]; ok {
   361  			i.EC2SecurityGroupName = aws.String(v.(string))
   362  		}
   363  
   364  		if v, ok := data["security_group_owner_id"]; ok {
   365  			i.EC2SecurityGroupOwnerId = aws.String(v.(string))
   366  		}
   367  
   368  		ingress = append(ingress, i)
   369  	}
   370  
   371  	return ingress, nil
   372  }
   373  
   374  func expandRedshiftSGRevokeIngress(configured []interface{}) ([]redshift.RevokeClusterSecurityGroupIngressInput, error) {
   375  	var ingress []redshift.RevokeClusterSecurityGroupIngressInput
   376  
   377  	// Loop over our configured parameters and create
   378  	// an array of aws-sdk-go compatible objects
   379  	for _, pRaw := range configured {
   380  		data := pRaw.(map[string]interface{})
   381  
   382  		i := redshift.RevokeClusterSecurityGroupIngressInput{}
   383  
   384  		if v, ok := data["cidr"]; ok {
   385  			i.CIDRIP = aws.String(v.(string))
   386  		}
   387  
   388  		if v, ok := data["security_group_name"]; ok {
   389  			i.EC2SecurityGroupName = aws.String(v.(string))
   390  		}
   391  
   392  		if v, ok := data["security_group_owner_id"]; ok {
   393  			i.EC2SecurityGroupOwnerId = aws.String(v.(string))
   394  		}
   395  
   396  		ingress = append(ingress, i)
   397  	}
   398  
   399  	return ingress, nil
   400  }