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