From e020a95865a1eea25ba2a940cba09e3c9acc28f3 Mon Sep 17 00:00:00 2001 From: cbrockington Date: Wed, 17 Oct 2018 13:28:18 +0100 Subject: [PATCH] Print the status to stdout or stderr. (#63) In adherence with http://refspecs.linuxbase.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/iniscrptact.html referenced in the README, status should "print the current status of the service". Previously go-init will log status to a file however this is not the conventional meaning of "print". --- README.md | 3 ++ init/cli/status.go | 98 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 85 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 229866de..f0c87371 100644 --- a/README.md +++ b/README.md @@ -149,5 +149,8 @@ command used to launch a specific subProcesses, and its subsequent stdout and st `var/log/${SUB_PROCESS}-startup.log` files. `go-init` does not launch each `subProcess` as a child process of the primary process. +Note that while the specification states that the `status` command prints the status of the service, the exact wording +used to denote that status is not defined and subsequently subject to change without warning. + # License This repository is made available under the [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0). diff --git a/init/cli/status.go b/init/cli/status.go index 18e795cd..844b32e7 100644 --- a/init/cli/status.go +++ b/init/cli/status.go @@ -15,6 +15,9 @@ package cli import ( + "fmt" + "os" + "github.com/palantir/pkg/cli" "github.com/pkg/errors" @@ -35,27 +38,90 @@ If exit code is nonzero, writes an error message to stderr and var/log/startup.l Action: executeWithLoggers(status, NewAlwaysAppending()), } +var ( + Running = ServiceState{ + Description: "Running", + Applicable: func(serviceStatus *serviceStatus, err error) bool { + return err == nil && len(serviceStatus.notRunningCmds) == 0 + }, + ExitStatus: func(serviceStatus *serviceStatus, err error) (int, error) { + return 0, nil + }, + } + Dead = ServiceState{ + Description: "Process dead but pidfile exists.", + Applicable: func(serviceStatus *serviceStatus, err error) bool { + return err == nil && len(serviceStatus.notRunningCmds) > 0 && len(serviceStatus.writtenPids) > 0 + }, + ExitStatus: func(serviceStatus *serviceStatus, err error) (int, error) { + return 1, errors.Errorf("commands '%v' are not running but there is a record of commands '%v' "+ + "having been started", commandNames(serviceStatus.notRunningCmds), serviceStatus.writtenPids) + }, + } + NotRunning = ServiceState{ + Description: "Service not running", + Applicable: func(serviceStatus *serviceStatus, err error) bool { + return err == nil && len(serviceStatus.notRunningCmds) > 0 && len(serviceStatus.writtenPids) == 0 + }, + ExitStatus: func(serviceStatus *serviceStatus, err error) (int, error) { + return 3, errors.Errorf("commands '%v' are not running", commandNames(serviceStatus.notRunningCmds)) + }, + } + ErrorState = ServiceState{ + Description: "Failed to determine service status", + Applicable: func(serviceStatus *serviceStatus, err error) bool { + return err != nil + }, + ExitStatus: func(serviceStatus *serviceStatus, err error) (int, error) { + if err != nil { + return 4, errors.Wrap(err, "failed to determine service status") + } + return 4, errors.Errorf("failed to determine service status") + }, + } +) + func status(ctx cli.Context, loggers launchlib.ServiceLoggers) error { // Executed with logging for errors, however we discard the verbose logging of getServiceStatus serviceStatus, err := getServiceStatus(ctx, &DevNullLoggers{}) - if err != nil { - return logErrorAndReturnWithExitCode(ctx, errors.Wrap(err, "failed to determine service status"), 4) - } - if len(serviceStatus.notRunningCmds) > 0 { - notRunningCmdNames := make([]string, 0, len(serviceStatus.notRunningCmds)) - for name := range serviceStatus.notRunningCmds { - notRunningCmdNames = append(notRunningCmdNames, name) + var matched *ServiceState + for _, state := range []ServiceState{ErrorState, NotRunning, Dead, Running} { + if state.Applicable(serviceStatus, err) { + matched = &state + break } - if len(serviceStatus.writtenPids) > 0 { - return logErrorAndReturnWithExitCode( - ctx, - errors.Errorf("commands '%v' are not running but there is a record of commands '%v'"+ - "having been started", notRunningCmdNames, serviceStatus.writtenPids), - 1, - ) + } + + // If no state has matched, default to error state + if matched == nil { + matched = &ErrorState + } + + code, err := matched.ExitStatus(serviceStatus, err) + if code != 0 { + fmt.Fprintln(os.Stderr, matched.Description) + if err != nil { + return logErrorAndReturnWithExitCode(ctx, err, code) } - return logErrorAndReturnWithExitCode(ctx, errors.Errorf("commands '%v' are not running", - notRunningCmdNames), 3) + // Non-zero exit codes can only be reported through cli.WithExitCode which must include an errors, though that + // error can be empty. Returning no error gives an exit code of 0 + return cli.WithExitCode(code, errors.New("")) } + + fmt.Println(matched.Description) return nil } + +type ServiceState struct { + Description string + Applicable func(serviceStatus *serviceStatus, err error) bool + ExitStatus func(serviceStatus *serviceStatus, err error) (int, error) +} + +func commandNames(commands map[string]CommandContext) []string { + names := make([]string, 0, len(commands)) + for name := range commands { + names = append(names, name) + } + return names +}