update... work pending

This commit is contained in:
brent saner
2025-02-04 12:14:08 -05:00
parent 3b4d712722
commit 3c984a0636
39 changed files with 2122 additions and 597 deletions

79
tplCmd/consts.go Normal file
View File

@@ -0,0 +1,79 @@
package tplCmd
import (
`context`
`net`
`net/netip`
`text/template`
`go4.org/netipx`
)
var (
// Funcs added externally to CombinedTplFuncMap will override any of the funcs defined here or in sprig.
CombinedTplFuncMap template.FuncMap = make(template.FuncMap)
// TODO: github.com/vishvananda/netlink funcs?
TplFuncs = template.FuncMap{
"GetCtx": context.Background,
// stdlib net funcs; everything missing you can get from a net.Resolver (see GetResolver/TplGetResolver)
"CIDRMask": net.CIDRMask,
"InterfaceAddrs": net.InterfaceAddrs,
"InterfaceByIndex": net.InterfaceByIndex,
"InterfaceByName": net.InterfaceByName,
"IPv4": net.IPv4,
"IPv4Mask": net.IPv4Mask,
"JoinHostPort": net.JoinHostPort,
"ParseIP": net.ParseIP,
"ParseMAC": net.ParseMAC,
"ResolveIPAddr": net.ResolveIPAddr,
"ResolveTCPAddr": net.ResolveTCPAddr,
"ResolveUDPAddr": net.ResolveUDPAddr,
"ResolveUnixAddr": net.ResolveUnixAddr,
"TCPAddrFromAddrPort": net.TCPAddrFromAddrPort,
"UDPAddrFromAddrPort": net.UDPAddrFromAddrPort,
// stdlib net/netip funcs
"AddrFrom16": netip.AddrFrom16,
"AddrFrom4": netip.AddrFrom4,
"AddrFromSlice": netip.AddrFromSlice,
"AddrPortFrom": netip.AddrPortFrom,
"IPv4Unspecified": netip.IPv4Unspecified,
"IPv6LinkLocalAllNodes": netip.IPv6LinkLocalAllNodes,
"IPv6LinkLocalAllRouters": netip.IPv6LinkLocalAllRouters,
"IPv6Loopback": netip.IPv6Loopback,
"IPv6Unspecified": netip.IPv6Unspecified,
"ParseAddr": netip.ParseAddr,
"ParseAddrPort": netip.ParseAddrPort,
"ParsePrefix": netip.ParsePrefix,
"PrefixFrom": netip.PrefixFrom,
// go4.org/netipx
"AddrIPNet": netipx.AddrIPNet,
"ComparePrefix": netipx.ComparePrefix,
"FromStdAddr": netipx.FromStdAddr,
"FromStdIP": netipx.FromStdIP,
"FromStdIPNet": netipx.FromStdIPNet,
"IPRangeFrom": netipx.IPRangeFrom,
"ParseIPRange": netipx.ParseIPRange,
"ParsePrefixOrAddr": netipx.ParsePrefixOrAddr,
"PrefixIPNet": netipx.PrefixIPNet,
"PrefixLastIP": netipx.PrefixLastIP,
"RangeOfPrefix": netipx.RangeOfPrefix,
// Custom-defined/compat wrappers
"GetIPSetBuilder": TplGetIPSetBuilder,
"GetResolver": TplGetResolver,
"SplitHostPortHost": TplSplitHostPortHost, // net.SplitHostPort
"SplitHostPortPort": TplSplitHostPortPort, // net.SplitHostPort
"ToCidrHost": TplToCidrHost, // net.ParseCIDR
"ToCidrNet": TplToCidrNet, // net.ParseCIDR
// Weak coercers and other funcs.
"IsNil": TplIsNil,
"ToBool": TplToBool,
"ToFloat": TplToFloat,
"ToInt": TplToInt,
"ToMap": TplToMap,
"ToString": TplToString,
"ToUint": TplToUint,
// Host information
"GetDefaultIface": GetDefaultIface,
"GetSITIface": GetSITIface,
}
)

20
tplCmd/funcs.go Normal file
View File

@@ -0,0 +1,20 @@
package tplCmd
import (
`text/template`
`github.com/Masterminds/sprig/v3`
)
// GetTpl returns a generic text/template.Template with the customization/FuncMap logic applied.
func GetTpl() (tpl *template.Template) {
tpl = template.New("")
tpl.Funcs(sprig.TxtFuncMap()).Funcs(TplFuncs)
if CombinedTplFuncMap != nil && len(CombinedTplFuncMap) > 0 {
tpl.Funcs(CombinedTplFuncMap)
}
return
}

