diff --git a/cmd/svc.go b/cmd/svc.go index cc8d6bb54..7fec708ff 100644 --- a/cmd/svc.go +++ b/cmd/svc.go @@ -232,22 +232,21 @@ func buildExecuteCmd() *cobra.Command { } const systemdScript = `[Unit] -Description={{.Description}} -ConditionFileIsExecutable={{.Path|cmdEscape}} -{{range $i, $dep := .Dependencies}} -{{$dep}} {{end}} - +Description={{Description}} +ConditionFileIsExecutable={{Path | cmdEscape}} +{{range Dependencies}}{{.}} +{{end}} [Service] StartLimitInterval=5 StartLimitBurst=10 -ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}} -{{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}} -{{if .UserName}}User={{.UserName}}{{end}} -{{if .Restart}}Restart={{.Restart}}{{end}} -{{if .SuccessExitStatus}}SuccessExitStatus={{.SuccessExitStatus}}{{end}} +ExecStart={{Path | cmdEscape}}{{range Arguments}} {{. | cmd}}{{end}} +{{if WorkingDirectory}}WorkingDirectory={{WorkingDirectory | cmdEscape}}{{end}} +{{if UserName}}User={{UserName}}{{end}} +{{if Restart}}Restart={{Restart}}{{end}} +{{if SuccessExitStatus}}SuccessExitStatus={{SuccessExitStatus}}{{end}} TimeoutStopSec=20 RestartSec=120 -EnvironmentFile=-/etc/sysconfig/{{.Name}} +EnvironmentFile=-/etc/sysconfig/{{Name}} Environment="ND_SYSTEMD_PRIORITY_LOGGING=1" DevicePolicy=closed @@ -260,7 +259,7 @@ RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 RestrictNamespaces=yes RestrictRealtime=yes SystemCallFilter=~@clock @debug @module @mount @obsolete @reboot @setuid @swap -{{if .WorkingDirectory}}ReadWritePaths={{.WorkingDirectory|cmdEscape}}{{end}} +{{if WorkingDirectory}}ReadWritePaths={{WorkingDirectory | cmdEscape}}{{end}} ProtectSystem=full [Install] diff --git a/cmd/svc_test.go b/cmd/svc_test.go new file mode 100644 index 000000000..7c34563b3 --- /dev/null +++ b/cmd/svc_test.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "regexp" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("systemdScript template", func() { + systemdKeys := map[string]bool{ + "Description": true, "Path": true, "Name": true, "Dependencies": true, + "Arguments": true, "ChRoot": true, "WorkingDirectory": true, + "UserName": true, "ReloadSignal": true, "PIDFile": true, + "LogDirectory": true, "OutputFileSupport": true, "LimitNOFILE": true, + "Restart": true, "SuccessExitStatus": true, "EnvVars": true, + } + systemdFuncs := map[string]bool{"cmd": true, "cmdEscape": true} + + actionRe := regexp.MustCompile(`\{\{(.*?)\}\}`) + + parseAction := func(action string) (key string, funcs []string) { + action = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(action, "-"), "-")) + kw, rest, _ := strings.Cut(action, " ") + switch kw { + case "end", "else": + return "", nil + case "if", "range": + return strings.TrimSpace(rest), nil + } + parts := strings.Split(action, "|") + for _, p := range parts[1:] { + funcs = append(funcs, strings.TrimSpace(p)) + } + return strings.TrimSpace(parts[0]), funcs + } + + It("only references keys and functions the service library provides", func() { + matches := actionRe.FindAllStringSubmatch(systemdScript, -1) + Expect(matches).ToNot(BeEmpty()) + + for _, m := range matches { + key, funcs := parseAction(m[1]) + if key != "" && key != "." { + Expect(systemdKeys).To(HaveKey(key), + "template action %q uses a key unknown to kardianos/service", m[0]) + } + for _, fn := range funcs { + Expect(systemdFuncs).To(HaveKey(fn), + "template action %q uses an unknown pipeline function", m[0]) + } + } + }) +})