package template_test import ( "testing" socktmpl "github.com/hashicorp/go-sockaddr/template" ) func TestSockAddr_Parse(t *testing.T) { tests := []struct { name string input string output string fail bool requireOnline bool }{ { name: `basic include "name"`, input: `{{GetAllInterfaces | include "name" "lo0" | printf "%v"}}`, output: `[127.0.0.1/8 {1 16384 lo0 up|loopback|multicast} ::1 {1 16384 lo0 up|loopback|multicast} fe80::1/64 {1 16384 lo0 up|loopback|multicast}]`, }, { name: "invalid input", input: `{{`, output: ``, fail: true, }, { name: "GetDefaultInterface", input: `{{GetDefaultInterfaces | include "type" "IPv4" | attr "name" }}`, output: `en0`, }, { name: `include "name" regexp`, input: `{{GetAllInterfaces | include "name" "^(en|lo)0$" | exclude "name" "^en0$" | sort "type" | sort "address" | join "address" " " }}`, output: `127.0.0.1 ::1 fe80::1`, }, { name: `exclude "name"`, input: `{{. | include "name" "^(en|lo)0$" | exclude "name" "^en0$" | sort "type" | sort "address" | join "address" " " }}`, output: `127.0.0.1 ::1 fe80::1`, }, { name: `"dot" pipeline, IPv4 type`, input: `{{. | include "type" "IPv4" | include "name" "^lo0$" | sort "type" | sort "address" }}`, output: `[127.0.0.1/8 {1 16384 lo0 up|loopback|multicast}]`, }, { name: `include "type" "IPv6`, input: `{{. | include "type" "IPv6" | include "name" "^lo0$" | sort "address" }}`, output: `[::1 {1 16384 lo0 up|loopback|multicast} fe80::1/64 {1 16384 lo0 up|loopback|multicast}]`, }, { name: "better example for IP types", input: `{{. | include "type" "IPv4|IPv6" | include "name" "^lo0$" | sort "type" | sort "address" }}`, output: `[127.0.0.1/8 {1 16384 lo0 up|loopback|multicast} ::1 {1 16384 lo0 up|loopback|multicast} fe80::1/64 {1 16384 lo0 up|loopback|multicast}]`, }, { name: "ifAddrs1", input: `{{. | include "type" "IPv4" | include "name" "^lo0$"}}`, output: `[127.0.0.1/8 {1 16384 lo0 up|loopback|multicast}]`, }, { name: "ifAddrs2", input: `{{. | include "type" "IP" | include "name" "^lo0$" | sort "type" | sort "address" }}`, output: `[127.0.0.1/8 {1 16384 lo0 up|loopback|multicast} ::1 {1 16384 lo0 up|loopback|multicast} fe80::1/64 {1 16384 lo0 up|loopback|multicast}]`, }, { name: `range "dot" example`, input: `{{range . | include "type" "IP" | include "name" "^lo0$"}}{{.Name}} {{.SockAddr}} {{end}}`, output: `lo0 127.0.0.1/8 lo0 ::1 lo0 fe80::1/64 `, }, { name: `exclude "type"`, input: `{{. | exclude "type" "IPv4" | include "name" "^lo0$" | sort "address" | unique "name" | join "name" " "}} {{range . | exclude "type" "IPv4" | include "name" "^lo0$"}}{{.SockAddr}} {{end}}`, output: `lo0 ::1 fe80::1/64 `, }, { name: "with variable pipeline", input: `{{with $ifSet := include "type" "IPv4" . | include "name" "^lo0$"}}{{range $ifSet }}{{.Name}} {{end}}{{range $ifSet}}{{.SockAddr}} {{end}}{{end}}`, output: `lo0 127.0.0.1/8 `, }, { name: "range sample on lo0", input: `{{with $ifAddrs := . | exclude "rfc" "1918" | include "name" "lo0" | sort "type,address" }}{{range $ifAddrs }}{{.Name}}/{{.SockAddr.NetIP}} {{end}}{{end}}`, output: `lo0/127.0.0.1 lo0/::1 lo0/fe80::1 `, }, { name: "non-RFC1918 on on lo0", input: `{{. | exclude "rfc" "1918" | include "name" "lo0" | sort "type,address" | len | eq 3}}`, output: `true`, }, { // NOTE(sean@): Difficult to reliably test includeByRFC. // In this case, we ass-u-me that the host running the // test has at least one RFC1918 address on their host. name: `include "rfc"`, input: `{{(. | include "rfc" "1918" | attr "name")}}`, output: `en0`, requireOnline: true, }, { name: "test for non-empty array", input: `{{. | include "type" "IPv4" | include "rfc" "1918" | print | len | eq (len "[]")}}`, output: `false`, }, { // NOTE(sean@): This will fail if there is a public IPv4 address on loopback. name: "non-IPv4 RFC1918", input: `{{. | include "name" "lo0" | exclude "type" "IPv4" | include "rfc" "1918" | len | eq 0}}`, output: `true`, }, { // NOTE(sean@): There are no RFC6598 addresses on most testing hosts so this should be empty. name: "rfc6598", input: `{{. | include "type" "IPv4" | include "rfc" "6598" | print | len | eq (len "[]")}}`, output: `true`, }, { name: "invalid RFC", input: `{{. | include "type" "IPv4" | include "rfc" "99999999999" | print | len | eq (len "[]")}}`, output: `true`, fail: true, }, { name: `sort asc address`, input: `{{ . | include "name" "lo0" | sort "type,address" | join "address" " " }}`, output: `127.0.0.1 ::1 fe80::1`, }, { name: `sort asc address old`, input: `{{with $ifSet := include "name" "lo0" . }}{{ range include "type" "IPv4" $ifSet | sort "address"}}{{ .SockAddr }} {{end}}{{ range include "type" "IPv6" $ifSet | sort "address"}}{{ .SockAddr }} {{end}}{{end}}`, output: `127.0.0.1/8 ::1 fe80::1/64 `, }, { name: `sort desc address`, input: `{{ . | include "name" "lo0" | sort "type,-address" | join "address" " " }}`, output: `127.0.0.1 fe80::1 ::1`, }, { name: `sort desc address`, input: `{{ . | include "name" "lo0" | include "type" "IPv6" | sort "type,-address" | join "address" " " }}`, output: `fe80::1 ::1`, }, { name: `sort asc address`, input: `{{with $ifSet := include "name" "lo0" . }}{{ range include "type" "IPv6" $ifSet | sort "address"}}{{ .SockAddr }} {{end}}{{end}}`, output: `::1 fe80::1/64 `, }, { name: "lo0 limit 1", input: `{{. | include "name" "lo0" | include "type" "IPv6" | sort "address" | limit 1 | len}}`, output: `1`, }, { name: "join address", input: `{{. | include "name" "lo0" | include "type" "IPv6" | sort "address" | join "address" " " }}`, output: `::1 fe80::1`, }, { name: "join name", input: `{{. | include "name" "lo0" | include "type" "IPv6" | sort "address" | join "name" " " }}`, output: `lo0 lo0`, }, { name: "lo0 flags up and limit 1", input: `{{. | include "name" "lo0" | include "flag" "up" | sort "-type,+address" | attr "address" }}`, output: `::1`, }, { // NOTE(sean@): This is the HashiCorp default in 2016. // Indented for effect. Using "true" as the output // instead of printing the correct $rfc*Addrs values. name: "HashiCorpDefault2016", input: ` {{- with $addr := GetAllInterfaces | include "type" "IP" | include "rfc" "1918|6598" | sort "address" | attr "address" -}} {{- if ($addr | len) gt 0 -}} {{- print "true" -}}{{/* print $addr*/ -}} {{- end -}} {{- end -}}`, output: `true`, }, { name: "math address +", input: `{{GetAllInterfaces | include "name" "^lo0$" | include "type" "IP" | math "address" "+2" | sort "+type,+address" | join "address" " " }}`, output: `127.0.0.3 ::3 fe80::3`, }, { name: "math address + overflow", input: `|{{- with $ifAddrs := GetAllInterfaces | include "name" "^lo0$" | include "type" "IP" | math "address" "+16777217" | sort "+type,+address" -}} {{- range $ifAddrs -}} {{- attr "address" . }} -- {{ attr "network" . }}/{{ attr "size" . }}|{{ end -}} {{- end -}}`, output: `|128.0.0.2 -- 128.0.0.0/16777216|::100:2 -- ::100:2/1|fe80::100:2 -- fe80::/18446744073709551616|`, }, { name: "math address + overflow+wrap", input: `{{GetAllInterfaces | include "name" "^lo0$" | include "type" "IP" | math "address" "+4294967294" | sort "+type,+address" | join "address" " " }}`, output: `126.255.255.255 ::ffff:ffff fe80::ffff:ffff`, }, { name: "math address -", input: `{{GetAllInterfaces | include "name" "^lo0$" | include "type" "IP" | math "address" "-256" | sort "+type,+address" | join "address" " " }}`, output: `126.255.255.1 fe7f:ffff:ffff:ffff:ffff:ffff:ffff:ff01 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff01`, }, { name: "math address - underflow", input: `{{GetAllInterfaces | include "name" "^lo0$" | include "type" "IP" | math "address" "-4278190082" | sort "+type,+address" | join "address" " " }}`, output: `127.255.255.255 fe7f:ffff:ffff:ffff:ffff:ffff:ff:ffff ffff:ffff:ffff:ffff:ffff:ffff:ff:ffff`, }, { // Note to readers: lo0's link-local address (::1) address has a mask of // /128 which means its value never changes and this is expected. lo0's // site-local address has a /64 address and is expected to change. name: "math network", input: `{{GetAllInterfaces | include "name" "^lo0$" | include "type" "IP" | math "network" "+2" | sort "+type,+address" | join "address" " " }}`, output: `127.0.0.2 ::1 fe80::2`, }, { // Assume an IPv4 input of 127.0.0.1. With a value of 0xff00ff01, we wrap once on purpose. name: "math network + wrap", input: `{{GetAllInterfaces | include "name" "^lo0$" | include "type" "IP" | math "network" "+4278255368" | sort "+type,+address" | join "address" " " }}`, output: `127.0.255.8 ::1 fe80::ff00:ff08`, }, { name: "math network -", input: `{{GetAllInterfaces | include "name" "^lo0$" | include "type" "IP" | math "network" "-2" | sort "+type,+address" | join "address" " " }}`, output: `127.255.255.254 ::1 fe80::ffff:ffff:ffff:fffe`, }, { // Assume an IPv4 input of 127.0.0.1. With a value of 0xff000008 it // should wrap and underflow by 8. Assume an IPv6 input of ::1. With a // value of -0xff000008 the value underflows and wraps. name: "math network - underflow+wrap", input: `{{GetAllInterfaces | include "name" "^lo0$" | include "type" "IP" | sort "+type,+address" | math "network" "-4278190088" | join "address" " " }}`, output: `127.255.255.248 ::1 fe80::ffff:ffff:ff:fff8`, }, { // Assume the private IPs available on the host are: 10.1.2.3 // fe80::1025:f732:1001:203 name: "GetPrivateIPs", input: `{{GetPrivateIPs}}`, output: `10.1.2.3 fe80::1025:f732:1001:203`, }, { // Assume the public IPs available on the host are: 1.2.3.4 6.7.8.9 name: "GetPublicIPs", input: `{{GetPublicIPs}}`, output: `1.2.3.4 6.7.8.9`, }, { // Assume the private IPs on this host are just the IPv4 addresses: // 10.1.2.3 and 172.16.4.6 name: "GetInterfaceIPs", input: `{{GetInterfaceIPs "en0"}}`, output: `10.1.2.3 and 172.16.4.6`, }, } for i, test := range tests { test := test // capture range variable if test.name == "" { t.Fatalf("test number %d has an empty test name", i) } t.Run(test.name, func(t *testing.T) { t.Parallel() out, err := socktmpl.Parse(test.input) if err != nil && !test.fail { t.Fatalf("%q: bad: %v", test.name, err) } if out != test.output && !test.fail { t.Fatalf("%q: Expected %+q, received %+q\n%+q", test.name, test.output, out, test.input) } }) } }