Merge #3127: CIDR-related interpolation functions

This commit is contained in:
Martin Atkins 2015-10-22 08:13:34 -07:00
commit 47abc2c764
4 changed files with 217 additions and 0 deletions

View File

@ -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:

View File

@ -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

View File

@ -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{

View File

@ -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.