github.com/subuk/terraform@v0.6.14-0.20160317140351-de1567c2e732/builtin/providers/aws/resource_aws_security_group.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"sort"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/aws/aws-sdk-go/aws"
    12  	"github.com/aws/aws-sdk-go/aws/awserr"
    13  	"github.com/aws/aws-sdk-go/service/ec2"
    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 resourceAwsSecurityGroup() *schema.Resource {
    20  	return &schema.Resource{
    21  		Create: resourceAwsSecurityGroupCreate,
    22  		Read:   resourceAwsSecurityGroupRead,
    23  		Update: resourceAwsSecurityGroupUpdate,
    24  		Delete: resourceAwsSecurityGroupDelete,
    25  
    26  		Schema: map[string]*schema.Schema{
    27  			"name": &schema.Schema{
    28  				Type:          schema.TypeString,
    29  				Optional:      true,
    30  				Computed:      true,
    31  				ForceNew:      true,
    32  				ConflictsWith: []string{"name_prefix"},
    33  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    34  					value := v.(string)
    35  					if len(value) > 255 {
    36  						errors = append(errors, fmt.Errorf(
    37  							"%q cannot be longer than 255 characters", k))
    38  					}
    39  					return
    40  				},
    41  			},
    42  
    43  			"name_prefix": &schema.Schema{
    44  				Type:     schema.TypeString,
    45  				Optional: true,
    46  				ForceNew: true,
    47  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    48  					value := v.(string)
    49  					if len(value) > 100 {
    50  						errors = append(errors, fmt.Errorf(
    51  							"%q cannot be longer than 100 characters, name is limited to 255", k))
    52  					}
    53  					return
    54  				},
    55  			},
    56  
    57  			"description": &schema.Schema{
    58  				Type:     schema.TypeString,
    59  				Optional: true,
    60  				ForceNew: true,
    61  				Default:  "Managed by Terraform",
    62  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    63  					value := v.(string)
    64  					if len(value) > 255 {
    65  						errors = append(errors, fmt.Errorf(
    66  							"%q cannot be longer than 255 characters", k))
    67  					}
    68  					return
    69  				},
    70  			},
    71  
    72  			"vpc_id": &schema.Schema{
    73  				Type:     schema.TypeString,
    74  				Optional: true,
    75  				ForceNew: true,
    76  				Computed: true,
    77  			},
    78  
    79  			"ingress": &schema.Schema{
    80  				Type:     schema.TypeSet,
    81  				Optional: true,
    82  				Computed: true,
    83  				Elem: &schema.Resource{
    84  					Schema: map[string]*schema.Schema{
    85  						"from_port": &schema.Schema{
    86  							Type:     schema.TypeInt,
    87  							Required: true,
    88  						},
    89  
    90  						"to_port": &schema.Schema{
    91  							Type:     schema.TypeInt,
    92  							Required: true,
    93  						},
    94  
    95  						"protocol": &schema.Schema{
    96  							Type:     schema.TypeString,
    97  							Required: true,
    98  						},
    99  
   100  						"cidr_blocks": &schema.Schema{
   101  							Type:     schema.TypeList,
   102  							Optional: true,
   103  							Elem:     &schema.Schema{Type: schema.TypeString},
   104  						},
   105  
   106  						"security_groups": &schema.Schema{
   107  							Type:     schema.TypeSet,
   108  							Optional: true,
   109  							Elem:     &schema.Schema{Type: schema.TypeString},
   110  							Set:      schema.HashString,
   111  						},
   112  
   113  						"self": &schema.Schema{
   114  							Type:     schema.TypeBool,
   115  							Optional: true,
   116  							Default:  false,
   117  						},
   118  					},
   119  				},
   120  				Set: resourceAwsSecurityGroupRuleHash,
   121  			},
   122  
   123  			"egress": &schema.Schema{
   124  				Type:     schema.TypeSet,
   125  				Optional: true,
   126  				Computed: true,
   127  				Elem: &schema.Resource{
   128  					Schema: map[string]*schema.Schema{
   129  						"from_port": &schema.Schema{
   130  							Type:     schema.TypeInt,
   131  							Required: true,
   132  						},
   133  
   134  						"to_port": &schema.Schema{
   135  							Type:     schema.TypeInt,
   136  							Required: true,
   137  						},
   138  
   139  						"protocol": &schema.Schema{
   140  							Type:     schema.TypeString,
   141  							Required: true,
   142  						},
   143  
   144  						"cidr_blocks": &schema.Schema{
   145  							Type:     schema.TypeList,
   146  							Optional: true,
   147  							Elem:     &schema.Schema{Type: schema.TypeString},
   148  						},
   149  
   150  						"security_groups": &schema.Schema{
   151  							Type:     schema.TypeSet,
   152  							Optional: true,
   153  							Elem:     &schema.Schema{Type: schema.TypeString},
   154  							Set:      schema.HashString,
   155  						},
   156  
   157  						"self": &schema.Schema{
   158  							Type:     schema.TypeBool,
   159  							Optional: true,
   160  							Default:  false,
   161  						},
   162  					},
   163  				},
   164  				Set: resourceAwsSecurityGroupRuleHash,
   165  			},
   166  
   167  			"owner_id": &schema.Schema{
   168  				Type:     schema.TypeString,
   169  				Computed: true,
   170  			},
   171  
   172  			"tags": tagsSchema(),
   173  		},
   174  	}
   175  }
   176  
   177  func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
   178  	conn := meta.(*AWSClient).ec2conn
   179  
   180  	securityGroupOpts := &ec2.CreateSecurityGroupInput{}
   181  
   182  	if v, ok := d.GetOk("vpc_id"); ok {
   183  		securityGroupOpts.VpcId = aws.String(v.(string))
   184  	}
   185  
   186  	if v := d.Get("description"); v != nil {
   187  		securityGroupOpts.Description = aws.String(v.(string))
   188  	}
   189  
   190  	var groupName string
   191  	if v, ok := d.GetOk("name"); ok {
   192  		groupName = v.(string)
   193  	} else if v, ok := d.GetOk("name_prefix"); ok {
   194  		groupName = resource.PrefixedUniqueId(v.(string))
   195  	} else {
   196  		groupName = resource.UniqueId()
   197  	}
   198  	securityGroupOpts.GroupName = aws.String(groupName)
   199  
   200  	var err error
   201  	log.Printf(
   202  		"[DEBUG] Security Group create configuration: %#v", securityGroupOpts)
   203  	createResp, err := conn.CreateSecurityGroup(securityGroupOpts)
   204  	if err != nil {
   205  		return fmt.Errorf("Error creating Security Group: %s", err)
   206  	}
   207  
   208  	d.SetId(*createResp.GroupId)
   209  
   210  	log.Printf("[INFO] Security Group ID: %s", d.Id())
   211  
   212  	// Wait for the security group to truly exist
   213  	log.Printf(
   214  		"[DEBUG] Waiting for Security Group (%s) to exist",
   215  		d.Id())
   216  	stateConf := &resource.StateChangeConf{
   217  		Pending: []string{""},
   218  		Target:  []string{"exists"},
   219  		Refresh: SGStateRefreshFunc(conn, d.Id()),
   220  		Timeout: 1 * time.Minute,
   221  	}
   222  
   223  	resp, err := stateConf.WaitForState()
   224  	if err != nil {
   225  		return fmt.Errorf(
   226  			"Error waiting for Security Group (%s) to become available: %s",
   227  			d.Id(), err)
   228  	}
   229  
   230  	// AWS defaults all Security Groups to have an ALLOW ALL egress rule. Here we
   231  	// revoke that rule, so users don't unknowingly have/use it.
   232  	group := resp.(*ec2.SecurityGroup)
   233  	if group.VpcId != nil && *group.VpcId != "" {
   234  		log.Printf("[DEBUG] Revoking default egress rule for Security Group for %s", d.Id())
   235  
   236  		req := &ec2.RevokeSecurityGroupEgressInput{
   237  			GroupId: createResp.GroupId,
   238  			IpPermissions: []*ec2.IpPermission{
   239  				&ec2.IpPermission{
   240  					FromPort: aws.Int64(int64(0)),
   241  					ToPort:   aws.Int64(int64(0)),
   242  					IpRanges: []*ec2.IpRange{
   243  						&ec2.IpRange{
   244  							CidrIp: aws.String("0.0.0.0/0"),
   245  						},
   246  					},
   247  					IpProtocol: aws.String("-1"),
   248  				},
   249  			},
   250  		}
   251  
   252  		if _, err = conn.RevokeSecurityGroupEgress(req); err != nil {
   253  			return fmt.Errorf(
   254  				"Error revoking default egress rule for Security Group (%s): %s",
   255  				d.Id(), err)
   256  		}
   257  
   258  	}
   259  
   260  	return resourceAwsSecurityGroupUpdate(d, meta)
   261  }
   262  
   263  func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
   264  	conn := meta.(*AWSClient).ec2conn
   265  
   266  	sgRaw, _, err := SGStateRefreshFunc(conn, d.Id())()
   267  	if err != nil {
   268  		return err
   269  	}
   270  	if sgRaw == nil {
   271  		d.SetId("")
   272  		return nil
   273  	}
   274  
   275  	sg := sgRaw.(*ec2.SecurityGroup)
   276  
   277  	remoteIngressRules := resourceAwsSecurityGroupIPPermGather(d.Id(), sg.IpPermissions, sg.OwnerId)
   278  	remoteEgressRules := resourceAwsSecurityGroupIPPermGather(d.Id(), sg.IpPermissionsEgress, sg.OwnerId)
   279  
   280  	localIngressRules := d.Get("ingress").(*schema.Set).List()
   281  	localEgressRules := d.Get("egress").(*schema.Set).List()
   282  
   283  	// Loop through the local state of rules, doing a match against the remote
   284  	// ruleSet we built above.
   285  	ingressRules := matchRules("ingress", localIngressRules, remoteIngressRules)
   286  	egressRules := matchRules("egress", localEgressRules, remoteEgressRules)
   287  
   288  	d.Set("description", sg.Description)
   289  	d.Set("name", sg.GroupName)
   290  	d.Set("vpc_id", sg.VpcId)
   291  	d.Set("owner_id", sg.OwnerId)
   292  
   293  	if err := d.Set("ingress", ingressRules); err != nil {
   294  		log.Printf("[WARN] Error setting Ingress rule set for (%s): %s", d.Id(), err)
   295  	}
   296  
   297  	if err := d.Set("egress", egressRules); err != nil {
   298  		log.Printf("[WARN] Error setting Egress rule set for (%s): %s", d.Id(), err)
   299  	}
   300  
   301  	d.Set("tags", tagsToMap(sg.Tags))
   302  	return nil
   303  }
   304  
   305  func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
   306  	conn := meta.(*AWSClient).ec2conn
   307  
   308  	sgRaw, _, err := SGStateRefreshFunc(conn, d.Id())()
   309  	if err != nil {
   310  		return err
   311  	}
   312  	if sgRaw == nil {
   313  		d.SetId("")
   314  		return nil
   315  	}
   316  
   317  	group := sgRaw.(*ec2.SecurityGroup)
   318  
   319  	err = resourceAwsSecurityGroupUpdateRules(d, "ingress", meta, group)
   320  	if err != nil {
   321  		return err
   322  	}
   323  
   324  	if d.Get("vpc_id") != nil {
   325  		err = resourceAwsSecurityGroupUpdateRules(d, "egress", meta, group)
   326  		if err != nil {
   327  			return err
   328  		}
   329  	}
   330  
   331  	if err := setTags(conn, d); err != nil {
   332  		return err
   333  	}
   334  
   335  	d.SetPartial("tags")
   336  
   337  	return resourceAwsSecurityGroupRead(d, meta)
   338  }
   339  
   340  func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
   341  	conn := meta.(*AWSClient).ec2conn
   342  
   343  	log.Printf("[DEBUG] Security Group destroy: %v", d.Id())
   344  
   345  	return resource.Retry(5*time.Minute, func() *resource.RetryError {
   346  		_, err := conn.DeleteSecurityGroup(&ec2.DeleteSecurityGroupInput{
   347  			GroupId: aws.String(d.Id()),
   348  		})
   349  		if err != nil {
   350  			ec2err, ok := err.(awserr.Error)
   351  			if !ok {
   352  				return resource.RetryableError(err)
   353  			}
   354  
   355  			switch ec2err.Code() {
   356  			case "InvalidGroup.NotFound":
   357  				return nil
   358  			case "DependencyViolation":
   359  				// If it is a dependency violation, we want to retry
   360  				return resource.RetryableError(err)
   361  			default:
   362  				// Any other error, we want to quit the retry loop immediately
   363  				return resource.NonRetryableError(err)
   364  			}
   365  		}
   366  
   367  		return nil
   368  	})
   369  }
   370  
   371  func resourceAwsSecurityGroupRuleHash(v interface{}) int {
   372  	var buf bytes.Buffer
   373  	m := v.(map[string]interface{})
   374  	buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int)))
   375  	buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int)))
   376  	buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string)))
   377  	buf.WriteString(fmt.Sprintf("%t-", m["self"].(bool)))
   378  
   379  	// We need to make sure to sort the strings below so that we always
   380  	// generate the same hash code no matter what is in the set.
   381  	if v, ok := m["cidr_blocks"]; ok {
   382  		vs := v.([]interface{})
   383  		s := make([]string, len(vs))
   384  		for i, raw := range vs {
   385  			s[i] = raw.(string)
   386  		}
   387  		sort.Strings(s)
   388  
   389  		for _, v := range s {
   390  			buf.WriteString(fmt.Sprintf("%s-", v))
   391  		}
   392  	}
   393  	if v, ok := m["security_groups"]; ok {
   394  		vs := v.(*schema.Set).List()
   395  		s := make([]string, len(vs))
   396  		for i, raw := range vs {
   397  			s[i] = raw.(string)
   398  		}
   399  		sort.Strings(s)
   400  
   401  		for _, v := range s {
   402  			buf.WriteString(fmt.Sprintf("%s-", v))
   403  		}
   404  	}
   405  
   406  	return hashcode.String(buf.String())
   407  }
   408  
   409  func resourceAwsSecurityGroupIPPermGather(groupId string, permissions []*ec2.IpPermission, ownerId *string) []map[string]interface{} {
   410  	ruleMap := make(map[string]map[string]interface{})
   411  	for _, perm := range permissions {
   412  		var fromPort, toPort int64
   413  		if v := perm.FromPort; v != nil {
   414  			fromPort = *v
   415  		}
   416  		if v := perm.ToPort; v != nil {
   417  			toPort = *v
   418  		}
   419  
   420  		k := fmt.Sprintf("%s-%d-%d", *perm.IpProtocol, fromPort, toPort)
   421  		m, ok := ruleMap[k]
   422  		if !ok {
   423  			m = make(map[string]interface{})
   424  			ruleMap[k] = m
   425  		}
   426  
   427  		m["from_port"] = fromPort
   428  		m["to_port"] = toPort
   429  		m["protocol"] = *perm.IpProtocol
   430  
   431  		if len(perm.IpRanges) > 0 {
   432  			raw, ok := m["cidr_blocks"]
   433  			if !ok {
   434  				raw = make([]string, 0, len(perm.IpRanges))
   435  			}
   436  			list := raw.([]string)
   437  
   438  			for _, ip := range perm.IpRanges {
   439  				list = append(list, *ip.CidrIp)
   440  			}
   441  
   442  			m["cidr_blocks"] = list
   443  		}
   444  
   445  		groups := flattenSecurityGroups(perm.UserIdGroupPairs, ownerId)
   446  		for i, g := range groups {
   447  			if *g.GroupId == groupId {
   448  				groups[i], groups = groups[len(groups)-1], groups[:len(groups)-1]
   449  				m["self"] = true
   450  			}
   451  		}
   452  
   453  		if len(groups) > 0 {
   454  			raw, ok := m["security_groups"]
   455  			if !ok {
   456  				raw = schema.NewSet(schema.HashString, nil)
   457  			}
   458  			list := raw.(*schema.Set)
   459  
   460  			for _, g := range groups {
   461  				if g.GroupName != nil {
   462  					list.Add(*g.GroupName)
   463  				} else {
   464  					list.Add(*g.GroupId)
   465  				}
   466  			}
   467  
   468  			m["security_groups"] = list
   469  		}
   470  	}
   471  	rules := make([]map[string]interface{}, 0, len(ruleMap))
   472  	for _, m := range ruleMap {
   473  		rules = append(rules, m)
   474  	}
   475  
   476  	return rules
   477  }
   478  
   479  func resourceAwsSecurityGroupUpdateRules(
   480  	d *schema.ResourceData, ruleset string,
   481  	meta interface{}, group *ec2.SecurityGroup) error {
   482  
   483  	if d.HasChange(ruleset) {
   484  		o, n := d.GetChange(ruleset)
   485  		if o == nil {
   486  			o = new(schema.Set)
   487  		}
   488  		if n == nil {
   489  			n = new(schema.Set)
   490  		}
   491  
   492  		os := o.(*schema.Set)
   493  		ns := n.(*schema.Set)
   494  
   495  		remove, err := expandIPPerms(group, os.Difference(ns).List())
   496  		if err != nil {
   497  			return err
   498  		}
   499  		add, err := expandIPPerms(group, ns.Difference(os).List())
   500  		if err != nil {
   501  			return err
   502  		}
   503  
   504  		// TODO: We need to handle partial state better in the in-between
   505  		// in this update.
   506  
   507  		// TODO: It'd be nicer to authorize before removing, but then we have
   508  		// to deal with complicated unrolling to get individual CIDR blocks
   509  		// to avoid authorizing already authorized sources. Removing before
   510  		// adding is easier here, and Terraform should be fast enough to
   511  		// not have service issues.
   512  
   513  		if len(remove) > 0 || len(add) > 0 {
   514  			conn := meta.(*AWSClient).ec2conn
   515  
   516  			var err error
   517  			if len(remove) > 0 {
   518  				log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v",
   519  					group, ruleset, remove)
   520  
   521  				if ruleset == "egress" {
   522  					req := &ec2.RevokeSecurityGroupEgressInput{
   523  						GroupId:       group.GroupId,
   524  						IpPermissions: remove,
   525  					}
   526  					_, err = conn.RevokeSecurityGroupEgress(req)
   527  				} else {
   528  					req := &ec2.RevokeSecurityGroupIngressInput{
   529  						GroupId:       group.GroupId,
   530  						IpPermissions: remove,
   531  					}
   532  					if group.VpcId == nil || *group.VpcId == "" {
   533  						req.GroupId = nil
   534  						req.GroupName = group.GroupName
   535  					}
   536  					_, err = conn.RevokeSecurityGroupIngress(req)
   537  				}
   538  
   539  				if err != nil {
   540  					return fmt.Errorf(
   541  						"Error revoking security group %s rules: %s",
   542  						ruleset, err)
   543  				}
   544  			}
   545  
   546  			if len(add) > 0 {
   547  				log.Printf("[DEBUG] Authorizing security group %#v %s rule: %#v",
   548  					group, ruleset, add)
   549  				// Authorize the new rules
   550  				if ruleset == "egress" {
   551  					req := &ec2.AuthorizeSecurityGroupEgressInput{
   552  						GroupId:       group.GroupId,
   553  						IpPermissions: add,
   554  					}
   555  					_, err = conn.AuthorizeSecurityGroupEgress(req)
   556  				} else {
   557  					req := &ec2.AuthorizeSecurityGroupIngressInput{
   558  						GroupId:       group.GroupId,
   559  						IpPermissions: add,
   560  					}
   561  					if group.VpcId == nil || *group.VpcId == "" {
   562  						req.GroupId = nil
   563  						req.GroupName = group.GroupName
   564  					}
   565  
   566  					_, err = conn.AuthorizeSecurityGroupIngress(req)
   567  				}
   568  
   569  				if err != nil {
   570  					return fmt.Errorf(
   571  						"Error authorizing security group %s rules: %s",
   572  						ruleset, err)
   573  				}
   574  			}
   575  		}
   576  	}
   577  	return nil
   578  }
   579  
   580  // SGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   581  // a security group.
   582  func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   583  	return func() (interface{}, string, error) {
   584  		req := &ec2.DescribeSecurityGroupsInput{
   585  			GroupIds: []*string{aws.String(id)},
   586  		}
   587  		resp, err := conn.DescribeSecurityGroups(req)
   588  		if err != nil {
   589  			if ec2err, ok := err.(awserr.Error); ok {
   590  				if ec2err.Code() == "InvalidSecurityGroupID.NotFound" ||
   591  					ec2err.Code() == "InvalidGroup.NotFound" {
   592  					resp = nil
   593  					err = nil
   594  				}
   595  			}
   596  
   597  			if err != nil {
   598  				log.Printf("Error on SGStateRefresh: %s", err)
   599  				return nil, "", err
   600  			}
   601  		}
   602  
   603  		if resp == nil {
   604  			return nil, "", nil
   605  		}
   606  
   607  		group := resp.SecurityGroups[0]
   608  		return group, "exists", nil
   609  	}
   610  }
   611  
   612  // matchRules receives the group id, type of rules, and the local / remote maps
   613  // of rules. We iterate through the local set of rules trying to find a matching
   614  // remote rule, which may be structured differently because of how AWS
   615  // aggregates the rules under the to, from, and type.
   616  //
   617  //
   618  // Matching rules are written to state, with their elements removed from the
   619  // remote set
   620  //
   621  // If no match is found, we'll write the remote rule to state and let the graph
   622  // sort things out
   623  func matchRules(rType string, local []interface{}, remote []map[string]interface{}) []map[string]interface{} {
   624  	// For each local ip or security_group, we need to match against the remote
   625  	// ruleSet until all ips or security_groups are found
   626  
   627  	// saves represents the rules that have been identified to be saved to state,
   628  	// in the appropriate d.Set("{ingress,egress}") call.
   629  	var saves []map[string]interface{}
   630  	for _, raw := range local {
   631  		l := raw.(map[string]interface{})
   632  
   633  		var selfVal bool
   634  		if v, ok := l["self"]; ok {
   635  			selfVal = v.(bool)
   636  		}
   637  
   638  		// matching against self is required to detect rules that only include self
   639  		// as the rule. resourceAwsSecurityGroupIPPermGather parses the group out
   640  		// and replaces it with self if it's ID is found
   641  		localHash := idHash(rType, l["protocol"].(string), int64(l["to_port"].(int)), int64(l["from_port"].(int)), selfVal)
   642  
   643  		// loop remote rules, looking for a matching hash
   644  		for _, r := range remote {
   645  			var remoteSelfVal bool
   646  			if v, ok := r["self"]; ok {
   647  				remoteSelfVal = v.(bool)
   648  			}
   649  
   650  			// hash this remote rule and compare it for a match consideration with the
   651  			// local rule we're examining
   652  			rHash := idHash(rType, r["protocol"].(string), r["to_port"].(int64), r["from_port"].(int64), remoteSelfVal)
   653  			if rHash == localHash {
   654  				var numExpectedCidrs, numExpectedSGs, numRemoteCidrs, numRemoteSGs int
   655  				var matchingCidrs []string
   656  				var matchingSGs []string
   657  
   658  				// grab the local/remote cidr and sg groups, capturing the expected and
   659  				// actual counts
   660  				lcRaw, ok := l["cidr_blocks"]
   661  				if ok {
   662  					numExpectedCidrs = len(l["cidr_blocks"].([]interface{}))
   663  				}
   664  				lsRaw, ok := l["security_groups"]
   665  				if ok {
   666  					numExpectedSGs = len(l["security_groups"].(*schema.Set).List())
   667  				}
   668  
   669  				rcRaw, ok := r["cidr_blocks"]
   670  				if ok {
   671  					numRemoteCidrs = len(r["cidr_blocks"].([]string))
   672  				}
   673  
   674  				rsRaw, ok := r["security_groups"]
   675  				if ok {
   676  					numRemoteSGs = len(r["security_groups"].(*schema.Set).List())
   677  				}
   678  
   679  				// check some early failures
   680  				if numExpectedCidrs > numRemoteCidrs {
   681  					log.Printf("[DEBUG] Local rule has more CIDR blocks, continuing (%d/%d)", numExpectedCidrs, numRemoteCidrs)
   682  					continue
   683  				}
   684  				if numExpectedSGs > numRemoteSGs {
   685  					log.Printf("[DEBUG] Local rule has more Security Groups, continuing (%d/%d)", numExpectedSGs, numRemoteSGs)
   686  					continue
   687  				}
   688  
   689  				// match CIDRs by converting both to sets, and using Set methods
   690  				var localCidrs []interface{}
   691  				if lcRaw != nil {
   692  					localCidrs = lcRaw.([]interface{})
   693  				}
   694  				localCidrSet := schema.NewSet(schema.HashString, localCidrs)
   695  
   696  				// remote cidrs are presented as a slice of strings, so we need to
   697  				// reformat them into a slice of interfaces to be used in creating the
   698  				// remote cidr set
   699  				var remoteCidrs []string
   700  				if rcRaw != nil {
   701  					remoteCidrs = rcRaw.([]string)
   702  				}
   703  				// convert remote cidrs to a set, for easy comparisions
   704  				var list []interface{}
   705  				for _, s := range remoteCidrs {
   706  					list = append(list, s)
   707  				}
   708  				remoteCidrSet := schema.NewSet(schema.HashString, list)
   709  
   710  				// Build up a list of local cidrs that are found in the remote set
   711  				for _, s := range localCidrSet.List() {
   712  					if remoteCidrSet.Contains(s) {
   713  						matchingCidrs = append(matchingCidrs, s.(string))
   714  					}
   715  				}
   716  
   717  				// match SGs. Both local and remote are already sets
   718  				var localSGSet *schema.Set
   719  				if lsRaw == nil {
   720  					localSGSet = schema.NewSet(schema.HashString, nil)
   721  				} else {
   722  					localSGSet = lsRaw.(*schema.Set)
   723  				}
   724  
   725  				var remoteSGSet *schema.Set
   726  				if rsRaw == nil {
   727  					remoteSGSet = schema.NewSet(schema.HashString, nil)
   728  				} else {
   729  					remoteSGSet = rsRaw.(*schema.Set)
   730  				}
   731  
   732  				// Build up a list of local security groups that are found in the remote set
   733  				for _, s := range localSGSet.List() {
   734  					if remoteSGSet.Contains(s) {
   735  						matchingSGs = append(matchingSGs, s.(string))
   736  					}
   737  				}
   738  
   739  				// compare equalities for matches.
   740  				// If we found the number of cidrs and number of sgs, we declare a
   741  				// match, and then remove those elements from the remote rule, so that
   742  				// this remote rule can still be considered by other local rules
   743  				if numExpectedCidrs == len(matchingCidrs) {
   744  					if numExpectedSGs == len(matchingSGs) {
   745  						// confirm that self references match
   746  						var lSelf bool
   747  						var rSelf bool
   748  						if _, ok := l["self"]; ok {
   749  							lSelf = l["self"].(bool)
   750  						}
   751  						if _, ok := r["self"]; ok {
   752  							rSelf = r["self"].(bool)
   753  						}
   754  						if rSelf == lSelf {
   755  							delete(r, "self")
   756  							// pop local cidrs from remote
   757  							diffCidr := remoteCidrSet.Difference(localCidrSet)
   758  							var newCidr []string
   759  							for _, cRaw := range diffCidr.List() {
   760  								newCidr = append(newCidr, cRaw.(string))
   761  							}
   762  
   763  							// reassigning
   764  							if len(newCidr) > 0 {
   765  								r["cidr_blocks"] = newCidr
   766  							} else {
   767  								delete(r, "cidr_blocks")
   768  							}
   769  
   770  							// pop local sgs from remote
   771  							diffSGs := remoteSGSet.Difference(localSGSet)
   772  							if len(diffSGs.List()) > 0 {
   773  								r["security_groups"] = diffSGs
   774  							} else {
   775  								delete(r, "security_groups")
   776  							}
   777  
   778  							saves = append(saves, l)
   779  						}
   780  					}
   781  				}
   782  			}
   783  		}
   784  	}
   785  
   786  	// Here we catch any remote rules that have not been stripped of all self,
   787  	// cidrs, and security groups. We'll add remote rules here that have not been
   788  	// matched locally, and let the graph sort things out. This will happen when
   789  	// rules are added externally to Terraform
   790  	for _, r := range remote {
   791  		var lenCidr, lenSGs int
   792  		if rCidrs, ok := r["cidr_blocks"]; ok {
   793  			lenCidr = len(rCidrs.([]string))
   794  		}
   795  
   796  		if rawSGs, ok := r["security_groups"]; ok {
   797  			lenSGs = len(rawSGs.(*schema.Set).List())
   798  		}
   799  
   800  		if _, ok := r["self"]; ok {
   801  			if r["self"].(bool) == true {
   802  				lenSGs++
   803  			}
   804  		}
   805  
   806  		if lenSGs+lenCidr > 0 {
   807  			log.Printf("[DEBUG] Found a remote Rule that wasn't empty: (%#v)", r)
   808  			saves = append(saves, r)
   809  		}
   810  	}
   811  
   812  	return saves
   813  }
   814  
   815  // Creates a unique hash for the type, ports, and protocol, used as a key in
   816  // maps
   817  func idHash(rType, protocol string, toPort, fromPort int64, self bool) string {
   818  	var buf bytes.Buffer
   819  	buf.WriteString(fmt.Sprintf("%s-", rType))
   820  	buf.WriteString(fmt.Sprintf("%d-", toPort))
   821  	buf.WriteString(fmt.Sprintf("%d-", fromPort))
   822  	buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(protocol)))
   823  	buf.WriteString(fmt.Sprintf("%t-", self))
   824  
   825  	return fmt.Sprintf("rule-%d", hashcode.String(buf.String()))
   826  }