1
0
Fork 0
cmdui/backend/api/jobs_controller.go

178 lines
4.8 KiB
Go

package api
import (
"context"
"fmt"
"net/http"
"syscall"
"github.com/juju/errors"
"github.com/localhots/cmdui/backend/commands"
"github.com/localhots/cmdui/backend/db"
"github.com/localhots/cmdui/backend/runner"
)
func init() {
router.GET("/api/jobs", protectedEndpoint(jobsIndexHandler))
router.GET("/api/jobs/:job_id", protectedEndpoint(jobShowHandler))
router.PUT("/api/jobs/:job_id", protectedEndpoint(jobActionHandler))
router.GET("/api/jobs/:job_id/log", protectedEndpoint(jobLogHandler))
router.GET("/api/commands/:cmd/jobs", protectedEndpoint(jobsIndexHandler))
router.GET("/api/users/:user_id/jobs", protectedEndpoint(jobsIndexHandler))
}
// jobLogHandler returns job's log. If the command is still running, than the
// client would be attached to a log file and receive updates as well as few
// previous lines of it. If the command is completed, the entire log would be
// returned. If a `full` parameter is provided and the command is still running,
// the client would receive all existing log contents and future updates.
// GET /api/jobs/:job_id/log
// FIXME: What the fuck is this function
func jobLogHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
id := param(ctx, "job_id")
proc := runner.FindProcess(id)
if proc != nil {
var done <-chan struct{}
var err error
if r.FormValue("full") != "" {
done, err = runner.ReadFullLog(ctx, proc, unbufferedWriter{w})
} else {
done, err = runner.ReadLogUpdates(ctx, proc, unbufferedWriter{w})
}
if err != nil {
renderError(w, err, http.StatusInternalServerError, "Failed to tail a log")
}
<-done
return
}
proc = &runner.Process{ID: id}
done, err := runner.ReadFullLog(ctx, proc, unbufferedWriter{w})
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
<-done
}
// jobsIndexHandler returns a list of jobs for a given criteria.
// GET /api/jobs
// GET /api/commands/:cmd/jobs
// GET /api/users/:user_id/jobs
func jobsIndexHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
renderJobs := func(jobs []db.Job, err error) {
if err != nil {
renderError(w, err, http.StatusInternalServerError, "Failed to find jobs")
} else {
renderJSON(w, jobs)
}
}
switch {
case param(ctx, "user_id") != "":
id := param(ctx, "user_id")
u, err := db.FindUser(id)
if err != nil {
renderError(w, err, http.StatusInternalServerError, "Failed to find a user")
}
if u == nil {
err := fmt.Errorf("User not found: %s", id)
renderError(w, err, http.StatusNotFound, "User not found")
return
}
renderJobs(db.FindUserJobs(id, requestedPage(r)))
case param(ctx, "cmd") != "":
cmdName := param(ctx, "cmd")
if _, ok := commands.Map()[cmdName]; ok {
renderJobs(db.FindCommandJobs(cmdName, requestedPage(r)))
} else {
err := fmt.Errorf("Command not found: %s", cmdName)
renderError(w, err, http.StatusNotFound, "Command not found")
}
default:
renderJobs(db.FindAllJobs(requestedPage(r)))
}
}
// jobShowHandler returns job details.
// GET /api/jobs/:job_id
func jobShowHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
id := param(ctx, "job_id")
job, err := db.FindJob(id)
if err != nil {
renderError(w, err, http.StatusInternalServerError, "Failed to find job")
return
}
if job != nil {
renderJSON(w, job)
} else {
w.WriteHeader(http.StatusNotFound)
}
}
// jobActionHandler performs certain actions on a job.
// PUT /api/jobs/:job_id
func jobActionHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
renderError(w, err, http.StatusBadRequest, "Failed to parse form")
return
}
id := r.PostForm.Get("id")
if id == "" {
err := errors.New("Job ID is required")
renderError(w, err, http.StatusBadRequest, err.Error())
return
}
proc := runner.FindProcess(id)
if proc == nil {
err := fmt.Errorf("Job %q was not found", id)
renderError(w, err, http.StatusNotFound, err.Error())
return
}
sigName := r.PostForm.Get("signal")
if sigName != "" {
sig, err := signalFromName(sigName)
if err != nil {
renderError(w, err, http.StatusBadRequest, err.Error())
return
}
err = proc.Signal(sig)
if err != nil {
renderError(w, err, http.StatusInternalServerError, "Failed to send signal to a process")
return
}
}
}
func signalFromName(name string) (syscall.Signal, error) {
switch name {
case "SIGHUP":
return syscall.SIGHUP, nil
case "SIGINT":
return syscall.SIGINT, nil
case "SIGKILL":
return syscall.SIGKILL, nil
case "SIGQUIT":
return syscall.SIGQUIT, nil
case "SIGTERM":
return syscall.SIGTERM, nil
case "SIGTTIN":
return syscall.SIGTTIN, nil
case "SIGTTOU":
return syscall.SIGTTOU, nil
case "SIGUSR1":
return syscall.SIGUSR1, nil
case "SIGUSR2":
return syscall.SIGUSR2, nil
default:
return 0, fmt.Errorf("Signal not supported: %s", name)
}
}