lang/funcs: cidrsubnets function
This is a companion to cidrsubnet that allows bulk-allocation of multiple subnet addresses at once, with automatic numbering. Unlike cidrsubnet, cidrsubnets allows each of the allocations to have a different prefix length, and will pack the networks consecutively into the given address space. cidrsubnets can potentially create more complicated addressing schemes than cidrsubnet alone can, because it's able to take into account the full set of requested prefix lengths rather than just one at a time.
This commit is contained in:
parent
54661ec1df
commit
f84ab99b7d
|
@ -113,6 +113,86 @@ var CidrSubnetFunc = function.New(&function.Spec{
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// CidrSubnetsFunc is similar to CidrSubnetFunc but calculates many consecutive
|
||||||
|
// subnet addresses at once, rather than just a single subnet extension.
|
||||||
|
var CidrSubnetsFunc = function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{
|
||||||
|
{
|
||||||
|
Name: "prefix",
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
VarParam: &function.Parameter{
|
||||||
|
Name: "newbits",
|
||||||
|
Type: cty.Number,
|
||||||
|
},
|
||||||
|
Type: function.StaticReturnType(cty.List(cty.String)),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
|
_, network, err := net.ParseCIDR(args[0].AsString())
|
||||||
|
if err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "invalid CIDR expression: %s", err)
|
||||||
|
}
|
||||||
|
startPrefixLen, _ := network.Mask.Size()
|
||||||
|
|
||||||
|
prefixLengthArgs := args[1:]
|
||||||
|
if len(prefixLengthArgs) == 0 {
|
||||||
|
return cty.ListValEmpty(cty.String), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstLength int
|
||||||
|
if err := gocty.FromCtyValue(prefixLengthArgs[0], &firstLength); err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), function.NewArgError(1, err)
|
||||||
|
}
|
||||||
|
firstLength += startPrefixLen
|
||||||
|
|
||||||
|
retVals := make([]cty.Value, len(prefixLengthArgs))
|
||||||
|
|
||||||
|
current, _ := cidr.PreviousSubnet(network, firstLength)
|
||||||
|
for i, lengthArg := range prefixLengthArgs {
|
||||||
|
var length int
|
||||||
|
if err := gocty.FromCtyValue(lengthArg, &length); err != nil {
|
||||||
|
return cty.UnknownVal(cty.String), function.NewArgError(i+1, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if length < 1 {
|
||||||
|
return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "must extend prefix by at least one bit")
|
||||||
|
}
|
||||||
|
// For portability with 32-bit systems where the subnet number
|
||||||
|
// will be a 32-bit int, we only allow extension of 32 bits in
|
||||||
|
// one call even if we're running on a 64-bit machine.
|
||||||
|
// (Of course, this is significant only for IPv6.)
|
||||||
|
if length > 32 {
|
||||||
|
return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "may not extend prefix by more than 32 bits")
|
||||||
|
}
|
||||||
|
length += startPrefixLen
|
||||||
|
if length > (len(network.IP) * 8) {
|
||||||
|
protocol := "IP"
|
||||||
|
switch len(network.IP) * 8 {
|
||||||
|
case 32:
|
||||||
|
protocol = "IPv4"
|
||||||
|
case 128:
|
||||||
|
protocol = "IPv6"
|
||||||
|
}
|
||||||
|
return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "would extend prefix to %d bits, which is too long for an %s address", length, protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
next, rollover := cidr.NextSubnet(current, length)
|
||||||
|
if rollover || !network.Contains(next.IP) {
|
||||||
|
// If we run out of suffix bits in the base CIDR prefix then
|
||||||
|
// NextSubnet will start incrementing the prefix bits, which
|
||||||
|
// we don't allow because it would then allocate addresses
|
||||||
|
// outside of the caller's given prefix.
|
||||||
|
return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "not enough remaining address space for a subnet with a prefix of %d bits after %s", length, current.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
current = next
|
||||||
|
retVals[i] = cty.StringVal(current.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return cty.ListVal(retVals), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// CidrHost calculates a full host IP address within a given IP network address prefix.
|
// CidrHost calculates a full host IP address within a given IP network address prefix.
|
||||||
func CidrHost(prefix, hostnum cty.Value) (cty.Value, error) {
|
func CidrHost(prefix, hostnum cty.Value) (cty.Value, error) {
|
||||||
return CidrHostFunc.Call([]cty.Value{prefix, hostnum})
|
return CidrHostFunc.Call([]cty.Value{prefix, hostnum})
|
||||||
|
@ -127,3 +207,12 @@ func CidrNetmask(prefix cty.Value) (cty.Value, error) {
|
||||||
func CidrSubnet(prefix, newbits, netnum cty.Value) (cty.Value, error) {
|
func CidrSubnet(prefix, newbits, netnum cty.Value) (cty.Value, error) {
|
||||||
return CidrSubnetFunc.Call([]cty.Value{prefix, newbits, netnum})
|
return CidrSubnetFunc.Call([]cty.Value{prefix, newbits, netnum})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CidrSubnets calculates a sequence of consecutive subnet prefixes that may
|
||||||
|
// be of different prefix lengths under a common base prefix.
|
||||||
|
func CidrSubnets(prefix cty.Value, newbits ...cty.Value) (cty.Value, error) {
|
||||||
|
args := make([]cty.Value, len(newbits)+1)
|
||||||
|
args[0] = prefix
|
||||||
|
copy(args[1:], newbits)
|
||||||
|
return CidrSubnetsFunc.Call(args)
|
||||||
|
}
|
||||||
|
|
|
@ -214,3 +214,107 @@ func TestCidrSubnet(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func TestCidrSubnets(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Prefix cty.Value
|
||||||
|
Newbits []cty.Value
|
||||||
|
Want cty.Value
|
||||||
|
Err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
cty.StringVal("10.0.0.0/21"),
|
||||||
|
[]cty.Value{
|
||||||
|
cty.NumberIntVal(3),
|
||||||
|
cty.NumberIntVal(3),
|
||||||
|
cty.NumberIntVal(3),
|
||||||
|
cty.NumberIntVal(4),
|
||||||
|
cty.NumberIntVal(4),
|
||||||
|
cty.NumberIntVal(4),
|
||||||
|
cty.NumberIntVal(7),
|
||||||
|
cty.NumberIntVal(7),
|
||||||
|
cty.NumberIntVal(7),
|
||||||
|
},
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("10.0.0.0/24"),
|
||||||
|
cty.StringVal("10.0.1.0/24"),
|
||||||
|
cty.StringVal("10.0.2.0/24"),
|
||||||
|
cty.StringVal("10.0.3.0/25"),
|
||||||
|
cty.StringVal("10.0.3.128/25"),
|
||||||
|
cty.StringVal("10.0.4.0/25"),
|
||||||
|
cty.StringVal("10.0.4.128/28"),
|
||||||
|
cty.StringVal("10.0.4.144/28"),
|
||||||
|
cty.StringVal("10.0.4.160/28"),
|
||||||
|
}),
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("10.0.0.0/30"),
|
||||||
|
[]cty.Value{
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
cty.NumberIntVal(3),
|
||||||
|
},
|
||||||
|
cty.UnknownVal(cty.List(cty.String)),
|
||||||
|
`would extend prefix to 33 bits, which is too long for an IPv4 address`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("10.0.0.0/8"),
|
||||||
|
[]cty.Value{
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
},
|
||||||
|
cty.UnknownVal(cty.List(cty.String)),
|
||||||
|
`not enough remaining address space for a subnet with a prefix of 9 bits after 10.128.0.0/9`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("10.0.0.0/8"),
|
||||||
|
[]cty.Value{
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
cty.NumberIntVal(0),
|
||||||
|
},
|
||||||
|
cty.UnknownVal(cty.List(cty.String)),
|
||||||
|
`must extend prefix by at least one bit`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("10.0.0.0/8"),
|
||||||
|
[]cty.Value{
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
cty.NumberIntVal(-1),
|
||||||
|
},
|
||||||
|
cty.UnknownVal(cty.List(cty.String)),
|
||||||
|
`must extend prefix by at least one bit`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cty.StringVal("fe80::/48"),
|
||||||
|
[]cty.Value{
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
cty.NumberIntVal(33),
|
||||||
|
},
|
||||||
|
cty.UnknownVal(cty.List(cty.String)),
|
||||||
|
`may not extend prefix by more than 32 bits`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("cidrsubnets(%#v, %#v)", test.Prefix, test.Newbits), func(t *testing.T) {
|
||||||
|
got, err := CidrSubnets(test.Prefix, test.Newbits...)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ func (s *Scope) Functions() map[string]function.Function {
|
||||||
"cidrhost": funcs.CidrHostFunc,
|
"cidrhost": funcs.CidrHostFunc,
|
||||||
"cidrnetmask": funcs.CidrNetmaskFunc,
|
"cidrnetmask": funcs.CidrNetmaskFunc,
|
||||||
"cidrsubnet": funcs.CidrSubnetFunc,
|
"cidrsubnet": funcs.CidrSubnetFunc,
|
||||||
|
"cidrsubnets": funcs.CidrSubnetsFunc,
|
||||||
"coalesce": funcs.CoalesceFunc,
|
"coalesce": funcs.CoalesceFunc,
|
||||||
"coalescelist": funcs.CoalesceListFunc,
|
"coalescelist": funcs.CoalesceListFunc,
|
||||||
"compact": funcs.CompactFunc,
|
"compact": funcs.CompactFunc,
|
||||||
|
|
|
@ -161,6 +161,18 @@ func TestFunctions(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"cidrsubnets": {
|
||||||
|
{
|
||||||
|
`cidrsubnets("10.0.0.0/8", 8, 8, 16, 8)`,
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("10.0.0.0/16"),
|
||||||
|
cty.StringVal("10.1.0.0/16"),
|
||||||
|
cty.StringVal("10.2.0.0/24"),
|
||||||
|
cty.StringVal("10.3.0.0/16"),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
"coalesce": {
|
"coalesce": {
|
||||||
{
|
{
|
||||||
`coalesce("first", "second", "third")`,
|
`coalesce("first", "second", "third")`,
|
||||||
|
|
|
@ -33,6 +33,11 @@ additional bits added to the prefix.
|
||||||
This function accepts both IPv6 and IPv4 prefixes, and the result always uses
|
This function accepts both IPv6 and IPv4 prefixes, and the result always uses
|
||||||
the same addressing scheme as the given prefix.
|
the same addressing scheme as the given prefix.
|
||||||
|
|
||||||
|
Unlike the related function [`cidrsubnets`](./cidrsubnets.html), `cidrsubnet`
|
||||||
|
allows you to give a specific network number to use. `cidrsubnets` can allocate
|
||||||
|
multiple network addresses at once, but numbers them automatically starting
|
||||||
|
with zero.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -163,3 +168,5 @@ For more information on CIDR notation and subnetting, see
|
||||||
within a given network address prefix.
|
within a given network address prefix.
|
||||||
* [`cidrnetmask`](./cidrnetmask.html) converts an IPv4 network prefix in CIDR
|
* [`cidrnetmask`](./cidrnetmask.html) converts an IPv4 network prefix in CIDR
|
||||||
notation into netmask notation.
|
notation into netmask notation.
|
||||||
|
* [`cidrsubnets`](./cidrsubnets.html) can allocate multiple consecutive
|
||||||
|
addresses under a prefix at once, numbering them automatically.
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
---
|
||||||
|
layout: "functions"
|
||||||
|
page_title: "cidrsubnets - Functions - Configuration Language"
|
||||||
|
sidebar_current: "docs-funcs-ipnet-cidrsubnets"
|
||||||
|
description: |-
|
||||||
|
The cidrsubnets function calculates a sequence of consecutive IP address
|
||||||
|
ranges within a particular CIDR prefix.
|
||||||
|
---
|
||||||
|
|
||||||
|
# `cidrsubnet` Function
|
||||||
|
|
||||||
|
-> **Note:** This page is about Terraform 0.12 and later. For Terraform 0.11 and
|
||||||
|
earlier, see
|
||||||
|
[0.11 Configuration Language: Interpolation Syntax](../../configuration-0-11/interpolation.html).
|
||||||
|
|
||||||
|
`cidrsubnet` calculates a sequence of consecutive IP address ranges within
|
||||||
|
a particular CIDR prefix.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
cidrsubnet(prefix, newbits...)
|
||||||
|
```
|
||||||
|
|
||||||
|
`prefix` must be given in CIDR notation, as defined in
|
||||||
|
[RFC 4632 section 3.1](https://tools.ietf.org/html/rfc4632#section-3.1).
|
||||||
|
|
||||||
|
The remaining arguments, indicated as `newbits` above, each specify the number
|
||||||
|
of additional network prefix bits for one returned address range. The return
|
||||||
|
value is therefore a list with one element per `newbits` argument, each
|
||||||
|
a string containing an address range in CIDR notation.
|
||||||
|
|
||||||
|
For more information on IP addressing concepts, see the documentation for the
|
||||||
|
related function [`cidrsubnet`](./cidrsubnet.html). `cidrsubnet` calculates
|
||||||
|
a single subnet address within a prefix while allowing you to specify its
|
||||||
|
subnet number, while `cidrsubnets` can calculate many at once, potentially of
|
||||||
|
different sizes, and assigns subnet numbers automatically.
|
||||||
|
|
||||||
|
When using this function to partition an address space as part of a network
|
||||||
|
address plan, you must not change any of the existing arguments once network
|
||||||
|
addresses have been assigned to real infrastructure, or else later address
|
||||||
|
assignments will be invalidated. However, you _can_ append new arguments to
|
||||||
|
existing calls safely, as long as there is sufficient address space available.
|
||||||
|
|
||||||
|
This function accepts both IPv6 and IPv4 prefixes, and the result always uses
|
||||||
|
the same addressing scheme as the given prefix.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
> cidrsubnets("10.1.0.0/16", 4, 4, 8, 4)
|
||||||
|
[
|
||||||
|
"10.1.0.0/20",
|
||||||
|
"10.1.16.0/20",
|
||||||
|
"10.1.32.0/24",
|
||||||
|
"10.1.48.0/20",
|
||||||
|
]
|
||||||
|
|
||||||
|
> cidrsubnets("fd00:fd12:3456:7890::/56", 16, 16, 16, 32)
|
||||||
|
[
|
||||||
|
"fd00:fd12:3456:7800::/72",
|
||||||
|
"fd00:fd12:3456:7800:100::/72",
|
||||||
|
"fd00:fd12:3456:7800:200::/72",
|
||||||
|
"fd00:fd12:3456:7800:300::/88",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use nested `cidrsubnets` calls with
|
||||||
|
[`for` expressions](/docs/configuration/expressions.html#for-expressions)
|
||||||
|
to concisely allocate groups of network address blocks:
|
||||||
|
|
||||||
|
```
|
||||||
|
> [for cidr_block in cidrsubnets("10.0.0.0/8", 8, 8, 8, 8) : cidrsubnets(cidr_block, 4, 4)]
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"10.0.0.0/20",
|
||||||
|
"10.0.16.0/20",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"10.1.0.0/20",
|
||||||
|
"10.1.16.0/20",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"10.2.0.0/20",
|
||||||
|
"10.2.16.0/20",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"10.3.0.0/20",
|
||||||
|
"10.3.16.0/20",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related Functions
|
||||||
|
|
||||||
|
* [`cidrhost`](./cidrhost.html) calculates the IP address for a single host
|
||||||
|
within a given network address prefix.
|
||||||
|
* [`cidrnetmask`](./cidrnetmask.html) converts an IPv4 network prefix in CIDR
|
||||||
|
notation into netmask notation.
|
||||||
|
* [`cidrsubnet`](./cidrsubnet.html) calculates a single subnet address, allowing
|
||||||
|
you to specify its network number.
|
Loading…
Reference in New Issue