Add support for late binding to IP addresses using go-sockaddr/template
This commit is contained in:
parent
e14c4e3ee4
commit
72b0a7f34d
|
@ -2,6 +2,7 @@ package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
@ -13,6 +14,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-sockaddr/template"
|
||||||
|
|
||||||
client "github.com/hashicorp/nomad/client/config"
|
client "github.com/hashicorp/nomad/client/config"
|
||||||
"github.com/hashicorp/nomad/helper"
|
"github.com/hashicorp/nomad/helper"
|
||||||
"github.com/hashicorp/nomad/nomad"
|
"github.com/hashicorp/nomad/nomad"
|
||||||
|
@ -692,22 +695,45 @@ func (c *Config) Merge(b *Config) *Config {
|
||||||
// normalizeAddrs normalizes Addresses and AdvertiseAddrs to always be
|
// normalizeAddrs normalizes Addresses and AdvertiseAddrs to always be
|
||||||
// initialized and have sane defaults.
|
// initialized and have sane defaults.
|
||||||
func (c *Config) normalizeAddrs() error {
|
func (c *Config) normalizeAddrs() error {
|
||||||
c.Addresses.HTTP = normalizeBind(c.Addresses.HTTP, c.BindAddr)
|
if c.BindAddr != "" {
|
||||||
c.Addresses.RPC = normalizeBind(c.Addresses.RPC, c.BindAddr)
|
ipStr, err := parseSingleIPTemplate(c.BindAddr)
|
||||||
c.Addresses.Serf = normalizeBind(c.Addresses.Serf, c.BindAddr)
|
if err != nil {
|
||||||
|
return fmt.Errorf("Bind address resolution failed: %v", err)
|
||||||
|
}
|
||||||
|
c.BindAddr = ipStr
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := normalizeBind(c.Addresses.HTTP, c.BindAddr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to parse HTTP address: %v", err)
|
||||||
|
}
|
||||||
|
c.Addresses.HTTP = addr
|
||||||
|
|
||||||
|
addr, err = normalizeBind(c.Addresses.RPC, c.BindAddr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to parse RPC address: %v", err)
|
||||||
|
}
|
||||||
|
c.Addresses.RPC = addr
|
||||||
|
|
||||||
|
addr, err = normalizeBind(c.Addresses.Serf, c.BindAddr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to parse Serf address: %v", err)
|
||||||
|
}
|
||||||
|
c.Addresses.Serf = addr
|
||||||
|
|
||||||
c.normalizedAddrs = &Addresses{
|
c.normalizedAddrs = &Addresses{
|
||||||
HTTP: net.JoinHostPort(c.Addresses.HTTP, strconv.Itoa(c.Ports.HTTP)),
|
HTTP: net.JoinHostPort(c.Addresses.HTTP, strconv.Itoa(c.Ports.HTTP)),
|
||||||
RPC: net.JoinHostPort(c.Addresses.RPC, strconv.Itoa(c.Ports.RPC)),
|
RPC: net.JoinHostPort(c.Addresses.RPC, strconv.Itoa(c.Ports.RPC)),
|
||||||
Serf: net.JoinHostPort(c.Addresses.Serf, strconv.Itoa(c.Ports.Serf)),
|
Serf: net.JoinHostPort(c.Addresses.Serf, strconv.Itoa(c.Ports.Serf)),
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := normalizeAdvertise(c.AdvertiseAddrs.HTTP, c.Addresses.HTTP, c.Ports.HTTP, c.DevMode)
|
addr, err = normalizeAdvertise(c.AdvertiseAddrs.HTTP, c.Addresses.HTTP, c.Ports.HTTP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to parse HTTP advertise address: %v", err)
|
return fmt.Errorf("Failed to parse HTTP advertise address: %v", err)
|
||||||
}
|
}
|
||||||
c.AdvertiseAddrs.HTTP = addr
|
c.AdvertiseAddrs.HTTP = addr
|
||||||
|
|
||||||
addr, err = normalizeAdvertise(c.AdvertiseAddrs.RPC, c.Addresses.RPC, c.Ports.RPC, c.DevMode)
|
addr, err = normalizeAdvertise(c.AdvertiseAddrs.RPC, c.Addresses.RPC, c.Ports.RPC)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to parse RPC advertise address: %v", err)
|
return fmt.Errorf("Failed to parse RPC advertise address: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -715,7 +741,7 @@ func (c *Config) normalizeAddrs() error {
|
||||||
|
|
||||||
// Skip serf if server is disabled
|
// Skip serf if server is disabled
|
||||||
if c.Server != nil && c.Server.Enabled {
|
if c.Server != nil && c.Server.Enabled {
|
||||||
addr, err = normalizeAdvertise(c.AdvertiseAddrs.Serf, c.Addresses.Serf, c.Ports.Serf, c.DevMode)
|
addr, err = normalizeAdvertise(c.AdvertiseAddrs.Serf, c.Addresses.Serf, c.Ports.Serf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to parse Serf advertise address: %v", err)
|
return fmt.Errorf("Failed to parse Serf advertise address: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -725,14 +751,34 @@ func (c *Config) normalizeAddrs() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseSingleIPTemplate is used as a helper function to parse out a single IP
|
||||||
|
// address from a config parameter.
|
||||||
|
func parseSingleIPTemplate(ipTmpl string) (string, error) {
|
||||||
|
out, err := template.Parse(ipTmpl)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Unable to parse address template %q: %v", ipTmpl, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ips := strings.Split(out, " ")
|
||||||
|
switch len(ips) {
|
||||||
|
case 0:
|
||||||
|
return "", errors.New("No addresses found, please configure one.")
|
||||||
|
case 1:
|
||||||
|
return ips[0], nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("Multiple addresses found (%q), please configure one.", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// normalizeBind returns a normalized bind address.
|
// normalizeBind returns a normalized bind address.
|
||||||
//
|
//
|
||||||
// If addr is set it is used, if not the default bind address is used.
|
// If addr is set it is used, if not the default bind address is used.
|
||||||
func normalizeBind(addr, bind string) string {
|
func normalizeBind(addr, bind string) (string, error) {
|
||||||
if addr == "" {
|
if addr == "" {
|
||||||
return bind
|
return bind, nil
|
||||||
|
} else {
|
||||||
|
return parseSingleIPTemplate(addr)
|
||||||
}
|
}
|
||||||
return addr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalizeAdvertise returns a normalized advertise address.
|
// normalizeAdvertise returns a normalized advertise address.
|
||||||
|
@ -747,61 +793,28 @@ func normalizeBind(addr, bind string) string {
|
||||||
// is resolved and returned with the port.
|
// is resolved and returned with the port.
|
||||||
//
|
//
|
||||||
// Loopback is only considered a valid advertise address in dev mode.
|
// Loopback is only considered a valid advertise address in dev mode.
|
||||||
func normalizeAdvertise(addr string, bind string, defport int, dev bool) (string, error) {
|
func normalizeAdvertise(addr string, bind string, defport int) (string, error) {
|
||||||
if addr != "" {
|
if addr != "" {
|
||||||
// Default to using manually configured address
|
// Default to using manually configured address
|
||||||
_, _, err := net.SplitHostPort(addr)
|
host, port, err := net.SplitHostPort(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !isMissingPort(err) {
|
if !isMissingPort(err) {
|
||||||
return "", fmt.Errorf("Error parsing advertise address %q: %v", addr, err)
|
return "", fmt.Errorf("Error parsing advertise address %q: %v", addr, err)
|
||||||
}
|
}
|
||||||
|
host = addr
|
||||||
// missing port, append the default
|
port = strconv.Itoa(defport)
|
||||||
return net.JoinHostPort(addr, strconv.Itoa(defport)), nil
|
|
||||||
}
|
|
||||||
return addr, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to bind address first, and then try resolving the local hostname
|
ipStr, err := parseSingleIPTemplate(host)
|
||||||
ips, err := net.LookupIP(bind)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Error resolving bind address %q: %v", bind, err)
|
return "", fmt.Errorf("Error parsing advertise address template: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the first unicast address
|
return net.JoinHostPort(ipStr, port), nil
|
||||||
for _, ip := range ips {
|
|
||||||
if ip.IsLinkLocalUnicast() || ip.IsGlobalUnicast() {
|
|
||||||
return net.JoinHostPort(ip.String(), strconv.Itoa(defport)), nil
|
|
||||||
}
|
|
||||||
if ip.IsLoopback() && dev {
|
|
||||||
// loopback is fine for dev mode
|
|
||||||
return net.JoinHostPort(ip.String(), strconv.Itoa(defport)), nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// As a last resort resolve the hostname and use it if it's not
|
// Fallback to bind address, as it has been resolved before.
|
||||||
// localhost (as localhost is never a sensible default)
|
return net.JoinHostPort(bind, strconv.Itoa(defport)), nil
|
||||||
host, err := os.Hostname()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("Unable to get hostname to set advertise address: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ips, err = net.LookupIP(host)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("Error resolving hostname %q for advertise address: %v", host, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the first unicast address
|
|
||||||
for _, ip := range ips {
|
|
||||||
if ip.IsLinkLocalUnicast() || ip.IsGlobalUnicast() {
|
|
||||||
return net.JoinHostPort(ip.String(), strconv.Itoa(defport)), nil
|
|
||||||
}
|
|
||||||
if ip.IsLoopback() && dev {
|
|
||||||
// loopback is fine for dev mode
|
|
||||||
return net.JoinHostPort(ip.String(), strconv.Itoa(defport)), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("No valid advertise addresses, please set `advertise` manually")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// isMissingPort returns true if an error is a "missing port" error from
|
// isMissingPort returns true if an error is a "missing port" error from
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
@ -520,6 +521,121 @@ func TestConfig_Listener(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfig_normalizeAddrs(t *testing.T) {
|
||||||
|
c := &Config{
|
||||||
|
BindAddr: "127.0.0.1",
|
||||||
|
Ports: &Ports{
|
||||||
|
HTTP: 4646,
|
||||||
|
RPC: 4647,
|
||||||
|
Serf: 4648,
|
||||||
|
},
|
||||||
|
Addresses: &Addresses{},
|
||||||
|
AdvertiseAddrs: &AdvertiseAddrs{},
|
||||||
|
DevMode: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.normalizeAddrs(); err != nil {
|
||||||
|
t.Fatalf("unable to normalize addresses: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.BindAddr != "127.0.0.1" {
|
||||||
|
t.Fatalf("expected BindAddr 127.0.0.1, got %s", c.BindAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.normalizedAddrs.HTTP != "127.0.0.1:4646" {
|
||||||
|
t.Fatalf("expected HTTP address 127.0.0.1:4646, got %s", c.normalizedAddrs.HTTP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.normalizedAddrs.RPC != "127.0.0.1:4647" {
|
||||||
|
t.Fatalf("expected RPC address 127.0.0.1:4647, got %s", c.normalizedAddrs.RPC)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.normalizedAddrs.Serf != "127.0.0.1:4648" {
|
||||||
|
t.Fatalf("expected Serf address 127.0.0.1:4648, got %s", c.normalizedAddrs.Serf)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.AdvertiseAddrs.HTTP != "127.0.0.1:4646" {
|
||||||
|
t.Fatalf("expected HTTP advertise address 127.0.0.1:4646, got %s", c.AdvertiseAddrs.HTTP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.AdvertiseAddrs.RPC != "127.0.0.1:4647" {
|
||||||
|
t.Fatalf("expected RPC advertise address 127.0.0.1:4647, got %s", c.AdvertiseAddrs.RPC)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client mode, no Serf address defined
|
||||||
|
if c.AdvertiseAddrs.Serf != "" {
|
||||||
|
t.Fatalf("expected unset Serf advertise address, got %s", c.AdvertiseAddrs.Serf)
|
||||||
|
}
|
||||||
|
|
||||||
|
c = &Config{
|
||||||
|
BindAddr: "169.254.1.5",
|
||||||
|
Ports: &Ports{
|
||||||
|
HTTP: 4646,
|
||||||
|
RPC: 4647,
|
||||||
|
Serf: 4648,
|
||||||
|
},
|
||||||
|
Addresses: &Addresses{
|
||||||
|
HTTP: "169.254.1.10",
|
||||||
|
},
|
||||||
|
AdvertiseAddrs: &AdvertiseAddrs{
|
||||||
|
RPC: "169.254.1.40",
|
||||||
|
},
|
||||||
|
Server: &ServerConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.normalizeAddrs(); err != nil {
|
||||||
|
t.Fatalf("unable to normalize addresses: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.BindAddr != "169.254.1.5" {
|
||||||
|
t.Fatalf("expected BindAddr 169.254.1.5, got %s", c.BindAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.AdvertiseAddrs.HTTP != "169.254.1.10:4646" {
|
||||||
|
t.Fatalf("expected HTTP advertise address 169.254.1.10:4646, got %s", c.AdvertiseAddrs.HTTP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.AdvertiseAddrs.RPC != "169.254.1.40:4647" {
|
||||||
|
t.Fatalf("expected RPC advertise address 169.254.1.40:4647, got %s", c.AdvertiseAddrs.RPC)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.AdvertiseAddrs.Serf != "169.254.1.5:4648" {
|
||||||
|
t.Fatalf("expected Serf advertise address 169.254.1.5:4648, got %s", c.AdvertiseAddrs.Serf)
|
||||||
|
}
|
||||||
|
|
||||||
|
c = &Config{
|
||||||
|
BindAddr: "{{ GetPrivateIP }}",
|
||||||
|
Ports: &Ports{
|
||||||
|
HTTP: 4646,
|
||||||
|
RPC: 4647,
|
||||||
|
Serf: 4648,
|
||||||
|
},
|
||||||
|
Addresses: &Addresses{},
|
||||||
|
AdvertiseAddrs: &AdvertiseAddrs{},
|
||||||
|
Server: &ServerConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.normalizeAddrs(); err != nil {
|
||||||
|
t.Fatalf("unable to normalize addresses: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.AdvertiseAddrs.HTTP != fmt.Sprintf("%s:4646", c.BindAddr) {
|
||||||
|
t.Fatalf("expected HTTP advertise address %s:4646, got %s", c.BindAddr, c.AdvertiseAddrs.HTTP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.AdvertiseAddrs.RPC != fmt.Sprintf("%s:4647", c.BindAddr) {
|
||||||
|
t.Fatalf("expected RPC advertise address %s:4647, got %s", c.BindAddr, c.AdvertiseAddrs.RPC)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.AdvertiseAddrs.Serf != fmt.Sprintf("%s:4648", c.BindAddr) {
|
||||||
|
t.Fatalf("expected Serf advertise address %s:4648, got %s", c.BindAddr, c.AdvertiseAddrs.Serf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestResources_ParseReserved(t *testing.T) {
|
func TestResources_ParseReserved(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Input string
|
Input string
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
test::
|
||||||
|
go test
|
|
@ -0,0 +1,6 @@
|
||||||
|
# sockaddr/template
|
||||||
|
|
||||||
|
sockaddr's template library. See
|
||||||
|
the
|
||||||
|
[sockaddr/template](https://godoc.org/github.com/hashicorp/go-sockaddr/template)
|
||||||
|
docs for details on how to use this template.
|
|
@ -0,0 +1,239 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
Package sockaddr/template provides a text/template interface the SockAddr helper
|
||||||
|
functions. The primary entry point into the sockaddr/template package is
|
||||||
|
through its Parse() call. For example:
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
template "github.com/hashicorp/go-sockaddr/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
results, err := template.Parse(`{{ GetPrivateIP }}`)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Errorf("Unable to find a private IP address: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("My Private IP address is: %s\n", results)
|
||||||
|
|
||||||
|
Below is a list of builtin template functions and details re: their usage. It
|
||||||
|
is possible to add additional functions by calling ParseIfAddrsTemplate
|
||||||
|
directly.
|
||||||
|
|
||||||
|
In general, the calling convention for this template library is to seed a list
|
||||||
|
of initial interfaces via one of the Get*Interfaces() calls, then filter, sort,
|
||||||
|
and extract the necessary attributes for use as string input. This template
|
||||||
|
interface is primarily geared toward resolving specific values that are only
|
||||||
|
available at runtime, but can be defined as a heuristic for execution when a
|
||||||
|
config file is parsed.
|
||||||
|
|
||||||
|
All functions, unless noted otherwise, return an array of IfAddr structs making
|
||||||
|
it possible to `sort`, `filter`, `limit`, seek (via the `offset` function), or
|
||||||
|
`unique` the list. To extract useful string information, the `attr` and `join`
|
||||||
|
functions return a single string value. See below for details.
|
||||||
|
|
||||||
|
Important note: see the
|
||||||
|
https://github.com/hashicorp/go-sockaddr/tree/master/cmd/sockaddr utility for
|
||||||
|
more examples and for a CLI utility to experiment with the template syntax.
|
||||||
|
|
||||||
|
`GetAllInterfaces` - Returns an exhaustive set of IfAddr structs available on
|
||||||
|
the host. `GetAllInterfaces` is the initial input and accessible as the initial
|
||||||
|
"dot" in the pipeline.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{ GetAllInterfaces }}
|
||||||
|
|
||||||
|
|
||||||
|
`GetDefaultInterfaces` - Returns one IfAddr for every IP that is on the
|
||||||
|
interface containing the default route for the host.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{ GetDefaultInterfaces }}
|
||||||
|
|
||||||
|
`GetPrivateInterfaces` - Returns one IfAddr for every forwardable IP address
|
||||||
|
that is included in RFC 6890, is attached to the interface with the default
|
||||||
|
route, and whose interface is marked as up. NOTE: RFC 6890 is a more exhaustive
|
||||||
|
version of RFC1918 because it spans IPv4 and IPv6, however it does permit the
|
||||||
|
inclusion of likely undesired addresses such as multicast, therefore our version
|
||||||
|
of "private" also filters out non-forwardable addresses.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{ GetPrivateInterfaces | include "flags" "up" }}
|
||||||
|
|
||||||
|
|
||||||
|
`GetPublicInterfaces` - Returns a list of IfAddr that do not match RFC 6890, is
|
||||||
|
attached to the default route, and whose interface is marked as up.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{ GetPublicInterfaces | include "flags" "up" }}
|
||||||
|
|
||||||
|
|
||||||
|
`GetPrivateIP` - Helper function that returns a string of the first IP address
|
||||||
|
from GetPrivateInterfaces.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{ GetPrivateIP }}
|
||||||
|
|
||||||
|
|
||||||
|
`GetPublicIP` - Helper function that returns a string of the first IP from
|
||||||
|
GetPublicInterfaces.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{ GetPublicIP }}
|
||||||
|
|
||||||
|
`GetInterfaceIP` - Helper function that returns a string of the first IP from
|
||||||
|
the named interface.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{ GetInterfaceIP }}
|
||||||
|
|
||||||
|
|
||||||
|
`sort` - Sorts the IfAddrs result based on its arguments. `sort` takes one
|
||||||
|
argument, a list of ways to sort its IfAddrs argument. The list of sort
|
||||||
|
criteria is comma separated (`,`):
|
||||||
|
- `address`, `+address`: Ascending sort of IfAddrs by Address
|
||||||
|
- `-address`: Descending sort of IfAddrs by Address
|
||||||
|
- `name`, `+name`: Ascending sort of IfAddrs by lexical ordering of interface name
|
||||||
|
- `-name`: Descending sort of IfAddrs by lexical ordering of interface name
|
||||||
|
- `port`, `+port`: Ascending sort of IfAddrs by port number
|
||||||
|
- `-port`: Descending sort of IfAddrs by port number
|
||||||
|
- `private`, `+private`: Ascending sort of IfAddrs with private addresses first
|
||||||
|
- `-private`: Descending sort IfAddrs with private addresses last
|
||||||
|
- `size`, `+size`: Ascending sort of IfAddrs by their network size as determined
|
||||||
|
by their netmask (larger networks first)
|
||||||
|
- `-size`: Descending sort of IfAddrs by their network size as determined by their
|
||||||
|
netmask (smaller networks first)
|
||||||
|
- `type`, `+type`: Ascending sort of IfAddrs by the type of the IfAddr (Unix,
|
||||||
|
IPv4, then IPv6)
|
||||||
|
- `-type`: Descending sort of IfAddrs by the type of the IfAddr (IPv6, IPv4, Unix)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{ GetPrivateInterfaces | sort "type,size,address" }}
|
||||||
|
|
||||||
|
|
||||||
|
`exclude` and `include`: Filters IfAddrs based on the selector criteria and its
|
||||||
|
arguments. Both `exclude` and `include` take two arguments. The list of
|
||||||
|
available filtering criteria is:
|
||||||
|
- "address": Filter IfAddrs based on a regexp matching the string representation
|
||||||
|
of the address
|
||||||
|
- "flag","flags": Filter IfAddrs based on the list of flags specified. Multiple
|
||||||
|
flags can be passed together using the pipe character (`|`) to create an inclusive
|
||||||
|
bitmask of flags. The list of flags is included below.
|
||||||
|
- "name": Filter IfAddrs based on a regexp matching the interface name.
|
||||||
|
- "network": Filter IfAddrs based on whether a netowkr is included in a given
|
||||||
|
CIDR. More than one CIDR can be passed in if each network is separated by
|
||||||
|
the pipe character (`|`).
|
||||||
|
- "port": Filter IfAddrs based on an exact match of the port number (number must
|
||||||
|
be expressed as a string)
|
||||||
|
- "rfc", "rfcs": Filter IfAddrs based on the matching RFC. If more than one RFC
|
||||||
|
is specified, the list of RFCs can be joined together using the pipe character (`|`).
|
||||||
|
- "size": Filter IfAddrs based on the exact match of the mask size.
|
||||||
|
- "type": Filter IfAddrs based on their SockAddr type. Multiple types can be
|
||||||
|
specified together by using the pipe character (`|`). Valid types include:
|
||||||
|
`ip`, `ipv4`, `ipv6`, and `unix`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{ GetPrivateInterfaces | exclude "type" "IPv6" | include "flag" "up|forwardable" }}
|
||||||
|
|
||||||
|
|
||||||
|
`unique`: Removes duplicate entries from the IfAddrs list, assuming the list has
|
||||||
|
already been sorted. `unique` only takes one argument:
|
||||||
|
- "address": Removes duplicates with the same address
|
||||||
|
- "name": Removes duplicates with the same interface names
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{ GetPrivateInterfaces | sort "type,address" | unique "name" }}
|
||||||
|
|
||||||
|
|
||||||
|
`limit`: Reduces the size of the list to the specified value.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{ GetPrivateInterfaces | include "flags" "forwardable|up" | limit 1 }}
|
||||||
|
|
||||||
|
|
||||||
|
`offset`: Seeks into the list by the specified value. A negative value can be
|
||||||
|
used to seek from the end of the list.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{ GetPrivateInterfaces | include "flags" "forwardable|up" | offset "-2" | limit 1 }}
|
||||||
|
|
||||||
|
|
||||||
|
`attr`: Extracts a single attribute of the first member of the list and returns
|
||||||
|
it as a string. `attr` takes a single attribute name. The list of available
|
||||||
|
attributes is type-specific and shared between `join`. See below for a list of
|
||||||
|
supported attributes.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{ GetPrivateInterfaces | include "flags" "forwardable|up" | attr "address" }}
|
||||||
|
|
||||||
|
|
||||||
|
`join`: Similar to `attr`, `join` extracts all matching attributes of the list
|
||||||
|
and returns them as a string joined by the separator, the second argument to
|
||||||
|
`join`. The list of available attributes is type-specific and shared between
|
||||||
|
`join`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{{ GetPrivateInterfaces | include "flags" "forwardable|up" | join "address" " " }}
|
||||||
|
|
||||||
|
|
||||||
|
`exclude` and `include` flags:
|
||||||
|
- `broadcast`
|
||||||
|
- `down`: Is the interface down?
|
||||||
|
- `forwardable`: Is the IP forwardable?
|
||||||
|
- `global unicast`
|
||||||
|
- `interface-local multicast`
|
||||||
|
- `link-local multicast`
|
||||||
|
- `link-local unicast`
|
||||||
|
- `loopback`
|
||||||
|
- `multicast`
|
||||||
|
- `point-to-point`
|
||||||
|
- `unspecified`: Is the IfAddr the IPv6 unspecified address?
|
||||||
|
- `up`: Is the interface up?
|
||||||
|
|
||||||
|
|
||||||
|
Attributes for `attr` and `join`:
|
||||||
|
|
||||||
|
SockAddr Type:
|
||||||
|
- `string`
|
||||||
|
- `type`
|
||||||
|
|
||||||
|
IPAddr Type:
|
||||||
|
- `address`
|
||||||
|
- `binary`
|
||||||
|
- `first_usable`
|
||||||
|
- `hex`
|
||||||
|
- `host`
|
||||||
|
- `last_usable`
|
||||||
|
- `mask_bits`
|
||||||
|
- `netmask`
|
||||||
|
- `network`
|
||||||
|
- `octets`: Decimal values per byte
|
||||||
|
- `port`
|
||||||
|
- `size`: Number of hosts in the network
|
||||||
|
|
||||||
|
IPv4Addr Type:
|
||||||
|
- `broadcast`
|
||||||
|
- `uint32`: unsigned integer representation of the value
|
||||||
|
|
||||||
|
IPv6Addr Type:
|
||||||
|
- `uint128`: unsigned integer representation of the value
|
||||||
|
|
||||||
|
UnixSock Type:
|
||||||
|
- `path`
|
||||||
|
|
||||||
|
*/
|
||||||
|
package template
|
|
@ -0,0 +1,125 @@
|
||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/hashicorp/errwrap"
|
||||||
|
sockaddr "github.com/hashicorp/go-sockaddr"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// SourceFuncs is a map of all top-level functions that generate
|
||||||
|
// sockaddr data types.
|
||||||
|
SourceFuncs template.FuncMap
|
||||||
|
|
||||||
|
// SortFuncs is a map of all functions used in sorting
|
||||||
|
SortFuncs template.FuncMap
|
||||||
|
|
||||||
|
// FilterFuncs is a map of all functions used in sorting
|
||||||
|
FilterFuncs template.FuncMap
|
||||||
|
|
||||||
|
// HelperFuncs is a map of all functions used in sorting
|
||||||
|
HelperFuncs template.FuncMap
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SourceFuncs = template.FuncMap{
|
||||||
|
// GetAllInterfaces - Returns an exhaustive set of IfAddr
|
||||||
|
// structs available on the host. `GetAllInterfaces` is the
|
||||||
|
// initial input and accessible as the initial "dot" in the
|
||||||
|
// pipeline.
|
||||||
|
"GetAllInterfaces": sockaddr.GetAllInterfaces,
|
||||||
|
|
||||||
|
// GetDefaultInterfaces - Returns one IfAddr for every IP that
|
||||||
|
// is on the interface containing the default route for the
|
||||||
|
// host.
|
||||||
|
"GetDefaultInterfaces": sockaddr.GetDefaultInterfaces,
|
||||||
|
|
||||||
|
// GetPrivateInterfaces - Returns one IfAddr for every IP that
|
||||||
|
// matches RFC 6890, are attached to the interface with the
|
||||||
|
// default route, and are forwardable IP addresses. NOTE: RFC
|
||||||
|
// 6890 is a more exhaustive version of RFC1918 because it spans
|
||||||
|
// IPv4 and IPv6, however it doespermit the inclusion of likely
|
||||||
|
// undesired addresses such as multicast, therefore our
|
||||||
|
// definition of a "private" address also excludes
|
||||||
|
// non-forwardable IP addresses (as defined by the IETF).
|
||||||
|
"GetPrivateInterfaces": sockaddr.GetPrivateInterfaces,
|
||||||
|
|
||||||
|
// GetPublicInterfaces - Returns a list of IfAddr that do not
|
||||||
|
// match RFC 6890, are attached to the default route, and are
|
||||||
|
// forwardable.
|
||||||
|
"GetPublicInterfaces": sockaddr.GetPublicInterfaces,
|
||||||
|
}
|
||||||
|
|
||||||
|
SortFuncs = template.FuncMap{
|
||||||
|
"sort": sockaddr.SortIfBy,
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterFuncs = template.FuncMap{
|
||||||
|
"exclude": sockaddr.ExcludeIfs,
|
||||||
|
"include": sockaddr.IncludeIfs,
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperFuncs = template.FuncMap{
|
||||||
|
// Misc functions that operate on IfAddrs inputs
|
||||||
|
"attr": sockaddr.IfAttr,
|
||||||
|
"join": sockaddr.JoinIfAddrs,
|
||||||
|
"limit": sockaddr.LimitIfAddrs,
|
||||||
|
"offset": sockaddr.OffsetIfAddrs,
|
||||||
|
"unique": sockaddr.UniqueIfAddrsBy,
|
||||||
|
|
||||||
|
// Return a Private RFC 6890 IP address string that is attached
|
||||||
|
// to the default route and a forwardable address.
|
||||||
|
"GetPrivateIP": sockaddr.GetPrivateIP,
|
||||||
|
|
||||||
|
// Return a Public RFC 6890 IP address string that is attached
|
||||||
|
// to the default route and a forwardable address.
|
||||||
|
"GetPublicIP": sockaddr.GetPublicIP,
|
||||||
|
|
||||||
|
// Return the first IP address of the named interface, sorted by
|
||||||
|
// the largest network size.
|
||||||
|
"GetInterfaceIP": sockaddr.GetInterfaceIP,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses input as template input using the addresses available on the
|
||||||
|
// host, then returns the string output if there are no errors.
|
||||||
|
func Parse(input string) (string, error) {
|
||||||
|
addrs, err := sockaddr.GetAllInterfaces()
|
||||||
|
if err != nil {
|
||||||
|
return "", errwrap.Wrapf("unable to query interface addresses: {{err}}", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseIfAddrs(input, addrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseIfAddrs parses input as template input using the IfAddrs inputs, then
|
||||||
|
// returns the string output if there are no errors.
|
||||||
|
func ParseIfAddrs(input string, ifAddrs sockaddr.IfAddrs) (string, error) {
|
||||||
|
return ParseIfAddrsTemplate(input, ifAddrs, template.New("sockaddr.Parse"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseIfAddrsTemplate parses input as template input using the IfAddrs inputs,
|
||||||
|
// then returns the string output if there are no errors.
|
||||||
|
func ParseIfAddrsTemplate(input string, ifAddrs sockaddr.IfAddrs, tmplIn *template.Template) (string, error) {
|
||||||
|
// Create a template, add the function map, and parse the text.
|
||||||
|
tmpl, err := tmplIn.Option("missingkey=error").
|
||||||
|
Funcs(SourceFuncs).
|
||||||
|
Funcs(SortFuncs).
|
||||||
|
Funcs(FilterFuncs).
|
||||||
|
Funcs(HelperFuncs).
|
||||||
|
Parse(input)
|
||||||
|
if err != nil {
|
||||||
|
return "", errwrap.Wrapf(fmt.Sprintf("unable to parse template %+q: {{err}}", input), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var outWriter bytes.Buffer
|
||||||
|
err = tmpl.Execute(&outWriter, ifAddrs)
|
||||||
|
if err != nil {
|
||||||
|
return "", errwrap.Wrapf(fmt.Sprintf("unable to execute sockaddr input %+q: {{err}}", input), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return outWriter.String(), nil
|
||||||
|
}
|
|
@ -793,6 +793,12 @@
|
||||||
"revision": "f910dd83c2052566cad78352c33af714358d1372",
|
"revision": "f910dd83c2052566cad78352c33af714358d1372",
|
||||||
"revisionTime": "2017-02-08T07:30:35Z"
|
"revisionTime": "2017-02-08T07:30:35Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "lPzwetgfMBtpHqdTPolgejMctVQ=",
|
||||||
|
"path": "github.com/hashicorp/go-sockaddr/template",
|
||||||
|
"revision": "f910dd83c2052566cad78352c33af714358d1372",
|
||||||
|
"revisionTime": "2017-02-08T07:30:35Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "github.com/hashicorp/go-syslog",
|
"path": "github.com/hashicorp/go-syslog",
|
||||||
"revision": "42a2b573b664dbf281bd48c3cc12c086b17a39ba"
|
"revision": "42a2b573b664dbf281bd48c3cc12c086b17a39ba"
|
||||||
|
|
Loading…
Reference in New Issue