Merge #3127: CIDR-related interpolation functions
This commit is contained in:
commit
47abc2c764
|
@ -7,6 +7,7 @@ FEATURES:
|
||||||
* **New resources: `aws_codeploy_app` and `aws_codeploy_deployment_group`** [GH-2783]
|
* **New resources: `aws_codeploy_app` and `aws_codeploy_deployment_group`** [GH-2783]
|
||||||
* New remote state backend: `etcd` [GH-3487]
|
* New remote state backend: `etcd` [GH-3487]
|
||||||
* New interpolation functions: `upper` and `lower` [GH-3558]
|
* New interpolation functions: `upper` and `lower` [GH-3558]
|
||||||
|
* New interpolation functions: `cidrhost`, `cidrnetmask` and `cidrsubnet` [GH-3127]
|
||||||
|
|
||||||
BUG FIXES:
|
BUG FIXES:
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,13 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/apparentlymart/go-cidr/cidr"
|
||||||
"github.com/hashicorp/terraform/config/lang/ast"
|
"github.com/hashicorp/terraform/config/lang/ast"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
)
|
)
|
||||||
|
@ -20,6 +22,9 @@ var Funcs map[string]ast.Function
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Funcs = map[string]ast.Function{
|
Funcs = map[string]ast.Function{
|
||||||
|
"cidrhost": interpolationFuncCidrHost(),
|
||||||
|
"cidrnetmask": interpolationFuncCidrNetmask(),
|
||||||
|
"cidrsubnet": interpolationFuncCidrSubnet(),
|
||||||
"compact": interpolationFuncCompact(),
|
"compact": interpolationFuncCompact(),
|
||||||
"concat": interpolationFuncConcat(),
|
"concat": interpolationFuncConcat(),
|
||||||
"element": interpolationFuncElement(),
|
"element": interpolationFuncElement(),
|
||||||
|
@ -54,6 +59,92 @@ func interpolationFuncCompact() ast.Function {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// interpolationFuncCidrHost implements the "cidrhost" function that
|
||||||
|
// fills in the host part of a CIDR range address to create a single
|
||||||
|
// host address
|
||||||
|
func interpolationFuncCidrHost() ast.Function {
|
||||||
|
return ast.Function{
|
||||||
|
ArgTypes: []ast.Type{
|
||||||
|
ast.TypeString, // starting CIDR mask
|
||||||
|
ast.TypeInt, // host number to insert
|
||||||
|
},
|
||||||
|
ReturnType: ast.TypeString,
|
||||||
|
Variadic: false,
|
||||||
|
Callback: func(args []interface{}) (interface{}, error) {
|
||||||
|
hostNum := args[1].(int)
|
||||||
|
_, network, err := net.ParseCIDR(args[0].(string))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid CIDR expression: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err := cidr.Host(network, hostNum)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip.String(), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolationFuncCidrNetmask implements the "cidrnetmask" function
|
||||||
|
// that returns the subnet mask in IP address notation.
|
||||||
|
func interpolationFuncCidrNetmask() ast.Function {
|
||||||
|
return ast.Function{
|
||||||
|
ArgTypes: []ast.Type{
|
||||||
|
ast.TypeString, // CIDR mask
|
||||||
|
},
|
||||||
|
ReturnType: ast.TypeString,
|
||||||
|
Variadic: false,
|
||||||
|
Callback: func(args []interface{}) (interface{}, error) {
|
||||||
|
_, network, err := net.ParseCIDR(args[0].(string))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid CIDR expression: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.IP(network.Mask).String(), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolationFuncCidrSubnet implements the "cidrsubnet" function that
|
||||||
|
// adds an additional subnet of the given length onto an existing
|
||||||
|
// IP block expressed in CIDR notation.
|
||||||
|
func interpolationFuncCidrSubnet() ast.Function {
|
||||||
|
return ast.Function{
|
||||||
|
ArgTypes: []ast.Type{
|
||||||
|
ast.TypeString, // starting CIDR mask
|
||||||
|
ast.TypeInt, // number of bits to extend the prefix
|
||||||
|
ast.TypeInt, // network number to append to the prefix
|
||||||
|
},
|
||||||
|
ReturnType: ast.TypeString,
|
||||||
|
Variadic: false,
|
||||||
|
Callback: func(args []interface{}) (interface{}, error) {
|
||||||
|
extraBits := args[1].(int)
|
||||||
|
subnetNum := args[2].(int)
|
||||||
|
_, network, err := net.ParseCIDR(args[0].(string))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid CIDR expression: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 extraBits > 32 {
|
||||||
|
return nil, fmt.Errorf("may not extend prefix by more than 32 bits")
|
||||||
|
}
|
||||||
|
|
||||||
|
newNetwork, err := cidr.Subnet(network, extraBits, subnetNum)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newNetwork.String(), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// interpolationFuncConcat implements the "concat" function that
|
// interpolationFuncConcat implements the "concat" function that
|
||||||
// concatenates multiple strings. This isn't actually necessary anymore
|
// concatenates multiple strings. This isn't actually necessary anymore
|
||||||
// since our language supports string concat natively, but for backwards
|
// since our language supports string concat natively, but for backwards
|
||||||
|
|
|
@ -38,6 +38,115 @@ func TestInterpolateFuncCompact(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInterpolateFuncCidrHost(t *testing.T) {
|
||||||
|
testFunction(t, testFunctionConfig{
|
||||||
|
Cases: []testFunctionCase{
|
||||||
|
{
|
||||||
|
`${cidrhost("192.168.1.0/24", 5)}`,
|
||||||
|
"192.168.1.5",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`${cidrhost("192.168.1.0/30", 255)}`,
|
||||||
|
nil,
|
||||||
|
true, // 255 doesn't fit in two bits
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`${cidrhost("not-a-cidr", 6)}`,
|
||||||
|
nil,
|
||||||
|
true, // not a valid CIDR mask
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`${cidrhost("10.256.0.0/8", 6)}`,
|
||||||
|
nil,
|
||||||
|
true, // can't have an octet >255
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInterpolateFuncCidrNetmask(t *testing.T) {
|
||||||
|
testFunction(t, testFunctionConfig{
|
||||||
|
Cases: []testFunctionCase{
|
||||||
|
{
|
||||||
|
`${cidrnetmask("192.168.1.0/24")}`,
|
||||||
|
"255.255.255.0",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`${cidrnetmask("192.168.1.0/32")}`,
|
||||||
|
"255.255.255.255",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`${cidrnetmask("0.0.0.0/0")}`,
|
||||||
|
"0.0.0.0",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// This doesn't really make sense for IPv6 networks
|
||||||
|
// but it ought to do something sensible anyway.
|
||||||
|
`${cidrnetmask("1::/64")}`,
|
||||||
|
"ffff:ffff:ffff:ffff::",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`${cidrnetmask("not-a-cidr")}`,
|
||||||
|
nil,
|
||||||
|
true, // not a valid CIDR mask
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`${cidrnetmask("10.256.0.0/8")}`,
|
||||||
|
nil,
|
||||||
|
true, // can't have an octet >255
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInterpolateFuncCidrSubnet(t *testing.T) {
|
||||||
|
testFunction(t, testFunctionConfig{
|
||||||
|
Cases: []testFunctionCase{
|
||||||
|
{
|
||||||
|
`${cidrsubnet("192.168.2.0/20", 4, 6)}`,
|
||||||
|
"192.168.6.0/24",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`${cidrsubnet("fe80::/48", 16, 6)}`,
|
||||||
|
"fe80:0:0:6::/64",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// IPv4 address encoded in IPv6 syntax gets normalized
|
||||||
|
`${cidrsubnet("::ffff:192.168.0.0/112", 8, 6)}`,
|
||||||
|
"192.168.6.0/24",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`${cidrsubnet("192.168.0.0/30", 4, 6)}`,
|
||||||
|
nil,
|
||||||
|
true, // not enough bits left
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`${cidrsubnet("192.168.0.0/16", 2, 16)}`,
|
||||||
|
nil,
|
||||||
|
true, // can't encode 16 in 2 bits
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`${cidrsubnet("not-a-cidr", 4, 6)}`,
|
||||||
|
nil,
|
||||||
|
true, // not a valid CIDR mask
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`${cidrsubnet("10.256.0.0/8", 4, 6)}`,
|
||||||
|
nil,
|
||||||
|
true, // can't have an octet >255
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestInterpolateFuncDeprecatedConcat(t *testing.T) {
|
func TestInterpolateFuncDeprecatedConcat(t *testing.T) {
|
||||||
testFunction(t, testFunctionConfig{
|
testFunction(t, testFunctionConfig{
|
||||||
Cases: []testFunctionCase{
|
Cases: []testFunctionCase{
|
||||||
|
|
|
@ -80,6 +80,22 @@ The supported built-in functions are:
|
||||||
* `base64encode(string)` - Returns a base64-encoded representation of the
|
* `base64encode(string)` - Returns a base64-encoded representation of the
|
||||||
given string.
|
given string.
|
||||||
|
|
||||||
|
* `cidrhost(iprange, hostnum)` - Takes an IP address range in CIDR notation
|
||||||
|
and creates an IP address with the given host number. For example,
|
||||||
|
``cidrhost("10.0.0.0/8", 2)`` returns ``10.0.0.2``.
|
||||||
|
|
||||||
|
* `cidrnetmask(iprange)` - Takes an IP address range in CIDR notation
|
||||||
|
and returns the address-formatted subnet mask format that some
|
||||||
|
systems expect for IPv4 interfaces. For example,
|
||||||
|
``cidrmask("10.0.0.0/8")`` returns ``255.0.0.0``. Not applicable
|
||||||
|
to IPv6 networks since CIDR notation is the only valid notation for
|
||||||
|
IPv6.
|
||||||
|
|
||||||
|
* `cidrsubnet(iprange, newbits, netnum)` - Takes an IP address range in
|
||||||
|
CIDR notation (like ``10.0.0.0/8``) and extends its prefix to include an
|
||||||
|
additional subnet number. For example,
|
||||||
|
``cidrsubnet("10.0.0.0/8", 8, 2)`` returns ``10.2.0.0/16``.
|
||||||
|
|
||||||
* `compact(list)` - Removes empty string elements from a list. This can be
|
* `compact(list)` - Removes empty string elements from a list. This can be
|
||||||
useful in some cases, for example when passing joined lists as module
|
useful in some cases, for example when passing joined lists as module
|
||||||
variables or when parsing module outputs.
|
variables or when parsing module outputs.
|
||||||
|
|
Loading…
Reference in New Issue