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