From 7db6f6f8f3be07860d8626a8236e608af62a590a Mon Sep 17 00:00:00 2001 From: Suyono Date: Wed, 27 Mar 2024 23:04:30 +1100 Subject: [PATCH] fix(splitargs): wrong code, check should be outside of loop feat(config): added WMPidProxyCheckVersion and WMExecCheckVersion to the interface; mutex for accessing viper fixed(docker/bookworm-newconfig): golang version and config path feat(UtilDepCheck): added utility dependency check before running the task --- cmd/cli/splitargs.go | 19 ++- cmd/cli/splitargs_test.go | 7 ++ cmd/exec/exec.go | 2 +- cmd/pidproxy/pidproxy.go | 2 +- cmd/wingmate/bridge.go | 2 +- config/config.go | 46 +++++++ docker/bookworm-newconfig/Dockerfile | 6 +- .../etc/wingmate/wingmate.yaml | 11 +- init/cron.go | 4 + init/init.go | 1 + init/service.go | 5 + task/cron.go | 116 ++++++++++++++++-- task/task.go | 114 +++++++++++++++-- 13 files changed, 295 insertions(+), 40 deletions(-) create mode 100644 cmd/cli/splitargs_test.go diff --git a/cmd/cli/splitargs.go b/cmd/cli/splitargs.go index 5a07433..4c7e56d 100644 --- a/cmd/cli/splitargs.go +++ b/cmd/cli/splitargs.go @@ -2,10 +2,9 @@ package cli import ( "errors" - "os" ) -func SplitArgs() ([]string, []string, error) { +func SplitArgs(args []string) ([]string, []string, error) { var ( i int arg string @@ -13,25 +12,25 @@ func SplitArgs() ([]string, []string, error) { childArgs []string ) found := false - for i, arg = range os.Args { + for i, arg = range args { if arg == "--" { found = true - if i+1 == len(os.Args) { + if i+1 == len(args) { return nil, nil, errors.New("invalid argument") } - if len(os.Args[i+1:]) == 0 { + if len(args[i+1:]) == 0 { return nil, nil, errors.New("invalid argument") } - selfArgs = os.Args[1:i] - childArgs = os.Args[i+1:] + selfArgs = args[1:i] + childArgs = args[i+1:] break } + } - if !found { - return nil, nil, errors.New("invalid argument") - } + if !found { + return nil, nil, errors.New("invalid argument") } return selfArgs, childArgs, nil diff --git a/cmd/cli/splitargs_test.go b/cmd/cli/splitargs_test.go new file mode 100644 index 0000000..2874c50 --- /dev/null +++ b/cmd/cli/splitargs_test.go @@ -0,0 +1,7 @@ +package cli + +import "testing" + +func TestSplitArgs(t *testing.T) { + SplitArgs([]string{"wmexec", "--user", "1200", "--", "wmspawner"}) +} diff --git a/cmd/exec/exec.go b/cmd/exec/exec.go index 666abed..e3b2e68 100644 --- a/cmd/exec/exec.go +++ b/cmd/exec/exec.go @@ -71,7 +71,7 @@ func main() { app.version.Cmd(rootCmd) - selfArgs, childArgs, err = cli.SplitArgs() + selfArgs, childArgs, err = cli.SplitArgs(os.Args) app.childArgs = childArgs app.err = err diff --git a/cmd/pidproxy/pidproxy.go b/cmd/pidproxy/pidproxy.go index 67b387d..0bb89b6 100644 --- a/cmd/pidproxy/pidproxy.go +++ b/cmd/pidproxy/pidproxy.go @@ -68,7 +68,7 @@ func main() { app.version.Flag(rootCmd) app.version.Cmd(rootCmd) - selfArgs, childArgs, err = cli.SplitArgs() + selfArgs, childArgs, err = cli.SplitArgs(os.Args) app.childArgs = childArgs app.err = err diff --git a/cmd/wingmate/bridge.go b/cmd/wingmate/bridge.go index 3d73f6e..6b084c1 100644 --- a/cmd/wingmate/bridge.go +++ b/cmd/wingmate/bridge.go @@ -53,7 +53,7 @@ func convert(cfg *config.Config) *wConfig { ct := task.NewCronTask(c.Name).SetCommand(c.Command...).SetEnv(c.Environ...) ct.SetFlagSetsid(c.Setsid).SetWorkingDir(c.WorkingDir).SetUser(c.User).SetGroup(c.Group) - ct.SetSchedule(schedule) + ct.SetSchedule(c.Schedule, schedule) ct.SetConfig(cfg) retval.tasks.AddCron(ct) diff --git a/config/config.go b/config/config.go index d1b62d3..07814a5 100644 --- a/config/config.go +++ b/config/config.go @@ -2,6 +2,7 @@ package config import ( "errors" + "fmt" "os" "path/filepath" "strings" @@ -108,6 +109,7 @@ func Read() (*Config, error) { serviceAvailable = false cronAvailable = false outConfig := &Config{ + viperMtx: &sync.Mutex{}, ServiceV0: make([]string, 0), } configPath := viper.GetString(ConfigPath) @@ -160,6 +162,13 @@ func Read() (*Config, error) { return outConfig, nil } +func (c *Config) GetAppVersion() string { + c.viperMtx.Lock() + defer c.viperMtx.Unlock() + + return viper.GetString(WingmateVersion) +} + func (c *Config) WMPidProxyPath() string { c.viperMtx.Lock() defer c.viperMtx.Unlock() @@ -167,9 +176,46 @@ func (c *Config) WMPidProxyPath() string { return viper.GetString(PidProxyPathConfig) } +func (c *Config) WMPidProxyCheckVersion() error { + var ( + binVersion string + appVersion string + err error + ) + + if binVersion, err = getVersion(c.WMPidProxyPath()); err != nil { + return fmt.Errorf("get wmpidproxy version: %w", err) + } + + appVersion = c.GetAppVersion() + if appVersion != binVersion { + return fmt.Errorf("wmpidproxy version mismatch") + } + return nil +} + func (c *Config) WMExecPath() string { c.viperMtx.Lock() defer c.viperMtx.Unlock() return viper.GetString(ExecPathConfig) } + +func (c *Config) WMExecCheckVersion() error { + var ( + binVersion string + appVersion string + err error + ) + + if binVersion, err = getVersion(c.WMExecPath()); err != nil { + return fmt.Errorf("get wmexec version: %w", err) + } + + appVersion = c.GetAppVersion() + if appVersion != binVersion { + return fmt.Errorf("wmexec version mismatch") + } + + return nil +} diff --git a/docker/bookworm-newconfig/Dockerfile b/docker/bookworm-newconfig/Dockerfile index 9021fc9..92272aa 100644 --- a/docker/bookworm-newconfig/Dockerfile +++ b/docker/bookworm-newconfig/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21-bookworm as builder +FROM golang:1.22-bookworm as builder ADD . /root/wingmate WORKDIR /root/wingmate/ @@ -13,8 +13,8 @@ RUN ln -sf /usr/share/zoneinfo/Australia/Sydney /etc/localtime && \ apt update && apt install -y procps && \ useradd -m -s /bin/bash user1 COPY --from=builder /usr/local/bin/wingmate/ /usr/local/bin/ -ADD --chmod=755 docker/bookworm/entry.sh /usr/local/bin/entry.sh -ADD --chmod=755 docker/bookworm/etc /etc +ADD --chmod=755 docker/bookworm-newconfig/entry.sh /usr/local/bin/entry.sh +ADD --chmod=755 docker/bookworm-newconfig/etc /etc ENTRYPOINT [ "/usr/local/bin/entry.sh" ] CMD [ "/usr/local/bin/wingmate" ] \ No newline at end of file diff --git a/docker/bookworm-newconfig/etc/wingmate/wingmate.yaml b/docker/bookworm-newconfig/etc/wingmate/wingmate.yaml index 15073d3..da3e3fa 100644 --- a/docker/bookworm-newconfig/etc/wingmate/wingmate.yaml +++ b/docker/bookworm-newconfig/etc/wingmate/wingmate.yaml @@ -1,5 +1,8 @@ service: - one: - command: [ "/workspace/wingmate/cmd/experiment/starter/starter" ] - environ: [ "DUMMY_PATH=/workspace/wingmate/cmd/experiment/dummy/dummy" ] - \ No newline at end of file +# one: +# command: [ "wmstarter" ] +# environ: [ "DUMMY_PATH=/workspace/wingmate/cmd/experiment/dummy/dummy" ] + + spawner: + command: [ "wmspawner" ] + user: "1200" diff --git a/init/cron.go b/init/cron.go index a620662..17d41b0 100644 --- a/init/cron.go +++ b/init/cron.go @@ -29,6 +29,10 @@ cron: for { if cron.TimeToRun(time.Now()) { wingmate.Log().Info().Str(cronTag, cron.Name()).Msg("executing") + if err = cron.UtilDepCheck(); err != nil { + wingmate.Log().Error().Str(cronTag, cron.Name()).Msgf("%+v", err) + goto fail + } cmd = exec.Command(cron.Command(), cron.Arguments()...) iwg = &sync.WaitGroup{} diff --git a/init/init.go b/init/init.go index d694dde..36e1093 100644 --- a/init/init.go +++ b/init/init.go @@ -30,6 +30,7 @@ type Task interface { UserGroup() UserGroup WorkingDir() string Status() TaskStatus + UtilDepCheck() error } type CronTask interface { diff --git a/init/service.go b/init/service.go index c07af6e..d9a3ada 100644 --- a/init/service.go +++ b/init/service.go @@ -33,6 +33,11 @@ func (i *Init) service(wg *sync.WaitGroup, task ServiceTask, exitFlag <-chan any service: for { failStatus = false + if err = task.UtilDepCheck(); err != nil { + wingmate.Log().Error().Str(serviceTag, task.Name()).Msgf("%+v", err) + failStatus = true + goto fail + } cmd = exec.Command(task.Command(), task.Arguments()...) iwg = &sync.WaitGroup{} diff --git a/task/cron.go b/task/cron.go index 167cdbf..c31cbd8 100644 --- a/task/cron.go +++ b/task/cron.go @@ -1,6 +1,10 @@ package task import ( + "crypto/sha256" + "encoding/json" + "fmt" + "gitea.suyono.dev/suyono/wingmate" "time" wminit "gitea.suyono.dev/suyono/wingmate/init" @@ -70,14 +74,16 @@ func (cms *CronMultiOccurrenceSpec) Match(u uint8) bool { type CronTask struct { CronSchedule userGroup - name string - command []string - environ []string - setsid bool - workingDir string - lastRun time.Time - hasRun bool //NOTE: make sure initialised as false - config config + cronScheduleString string + name string + command []string + cmdLine []string + environ []string + setsid bool + workingDir string + lastRun time.Time + hasRun bool //NOTE: make sure initialised as false + config config } func NewCronTask(name string) *CronTask { @@ -119,7 +125,8 @@ func (c *CronTask) SetGroup(group string) *CronTask { return c } -func (c *CronTask) SetSchedule(schedule CronSchedule) *CronTask { +func (c *CronTask) SetSchedule(scheduleStr string, schedule CronSchedule) *CronTask { + c.cronScheduleString = scheduleStr c.CronSchedule = schedule return c } @@ -129,21 +136,104 @@ func (c *CronTask) SetConfig(config config) *CronTask { return c } +func (c *CronTask) Equals(another *CronTask) bool { + if another == nil { + return false + } + + type toCompare struct { + Name string + Command string + Arguments []string + Environ []string + Setsid bool + UserGroup string + WorkingDir string + Schedule string + } + + cmpStruct := func(p *CronTask) ([]byte, error) { + s := &toCompare{ + Name: c.Name(), + Command: c.Command(), + Arguments: c.Arguments(), + Environ: c.Environ(), + Setsid: c.Setsid(), + UserGroup: c.UserGroup().String(), + WorkingDir: c.WorkingDir(), + Schedule: c.cronScheduleString, + } + + return json.Marshal(s) + } + + var ( + err error + ours, theirs []byte + ourHash, theirHash [sha256.Size]byte + ) + + if ours, err = cmpStruct(c); err != nil { + wingmate.Log().Error().Msgf("cron task equals: %+v", err) + return false + } + ourHash = sha256.Sum256(ours) + + if theirs, err = cmpStruct(another); err != nil { + wingmate.Log().Error().Msgf("cron task equals: %+v", err) + return false + } + theirHash = sha256.Sum256(theirs) + + for i := 0; i < sha256.Size; i++ { + if ourHash[i] != theirHash[i] { + return false + } + } + + return true +} + func (c *CronTask) Name() string { return c.name } +func (c *CronTask) UtilDepCheck() error { + c.cmdLine = make([]string, 0) + if c.setsid || c.UserGroup().IsSet() { + if err := c.config.WMExecCheckVersion(); err != nil { + return fmt.Errorf("utility dependency check: %w", err) + } + + c.cmdLine = append(c.cmdLine, c.config.WMExecPath()) + + if c.setsid { + c.cmdLine = append(c.cmdLine, "--setsid") + } + + if c.UserGroup().IsSet() { + c.cmdLine = append(c.cmdLine, "--user", c.UserGroup().String()) + } + + c.cmdLine = append(c.cmdLine, "--") + } + + c.cmdLine = append(c.cmdLine, c.command...) + + return nil +} + func (c *CronTask) Command() string { - return c.command[0] + return c.cmdLine[0] } func (c *CronTask) Arguments() []string { - if len(c.command) == 1 { + if len(c.cmdLine) == 1 { return nil } - retval := make([]string, len(c.command)-1) - copy(retval, c.command[1:]) + retval := make([]string, len(c.cmdLine)-1) + copy(retval, c.cmdLine[1:]) return retval } diff --git a/task/task.go b/task/task.go index 853732e..4b7c6f2 100644 --- a/task/task.go +++ b/task/task.go @@ -1,14 +1,19 @@ package task import ( + "crypto/sha256" + "encoding/json" "fmt" + "gitea.suyono.dev/suyono/wingmate" wminit "gitea.suyono.dev/suyono/wingmate/init" ) type config interface { WMPidProxyPath() string + WMPidProxyCheckVersion() error WMExecPath() string + WMExecCheckVersion() error } type Tasks struct { @@ -33,7 +38,7 @@ func (ts *Tasks) AddService(serviceTask *ServiceTask) *ServiceTask { } func (ts *Tasks) AddV0Cron(schedule CronSchedule, path string) { - ts.AddCron(NewCronTask(path)).SetCommand(path).SetSchedule(schedule) + ts.AddCron(NewCronTask(path)).SetCommand(path).SetSchedule("", schedule) } func (ts *Tasks) AddCron(cronTask *CronTask) *CronTask { @@ -138,6 +143,70 @@ func (t *ServiceTask) SetConfig(config config) *ServiceTask { return t } +func (t *ServiceTask) Equals(another *ServiceTask) bool { + if another == nil { + return false + } + + type toCompare struct { + Name string + Command string + Arguments []string + Environ []string + Setsid bool + UserGroup string + WorkingDir string + PidFile string + StartSecs uint + AutoStart bool + AutoRestart bool + } + + cmpStruct := func(p *ServiceTask) ([]byte, error) { + s := &toCompare{ + Name: p.Name(), + Command: p.Command(), + Arguments: p.Arguments(), + Environ: p.Environ(), + Setsid: p.Setsid(), + UserGroup: p.UserGroup().String(), + WorkingDir: p.WorkingDir(), + PidFile: p.PidFile(), + StartSecs: p.StartSecs(), + AutoStart: p.AutoStart(), + AutoRestart: p.AutoRestart(), + } + + return json.Marshal(s) + } + + var ( + err error + ours, theirs []byte + ourHash, theirHash [sha256.Size]byte + ) + + if ours, err = cmpStruct(t); err != nil { + wingmate.Log().Error().Msgf("task equals: %+v", err) + return false + } + ourHash = sha256.Sum256(ours) + + if theirs, err = cmpStruct(another); err != nil { + wingmate.Log().Error().Msgf("task equals: %+v", err) + return false + } + theirHash = sha256.Sum256(theirs) + + for i := 0; i < sha256.Size; i++ { + if ourHash[i] != theirHash[i] { + return false + } + } + + return true +} + func (t *ServiceTask) Validate() error { // call this function for validate the field return validate( /* input the validators here */ ) @@ -176,19 +245,50 @@ func (t *ServiceTask) prepareCommandLine() []string { return t.cmdLine } +func (t *ServiceTask) UtilDepCheck() error { + t.cmdLine = make([]string, 0) + if t.background { + if err := t.config.WMPidProxyCheckVersion(); err != nil { + return fmt.Errorf("utility dependency check: %w", err) + } + + t.cmdLine = append(t.cmdLine, t.config.WMPidProxyPath(), "--pid-file", t.pidFile, "--") + } + + if t.setsid || t.UserGroup().IsSet() { + if err := t.config.WMExecCheckVersion(); err != nil { + return fmt.Errorf("utility dependency check: %w", err) + } + + t.cmdLine = append(t.cmdLine, t.config.WMExecPath()) + + if t.setsid { + t.cmdLine = append(t.cmdLine, "--setsid") + } + + if t.UserGroup().IsSet() { + t.cmdLine = append(t.cmdLine, "--user", t.UserGroup().String()) + } + + t.cmdLine = append(t.cmdLine, "--") + } + + t.cmdLine = append(t.cmdLine, t.command...) + + return nil +} + func (t *ServiceTask) Command() string { - cl := t.prepareCommandLine() - return cl[0] + return t.cmdLine[0] } func (t *ServiceTask) Arguments() []string { - cl := t.prepareCommandLine() - if len(cl) == 1 { + if len(t.cmdLine) == 1 { return nil } - retval := make([]string, len(cl)-1) - copy(retval, cl[1:]) + retval := make([]string, len(t.cmdLine)-1) + copy(retval, t.cmdLine[1:]) return retval }