View File

@@ -7,7 +7,10 @@ import (
`text/template`
)
// ToCmd returns an (os/)exec.Cmd from a TemplateCmd. t should be a tunnelbroker.FetchedTunnel, generally.
/*
ToCmd returns an (os/)exec.Cmd from a TemplateCmd.
t should be a runner.TunnelResult.
*/
func (c *TemplateCmd) ToCmd(t any) (cmd *exec.Cmd, err error) {
var progName string
@@ -16,8 +19,15 @@ func (c *TemplateCmd) ToCmd(t any) (cmd *exec.Cmd, err error) {
var args []string
var buf *bytes.Buffer = new(bytes.Buffer)
if !c.IsTemplate {
cmd, err = c.Cmd.ToCmd()
return
}
buf.Reset()
if tpl, err = template.New("").Parse(c.Program); err != nil {
tpl = GetTpl()
if _, err = tpl.Parse(c.Program); err != nil {
return
}
if err = tpl.Execute(buf, t); err != nil {
@@ -29,7 +39,8 @@ func (c *TemplateCmd) ToCmd(t any) (cmd *exec.Cmd, err error) {
args = make([]string, len(c.Args))
for idx, arg := range c.Args {
buf.Reset()
if tpl, err = template.New("").Parse(arg); err != nil {
tpl = GetTpl()
if _, err = tpl.Parse(arg); err != nil {
return
}
if err = tpl.Execute(buf, t); err != nil {
@@ -44,7 +55,8 @@ func (c *TemplateCmd) ToCmd(t any) (cmd *exec.Cmd, err error) {
envs = make([]string, len(c.EnvVars))
for idx, env := range c.EnvVars {
buf.Reset()
if tpl, err = template.New("").Parse(env); err != nil {
tpl = GetTpl()
if _, err = tpl.Parse(env); err != nil {
return
}
if err = tpl.Execute(buf, t); err != nil {

310
tplCmd/funcs_tpl.go Normal file
View File

@@ -0,0 +1,310 @@
package tplCmd
import (
`fmt`
`net`
`strconv`
`strings`
`github.com/vishvananda/netlink`
`go4.org/netipx`
)
/*
This file contains functions strictly for use in templates.
*/
// Host functionality
// TODO: How would I do this on non-Linux?
/*
GetDefaultIface returns the interface name for the default route using netlink.
IPv4 by default, IPv6 if ipv6 is true.
If multiple routes match the default route for the inet family,
the lowest metric route's interface will be returned.
*/
func GetDefaultIface(ipv6 bool) (iface string, err error) {
var inetFamily int
var defNet *net.IPNet
var routes []netlink.Route
var defRt *netlink.Route
var defIface *net.Interface
var curPrio int = -1
// This can even be netlink.FAMILY_ALL, but that's silly.
if !ipv6 {
inetFamily = netlink.FAMILY_V4
defNet = &net.IPNet{
IP: net.ParseIP("0.0.0.0"),
Mask: net.CIDRMask(0, 32),
}
} else {
inetFamily = netlink.FAMILY_V6
defNet = &net.IPNet{
IP: net.ParseIP("::"),
Mask: net.CIDRMask(0, 128),
}
}
if routes, err = netlink.RouteList(nil, inetFamily); err != nil {
return
}
for _, route := range routes {
if !(route.Dst != nil || route.Dst.String() == defNet.String()) {
continue
}
if curPrio == -1 {
curPrio = route.Priority
defRt = &route
} else {
if route.Priority < curPrio {
curPrio = route.Priority
defRt = &route
}
}
}
if defRt != nil {
// There's also defRt.ILinkIndex, which is used for VLANs, tunnels, etc.
if defIface, err = net.InterfaceByIndex(defRt.LinkIndex); err != nil {
return
}
if defIface != nil {
iface = defIface.Name
return
}
}
return
}
func GetSITIface() () {
return
}
// Conversions/assertions/coercions.
/*
TplIsNil returns true if v is nil. This lets you determine if e.g. a map or slice is empty or nil.
It currently only really works for a []interface{}, map[interface{}]interface{}, or map[string]interface{}.
*/
func TplIsNil(v interface{}) (isNil bool) {
switch t := v.(type) {
case []interface{}:
isNil = t == nil
case map[interface{}]interface{}:
isNil = t == nil
case map[string]interface{}:
isNil = t == nil
case nil:
isNil = true
}
return
}
// TplToBool attempts to determine a boolean from v. Note that for numbers, b is true if v is *NOT* 0.
func TplToBool(v interface{}) (b bool, err error) {
switch t := v.(type) {
case bool:
b = t
case string:
switch s := strings.ToLower(t); s {
case "true", "y", "yes", "on", "1":
b = true
}
case []byte:
switch s := strings.ToLower(string(t)); s {
case "true", "y", "yes", "on", "1":
b = true
}
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
b = t != 0
}
return
}
/*
TplToFloat returns a float64 from v.
Strings will be run through strconv.ParseFloat with 64 bitness.
Mind overflows.
*/
func TplToFloat(v interface{}) (i float64, err error) {
switch t := v.(type) {
case uint, uint8, uint16, uint32, uint64:
i = float64(t.(uint64))
case int, int8, int16, int32, int64:
i = float64(t.(int64))
case float64:
i = t
case float32:
i = float64(t)
case string:
if i, err = strconv.ParseFloat(t, 64); err != nil {
return
}
case []byte:
if i, err = strconv.ParseFloat(string(t), 64); err != nil {
return
}
}
return
}
/*
TplToInt returns a signed 64-bit integer from primitives.
Strings will be run through strconv.ParseInt with base 10 and 64 bitness.
Mind overflows.
*/
func TplToInt(v interface{}) (i int64, err error) {
switch t := v.(type) {
case uint, uint8, uint16, uint32, uint64:
i = int64(t.(uint64)) // If an overflow happens anywhere, it's either here or the string/[]bytes.
case int64:
i = t
case int, int8, int16, int32:
i = t.(int64)
case float32, float64:
i = int64(t.(float64))
case string:
if i, err = strconv.ParseInt(t, 10, 64); err != nil {
return
}
case []byte:
if i, err = strconv.ParseInt(string(t), 10, 64); err != nil {
return
}
}
return
}
/*
TplToMap attempts to return a map[string]interface{} from v.
Values can then be used with other TplTo* functions further.
m will be nil if it can't be asserted.
*/
func TplToMap(v interface{}) (m map[string]interface{}, err error) {
switch t := v.(type) {
case map[string]interface{}:
m = t
}
return
}
// TplToString returns a string representation of primitives.
func TplToString(v interface{}) (s string, err error) {
switch t := v.(type) {
case string:
s = t
case []byte:
s = string(t)
default:
s = fmt.Sprintf("%v", v)
}
return
}
/*
TplToUint returns an unsigned 64-bit integer from primitives.
Strings will be run through strconv.ParseUint with base 10 and 64 bitness.
Mind overflows.
*/
func TplToUint(v interface{}) (i uint64, err error) {
switch t := v.(type) {
case uint64:
i = t
case uint, uint8, uint16, uint32:
i = t.(uint64)
case int, int8, int16, int32, int64:
i = uint64(t.(int64))
case float32, float64:
i = uint64(t.(float64))
case string:
if i, err = strconv.ParseUint(t, 10, 64); err != nil {
return
}
case []byte:
if i, err = strconv.ParseUint(string(t), 10, 64); err != nil {
return
}
}
return
}
// Wrappers
// TplGetIPSetBuilder returns a netipx.IPSetBuilder.
func TplGetIPSetBuilder() (ipsb *netipx.IPSetBuilder) {
ipsb = new(netipx.IPSetBuilder)
*ipsb = netipx.IPSetBuilder{}
return
}
// TplGetResolver returns a net.Resolver from the given options.
func TplGetResolver(useGo, strictErr bool) (resolver *net.Resolver) {
resolver = &net.Resolver{
PreferGo: useGo,
StrictErrors: strictErr,
}
return
}
// TplSplitHostPortHost wraps net.SplitHostPort and returns the host.
func TplSplitHostPortHost(s string) (host string, err error) {
if host, _, err = net.SplitHostPort(s); err != nil {
return
}
return
}
// TplSplitHostPortPort wraps net.SplitHostPort and returns the port.
func TplSplitHostPortPort(s string) (port string, err error) {
if _, port, err = net.SplitHostPort(s); err != nil {
return
}
return
}
// TplToCidrHost wraps net.ParseCIDR and returns the host net.IP and any error.
func TplToCidrHost(s string) (host net.IP, err error) {
if host, _, err = net.ParseCIDR(s); err != nil {
return
}
return
}
// TplToCidrNet wraps net.ParseCIDR and returns the network *net.IPNet and any error.
func TplToCidrNet(s string) (netwk *net.IPNet, err error) {
if _, netwk, err = net.ParseCIDR(s); err != nil {
return
}
return
}