Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

canonicalise security group rules #186

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions builtin/providers/aws/resource_aws_security_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,31 @@ func resource_aws_security_group_diff(
c *terraform.ResourceConfig,
meta interface{}) (*terraform.ResourceDiff, error) {

targets := []struct {
M map[string]interface{}
K string
}{{c.Raw, "ingress"}, {c.Config, "ingress"}}

for _, t := range targets {
ingressRules := []ec2.IPPerm{}
if v, ok := t.M[t.K]; ok {
if v, ok := v.([]map[string]interface{}); ok {
a := make([]interface{}, len(v))
for i, v := range v {
a[i] = v
}

if v, err := expandIPPerms(a); err != nil {
return nil, err
} else {
ingressRules = v
}
}
}

t.M[t.K] = flattenIPPerms(canonicaliseIPPerms(ingressRules))
}

b := &diff.ResourceBuilder{
Attrs: map[string]diff.AttrType{
"name": diff.AttrTypeCreate,
Expand Down Expand Up @@ -175,9 +200,7 @@ func resource_aws_security_group_update_state(
n["security_groups"] = flattenSecurityGroups(perm.SourceGroups)
}

// Reverse the order, as Amazon sorts it the reverse of how we created
// it.
ingressRules = append([]map[string]interface{}{n}, ingressRules...)
ingressRules = append(ingressRules, n)
}

toFlatten["ingress"] = ingressRules
Expand Down Expand Up @@ -240,6 +263,9 @@ func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
}

group := &resp.Groups[0]

group.IPPerms = canonicaliseIPPerms(group.IPPerms)

return group, "exists", nil
}
}
69 changes: 69 additions & 0 deletions builtin/providers/aws/structure.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package aws

import (
"sort"
"strconv"
"strings"

Expand Down Expand Up @@ -39,6 +40,70 @@ func expandListeners(configured []interface{}) ([]elb.Listener, error) {
return listeners, nil
}

type ipPermKey struct {
Protocol string
FromPort int
ToPort int
}

type sortableGroups []ec2.UserSecurityGroup

func (g sortableGroups) Len() int { return len(g) }
func (g sortableGroups) Swap(i, j int) { g[i], g[j] = g[j], g[i] }
func (g sortableGroups) Less(i, j int) bool { return g[i].Id < g[j].Id }

type sortableHosts []string

func (g sortableHosts) Len() int { return len(g) }
func (g sortableHosts) Swap(i, j int) { g[i], g[j] = g[j], g[i] }
func (g sortableHosts) Less(i, j int) bool { return g[i] < g[j] }

type sortableRules []ec2.IPPerm

func (s sortableRules) Len() int { return len(s) }
func (s sortableRules) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s sortableRules) Less(i, j int) bool {
return s[i].Protocol < s[j].Protocol || s[i].FromPort < s[j].FromPort || s[i].ToPort < s[j].ToPort
}

func canonicaliseIPPerms(input []ec2.IPPerm) []ec2.IPPerm {
rules := map[ipPermKey]*ec2.IPPerm{}
for _, p := range input {
key := ipPermKey{p.Protocol, p.FromPort, p.ToPort}
rule, ok := rules[key]
if !ok {
rule = &ec2.IPPerm{
Protocol: p.Protocol,
FromPort: p.FromPort,
ToPort: p.ToPort,
}

rules[key] = rule
}

if len(p.SourceGroups) > 0 {
rule.SourceGroups = append(rule.SourceGroups, p.SourceGroups...)
}

if len(p.SourceIPs) > 0 {
rule.SourceIPs = append(rule.SourceIPs, p.SourceIPs...)
}
}

res := make([]ec2.IPPerm, 0, len(rules))

for _, r := range rules {
sort.Sort(sortableHosts(r.SourceIPs))
sort.Sort(sortableGroups(r.SourceGroups))

res = append(res, *r)
}

sort.Sort(sortableRules(res))

return res
}

// Takes the result of flatmap.Expand for an array of ingress/egress
// security group rules and returns EC2 API compatible objects
func expandIPPerms(configured []interface{}) ([]ec2.IPPerm, error) {
Expand All @@ -60,6 +125,8 @@ func expandIPPerms(configured []interface{}) ([]ec2.IPPerm, error) {
return nil, err
}
p.FromPort = fromPort
} else if attr, ok := newP["from_port"].(int); ok {
p.FromPort = attr
}

if attr, ok := newP["to_port"].(string); ok {
Expand All @@ -68,6 +135,8 @@ func expandIPPerms(configured []interface{}) ([]ec2.IPPerm, error) {
return nil, err
}
p.ToPort = toPort
} else if attr, ok := newP["to_port"].(int); ok {
p.ToPort = attr
}

if attr, ok := newP["protocol"].(string); ok {
Expand Down