WIP: updated log level on some log entries and added environment variable expansion for command line and its arguments
This commit is contained in:
parent
2474d3cddf
commit
1827cf2e3e
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"gitea.suyono.dev/suyono/wingmate"
|
"gitea.suyono.dev/suyono/wingmate"
|
||||||
"gitea.suyono.dev/suyono/wingmate/cmd/cli"
|
"gitea.suyono.dev/suyono/wingmate/cmd/cli"
|
||||||
|
wmenv "gitea.suyono.dev/suyono/wingmate/task/env"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
@ -74,7 +75,7 @@ func main() {
|
|||||||
if selfArgs, childArgs, err = cli.SplitArgs(os.Args); err != nil {
|
if selfArgs, childArgs, err = cli.SplitArgs(os.Args); err != nil {
|
||||||
selfArgs = os.Args
|
selfArgs = os.Args
|
||||||
}
|
}
|
||||||
app.childArgs = childArgs
|
app.childArgs = wmenv.ExpandEnv(os.Environ(), childArgs)
|
||||||
app.err = err
|
app.err = err
|
||||||
|
|
||||||
rootCmd.SetArgs(selfArgs[1:])
|
rootCmd.SetArgs(selfArgs[1:])
|
||||||
|
|||||||
@ -22,6 +22,7 @@ func main() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
_ = wingmate.NewLog(os.Stderr, wingmate.Time|wingmate.Caller)
|
_ = wingmate.NewLog(os.Stderr, wingmate.Time|wingmate.Caller)
|
||||||
|
wingmate.SetGlobalLevel(wingmate.InfoLevel)
|
||||||
config.SetVersion(version)
|
config.SetVersion(version)
|
||||||
config.ParseFlags()
|
config.ParseFlags()
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,7 @@ const (
|
|||||||
WMPidProxyPathFlag = "pid-proxy"
|
WMPidProxyPathFlag = "pid-proxy"
|
||||||
WMExecPathFlag = "exec"
|
WMExecPathFlag = "exec"
|
||||||
PathConfigFlag = "config"
|
PathConfigFlag = "config"
|
||||||
|
GlobalLogConfig = "log_level"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@ -84,9 +85,11 @@ func Read() (*Config, error) {
|
|||||||
_ = viper.BindEnv(PathConfig)
|
_ = viper.BindEnv(PathConfig)
|
||||||
_ = viper.BindEnv(PidProxyPathConfig)
|
_ = viper.BindEnv(PidProxyPathConfig)
|
||||||
_ = viper.BindEnv(ExecPathConfig)
|
_ = viper.BindEnv(ExecPathConfig)
|
||||||
|
_ = viper.BindEnv(GlobalLogConfig)
|
||||||
viper.SetDefault(PathConfig, DefaultConfigPath)
|
viper.SetDefault(PathConfig, DefaultConfigPath)
|
||||||
viper.SetDefault(PidProxyPathConfig, PidProxyPathDefault)
|
viper.SetDefault(PidProxyPathConfig, PidProxyPathDefault)
|
||||||
viper.SetDefault(ExecPathConfig, ExecPathDefault)
|
viper.SetDefault(ExecPathConfig, ExecPathDefault)
|
||||||
|
viper.SetDefault(GlobalLogConfig, wingmate.InfoLevelStr)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dirent []os.DirEntry
|
dirent []os.DirEntry
|
||||||
@ -101,6 +104,7 @@ func Read() (*Config, error) {
|
|||||||
crones []CronTask
|
crones []CronTask
|
||||||
)
|
)
|
||||||
|
|
||||||
|
wingmate.SetGlobalLevelStr(viper.GetString(GlobalLogConfig))
|
||||||
serviceAvailable = false
|
serviceAvailable = false
|
||||||
cronAvailable = false
|
cronAvailable = false
|
||||||
outConfig := &Config{
|
outConfig := &Config{
|
||||||
@ -111,7 +115,7 @@ func Read() (*Config, error) {
|
|||||||
svcdir = filepath.Join(configPath, ServiceDirName)
|
svcdir = filepath.Join(configPath, ServiceDirName)
|
||||||
dirent, err = os.ReadDir(svcdir)
|
dirent, err = os.ReadDir(svcdir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wingmate.Log().Error().Msgf("encounter error when reading service directory %s: %+v", svcdir, err)
|
wingmate.Log().Warn().Msgf("encounter error when reading service directory %s: %+v", svcdir, err)
|
||||||
}
|
}
|
||||||
if len(dirent) > 0 {
|
if len(dirent) > 0 {
|
||||||
for _, d := range dirent {
|
for _, d := range dirent {
|
||||||
@ -134,7 +138,7 @@ func Read() (*Config, error) {
|
|||||||
cronAvailable = true
|
cronAvailable = true
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wingmate.Log().Error().Msgf("encounter error when reading crontab %s: %+v", crontabfile, err)
|
wingmate.Log().Warn().Msgf("encounter error when reading crontab %s: %+v", crontabfile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wingmateConfigAvailable = false
|
wingmateConfigAvailable = false
|
||||||
|
|||||||
@ -23,6 +23,7 @@ func (i *Init) cron(wg *sync.WaitGroup, cron CronTask, exitFlag <-chan any) {
|
|||||||
stdout io.ReadCloser
|
stdout io.ReadCloser
|
||||||
stderr io.ReadCloser
|
stderr io.ReadCloser
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
|
patchedEnv []string
|
||||||
)
|
)
|
||||||
|
|
||||||
ticker := time.NewTicker(time.Second * 30)
|
ticker := time.NewTicker(time.Second * 30)
|
||||||
@ -34,8 +35,9 @@ cron:
|
|||||||
wingmate.Log().Error().Str(cronTag, cron.Name()).Msgf("%+v", err)
|
wingmate.Log().Error().Str(cronTag, cron.Name()).Msgf("%+v", err)
|
||||||
goto fail
|
goto fail
|
||||||
}
|
}
|
||||||
cmd = exec.Command(cron.Command(), cron.Arguments()...)
|
patchedEnv = cron.PatchEnv(os.Environ())
|
||||||
cmd.Env = cron.PatchEnv(os.Environ())
|
cmd = exec.Command(cron.Command(patchedEnv...), cron.Arguments(patchedEnv...)...)
|
||||||
|
cmd.Env = patchedEnv
|
||||||
|
|
||||||
if len(cron.WorkingDir()) > 0 {
|
if len(cron.WorkingDir()) > 0 {
|
||||||
cmd.Dir = cron.WorkingDir()
|
cmd.Dir = cron.WorkingDir()
|
||||||
|
|||||||
@ -23,8 +23,8 @@ type TaskStatus interface {
|
|||||||
|
|
||||||
type Task interface {
|
type Task interface {
|
||||||
Name() string
|
Name() string
|
||||||
Command() string
|
Command(...string) string
|
||||||
Arguments() []string
|
Arguments(...string) []string
|
||||||
EnvLen() int
|
EnvLen() int
|
||||||
Environ() []string
|
Environ() []string
|
||||||
PatchEnv([]string) []string
|
PatchEnv([]string) []string
|
||||||
|
|||||||
@ -25,6 +25,7 @@ func (i *Init) service(wg *sync.WaitGroup, task ServiceTask, exitFlag <-chan any
|
|||||||
stdout io.ReadCloser
|
stdout io.ReadCloser
|
||||||
failStatus bool
|
failStatus bool
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
|
patchedEnv []string
|
||||||
)
|
)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -39,8 +40,10 @@ service:
|
|||||||
failStatus = true
|
failStatus = true
|
||||||
goto fail
|
goto fail
|
||||||
}
|
}
|
||||||
cmd = exec.Command(task.Command(), task.Arguments()...)
|
|
||||||
cmd.Env = task.PatchEnv(os.Environ())
|
patchedEnv = task.PatchEnv(os.Environ())
|
||||||
|
cmd = exec.Command(task.Command(patchedEnv...), task.Arguments(patchedEnv...)...)
|
||||||
|
cmd.Env = patchedEnv
|
||||||
|
|
||||||
if len(task.WorkingDir()) > 0 {
|
if len(task.WorkingDir()) > 0 {
|
||||||
cmd.Dir = task.WorkingDir()
|
cmd.Dir = task.WorkingDir()
|
||||||
@ -104,5 +107,5 @@ func (i *Init) pipeReader(wg *sync.WaitGroup, pipe io.ReadCloser, tag, serviceNa
|
|||||||
wingmate.Log().Error().Str(tag, serviceName).Msgf("got error when reading pipe: %#v", err)
|
wingmate.Log().Error().Str(tag, serviceName).Msgf("got error when reading pipe: %#v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wingmate.Log().Info().Str(tag, serviceName).Msg("closing pipe")
|
wingmate.Log().Debug().Str(tag, serviceName).Msg("closing pipe")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,9 +52,9 @@ func (i *Init) sigTermPump(startTime time.Time, selfExit <-chan any) status {
|
|||||||
t := time.NewTicker(time.Millisecond * 100)
|
t := time.NewTicker(time.Millisecond * 100)
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
|
|
||||||
wingmate.Log().Info().Msg("start pumping SIGTERM signal")
|
wingmate.Log().Debug().Msg("start pumping SIGTERM signal")
|
||||||
defer func() {
|
defer func() {
|
||||||
wingmate.Log().Info().Msg("stop pumping SIGTERM signal")
|
wingmate.Log().Debug().Msg("stop pumping SIGTERM signal")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for time.Since(startTime) < time.Duration(time.Second*4) {
|
for time.Since(startTime) < time.Duration(time.Second*4) {
|
||||||
|
|||||||
@ -20,7 +20,7 @@ func (i *Init) waiter(wg *sync.WaitGroup, runningFlag <-chan any, sigHandlerFlag
|
|||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
wingmate.Log().Info().Msg("waiter exiting...")
|
wingmate.Log().Debug().Msg("waiter exiting...")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
running = true
|
running = true
|
||||||
@ -41,7 +41,7 @@ wait:
|
|||||||
} else {
|
} else {
|
||||||
select {
|
select {
|
||||||
case <-runningFlag:
|
case <-runningFlag:
|
||||||
wingmate.Log().Info().Msg("waiter received shutdown signal...")
|
wingmate.Log().Debug().Msg("waiter received shutdown signal...")
|
||||||
running = false
|
running = false
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
@ -55,14 +55,14 @@ wait:
|
|||||||
if flagged {
|
if flagged {
|
||||||
close(sigHandlerFlag)
|
close(sigHandlerFlag)
|
||||||
flagged = false
|
flagged = false
|
||||||
wingmate.Log().Warn().Msg("waiter: inner flag")
|
wingmate.Log().Debug().Msg("waiter: inner flag")
|
||||||
}
|
}
|
||||||
wingmate.Log().Warn().Msg("waiter: no child left")
|
wingmate.Log().Debug().Msg("waiter: no child left")
|
||||||
break wait
|
break wait
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wingmate.Log().Warn().Msgf("Wait4 returns error: %+v", err)
|
wingmate.Log().Debug().Msgf("Wait4 returns error: %+v", err)
|
||||||
waitingForSignal = true
|
waitingForSignal = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
68
logger.go
68
logger.go
@ -17,6 +17,26 @@ const (
|
|||||||
Caller
|
Caller
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PanicLevel = 5 - iota
|
||||||
|
FatalLevel
|
||||||
|
ErrorLevel
|
||||||
|
WarnLevel
|
||||||
|
InfoLevel
|
||||||
|
DebugLevel
|
||||||
|
TraceLevel
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PanicLevelStr = "panic"
|
||||||
|
FatalLevelStr = "fatal"
|
||||||
|
ErrorLevelStr = "error"
|
||||||
|
WarnLevelStr = "warn"
|
||||||
|
InfoLevelStr = "info"
|
||||||
|
DebugLevelStr = "debug"
|
||||||
|
TraceLevelStr = "trace"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
w *wrapper
|
w *wrapper
|
||||||
)
|
)
|
||||||
@ -41,13 +61,51 @@ func Log() logger.Log {
|
|||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetGlobalLevel(level int) {
|
||||||
|
switch level {
|
||||||
|
case PanicLevel:
|
||||||
|
zerolog.SetGlobalLevel(zerolog.PanicLevel)
|
||||||
|
case FatalLevel:
|
||||||
|
zerolog.SetGlobalLevel(zerolog.FatalLevel)
|
||||||
|
case ErrorLevel:
|
||||||
|
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
||||||
|
case WarnLevel:
|
||||||
|
zerolog.SetGlobalLevel(zerolog.WarnLevel)
|
||||||
|
case InfoLevel:
|
||||||
|
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||||
|
case DebugLevel:
|
||||||
|
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||||
|
case TraceLevel:
|
||||||
|
zerolog.SetGlobalLevel(zerolog.TraceLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetGlobalLevelStr(level string) {
|
||||||
|
switch level {
|
||||||
|
case PanicLevelStr:
|
||||||
|
zerolog.SetGlobalLevel(zerolog.PanicLevel)
|
||||||
|
case FatalLevelStr:
|
||||||
|
zerolog.SetGlobalLevel(zerolog.FatalLevel)
|
||||||
|
case ErrorLevelStr:
|
||||||
|
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
||||||
|
case WarnLevelStr:
|
||||||
|
zerolog.SetGlobalLevel(zerolog.WarnLevel)
|
||||||
|
case InfoLevelStr:
|
||||||
|
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||||
|
case DebugLevelStr:
|
||||||
|
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||||
|
case TraceLevelStr:
|
||||||
|
zerolog.SetGlobalLevel(zerolog.TraceLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (w *wrapper) flagPass(e *zerolog.Event) *zerolog.Event {
|
func (w *wrapper) flagPass(e *zerolog.Event) *zerolog.Event {
|
||||||
if w.flag&Time != 0 {
|
if w.flag&Time != 0 {
|
||||||
e = e.Time(timeTag, time.Now())
|
e = e.Time(timeTag, time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
if w.flag&Caller != 0 {
|
if w.flag&Caller != 0 {
|
||||||
e = e.Caller()
|
e = e.Caller(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
return e
|
return e
|
||||||
@ -57,6 +115,14 @@ func (w *wrapper) Info() logger.Content {
|
|||||||
return (*eventWrapper)(w.flagPass(w.log.Info()))
|
return (*eventWrapper)(w.flagPass(w.log.Info()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) Debug() logger.Content {
|
||||||
|
return (*eventWrapper)(w.flagPass(w.log.Debug()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) Trace() logger.Content {
|
||||||
|
return (*eventWrapper)(w.flagPass(w.log.Trace()))
|
||||||
|
}
|
||||||
|
|
||||||
func (w *wrapper) Warn() logger.Content {
|
func (w *wrapper) Warn() logger.Content {
|
||||||
return (*eventWrapper)(w.flagPass(w.log.Warn()))
|
return (*eventWrapper)(w.flagPass(w.log.Warn()))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,13 +7,15 @@ type Content interface {
|
|||||||
Err(error) Content
|
Err(error) Content
|
||||||
}
|
}
|
||||||
|
|
||||||
type Level interface {
|
type LogLevel interface {
|
||||||
Info() Content
|
Info() Content
|
||||||
Warn() Content
|
Warn() Content
|
||||||
Error() Content
|
Error() Content
|
||||||
Fatal() Content
|
Fatal() Content
|
||||||
|
Debug() Content
|
||||||
|
Trace() Content
|
||||||
}
|
}
|
||||||
|
|
||||||
type Log interface {
|
type Log interface {
|
||||||
Level
|
LogLevel
|
||||||
}
|
}
|
||||||
|
|||||||
19
task/cron.go
19
task/cron.go
@ -4,6 +4,7 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
wmenv "gitea.suyono.dev/suyono/wingmate/task/env"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.suyono.dev/suyono/wingmate"
|
"gitea.suyono.dev/suyono/wingmate"
|
||||||
@ -224,19 +225,25 @@ func (c *CronTask) UtilDepCheck() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CronTask) Command() string {
|
func (c *CronTask) Command(env ...string) string {
|
||||||
|
if len(env) > 0 {
|
||||||
|
return wmenv.ExpandEnv(env, []string{c.cmdLine[0]})[0]
|
||||||
|
}
|
||||||
return c.cmdLine[0]
|
return c.cmdLine[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CronTask) Arguments() []string {
|
func (c *CronTask) Arguments(env ...string) []string {
|
||||||
if len(c.cmdLine) == 1 {
|
if len(c.cmdLine) == 1 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
retval := make([]string, len(c.cmdLine)-1)
|
argCopy := make([]string, len(c.cmdLine)-1)
|
||||||
copy(retval, c.cmdLine[1:])
|
copy(argCopy, c.cmdLine[1:])
|
||||||
|
if len(env) > 0 {
|
||||||
|
argCopy = wmenv.ExpandEnv(env, argCopy)
|
||||||
|
}
|
||||||
|
|
||||||
return retval
|
return argCopy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CronTask) EnvLen() int {
|
func (c *CronTask) EnvLen() int {
|
||||||
@ -250,7 +257,7 @@ func (c *CronTask) Environ() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *CronTask) PatchEnv(env []string) []string {
|
func (c *CronTask) PatchEnv(env []string) []string {
|
||||||
return patchEnv(env, c.environ)
|
return wmenv.PatchEnv(env, c.environ)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CronTask) Setsid() bool {
|
func (c *CronTask) Setsid() bool {
|
||||||
|
|||||||
97
task/env/env.go
vendored
Normal file
97
task/env/env.go
vendored
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"gitea.suyono.dev/suyono/wingmate"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
envCapture = regexp.MustCompile(`\$+[a-zA-Z_][a-zA-Z0-9_]*|\$+{[a-zA-Z_][a-zA-Z0-9_]*}`)
|
||||||
|
envEsc = regexp.MustCompile(`^\$\$+[^$]+$`) // escaped, starts with two or more $ character
|
||||||
|
envRef = regexp.MustCompile(`^\$([^$]+)$`) // capture the variable name
|
||||||
|
envRefExplicit = regexp.MustCompile(`^\${([^$]+)}$`) // capture the variable name - explicit
|
||||||
|
)
|
||||||
|
|
||||||
|
func expandEnv(envMap map[string]string, input string) string {
|
||||||
|
if envEsc.MatchString(input) {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
if envName := envRefExplicit.FindStringSubmatch(input); envName != nil && envName[1] != "" {
|
||||||
|
exVal, ok := envMap[envName[1]]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return exVal
|
||||||
|
}
|
||||||
|
|
||||||
|
if envName := envRef.FindStringSubmatch(input); envName != nil && envName[1] != "" {
|
||||||
|
exVal, ok := envMap[envName[1]]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return exVal
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func PatchEnv(existing, new []string) []string {
|
||||||
|
tMap := make(map[string]string)
|
||||||
|
for _, e := range existing {
|
||||||
|
key, value, ok := strings.Cut(e, "=")
|
||||||
|
if !ok {
|
||||||
|
wingmate.Log().Warn().Msgf("removing invalid environment:", e)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tMap[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range new {
|
||||||
|
key, value, ok := strings.Cut(e, "=")
|
||||||
|
if !ok {
|
||||||
|
wingmate.Log().Warn().Msgf("removing invalid environment:", e)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.ContainsAny(key, "$") {
|
||||||
|
wingmate.Log().Error().Err(fmt.Errorf("variable name contains $")).Msgf("removing invalid environment:", e)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
value = envCapture.ReplaceAllStringFunc(value, func(rep string) string {
|
||||||
|
return expandEnv(tMap, rep)
|
||||||
|
})
|
||||||
|
|
||||||
|
tMap[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
outEnv := make([]string, 0, len(existing))
|
||||||
|
for key, val := range tMap {
|
||||||
|
outEnv = append(outEnv, fmt.Sprintf("%s=%s", key, val))
|
||||||
|
}
|
||||||
|
|
||||||
|
return outEnv
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExpandEnv(env []string, input []string) []string {
|
||||||
|
envMap := make(map[string]string)
|
||||||
|
for _, e := range env {
|
||||||
|
key, value, ok := strings.Cut(e, "=")
|
||||||
|
if !ok {
|
||||||
|
wingmate.Log().Warn().Msgf("removing bad environment:", e)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
envMap[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, s := range input {
|
||||||
|
s = envCapture.ReplaceAllStringFunc(s, func(rep string) string {
|
||||||
|
return expandEnv(envMap, rep)
|
||||||
|
})
|
||||||
|
input[i] = s
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
}
|
||||||
88
task/task.go
88
task/task.go
@ -4,19 +4,9 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gitea.suyono.dev/suyono/wingmate"
|
"gitea.suyono.dev/suyono/wingmate"
|
||||||
|
|
||||||
wminit "gitea.suyono.dev/suyono/wingmate/init"
|
wminit "gitea.suyono.dev/suyono/wingmate/init"
|
||||||
)
|
wmenv "gitea.suyono.dev/suyono/wingmate/task/env"
|
||||||
|
|
||||||
var (
|
|
||||||
envCapture = regexp.MustCompile(`\$+[a-zA-Z_][a-zA-Z0-9_]*|\$+{[a-zA-Z_][a-zA-Z0-9_]*}`)
|
|
||||||
envEsc = regexp.MustCompile(`^\$\$+[^\$]+$`) // escaped, starts with two or more $ character
|
|
||||||
envRef = regexp.MustCompile(`^\$([^\$]+)$`) // capture the variable name
|
|
||||||
envRefExplicit = regexp.MustCompile(`^\${([^\$]+)}$`) // capture the variable name - explicit
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type config interface {
|
type config interface {
|
||||||
@ -227,6 +217,7 @@ func (t *ServiceTask) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *ServiceTask) prepareCommandLine() []string {
|
func (t *ServiceTask) prepareCommandLine() []string {
|
||||||
|
//TODO: is this method used somewhere? if not, can I remove this?
|
||||||
if len(t.cmdLine) > 0 {
|
if len(t.cmdLine) > 0 {
|
||||||
return t.cmdLine
|
return t.cmdLine
|
||||||
}
|
}
|
||||||
@ -288,19 +279,25 @@ func (t *ServiceTask) UtilDepCheck() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ServiceTask) Command() string {
|
func (t *ServiceTask) Command(env ...string) string {
|
||||||
|
if len(env) > 0 {
|
||||||
|
return wmenv.ExpandEnv(env, []string{t.cmdLine[0]})[0]
|
||||||
|
}
|
||||||
return t.cmdLine[0]
|
return t.cmdLine[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ServiceTask) Arguments() []string {
|
func (t *ServiceTask) Arguments(env ...string) []string {
|
||||||
if len(t.cmdLine) == 1 {
|
if len(t.cmdLine) == 1 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
retval := make([]string, len(t.cmdLine)-1)
|
argCopy := make([]string, len(t.cmdLine)-1)
|
||||||
copy(retval, t.cmdLine[1:])
|
copy(argCopy, t.cmdLine[1:])
|
||||||
|
if len(env) > 0 {
|
||||||
|
argCopy = wmenv.ExpandEnv(env, argCopy)
|
||||||
|
}
|
||||||
|
|
||||||
return retval
|
return argCopy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ServiceTask) EnvLen() int {
|
func (t *ServiceTask) EnvLen() int {
|
||||||
@ -314,7 +311,7 @@ func (t *ServiceTask) Environ() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *ServiceTask) PatchEnv(env []string) []string {
|
func (t *ServiceTask) PatchEnv(env []string) []string {
|
||||||
return patchEnv(env, t.environ)
|
return wmenv.PatchEnv(env, t.environ)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ServiceTask) Setsid() bool {
|
func (t *ServiceTask) Setsid() bool {
|
||||||
@ -385,60 +382,3 @@ func validate(validators ...func() error) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func patchEnv(existing, new []string) []string {
|
|
||||||
tMap := make(map[string]string)
|
|
||||||
for _, e := range existing {
|
|
||||||
key, value, ok := strings.Cut(e, "=")
|
|
||||||
if !ok {
|
|
||||||
wingmate.Log().Warn().Msgf("removing invalid environment:", e)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tMap[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, e := range new {
|
|
||||||
key, value, ok := strings.Cut(e, "=")
|
|
||||||
if !ok {
|
|
||||||
wingmate.Log().Warn().Msgf("removing invalid environment:", e)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.ContainsAny(key, "$") {
|
|
||||||
wingmate.Log().Error().Err(fmt.Errorf("variable name contains $")).Msgf("removing invalid environment:", e)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
value = envCapture.ReplaceAllStringFunc(value, func(rep string) string {
|
|
||||||
if envEsc.MatchString(rep) {
|
|
||||||
return rep
|
|
||||||
}
|
|
||||||
|
|
||||||
if envName := envRefExplicit.FindStringSubmatch(rep); envName != nil && envName[1] != "" {
|
|
||||||
exVal, ok := tMap[envName[1]]
|
|
||||||
if !ok {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return exVal
|
|
||||||
}
|
|
||||||
|
|
||||||
if envName := envRef.FindStringSubmatch(rep); envName != nil && envName[1] != "" {
|
|
||||||
exVal, ok := tMap[envName[1]]
|
|
||||||
if !ok {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return exVal
|
|
||||||
}
|
|
||||||
return rep
|
|
||||||
})
|
|
||||||
|
|
||||||
tMap[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
outEnv := make([]string, 0, len(existing))
|
|
||||||
for key, val := range tMap {
|
|
||||||
outEnv = append(outEnv, fmt.Sprintf("%s=%s", key, val))
|
|
||||||
}
|
|
||||||
|
|
||||||
return outEnv
|
|
||||||
}
|
|
||||||
|
|||||||
@ -112,17 +112,20 @@ func TestServiceTaskPatchEnv(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name+" - service", func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Run("service", func(t *testing.T) {
|
||||||
st := NewServiceTask(tt.name)
|
st := NewServiceTask(tt.name)
|
||||||
st.SetEnv(tt.serviceEnv...)
|
st.SetEnv(tt.serviceEnv...)
|
||||||
result := st.PatchEnv(tt.systemEnv)
|
result := st.PatchEnv(tt.systemEnv)
|
||||||
assert.ElementsMatch(t, result, tt.expected)
|
assert.ElementsMatch(t, result, tt.expected)
|
||||||
})
|
})
|
||||||
t.Run(tt.name+" - cron", func(t *testing.T) {
|
|
||||||
|
t.Run("cron", func(t *testing.T) {
|
||||||
st := NewCronTask(tt.name)
|
st := NewCronTask(tt.name)
|
||||||
st.SetEnv(tt.serviceEnv...)
|
st.SetEnv(tt.serviceEnv...)
|
||||||
result := st.PatchEnv(tt.systemEnv)
|
result := st.PatchEnv(tt.systemEnv)
|
||||||
assert.ElementsMatch(t, result, tt.expected)
|
assert.ElementsMatch(t, result, tt.expected)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user