diff --git a/internal/app/xhttp/handler.go b/internal/app/xhttp/handler.go index de8468c1..345dafef 100644 --- a/internal/app/xhttp/handler.go +++ b/internal/app/xhttp/handler.go @@ -8,8 +8,10 @@ import ( "github.com/labstack/echo/v4" "github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/context" "github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/resource" + "github.com/thecodingmachine/gotenberg/internal/pkg/pm2" "github.com/thecodingmachine/gotenberg/internal/pkg/printer" "github.com/thecodingmachine/gotenberg/internal/pkg/xerror" + "github.com/thecodingmachine/gotenberg/internal/pkg/xlog" "github.com/thecodingmachine/gotenberg/internal/pkg/xrand" "github.com/thecodingmachine/gotenberg/internal/pkg/xtime" ) @@ -28,11 +30,24 @@ const ( func pingHandler(c echo.Context) error { const op string = "xhttp.pingHandler" ctx := context.MustCastFromEchoContext(c) - ctx.XLogger().DebugOp(op, "handling ping request...") - if err := ctx.ProcessesHealthcheck(); err != nil { + logger := ctx.XLogger() + logger.DebugOp(op, "handling ping request...") + resolver := func() error { + if err := ctx.ProcessesHealthcheck(); err != nil { + return err + } + if logger.Level() != xlog.DebugLevel { + return nil + } + list, err := pm2.List() + if err != nil { + return err + } + return ctx.JSON(http.StatusOK, list) + } + if err := resolver(); err != nil { return xerror.New(op, err) } - // TODO return processes info return nil } diff --git a/internal/app/xhttp/handler_test.go b/internal/app/xhttp/handler_test.go index f3e1548d..c4c78add 100644 --- a/internal/app/xhttp/handler_test.go +++ b/internal/app/xhttp/handler_test.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "os" "testing" "github.com/labstack/echo/v4" @@ -15,7 +16,26 @@ import ( "github.com/thecodingmachine/gotenberg/test" ) -// TODO test ping handler +func TestPingHandler(t *testing.T) { + // should return 200. + config := conf.DefaultConfig() + srv := New(config) + srv = New(config) + req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil) + test.AssertStatusCode(t, http.StatusOK, srv, req) + // should returns a JSON as + // LOG_LEVEL is "DEBUG". + os.Setenv(conf.LogLevelEnvVar, "DEBUG") + config, err := conf.FromEnv() + assert.Nil(t, err) + srv = New(config) + req = httptest.NewRequest(http.MethodGet, pingEndpoint, nil) + rec := httptest.NewRecorder() + srv.ServeHTTP(rec, req) + assert.Equal(t, "application/json; charset=UTF-8", rec.Header().Get("Content-type")) + assert.Equal(t, http.StatusOK, rec.Code) + os.Unsetenv(conf.LogLevelEnvVar) +} func TestMergeHandler(t *testing.T) { config := conf.DefaultConfig() diff --git a/internal/pkg/pm2/pm2.go b/internal/pkg/pm2/pm2.go index 6338ecb5..d267259b 100644 --- a/internal/pkg/pm2/pm2.go +++ b/internal/pkg/pm2/pm2.go @@ -1,7 +1,9 @@ package pm2 import ( + "encoding/json" "fmt" + "os/exec" "github.com/thecodingmachine/gotenberg/internal/pkg/xerror" "github.com/thecodingmachine/gotenberg/internal/pkg/xexec" @@ -27,8 +29,68 @@ const ( restartCommand pm2Command = "restart" stopCommand pm2Command = "stop" logsCommand pm2Command = "logs" + jlistCommand pm2Command = "jlist" ) +/* +JListItem is a struct +used for unmarshaling +ONE item of the result +of the command "pm2 jlist". +*/ +type JListItem struct { + Name string `json:"name"` + PM2Env struct { + Status string `json:"status"` + } `json:"pm2_env"` + Monit struct { + Memory int64 `json:"memory"` + CPU float64 `json:"cpu"` + } `json:"monit"` +} + +/* +JList is a struct +used for unmarshaling +the result of the command +"pm2 jlist". +*/ +type JList []JListItem + +func (list JList) isOnline(p Process) bool { + const onlineStatus string = "online" + for _, item := range list { + if item.Name == p.binary() { + return item.PM2Env.Status == onlineStatus + } + } + return false +} + +// List returns the +// processes details. +func List() (*JList, error) { + const op = "pm2.List" + resolver := func() (*JList, error) { + out, err := exec. + Command("pm2", string(jlistCommand)). + Output() + if err != nil { + return nil, err + } + data := &JList{} + if err := json.Unmarshal(out, data); err != nil { + return nil, err + } + return data, nil + } + list, err := resolver() + if err != nil { + return nil, xerror.New(op, err) + } + return list, nil +} + func start(logger xlog.Logger, process Process) error { const ( op string = "pm2.start" diff --git a/internal/pkg/pm2/unoconv.go b/internal/pkg/pm2/unoconv.go index 05cd5b06..219dc554 100644 --- a/internal/pkg/pm2/unoconv.go +++ b/internal/pkg/pm2/unoconv.go @@ -32,10 +32,36 @@ func (p unoconvProcess) Start() error { } func (p unoconvProcess) IsViable() bool { - // TODO find a way to check if - // the unoconv listener - // is correctly started? - return true + const op string = "pm2.unoconvProcess.IsViable" + p.logger.DebugfOp( + op, + "checking '%s' viability via PM2", + p.Fullname(), + ) + list, err := List() + if err != nil { + p.logger.ErrorfOp( + op, + "'%s' seems not viable as retrieving the list of processes via 'pm2 jlist' returned '%v'", + p.Fullname(), + err, + ) + return false + } + if list.isOnline(p) { + p.logger.DebugfOp( + op, + "'%s' is viable as its status is 'online'", + p.Fullname(), + ) + return true + } + p.logger.DebugfOp( + op, + "'%s' is not viable as its status is not 'online'", + p.Fullname(), + ) + return false } func (p unoconvProcess) Stop() error {