118 lines
2.6 KiB
Go
118 lines
2.6 KiB
Go
package runner
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os/exec"
|
|
"time"
|
|
|
|
"github.com/juju/errors"
|
|
|
|
"github.com/localhots/cmdui/backend/commands"
|
|
"github.com/localhots/cmdui/backend/config"
|
|
"github.com/localhots/cmdui/backend/db"
|
|
"github.com/localhots/cmdui/backend/log"
|
|
)
|
|
|
|
func Start(cmd commands.Command, user db.User) (*Process, error) {
|
|
job := db.NewJob(cmd, user)
|
|
if err := job.Create(); err != nil {
|
|
return nil, errors.Annotate(err, "Failed to create a job")
|
|
}
|
|
|
|
p := &Process{
|
|
ID: job.ID,
|
|
Job: &job,
|
|
}
|
|
|
|
basePath := config.Get().Commands.BasePath
|
|
p.exec = exec.Command(basePath, cmd.CombinedArgs()...)
|
|
fd, err := p.useLogfile(p.logfile())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.exec.Stdout = fd
|
|
p.exec.Stderr = fd
|
|
|
|
if err := pool.add(context.Background(), p); err != nil {
|
|
return p, errors.Annotate(err, "Failed to start a process")
|
|
}
|
|
|
|
return p, nil
|
|
}
|
|
|
|
func ReadLogUpdates(ctx context.Context, p *Process, w io.Writer) (done <-chan struct{}, err error) {
|
|
cmde := exec.CommandContext(ctx, "/usr/bin/tail", "-n", "100", "-f", p.logfile())
|
|
return readLog(ctx, cmde, p, w)
|
|
}
|
|
|
|
func ReadFullLog(ctx context.Context, p *Process, w io.Writer) (done <-chan struct{}, err error) {
|
|
cmde := exec.CommandContext(ctx, "/bin/cat", p.logfile())
|
|
return readLog(ctx, cmde, p, w)
|
|
}
|
|
|
|
func readLog(ctx context.Context, cmde *exec.Cmd, p *Process, w io.Writer) (done <-chan struct{}, err error) {
|
|
cmde.Stdout = w
|
|
cmde.Stderr = w
|
|
|
|
tp := &Process{
|
|
ID: sysID("log-access"),
|
|
exec: cmde,
|
|
}
|
|
|
|
exited := make(chan struct{}, 2)
|
|
p.onExit(func(p *Process) {
|
|
exited <- struct{}{}
|
|
})
|
|
tp.onExit(func(p *Process) {
|
|
exited <- struct{}{}
|
|
})
|
|
|
|
if err := pool.add(ctx, tp); err != nil {
|
|
return nil, errors.Annotate(err, "Failed to start a tail process")
|
|
}
|
|
|
|
return exited, nil
|
|
}
|
|
|
|
func CommandsList() ([]commands.Command, error) {
|
|
var buf bytes.Buffer
|
|
cmde := exec.Command("/bin/bash", "-c", config.Get().Commands.ConfigCommand)
|
|
cmde.Stdout = &buf
|
|
cmde.Stderr = &buf
|
|
|
|
p := &Process{
|
|
ID: sysID("commands-list"),
|
|
exec: cmde,
|
|
}
|
|
|
|
exited := make(chan struct{})
|
|
p.onExit(func(p *Process) {
|
|
close(exited)
|
|
})
|
|
|
|
if err := pool.add(context.Background(), p); err != nil {
|
|
return nil, errors.Annotate(err, "Failed to import commands")
|
|
}
|
|
<-exited
|
|
|
|
body := buf.Bytes()
|
|
var list []commands.Command
|
|
if err := json.Unmarshal(body, &list); err != nil {
|
|
log.WithFields(log.F{
|
|
"error": err,
|
|
"json": string(body),
|
|
}).Error("Invalid commands JSON")
|
|
return nil, errors.Annotate(err, "Failed to decode commands JSON")
|
|
}
|
|
|
|
return list, nil
|
|
}
|
|
|
|
func sysID(name string) string {
|
|
return fmt.Sprintf("%s-%d", name, time.Now().UnixNano())
|
|
}
|