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
This commit is contained in:
Suyono 2024-03-27 23:04:30 +11:00
parent a63646aab2
commit 7db6f6f8f3
13 changed files with 295 additions and 40 deletions

View File

@ -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

View File

@ -0,0 +1,7 @@
package cli
import "testing"
func TestSplitArgs(t *testing.T) {
SplitArgs([]string{"wmexec", "--user", "1200", "--", "wmspawner"})
}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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" ]

View File

@ -1,5 +1,8 @@
service:
one:
command: [ "/workspace/wingmate/cmd/experiment/starter/starter" ]
environ: [ "DUMMY_PATH=/workspace/wingmate/cmd/experiment/dummy/dummy" ]
# one:
# command: [ "wmstarter" ]
# environ: [ "DUMMY_PATH=/workspace/wingmate/cmd/experiment/dummy/dummy" ]
spawner:
command: [ "wmspawner" ]
user: "1200"

View File

@ -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{}

View File

@ -30,6 +30,7 @@ type Task interface {
UserGroup() UserGroup
WorkingDir() string
Status() TaskStatus
UtilDepCheck() error
}
type CronTask interface {

View File

@ -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{}

View File

@ -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
}

View File

@ -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
}