Skip to content

Commit

Permalink
systemd fixes and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
et-nik committed Jun 2, 2024
1 parent 3b1d31a commit fc51c52
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 10 deletions.
3 changes: 2 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,15 @@ linters:
- whitespace
issues:
exclude-rules:
- path: test
- path: _test\.go|test
linters:
- funlen
- lll
- dupl
- gosec
- scopelint
- govet
- gocognit
- path: internal/app/di
linters:
- govet
Expand Down
51 changes: 42 additions & 9 deletions internal/processmanager/systemd.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
//go:build linux
// +build linux

package processmanager

import (
"context"
"fmt"
"io"
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"
Expand All @@ -16,6 +14,7 @@ import (
"github.com/gameap/daemon/internal/app/contracts"
"github.com/gameap/daemon/internal/app/domain"
"github.com/gameap/daemon/pkg/logger"
"github.com/gameap/daemon/pkg/shellquote"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -333,13 +332,12 @@ func (pm *SystemD) buildServiceConfig(server *domain.Server) (string, error) {

builder.WriteString("Type=simple\n")

cmd, err := pm.makeStartCommand(server)
if err != nil {
return "", errors.WithMessage(err, "failed to make command")
}
builder.WriteString("ExecStart=")
builder.WriteString(
filepath.Join(
server.WorkDir(pm.cfg),
domain.MakeFullCommand(pm.cfg, server, pm.cfg.Scripts.Start, server.StartCommand()),
),
)
builder.WriteString(cmd)
builder.WriteString("\n")

builder.WriteString("Sockets=")
Expand Down Expand Up @@ -385,6 +383,41 @@ func (pm *SystemD) buildServiceConfig(server *domain.Server) (string, error) {
return builder.String(), nil
}

func (pm *SystemD) makeStartCommand(server *domain.Server) (string, error) {
startCMD := server.StartCommand()

parts, err := shellquote.Split(startCMD)
if err != nil {
return "", errors.WithMessage(err, "failed to split command")
}

cmd := parts[0]
args := parts[1:]

var foundPath string

if !filepath.IsAbs(cmd) {
foundPath, err = exec.LookPath(filepath.Join(server.WorkDir(pm.cfg), cmd))
if err != nil {
foundPath, err = exec.LookPath(cmd)
if err != nil {
return "", errors.WithMessagef(err, "failed to find command '%s'", cmd)
}
}
}

if filepath.IsAbs(cmd) {
foundPath, err = exec.LookPath(cmd)
if err != nil {
return "", errors.WithMessagef(err, "failed to find command '%s'", cmd)
}
}

startCommand := shellquote.Join(append([]string{foundPath}, args...)...)

return domain.MakeFullCommand(pm.cfg, server, pm.cfg.Scripts.Start, startCommand), nil
}

func (pm *SystemD) makeSocket(ctx context.Context, server *domain.Server) error {
f, err := os.OpenFile(pm.socketFile(server), os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
Expand Down
209 changes: 209 additions & 0 deletions internal/processmanager/systemd_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package processmanager

import (
"os"
"path/filepath"
"testing"
"time"

"github.com/gameap/daemon/internal/app/config"
"github.com/gameap/daemon/internal/app/domain"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_makeCommand(t *testing.T) {
tempDir, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(errors.WithMessage(err, "failed to create temp dir"))
}

tests := []struct {
name string
server func() *domain.Server
expectedCommand string
expectedError string
}{
{
name: "success with local file",
server: func() *domain.Server {
f, err := os.OpenFile(filepath.Join(tempDir, "start.sh"), os.O_CREATE, 0755)
if err != nil {
t.Fatal(errors.WithMessage(err, "failed to create start.sh"))
}
err = f.Close()
if err != nil {
t.Fatal(errors.WithMessage(err, "failed to close start.sh"))
}

return makeServerWithStartCommandAndDir("start.sh start command", tempDir)
},
expectedCommand: filepath.Join(tempDir, "start.sh start command"),
},
{
name: "success with local file and dot",
server: func() *domain.Server {
f, err := os.OpenFile(filepath.Join(tempDir, "start.sh"), os.O_CREATE, 0755)
if err != nil {
t.Fatal(errors.WithMessage(err, "failed to create start.sh"))
}
err = f.Close()
if err != nil {
t.Fatal(errors.WithMessage(err, "failed to close start.sh"))
}

return makeServerWithStartCommandAndDir("./start.sh start command", tempDir)
},
expectedCommand: filepath.Join(tempDir, "start.sh start command"),
},
{
name: "success with local file and quotes",
server: func() *domain.Server {
f, err := os.OpenFile(filepath.Join(tempDir, "start.sh"), os.O_CREATE, 0755)
if err != nil {
t.Fatal(errors.WithMessage(err, "failed to create start.sh"))
}
err = f.Close()
if err != nil {
t.Fatal(errors.WithMessage(err, "failed to close start.sh"))
}

return makeServerWithStartCommandAndDir("./start.sh 'some quotes' \"some quotes\" args", tempDir)
},
expectedCommand: filepath.Join(tempDir, "./start.sh 'some quotes' 'some quotes' args"),
},
{
name: "success with global file",
server: func() *domain.Server {
return makeServerWithStartCommandAndDir("env --help", tempDir)
},
expectedCommand: "/usr/bin/env --help",
},
{
name: "success with abs path",
server: func() *domain.Server {
return makeServerWithStartCommandAndDir("/usr/bin/env --help", tempDir)
},
expectedCommand: "/usr/bin/env --help",
},
{
name: "success with both existing file",
server: func() *domain.Server {
f, err := os.OpenFile(filepath.Join(tempDir, "env"), os.O_CREATE, 0755)
if err != nil {
t.Fatal(errors.WithMessage(err, "failed to create env file"))
}
err = f.Close()
if err != nil {
t.Fatal(errors.WithMessage(err, "failed to close env file"))
}

return makeServerWithStartCommandAndDir("env --help", tempDir)
},
expectedCommand: filepath.Join(tempDir, "env --help"),
},
{
name: "success with both existing file and dot",
server: func() *domain.Server {
f, err := os.OpenFile(filepath.Join(tempDir, "env"), os.O_CREATE, 0755)
if err != nil {
t.Fatal(errors.WithMessage(err, "failed to create env file"))
}
err = f.Close()
if err != nil {
t.Fatal(errors.WithMessage(err, "failed to close env file"))
}

return makeServerWithStartCommandAndDir("./env --help", tempDir)
},
expectedCommand: filepath.Join(tempDir, "env --help"),
},
{
name: "error invalid command",
server: func() *domain.Server {
return makeServerWithStartCommandAndDir("invalid", tempDir)
},
expectedError: `failed to find command 'invalid'`,
},
{
name: "error invalid global command",
server: func() *domain.Server {
return makeServerWithStartCommandAndDir("/usr/bin/invalid", tempDir)
},
expectedError: `failed to find command '/usr/bin/invalid'`,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := filepath.Walk(tempDir, func(path string, _ os.FileInfo, err error) error {
if tempDir == path {
return nil
}

if err != nil {
return err
}
return os.RemoveAll(path)
})
if err != nil {
t.Fatal(errors.WithMessage(err, "failed to remove temp dir content"))
}

server := test.server()
systemd := NewSystemD(&config.Config{
WorkPath: "",
Scripts: config.Scripts{
Start: "{command}",
},
}, nil, nil)
command, err := systemd.makeStartCommand(server)

if test.expectedError != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), test.expectedError)
} else {
require.NoError(t, err)
assert.Equal(t, test.expectedCommand, command)
}
})
}
}

func makeServerWithStartCommandAndDir(startCommand, dir string) *domain.Server {
return domain.NewServer(
1337,
true,
domain.ServerInstalled,
false,
"name",
"759b875e-d910-11eb-aff7-d796d7fcf7ef",
"759b875e",
domain.Game{
StartCode: "cstrike",
},
domain.GameMod{
Name: "public",
},
"1.3.3.7",
1337,
1338,
1339,
"paS$w0rD",
dir,
"gameap-user",
startCommand,
"",
"",
"",
true,
time.Now(),
map[string]string{
"default_map": "de_dust2",
"tickrate": "1000",
},
map[string]string{},
time.Now(),
)
}
4 changes: 4 additions & 0 deletions pkg/shellquote/unquote.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ func Split(input string) (words []string, err error) {

return shellquote.Split(input)
}

func Join(words ...string) string {
return shellquote.Join(words...)
}

0 comments on commit fc51c52

Please sign in to comment.