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