RoadSign/pkg/warden/executor.go

150 lines
3.1 KiB
Go
Raw Normal View History

2024-01-17 06:34:08 +00:00
package warden
import (
"fmt"
2025-01-14 09:37:18 +00:00
"io"
"os"
2024-01-17 06:34:08 +00:00
"os/exec"
"path/filepath"
"syscall"
2024-01-17 06:34:08 +00:00
"time"
2025-01-14 09:37:18 +00:00
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
"gopkg.in/natefinch/lumberjack.v2"
2024-01-17 06:34:08 +00:00
"github.com/samber/lo"
)
2024-01-24 16:09:39 +00:00
var InstancePool []*AppInstance
func GetFromPool(id string) *AppInstance {
val, ok := lo.Find(InstancePool, func(item *AppInstance) bool {
return item.Manifest.ID == id
})
return lo.Ternary(ok, val, nil)
}
func StartPool() []error {
var errors []error
for _, instance := range InstancePool {
if err := instance.Wake(); err != nil {
errors = append(errors, err)
}
}
return errors
}
2024-01-17 06:34:08 +00:00
type AppStatus = int8
const (
AppCreated = AppStatus(iota)
AppStarting
AppStarted
AppExited
AppFailure
)
2024-01-24 16:09:39 +00:00
type AppInstance struct {
Manifest Application `json:"manifest"`
2025-01-14 09:37:18 +00:00
Status AppStatus `json:"status"`
2024-01-17 06:34:08 +00:00
2025-01-14 09:37:18 +00:00
Cmd *exec.Cmd `json:"-"`
2024-01-17 06:34:08 +00:00
2025-01-14 09:37:18 +00:00
LogPath string `json:"-"`
Logger *lumberjack.Logger `json:"-"`
2024-01-17 06:34:08 +00:00
}
2024-01-24 16:09:39 +00:00
func (v *AppInstance) Wake() error {
2024-01-17 06:34:08 +00:00
if v.Cmd != nil {
return nil
}
if v.Cmd == nil {
return v.Start()
}
if v.Cmd.Process == nil || v.Cmd.ProcessState == nil {
return v.Start()
}
if v.Cmd.ProcessState.Exited() {
return v.Start()
}
2024-10-03 12:59:18 +00:00
return nil
2024-01-17 06:34:08 +00:00
}
2024-01-24 16:09:39 +00:00
func (v *AppInstance) Start() error {
2024-01-17 06:34:08 +00:00
manifest := v.Manifest
if len(manifest.Command) <= 0 {
return fmt.Errorf("you need set the command for %s to enable process manager", manifest.ID)
}
v.Cmd = exec.Command(manifest.Command[0], manifest.Command[1:]...)
v.Cmd.Dir = filepath.Join(manifest.Workdir)
v.Cmd.Env = append(v.Cmd.Env, manifest.Environment...)
2025-01-14 09:37:18 +00:00
logBasePath := viper.GetString("logging.warden_apps")
logPath := filepath.Join(logBasePath, fmt.Sprintf("%s.log", manifest.ID))
v.LogPath = logPath
v.Logger = &lumberjack.Logger{
Filename: v.LogPath,
MaxSize: 10,
MaxBackups: 3,
MaxAge: 30,
Compress: true,
}
v.Cmd.Stdout = v.Logger
v.Cmd.Stderr = v.Logger
2024-01-17 06:34:08 +00:00
// Monitor
go func() {
for {
if v.Cmd != nil && v.Cmd.Process == nil {
2024-01-17 06:34:08 +00:00
v.Status = AppStarting
} else if v.Cmd != nil && v.Cmd.ProcessState == nil {
2024-01-17 06:34:08 +00:00
v.Status = AppStarted
} else {
2024-10-03 12:59:18 +00:00
v.Status = AppFailure
v.Cmd = nil
2024-01-17 06:34:08 +00:00
return
}
time.Sleep(1000 * time.Millisecond)
2024-01-17 06:34:08 +00:00
}
}()
return v.Cmd.Start()
}
2024-01-24 16:09:39 +00:00
func (v *AppInstance) Stop() error {
2024-01-17 06:34:08 +00:00
if v.Cmd != nil && v.Cmd.Process != nil {
if err := v.Cmd.Process.Signal(syscall.SIGTERM); err != nil {
log.Warn().Int("pid", v.Cmd.Process.Pid).Err(err).Msgf("Failed to send SIGTERM to process...")
if err = v.Cmd.Process.Kill(); err != nil {
log.Error().Int("pid", v.Cmd.Process.Pid).Err(err).Msgf("Failed to kill process...")
return err
}
2024-01-17 06:34:08 +00:00
}
}
// We need to wait for the process to exit
// The wait syscall will read the exit status of the process
// So that we don't produce defunct processes
// Refer to https://stackoverflow.com/questions/46293435/golang-exec-command-cause-a-lot-of-defunct-processes
_ = v.Cmd.Wait()
v.Cmd = nil
2024-10-03 12:59:18 +00:00
v.Status = AppExited
2025-01-14 09:37:18 +00:00
v.Logger.Close()
2024-01-17 06:34:08 +00:00
return nil
}
2024-01-24 16:09:39 +00:00
func (v *AppInstance) Logs() string {
2025-01-14 09:37:18 +00:00
file, err := os.Open(v.LogPath)
if err != nil {
return ""
}
raw, _ := io.ReadAll(file)
return string(raw)
2024-01-17 06:34:08 +00:00
}