mirror of
https://github.com/caddyserver/caddy.git
synced 2025-12-08 06:09:53 +00:00
systemd.listen placeholder
This commit is contained in:
parent
8285eba842
commit
a33da3415b
6 changed files with 513 additions and 299 deletions
|
|
@ -15,7 +15,6 @@
|
|||
package caddy
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
|
|
@ -653,286 +652,3 @@ func TestSplitUnixSocketPermissionsBits(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetFdByName tests the getFdByName function for systemd socket activation.
|
||||
func TestGetFdByName(t *testing.T) {
|
||||
// Save original environment
|
||||
originalFdNames := os.Getenv("LISTEN_FDNAMES")
|
||||
|
||||
// Restore environment after test
|
||||
defer func() {
|
||||
if originalFdNames != "" {
|
||||
os.Setenv("LISTEN_FDNAMES", originalFdNames)
|
||||
} else {
|
||||
os.Unsetenv("LISTEN_FDNAMES")
|
||||
}
|
||||
}()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fdNames string
|
||||
socketName string
|
||||
expectedFd int
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "simple http socket",
|
||||
fdNames: "http",
|
||||
socketName: "http",
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
name: "multiple different sockets - first",
|
||||
fdNames: "http:https:dns",
|
||||
socketName: "http",
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
name: "multiple different sockets - second",
|
||||
fdNames: "http:https:dns",
|
||||
socketName: "https",
|
||||
expectedFd: 4,
|
||||
},
|
||||
{
|
||||
name: "multiple different sockets - third",
|
||||
fdNames: "http:https:dns",
|
||||
socketName: "dns",
|
||||
expectedFd: 5,
|
||||
},
|
||||
{
|
||||
name: "duplicate names - first occurrence (no index)",
|
||||
fdNames: "web:web:api",
|
||||
socketName: "web",
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
name: "duplicate names - first occurrence (explicit index 0)",
|
||||
fdNames: "web:web:api",
|
||||
socketName: "web:0",
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
name: "duplicate names - second occurrence (index 1)",
|
||||
fdNames: "web:web:api",
|
||||
socketName: "web:1",
|
||||
expectedFd: 4,
|
||||
},
|
||||
{
|
||||
name: "complex duplicates - first api",
|
||||
fdNames: "web:api:web:api:dns",
|
||||
socketName: "api:0",
|
||||
expectedFd: 4,
|
||||
},
|
||||
{
|
||||
name: "complex duplicates - second api",
|
||||
fdNames: "web:api:web:api:dns",
|
||||
socketName: "api:1",
|
||||
expectedFd: 6,
|
||||
},
|
||||
{
|
||||
name: "complex duplicates - first web",
|
||||
fdNames: "web:api:web:api:dns",
|
||||
socketName: "web:0",
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
name: "complex duplicates - second web",
|
||||
fdNames: "web:api:web:api:dns",
|
||||
socketName: "web:1",
|
||||
expectedFd: 5,
|
||||
},
|
||||
{
|
||||
name: "socket not found",
|
||||
fdNames: "http:https",
|
||||
socketName: "missing",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "empty socket name",
|
||||
fdNames: "http",
|
||||
socketName: "",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "missing LISTEN_FDNAMES",
|
||||
fdNames: "",
|
||||
socketName: "http",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "index out of range",
|
||||
fdNames: "web:web",
|
||||
socketName: "web:2",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "negative index",
|
||||
fdNames: "web",
|
||||
socketName: "web:-1",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "invalid index format",
|
||||
fdNames: "web",
|
||||
socketName: "web:abc",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "too many colons",
|
||||
fdNames: "web",
|
||||
socketName: "web:0:extra",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Set up environment
|
||||
if tc.fdNames != "" {
|
||||
os.Setenv("LISTEN_FDNAMES", tc.fdNames)
|
||||
} else {
|
||||
os.Unsetenv("LISTEN_FDNAMES")
|
||||
}
|
||||
|
||||
// Test the function
|
||||
fd, err := getFdByName(tc.socketName)
|
||||
|
||||
if tc.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("Expected error but got none")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error but got: %v", err)
|
||||
}
|
||||
if fd != tc.expectedFd {
|
||||
t.Errorf("Expected FD %d but got %d", tc.expectedFd, fd)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseNetworkAddressFdName tests parsing of fdname and fdgramname addresses.
|
||||
func TestParseNetworkAddressFdName(t *testing.T) {
|
||||
// Save and restore environment
|
||||
originalFdNames := os.Getenv("LISTEN_FDNAMES")
|
||||
defer func() {
|
||||
if originalFdNames != "" {
|
||||
os.Setenv("LISTEN_FDNAMES", originalFdNames)
|
||||
} else {
|
||||
os.Unsetenv("LISTEN_FDNAMES")
|
||||
}
|
||||
}()
|
||||
|
||||
// Set up test environment
|
||||
os.Setenv("LISTEN_FDNAMES", "http:https:dns")
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
expectAddr NetworkAddress
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
input: "fdname/http",
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "fd",
|
||||
Host: "3",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "fdname/https",
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "fd",
|
||||
Host: "4",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "fdname/dns",
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "fd",
|
||||
Host: "5",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "fdname/http:0",
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "fd",
|
||||
Host: "3",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "fdname/https:0",
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "fd",
|
||||
Host: "4",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "fdgramname/http",
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "fdgram",
|
||||
Host: "3",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "fdgramname/https",
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "fdgram",
|
||||
Host: "4",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "fdgramname/http:0",
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "fdgram",
|
||||
Host: "3",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "fdname/nonexistent",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
input: "fdgramname/nonexistent",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
input: "fdname/http:99",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
input: "fdname/invalid:abc",
|
||||
expectErr: true,
|
||||
},
|
||||
// Test that old fd/N syntax still works
|
||||
{
|
||||
input: "fd/7",
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "fd",
|
||||
Host: "7",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "fdgram/8",
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "fdgram",
|
||||
Host: "8",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
actualAddr, err := ParseNetworkAddress(tc.input)
|
||||
|
||||
if tc.expectErr && err == nil {
|
||||
t.Errorf("Test %d (%s): Expected error but got none", i, tc.input)
|
||||
}
|
||||
if !tc.expectErr && err != nil {
|
||||
t.Errorf("Test %d (%s): Expected no error but got: %v", i, tc.input, err)
|
||||
}
|
||||
if !tc.expectErr && !reflect.DeepEqual(tc.expectAddr, actualAddr) {
|
||||
t.Errorf("Test %d (%s): Expected %+v but got %+v", i, tc.input, tc.expectAddr, actualAddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
17
replacer.go
17
replacer.go
|
|
@ -36,16 +36,12 @@ func NewReplacer() *Replacer {
|
|||
static: make(map[string]any),
|
||||
mapMutex: &sync.RWMutex{},
|
||||
}
|
||||
rep.providers = []replacementProvider{
|
||||
globalDefaultReplacementProvider{},
|
||||
fileReplacementProvider{},
|
||||
ReplacerFunc(rep.fromStatic),
|
||||
}
|
||||
rep.providers = append(globalReplacementProviders, ReplacerFunc(rep.fromStatic))
|
||||
return rep
|
||||
}
|
||||
|
||||
// NewEmptyReplacer returns a new Replacer,
|
||||
// without the global default replacements.
|
||||
// without the global replacements.
|
||||
func NewEmptyReplacer() *Replacer {
|
||||
rep := &Replacer{
|
||||
static: make(map[string]any),
|
||||
|
|
@ -360,12 +356,11 @@ func (f fileReplacementProvider) replace(key string) (any, bool) {
|
|||
return string(body), true
|
||||
}
|
||||
|
||||
// globalDefaultReplacementProvider handles replacements
|
||||
// that can be used in any context, such as system variables,
|
||||
// time, or environment variables.
|
||||
type globalDefaultReplacementProvider struct{}
|
||||
// defaultReplacementProvider handles replacements
|
||||
// such as system variables, time, or environment variables.
|
||||
type defaultReplacementProvider struct{}
|
||||
|
||||
func (f globalDefaultReplacementProvider) replace(key string) (any, bool) {
|
||||
func (f defaultReplacementProvider) replace(key string) (any, bool) {
|
||||
// check environment variable
|
||||
const envPrefix = "env."
|
||||
if strings.HasPrefix(key, envPrefix) {
|
||||
|
|
|
|||
8
replacer_nosystemd.go
Normal file
8
replacer_nosystemd.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
//go:build !linux || nosystemd
|
||||
|
||||
package caddy
|
||||
|
||||
var globalReplacementProviders = []replacementProvider{
|
||||
defaultReplacementProvider{},
|
||||
fileReplacementProvider{},
|
||||
}
|
||||
123
replacer_systemd.go
Normal file
123
replacer_systemd.go
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
//go:build linux && !nosystemd
|
||||
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func sdListenFds() (int, error) {
|
||||
lnPid, ok := os.LookupEnv("LISTEN_PID")
|
||||
if !ok {
|
||||
return 0, errors.New("LISTEN_PID is unset")
|
||||
}
|
||||
|
||||
pid, err := strconv.Atoi(lnPid)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if pid != os.Getpid() {
|
||||
return 0, fmt.Errorf("LISTEN_PID does not match pid: %d != %d", pid, os.Getpid())
|
||||
}
|
||||
|
||||
lnFds, ok := os.LookupEnv("LISTEN_FDS")
|
||||
if !ok {
|
||||
return 0, errors.New("LISTEN_FDS is unset")
|
||||
}
|
||||
|
||||
fds, err := strconv.Atoi(lnFds)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return fds, nil
|
||||
}
|
||||
|
||||
func sdListenFdsWithNames() (map[string][]uint, error) {
|
||||
const lnFdsStart = 3
|
||||
|
||||
fds, err := sdListenFds()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lnFdnames, ok := os.LookupEnv("LISTEN_FDNAMES")
|
||||
if !ok {
|
||||
return nil, errors.New("LISTEN_FDNAMES is unset")
|
||||
}
|
||||
|
||||
fdNames := strings.Split(lnFdnames, ":")
|
||||
if fds != len(fdNames) {
|
||||
return nil, fmt.Errorf("LISTEN_FDS does not match LISTEN_FDNAMES length: %d != %d", fds, len(fdNames))
|
||||
}
|
||||
|
||||
nameToFiles := make(map[string][]uint, len(fdNames))
|
||||
for index, name := range fdNames {
|
||||
nameToFiles[name] = append(nameToFiles[name], lnFdsStart+uint(index))
|
||||
}
|
||||
|
||||
return nameToFiles, nil
|
||||
}
|
||||
|
||||
func getSdListenFd(nameToFiles map[string][]uint, nameOffset string) (uint, error) {
|
||||
index := uint(0)
|
||||
|
||||
name, offset, found := strings.Cut(nameOffset, ":")
|
||||
if found {
|
||||
off, err := strconv.ParseUint(offset, 0, strconv.IntSize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
index += uint(off)
|
||||
}
|
||||
|
||||
files, ok := nameToFiles[name]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("invalid listen fd name: %s", name)
|
||||
}
|
||||
|
||||
if uint(len(files)) <= index {
|
||||
return 0, fmt.Errorf("invalid listen fd index: %d", index)
|
||||
}
|
||||
|
||||
return files[index], nil
|
||||
}
|
||||
|
||||
var initNameToFiles, initNameToFilesErr = sdListenFdsWithNames()
|
||||
|
||||
// systemdReplacementProvider handles {systemd.*} replacements
|
||||
type systemdReplacementProvider struct{}
|
||||
|
||||
func (f systemdReplacementProvider) replace(key string) (any, bool) {
|
||||
// check environment variable
|
||||
const systemdListenPrefix = "systemd.listen."
|
||||
if strings.HasPrefix(key, systemdListenPrefix) {
|
||||
if initNameToFilesErr != nil {
|
||||
Log().Error("unable to read LISTEN_FDNAMES", zap.Error(initNameToFilesErr))
|
||||
return nil, false
|
||||
}
|
||||
fd, err := getSdListenFd(initNameToFiles, key[len(systemdListenPrefix):])
|
||||
if err != nil {
|
||||
Log().Error("unable to process {"+key+"}", zap.Error(err))
|
||||
return nil, false
|
||||
}
|
||||
return fd, true
|
||||
}
|
||||
|
||||
// TODO const systemdCredsPrefix = "systemd.creds."
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var globalReplacementProviders = []replacementProvider{
|
||||
defaultReplacementProvider{},
|
||||
fileReplacementProvider{},
|
||||
systemdReplacementProvider{},
|
||||
}
|
||||
|
|
@ -374,10 +374,6 @@ func TestReplacerMap(t *testing.T) {
|
|||
func TestReplacerNew(t *testing.T) {
|
||||
repl := NewReplacer()
|
||||
|
||||
if len(repl.providers) != 3 {
|
||||
t.Errorf("Expected providers length '%v' got length '%v'", 3, len(repl.providers))
|
||||
}
|
||||
|
||||
// test if default global replacements are added as the first provider
|
||||
hostname, _ := os.Hostname()
|
||||
wd, _ := os.Getwd()
|
||||
|
|
|
|||
376
replacer_test_systemd.go
Normal file
376
replacer_test_systemd.go
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
//go:build linux && !nosystemd
|
||||
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestGetSdListenFd tests the getSdListenFd function for systemd socket activation.
|
||||
func TestGetSdListenFd(t *testing.T) {
|
||||
// Save original environment
|
||||
originalFdNames := os.Getenv("LISTEN_FDNAMES")
|
||||
originalFds := os.Getenv("LISTEN_FDS")
|
||||
originalPid := os.Getenv("LISTEN_PID")
|
||||
|
||||
// Restore environment after test
|
||||
defer func() {
|
||||
if originalFdNames != "" {
|
||||
os.Setenv("LISTEN_FDNAMES", originalFdNames)
|
||||
} else {
|
||||
os.Unsetenv("LISTEN_FDNAMES")
|
||||
}
|
||||
if originalFds != "" {
|
||||
os.Setenv("LISTEN_FDS", originalFds)
|
||||
} else {
|
||||
os.Unsetenv("LISTEN_FDS")
|
||||
}
|
||||
if originalPid != "" {
|
||||
os.Setenv("LISTEN_PID", originalPid)
|
||||
} else {
|
||||
os.Unsetenv("LISTEN_PID")
|
||||
}
|
||||
}()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fdNames string
|
||||
fds string
|
||||
socketName string
|
||||
expectedFd uint
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "simple http socket",
|
||||
fdNames: "http",
|
||||
fds: "1",
|
||||
socketName: "http",
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
name: "multiple different sockets - first",
|
||||
fdNames: "http:https:dns",
|
||||
fds: "3",
|
||||
socketName: "http",
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
name: "multiple different sockets - second",
|
||||
fdNames: "http:https:dns",
|
||||
fds: "3",
|
||||
socketName: "https",
|
||||
expectedFd: 4,
|
||||
},
|
||||
{
|
||||
name: "multiple different sockets - third",
|
||||
fdNames: "http:https:dns",
|
||||
fds: "3",
|
||||
socketName: "dns",
|
||||
expectedFd: 5,
|
||||
},
|
||||
{
|
||||
name: "duplicate names - first occurrence (no index)",
|
||||
fdNames: "web:web:api",
|
||||
fds: "3",
|
||||
socketName: "web",
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
name: "duplicate names - first occurrence (explicit index 0)",
|
||||
fdNames: "web:web:api",
|
||||
fds: "3",
|
||||
socketName: "web:0",
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
name: "duplicate names - second occurrence (index 1)",
|
||||
fdNames: "web:web:api",
|
||||
fds: "3",
|
||||
socketName: "web:1",
|
||||
expectedFd: 4,
|
||||
},
|
||||
{
|
||||
name: "complex duplicates - first api",
|
||||
fdNames: "web:api:web:api:dns",
|
||||
fds: "5",
|
||||
socketName: "api:0",
|
||||
expectedFd: 4,
|
||||
},
|
||||
{
|
||||
name: "complex duplicates - second api",
|
||||
fdNames: "web:api:web:api:dns",
|
||||
fds: "5",
|
||||
socketName: "api:1",
|
||||
expectedFd: 6,
|
||||
},
|
||||
{
|
||||
name: "complex duplicates - first web",
|
||||
fdNames: "web:api:web:api:dns",
|
||||
fds: "5",
|
||||
socketName: "web:0",
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
name: "complex duplicates - second web",
|
||||
fdNames: "web:api:web:api:dns",
|
||||
fds: "5",
|
||||
socketName: "web:1",
|
||||
expectedFd: 5,
|
||||
},
|
||||
{
|
||||
name: "socket not found",
|
||||
fdNames: "http:https",
|
||||
fds: "2",
|
||||
socketName: "missing",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "empty socket name",
|
||||
fdNames: "http",
|
||||
fds: "1",
|
||||
socketName: "",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "missing LISTEN_FDNAMES",
|
||||
fdNames: "",
|
||||
fds: "",
|
||||
socketName: "http",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "index out of range",
|
||||
fdNames: "web:web",
|
||||
fds: "2",
|
||||
socketName: "web:2",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "negative index",
|
||||
fdNames: "web",
|
||||
fds: "1",
|
||||
socketName: "web:-1",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "invalid index format",
|
||||
fdNames: "web",
|
||||
fds: "1",
|
||||
socketName: "web:abc",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "too many colons",
|
||||
fdNames: "web",
|
||||
fds: "1",
|
||||
socketName: "web:0:extra",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Set up environment
|
||||
if tc.fdNames != "" {
|
||||
os.Setenv("LISTEN_FDNAMES", tc.fdNames)
|
||||
} else {
|
||||
os.Unsetenv("LISTEN_FDNAMES")
|
||||
}
|
||||
|
||||
if tc.fds != "" {
|
||||
os.Setenv("LISTEN_FDS", tc.fds)
|
||||
} else {
|
||||
os.Unsetenv("LISTEN_FDS")
|
||||
}
|
||||
|
||||
os.Setenv("LISTEN_PID", strconv.Itoa(os.Getpid()))
|
||||
|
||||
// Test the function
|
||||
var (
|
||||
listenFdsWithNames map[string][]uint
|
||||
err error
|
||||
fd uint
|
||||
)
|
||||
listenFdsWithNames, err = sdListenFdsWithNames()
|
||||
if err == nil {
|
||||
fd, err = getSdListenFd(listenFdsWithNames, tc.socketName)
|
||||
}
|
||||
|
||||
if tc.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("Expected error but got none")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error but got: %v", err)
|
||||
}
|
||||
if fd != tc.expectedFd {
|
||||
t.Errorf("Expected FD %d but got %d", tc.expectedFd, fd)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseSystemdListenPlaceholder tests parsing of {systemd.listen.name} placeholders.
|
||||
func TestParseSystemdListenPlaceholder(t *testing.T) {
|
||||
// Save and restore environment
|
||||
originalFdNames := os.Getenv("LISTEN_FDNAMES")
|
||||
originalFds := os.Getenv("LISTEN_FDS")
|
||||
originalPid := os.Getenv("LISTEN_PID")
|
||||
|
||||
defer func() {
|
||||
if originalFdNames != "" {
|
||||
os.Setenv("LISTEN_FDNAMES", originalFdNames)
|
||||
} else {
|
||||
os.Unsetenv("LISTEN_FDNAMES")
|
||||
}
|
||||
if originalFds != "" {
|
||||
os.Setenv("LISTEN_FDS", originalFds)
|
||||
} else {
|
||||
os.Unsetenv("LISTEN_FDS")
|
||||
}
|
||||
if originalPid != "" {
|
||||
os.Setenv("LISTEN_PID", originalPid)
|
||||
} else {
|
||||
os.Unsetenv("LISTEN_PID")
|
||||
}
|
||||
}()
|
||||
|
||||
// Set up test environment
|
||||
os.Setenv("LISTEN_FDNAMES", "http:https:dns")
|
||||
os.Setenv("LISTEN_FDS", "3")
|
||||
os.Setenv("LISTEN_PID", strconv.Itoa(os.Getpid()))
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
expectedAddr NetworkAddress
|
||||
expectedFd uint
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
input: "fd/{systemd.listen.http}",
|
||||
expectedAddr: NetworkAddress{
|
||||
Network: "fd",
|
||||
Host: "{systemd.listen.http}",
|
||||
},
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
input: "fd/{systemd.listen.https}",
|
||||
expectedAddr: NetworkAddress{
|
||||
Network: "fd",
|
||||
Host: "{systemd.listen.https}",
|
||||
},
|
||||
expectedFd: 4,
|
||||
},
|
||||
{
|
||||
input: "fd/{systemd.listen.dns}",
|
||||
expectedAddr: NetworkAddress{
|
||||
Network: "fd",
|
||||
Host: "{systemd.listen.dns}",
|
||||
},
|
||||
expectedFd: 5,
|
||||
},
|
||||
{
|
||||
input: "fd/{systemd.listen.http:0}",
|
||||
expectedAddr: NetworkAddress{
|
||||
Network: "fd",
|
||||
Host: "{systemd.listen.http:0}",
|
||||
},
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
input: "fd/{systemd.listen.https:0}",
|
||||
expectedAddr: NetworkAddress{
|
||||
Network: "fd",
|
||||
Host: "{systemd.listen.https:0}",
|
||||
},
|
||||
expectedFd: 4,
|
||||
},
|
||||
{
|
||||
input: "fdgram/{systemd.listen.http}",
|
||||
expectedAddr: NetworkAddress{
|
||||
Network: "fdgram",
|
||||
Host: "{systemd.listen.http}",
|
||||
},
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
input: "fdgram/{systemd.listen.https}",
|
||||
expectedAddr: NetworkAddress{
|
||||
Network: "fdgram",
|
||||
Host: "{systemd.listen.https}",
|
||||
},
|
||||
expectedFd: 4,
|
||||
},
|
||||
{
|
||||
input: "fdgram/{systemd.listen.http:0}",
|
||||
expectedAddr: NetworkAddress{
|
||||
Network: "fdgram",
|
||||
Host: "http:0",
|
||||
},
|
||||
expectedFd: 3,
|
||||
},
|
||||
{
|
||||
input: "fd/{systemd.listen.nonexistent}",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
input: "fdgram/{systemd.listen.nonexistent}",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
input: "fd/{systemd.listen.http:99}",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
input: "fd/{systemd.listen.invalid:abc}",
|
||||
expectErr: true,
|
||||
},
|
||||
// Test that old fd/N syntax still works
|
||||
{
|
||||
input: "fd/7",
|
||||
expectedAddr: NetworkAddress{
|
||||
Network: "fd",
|
||||
Host: "7",
|
||||
},
|
||||
expectedFd: 7,
|
||||
},
|
||||
{
|
||||
input: "fdgram/8",
|
||||
expectedAddr: NetworkAddress{
|
||||
Network: "fdgram",
|
||||
Host: "8",
|
||||
},
|
||||
expectedFd: 8,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
actualAddr, err := ParseNetworkAddress(tc.input)
|
||||
if err == nil {
|
||||
var fd uint
|
||||
fdWide, err := strconv.ParseUint(actualAddr.Host, 0, strconv.IntSize)
|
||||
if err == nil {
|
||||
fd = uint(fdWide)
|
||||
}
|
||||
|
||||
if tc.expectErr && err == nil {
|
||||
t.Errorf("Test %d (%s): Expected error but got none", i, tc.input)
|
||||
}
|
||||
if !tc.expectErr && err != nil {
|
||||
t.Errorf("Test %d (%s): Expected no error but got: %v", i, tc.input, err)
|
||||
}
|
||||
if !tc.expectErr && !reflect.DeepEqual(tc.expectedAddr, actualAddr) {
|
||||
t.Errorf("Test %d (%s): Expected %+v but got %+v", i, tc.input, tc.expectedAddr, actualAddr)
|
||||
}
|
||||
if !tc.expectErr && fd != tc.expectedFd {
|
||||
t.Errorf("Expected FD %d but got %d", tc.expectedFd, fd)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue