github.com/gabrielperezs/terraform@v0.7.0-rc2.0.20160715084931-f7da2612946f/builtin/providers/aws/resource_aws_db_security_group.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/aws/aws-sdk-go/aws"
    11  	"github.com/aws/aws-sdk-go/aws/awserr"
    12  	"github.com/aws/aws-sdk-go/service/iam"
    13  	"github.com/aws/aws-sdk-go/service/rds"
    14  	"github.com/hashicorp/go-multierror"
    15  	"github.com/hashicorp/terraform/helper/hashcode"
    16  	"github.com/hashicorp/terraform/helper/resource"
    17  	"github.com/hashicorp/terraform/helper/schema"
    18  )
    19  
    20  func resourceAwsDbSecurityGroup() *schema.Resource {
    21  	return &schema.Resource{
    22  		Create: resourceAwsDbSecurityGroupCreate,
    23  		Read:   resourceAwsDbSecurityGroupRead,
    24  		Update: resourceAwsDbSecurityGroupUpdate,
    25  		Delete: resourceAwsDbSecurityGroupDelete,
    26  
    27  		Schema: map[string]*schema.Schema{
    28  			"arn": &schema.Schema{
    29  				Type:     schema.TypeString,
    30  				Computed: true,
    31  			},
    32  
    33  			"name": &schema.Schema{
    34  				Type:     schema.TypeString,
    35  				Required: true,
    36  				ForceNew: true,
    37  			},
    38  
    39  			"description": &schema.Schema{
    40  				Type:     schema.TypeString,
    41  				Optional: true,
    42  				ForceNew: true,
    43  				Default:  "Managed by Terraform",
    44  			},
    45  
    46  			"ingress": &schema.Schema{
    47  				Type:     schema.TypeSet,
    48  				Required: true,
    49  				Elem: &schema.Resource{
    50  					Schema: map[string]*schema.Schema{
    51  						"cidr": &schema.Schema{
    52  							Type:     schema.TypeString,
    53  							Optional: true,
    54  						},
    55  
    56  						"security_group_name": &schema.Schema{
    57  							Type:     schema.TypeString,
    58  							Optional: true,
    59  							Computed: true,
    60  						},
    61  
    62  						"security_group_id": &schema.Schema{
    63  							Type:     schema.TypeString,
    64  							Optional: true,
    65  							Computed: true,
    66  						},
    67  
    68  						"security_group_owner_id": &schema.Schema{
    69  							Type:     schema.TypeString,
    70  							Optional: true,
    71  							Computed: true,
    72  						},
    73  					},
    74  				},
    75  				Set: resourceAwsDbSecurityGroupIngressHash,
    76  			},
    77  
    78  			"tags": tagsSchema(),
    79  		},
    80  	}
    81  }
    82  
    83  func resourceAwsDbSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
    84  	conn := meta.(*AWSClient).rdsconn
    85  	tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{}))
    86  
    87  	var err error
    88  	var errs []error
    89  
    90  	opts := rds.CreateDBSecurityGroupInput{
    91  		DBSecurityGroupName:        aws.String(d.Get("name").(string)),
    92  		DBSecurityGroupDescription: aws.String(d.Get("description").(string)),
    93  		Tags: tags,
    94  	}
    95  
    96  	log.Printf("[DEBUG] DB Security Group create configuration: %#v", opts)
    97  	_, err = conn.CreateDBSecurityGroup(&opts)
    98  	if err != nil {
    99  		return fmt.Errorf("Error creating DB Security Group: %s", err)
   100  	}
   101  
   102  	d.SetId(d.Get("name").(string))
   103  
   104  	log.Printf("[INFO] DB Security Group ID: %s", d.Id())
   105  
   106  	sg, err := resourceAwsDbSecurityGroupRetrieve(d, meta)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	ingresses := d.Get("ingress").(*schema.Set)
   112  	for _, ing := range ingresses.List() {
   113  		err := resourceAwsDbSecurityGroupAuthorizeRule(ing, *sg.DBSecurityGroupName, conn)
   114  		if err != nil {
   115  			errs = append(errs, err)
   116  		}
   117  	}
   118  
   119  	if len(errs) > 0 {
   120  		return &multierror.Error{Errors: errs}
   121  	}
   122  
   123  	log.Println(
   124  		"[INFO] Waiting for Ingress Authorizations to be authorized")
   125  
   126  	stateConf := &resource.StateChangeConf{
   127  		Pending: []string{"authorizing"},
   128  		Target:  []string{"authorized"},
   129  		Refresh: resourceAwsDbSecurityGroupStateRefreshFunc(d, meta),
   130  		Timeout: 10 * time.Minute,
   131  	}
   132  
   133  	// Wait, catching any errors
   134  	_, err = stateConf.WaitForState()
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	return resourceAwsDbSecurityGroupRead(d, meta)
   140  }
   141  
   142  func resourceAwsDbSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
   143  	sg, err := resourceAwsDbSecurityGroupRetrieve(d, meta)
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	d.Set("name", *sg.DBSecurityGroupName)
   149  	d.Set("description", *sg.DBSecurityGroupDescription)
   150  
   151  	// Create an empty schema.Set to hold all ingress rules
   152  	rules := &schema.Set{
   153  		F: resourceAwsDbSecurityGroupIngressHash,
   154  	}
   155  
   156  	for _, v := range sg.IPRanges {
   157  		rule := map[string]interface{}{"cidr": *v.CIDRIP}
   158  		rules.Add(rule)
   159  	}
   160  
   161  	for _, g := range sg.EC2SecurityGroups {
   162  		rule := map[string]interface{}{
   163  			"security_group_name":     *g.EC2SecurityGroupName,
   164  			"security_group_id":       *g.EC2SecurityGroupId,
   165  			"security_group_owner_id": *g.EC2SecurityGroupOwnerId,
   166  		}
   167  		rules.Add(rule)
   168  	}
   169  
   170  	d.Set("ingress", rules)
   171  
   172  	conn := meta.(*AWSClient).rdsconn
   173  	arn, err := buildRDSSecurityGroupARN(d, meta)
   174  	if err != nil {
   175  		name := "<empty>"
   176  		if sg.DBSecurityGroupName != nil && *sg.DBSecurityGroupName != "" {
   177  			name = *sg.DBSecurityGroupName
   178  		}
   179  		log.Printf("[DEBUG] Error building ARN for DB Security Group, not setting Tags for DB Security Group %s", name)
   180  	} else {
   181  		d.Set("arn", arn)
   182  		resp, err := conn.ListTagsForResource(&rds.ListTagsForResourceInput{
   183  			ResourceName: aws.String(arn),
   184  		})
   185  
   186  		if err != nil {
   187  			log.Printf("[DEBUG] Error retrieving tags for ARN: %s", arn)
   188  		}
   189  
   190  		var dt []*rds.Tag
   191  		if len(resp.TagList) > 0 {
   192  			dt = resp.TagList
   193  		}
   194  		d.Set("tags", tagsToMapRDS(dt))
   195  	}
   196  
   197  	return nil
   198  }
   199  
   200  func resourceAwsDbSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
   201  	conn := meta.(*AWSClient).rdsconn
   202  
   203  	d.Partial(true)
   204  	if arn, err := buildRDSSecurityGroupARN(d, meta); err == nil {
   205  		if err := setTagsRDS(conn, d, arn); err != nil {
   206  			return err
   207  		} else {
   208  			d.SetPartial("tags")
   209  		}
   210  	}
   211  
   212  	if d.HasChange("ingress") {
   213  		sg, err := resourceAwsDbSecurityGroupRetrieve(d, meta)
   214  		if err != nil {
   215  			return err
   216  		}
   217  
   218  		oi, ni := d.GetChange("ingress")
   219  		if oi == nil {
   220  			oi = new(schema.Set)
   221  		}
   222  		if ni == nil {
   223  			ni = new(schema.Set)
   224  		}
   225  
   226  		ois := oi.(*schema.Set)
   227  		nis := ni.(*schema.Set)
   228  		removeIngress := ois.Difference(nis).List()
   229  		newIngress := nis.Difference(ois).List()
   230  
   231  		// DELETE old Ingress rules
   232  		for _, ing := range removeIngress {
   233  			err := resourceAwsDbSecurityGroupRevokeRule(ing, *sg.DBSecurityGroupName, conn)
   234  			if err != nil {
   235  				return err
   236  			}
   237  		}
   238  
   239  		// ADD new/updated Ingress rules
   240  		for _, ing := range newIngress {
   241  			err := resourceAwsDbSecurityGroupAuthorizeRule(ing, *sg.DBSecurityGroupName, conn)
   242  			if err != nil {
   243  				return err
   244  			}
   245  		}
   246  	}
   247  	d.Partial(false)
   248  
   249  	return resourceAwsDbSecurityGroupRead(d, meta)
   250  }
   251  
   252  func resourceAwsDbSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
   253  	conn := meta.(*AWSClient).rdsconn
   254  
   255  	log.Printf("[DEBUG] DB Security Group destroy: %v", d.Id())
   256  
   257  	opts := rds.DeleteDBSecurityGroupInput{DBSecurityGroupName: aws.String(d.Id())}
   258  
   259  	log.Printf("[DEBUG] DB Security Group destroy configuration: %v", opts)
   260  	_, err := conn.DeleteDBSecurityGroup(&opts)
   261  
   262  	if err != nil {
   263  		newerr, ok := err.(awserr.Error)
   264  		if ok && newerr.Code() == "InvalidDBSecurityGroup.NotFound" {
   265  			return nil
   266  		}
   267  		return err
   268  	}
   269  
   270  	return nil
   271  }
   272  
   273  func resourceAwsDbSecurityGroupRetrieve(d *schema.ResourceData, meta interface{}) (*rds.DBSecurityGroup, error) {
   274  	conn := meta.(*AWSClient).rdsconn
   275  
   276  	opts := rds.DescribeDBSecurityGroupsInput{
   277  		DBSecurityGroupName: aws.String(d.Id()),
   278  	}
   279  
   280  	log.Printf("[DEBUG] DB Security Group describe configuration: %#v", opts)
   281  
   282  	resp, err := conn.DescribeDBSecurityGroups(&opts)
   283  
   284  	if err != nil {
   285  		return nil, fmt.Errorf("Error retrieving DB Security Groups: %s", err)
   286  	}
   287  
   288  	if len(resp.DBSecurityGroups) != 1 ||
   289  		*resp.DBSecurityGroups[0].DBSecurityGroupName != d.Id() {
   290  		return nil, fmt.Errorf("Unable to find DB Security Group: %#v", resp.DBSecurityGroups)
   291  	}
   292  
   293  	return resp.DBSecurityGroups[0], nil
   294  }
   295  
   296  // Authorizes the ingress rule on the db security group
   297  func resourceAwsDbSecurityGroupAuthorizeRule(ingress interface{}, dbSecurityGroupName string, conn *rds.RDS) error {
   298  	ing := ingress.(map[string]interface{})
   299  
   300  	opts := rds.AuthorizeDBSecurityGroupIngressInput{
   301  		DBSecurityGroupName: aws.String(dbSecurityGroupName),
   302  	}
   303  
   304  	if attr, ok := ing["cidr"]; ok && attr != "" {
   305  		opts.CIDRIP = aws.String(attr.(string))
   306  	}
   307  
   308  	if attr, ok := ing["security_group_name"]; ok && attr != "" {
   309  		opts.EC2SecurityGroupName = aws.String(attr.(string))
   310  	}
   311  
   312  	if attr, ok := ing["security_group_id"]; ok && attr != "" {
   313  		opts.EC2SecurityGroupId = aws.String(attr.(string))
   314  	}
   315  
   316  	if attr, ok := ing["security_group_owner_id"]; ok && attr != "" {
   317  		opts.EC2SecurityGroupOwnerId = aws.String(attr.(string))
   318  	}
   319  
   320  	log.Printf("[DEBUG] Authorize ingress rule configuration: %#v", opts)
   321  
   322  	_, err := conn.AuthorizeDBSecurityGroupIngress(&opts)
   323  
   324  	if err != nil {
   325  		return fmt.Errorf("Error authorizing security group ingress: %s", err)
   326  	}
   327  
   328  	return nil
   329  }
   330  
   331  // Revokes the ingress rule on the db security group
   332  func resourceAwsDbSecurityGroupRevokeRule(ingress interface{}, dbSecurityGroupName string, conn *rds.RDS) error {
   333  	ing := ingress.(map[string]interface{})
   334  
   335  	opts := rds.RevokeDBSecurityGroupIngressInput{
   336  		DBSecurityGroupName: aws.String(dbSecurityGroupName),
   337  	}
   338  
   339  	if attr, ok := ing["cidr"]; ok && attr != "" {
   340  		opts.CIDRIP = aws.String(attr.(string))
   341  	}
   342  
   343  	if attr, ok := ing["security_group_name"]; ok && attr != "" {
   344  		opts.EC2SecurityGroupName = aws.String(attr.(string))
   345  	}
   346  
   347  	if attr, ok := ing["security_group_id"]; ok && attr != "" {
   348  		opts.EC2SecurityGroupId = aws.String(attr.(string))
   349  	}
   350  
   351  	if attr, ok := ing["security_group_owner_id"]; ok && attr != "" {
   352  		opts.EC2SecurityGroupOwnerId = aws.String(attr.(string))
   353  	}
   354  
   355  	log.Printf("[DEBUG] Revoking ingress rule configuration: %#v", opts)
   356  
   357  	_, err := conn.RevokeDBSecurityGroupIngress(&opts)
   358  
   359  	if err != nil {
   360  		return fmt.Errorf("Error revoking security group ingress: %s", err)
   361  	}
   362  
   363  	return nil
   364  }
   365  
   366  func resourceAwsDbSecurityGroupIngressHash(v interface{}) int {
   367  	var buf bytes.Buffer
   368  	m := v.(map[string]interface{})
   369  
   370  	if v, ok := m["cidr"]; ok {
   371  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   372  	}
   373  
   374  	if v, ok := m["security_group_name"]; ok {
   375  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   376  	}
   377  
   378  	if v, ok := m["security_group_id"]; ok {
   379  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   380  	}
   381  
   382  	if v, ok := m["security_group_owner_id"]; ok {
   383  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   384  	}
   385  
   386  	return hashcode.String(buf.String())
   387  }
   388  
   389  func resourceAwsDbSecurityGroupStateRefreshFunc(
   390  	d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
   391  	return func() (interface{}, string, error) {
   392  		v, err := resourceAwsDbSecurityGroupRetrieve(d, meta)
   393  
   394  		if err != nil {
   395  			log.Printf("Error on retrieving DB Security Group when waiting: %s", err)
   396  			return nil, "", err
   397  		}
   398  
   399  		statuses := make([]string, 0, len(v.EC2SecurityGroups)+len(v.IPRanges))
   400  		for _, ec2g := range v.EC2SecurityGroups {
   401  			statuses = append(statuses, *ec2g.Status)
   402  		}
   403  		for _, ips := range v.IPRanges {
   404  			statuses = append(statuses, *ips.Status)
   405  		}
   406  
   407  		for _, stat := range statuses {
   408  			// Not done
   409  			if stat != "authorized" {
   410  				return nil, "authorizing", nil
   411  			}
   412  		}
   413  
   414  		return v, "authorized", nil
   415  	}
   416  }
   417  
   418  func buildRDSSecurityGroupARN(d *schema.ResourceData, meta interface{}) (string, error) {
   419  	iamconn := meta.(*AWSClient).iamconn
   420  	region := meta.(*AWSClient).region
   421  	// An zero value GetUserInput{} defers to the currently logged in user
   422  	resp, err := iamconn.GetUser(&iam.GetUserInput{})
   423  	if err != nil {
   424  		return "", err
   425  	}
   426  	userARN := *resp.User.Arn
   427  	accountID := strings.Split(userARN, ":")[4]
   428  	arn := fmt.Sprintf("arn:aws:rds:%s:%s:secgrp:%s", region, accountID, d.Id())
   429  	return arn, nil
   430  }