mirror of
https://github.com/versity/versitygw.git
synced 2026-03-27 09:54:59 +00:00
Merge pull request #1932 from versity/ben/uds-listener
feat: add unix domain socket listener support to port option
This commit is contained in:
@@ -156,6 +156,19 @@ documentation can be found in the GitHub wiki.`,
|
||||
admPorts = ctx.StringSlice("admin-port")
|
||||
webuiGateways = ctx.StringSlice("webui-gateways")
|
||||
webuiAdminGateways = ctx.StringSlice("webui-admin-gateways")
|
||||
|
||||
// Resolve relative UNIX socket paths to absolute before any backend
|
||||
// (e.g. posix) can change the working directory via os.Chdir.
|
||||
var err error
|
||||
if ports, err = utils.AbsSocketPaths(ports); err != nil {
|
||||
return err
|
||||
}
|
||||
if admPorts, err = utils.AbsSocketPaths(admPorts); err != nil {
|
||||
return err
|
||||
}
|
||||
if webuiPorts, err = utils.AbsSocketPaths(webuiPorts); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
@@ -181,14 +194,14 @@ func initFlags() []cli.Flag {
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "port",
|
||||
Usage: "gateway listen address <ip>:<port> or :<port> (can be specified multiple times for listening on multiple addresses)",
|
||||
Usage: "gateway listen address: <ip>:<port>, :<port>, /path/to/socket for file-backed UNIX sockets, or @name for Linux abstract namespace sockets (can be specified multiple times for listening on multiple addresses)",
|
||||
EnvVars: []string{"VGW_PORT"},
|
||||
Value: cli.NewStringSlice(":7070"),
|
||||
Aliases: []string{"p"},
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "webui",
|
||||
Usage: "enable WebUI server on the specified listen address (e.g. ':7071', '127.0.0.1:7071', 'localhost:7071'; can be specified multiple times for listening on multiple addresses; disabled when omitted)",
|
||||
Usage: "enable WebUI server on the specified listen address (e.g. ':7071', '127.0.0.1:7071', 'localhost:7071', '/run/vgw/webui.sock'; supports the same UNIX socket forms as --port; can be specified multiple times for listening on multiple addresses; disabled when omitted)",
|
||||
EnvVars: []string{"VGW_WEBUI_PORT"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
@@ -277,7 +290,7 @@ func initFlags() []cli.Flag {
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "admin-port",
|
||||
Usage: "gateway admin server listen address <ip>:<port> or :<port> (can be specified multiple times for listening on multiple addresses)",
|
||||
Usage: "gateway admin server listen address: <ip>:<port>, :<port>, /path/to/socket for file-backed UNIX sockets, or @name for Linux abstract namespace sockets (can be specified multiple times for listening on multiple addresses)",
|
||||
EnvVars: []string{"VGW_ADMIN_PORT"},
|
||||
Aliases: []string{"ap"},
|
||||
},
|
||||
@@ -1242,6 +1255,14 @@ func printBanner(ports []string, admPorts []string, ssl, admSsl bool, webuiAddrs
|
||||
interfaceMap := make(map[string]bool) // deduplicate
|
||||
|
||||
for _, portSpec := range ports {
|
||||
if utils.IsUnixSocketPath(portSpec) {
|
||||
allPorts = append(allPorts, portSpec)
|
||||
if !interfaceMap[portSpec] {
|
||||
interfaceMap[portSpec] = true
|
||||
allInterfaces = append(allInterfaces, portSpec)
|
||||
}
|
||||
continue
|
||||
}
|
||||
interfaces, err := getMatchingIPs(portSpec)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to match local IP addresses for %s: %v\n", portSpec, err)
|
||||
@@ -1271,6 +1292,13 @@ func printBanner(ports []string, admPorts []string, ssl, admSsl bool, webuiAddrs
|
||||
var allAdmInterfaces []string
|
||||
admInterfaceMap := make(map[string]bool)
|
||||
for _, admPort := range admPorts {
|
||||
if utils.IsUnixSocketPath(admPort) {
|
||||
if !admInterfaceMap[admPort] {
|
||||
admInterfaceMap[admPort] = true
|
||||
allAdmInterfaces = append(allAdmInterfaces, admPort)
|
||||
}
|
||||
continue
|
||||
}
|
||||
interfaces, err := getMatchingIPs(admPort)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to match admin port local IP addresses for %s: %v\n", admPort, err)
|
||||
@@ -1296,6 +1324,10 @@ func printBanner(ports []string, admPorts []string, ssl, admSsl bool, webuiAddrs
|
||||
|
||||
// Build URLs for all listening addresses
|
||||
for _, addrPort := range allInterfaces {
|
||||
if utils.IsUnixSocketPath(addrPort) {
|
||||
urls = append(urls, "unix:"+addrPort)
|
||||
continue
|
||||
}
|
||||
ip, prt, err := net.SplitHostPort(addrPort)
|
||||
if err != nil {
|
||||
// Shouldn't happen as we constructed these properly, but handle it
|
||||
@@ -1313,11 +1345,15 @@ func printBanner(ports []string, admPorts []string, ssl, admSsl bool, webuiAddrs
|
||||
// Determine bound host description
|
||||
var boundHost string
|
||||
if len(ports) == 1 {
|
||||
hst, prt, _ := net.SplitHostPort(ports[0])
|
||||
if hst == "" {
|
||||
hst = "0.0.0.0"
|
||||
if utils.IsUnixSocketPath(ports[0]) {
|
||||
boundHost = fmt.Sprintf("(unix socket: %s)", ports[0])
|
||||
} else {
|
||||
hst, prt, _ := net.SplitHostPort(ports[0])
|
||||
if hst == "" {
|
||||
hst = "0.0.0.0"
|
||||
}
|
||||
boundHost = fmt.Sprintf("(bound on host %s and port %s)", hst, prt)
|
||||
}
|
||||
boundHost = fmt.Sprintf("(bound on host %s and port %s)", hst, prt)
|
||||
} else {
|
||||
// Multiple ports
|
||||
portList := strings.Join(allPorts, ", ")
|
||||
@@ -1352,6 +1388,10 @@ func printBanner(ports []string, admPorts []string, ssl, admSsl bool, webuiAddrs
|
||||
)
|
||||
|
||||
for _, addrPort := range allAdmInterfaces {
|
||||
if utils.IsUnixSocketPath(addrPort) {
|
||||
lines = append(lines, leftText(" unix:"+addrPort))
|
||||
continue
|
||||
}
|
||||
ip, prt, err := net.SplitHostPort(addrPort)
|
||||
if err != nil {
|
||||
continue
|
||||
@@ -1374,6 +1414,13 @@ func printBanner(ports []string, admPorts []string, ssl, admSsl bool, webuiAddrs
|
||||
if strings.TrimSpace(webuiAddr) == "" {
|
||||
continue
|
||||
}
|
||||
if utils.IsUnixSocketPath(webuiAddr) {
|
||||
if !webInterfaceMap[webuiAddr] {
|
||||
webInterfaceMap[webuiAddr] = true
|
||||
allWebInterfaces = append(allWebInterfaces, webuiAddr)
|
||||
}
|
||||
continue
|
||||
}
|
||||
webInterfaces, err := getMatchingIPs(webuiAddr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to match webui port local IP addresses for %s: %v\n", webuiAddr, err)
|
||||
@@ -1399,6 +1446,10 @@ func printBanner(ports []string, admPorts []string, ssl, admSsl bool, webuiAddrs
|
||||
leftText("WebUI listening on:"),
|
||||
)
|
||||
for _, addrPort := range allWebInterfaces {
|
||||
if utils.IsUnixSocketPath(addrPort) {
|
||||
lines = append(lines, leftText(" unix:"+addrPort))
|
||||
continue
|
||||
}
|
||||
ip, prt, err := net.SplitHostPort(addrPort)
|
||||
if err != nil {
|
||||
continue
|
||||
@@ -1429,6 +1480,11 @@ func printBanner(ports []string, admPorts []string, ssl, admSsl bool, webuiAddrs
|
||||
// for the given address specification. For hostnames, it resolves to all
|
||||
// IP addresses (e.g., localhost -> 127.0.0.1 and ::1).
|
||||
func getMatchingIPs(spec string) ([]string, error) {
|
||||
if utils.IsUnixSocketPath(spec) {
|
||||
// Unix socket paths have no IP addresses; return the path itself as an identifier.
|
||||
return []string{spec}, nil
|
||||
}
|
||||
|
||||
ips, err := utils.ResolveHostnameIPs(spec)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve hostname: %v", err)
|
||||
@@ -1488,6 +1544,12 @@ func getAllLocalIPs() ([]string, error) {
|
||||
}
|
||||
|
||||
func buildServiceURLs(spec string, ssl bool) ([]string, error) {
|
||||
if utils.IsUnixSocketPath(spec) {
|
||||
// UNIX socket paths cannot be expressed as HTTP(S) URLs for WebUI gateways;
|
||||
// skip them silently.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
interfaces, err := getMatchingIPs(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1593,6 +1655,8 @@ func sortGatewayURLs(urls []string) {
|
||||
// A bare port spec (e.g., ":7071") binds to all interfaces and will conflict with any other
|
||||
// binding on the same port, whether it's ":7071" or "ip:7071".
|
||||
// However, two identical "ip:port" specs are allowed (will be caught by later errors).
|
||||
// UNIX socket paths (e.g., "/tmp/gw.sock") are checked for duplicate path conflicts only,
|
||||
// and do not conflict with TCP port specifications.
|
||||
// This is needed because net.Listen() does not return the address already in use
|
||||
// error for the bare port spec arguments.
|
||||
func validatePortConflicts(ports, admPorts, webuiPorts []string) error {
|
||||
@@ -1600,6 +1664,7 @@ func validatePortConflicts(ports, admPorts, webuiPorts []string) error {
|
||||
spec string
|
||||
port string
|
||||
isBare bool
|
||||
isUnix bool
|
||||
portType string // "s3", "admin", or "webui"
|
||||
}
|
||||
|
||||
@@ -1607,6 +1672,10 @@ func validatePortConflicts(ports, admPorts, webuiPorts []string) error {
|
||||
|
||||
// Collect all port specs
|
||||
for _, p := range ports {
|
||||
if utils.IsUnixSocketPath(p) {
|
||||
allSpecs = append(allSpecs, portSpec{spec: p, port: p, isUnix: true, portType: "s3"})
|
||||
continue
|
||||
}
|
||||
_, port, err := net.SplitHostPort(p)
|
||||
if err != nil {
|
||||
continue // will be caught by later validation
|
||||
@@ -1620,6 +1689,10 @@ func validatePortConflicts(ports, admPorts, webuiPorts []string) error {
|
||||
}
|
||||
|
||||
for _, p := range admPorts {
|
||||
if utils.IsUnixSocketPath(p) {
|
||||
allSpecs = append(allSpecs, portSpec{spec: p, port: p, isUnix: true, portType: "admin"})
|
||||
continue
|
||||
}
|
||||
_, port, err := net.SplitHostPort(p)
|
||||
if err != nil {
|
||||
continue // will be caught by later validation
|
||||
@@ -1633,6 +1706,10 @@ func validatePortConflicts(ports, admPorts, webuiPorts []string) error {
|
||||
}
|
||||
|
||||
for _, p := range webuiPorts {
|
||||
if utils.IsUnixSocketPath(p) {
|
||||
allSpecs = append(allSpecs, portSpec{spec: p, port: p, isUnix: true, portType: "webui"})
|
||||
continue
|
||||
}
|
||||
_, port, err := net.SplitHostPort(p)
|
||||
if err != nil {
|
||||
continue // will be caught by later validation
|
||||
@@ -1652,6 +1729,16 @@ func validatePortConflicts(ports, admPorts, webuiPorts []string) error {
|
||||
continue // skip comparing with self and already compared pairs
|
||||
}
|
||||
|
||||
// Unix sockets and TCP ports never conflict with each other;
|
||||
// only check for duplicate socket paths.
|
||||
if spec1.isUnix || spec2.isUnix {
|
||||
if spec1.isUnix && spec2.isUnix && spec1.spec == spec2.spec {
|
||||
return fmt.Errorf("duplicate unix socket path: --%s %s conflicts with --%s %s",
|
||||
spec1.portType, spec1.spec, spec2.portType, spec2.spec)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// If ports don't match, no conflict
|
||||
if spec1.port != spec2.port {
|
||||
continue
|
||||
|
||||
@@ -69,6 +69,11 @@ ROOT_SECRET_ACCESS_KEY=
|
||||
# in /etc/services.
|
||||
# To specify multiple ports, use a comma-separated list
|
||||
# (e.g., VGW_PORT=:7070,:8080,localhost:9090).
|
||||
# UNIX domain sockets are also supported by specifying a path or on Linux
|
||||
# an abstract namespace socket with the "@" prefix (e.g., @versitygw-s3).
|
||||
# UNIX socket addresses can be mixed with
|
||||
# TCP/IP addresses in a comma-separated list
|
||||
# (e.g., VGW_PORT=:7070,/run/vgw/s3.sock).
|
||||
#VGW_PORT=:7070
|
||||
|
||||
# The VGW_REGION option will specify the region that the S3 server will
|
||||
@@ -89,8 +94,10 @@ ROOT_SECRET_ACCESS_KEY=
|
||||
# these are not specified is to have the admin server listen on the same
|
||||
# endpoint as the S3 service. This can specify multiple ports with comma
|
||||
# separated list and will resolve hostnames to multiple addresses the same
|
||||
# as VGW_PORT. When VGW_ADMIN_CERT and VGW_ADMIN_CERT_KEY are specified,
|
||||
# the admin server will use SSL.
|
||||
# as VGW_PORT. UNIX domain sockets are supported using the same syntax as
|
||||
# VGW_PORT (absolute/relative paths and Linux abstract "@" sockets).
|
||||
# When VGW_ADMIN_CERT and VGW_ADMIN_CERT_KEY are specified, the admin server
|
||||
# will use SSL.
|
||||
#VGW_ADMIN_PORT=
|
||||
#VGW_ADMIN_CERT=
|
||||
#VGW_ADMIN_CERT_KEY=
|
||||
@@ -225,7 +232,9 @@ ROOT_SECRET_ACCESS_KEY=
|
||||
# interfaces (e.g., ':7071') or 'host:port' to listen on a specific interface
|
||||
# (e.g., '127.0.0.1:7071' or 'localhost:7071'). When omitted, the Web GUI is
|
||||
# disabled. This can specify multiple ports with comma separated list and will
|
||||
# resolve hostnames to multiple addresses the same as VGW_PORT.
|
||||
# resolve hostnames to multiple addresses the same as VGW_PORT. UNIX domain
|
||||
# sockets are supported using the same syntax as VGW_PORT (absolute/relative
|
||||
# paths and Linux abstract "@" sockets).
|
||||
#VGW_WEBUI_PORT=
|
||||
|
||||
# The VGW_WEBUI_CERT and VGW_WEBUI_KEY options specify the TLS certificate and
|
||||
|
||||
@@ -30,17 +30,17 @@ func TestS3ApiServer_Serve(t *testing.T) {
|
||||
port string
|
||||
}{
|
||||
{
|
||||
name: "Serve-invalid-address",
|
||||
name: "Serve-invalid-tcp-address",
|
||||
wantErr: true,
|
||||
sa: &S3ApiServer{
|
||||
app: fiber.New(),
|
||||
backend: backend.BackendUnsupported{},
|
||||
Router: &S3ApiRouter{},
|
||||
},
|
||||
port: "Invalid address",
|
||||
port: "localhost:notaport",
|
||||
},
|
||||
{
|
||||
name: "Serve-invalid-address-with-certificate",
|
||||
name: "Serve-invalid-tcp-address-with-certificate",
|
||||
wantErr: true,
|
||||
sa: &S3ApiServer{
|
||||
app: fiber.New(),
|
||||
@@ -48,7 +48,7 @@ func TestS3ApiServer_Serve(t *testing.T) {
|
||||
Router: &S3ApiRouter{},
|
||||
CertStorage: &utils.CertStorage{},
|
||||
},
|
||||
port: "Invalid address",
|
||||
port: "localhost:notaport",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -19,6 +19,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -135,10 +138,69 @@ func (ml *MultiListener) Addr() net.Addr {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsUnixSocketPath reports whether addr should be treated as a UNIX domain
|
||||
// socket path rather than a TCP/IP address. It does so by attempting to parse
|
||||
// addr as a host:port spec using net.SplitHostPort; anything that cannot be
|
||||
// parsed that way (e.g. "/path/to/socket", "./rel/socket", "@abstract") is
|
||||
// considered a socket path.
|
||||
func IsUnixSocketPath(addr string) bool {
|
||||
_, _, err := net.SplitHostPort(addr)
|
||||
return err != nil
|
||||
}
|
||||
|
||||
// AbsSocketPaths converts any relative UNIX socket paths in addrs to absolute
|
||||
// paths using the current working directory. Non-socket addresses (TCP/IP) and
|
||||
// abstract sockets ("@name") are returned unchanged. This should be called
|
||||
// early in program startup — before any backend that calls os.Chdir — so that
|
||||
// relative paths are resolved against the shell's working directory.
|
||||
func AbsSocketPaths(addrs []string) ([]string, error) {
|
||||
result := make([]string, len(addrs))
|
||||
for i, addr := range addrs {
|
||||
if strings.HasPrefix(addr, "./") {
|
||||
abs, err := filepath.Abs(addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve socket path %q: %w", addr, err)
|
||||
}
|
||||
result[i] = abs
|
||||
} else {
|
||||
result[i] = addr
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// isAbstractSocket reports whether addr is a Linux abstract namespace socket.
|
||||
// Abstract sockets start with "@"; Go's net package maps this to a leading
|
||||
// null byte (\0) in the sockaddr, so no socket file is created on disk.
|
||||
func isAbstractSocket(addr string) bool {
|
||||
return strings.HasPrefix(addr, "@")
|
||||
}
|
||||
|
||||
// removeStaleSocket removes a leftover UNIX socket file at path so the
|
||||
// address can be reused. It returns an error if the path exists but is not
|
||||
// a socket, protecting regular files and directories from accidental deletion.
|
||||
func removeStaleSocket(path string) error {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to stat socket path %q: %w", path, err)
|
||||
}
|
||||
if fi.Mode()&os.ModeSocket == 0 {
|
||||
return fmt.Errorf("path %q already exists and is not a socket (mode %s)", path, fi.Mode())
|
||||
}
|
||||
return os.Remove(path)
|
||||
}
|
||||
|
||||
// ResolveHostnameIPs resolves a hostname to all its IP addresses (IPv4 and IPv6).
|
||||
// If the input is already an IP address or empty, it returns it as-is.
|
||||
// This is useful for determining all addresses a server will listen on.
|
||||
func ResolveHostnameIPs(address string) ([]string, error) {
|
||||
if IsUnixSocketPath(address) {
|
||||
return []string{address}, nil
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid address %q: %w", address, err)
|
||||
@@ -176,6 +238,10 @@ func ResolveHostnameIPs(address string) ([]string, error) {
|
||||
// resolveHostnameAddrs resolves a hostname to all its IP addresses (IPv4 and IPv6)
|
||||
// and returns them as a list of addresses with the port attached.
|
||||
func resolveHostnameAddrs(address string) ([]string, error) {
|
||||
if IsUnixSocketPath(address) {
|
||||
return []string{address}, nil
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid address %q: %w", address, err)
|
||||
@@ -210,7 +276,27 @@ func resolveHostnameAddrs(address string) ([]string, error) {
|
||||
// in the address resolves to. If the address is already an IP, it creates a
|
||||
// single listener. Returns a MultiListener if multiple addresses are resolved,
|
||||
// or a single listener if only one address is found.
|
||||
//
|
||||
// UNIX domain socket forms are also supported:
|
||||
// - "/path/to/socket" or "./rel/socket" — file-backed socket; any stale
|
||||
// socket file is removed before binding.
|
||||
// - "@name" — Linux abstract namespace socket; no file is created or removed.
|
||||
func NewMultiAddrListener(network, address string) (net.Listener, error) {
|
||||
if IsUnixSocketPath(address) {
|
||||
// For file-backed sockets, remove any stale socket file so re-binding works cleanly.
|
||||
// Abstract sockets (@name) have no filesystem entry; skip removal for them.
|
||||
if !isAbstractSocket(address) {
|
||||
if err := removeStaleSocket(address); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ln, err := net.Listen("unix", address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to bind unix socket listener %s: %w", address, err)
|
||||
}
|
||||
return NewMultiListener(ln), nil
|
||||
}
|
||||
|
||||
addrs, err := resolveHostnameAddrs(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -237,12 +323,30 @@ func NewMultiAddrListener(network, address string) (net.Listener, error) {
|
||||
|
||||
// NewMultiAddrTLSListener creates TLS listeners for all IP addresses that the
|
||||
// hostname in the address resolves to. Similar to NewMultiAddrListener but with TLS.
|
||||
//
|
||||
// UNIX domain socket forms are also supported:
|
||||
// - "/path/to/socket" or "./rel/socket" — file-backed socket; any stale
|
||||
// socket file is removed before binding.
|
||||
// - "@name" — Linux abstract namespace socket; no file is created or removed.
|
||||
func NewMultiAddrTLSListener(network, address string, getCertificateFunc func(*tls.ClientHelloInfo) (*tls.Certificate, error)) (net.Listener, error) {
|
||||
config := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
GetCertificate: getCertificateFunc,
|
||||
}
|
||||
|
||||
if IsUnixSocketPath(address) {
|
||||
if !isAbstractSocket(address) {
|
||||
if err := removeStaleSocket(address); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ln, err := net.Listen("unix", address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to bind unix TLS socket listener %s: %w", address, err)
|
||||
}
|
||||
return NewMultiListener(tls.NewListener(ln, config)), nil
|
||||
}
|
||||
|
||||
addrs, err := resolveHostnameAddrs(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -171,9 +171,12 @@ func TestResolveHostnameAddrs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid address",
|
||||
name: "no port treated as unix socket path",
|
||||
address: "invalid-no-port",
|
||||
wantErr: true,
|
||||
wantErr: false,
|
||||
checkResult: func(addrs []string) bool {
|
||||
return len(addrs) == 1 && addrs[0] == "invalid-no-port"
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -235,9 +238,20 @@ func TestResolveHostnameIPs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid address",
|
||||
address: "invalid-no-port",
|
||||
wantErr: true,
|
||||
name: "unix socket path",
|
||||
address: "/tmp/test.sock",
|
||||
wantErr: false,
|
||||
checkResult: func(ips []string) bool {
|
||||
return len(ips) == 1 && ips[0] == "/tmp/test.sock"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relative unix socket path",
|
||||
address: "./test.sock",
|
||||
wantErr: false,
|
||||
checkResult: func(ips []string) bool {
|
||||
return len(ips) == 1 && ips[0] == "./test.sock"
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user