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

Add CIDR collapse functionality #33629

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
57 changes: 57 additions & 0 deletions internal/lang/funcs/cidr.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package funcs
import (
"fmt"
"math/big"
"sort"

"github.com/apparentlymart/go-cidr/cidr"
"github.com/hashicorp/terraform/internal/ipaddr"
Expand All @@ -14,6 +15,57 @@ import (
"github.com/zclconf/go-cty/cty/gocty"
)

// CidrCollapseFunc contructs a function that collapses input CIDRs
var CidrCollapseFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "cidrs",
Type: cty.List(cty.String),
},
},
Type: function.StaticReturnType(cty.List(cty.String)),
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
var cidrsInput []string
if err := gocty.FromCtyValue(args[0], &cidrsInput); err != nil {
return cty.UnknownVal(cty.String), err
}

// confirm proper cidr notation
cidrs := make(map[string]*ipaddr.IPNet)
for _, c := range cidrsInput {
parsedIP, parsedNet, err := ipaddr.ParseCIDR(c)
if err != nil {
return cty.UnknownVal(cty.String), err
}
cidrs[parsedIP.String()] = parsedNet
}

// collapse the cidr ranges
for iIP, iNet := range cidrs {
for jIP, jNet := range cidrs {
if iIP == jIP {
continue
}
if iNet.Contains(jNet.IP) {
delete(cidrs, jIP)
continue
}
}
}

retVals := []cty.Value{}
for _, net := range cidrs {
retVals = append(retVals, cty.StringVal(net.String()))
}
sort.Slice(retVals, func(i, j int) bool {
return i > j
})

return cty.ListVal(retVals), nil
},
})

// CidrHostFunc contructs a function that calculates a full host IP address
// within a given IP network address prefix.
var CidrHostFunc = function.New(&function.Spec{
Expand Down Expand Up @@ -197,6 +249,11 @@ var CidrSubnetsFunc = function.New(&function.Spec{
},
})

// CidrCollapse collapses input CIDRs
func CidrCollapse(cidrs cty.Value) (cty.Value, error) {
return CidrCollapseFunc.Call([]cty.Value{cidrs})
}

// CidrHost calculates a full host IP address within a given IP network address prefix.
func CidrHost(prefix, hostnum cty.Value) (cty.Value, error) {
return CidrHostFunc.Call([]cty.Value{prefix, hostnum})
Expand Down
55 changes: 55 additions & 0 deletions internal/lang/funcs/cidr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,58 @@ func TestCidrSubnets(t *testing.T) {
})
}
}

func TestCidrCollapse(t *testing.T) {
tests := []struct {
Cidrs cty.Value
Want cty.Value
Err string
}{
{
Cidrs: cty.ListVal([]cty.Value{
cty.StringVal("192.168.0.0/16"),
cty.StringVal("192.168.0.56/32"),
cty.StringVal("192.0.0.0/8"),
}),
Want: cty.ListVal([]cty.Value{
cty.StringVal("192.0.0.0/8"),
}),
Err: "",
},
{
Cidrs: cty.ListVal([]cty.Value{
cty.StringVal("192.168.0.0/16"),
cty.StringVal("167.123.0.42/32"),
cty.StringVal("167.123.0.0/16"),
}),
Want: cty.ListVal([]cty.Value{
cty.StringVal("167.123.0.0/16"),
cty.StringVal("192.168.0.0/16"),
}),
Err: "",
},
}

for _, test := range tests {
t.Run(fmt.Sprintf("cidrcollapse(%#v)", test.Cidrs), func(t *testing.T) {
got, err := CidrCollapse(test.Cidrs)
wantErr := test.Err != ""

if wantErr {
if err == nil {
t.Fatal("succeeded; want error")
}
if err.Error() != test.Err {
t.Fatalf("wrong error\ngot: %s\nwant: %s", err.Error(), test.Err)
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}

if !got.RawEquals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}
4 changes: 4 additions & 0 deletions website/data/language-nav-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,10 @@
{
"title": "<code>cidrsubnets</code>",
"href": "/language/functions/cidrsubnets"
},
{
"title": "<code>cidrcollapse</code>",
"href": "/language/functions/cidrcollapse"
}
]
},
Expand Down
26 changes: 26 additions & 0 deletions website/docs/language/functions/cidrcollapse.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
page_title: cidrcollapse - Functions - Configuration Language
description: |-
The cidrcollapse function takes a list of CIDR ranges and collapses them into a
list of strings of superset ranges
---

# `cidrcollapse` Function

`cidrcollapse` takes a list of CIDR ranges and collapses them into a list
of superset ranges.

```hcl
cidrcollapse(cidrs)
```

`cidrs` must be given as a list of valid CIDR ranges.

The result is a string list value of superset ranges.

## Examples

```
> cidrcollapse(["192.168.0.0/16", "167.123.0.42/32", "167.123.0.0/16"])
["167.123.0.0/16","192.168.0.0/16"]
```
Loading