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 remote state backend: `etcd` [GH-3487]
|
||||
* New interpolation functions: `upper` and `lower` [GH-3558]
|
||||
* New interpolation functions: `cidrhost`, `cidrnetmask` and `cidrsubnet` [GH-3127]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
|
|
|
@ -6,11 +6,13 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/apparentlymart/go-cidr/cidr"
|
||||
"github.com/hashicorp/terraform/config/lang/ast"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
@ -20,6 +22,9 @@ var Funcs map[string]ast.Function
|
|||
|
||||
func init() {
|
||||
Funcs = map[string]ast.Function{
|
||||
"cidrhost": interpolationFuncCidrHost(),
|
||||
"cidrnetmask": interpolationFuncCidrNetmask(),
|
||||
"cidrsubnet": interpolationFuncCidrSubnet(),
|
||||
"compact": interpolationFuncCompact(),
|
||||
"concat": interpolationFuncConcat(),
|
||||
"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
|
||||
// concatenates multiple strings. This isn't actually necessary anymore
|
||||
// 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) {
|
||||
testFunction(t, testFunctionConfig{
|
||||
Cases: []testFunctionCase{
|
||||
|
|
|
@ -80,6 +80,22 @@ The supported built-in functions are:
|
|||
* `base64encode(string)` - Returns a base64-encoded representation of the
|
||||
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
|
||||
useful in some cases, for example when passing joined lists as module
|
||||
variables or when parsing module outputs.
|
||||
|
|
Loading…
Reference in New Issue