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
|
package caddy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"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),
|
static: make(map[string]any),
|
||||||
mapMutex: &sync.RWMutex{},
|
mapMutex: &sync.RWMutex{},
|
||||||
}
|
}
|
||||||
rep.providers = []replacementProvider{
|
rep.providers = append(globalReplacementProviders, ReplacerFunc(rep.fromStatic))
|
||||||
globalDefaultReplacementProvider{},
|
|
||||||
fileReplacementProvider{},
|
|
||||||
ReplacerFunc(rep.fromStatic),
|
|
||||||
}
|
|
||||||
return rep
|
return rep
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEmptyReplacer returns a new Replacer,
|
// NewEmptyReplacer returns a new Replacer,
|
||||||
// without the global default replacements.
|
// without the global replacements.
|
||||||
func NewEmptyReplacer() *Replacer {
|
func NewEmptyReplacer() *Replacer {
|
||||||
rep := &Replacer{
|
rep := &Replacer{
|
||||||
static: make(map[string]any),
|
static: make(map[string]any),
|
||||||
|
|
@ -360,12 +356,11 @@ func (f fileReplacementProvider) replace(key string) (any, bool) {
|
||||||
return string(body), true
|
return string(body), true
|
||||||
}
|
}
|
||||||
|
|
||||||
// globalDefaultReplacementProvider handles replacements
|
// defaultReplacementProvider handles replacements
|
||||||
// that can be used in any context, such as system variables,
|
// such as system variables, time, or environment variables.
|
||||||
// time, or environment variables.
|
type defaultReplacementProvider struct{}
|
||||||
type globalDefaultReplacementProvider struct{}
|
|
||||||
|
|
||||||
func (f globalDefaultReplacementProvider) replace(key string) (any, bool) {
|
func (f defaultReplacementProvider) replace(key string) (any, bool) {
|
||||||
// check environment variable
|
// check environment variable
|
||||||
const envPrefix = "env."
|
const envPrefix = "env."
|
||||||
if strings.HasPrefix(key, envPrefix) {
|
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) {
|
func TestReplacerNew(t *testing.T) {
|
||||||
repl := NewReplacer()
|
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
|
// test if default global replacements are added as the first provider
|
||||||
hostname, _ := os.Hostname()
|
hostname, _ := os.Hostname()
|
||||||
wd, _ := os.Getwd()
|
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