github.com/ndarilek/terraform@v0.3.8-0.20150320140257-d3135c1b2bac/builtin/providers/aws/resource_aws_security_group.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"sort"
     8  	"time"
     9  
    10  	"github.com/hashicorp/aws-sdk-go/aws"
    11  	"github.com/hashicorp/aws-sdk-go/gen/ec2"
    12  	"github.com/hashicorp/terraform/helper/hashcode"
    13  	"github.com/hashicorp/terraform/helper/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  )
    16  
    17  func resourceAwsSecurityGroup() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceAwsSecurityGroupCreate,
    20  		Read:   resourceAwsSecurityGroupRead,
    21  		Update: resourceAwsSecurityGroupUpdate,
    22  		Delete: resourceAwsSecurityGroupDelete,
    23  
    24  		Schema: map[string]*schema.Schema{
    25  			"name": &schema.Schema{
    26  				Type:     schema.TypeString,
    27  				Required: true,
    28  				ForceNew: true,
    29  			},
    30  
    31  			"description": &schema.Schema{
    32  				Type:     schema.TypeString,
    33  				Required: true,
    34  			},
    35  
    36  			"vpc_id": &schema.Schema{
    37  				Type:     schema.TypeString,
    38  				Optional: true,
    39  				ForceNew: true,
    40  				Computed: true,
    41  			},
    42  
    43  			"ingress": &schema.Schema{
    44  				Type:     schema.TypeSet,
    45  				Optional: true,
    46  				Elem: &schema.Resource{
    47  					Schema: map[string]*schema.Schema{
    48  						"from_port": &schema.Schema{
    49  							Type:     schema.TypeInt,
    50  							Required: true,
    51  						},
    52  
    53  						"to_port": &schema.Schema{
    54  							Type:     schema.TypeInt,
    55  							Required: true,
    56  						},
    57  
    58  						"protocol": &schema.Schema{
    59  							Type:     schema.TypeString,
    60  							Required: true,
    61  						},
    62  
    63  						"cidr_blocks": &schema.Schema{
    64  							Type:     schema.TypeList,
    65  							Optional: true,
    66  							Elem:     &schema.Schema{Type: schema.TypeString},
    67  						},
    68  
    69  						"security_groups": &schema.Schema{
    70  							Type:     schema.TypeSet,
    71  							Optional: true,
    72  							Elem:     &schema.Schema{Type: schema.TypeString},
    73  							Set: func(v interface{}) int {
    74  								return hashcode.String(v.(string))
    75  							},
    76  						},
    77  
    78  						"self": &schema.Schema{
    79  							Type:     schema.TypeBool,
    80  							Optional: true,
    81  							Default:  false,
    82  						},
    83  					},
    84  				},
    85  				Set: resourceAwsSecurityGroupRuleHash,
    86  			},
    87  
    88  			"egress": &schema.Schema{
    89  				Type:     schema.TypeSet,
    90  				Optional: true,
    91  				Computed: true,
    92  				Elem: &schema.Resource{
    93  					Schema: map[string]*schema.Schema{
    94  						"from_port": &schema.Schema{
    95  							Type:     schema.TypeInt,
    96  							Required: true,
    97  						},
    98  
    99  						"to_port": &schema.Schema{
   100  							Type:     schema.TypeInt,
   101  							Required: true,
   102  						},
   103  
   104  						"protocol": &schema.Schema{
   105  							Type:     schema.TypeString,
   106  							Required: true,
   107  						},
   108  
   109  						"cidr_blocks": &schema.Schema{
   110  							Type:     schema.TypeList,
   111  							Optional: true,
   112  							Elem:     &schema.Schema{Type: schema.TypeString},
   113  						},
   114  
   115  						"security_groups": &schema.Schema{
   116  							Type:     schema.TypeSet,
   117  							Optional: true,
   118  							Elem:     &schema.Schema{Type: schema.TypeString},
   119  							Set: func(v interface{}) int {
   120  								return hashcode.String(v.(string))
   121  							},
   122  						},
   123  
   124  						"self": &schema.Schema{
   125  							Type:     schema.TypeBool,
   126  							Optional: true,
   127  							Default:  false,
   128  						},
   129  					},
   130  				},
   131  				Set: resourceAwsSecurityGroupRuleHash,
   132  			},
   133  
   134  			"owner_id": &schema.Schema{
   135  				Type:     schema.TypeString,
   136  				Computed: true,
   137  			},
   138  
   139  			"tags": tagsSchema(),
   140  		},
   141  	}
   142  }
   143  
   144  func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
   145  	ec2conn := meta.(*AWSClient).ec2conn
   146  
   147  	securityGroupOpts := &ec2.CreateSecurityGroupRequest{
   148  		GroupName: aws.String(d.Get("name").(string)),
   149  	}
   150  
   151  	if v := d.Get("vpc_id"); v != nil {
   152  		securityGroupOpts.VPCID = aws.String(v.(string))
   153  	}
   154  
   155  	if v := d.Get("description"); v != nil {
   156  		securityGroupOpts.Description = aws.String(v.(string))
   157  	}
   158  
   159  	log.Printf(
   160  		"[DEBUG] Security Group create configuration: %#v", securityGroupOpts)
   161  	createResp, err := ec2conn.CreateSecurityGroup(securityGroupOpts)
   162  	if err != nil {
   163  		return fmt.Errorf("Error creating Security Group: %s", err)
   164  	}
   165  
   166  	d.SetId(*createResp.GroupID)
   167  
   168  	log.Printf("[INFO] Security Group ID: %s", d.Id())
   169  
   170  	// Wait for the security group to truly exist
   171  	log.Printf(
   172  		"[DEBUG] Waiting for Security Group (%s) to exist",
   173  		d.Id())
   174  	stateConf := &resource.StateChangeConf{
   175  		Pending: []string{""},
   176  		Target:  "exists",
   177  		Refresh: SGStateRefreshFunc(ec2conn, d.Id()),
   178  		Timeout: 1 * time.Minute,
   179  	}
   180  	if _, err := stateConf.WaitForState(); err != nil {
   181  		return fmt.Errorf(
   182  			"Error waiting for Security Group (%s) to become available: %s",
   183  			d.Id(), err)
   184  	}
   185  
   186  	return resourceAwsSecurityGroupUpdate(d, meta)
   187  }
   188  
   189  func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
   190  	ec2conn := meta.(*AWSClient).ec2conn
   191  
   192  	sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
   193  	if err != nil {
   194  		return err
   195  	}
   196  	if sgRaw == nil {
   197  		d.SetId("")
   198  		return nil
   199  	}
   200  
   201  	sg := sgRaw.(ec2.SecurityGroup)
   202  
   203  	ingressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermissions)
   204  	egressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermissionsEgress)
   205  
   206  	d.Set("description", sg.Description)
   207  	d.Set("name", sg.GroupName)
   208  	d.Set("vpc_id", sg.VPCID)
   209  	d.Set("owner_id", sg.OwnerID)
   210  	d.Set("ingress", ingressRules)
   211  	d.Set("egress", egressRules)
   212  	d.Set("tags", tagsToMap(sg.Tags))
   213  	return nil
   214  }
   215  
   216  func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
   217  	ec2conn := meta.(*AWSClient).ec2conn
   218  
   219  	sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())()
   220  	if err != nil {
   221  		return err
   222  	}
   223  	if sgRaw == nil {
   224  		d.SetId("")
   225  		return nil
   226  	}
   227  
   228  	group := sgRaw.(ec2.SecurityGroup)
   229  
   230  	err = resourceAwsSecurityGroupUpdateRules(d, "ingress", meta, group)
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	if d.Get("vpc_id") != nil {
   236  		err = resourceAwsSecurityGroupUpdateRules(d, "egress", meta, group)
   237  		if err != nil {
   238  			return err
   239  		}
   240  	}
   241  
   242  	if err := setTags(ec2conn, d); err != nil {
   243  		return err
   244  	}
   245  
   246  	d.SetPartial("tags")
   247  
   248  	return resourceAwsSecurityGroupRead(d, meta)
   249  }
   250  
   251  func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
   252  	ec2conn := meta.(*AWSClient).ec2conn
   253  
   254  	log.Printf("[DEBUG] Security Group destroy: %v", d.Id())
   255  
   256  	return resource.Retry(5*time.Minute, func() error {
   257  		err := ec2conn.DeleteSecurityGroup(&ec2.DeleteSecurityGroupRequest{
   258  			GroupID: aws.String(d.Id()),
   259  		})
   260  		if err != nil {
   261  			ec2err, ok := err.(aws.APIError)
   262  			if !ok {
   263  				return err
   264  			}
   265  
   266  			switch ec2err.Code {
   267  			case "InvalidGroup.NotFound":
   268  				return nil
   269  			case "DependencyViolation":
   270  				// If it is a dependency violation, we want to retry
   271  				return err
   272  			default:
   273  				// Any other error, we want to quit the retry loop immediately
   274  				return resource.RetryError{Err: err}
   275  			}
   276  		}
   277  
   278  		return nil
   279  	})
   280  }
   281  
   282  func resourceAwsSecurityGroupRuleHash(v interface{}) int {
   283  	var buf bytes.Buffer
   284  	m := v.(map[string]interface{})
   285  	buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int)))
   286  	buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int)))
   287  	buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string)))
   288  	buf.WriteString(fmt.Sprintf("%t-", m["self"].(bool)))
   289  
   290  	// We need to make sure to sort the strings below so that we always
   291  	// generate the same hash code no matter what is in the set.
   292  	if v, ok := m["cidr_blocks"]; ok {
   293  		vs := v.([]interface{})
   294  		s := make([]string, len(vs))
   295  		for i, raw := range vs {
   296  			s[i] = raw.(string)
   297  		}
   298  		sort.Strings(s)
   299  
   300  		for _, v := range s {
   301  			buf.WriteString(fmt.Sprintf("%s-", v))
   302  		}
   303  	}
   304  	if v, ok := m["security_groups"]; ok {
   305  		vs := v.(*schema.Set).List()
   306  		s := make([]string, len(vs))
   307  		for i, raw := range vs {
   308  			s[i] = raw.(string)
   309  		}
   310  		sort.Strings(s)
   311  
   312  		for _, v := range s {
   313  			buf.WriteString(fmt.Sprintf("%s-", v))
   314  		}
   315  	}
   316  
   317  	return hashcode.String(buf.String())
   318  }
   319  
   320  func resourceAwsSecurityGroupIPPermGather(d *schema.ResourceData, permissions []ec2.IPPermission) []map[string]interface{} {
   321  	ruleMap := make(map[string]map[string]interface{})
   322  	for _, perm := range permissions {
   323  		var fromPort, toPort int
   324  		if v := perm.FromPort; v != nil {
   325  			fromPort = *v
   326  		}
   327  		if v := perm.ToPort; v != nil {
   328  			toPort = *v
   329  		}
   330  
   331  		k := fmt.Sprintf("%s-%d-%d", *perm.IPProtocol, fromPort, toPort)
   332  		m, ok := ruleMap[k]
   333  		if !ok {
   334  			m = make(map[string]interface{})
   335  			ruleMap[k] = m
   336  		}
   337  
   338  		m["from_port"] = fromPort
   339  		m["to_port"] = toPort
   340  		m["protocol"] = *perm.IPProtocol
   341  
   342  		if len(perm.IPRanges) > 0 {
   343  			raw, ok := m["cidr_blocks"]
   344  			if !ok {
   345  				raw = make([]string, 0, len(perm.IPRanges))
   346  			}
   347  			list := raw.([]string)
   348  
   349  			for _, ip := range perm.IPRanges {
   350  				list = append(list, *ip.CIDRIP)
   351  			}
   352  
   353  			m["cidr_blocks"] = list
   354  		}
   355  
   356  		var groups []string
   357  		if len(perm.UserIDGroupPairs) > 0 {
   358  			groups = flattenSecurityGroups(perm.UserIDGroupPairs)
   359  		}
   360  		for i, id := range groups {
   361  			if id == d.Id() {
   362  				groups[i], groups = groups[len(groups)-1], groups[:len(groups)-1]
   363  				m["self"] = true
   364  			}
   365  		}
   366  
   367  		if len(groups) > 0 {
   368  			raw, ok := m["security_groups"]
   369  			if !ok {
   370  				raw = make([]string, 0, len(groups))
   371  			}
   372  			list := raw.([]string)
   373  
   374  			list = append(list, groups...)
   375  			m["security_groups"] = list
   376  		}
   377  	}
   378  	rules := make([]map[string]interface{}, 0, len(ruleMap))
   379  	for _, m := range ruleMap {
   380  		rules = append(rules, m)
   381  	}
   382  	return rules
   383  }
   384  
   385  func resourceAwsSecurityGroupUpdateRules(
   386  	d *schema.ResourceData, ruleset string,
   387  	meta interface{}, group ec2.SecurityGroup) error {
   388  	if d.HasChange(ruleset) {
   389  		o, n := d.GetChange(ruleset)
   390  		if o == nil {
   391  			o = new(schema.Set)
   392  		}
   393  		if n == nil {
   394  			n = new(schema.Set)
   395  		}
   396  
   397  		os := o.(*schema.Set)
   398  		ns := n.(*schema.Set)
   399  
   400  		remove := expandIPPerms(group, os.Difference(ns).List())
   401  		add := expandIPPerms(group, ns.Difference(os).List())
   402  
   403  		// TODO: We need to handle partial state better in the in-between
   404  		// in this update.
   405  
   406  		// TODO: It'd be nicer to authorize before removing, but then we have
   407  		// to deal with complicated unrolling to get individual CIDR blocks
   408  		// to avoid authorizing already authorized sources. Removing before
   409  		// adding is easier here, and Terraform should be fast enough to
   410  		// not have service issues.
   411  
   412  		if len(remove) > 0 || len(add) > 0 {
   413  			ec2conn := meta.(*AWSClient).ec2conn
   414  
   415  			var err error
   416  			if len(remove) > 0 {
   417  				log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v",
   418  					group, ruleset, remove)
   419  
   420  				if ruleset == "egress" {
   421  					req := &ec2.RevokeSecurityGroupEgressRequest{
   422  						GroupID:       group.GroupID,
   423  						IPPermissions: remove,
   424  					}
   425  					err = ec2conn.RevokeSecurityGroupEgress(req)
   426  				} else {
   427  					req := &ec2.RevokeSecurityGroupIngressRequest{
   428  						GroupID:       group.GroupID,
   429  						IPPermissions: remove,
   430  					}
   431  					err = ec2conn.RevokeSecurityGroupIngress(req)
   432  				}
   433  
   434  				if err != nil {
   435  					return fmt.Errorf(
   436  						"Error authorizing security group %s rules: %s",
   437  						ruleset, err)
   438  				}
   439  			}
   440  
   441  			if len(add) > 0 {
   442  				log.Printf("[DEBUG] Authorizing security group %#v %s rule: %#v",
   443  					group, ruleset, add)
   444  				// Authorize the new rules
   445  				if ruleset == "egress" {
   446  					req := &ec2.AuthorizeSecurityGroupEgressRequest{
   447  						GroupID:       group.GroupID,
   448  						IPPermissions: add,
   449  					}
   450  					err = ec2conn.AuthorizeSecurityGroupEgress(req)
   451  				} else {
   452  					req := &ec2.AuthorizeSecurityGroupIngressRequest{
   453  						GroupID:       group.GroupID,
   454  						IPPermissions: add,
   455  					}
   456  					if group.VPCID == nil || *group.VPCID == "" {
   457  						req.GroupID = nil
   458  						req.GroupName = group.GroupName
   459  					}
   460  
   461  					err = ec2conn.AuthorizeSecurityGroupIngress(req)
   462  				}
   463  
   464  				if err != nil {
   465  					return fmt.Errorf(
   466  						"Error authorizing security group %s rules: %s",
   467  						ruleset, err)
   468  				}
   469  			}
   470  		}
   471  	}
   472  	return nil
   473  }
   474  
   475  // SGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   476  // a security group.
   477  func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   478  	return func() (interface{}, string, error) {
   479  		req := &ec2.DescribeSecurityGroupsRequest{
   480  			GroupIDs: []string{id},
   481  		}
   482  		resp, err := conn.DescribeSecurityGroups(req)
   483  		if err != nil {
   484  			if ec2err, ok := err.(aws.APIError); ok {
   485  				if ec2err.Code == "InvalidSecurityGroupID.NotFound" ||
   486  					ec2err.Code == "InvalidGroup.NotFound" {
   487  					resp = nil
   488  					err = nil
   489  				}
   490  			}
   491  
   492  			if err != nil {
   493  				log.Printf("Error on SGStateRefresh: %s", err)
   494  				return nil, "", err
   495  			}
   496  		}
   497  
   498  		if resp == nil {
   499  			return nil, "", nil
   500  		}
   501  
   502  		group := resp.SecurityGroups[0]
   503  		return group, "exists", nil
   504  	}
   505  }