github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/aws/resource_aws_db_security_group.go (about)

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