diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 00000000..27e0580a --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,63 @@ +name: Linter + +on: [pull_request] + +jobs: + go-staticcheck: + name: go-staticcheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-go@v3 + with: + go-version: 1.23.x + + - name: Install go-staticcheck + run: | + go get honnef.co/go/tools/cmd/staticcheck@latest # ideally we should version pin + + - name: Run go-staticcheck + run: | + PKGS=$(go list ./... | grep -v /vendor/ | grep -v example ) + go run honnef.co/go/tools/cmd/staticcheck $PKGS + + go-vet: + name: go-vet + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-go@v3 + with: + go-version: 1.23.x + + - name: Run go-vet + run: | + PKGS=$(go list ./... | grep -v /vendor/ | grep -v example ) + go vet -json $PKGS | tee vet-report.json + + - name: Upload results + uses: actions/upload-artifact@v3 + with: + name: go-vet-results + path: vet-report.json + + + golangci-lint: + name: golangci-lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-go@v3 + with: + go-version: 1.23.x + + - name: Install golangci-lint + run: | + go get github.com/golangci/golangci-lint/cmd/golangci-lint@latest + + - name: Run golangci-lint + run: | + go run github.com/golangci/golangci-lint/cmd/golangci-lint run -v --timeout 10m0s diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml new file mode 100644 index 00000000..6582b788 --- /dev/null +++ b/.github/workflows/unit-test.yml @@ -0,0 +1,84 @@ +# This workflow will install Go dependencies, run tests for 4 versions of Go +# For more information see: https://support.github.com/features/actions + +name: Unit tests + +on: + pull_request: + paths: + - ".github/workflows/*.yml" + - "**/*.go" + - "**/go.mod" + - "**/go.sum" + +permissions: + contents: read + pull-requests: write + +jobs: + go-test: + name: Run go unit tests + strategy: + matrix: + go-version: ["1.18", "1.19", "1.23"] + runs-on: ubuntu-latest + + steps: + - name: "Install Go" + uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go-version }} + + - uses: actions/checkout@v3 + + - name: Install test dependencies + run: | + go get github.com/boumenot/gocover-cobertura + + - name: Install project dependencies + run: go build + + - name: Run test + run: | + # run test: + PKGS=$(go list ./... | grep -v /vendor/) + DEPS=$(go list ./... | grep -v vendor | grep -v test | xargs | sed 's/ /,/g') + go test ${PKGS} -v \ + -coverprofile=coverage_${{ matrix.go-version }}.out \ + -covermode=count \ + -coverpkg ${DEPS} 2>&1 + + - name: Generate code coverage report + run: | + go run github.com/boumenot/gocover-cobertura < coverage_${{ matrix.go-version }}.out > coverage-unit_${{ matrix.go-version }}.xml + + - name: compute valid coverage total + run: | + go tool cover -func=coverage_${{ matrix.go-version }}.out + + - name: Code Coverage Report + uses: irongut/CodeCoverageSummary@v1.3.0 + if: matrix.go-version == '1.19' + with: + # will generate code-coverage-results.md + filename: coverage-unit_*.xml + badge: true + fail_below_min: true + format: markdown + hide_branch_rate: false + hide_complexity: true + indicators: true + output: both + thresholds: "5 10" + + - name: Add coverage comment to PR + uses: marocchino/sticky-pull-request-comment@v2 + if: matrix.go-version == '1.19' && github.event_name == 'pull_request' + with: + path: code-coverage-results.md + + - name: Upload coverage results + uses: actions/upload-artifact@v3 + with: + name: go-coverage-results + path: coverage-unit_*.xml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..6028d40f --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,30 @@ +linters: + # Enable all available linters. + # Default: false + enable-all: true + # Disable specific linter + # https://golangci-lint.run/usage/linters/#disabled-by-default + disable: + - copyloopvar # we use a too old go version + - intrange # we use a too old go version + - execinquery # deprecated + - exportloopref # deprecated + # - depguard + # - gci + # - godox + # - gofumpt + - gomnd + - wsl + - wrapcheck # old code => no wrapped error + - varnamelen + - paralleltest + - ireturn + - mnd + - testpackage + - err113 # old code => no wrapped error + - stylecheck + - cyclop + # - revive + - nlreturn + - exhaustruct + diff --git a/console.go b/console.go index ff033753..c752ffbd 100644 --- a/console.go +++ b/console.go @@ -10,13 +10,13 @@ import ( ) // ConsoleLogger logs to the std err. -var ConsoleLogger = consoleLogger{} +var ConsoleLogger = consoleLogger{} //nolint:gochecknoglobals type consoleLogger struct { info, warn, err *log.Logger } -func init() { +func init() { //nolint:gochecknoinits ConsoleLogger.info = log.New(os.Stderr, "I: ", log.Ltime) ConsoleLogger.warn = log.New(os.Stderr, "W: ", log.Ltime) ConsoleLogger.err = log.New(os.Stderr, "E: ", log.Ltime) @@ -26,22 +26,27 @@ func (c consoleLogger) Error(v ...interface{}) error { c.err.Print(v...) return nil } + func (c consoleLogger) Warning(v ...interface{}) error { c.warn.Print(v...) return nil } + func (c consoleLogger) Info(v ...interface{}) error { c.info.Print(v...) return nil } + func (c consoleLogger) Errorf(format string, a ...interface{}) error { c.err.Printf(format, a...) return nil } + func (c consoleLogger) Warningf(format string, a ...interface{}) error { c.warn.Printf(format, a...) return nil } + func (c consoleLogger) Infof(format string, a ...interface{}) error { c.info.Printf(format, a...) return nil diff --git a/example/logging/main.go b/example/logging/main.go index 976c7459..045488ac 100644 --- a/example/logging/main.go +++ b/example/logging/main.go @@ -10,10 +10,10 @@ import ( "log" "time" - "github.com/kardianos/service" + "github.com/kardianos/service" //nolint:depguard ) -var logger service.Logger +var logger service.Logger //nolint:gochecknoglobals // Program structures. // @@ -22,7 +22,8 @@ type program struct { exit chan struct{} } -func (p *program) Start(s service.Service) error { +//nolint:errcheck +func (p *program) Start(_ service.Service) error { if service.Interactive() { logger.Info("Running in terminal.") } else { @@ -34,6 +35,8 @@ func (p *program) Start(s service.Service) error { go p.run() return nil } + +//nolint:errcheck,unparam func (p *program) run() error { logger.Infof("I'm running %v.", service.Platform()) ticker := time.NewTicker(2 * time.Second) @@ -47,7 +50,9 @@ func (p *program) run() error { } } } -func (p *program) Stop(s service.Service) error { + +//nolint:errcheck +func (p *program) Stop(_ service.Service) error { // Any work in Stop should be quick, usually a few seconds at most. logger.Info("I'm Stopping!") close(p.exit) @@ -74,7 +79,8 @@ func main() { Description: "This is an example Go service that outputs log messages.", Dependencies: []string{ "Requires=network.target", - "After=network-online.target syslog.target"}, + "After=network-online.target syslog.target", + }, Option: options, } @@ -101,13 +107,16 @@ func main() { if len(*svcFlag) != 0 { err := service.Control(s, *svcFlag) if err != nil { - log.Printf("Valid actions: %q\n", service.ControlAction) + log.Printf("Valid actions: %q\n", []string{ + service.ControlActionStart, service.ControlActionStop, + service.ControlActionRestart, service.ControlActionInstall, service.ControlActionUninstall, + }) log.Fatal(err) } return } err = s.Run() if err != nil { - logger.Error(err) + _ = logger.Error(err) } } diff --git a/example/runner/runner.go b/example/runner/runner.go index 8729cf4e..c0600c7a 100644 --- a/example/runner/runner.go +++ b/example/runner/runner.go @@ -14,22 +14,25 @@ import ( "os/exec" "path/filepath" - "github.com/kardianos/service" + "github.com/kardianos/service" //nolint:depguard ) // Config is the runner app config structure. type Config struct { - Name, DisplayName, Description string + Name string `json:"name"` + DisplayName string `json:"displyName"` + Description string `json:"description"` - Dir string - Exec string - Args []string - Env []string + Dir string `json:"dir"` + Exec string `json:"exec"` + Args []string `json:"args"` + Env []string `json:"env"` - Stderr, Stdout string + Stderr string `json:"stderr"` + Stdout string `json:"stdout"` } -var logger service.Logger +var logger service.Logger //nolint:gochecknoglobals type program struct { exit chan struct{} @@ -40,12 +43,12 @@ type program struct { cmd *exec.Cmd } -func (p *program) Start(s service.Service) error { +func (p *program) Start(_ service.Service) error { // Look for exec. // Verify home directory. fullExec, err := exec.LookPath(p.Exec) if err != nil { - return fmt.Errorf("Failed to find executable %q: %v", p.Exec, err) + return fmt.Errorf("Failed to find executable %q: %w", p.Exec, err) } p.cmd = exec.Command(fullExec, p.Args...) @@ -55,6 +58,8 @@ func (p *program) Start(s service.Service) error { go p.run() return nil } + +//nolint:errcheck func (p *program) run() { logger.Info("Starting ", p.DisplayName) defer func() { @@ -66,16 +71,16 @@ func (p *program) run() { }() if p.Stderr != "" { - f, err := os.OpenFile(p.Stderr, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777) + f, err := os.OpenFile(p.Stderr, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o777) if err != nil { - logger.Warningf("Failed to open std err %q: %v", p.Stderr, err) + logger.Warningf("Failed to open std err %q: %w", p.Stderr, err) return } defer f.Close() p.cmd.Stderr = f } if p.Stdout != "" { - f, err := os.OpenFile(p.Stdout, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777) + f, err := os.OpenFile(p.Stdout, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o777) if err != nil { logger.Warningf("Failed to open std out %q: %v", p.Stdout, err) return @@ -88,10 +93,10 @@ func (p *program) run() { if err != nil { logger.Warningf("Error running: %v", err) } - - return } -func (p *program) Stop(s service.Service) error { + +//nolint:errcheck +func (p *program) Stop(_ service.Service) error { close(p.exit) logger.Info("Stopping ", p.DisplayName) if p.cmd.Process != nil { @@ -181,13 +186,16 @@ func main() { if len(*svcFlag) != 0 { err := service.Control(s, *svcFlag) if err != nil { - log.Printf("Valid actions: %q\n", service.ControlAction) + log.Printf("Valid actions: %q\n", []string{ + service.ControlActionStart, service.ControlActionStop, + service.ControlActionRestart, service.ControlActionInstall, service.ControlActionUninstall, + }) log.Fatal(err) } return } err = s.Run() if err != nil { - logger.Error(err) + _ = logger.Error(err) } } diff --git a/example/simple/main.go b/example/simple/main.go index 2ec9f462..aee51940 100644 --- a/example/simple/main.go +++ b/example/simple/main.go @@ -8,22 +8,24 @@ package main import ( "log" - "github.com/kardianos/service" + "github.com/kardianos/service" //nolint:depguard ) -var logger service.Logger +var logger service.Logger //nolint:gochecknoglobals type program struct{} -func (p *program) Start(s service.Service) error { +func (p *program) Start(_ service.Service) error { // Start should not block. Do the actual work async. go p.run() return nil } + func (p *program) run() { // Do work here } -func (p *program) Stop(s service.Service) error { + +func (p *program) Stop(_ service.Service) error { // Stop should not block. Return with a few seconds. return nil } @@ -46,6 +48,6 @@ func main() { } err = s.Run() if err != nil { - logger.Error(err) + _ = logger.Error(err) } } diff --git a/example/stopPause/main.go b/example/stopPause/main.go index ec7d598e..bdd35eef 100644 --- a/example/stopPause/main.go +++ b/example/stopPause/main.go @@ -10,22 +10,24 @@ import ( "os" "time" - "github.com/kardianos/service" + "github.com/kardianos/service" //nolint:depguard ) -var logger service.Logger +var logger service.Logger //nolint:gochecknoglobals type program struct{} -func (p *program) Start(s service.Service) error { +func (p *program) Start(_ service.Service) error { // Start should not block. Do the actual work async. go p.run() return nil } + func (p *program) run() { // Do work here } -func (p *program) Stop(s service.Service) error { + +func (p *program) Stop(_ service.Service) error { // Stop should not block. Return with a few seconds. <-time.After(time.Second * 13) return nil @@ -57,6 +59,6 @@ func main() { } err = s.Run() if err != nil { - logger.Error(err) + _ = logger.Error(err) } } diff --git a/go.mod b/go.mod index 55e67ea6..f178cc34 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/kardianos/service -go 1.12 +go 1.18 -require golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 +require golang.org/x/sys v0.20.0 diff --git a/go.sum b/go.sum index 04967d5b..5d1e088e 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88= -golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/service.go b/service.go index 130fc22d..6eeeaad4 100644 --- a/service.go +++ b/service.go @@ -99,10 +99,10 @@ const ( optionLogDirectory = "LogDirectory" ) -// Status represents service status as an byte value +// Status represents service status as an byte value. type Status byte -// Status of service represented as an byte +// Status of service represented as an byte. const ( StatusUnknown Status = iota // Status is unable to be determined due to an error or it was not installed. StatusRunning @@ -141,6 +141,7 @@ type Config struct { EnvVars map[string]string } +//nolint:gochecknoglobals var ( system System systemRegistry []System @@ -148,9 +149,9 @@ var ( var ( // ErrNameFieldRequired is returned when Config.Name is empty. - ErrNameFieldRequired = errors.New("Config.Name field is required.") + ErrNameFieldRequired = errors.New("Config.Name field is required.") //nolint:revive // ErrNoServiceSystemDetected is returned when no system was detected. - ErrNoServiceSystemDetected = errors.New("No service system detected.") + ErrNoServiceSystemDetected = errors.New("No service system detected.") //nolint:revive // ErrNotInstalled is returned when the service is not installed. ErrNotInstalled = errors.New("the service is not installed") ) @@ -231,6 +232,8 @@ func New(i Interface, c *Config) (Service, error) { // - OnFailureDelayDuration string ( "1s" ) - Delay before restarting the service, time.Duration string. // // - OnFailureResetPeriod int ( 10 ) - Reset period for errors, seconds. +// +//nolint:lll type KeyValue map[string]interface{} // bool returns the value of the given name, assuming the value is a boolean. @@ -268,7 +271,7 @@ func (kv KeyValue) string(name string, defaultValue string) string { // float64 returns the value of the given name, assuming the value is a float64. // If the value isn't found or is not of the type, the defaultValue is returned. -func (kv KeyValue) float64(name string, defaultValue float64) float64 { +func (kv KeyValue) float64(name string, defaultValue float64) float64 { //nolint:unused if v, found := kv[name]; found { if castValue, is := v.(float64); is { return castValue @@ -307,7 +310,7 @@ func Interactive() bool { func newSystem() System { for _, choice := range systemRegistry { - if choice.Detect() == false { + if !choice.Detect() { continue } return choice @@ -388,7 +391,7 @@ type Shutdowner interface { // TODO: Add Configure to Service interface. // Service represents a service that can be run or controlled. -type Service interface { +type Service interface { //nolint:interfacebloat // Run should be called shortly after the program entry point. // After Interface.Stop has finished running, Run will stop blocking. // After Run stops blocking, the program must exit shortly after. @@ -434,27 +437,33 @@ type Service interface { } // ControlAction list valid string texts to use in Control. -var ControlAction = [5]string{"start", "stop", "restart", "install", "uninstall"} +const ( + ControlActionStart = "start" + ControlActionStop = "stop" + ControlActionRestart = "restart" + ControlActionInstall = "install" + ControlActionUninstall = "uninstall" +) // Control issues control functions to the service from a given action string. func Control(s Service, action string) error { var err error switch action { - case ControlAction[0]: + case ControlActionStart: err = s.Start() - case ControlAction[1]: + case ControlActionStop: err = s.Stop() - case ControlAction[2]: + case ControlActionRestart: err = s.Restart() - case ControlAction[3]: + case ControlActionInstall: err = s.Install() - case ControlAction[4]: + case ControlActionUninstall: err = s.Uninstall() default: err = fmt.Errorf("Unknown action %s", action) } if err != nil { - return fmt.Errorf("Failed to %s %v: %v", action, s, err) + return fmt.Errorf("Failed to %s %v: %w", action, s, err) } return nil } diff --git a/service_aix.go b/service_aix.go index 2563b753..857716d5 100644 --- a/service_aix.go +++ b/service_aix.go @@ -112,9 +112,8 @@ func (s *aixService) template() *template.Template { } } -func (s *aixService) configPath() (cp string, err error) { - cp = "/etc/rc.d/init.d/" + s.Config.Name - return +func (s *aixService) configPath() (string, error) { + return "/etc/rc.d/init.d/" + s.Config.Name, nil } func (s *aixService) Install() error { diff --git a/service_freebsd.go b/service_freebsd.go index 752f5feb..a4ee0086 100644 --- a/service_freebsd.go +++ b/service_freebsd.go @@ -89,13 +89,12 @@ func (s *freebsdService) template() *template.Template { } } -func (s *freebsdService) configPath() (cp string, err error) { +func (s *freebsdService) configPath() (string, error) { if oserr := os.MkdirAll(configDir, 0755); oserr != nil { - err = oserr - return + return "", oserr } - cp = filepath.Join(configDir, s.Config.Name) - return + + return filepath.Join(configDir, s.Config.Name), nil } func (s *freebsdService) Install() error { diff --git a/service_linux.go b/service_linux.go index cecbb703..a3116fc5 100644 --- a/service_linux.go +++ b/service_linux.go @@ -6,13 +6,17 @@ package service import ( "bufio" + "errors" "fmt" - "io/ioutil" "os" "strings" ) -var cgroupFile = "/proc/1/cgroup" +var ( + cgroupFile = "/proc/1/cgroup" //nolint:gochecknoglobals + mountInfoFile = "/proc/self/mountinfo" //nolint:gochecknoglobals + dockerEnvFile = "/.dockerenv" //nolint:gochecknoglobals +) type linuxSystemService struct { name string @@ -24,17 +28,20 @@ type linuxSystemService struct { func (sc linuxSystemService) String() string { return sc.name } + func (sc linuxSystemService) Detect() bool { return sc.detect() } + func (sc linuxSystemService) Interactive() bool { return sc.interactive() } + func (sc linuxSystemService) New(i Interface, c *Config) (Service, error) { return sc.new(i, sc.String(), c) } -func init() { +func init() { //nolint:gochecknoinits ChooseSystem(linuxSystemService{ name: "linux-systemd", detect: isSystemd, @@ -85,7 +92,7 @@ func init() { func binaryName(pid int) (string, error) { statPath := fmt.Sprintf("/proc/%d/stat", pid) - dataBytes, err := ioutil.ReadFile(statPath) + dataBytes, err := os.ReadFile(statPath) if err != nil { return "", err } @@ -98,7 +105,7 @@ func binaryName(pid int) (string, error) { } func isInteractive() (bool, error) { - inContainer, err := isInContainer(cgroupFile) + inContainer, err := isInContainer() if err != nil { return false, err } @@ -118,7 +125,62 @@ func isInteractive() (bool, error) { // isInContainer checks if the service is being executed in docker or lxc // container. -func isInContainer(cgroupPath string) (bool, error) { +func isInContainer() (bool, error) { + inContainer, err := isInContainerDockerEnv(dockerEnvFile) + if err != nil { + return false, err + } + if inContainer { + return true, nil + } + + inContainer, err = isInContainerCGroup(cgroupFile) + if err != nil { + return false, err + } + if inContainer { + return true, nil + } + + return isInContainerMountInfo(mountInfoFile) +} + +func isInContainerDockerEnv(filePath string) (bool, error) { + _, err := os.Stat(filePath) + if err == nil { + return true, nil + } + if errors.Is(err, os.ErrNotExist) { + return false, nil + } + + return false, err +} + +func isInContainerMountInfo(filePath string) (bool, error) { + const maxlines = 15 // maximum lines to scan + f, err := os.Open(filePath) + if err != nil { + return false, err + } + defer f.Close() + scan := bufio.NewScanner(f) + + lines := 0 + for scan.Scan() && !(lines > maxlines) { + if strings.Contains(scan.Text(), "/docker/containers") { + return true, nil + } + lines++ + } + if err := scan.Err(); err != nil { + return false, err + } + + return false, nil +} + +func isInContainerCGroup(cgroupPath string) (bool, error) { const maxlines = 5 // maximum lines to scan f, err := os.Open(cgroupPath) @@ -142,11 +204,13 @@ func isInContainer(cgroupPath string) (bool, error) { return false, nil } -var tf = map[string]interface{}{ - "cmd": func(s string) string { - return `"` + strings.Replace(s, `"`, `\"`, -1) + `"` - }, - "cmdEscape": func(s string) string { - return strings.Replace(s, " ", `\x20`, -1) - }, +func getTemplateFunctions() map[string]interface{} { + return map[string]interface{}{ + "cmd": func(s string) string { + return `"` + strings.ReplaceAll(s, `"`, `\"`) + `"` + }, + "cmdEscape": func(s string) string { + return strings.ReplaceAll(s, " ", `\x20`) + }, + } } diff --git a/service_linux_test.go b/service_linux_test.go index c88b940c..419bc845 100644 --- a/service_linux_test.go +++ b/service_linux_test.go @@ -6,29 +6,28 @@ package service import ( "errors" - "io/ioutil" "os" "testing" ) -// createTestCgroupFiles creates mock files for tests +// createTestCgroupFiles creates mock files for tests. func createTestCgroupFiles() (*os.File, *os.File, error) { // docker cgroup setup - hDockerGrp, err := ioutil.TempFile("", "*") + hDockerGrp, err := os.CreateTemp("", "*") if err != nil { return nil, nil, errors.New("docker tempfile create failed") } - _, err = hDockerGrp.Write([]byte(dockerCgroup)) + _, err = hDockerGrp.WriteString(dockerCgroup) if err != nil { return nil, nil, errors.New("docker tempfile write failed") } // linux cgroup setup - hLinuxGrp, err := ioutil.TempFile("", "*") + hLinuxGrp, err := os.CreateTemp("", "*") if err != nil { return nil, nil, errors.New("\"normal\" tempfile create failed") } - _, err = hLinuxGrp.Write([]byte(linuxCgroup)) + _, err = hLinuxGrp.WriteString(linuxCgroup) if err != nil { return nil, nil, errors.New("\"normal\" tempfile write failed") } @@ -36,14 +35,38 @@ func createTestCgroupFiles() (*os.File, *os.File, error) { return hDockerGrp, hLinuxGrp, nil } -// removeTestFile closes and removes the provided file +// createTestMountInfoFiles creates mock files for tests. +func createTestMountInfoFiles() (*os.File, *os.File, error) { + // docker cgroup setup + hDockerGrp, err := os.CreateTemp("", "*") + if err != nil { + return nil, nil, errors.New("docker tempfile create failed") + } + _, err = hDockerGrp.WriteString(dockerMountInfo) + if err != nil { + return nil, nil, errors.New("docker tempfile write failed") + } + + // linux cgroup setup + hLinuxGrp, err := os.CreateTemp("", "*") + if err != nil { + return nil, nil, errors.New("\"normal\" tempfile create failed") + } + _, err = hLinuxGrp.WriteString(linuxMountInfo) + if err != nil { + return nil, nil, errors.New("\"normal\" tempfile write failed") + } + + return hDockerGrp, hLinuxGrp, nil +} + +// removeTestFile closes and removes the provided file. func removeTestFile(hFile *os.File) { hFile.Close() os.Remove(hFile.Name()) } -func Test_isInContainer(t *testing.T) { - +func Test_isInContainerCGroup(t *testing.T) { //nolint:dupl // setup hDockerGrp, hLinuxGrp, err := createTestCgroupFiles() if err != nil { @@ -57,7 +80,7 @@ func Test_isInContainer(t *testing.T) { // TEST type args struct { - cgroupPath string + filePath string } tests := []struct { name string @@ -70,7 +93,7 @@ func Test_isInContainer(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := isInContainer(tt.args.cgroupPath) + got, err := isInContainerCGroup(tt.args.filePath) if (err != nil) != tt.wantErr { t.Errorf("isInContainer() error = %v, wantErr %v", err, tt.wantErr) return @@ -82,8 +105,74 @@ func Test_isInContainer(t *testing.T) { } } -func Test_isInteractive(t *testing.T) { +func Test_isInContainerMountInfo(t *testing.T) { //nolint:dupl + // setup + hDockerGrp, hLinuxGrp, err := createTestMountInfoFiles() + if err != nil { + t.Fatal(err) + } + defer func() { + // tear down + removeTestFile(hDockerGrp) + removeTestFile(hLinuxGrp) + }() + // TEST + type args struct { + filePath string + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + {"docker", args{hDockerGrp.Name()}, true, false}, + {"linux", args{hLinuxGrp.Name()}, false, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := isInContainerMountInfo(tt.args.filePath) + if (err != nil) != tt.wantErr { + t.Errorf("isInContainer() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("isInContainer() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_isInContainerDockerEnv(t *testing.T) { + // TEST + type args struct { + filePath string + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + {"docker", args{os.TempDir()}, true, false}, + {"linux", args{"/non_existent_file"}, false, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := isInContainerDockerEnv(tt.args.filePath) + if (err != nil) != tt.wantErr { + t.Errorf("isInContainer() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("isInContainer() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_isInteractive(t *testing.T) { // setup hDockerGrp, hLinuxGrp, err := createTestCgroupFiles() if err != nil { @@ -106,7 +195,8 @@ func Test_isInteractive(t *testing.T) { want bool wantErr bool }{ - {"docker", + { + "docker", func() { strStack <- cgroupFile cgroupFile = hDockerGrp.Name() @@ -116,7 +206,8 @@ func Test_isInteractive(t *testing.T) { }, true, false, }, - {"linux", + { + "linux", func() { strStack <- cgroupFile cgroupFile = hLinuxGrp.Name() @@ -170,4 +261,36 @@ const ( 2:cpu,cpuacct:/ 1:name=systemd:/init.scope 0::/init.scope` + + //nolint:lll,dupword + dockerMountInfo = `3860 3859 0:159 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +3861 3857 0:160 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro +3862 3861 0:29 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw +3863 3859 0:156 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw +3864 3859 0:161 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k,inode64 +3865 3857 259:4 /var/lib/docker/volumes/345b0c4550daa5dbc7f4fa9fbd28717844e69e235f2aa014c7d24114320dd9a1/_data /opt/data rw,relatime master:1 - ext4 /dev/nvme0n1p4 rw +3866 3857 259:4 /var/lib/docker/containers/ea4d56df6742a4940bfa0b31a4481707511f2da7b7c0708ffe901b46f461eb89/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/nvme0n1p4 rw +3867 3857 259:4 /var/lib/docker/containers/ea4d56df6742a4940bfa0b31a4481707511f2da7b7c0708ffe901b46f461eb89/hostname /etc/hostname rw,relatime - ext4 /dev/nvme0n1p4 rw +3868 3857 259:4 /var/lib/docker/containers/ea4d56df6742a4940bfa0b31a4481707511f2da7b7c0708ffe901b46f461eb89/hosts /etc/hosts rw,relatime - ext4 /dev/nvme0n1p4 rw +3776 3859 0:159 /0 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +3777 3858 0:157 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw +3778 3858 0:157 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw` + + //nolint:lll,dupword + linuxMountInfo = `183 28 0:44 / /run/credentials/systemd-tmpfiles-setup.service ro,nosuid,nodev,noexec,relatime,nosymfollow shared:103 - tmpfs tmpfs rw,size=1024k,nr_inodes=1024,mode=700,inode64,noswap +129 36 0:45 / /proc/sys/fs/binfmt_misc rw,nosuid,nodev,noexec,relatime shared:105 - binfmt_misc binfmt_misc rw +407 28 0:48 / /run/credentials/systemd-resolved.service ro,nosuid,nodev,noexec,relatime,nosymfollow shared:107 - tmpfs tmpfs rw,size=1024k,nr_inodes=1024,mode=700,inode64,noswap +166 30 0:60 / /var/lib/lxcfs rw,nosuid,nodev,relatime shared:329 - fuse.lxcfs lxcfs rw,user_id=0,group_id=0,allow_other +217 28 0:67 / /run/rpc_pipefs rw,relatime shared:911 - rpc_pipefs sunrpc rw +298 28 0:26 /snapd/ns /run/snapd/ns rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,size=4066576k,mode=755,inode64 +335 298 0:4 mnt:[4026533124] /run/snapd/ns/cups.mnt rw - nsfs nsfs rw +1821 298 0:4 mnt:[4026533318] /run/snapd/ns/snapd-desktop-integration.mnt rw - nsfs nsfs rw +169 28 0:64 / /run/user/1000 rw,nosuid,nodev,relatime shared:700 - tmpfs tmpfs rw,size=4066576k,nr_inodes=1016644,mode=700,uid=1000,gid=1000,inode64 +924 169 0:65 / /run/user/1000/doc rw,nosuid,nodev,relatime shared:794 - fuse.portal portal rw,user_id=1000,group_id=1000 +2388 169 0:71 / /run/user/1000/gvfs rw,nosuid,nodev,relatime shared:814 - fuse.gvfsd-fuse gvfsd-fuse rw,user_id=1000,group_id=1000 +2948 298 0:4 mnt:[4026534753] /run/snapd/ns/firmware-updater.mnt rw - nsfs nsfs rw +3517 30 0:129 / /var/lib/docker/overlay2/79490de289b65b7a63e86a6ae48a8607cf251030eec80d554f99eff740ba425e/merged rw,relatime shared:834 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/FCSCRZU3XZN6KVYV6UA4IU6CZM:/var/lib/docker/overlay2/l/I7GRZ2AVB7NO3MPA7VF75DF25N:/var/lib/docker/overlay2/l/3PHQOMHS6SD5HZFMUUENIQXENT:/var/lib/docker/overlay2/l/YOR22M55ADQZ4GRJIW3NR4EKF3:/var/lib/docker/overlay2/l/SLIJGALM6YROVNIV62WO7HLAXT,upperdir=/var/lib/docker/overlay2/79490de289b65b7a63e86a6ae48a8607cf251030eec80d554f99eff740ba425e/diff,workdir=/var/lib/docker/overlay2/79490de289b65b7a63e86a6ae48a8607cf251030eec80d554f99eff740ba425e/work,nouserxattr +3623 28 0:4 net:[4026537044] /run/docker/netns/e63556d83137 rw shared:1054 - nsfs nsfs rw +3548 30 0:137 / /var/lib/docker/overlay2/5f0fd269ad76199040b9b3ca1fa13ce36f9ab6799cd4b0b5406732c2c8407ff6/merged rw,relatime shared:1074 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/SXPONMLN4JW3QBFQWXCZ3RHVST:/var/lib/docker/overlay2/l/LA6JENXEZAPXZZF2FP4ON5WDEA:/var/lib/docker/overlay2/l/XEGVGERQJ7L72RWT3VIXEWGBL4:/var/lib/docker/overlay2/l/BPXXR3DHMVCSQWGSXG5NFNBE5W,upperdir=/var/lib/docker/overlay2/5f0fd269ad76199040b9b3ca1fa13ce36f9ab6799cd4b0b5406732c2c8407ff6/diff,workdir=/var/lib/docker/overlay2/5f0fd269ad76199040b9b3ca1fa13ce36f9ab6799cd4b0b5406732c2c8407ff6/work,nouserxattr +3700 28 0:4 net:[4026537144] /run/docker/netns/0b489b9c590d rw shared:1094 - nsfs nsfs rw` ) diff --git a/service_openrc_linux.go b/service_openrc_linux.go index c678e3ae..7005f40a 100644 --- a/service_openrc_linux.go +++ b/service_openrc_linux.go @@ -25,15 +25,13 @@ func isOpenRC() bool { defer filerc.Close() buf := new(bytes.Buffer) - buf.ReadFrom(filerc) + _, _ = buf.ReadFrom(filerc) contents := buf.String() re := regexp.MustCompile(`::sysinit:.*openrc.*sysinit`) matches := re.FindStringSubmatch(contents) - if len(matches) > 0 { - return true - } - return false + + return len(matches) > 0 } return false } @@ -59,9 +57,9 @@ func (s *openrc) template() *template.Template { customScript := s.Option.string(optionOpenRCScript, "") if customScript != "" { - return template.Must(template.New("").Funcs(tf).Parse(customScript)) + return template.Must(template.New("").Funcs(getTemplateFunctions()).Parse(customScript)) } - return template.Must(template.New("").Funcs(tf).Parse(openRCScript)) + return template.Must(template.New("").Funcs(getTemplateFunctions()).Parse(openRCScript)) } func newOpenRCService(i Interface, platform string, c *Config) (Service, error) { @@ -75,13 +73,12 @@ func newOpenRCService(i Interface, platform string, c *Config) (Service, error) var errNoUserServiceOpenRC = errors.New("user services are not supported on OpenRC") -func (s *openrc) configPath() (cp string, err error) { +func (s *openrc) configPath() (string, error) { if s.Option.bool(optionUserService, optionUserServiceDefault) { - err = errNoUserServiceOpenRC - return + return "", errNoUserServiceOpenRC } - cp = "/etc/init.d/" + s.Config.Name - return + + return "/etc/init.d/" + s.Config.Name, nil } func (s *openrc) Install() error { @@ -100,7 +97,7 @@ func (s *openrc) Install() error { } defer f.Close() - err = os.Chmod(confPath, 0755) + err = os.Chmod(confPath, 0o755) if err != nil { return err } @@ -110,7 +107,7 @@ func (s *openrc) Install() error { return err } - var to = &struct { + to := &struct { *Config Path string LogDirectory string @@ -150,14 +147,14 @@ func (s *openrc) SystemLogger(errs chan<- error) (Logger, error) { return newSysLogger(s.Name, errs) } -func (s *openrc) Run() (err error) { - err = s.i.Start(s) +func (s *openrc) Run() error { + err := s.i.Start(s) if err != nil { return err } s.Option.funcSingle(optionRunWait, func() { - var sigChan = make(chan os.Signal, 3) + sigChan := make(chan os.Signal, 3) signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) <-sigChan })() @@ -185,7 +182,7 @@ func (s *openrc) Status() (Status, error) { case exitCode == 3: return StatusStopped, nil default: - return StatusUnknown, fmt.Errorf("unknown error: %v - %v", out, err) + return StatusUnknown, fmt.Errorf("unknown error: %v - %w", out, err) } } else { return StatusUnknown, err @@ -236,7 +233,7 @@ export {{$k}}={{$v}} {{- if .Dependencies }} depend() { -{{- range $i, $dep := .Dependencies}} +{{- range $i, $dep := .Dependencies}} {{"\t"}}{{$dep}}{{end}} } {{- end}} diff --git a/service_rcs_linux.go b/service_rcs_linux.go index 0fca97dc..4354ce80 100644 --- a/service_rcs_linux.go +++ b/service_rcs_linux.go @@ -39,15 +39,13 @@ func isRCS() bool { defer filerc.Close() buf := new(bytes.Buffer) - buf.ReadFrom(filerc) + _, _ = buf.ReadFrom(filerc) contents := buf.String() re := regexp.MustCompile(`::sysinit:.*rcS`) matches := re.FindStringSubmatch(contents) - if len(matches) > 0 { - return true - } - return false + + return len(matches) > 0 } return false } @@ -73,25 +71,24 @@ func (s *rcs) Platform() string { return s.platform } -// todo -var errNoUserServiceRCS = errors.New("User services are not supported on rcS.") +// todo. +var errNoUserServiceRCS = errors.New("User services are not supported on rcS.") //nolint:revive -func (s *rcs) configPath() (cp string, err error) { +func (s *rcs) configPath() (string, error) { if s.Option.bool(optionUserService, optionUserServiceDefault) { - err = errNoUserServiceRCS - return + return "", errNoUserServiceRCS } - cp = "/etc/init.d/" + s.Config.Name - return + + return "/etc/init.d/" + s.Config.Name, nil } func (s *rcs) template() *template.Template { customScript := s.Option.string(optionRCSScript, "") if customScript != "" { - return template.Must(template.New("").Funcs(tf).Parse(customScript)) + return template.Must(template.New("").Funcs(getTemplateFunctions()).Parse(customScript)) } - return template.Must(template.New("").Funcs(tf).Parse(rcsScript)) + return template.Must(template.New("").Funcs(getTemplateFunctions()).Parse(rcsScript)) } func (s *rcs) Install() error { @@ -115,7 +112,7 @@ func (s *rcs) Install() error { return err } - var to = &struct { + to := &struct { *Config Path string LogDirectory string @@ -130,7 +127,7 @@ func (s *rcs) Install() error { return err } - if err = os.Chmod(confPath, 0755); err != nil { + if err = os.Chmod(confPath, 0o755); err != nil { return err } @@ -161,18 +158,19 @@ func (s *rcs) Logger(errs chan<- error) (Logger, error) { } return s.SystemLogger(errs) } + func (s *rcs) SystemLogger(errs chan<- error) (Logger, error) { return newSysLogger(s.Name, errs) } -func (s *rcs) Run() (err error) { - err = s.i.Start(s) +func (s *rcs) Run() error { + err := s.i.Start(s) if err != nil { return err } s.Option.funcSingle(optionRunWait, func() { - var sigChan = make(chan os.Signal, 3) + sigChan := make(chan os.Signal, 3) signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) <-sigChan })() @@ -213,6 +211,7 @@ func (s *rcs) Restart() error { return s.Start() } +//nolint:dupword const rcsScript = `#!/bin/sh # For RedHat and cousins: # chkconfig: - 99 01 diff --git a/service_systemd_linux.go b/service_systemd_linux.go index e9fb4d65..a7e3944b 100644 --- a/service_systemd_linux.go +++ b/service_systemd_linux.go @@ -34,7 +34,7 @@ func isSystemd() bool { defer filerc.Close() buf := new(bytes.Buffer) - buf.ReadFrom(filerc) + _, _ = buf.ReadFrom(filerc) contents := buf.String() if strings.Trim(contents, " \r\n") == "systemd" { @@ -71,22 +71,21 @@ func (s *systemd) Platform() string { return s.platform } -func (s *systemd) configPath() (cp string, err error) { +func (s *systemd) configPath() (string, error) { if !s.isUserService() { - cp = "/etc/systemd/system/" + s.unitName() - return + return "/etc/systemd/system/" + s.unitName(), nil } homeDir, err := os.UserHomeDir() if err != nil { - return + return "", err } systemdUserDir := filepath.Join(homeDir, ".config/systemd/user") err = os.MkdirAll(systemdUserDir, os.ModePerm) if err != nil { - return + return "", err } - cp = filepath.Join(systemdUserDir, s.unitName()) - return + + return filepath.Join(systemdUserDir, s.unitName()), nil } func (s *systemd) unitName() string { @@ -131,9 +130,9 @@ func (s *systemd) template() *template.Template { customScript := s.Option.string(optionSystemdScript, "") if customScript != "" { - return template.Must(template.New("").Funcs(tf).Parse(customScript)) + return template.Must(template.New("").Funcs(getTemplateFunctions()).Parse(customScript)) } - return template.Must(template.New("").Funcs(tf).Parse(systemdScript)) + return template.Must(template.New("").Funcs(getTemplateFunctions()).Parse(systemdScript)) } func (s *systemd) isUserService() bool { @@ -150,7 +149,7 @@ func (s *systemd) Install() error { return fmt.Errorf("Init already exists: %s", confPath) } - f, err := os.OpenFile(confPath, os.O_WRONLY|os.O_CREATE, 0644) + f, err := os.OpenFile(confPath, os.O_WRONLY|os.O_CREATE, 0o644) if err != nil { return err } @@ -161,7 +160,7 @@ func (s *systemd) Install() error { return err } - var to = &struct { + to := &struct { *Config Path string HasOutputFileSupport bool @@ -219,18 +218,19 @@ func (s *systemd) Logger(errs chan<- error) (Logger, error) { } return s.SystemLogger(errs) } + func (s *systemd) SystemLogger(errs chan<- error) (Logger, error) { return newSysLogger(s.Name, errs) } -func (s *systemd) Run() (err error) { - err = s.i.Start(s) +func (s *systemd) Run() error { + err := s.i.Start(s) if err != nil { return err } s.Option.funcSingle(optionRunWait, func() { - var sigChan = make(chan os.Signal, 3) + sigChan := make(chan os.Signal, 3) signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) <-sigChan })() @@ -301,7 +301,7 @@ func (s *systemd) runAction(action string) error { const systemdScript = `[Unit] Description={{.Description}} ConditionFileIsExecutable={{.Path|cmdEscape}} -{{range $i, $dep := .Dependencies}} +{{range $i, $dep := .Dependencies}} {{$dep}} {{end}} [Service] diff --git a/service_sysv_linux.go b/service_sysv_linux.go index 5a98a514..41aa8d71 100644 --- a/service_sysv_linux.go +++ b/service_sysv_linux.go @@ -42,24 +42,23 @@ func (s *sysv) Platform() string { return s.platform } -var errNoUserServiceSystemV = errors.New("User services are not supported on SystemV.") +var errNoUserServiceSystemV = errors.New("User services are not supported on SystemV.") //nolint:revive -func (s *sysv) configPath() (cp string, err error) { +func (s *sysv) configPath() (string, error) { if s.Option.bool(optionUserService, optionUserServiceDefault) { - err = errNoUserServiceSystemV - return + return "", errNoUserServiceSystemV } - cp = "/etc/init.d/" + s.Config.Name - return + + return "/etc/init.d/" + s.Config.Name, nil } func (s *sysv) template() *template.Template { customScript := s.Option.string(optionSysvScript, "") if customScript != "" { - return template.Must(template.New("").Funcs(tf).Parse(customScript)) + return template.Must(template.New("").Funcs(getTemplateFunctions()).Parse(customScript)) } - return template.Must(template.New("").Funcs(tf).Parse(sysvScript)) + return template.Must(template.New("").Funcs(getTemplateFunctions()).Parse(sysvScript)) } func (s *sysv) Install() error { @@ -83,7 +82,7 @@ func (s *sysv) Install() error { return err } - var to = &struct { + to := &struct { *Config Path string LogDirectory string @@ -98,7 +97,7 @@ func (s *sysv) Install() error { return err } - if err = os.Chmod(confPath, 0755); err != nil { + if err = os.Chmod(confPath, 0o755); err != nil { return err } for _, i := range [...]string{"2", "3", "4", "5"} { @@ -132,18 +131,19 @@ func (s *sysv) Logger(errs chan<- error) (Logger, error) { } return s.SystemLogger(errs) } + func (s *sysv) SystemLogger(errs chan<- error) (Logger, error) { return newSysLogger(s.Name, errs) } -func (s *sysv) Run() (err error) { - err = s.i.Start(s) +func (s *sysv) Run() error { + err := s.i.Start(s) if err != nil { return err } s.Option.funcSingle(optionRunWait, func() { - var sigChan = make(chan os.Signal, 3) + sigChan := make(chan os.Signal, 3) signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) <-sigChan })() @@ -184,6 +184,7 @@ func (s *sysv) Restart() error { return s.Start() } +//nolint:dupword const sysvScript = `#!/bin/sh # For RedHat and cousins: # chkconfig: - 99 01 diff --git a/service_test.go b/service_test.go index 886b0cfb..f7dcebfd 100644 --- a/service_test.go +++ b/service_test.go @@ -5,11 +5,12 @@ package service_test import ( + "log" "os" "testing" "time" - "github.com/kardianos/service" + "github.com/kardianos/service" //nolint:depguard ) func TestRunInterrupt(t *testing.T) { @@ -24,7 +25,7 @@ func TestRunInterrupt(t *testing.T) { go func() { <-time.After(1 * time.Second) - interruptProcess(t) + interruptProcess() }() go func() { @@ -32,7 +33,7 @@ func TestRunInterrupt(t *testing.T) { <-time.After(200 * time.Millisecond) } if p.numStopped == 0 { - t.Fatal("Run() hasn't been stopped") + log.Fatal("Run() hasn't been stopped") } }() @@ -43,7 +44,7 @@ func TestRunInterrupt(t *testing.T) { const testInstallEnv = "TEST_USER_INSTALL" -// Should always run, without asking for any permission +// Should always run, without asking for any permission. func TestUserRunInterrupt(t *testing.T) { if os.Getenv(testInstallEnv) != "1" { t.Skipf("env %q is not set to 1", testInstallEnv) @@ -73,14 +74,16 @@ type program struct { numStopped int } -func (p *program) Start(s service.Service) error { +func (p *program) Start(_ service.Service) error { go p.run() return nil } + func (p *program) run() { // Do work here } -func (p *program) Stop(s service.Service) error { + +func (p *program) Stop(_ service.Service) error { p.numStopped++ return nil } diff --git a/service_unix.go b/service_unix.go index 96582532..8e211ac4 100644 --- a/service_unix.go +++ b/service_unix.go @@ -11,7 +11,6 @@ import ( "bytes" "fmt" "io" - "io/ioutil" "log/syslog" "os/exec" "syscall" @@ -42,18 +41,23 @@ func (s sysLogger) send(err error) error { func (s sysLogger) Error(v ...interface{}) error { return s.send(s.Writer.Err(fmt.Sprint(v...))) } + func (s sysLogger) Warning(v ...interface{}) error { return s.send(s.Writer.Warning(fmt.Sprint(v...))) } + func (s sysLogger) Info(v ...interface{}) error { return s.send(s.Writer.Info(fmt.Sprint(v...))) } + func (s sysLogger) Errorf(format string, a ...interface{}) error { return s.send(s.Writer.Err(fmt.Sprintf(format, a...))) } + func (s sysLogger) Warningf(format string, a ...interface{}) error { return s.send(s.Writer.Warning(fmt.Sprintf(format, a...))) } + func (s sysLogger) Infof(format string, a ...interface{}) error { return s.send(s.Writer.Info(fmt.Sprintf(format, a...))) } @@ -77,41 +81,39 @@ func runCommand(command string, readStdout bool, arguments ...string) (int, stri if readStdout { // Connect pipe to read Stdout stdout, err = cmd.StdoutPipe() - if err != nil { // Failed to connect pipe - return 0, "", fmt.Errorf("%q failed to connect stdout pipe: %v", command, err) + return 0, "", fmt.Errorf("%q failed to connect stdout pipe: %w", command, err) } } // Connect pipe to read Stderr stderr, err := cmd.StderrPipe() - if err != nil { // Failed to connect pipe - return 0, "", fmt.Errorf("%q failed to connect stderr pipe: %v", command, err) + return 0, "", fmt.Errorf("%q failed to connect stderr pipe: %w", command, err) } // Do not use cmd.Run() if err := cmd.Start(); err != nil { // Problem while copying stdin, stdout, or stderr - return 0, "", fmt.Errorf("%q failed: %v", command, err) + return 0, "", fmt.Errorf("%q failed: %w", command, err) } // Zero exit status // Darwin: launchctl can fail with a zero exit status, - // so check for emtpy stderr + // so check for empty stderr if command == "launchctl" { - slurp, _ := ioutil.ReadAll(stderr) + slurp, _ := io.ReadAll(stderr) if len(slurp) > 0 && !bytes.HasSuffix(slurp, []byte("Operation now in progress\n")) { return 0, "", fmt.Errorf("%q failed with stderr: %s", command, slurp) } } if readStdout { - out, err := ioutil.ReadAll(stdout) + out, err := io.ReadAll(stdout) if err != nil { - return 0, "", fmt.Errorf("%q failed while attempting to read stdout: %v", command, err) + return 0, "", fmt.Errorf("%q failed while attempting to read stdout: %w", command, err) } else if len(out) > 0 { output = string(out) } @@ -125,7 +127,7 @@ func runCommand(command string, readStdout bool, arguments ...string) (int, stri } // An error occurred and there is no exit status. - return 0, output, fmt.Errorf("%q failed: %v", command, err) + return 0, output, fmt.Errorf("%q failed: %w", command, err) } return 0, output, nil diff --git a/service_upstart_linux.go b/service_upstart_linux.go index 42b947cb..98a54cb8 100644 --- a/service_upstart_linux.go +++ b/service_upstart_linux.go @@ -59,15 +59,14 @@ func (s *upstart) Platform() string { // Upstart has some support for user services in graphical sessions. // Due to the mix of actual support for user services over versions, just don't bother. // Upstart will be replaced by systemd in most cases anyway. -var errNoUserServiceUpstart = errors.New("User services are not supported on Upstart.") +var errNoUserServiceUpstart = errors.New("User services are not supported on Upstart.") //nolint:revive -func (s *upstart) configPath() (cp string, err error) { +func (s *upstart) configPath() (string, error) { if s.Option.bool(optionUserService, optionUserServiceDefault) { - err = errNoUserServiceUpstart - return + return "", errNoUserServiceUpstart } - cp = "/etc/init/" + s.Config.Name + ".conf" - return + + return "/etc/init/" + s.Config.Name + ".conf", nil } func (s *upstart) hasKillStanza() bool { @@ -119,10 +118,10 @@ func (s *upstart) template() *template.Template { customScript := s.Option.string(optionUpstartScript, "") if customScript != "" { - return template.Must(template.New("").Funcs(tf).Parse(customScript)) - } else { - return template.Must(template.New("").Funcs(tf).Parse(upstartScript)) + return template.Must(template.New("").Funcs(getTemplateFunctions()).Parse(customScript)) } + + return template.Must(template.New("").Funcs(getTemplateFunctions()).Parse(upstartScript)) } func (s *upstart) Install() error { @@ -146,7 +145,7 @@ func (s *upstart) Install() error { return err } - var to = &struct { + to := &struct { *Config Path string HasKillStanza bool @@ -182,18 +181,19 @@ func (s *upstart) Logger(errs chan<- error) (Logger, error) { } return s.SystemLogger(errs) } + func (s *upstart) SystemLogger(errs chan<- error) (Logger, error) { return newSysLogger(s.Name, errs) } -func (s *upstart) Run() (err error) { - err = s.i.Start(s) +func (s *upstart) Run() error { + err := s.i.Start(s) if err != nil { return err } s.Option.funcSingle(optionRunWait, func() { - var sigChan = make(chan os.Signal, 3) + sigChan := make(chan os.Signal, 3) signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) <-sigChan })() @@ -208,9 +208,9 @@ func (s *upstart) Status() (Status, error) { } switch { - case strings.HasPrefix(out, fmt.Sprintf("%s start/running", s.Name)): + case strings.HasPrefix(out, s.Name+" start/running"): return StatusRunning, nil - case strings.HasPrefix(out, fmt.Sprintf("%s stop/waiting", s.Name)): + case strings.HasPrefix(out, s.Name+" stop/waiting"): return StatusStopped, nil default: return StatusUnknown, ErrNotInstalled @@ -231,6 +231,8 @@ func (s *upstart) Restart() error { // The upstart script should stop with an INT or the Go runtime will terminate // the program before the Stop handler can run. +// +//nolint:lll,dupword const upstartScript = `# {{.Description}} {{if .DisplayName}}description "{{.DisplayName}}"{{end}} @@ -259,7 +261,7 @@ script stdout_log="{{.LogDirectory}}/{{.Name}}.out" stderr_log="{{.LogDirectory}}/{{.Name}}.err" {{end}} - + if [ -f "/etc/sysconfig/{{.Name}}" ]; then set -a source /etc/sysconfig/{{.Name}} diff --git a/servicetest_unix_test.go b/servicetest_unix_test.go index c4b0b3de..24cb4ac7 100644 --- a/servicetest_unix_test.go +++ b/servicetest_unix_test.go @@ -2,23 +2,23 @@ // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. -//go:build darwin || dragonfly || freebsd || linux || nacl || netbsd || openbsd || solaris -// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris +//go:build darwin || dragonfly || freebsd || linux || nacl || netbsd || openbsd || solaris || aix || ppc64 +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris aix ppc64 package service_test import ( + "log" "os" - "testing" ) -func interruptProcess(t *testing.T) { +func interruptProcess() { pid := os.Getpid() p, err := os.FindProcess(pid) if err != nil { - t.Fatalf("FindProcess: %s", err) + log.Fatalf("FindProcess: %s", err) } if err := p.Signal(os.Interrupt); err != nil { - t.Fatalf("Signal: %s", err) + log.Fatalf("Signal: %s", err) } } diff --git a/version.go b/version.go index a7a2f3a1..2994f0ff 100644 --- a/version.go +++ b/version.go @@ -6,9 +6,9 @@ import ( "strings" ) -// versionAtMost will return true if the provided version is less than or equal to max -func versionAtMost(version, max []int) (bool, error) { - if comp, err := versionCompare(version, max); err != nil { +// versionAtMost will return true if the provided version is less than or equal to max. +func versionAtMost(version, maxVersion []int) (bool, error) { + if comp, err := versionCompare(version, maxVersion); err != nil { return false, err } else if comp == 1 { return false, nil @@ -21,7 +21,7 @@ func versionAtMost(version, max []int) (bool, error) { // Return values are as follows // -1 - v1 is less than v2 // 0 - v1 is equal to v2 -// 1 - v1 is greater than v2 +// 1 - v1 is greater than v2. func versionCompare(v1, v2 []int) (int, error) { if len(v1) != len(v2) { return 0, errors.New("version length mismatch")