github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/lang/funcs/cidr.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package funcs
     5  
     6  import (
     7  	"fmt"
     8  	"math/big"
     9  
    10  	"github.com/apparentlymart/go-cidr/cidr"
    11  	"github.com/terramate-io/tf/ipaddr"
    12  	"github.com/zclconf/go-cty/cty"
    13  	"github.com/zclconf/go-cty/cty/function"
    14  	"github.com/zclconf/go-cty/cty/gocty"
    15  )
    16  
    17  // CidrHostFunc contructs a function that calculates a full host IP address
    18  // within a given IP network address prefix.
    19  var CidrHostFunc = function.New(&function.Spec{
    20  	Params: []function.Parameter{
    21  		{
    22  			Name: "prefix",
    23  			Type: cty.String,
    24  		},
    25  		{
    26  			Name: "hostnum",
    27  			Type: cty.Number,
    28  		},
    29  	},
    30  	Type:         function.StaticReturnType(cty.String),
    31  	RefineResult: refineNotNull,
    32  	Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
    33  		var hostNum *big.Int
    34  		if err := gocty.FromCtyValue(args[1], &hostNum); err != nil {
    35  			return cty.UnknownVal(cty.String), err
    36  		}
    37  		_, network, err := ipaddr.ParseCIDR(args[0].AsString())
    38  		if err != nil {
    39  			return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err)
    40  		}
    41  
    42  		ip, err := cidr.HostBig(network, hostNum)
    43  		if err != nil {
    44  			return cty.UnknownVal(cty.String), err
    45  		}
    46  
    47  		return cty.StringVal(ip.String()), nil
    48  	},
    49  })
    50  
    51  // CidrNetmaskFunc contructs a function that converts an IPv4 address prefix given
    52  // in CIDR notation into a subnet mask address.
    53  var CidrNetmaskFunc = function.New(&function.Spec{
    54  	Params: []function.Parameter{
    55  		{
    56  			Name: "prefix",
    57  			Type: cty.String,
    58  		},
    59  	},
    60  	Type:         function.StaticReturnType(cty.String),
    61  	RefineResult: refineNotNull,
    62  	Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
    63  		_, network, err := ipaddr.ParseCIDR(args[0].AsString())
    64  		if err != nil {
    65  			return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err)
    66  		}
    67  
    68  		if network.IP.To4() == nil {
    69  			return cty.UnknownVal(cty.String), fmt.Errorf("IPv6 addresses cannot have a netmask: %s", args[0].AsString())
    70  		}
    71  
    72  		return cty.StringVal(ipaddr.IP(network.Mask).String()), nil
    73  	},
    74  })
    75  
    76  // CidrSubnetFunc contructs a function that calculates a subnet address within
    77  // a given IP network address prefix.
    78  var CidrSubnetFunc = function.New(&function.Spec{
    79  	Params: []function.Parameter{
    80  		{
    81  			Name: "prefix",
    82  			Type: cty.String,
    83  		},
    84  		{
    85  			Name: "newbits",
    86  			Type: cty.Number,
    87  		},
    88  		{
    89  			Name: "netnum",
    90  			Type: cty.Number,
    91  		},
    92  	},
    93  	Type:         function.StaticReturnType(cty.String),
    94  	RefineResult: refineNotNull,
    95  	Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
    96  		var newbits int
    97  		if err := gocty.FromCtyValue(args[1], &newbits); err != nil {
    98  			return cty.UnknownVal(cty.String), err
    99  		}
   100  		var netnum *big.Int
   101  		if err := gocty.FromCtyValue(args[2], &netnum); err != nil {
   102  			return cty.UnknownVal(cty.String), err
   103  		}
   104  
   105  		_, network, err := ipaddr.ParseCIDR(args[0].AsString())
   106  		if err != nil {
   107  			return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err)
   108  		}
   109  
   110  		newNetwork, err := cidr.SubnetBig(network, newbits, netnum)
   111  		if err != nil {
   112  			return cty.UnknownVal(cty.String), err
   113  		}
   114  
   115  		return cty.StringVal(newNetwork.String()), nil
   116  	},
   117  })
   118  
   119  // CidrSubnetsFunc is similar to CidrSubnetFunc but calculates many consecutive
   120  // subnet addresses at once, rather than just a single subnet extension.
   121  var CidrSubnetsFunc = function.New(&function.Spec{
   122  	Params: []function.Parameter{
   123  		{
   124  			Name: "prefix",
   125  			Type: cty.String,
   126  		},
   127  	},
   128  	VarParam: &function.Parameter{
   129  		Name: "newbits",
   130  		Type: cty.Number,
   131  	},
   132  	Type:         function.StaticReturnType(cty.List(cty.String)),
   133  	RefineResult: refineNotNull,
   134  	Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
   135  		_, network, err := ipaddr.ParseCIDR(args[0].AsString())
   136  		if err != nil {
   137  			return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "invalid CIDR expression: %s", err)
   138  		}
   139  		startPrefixLen, _ := network.Mask.Size()
   140  
   141  		prefixLengthArgs := args[1:]
   142  		if len(prefixLengthArgs) == 0 {
   143  			return cty.ListValEmpty(cty.String), nil
   144  		}
   145  
   146  		var firstLength int
   147  		if err := gocty.FromCtyValue(prefixLengthArgs[0], &firstLength); err != nil {
   148  			return cty.UnknownVal(cty.String), function.NewArgError(1, err)
   149  		}
   150  		firstLength += startPrefixLen
   151  
   152  		retVals := make([]cty.Value, len(prefixLengthArgs))
   153  
   154  		current, _ := cidr.PreviousSubnet(network, firstLength)
   155  		for i, lengthArg := range prefixLengthArgs {
   156  			var length int
   157  			if err := gocty.FromCtyValue(lengthArg, &length); err != nil {
   158  				return cty.UnknownVal(cty.String), function.NewArgError(i+1, err)
   159  			}
   160  
   161  			if length < 1 {
   162  				return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "must extend prefix by at least one bit")
   163  			}
   164  			// For portability with 32-bit systems where the subnet number
   165  			// will be a 32-bit int, we only allow extension of 32 bits in
   166  			// one call even if we're running on a 64-bit machine.
   167  			// (Of course, this is significant only for IPv6.)
   168  			if length > 32 {
   169  				return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "may not extend prefix by more than 32 bits")
   170  			}
   171  			length += startPrefixLen
   172  			if length > (len(network.IP) * 8) {
   173  				protocol := "IP"
   174  				switch len(network.IP) * 8 {
   175  				case 32:
   176  					protocol = "IPv4"
   177  				case 128:
   178  					protocol = "IPv6"
   179  				}
   180  				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)
   181  			}
   182  
   183  			next, rollover := cidr.NextSubnet(current, length)
   184  			if rollover || !network.Contains(next.IP) {
   185  				// If we run out of suffix bits in the base CIDR prefix then
   186  				// NextSubnet will start incrementing the prefix bits, which
   187  				// we don't allow because it would then allocate addresses
   188  				// outside of the caller's given prefix.
   189  				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())
   190  			}
   191  
   192  			current = next
   193  			retVals[i] = cty.StringVal(current.String())
   194  		}
   195  
   196  		return cty.ListVal(retVals), nil
   197  	},
   198  })
   199  
   200  // CidrHost calculates a full host IP address within a given IP network address prefix.
   201  func CidrHost(prefix, hostnum cty.Value) (cty.Value, error) {
   202  	return CidrHostFunc.Call([]cty.Value{prefix, hostnum})
   203  }
   204  
   205  // CidrNetmask converts an IPv4 address prefix given in CIDR notation into a subnet mask address.
   206  func CidrNetmask(prefix cty.Value) (cty.Value, error) {
   207  	return CidrNetmaskFunc.Call([]cty.Value{prefix})
   208  }
   209  
   210  // CidrSubnet calculates a subnet address within a given IP network address prefix.
   211  func CidrSubnet(prefix, newbits, netnum cty.Value) (cty.Value, error) {
   212  	return CidrSubnetFunc.Call([]cty.Value{prefix, newbits, netnum})
   213  }
   214  
   215  // CidrSubnets calculates a sequence of consecutive subnet prefixes that may
   216  // be of different prefix lengths under a common base prefix.
   217  func CidrSubnets(prefix cty.Value, newbits ...cty.Value) (cty.Value, error) {
   218  	args := make([]cty.Value, len(newbits)+1)
   219  	args[0] = prefix
   220  	copy(args[1:], newbits)
   221  	return CidrSubnetsFunc.Call(args)
   222  }