wip: log
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.suyono.dev/suyono/wingmate/config"
|
||||
"gitea.suyono.dev/suyono/wingmate/debugframes"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"os/exec"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
type InstanceType int
|
||||
@@ -23,10 +26,11 @@ type process struct {
|
||||
}
|
||||
|
||||
type daemon struct {
|
||||
running map[string]process
|
||||
available map[string]process
|
||||
services map[string]WingMateDescriptor
|
||||
cron map[string]WingMateDescriptor
|
||||
running map[string]process
|
||||
services map[string]*service
|
||||
cron map[string]WingMateDescriptor
|
||||
logChannel chan<- any
|
||||
errorChannel chan<- error
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -45,27 +49,61 @@ func (t InstanceType) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
func Start(cmd *cobra.Command, args []string) error {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
func Start(cmd *cobra.Command, args []string) (err error) {
|
||||
defer func() {
|
||||
if o := recover(); o != nil {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("panic: %v %w and error: %w", o, debugframes.PanicTrace(debug.Stack()), err)
|
||||
} else {
|
||||
err = fmt.Errorf("panic: %v %w", o, debugframes.PanicTrace(debug.Stack()))
|
||||
}
|
||||
}
|
||||
}()
|
||||
_, _ = cmd, args // prevent warnings for unused arguments
|
||||
|
||||
_, err = start()
|
||||
return
|
||||
}
|
||||
|
||||
func start() (*daemon, error) {
|
||||
var (
|
||||
err error
|
||||
k string
|
||||
svc *service
|
||||
cmd *exec.Cmd
|
||||
)
|
||||
|
||||
d := &daemon{
|
||||
running: make(map[string]process),
|
||||
available: make(map[string]process),
|
||||
services: make(map[string]WingMateDescriptor),
|
||||
cron: make(map[string]WingMateDescriptor),
|
||||
running: make(map[string]process),
|
||||
services: make(map[string]*service),
|
||||
cron: make(map[string]WingMateDescriptor),
|
||||
logChannel: make(chan<- any, 16),
|
||||
errorChannel: make(chan<- error, 4),
|
||||
}
|
||||
if err = d.buildServiceMap(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
return nil
|
||||
|
||||
for k, svc = range d.services {
|
||||
if cmd, err = StartProcess(svc); err != nil {
|
||||
return nil, err //TODO: this is not supposed to return, log and start the next service
|
||||
}
|
||||
d.running[k] = process{
|
||||
cmd: cmd,
|
||||
descriptor: svc,
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: create loop to receive and process log
|
||||
//TODO: create loop to receive error
|
||||
//TODO: create signal handler
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *daemon) buildServiceMap() error {
|
||||
for s := range viper.GetStringMap(Service.String()) {
|
||||
_ = s //TODO: resume work
|
||||
d.services[s] = newService(s, d.errorChannel, d.logChannel)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,27 +7,28 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
type ProcessConfig interface {
|
||||
Descriptor() WingMateDescriptor
|
||||
|
||||
// Name returns the binary name to be executed, to be passed in to exec.Command
|
||||
Name() string
|
||||
// ExecutableName returns the executable/binary name to be executed, to be passed in to exec.Command
|
||||
ExecutableName() string
|
||||
|
||||
// Args returns the arguments for the process, to be passed in to exec.Command
|
||||
Args() []string
|
||||
Env() map[string]string
|
||||
WorkingDir() string
|
||||
LogChannel() chan<- any
|
||||
ControlChannel() chan<- any
|
||||
ErrorChannel() chan<- error
|
||||
}
|
||||
|
||||
type StreamID int
|
||||
|
||||
type ProcessLogEntry struct {
|
||||
ID WingMateDescriptor
|
||||
LogEntry string
|
||||
Descriptor WingMateDescriptor
|
||||
LogEntry string
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -53,17 +54,19 @@ func StartProcess(config ProcessConfig) (*exec.Cmd, error) {
|
||||
k, v string
|
||||
stdoutPipe, stderrPipe io.ReadCloser
|
||||
)
|
||||
cmd = exec.Command(config.Name(), config.Args()...)
|
||||
cmd = exec.Command(config.ExecutableName(), config.Args()...)
|
||||
cmd.Env = os.Environ()
|
||||
for k, v = range config.Env() {
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
cmd.Dir = config.WorkingDir()
|
||||
if stdoutPipe, err = cmd.StdoutPipe(); err != nil {
|
||||
return nil, fmt.Errorf("set up stdout pipe on process %w: %w %w", config.Descriptor(), err, debugframes.GetTraces())
|
||||
return nil, fmt.Errorf("set up stdout pipe on process %w: %w %w",
|
||||
config.Descriptor(), err, debugframes.GetTraces())
|
||||
}
|
||||
if stderrPipe, err = cmd.StderrPipe(); err != nil {
|
||||
return nil, fmt.Errorf("set up stderr pipe on process %w: %w %w", config.Descriptor(), err, debugframes.GetTraces())
|
||||
return nil, fmt.Errorf("set up stderr pipe on process %w: %w %w",
|
||||
config.Descriptor(), err, debugframes.GetTraces())
|
||||
}
|
||||
|
||||
if err = cmd.Start(); err != nil {
|
||||
@@ -80,38 +83,41 @@ func StartProcess(config ProcessConfig) (*exec.Cmd, error) {
|
||||
|
||||
func serviceLogReader(config ProcessConfig, stream io.Reader, id StreamID) {
|
||||
out := config.LogChannel()
|
||||
eChan := config.ErrorChannel()
|
||||
defer func() {
|
||||
if o := recover(); o != nil {
|
||||
out <- o
|
||||
//TODO: include stack trace, make the return object an error
|
||||
eChan <- fmt.Errorf("log reader %s of %w panic: %v %w",
|
||||
id.String(), config.Descriptor(), o, debugframes.PanicTrace(debug.Stack()))
|
||||
} else {
|
||||
eChan <- fmt.Errorf("log reader %s of %w returned", id.String(), config.Descriptor())
|
||||
}
|
||||
}()
|
||||
|
||||
scanner := bufio.NewScanner(stream)
|
||||
for scanner.Scan() {
|
||||
out <- ProcessLogEntry{
|
||||
ID: config.Descriptor(),
|
||||
LogEntry: scanner.Text(),
|
||||
Descriptor: config.Descriptor(),
|
||||
LogEntry: scanner.Text(),
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
out <- fmt.Errorf("read stream from %w: %w %w", config.Descriptor(), err, debugframes.GetTraces())
|
||||
eChan <- fmt.Errorf("read %s from %w: %w %w",
|
||||
id.String(), config.Descriptor(), err, debugframes.GetTraces())
|
||||
}
|
||||
}
|
||||
|
||||
func serviceWaiter(config ProcessConfig, cmd *exec.Cmd) {
|
||||
ctrl := config.ControlChannel()
|
||||
eChan := config.ErrorChannel()
|
||||
defer func() {
|
||||
if o := recover(); o != nil {
|
||||
ctrl <- o
|
||||
//TODO: include stack trace
|
||||
eChan <- fmt.Errorf("%w panic: %v %w", config.Descriptor(), o, debugframes.PanicTrace(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
ctrl <- fmt.Errorf("wait error on service %w: %w", config.Descriptor(), err)
|
||||
eChan <- fmt.Errorf("wait error on service %w: %w", config.Descriptor(), err)
|
||||
} else {
|
||||
ctrl <- config.Descriptor()
|
||||
eChan <- config.Descriptor()
|
||||
}
|
||||
}
|
||||
|
||||
86
daemon/service.go
Normal file
86
daemon/service.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.suyono.dev/suyono/wingmate/config"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
name string
|
||||
logChannel chan<- any
|
||||
errChannel chan<- error
|
||||
}
|
||||
|
||||
func newService(name string, error chan<- error, logging chan<- any) *service {
|
||||
return &service{
|
||||
name: name,
|
||||
logChannel: logging,
|
||||
errChannel: error,
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: review back the implementation of service as WingMateDescriptor
|
||||
|
||||
func (s *service) String() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
func (s *service) Name() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
func (s *service) InstanceName() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
func (s *service) InstanceType() InstanceType {
|
||||
return Service
|
||||
}
|
||||
|
||||
func (s *service) Error() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
func (s *service) parseCommand() []string {
|
||||
key := fmt.Sprintf("%s.%s.%s", Service.String(), s.name, config.CommandKey)
|
||||
if !viper.IsSet(key) {
|
||||
panic(fmt.Errorf("parse command: key %s is not set", key))
|
||||
}
|
||||
result := viper.GetStringSlice(key)
|
||||
if len(result) < 1 {
|
||||
panic(fmt.Errorf("parse command: zero command length"))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *service) ExecutableName() string {
|
||||
return s.parseCommand()[0]
|
||||
}
|
||||
|
||||
func (s *service) Args() []string {
|
||||
return s.parseCommand()[1:]
|
||||
}
|
||||
|
||||
func (s *service) Descriptor() WingMateDescriptor {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *service) Env() map[string]string {
|
||||
//TODO: stub implementation; FIX!
|
||||
return make(map[string]string)
|
||||
}
|
||||
|
||||
func (s *service) WorkingDir() string {
|
||||
//TODO: stub implementation; FIX!
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *service) LogChannel() chan<- any {
|
||||
return s.logChannel
|
||||
}
|
||||
|
||||
func (s *service) ErrorChannel() chan<- error {
|
||||
return s.errChannel
|
||||
}
|
||||
82
daemon/service_test.go
Normal file
82
daemon/service_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"gitea.suyono.dev/suyono/wingmate/config"
|
||||
"gitea.suyono.dev/suyono/wingmate/files/testconfig"
|
||||
"github.com/spf13/viper"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type parseCommandTestCase struct {
|
||||
name string
|
||||
pre func(t *testing.T, tt *parseCommandTestCase)
|
||||
post func(t *testing.T, tt *parseCommandTestCase)
|
||||
}
|
||||
|
||||
func TestParseCommand(t *testing.T) {
|
||||
tests := []parseCommandTestCase{
|
||||
{
|
||||
name: "initial",
|
||||
pre: func(t *testing.T, tt *parseCommandTestCase) {
|
||||
var (
|
||||
f *os.File
|
||||
err error
|
||||
fname, key string
|
||||
)
|
||||
if f, err = os.CreateTemp("", "config-*.yml"); err != nil {
|
||||
t.Fatal("create temp:", err)
|
||||
}
|
||||
fname = f.Name()
|
||||
if _, err = f.WriteString(testconfig.One); err != nil {
|
||||
t.Fatal("writing temp:", err)
|
||||
}
|
||||
if err = f.Close(); err != nil {
|
||||
t.Fatal("closing temp:", err)
|
||||
}
|
||||
|
||||
key = strings.ToUpper(config.EnvPrefix + "_" + config.PathKey)
|
||||
if err = os.Setenv(key, fname); err != nil {
|
||||
t.Fatal("set up env failed", err)
|
||||
}
|
||||
|
||||
tt.post = func(t *testing.T, tt *parseCommandTestCase) {
|
||||
_ = os.Unsetenv(key)
|
||||
_ = os.Remove(fname)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var (
|
||||
err error
|
||||
d *daemon
|
||||
s *service
|
||||
)
|
||||
|
||||
if tt.pre != nil {
|
||||
tt.pre(t, &tt)
|
||||
}
|
||||
if tt.post != nil {
|
||||
defer tt.post(t, &tt)
|
||||
}
|
||||
|
||||
if err = config.Read(nil, []string{}); err != nil {
|
||||
t.Fatal("reading config", err)
|
||||
}
|
||||
|
||||
if d, err = start(); err != nil {
|
||||
t.Fatal("starting daemon", err)
|
||||
}
|
||||
|
||||
for _, s = range d.services {
|
||||
s.parseCommand()
|
||||
}
|
||||
|
||||
x := viper.GetStringSlice("non.existent")
|
||||
t.Log(x)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user