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 }