preparation for v0.2.0 #3
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Golang Dev",
|
||||
"image": "golang-dev:1.21-bookworm-user",
|
||||
"image": "golang-dev:1.22-bookworm-user",
|
||||
"mounts": [
|
||||
{
|
||||
"source": "WingmateGoPath",
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,6 @@
|
||||
/cmd/wingmate/wingmate
|
||||
/cmd/wingmate/version.txt
|
||||
/cmd/pidproxy/pidproxy
|
||||
/cmd/exec/exec
|
||||
/cmd/pidproxy/version.txt
|
||||
/cmd/exec/exec
|
||||
/cmd/exec/version.txt
|
||||
55
.idea/remote-targets.xml
generated
Normal file
55
.idea/remote-targets.xml
generated
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteTargetsManager">
|
||||
<targets>
|
||||
<target name="golang-dev:1.21-bookworm-user" type="docker" uuid="5b79636b-3db9-4e2a-9fd3-03277653ae58">
|
||||
<config>
|
||||
<option name="targetPlatform">
|
||||
<TargetPlatform />
|
||||
</option>
|
||||
<option name="buildImageConfig">
|
||||
<BuildImageConfig>
|
||||
<option name="dockerFile" value="docker/bookworm/Dockerfile" />
|
||||
</BuildImageConfig>
|
||||
</option>
|
||||
<option name="buildNotPull" value="false" />
|
||||
<option name="containerConfig">
|
||||
<ContainerConfig>
|
||||
<option name="runCliOptions" value="-u "1000:1000" --rm" />
|
||||
</ContainerConfig>
|
||||
</option>
|
||||
<option name="pullImageConfig">
|
||||
<PullImageConfig>
|
||||
<option name="tagToPull" value="golang-dev:1.21-bookworm-user" />
|
||||
</PullImageConfig>
|
||||
</option>
|
||||
</config>
|
||||
<ContributedStateBase type="GoLanguageRuntime">
|
||||
<config>
|
||||
<option name="compiledExecutablesVolume">
|
||||
<VolumeState>
|
||||
<option name="targetSpecificBits">
|
||||
<map>
|
||||
<entry key="mountAsVolume" value="false" />
|
||||
</map>
|
||||
</option>
|
||||
</VolumeState>
|
||||
</option>
|
||||
<option name="goPath" value="/go" />
|
||||
<option name="goRoot" value="/usr/local/go/bin/go" />
|
||||
<option name="goVersion" value="go1.21.5 linux/amd64" />
|
||||
<option name="projectSourcesVolume">
|
||||
<VolumeState>
|
||||
<option name="targetSpecificBits">
|
||||
<map>
|
||||
<entry key="mountAsVolume" value="false" />
|
||||
</map>
|
||||
</option>
|
||||
</VolumeState>
|
||||
</option>
|
||||
</config>
|
||||
</ContributedStateBase>
|
||||
</target>
|
||||
</targets>
|
||||
</component>
|
||||
</project>
|
||||
@ -1 +0,0 @@
|
||||
golang 1.21.5
|
||||
7
Makefile
7
Makefile
@ -3,7 +3,7 @@ DESTDIR = /usr/local/bin
|
||||
installs = install-dir
|
||||
programs = wingmate pidproxy exec
|
||||
ifdef TEST_BUILD
|
||||
programs += oneshot spawner starter dummy
|
||||
programs += oneshot spawner starter dummy bg
|
||||
installs += install-test
|
||||
endif
|
||||
|
||||
@ -30,6 +30,9 @@ spawner:
|
||||
starter:
|
||||
$(MAKE) -C cmd/experiment/starter all
|
||||
|
||||
bg:
|
||||
$(MAKE) -C cmd/experiment/bg all
|
||||
|
||||
clean:
|
||||
$(MAKE) -C cmd/wingmate clean
|
||||
$(MAKE) -C cmd/pidproxy clean
|
||||
@ -38,6 +41,7 @@ clean:
|
||||
$(MAKE) -C cmd/experiment/oneshot clean
|
||||
$(MAKE) -C cmd/experiment/spawner clean
|
||||
$(MAKE) -C cmd/experiment/starter clean
|
||||
$(MAKE) -C cmd/experiment/bg clean
|
||||
|
||||
install: ${installs}
|
||||
$(MAKE) -C cmd/wingmate DESTDIR=${DESTDIR} install
|
||||
@ -49,6 +53,7 @@ install-test:
|
||||
$(MAKE) -C cmd/experiment/oneshot DESTDIR=${DESTDIR} install
|
||||
$(MAKE) -C cmd/experiment/spawner DESTDIR=${DESTDIR} install
|
||||
$(MAKE) -C cmd/experiment/starter DESTDIR=${DESTDIR} install
|
||||
$(MAKE) -C cmd/experiment/bg DESTDIR=${DESTDIR} install
|
||||
|
||||
install-dir:
|
||||
install -d ${DESTDIR}
|
||||
|
||||
32
README.md
32
README.md
@ -50,17 +50,32 @@ You can find some examples for shell script in [alpine docker](docker/alpine) an
|
||||
|
||||
When `wingmate` binary starts, it will look for some files. By default, it will
|
||||
try to read the content of `/etc/wingmate` directory. You can change the directory
|
||||
where it reads by setting `WINGMATE_CONFIG_PATH` environment variable. The structure
|
||||
inside the config path should look like this.
|
||||
where it reads by setting `WINGMATE_CONFIG_PATH` environment variable. Wingmate supports
|
||||
two format of configurations: yaml and shell script.
|
||||
|
||||
### YAML configuration
|
||||
|
||||
File structure:
|
||||
```shell
|
||||
/etc
|
||||
└── wingmate
|
||||
└── wingmate.yaml
|
||||
```
|
||||
Wingmate will parse the `wingmate.yaml` file and start services and crones based on the content
|
||||
of the yaml file. Please read [wingmate.yaml.md](wingmate.yaml.md) for details on
|
||||
the structure of yaml configuration file and some examples.
|
||||
|
||||
### Shell script configuration
|
||||
|
||||
Files and directories structure:
|
||||
```shell
|
||||
/etc
|
||||
└── wingmate
|
||||
├── crontab
|
||||
├── crontab.d
|
||||
│ ├── cron1.sh
|
||||
│ ├── cron2.sh
|
||||
│ └── cron3.sh
|
||||
│ ├── cron1.sh
|
||||
│ ├── cron2.sh
|
||||
│ └── cron3.sh
|
||||
└── service
|
||||
├── one.sh
|
||||
└── spawner.sh
|
||||
@ -85,10 +100,13 @@ common UNIX crontab file format. Something like this
|
||||
* * * * * <commad or shell script or binary>
|
||||
```
|
||||
|
||||
The command part only support simple command and arguments. Shell expression is not supported
|
||||
yet. It is recommended to write a shell script and put the path to shell script in
|
||||
The command part only support simple command and arguments. Shell expression is not supported.
|
||||
It is recommended to write a shell script and put the path to shell script in
|
||||
the command part.
|
||||
|
||||
**Note: It is recommended to use the yaml format instead of shell script. In order to avoid less
|
||||
obvious mistake when writing shell script.**
|
||||
|
||||
# Appendix
|
||||
## Wingmate PID Proxy binary
|
||||
|
||||
|
||||
37
cmd/cli/splitargs.go
Normal file
37
cmd/cli/splitargs.go
Normal file
@ -0,0 +1,37 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
func SplitArgs(args []string) ([]string, []string, error) {
|
||||
var (
|
||||
i int
|
||||
arg string
|
||||
selfArgs []string
|
||||
childArgs []string
|
||||
)
|
||||
found := false
|
||||
for i, arg = range args {
|
||||
if arg == "--" {
|
||||
found = true
|
||||
if i+1 == len(args) {
|
||||
return nil, nil, errors.New("invalid argument")
|
||||
}
|
||||
|
||||
if len(args[i+1:]) == 0 {
|
||||
return nil, nil, errors.New("invalid argument")
|
||||
}
|
||||
|
||||
selfArgs = args[:i]
|
||||
childArgs = args[i+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil, nil, errors.New("invalid argument")
|
||||
}
|
||||
return selfArgs, childArgs, nil
|
||||
|
||||
}
|
||||
7
cmd/cli/splitargs_test.go
Normal file
7
cmd/cli/splitargs_test.go
Normal file
@ -0,0 +1,7 @@
|
||||
package cli
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSplitArgs(t *testing.T) {
|
||||
SplitArgs([]string{"wmexec", "--user", "1200", "--", "wmspawner"})
|
||||
}
|
||||
45
cmd/cli/version.go
Normal file
45
cmd/cli/version.go
Normal file
@ -0,0 +1,45 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Version string
|
||||
|
||||
const versionFlag = "version"
|
||||
|
||||
func (v Version) Print() {
|
||||
fmt.Print(v)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func (v Version) Cmd(cmd *cobra.Command) {
|
||||
cmd.AddCommand(&cobra.Command{
|
||||
Use: "version",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v.Print()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (v Version) Flag(cmd *cobra.Command) {
|
||||
v.FlagSet(cmd.PersistentFlags())
|
||||
}
|
||||
|
||||
func (v Version) FlagSet(fs *pflag.FlagSet) {
|
||||
viper.SetDefault(versionFlag, false)
|
||||
fs.Bool(versionFlag, false, "print version")
|
||||
_ = viper.BindPFlag(versionFlag, fs.Lookup(versionFlag))
|
||||
}
|
||||
|
||||
func (v Version) FlagHook() {
|
||||
if viper.GetBool(versionFlag) {
|
||||
v.Print()
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
all:
|
||||
git describe > version.txt
|
||||
go build -v
|
||||
|
||||
clean:
|
||||
echo "dev" > version.txt
|
||||
go clean -i -cache -testcache
|
||||
|
||||
install:
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
@ -10,11 +11,18 @@ import (
|
||||
"strings"
|
||||
|
||||
"gitea.suyono.dev/suyono/wingmate"
|
||||
"gitea.suyono.dev/suyono/wingmate/cmd/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type execApp struct {
|
||||
childArgs []string
|
||||
err error
|
||||
version cli.Version
|
||||
}
|
||||
|
||||
const (
|
||||
setsidFlag = "setsid"
|
||||
EnvSetsid = "SETSID"
|
||||
@ -23,65 +31,66 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "wmexec",
|
||||
RunE: execCmd,
|
||||
}
|
||||
|
||||
childArgs []string
|
||||
//go:embed version.txt
|
||||
version string
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
found bool
|
||||
i int
|
||||
arg string
|
||||
selfArgs []string
|
||||
selfArgs []string
|
||||
childArgs []string
|
||||
app *execApp
|
||||
rootCmd *cobra.Command
|
||||
err error
|
||||
)
|
||||
|
||||
app = &execApp{
|
||||
version: cli.Version(version),
|
||||
}
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "wmexec",
|
||||
SilenceUsage: true,
|
||||
RunE: app.execCmd,
|
||||
}
|
||||
|
||||
rootCmd.PersistentFlags().BoolP(setsidFlag, "s", false, "set to true to run setsid() before exec")
|
||||
viper.BindPFlag(EnvSetsid, rootCmd.PersistentFlags().Lookup(setsidFlag))
|
||||
_ = viper.BindPFlag(EnvSetsid, rootCmd.PersistentFlags().Lookup(setsidFlag))
|
||||
|
||||
rootCmd.PersistentFlags().StringP(userFlag, "u", "", "\"user:[group]\"")
|
||||
viper.BindPFlag(EnvUser, rootCmd.PersistentFlags().Lookup(userFlag))
|
||||
_ = viper.BindPFlag(EnvUser, rootCmd.PersistentFlags().Lookup(userFlag))
|
||||
|
||||
app.version.Flag(rootCmd)
|
||||
|
||||
viper.SetEnvPrefix(wingmate.EnvPrefix)
|
||||
viper.BindEnv(EnvUser)
|
||||
viper.BindEnv(EnvSetsid)
|
||||
_ = viper.BindEnv(EnvUser)
|
||||
_ = viper.BindEnv(EnvSetsid)
|
||||
viper.SetDefault(EnvSetsid, false)
|
||||
viper.SetDefault(EnvUser, "")
|
||||
|
||||
found = false
|
||||
for i, arg = range os.Args {
|
||||
if arg == "--" {
|
||||
found = true
|
||||
if len(os.Args) <= i+1 {
|
||||
log.Println("invalid argument")
|
||||
os.Exit(1)
|
||||
}
|
||||
selfArgs = os.Args[1:i]
|
||||
childArgs = os.Args[i+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
log.Println("invalid argument")
|
||||
os.Exit(1)
|
||||
}
|
||||
app.version.Cmd(rootCmd)
|
||||
|
||||
if len(childArgs) == 0 {
|
||||
log.Println("invalid argument")
|
||||
os.Exit(1)
|
||||
if selfArgs, childArgs, err = cli.SplitArgs(os.Args); err != nil {
|
||||
selfArgs = os.Args
|
||||
}
|
||||
app.childArgs = childArgs
|
||||
app.err = err
|
||||
|
||||
rootCmd.SetArgs(selfArgs)
|
||||
rootCmd.SetArgs(selfArgs[1:])
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func execCmd(cmd *cobra.Command, args []string) error {
|
||||
func (e *execApp) execCmd(_ *cobra.Command, _ []string) error {
|
||||
e.version.FlagHook()
|
||||
|
||||
if e.err != nil {
|
||||
return e.err
|
||||
}
|
||||
|
||||
if viper.GetBool(EnvSetsid) {
|
||||
_, _ = unix.Setsid()
|
||||
}
|
||||
@ -119,13 +128,13 @@ func execCmd(cmd *cobra.Command, args []string) error {
|
||||
|
||||
}
|
||||
|
||||
if path, err = exec.LookPath(childArgs[0]); err != nil {
|
||||
if path, err = exec.LookPath(e.childArgs[0]); err != nil {
|
||||
if !errors.Is(err, exec.ErrDot) {
|
||||
return fmt.Errorf("lookpath: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = unix.Exec(path, childArgs, os.Environ()); err != nil {
|
||||
if err = unix.Exec(path, e.childArgs, os.Environ()); err != nil {
|
||||
return fmt.Errorf("exec: %w", err)
|
||||
}
|
||||
|
||||
|
||||
8
cmd/experiment/.gitignore
vendored
8
cmd/experiment/.gitignore
vendored
@ -1,4 +1,10 @@
|
||||
/dummy/dummy
|
||||
/dummy/version.txt
|
||||
/starter/starter
|
||||
/starter/version.txt
|
||||
/oneshot/oneshot
|
||||
/spawner/spawner
|
||||
/oneshot/version.txt
|
||||
/spawner/spawner
|
||||
/spawner/version.txt
|
||||
/bg/bg
|
||||
/bg/version.txt
|
||||
|
||||
10
cmd/experiment/bg/Makefile
Normal file
10
cmd/experiment/bg/Makefile
Normal file
@ -0,0 +1,10 @@
|
||||
all:
|
||||
git describe > version.txt
|
||||
go build -v
|
||||
|
||||
clean:
|
||||
echo "dev" > version.txt
|
||||
go clean -i -cache -testcache
|
||||
|
||||
install:
|
||||
install bg ${DESTDIR}/wmbg
|
||||
67
cmd/experiment/bg/background.go
Normal file
67
cmd/experiment/bg/background.go
Normal file
@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
logPathFlag = "log-path"
|
||||
pidFileFlag = "pid-file"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
logPath string
|
||||
pidFilePath string
|
||||
name string
|
||||
pause uint
|
||||
lf *os.File
|
||||
err error
|
||||
)
|
||||
|
||||
pflag.StringVarP(&logPath, logPathFlag, "l", "/var/log/wmbg.log", "log file path")
|
||||
pflag.StringVarP(&pidFilePath, pidFileFlag, "p", "/var/run/wmbg.pid", "pid file path")
|
||||
pflag.StringVar(&name, "name", "no-name", "process name")
|
||||
pflag.UintVar(&pause, "pause", 5, "pause interval")
|
||||
pflag.Parse()
|
||||
|
||||
if lf, err = os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644); err != nil {
|
||||
os.Exit(2)
|
||||
}
|
||||
defer func() {
|
||||
_ = lf.Close()
|
||||
}()
|
||||
|
||||
log.SetOutput(lf)
|
||||
log.Printf("starting process %s with pause interval %d", name, pause)
|
||||
if err = writePid(pidFilePath); err != nil {
|
||||
log.Printf("failed to write pid file: %+v", err)
|
||||
}
|
||||
time.Sleep(time.Duration(pause) * time.Second)
|
||||
log.Printf("process %s finished", name)
|
||||
}
|
||||
|
||||
func writePid(path string) error {
|
||||
var (
|
||||
err error
|
||||
pf *os.File
|
||||
)
|
||||
|
||||
if pf, err = os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644); err != nil {
|
||||
return fmt.Errorf("opening pid file for write: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = pf.Close()
|
||||
}()
|
||||
|
||||
if _, err = fmt.Fprintf(pf, "%d", unix.Getpid()); err != nil {
|
||||
return fmt.Errorf("writing pid to the pid file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
all:
|
||||
git describe > version.txt
|
||||
go build -v
|
||||
|
||||
clean:
|
||||
echo "dev" > version.txt
|
||||
go clean -i -cache -testcache
|
||||
|
||||
install:
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
all:
|
||||
git describe > version.txt
|
||||
go build -v
|
||||
|
||||
clean:
|
||||
echo "dev" > version.txt
|
||||
go clean -i -cache -testcache
|
||||
|
||||
install:
|
||||
|
||||
@ -7,38 +7,37 @@ import (
|
||||
"os/exec"
|
||||
|
||||
"gitea.suyono.dev/suyono/wingmate"
|
||||
"gitea.suyono.dev/suyono/wingmate/cmd/cli"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
// DummyPath = "/workspaces/wingmate/cmd/experiment/dummy/dummy"
|
||||
DummyPath = "/usr/local/bin/wmdummy"
|
||||
EnvDummyPath = "DUMMY_PATH"
|
||||
EnvLog = "LOG"
|
||||
EnvLogMessage = "LOG_MESSAGE"
|
||||
EnvDefaultLogMessage = "oneshot executed"
|
||||
EnvInstanceNum = "INSTANCE_NUM"
|
||||
EnvDefaultInstances = -1
|
||||
EnvDefaultInstances = 0
|
||||
)
|
||||
|
||||
func main() {
|
||||
viper.SetEnvPrefix(wingmate.EnvPrefix)
|
||||
viper.BindEnv(EnvDummyPath)
|
||||
viper.BindEnv(EnvLog)
|
||||
viper.BindEnv(EnvLogMessage)
|
||||
viper.BindEnv(EnvInstanceNum)
|
||||
viper.SetDefault(EnvDummyPath, DummyPath)
|
||||
viper.SetDefault(EnvLogMessage, EnvDefaultLogMessage)
|
||||
viper.SetDefault(EnvInstanceNum, EnvDefaultInstances)
|
||||
|
||||
exePath := viper.GetString(EnvDummyPath)
|
||||
_, childArgs, err := cli.SplitArgs(os.Args)
|
||||
if err != nil {
|
||||
log.Printf("splitargs: %+v", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
logPath := viper.GetString(EnvLog)
|
||||
logMessage := viper.GetString(EnvLogMessage)
|
||||
log.Println("log path:", logPath)
|
||||
if logPath != "" {
|
||||
var (
|
||||
err error
|
||||
file *os.File
|
||||
)
|
||||
|
||||
@ -53,10 +52,12 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
StartInstances(exePath)
|
||||
if len(childArgs) > 0 {
|
||||
StartInstances(childArgs[0], childArgs[1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
func StartInstances(exePath string) {
|
||||
func StartInstances(exePath string, args ...string) {
|
||||
num := (rand.Uint32() % 16) + 16
|
||||
|
||||
iNum := viper.GetInt(EnvInstanceNum)
|
||||
@ -70,7 +71,7 @@ func StartInstances(exePath string) {
|
||||
err error
|
||||
)
|
||||
for ctr = 0; ctr < num; ctr++ {
|
||||
cmd = exec.Command(exePath)
|
||||
cmd = exec.Command(exePath, args...)
|
||||
if err = cmd.Start(); err != nil {
|
||||
log.Printf("failed to run %s: %+v\n", exePath, err)
|
||||
}
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
all:
|
||||
git describe > version.txt
|
||||
go build -v
|
||||
|
||||
clean:
|
||||
echo "dev" > version.txt
|
||||
go clean -i -cache -testcache
|
||||
|
||||
install:
|
||||
|
||||
@ -28,7 +28,7 @@ func main() {
|
||||
|
||||
t = time.NewTicker(time.Second * 5)
|
||||
for {
|
||||
cmd = exec.Command(exePath)
|
||||
cmd = exec.Command(exePath, "--", "wmdummy")
|
||||
if err = cmd.Run(); err != nil {
|
||||
log.Printf("failed to run %s: %+v\n", exePath, err)
|
||||
} else {
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
all:
|
||||
git describe > version.txt
|
||||
go build -v
|
||||
|
||||
clean:
|
||||
echo "dev" > version.txt
|
||||
go clean -i -cache -testcache
|
||||
|
||||
install:
|
||||
|
||||
@ -4,55 +4,92 @@ import (
|
||||
"bufio"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"gitea.suyono.dev/suyono/wingmate"
|
||||
"gitea.suyono.dev/suyono/wingmate/cmd/cli"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
// DummyPath = "/workspaces/wingmate/cmd/experiment/dummy/dummy"
|
||||
DummyPath = "/usr/local/bin/wmdummy"
|
||||
EnvDummyPath = "DUMMY_PATH"
|
||||
NoWaitFlag = "no-wait"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
stdout io.ReadCloser
|
||||
stderr io.ReadCloser
|
||||
wg *sync.WaitGroup
|
||||
err error
|
||||
exePath string
|
||||
stdout io.ReadCloser
|
||||
stderr io.ReadCloser
|
||||
wg *sync.WaitGroup
|
||||
err error
|
||||
exePath string
|
||||
selfArgs []string
|
||||
childArgs []string
|
||||
flagSet *pflag.FlagSet
|
||||
noWait bool
|
||||
cmd *exec.Cmd
|
||||
)
|
||||
if selfArgs, childArgs, err = cli.SplitArgs(os.Args); err == nil {
|
||||
flagSet = pflag.NewFlagSet(selfArgs[0], pflag.ExitOnError)
|
||||
flagSet.Count(NoWaitFlag, "do not wait for the child process")
|
||||
if err = flagSet.Parse(selfArgs[1:]); err != nil {
|
||||
log.Printf("invalid argument: %+v", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
flagSet = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError)
|
||||
flagSet.Count(NoWaitFlag, "do not wait for the child process")
|
||||
if err = flagSet.Parse(os.Args[1:]); err != nil {
|
||||
log.Printf("invalid argument: %+v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
_ = viper.BindPFlag(NoWaitFlag, flagSet.Lookup(NoWaitFlag))
|
||||
if viper.GetInt(NoWaitFlag) > 0 {
|
||||
noWait = true
|
||||
}
|
||||
|
||||
viper.SetEnvPrefix(wingmate.EnvPrefix)
|
||||
viper.BindEnv(EnvDummyPath)
|
||||
_ = viper.BindEnv(EnvDummyPath)
|
||||
viper.SetDefault(EnvDummyPath, DummyPath)
|
||||
|
||||
exePath = viper.GetString(EnvDummyPath)
|
||||
|
||||
cmd := exec.Command(exePath)
|
||||
|
||||
if stdout, err = cmd.StdoutPipe(); err != nil {
|
||||
log.Panic(err)
|
||||
if len(childArgs) > 0 {
|
||||
cmd = exec.Command(childArgs[0], childArgs[1:]...)
|
||||
} else {
|
||||
cmd = exec.Command(exePath)
|
||||
}
|
||||
|
||||
if stderr, err = cmd.StderrPipe(); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
if !noWait {
|
||||
if stdout, err = cmd.StdoutPipe(); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
wg = &sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
go pulley(wg, stdout, "stdout")
|
||||
go pulley(wg, stderr, "stderr")
|
||||
if stderr, err = cmd.StderrPipe(); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
wg = &sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
go pulley(wg, stdout, "stdout")
|
||||
go pulley(wg, stderr, "stderr")
|
||||
}
|
||||
|
||||
if err = cmd.Start(); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if err = cmd.Wait(); err != nil {
|
||||
log.Printf("got error when Waiting for child process: %#v\n", err)
|
||||
if !noWait {
|
||||
wg.Wait()
|
||||
|
||||
if err = cmd.Wait(); err != nil {
|
||||
log.Printf("got error when Waiting for child process: %#v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
all:
|
||||
git describe > version.txt
|
||||
go build -v
|
||||
|
||||
clean:
|
||||
echo "dev" > version.txt
|
||||
go clean -i -cache -testcache
|
||||
|
||||
install:
|
||||
|
||||
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"gitea.suyono.dev/suyono/wingmate/cmd/cli"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
@ -15,8 +16,16 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
type pidProxyApp struct {
|
||||
childArgs []string
|
||||
err error
|
||||
version cli.Version
|
||||
}
|
||||
|
||||
const (
|
||||
pidFileFlag = "pid-file"
|
||||
EnvStartSecs = "STARTSECS"
|
||||
@ -24,68 +33,63 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "wmpidproxy",
|
||||
RunE: pidProxy,
|
||||
}
|
||||
|
||||
childArgs []string
|
||||
//go:embed version.txt
|
||||
version string
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
i int
|
||||
arg string
|
||||
selfArgs []string
|
||||
found bool
|
||||
selfArgs []string
|
||||
childArgs []string
|
||||
err error
|
||||
app *pidProxyApp
|
||||
rootCmd *cobra.Command
|
||||
)
|
||||
|
||||
app = &pidProxyApp{
|
||||
version: cli.Version(version),
|
||||
}
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "wmpidproxy",
|
||||
SilenceUsage: true,
|
||||
RunE: app.pidProxy,
|
||||
}
|
||||
|
||||
viper.SetEnvPrefix(wingmate.EnvPrefix)
|
||||
viper.BindEnv(EnvStartSecs)
|
||||
_ = viper.BindEnv(EnvStartSecs)
|
||||
viper.SetDefault(EnvStartSecs, EnvDefaultStartSecs)
|
||||
|
||||
rootCmd.PersistentFlags().StringP(pidFileFlag, "p", "", "location of pid file")
|
||||
rootCmd.MarkFlagRequired(pidFileFlag)
|
||||
viper.BindPFlag(pidFileFlag, rootCmd.PersistentFlags().Lookup(pidFileFlag))
|
||||
_ = rootCmd.MarkFlagRequired(pidFileFlag)
|
||||
_ = viper.BindPFlag(pidFileFlag, rootCmd.PersistentFlags().Lookup(pidFileFlag))
|
||||
|
||||
found = false
|
||||
for i, arg = range os.Args {
|
||||
if arg == "--" {
|
||||
found = true
|
||||
if len(os.Args) <= i+1 {
|
||||
log.Println("invalid argument")
|
||||
os.Exit(1)
|
||||
}
|
||||
selfArgs = os.Args[1:i]
|
||||
childArgs = os.Args[i+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
log.Println("invalid argument")
|
||||
os.Exit(1)
|
||||
app.version.Flag(rootCmd)
|
||||
app.version.Cmd(rootCmd)
|
||||
|
||||
if selfArgs, childArgs, err = cli.SplitArgs(os.Args); err != nil {
|
||||
selfArgs = os.Args
|
||||
}
|
||||
app.childArgs = childArgs
|
||||
app.err = err
|
||||
|
||||
if len(childArgs) == 0 {
|
||||
log.Println("invalid argument")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
rootCmd.SetArgs(selfArgs)
|
||||
|
||||
rootCmd.SetArgs(selfArgs[1:])
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func pidProxy(cmd *cobra.Command, args []string) error {
|
||||
func (p *pidProxyApp) pidProxy(cmd *cobra.Command, args []string) error {
|
||||
p.version.FlagHook()
|
||||
|
||||
pidfile := viper.GetString(pidFileFlag)
|
||||
log.Printf("%s %v", pidfile, childArgs)
|
||||
if len(childArgs) > 1 {
|
||||
go startProcess(childArgs[0], childArgs[1:]...)
|
||||
log.Printf("%s %v", pidfile, p.childArgs)
|
||||
if len(p.childArgs) > 1 {
|
||||
go p.startProcess(p.childArgs[0], p.childArgs[1:]...)
|
||||
} else {
|
||||
go startProcess(childArgs[0])
|
||||
go p.startProcess(p.childArgs[0])
|
||||
}
|
||||
initialWait := viper.GetInt(EnvStartSecs)
|
||||
time.Sleep(time.Second * time.Duration(initialWait))
|
||||
@ -104,18 +108,21 @@ func pidProxy(cmd *cobra.Command, args []string) error {
|
||||
|
||||
check:
|
||||
for {
|
||||
if pid, err = readPid(pidfile); err != nil {
|
||||
if pid, err = p.readPid(pidfile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = unix.Kill(pid, syscall.Signal(0)); err != nil {
|
||||
return err
|
||||
if !errors.Is(err, unix.ESRCH) {
|
||||
return err
|
||||
}
|
||||
break check
|
||||
}
|
||||
|
||||
select {
|
||||
case <-t.C:
|
||||
case <-sc:
|
||||
if pid, err = readPid(pidfile); err != nil {
|
||||
if pid, err = p.readPid(pidfile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -128,7 +135,7 @@ check:
|
||||
return nil
|
||||
}
|
||||
|
||||
func readPid(pidFile string) (int, error) {
|
||||
func (p *pidProxyApp) readPid(pidFile string) (int, error) {
|
||||
var (
|
||||
file *os.File
|
||||
err error
|
||||
@ -153,7 +160,7 @@ func readPid(pidFile string) (int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func startProcess(arg0 string, args ...string) {
|
||||
func (p *pidProxyApp) startProcess(arg0 string, args ...string) {
|
||||
if err := exec.Command(arg0, args...).Run(); err != nil {
|
||||
log.Println("exec:", err)
|
||||
return
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
all:
|
||||
git describe > version.txt
|
||||
go build -v
|
||||
|
||||
clean:
|
||||
echo "dev" > version.txt
|
||||
go clean -i -cache -testcache
|
||||
|
||||
install:
|
||||
|
||||
@ -1,60 +1,86 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
"sync"
|
||||
|
||||
"gitea.suyono.dev/suyono/wingmate/config"
|
||||
wminit "gitea.suyono.dev/suyono/wingmate/init"
|
||||
"gitea.suyono.dev/suyono/wingmate/task"
|
||||
)
|
||||
|
||||
type wPath struct {
|
||||
path string
|
||||
}
|
||||
|
||||
func (p wPath) Path() string {
|
||||
return p.path
|
||||
}
|
||||
|
||||
type wConfig struct {
|
||||
services []wminit.Path
|
||||
cron []wminit.Cron
|
||||
tasks *task.Tasks
|
||||
config *config.Config
|
||||
viperMtx *sync.Mutex
|
||||
}
|
||||
|
||||
func (c wConfig) Services() []wminit.Path {
|
||||
return c.services
|
||||
func (c *wConfig) Tasks() wminit.Tasks {
|
||||
return c.tasks
|
||||
}
|
||||
|
||||
func (c wConfig) Cron() []wminit.Cron {
|
||||
return c.cron
|
||||
func (c *wConfig) Reload() error {
|
||||
//NOTE: for future use when reloading is possible
|
||||
return nil
|
||||
}
|
||||
|
||||
type wCron struct {
|
||||
iCron *config.Cron
|
||||
}
|
||||
|
||||
func (c wCron) TimeToRun(now time.Time) bool {
|
||||
return c.iCron.TimeToRun(now)
|
||||
}
|
||||
|
||||
func (c wCron) Command() wminit.Path {
|
||||
return wPath{
|
||||
path: c.iCron.Command(),
|
||||
}
|
||||
}
|
||||
|
||||
func convert(cfg *config.Config) wConfig {
|
||||
retval := wConfig{
|
||||
services: make([]wminit.Path, 0, len(cfg.ServicePaths)),
|
||||
cron: make([]wminit.Cron, 0, len(cfg.Cron)),
|
||||
func convert(cfg *config.Config) *wConfig {
|
||||
retval := &wConfig{
|
||||
tasks: task.NewTasks(),
|
||||
config: cfg,
|
||||
viperMtx: &sync.Mutex{},
|
||||
}
|
||||
|
||||
for _, s := range cfg.ServicePaths {
|
||||
retval.services = append(retval.services, wPath{path: s})
|
||||
for _, s := range cfg.Service {
|
||||
st := task.NewServiceTask(s.Name).SetCommand(s.Command...).SetEnv(s.Environ...)
|
||||
st.SetFlagSetsid(s.Setsid).SetWorkingDir(s.WorkingDir)
|
||||
st.SetUser(s.User).SetGroup(s.Group).SetStartSecs(s.StartSecs).SetPidFile(s.PidFile)
|
||||
st.SetConfig(cfg)
|
||||
retval.tasks.AddService(st)
|
||||
}
|
||||
|
||||
for _, s := range cfg.ServiceV0 {
|
||||
retval.tasks.AddV0Service(s)
|
||||
}
|
||||
|
||||
var schedule task.CronSchedule
|
||||
for _, c := range cfg.CronV0 {
|
||||
schedule = configToTaskCronSchedule(c.CronSchedule)
|
||||
retval.tasks.AddV0Cron(schedule, c.Command)
|
||||
}
|
||||
|
||||
for _, c := range cfg.Cron {
|
||||
retval.cron = append(retval.cron, wCron{iCron: c})
|
||||
schedule = configToTaskCronSchedule(c.CronSchedule)
|
||||
|
||||
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(c.Schedule, schedule)
|
||||
ct.SetConfig(cfg)
|
||||
|
||||
retval.tasks.AddCron(ct)
|
||||
}
|
||||
|
||||
return retval
|
||||
}
|
||||
|
||||
func configToTaskCronSchedule(cfgSchedule config.CronSchedule) (taskSchedule task.CronSchedule) {
|
||||
taskSchedule.Minute = configToTaskCronTimeSpec(cfgSchedule.Minute)
|
||||
taskSchedule.Hour = configToTaskCronTimeSpec(cfgSchedule.Hour)
|
||||
taskSchedule.DoM = configToTaskCronTimeSpec(cfgSchedule.DoM)
|
||||
taskSchedule.Month = configToTaskCronTimeSpec(cfgSchedule.Month)
|
||||
taskSchedule.DoW = configToTaskCronTimeSpec(cfgSchedule.DoW)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func configToTaskCronTimeSpec(cfg config.CronTimeSpec) task.CronTimeSpec {
|
||||
switch v := cfg.(type) {
|
||||
case *config.SpecAny:
|
||||
return task.NewCronAnySpec()
|
||||
case *config.SpecExact:
|
||||
return task.NewCronExactSpec(v.Value())
|
||||
case *config.SpecMultiOccurrence:
|
||||
return task.NewCronMultiOccurrenceSpec(v.Values()...)
|
||||
}
|
||||
|
||||
panic("invalid conversion")
|
||||
}
|
||||
|
||||
@ -1,11 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"os"
|
||||
|
||||
"gitea.suyono.dev/suyono/wingmate"
|
||||
"gitea.suyono.dev/suyono/wingmate/config"
|
||||
wminit "gitea.suyono.dev/suyono/wingmate/init"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed version.txt
|
||||
version string
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -15,8 +22,13 @@ func main() {
|
||||
)
|
||||
|
||||
_ = wingmate.NewLog(os.Stderr)
|
||||
config.SetVersion(version)
|
||||
config.ParseFlags()
|
||||
|
||||
wingmate.Log().Info().Msgf("starting wingmate version %s", viper.GetString(config.WingmateVersion))
|
||||
|
||||
if cfg, err = config.Read(); err != nil {
|
||||
wingmate.Log().Error().Msgf("failed to read config %#v", err)
|
||||
wingmate.Log().Fatal().Err(err).Msg("failed to read config")
|
||||
}
|
||||
|
||||
initCfg := convert(cfg)
|
||||
|
||||
23
cmd/wingmate/wingmate_test.go
Normal file
23
cmd/wingmate/wingmate_test.go
Normal file
@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEntry_configPathEnv(t *testing.T) {
|
||||
_ = os.Setenv("WINGMATE_CONFIG_PATH", "/Volumes/Source/go/src/gitea.suyono.dev/suyono/wingmate/docker/bookworm/etc/wingmate")
|
||||
defer func() {
|
||||
_ = os.Unsetenv("WINGMATE_CONFIG_PATH")
|
||||
}()
|
||||
main()
|
||||
}
|
||||
|
||||
func TestEntry_configPathPFlag(t *testing.T) {
|
||||
os.Args = []string{"wingmate", "--config", "/workspaces/wingmate/docker/bookworm-newconfig/etc/wingmate"}
|
||||
main()
|
||||
}
|
||||
|
||||
func TestEntry(t *testing.T) {
|
||||
main()
|
||||
}
|
||||
185
config/config.go
185
config/config.go
@ -2,8 +2,11 @@ package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gitea.suyono.dev/suyono/wingmate"
|
||||
"github.com/spf13/viper"
|
||||
@ -11,69 +14,203 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
EnvPrefix = "WINGMATE"
|
||||
EnvConfigPath = "CONFIG_PATH"
|
||||
DefaultConfigPath = "/etc/wingmate"
|
||||
ServiceDirName = "service"
|
||||
CrontabFileName = "crontab"
|
||||
EnvPrefix = "WINGMATE"
|
||||
PathConfig = "config_path"
|
||||
DefaultConfigPath = "/etc/wingmate"
|
||||
ServiceDirName = "service"
|
||||
CrontabFileName = "crontab"
|
||||
WingmateConfigFileName = "wingmate"
|
||||
WingmateConfigFileFormat = "yaml"
|
||||
WingmateVersion = "APP_VERSION"
|
||||
PidProxyPathConfig = "pidproxy_path"
|
||||
PidProxyPathDefault = "wmpidproxy"
|
||||
ExecPathConfig = "exec_path"
|
||||
ExecPathDefault = "wmexec"
|
||||
versionTrimRightCutSet = "\r\n "
|
||||
WMPidProxyPathFlag = "pid-proxy"
|
||||
WMExecPathFlag = "exec"
|
||||
PathConfigFlag = "config"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ServicePaths []string
|
||||
Cron []*Cron
|
||||
ServiceV0 []string
|
||||
CronV0 []*Cron
|
||||
Service []ServiceTask
|
||||
Cron []CronTask
|
||||
viperMtx *sync.Mutex
|
||||
}
|
||||
|
||||
type Task struct {
|
||||
Command []string `mapstructure:"command"`
|
||||
Environ []string `mapstructure:"environ"`
|
||||
Setsid bool `mapstructure:"setsid"`
|
||||
User string `mapstructure:"user"`
|
||||
Group string `mapstructure:"group"`
|
||||
WorkingDir string `mapstructure:"working_dir"`
|
||||
}
|
||||
|
||||
type ServiceTask struct {
|
||||
Task `mapstructure:",squash"`
|
||||
Name string `mapstructure:"-"`
|
||||
Background bool `mapstructure:"background"`
|
||||
PidFile string `mapstructure:"pidfile"`
|
||||
StartSecs uint `mapstructure:"startsecs"`
|
||||
AutoStart bool `mapstructure:"autostart"`
|
||||
AutoRestart bool `mapstructure:"autorestart"`
|
||||
}
|
||||
|
||||
type CronTask struct {
|
||||
CronSchedule `mapstructure:"-"`
|
||||
Task `mapstructure:",squash"`
|
||||
Name string `mapstructure:"-"`
|
||||
Schedule string `mapstructure:"schedule"`
|
||||
}
|
||||
|
||||
type CronSchedule struct {
|
||||
Minute CronTimeSpec
|
||||
Hour CronTimeSpec
|
||||
DoM CronTimeSpec
|
||||
Month CronTimeSpec
|
||||
DoW CronTimeSpec
|
||||
}
|
||||
|
||||
func SetVersion(version string) {
|
||||
version = strings.TrimRight(version, versionTrimRightCutSet)
|
||||
viper.Set(WingmateVersion, version)
|
||||
}
|
||||
|
||||
func Read() (*Config, error) {
|
||||
viper.SetEnvPrefix(EnvPrefix)
|
||||
viper.BindEnv(EnvConfigPath)
|
||||
viper.SetDefault(EnvConfigPath, DefaultConfigPath)
|
||||
_ = viper.BindEnv(PathConfig)
|
||||
_ = viper.BindEnv(PidProxyPathConfig)
|
||||
_ = viper.BindEnv(ExecPathConfig)
|
||||
viper.SetDefault(PathConfig, DefaultConfigPath)
|
||||
viper.SetDefault(PidProxyPathConfig, PidProxyPathDefault)
|
||||
viper.SetDefault(ExecPathConfig, ExecPathDefault)
|
||||
|
||||
var (
|
||||
dirent []os.DirEntry
|
||||
err error
|
||||
svcdir string
|
||||
serviceAvailable bool
|
||||
cronAvailable bool
|
||||
cron []*Cron
|
||||
crontabfile string
|
||||
dirent []os.DirEntry
|
||||
err error
|
||||
svcdir string
|
||||
serviceAvailable bool
|
||||
cronAvailable bool
|
||||
wingmateConfigAvailable bool
|
||||
cron []*Cron
|
||||
crontabfile string
|
||||
services []ServiceTask
|
||||
crones []CronTask
|
||||
)
|
||||
|
||||
serviceAvailable = false
|
||||
cronAvailable = false
|
||||
outConfig := &Config{
|
||||
ServicePaths: make([]string, 0),
|
||||
viperMtx: &sync.Mutex{},
|
||||
ServiceV0: make([]string, 0),
|
||||
}
|
||||
configPath := viper.GetString(EnvConfigPath)
|
||||
configPath := viper.GetString(PathConfig)
|
||||
svcdir = filepath.Join(configPath, ServiceDirName)
|
||||
dirent, err = os.ReadDir(svcdir)
|
||||
if err != nil {
|
||||
wingmate.Log().Error().Msgf("encounter error when reading service directory %s: %+v", svcdir, err)
|
||||
}
|
||||
if len(dirent) > 0 {
|
||||
for _, d := range dirent {
|
||||
if d.Type().IsRegular() {
|
||||
svcPath := filepath.Join(svcdir, d.Name())
|
||||
if err = unix.Access(svcPath, unix.X_OK); err == nil {
|
||||
serviceAvailable = true
|
||||
outConfig.ServicePaths = append(outConfig.ServicePaths, svcPath)
|
||||
outConfig.ServiceV0 = append(outConfig.ServiceV0, svcPath)
|
||||
} else {
|
||||
wingmate.Log().Error().Msgf("checking executable access for %s: %+v", svcPath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
wingmate.Log().Error().Msgf("encounter error when reading service directory %s: %+v", svcdir, err)
|
||||
}
|
||||
|
||||
crontabfile = filepath.Join(configPath, CrontabFileName)
|
||||
cron, err = readCrontab(crontabfile)
|
||||
if len(cron) > 0 {
|
||||
outConfig.Cron = cron
|
||||
outConfig.CronV0 = cron
|
||||
cronAvailable = true
|
||||
}
|
||||
if err != nil {
|
||||
wingmate.Log().Error().Msgf("encounter error when reading crontab %s: %+v", crontabfile, err)
|
||||
}
|
||||
|
||||
if !serviceAvailable && !cronAvailable {
|
||||
wingmateConfigAvailable = false
|
||||
if services, crones, err = readConfigYaml(configPath, WingmateConfigFileName, WingmateConfigFileFormat); err != nil {
|
||||
wingmate.Log().Error().Msgf("encounter error when reading wingmate config file in %s/%s: %+v", configPath, WingmateConfigFileName, err)
|
||||
}
|
||||
if len(services) > 0 {
|
||||
outConfig.Service = services
|
||||
wingmateConfigAvailable = true
|
||||
}
|
||||
if len(crones) > 0 {
|
||||
outConfig.Cron = crones
|
||||
wingmateConfigAvailable = true
|
||||
}
|
||||
|
||||
if !serviceAvailable && !cronAvailable && !wingmateConfigAvailable {
|
||||
return nil, errors.New("no config found")
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -10,6 +10,28 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
serviceDir = "service"
|
||||
)
|
||||
|
||||
var (
|
||||
configDir string
|
||||
)
|
||||
|
||||
func setup(t *testing.T) {
|
||||
var err error
|
||||
if configDir, err = os.MkdirTemp("", "wingmate-*-test"); err != nil {
|
||||
t.Fatal("setup", err)
|
||||
}
|
||||
viper.Set(PathConfig, configDir)
|
||||
}
|
||||
|
||||
func tear(t *testing.T) {
|
||||
if err := os.RemoveAll(configDir); err != nil {
|
||||
t.Fatal("tear", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
|
||||
type testEntry struct {
|
||||
@ -17,26 +39,6 @@ func TestRead(t *testing.T) {
|
||||
testFunc func(t *testing.T)
|
||||
}
|
||||
|
||||
var (
|
||||
configDir string
|
||||
err error
|
||||
)
|
||||
|
||||
const serviceDir = "service"
|
||||
|
||||
setup := func(t *testing.T) {
|
||||
if configDir, err = os.MkdirTemp("", "wingmate-*-test"); err != nil {
|
||||
t.Fatal("setup", err)
|
||||
}
|
||||
viper.Set(EnvConfigPath, configDir)
|
||||
}
|
||||
|
||||
tear := func(t *testing.T) {
|
||||
if err = os.RemoveAll(configDir); err != nil {
|
||||
t.Fatal("tear", err)
|
||||
}
|
||||
}
|
||||
|
||||
mkSvcDir := func(t *testing.T) {
|
||||
if err := os.MkdirAll(path.Join(configDir, serviceDir), 0755); err != nil {
|
||||
t.Fatal("create dir", err)
|
||||
@ -64,7 +66,7 @@ func TestRead(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(
|
||||
t,
|
||||
cfg.ServicePaths,
|
||||
cfg.ServiceV0,
|
||||
[]string{
|
||||
path.Join(configDir, serviceDir, "one.sh"),
|
||||
path.Join(configDir, serviceDir, "two.sh"),
|
||||
@ -84,7 +86,7 @@ func TestRead(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(
|
||||
t,
|
||||
cfg.ServicePaths,
|
||||
cfg.ServiceV0,
|
||||
[]string{
|
||||
path.Join(configDir, serviceDir, "two.sh"),
|
||||
},
|
||||
@ -102,7 +104,7 @@ func TestRead(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(
|
||||
t,
|
||||
cfg.ServicePaths,
|
||||
cfg.ServiceV0,
|
||||
[]string{
|
||||
path.Join(configDir, serviceDir, "one.sh"),
|
||||
},
|
||||
|
||||
@ -4,46 +4,30 @@ import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitea.suyono.dev/suyono/wingmate"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.suyono.dev/suyono/wingmate"
|
||||
)
|
||||
|
||||
type CronExactSpec interface {
|
||||
CronTimeSpec
|
||||
Value() uint8
|
||||
}
|
||||
|
||||
type CronMultipleOccurrenceSpec interface {
|
||||
CronTimeSpec
|
||||
Values() []uint8
|
||||
}
|
||||
|
||||
type CronTimeSpec interface {
|
||||
Type() wingmate.CronTimeType
|
||||
Match(uint8) bool
|
||||
//Type() wingmate.CronTimeType
|
||||
//Match(uint8) bool
|
||||
}
|
||||
|
||||
type Cron struct {
|
||||
minute CronTimeSpec
|
||||
hour CronTimeSpec
|
||||
dom CronTimeSpec
|
||||
month CronTimeSpec
|
||||
dow CronTimeSpec
|
||||
command string
|
||||
lastRun time.Time
|
||||
hasRun bool
|
||||
CronSchedule
|
||||
Command string
|
||||
}
|
||||
|
||||
type cronField int
|
||||
|
||||
const (
|
||||
CrontabEntryRegex = `^\s*(?P<minute>\S+)\s+(?P<hour>\S+)\s+(?P<dom>\S+)\s+(?P<month>\S+)\s+(?P<dow>\S+)\s+(?P<command>\S.*\S)\s*$`
|
||||
CrontabSubmatchLen = 7
|
||||
CrontabEntryRegexPattern = `^\s*(?P<minute>\S+)\s+(?P<hour>\S+)\s+(?P<dom>\S+)\s+(?P<month>\S+)\s+(?P<dow>\S+)\s+(?P<command>\S.*\S)\s*$`
|
||||
CrontabCommentLineRegexPattern = `^\s*#.*$`
|
||||
CrontabCommentSuffixRegexPattern = `^\s*([^#]+)#.*$`
|
||||
CrontabSubMatchLen = 7
|
||||
|
||||
minute cronField = iota
|
||||
hour
|
||||
@ -52,21 +36,22 @@ const (
|
||||
dow
|
||||
)
|
||||
|
||||
var (
|
||||
crontabEntryRegex = regexp.MustCompile(CrontabEntryRegexPattern)
|
||||
crontabCommentLineRegex = regexp.MustCompile(CrontabCommentLineRegexPattern)
|
||||
crontabCommentSuffixRegex = regexp.MustCompile(CrontabCommentSuffixRegexPattern)
|
||||
)
|
||||
|
||||
func readCrontab(path string) ([]*Cron, error) {
|
||||
var (
|
||||
file *os.File
|
||||
err error
|
||||
scanner *bufio.Scanner
|
||||
line string
|
||||
re *regexp.Regexp
|
||||
parts []string
|
||||
retval []*Cron
|
||||
)
|
||||
|
||||
if re, err = regexp.Compile(CrontabEntryRegex); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if file, err = os.Open(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -79,41 +64,48 @@ func readCrontab(path string) ([]*Cron, error) {
|
||||
for scanner.Scan() {
|
||||
line = scanner.Text()
|
||||
|
||||
parts = re.FindStringSubmatch(line)
|
||||
if len(parts) != CrontabSubmatchLen {
|
||||
if crontabCommentLineRegex.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
|
||||
parts = crontabCommentSuffixRegex.FindStringSubmatch(line)
|
||||
if len(parts) == 2 {
|
||||
line = parts[1]
|
||||
}
|
||||
|
||||
parts = crontabEntryRegex.FindStringSubmatch(line)
|
||||
if len(parts) != CrontabSubMatchLen {
|
||||
wingmate.Log().Error().Msgf("invalid entry %s", line)
|
||||
continue
|
||||
}
|
||||
|
||||
c := &Cron{
|
||||
hasRun: false,
|
||||
}
|
||||
c := &Cron{}
|
||||
if err = c.setField(minute, parts[1]); err != nil {
|
||||
wingmate.Log().Error().Msgf("error parsing minute field %+v", err)
|
||||
wingmate.Log().Error().Msgf("error parsing Minute field %+v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err = c.setField(hour, parts[2]); err != nil {
|
||||
wingmate.Log().Error().Msgf("error parsing hour field %+v", err)
|
||||
wingmate.Log().Error().Msgf("error parsing Hour field %+v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err = c.setField(dom, parts[3]); err != nil {
|
||||
wingmate.Log().Error().Msgf("error parsing day of month field %+v", err)
|
||||
wingmate.Log().Error().Msgf("error parsing Day of Month field %+v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err = c.setField(month, parts[4]); err != nil {
|
||||
wingmate.Log().Error().Msgf("error parsing month field %+v", err)
|
||||
wingmate.Log().Error().Msgf("error parsing Month field %+v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err = c.setField(dow, parts[5]); err != nil {
|
||||
wingmate.Log().Error().Msgf("error parsing day of week field %+v", err)
|
||||
wingmate.Log().Error().Msgf("error parsing Day of Week field %+v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
c.command = parts[6]
|
||||
c.Command = parts[6]
|
||||
|
||||
retval = append(retval, c)
|
||||
}
|
||||
@ -121,35 +113,6 @@ func readCrontab(path string) ([]*Cron, error) {
|
||||
return retval, nil
|
||||
}
|
||||
|
||||
func (c *Cron) Command() string {
|
||||
return c.command
|
||||
}
|
||||
|
||||
func (c *Cron) TimeToRun(now time.Time) bool {
|
||||
if c.minute.Match(uint8(now.Minute())) &&
|
||||
c.hour.Match(uint8(now.Hour())) &&
|
||||
c.dom.Match(uint8(now.Day())) &&
|
||||
c.month.Match(uint8(now.Month())) &&
|
||||
c.dow.Match(uint8(now.Weekday())) {
|
||||
|
||||
if c.hasRun {
|
||||
if now.Sub(c.lastRun) <= time.Minute && now.Minute() == c.lastRun.Minute() {
|
||||
return false
|
||||
} else {
|
||||
c.lastRun = now
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
|
||||
c.lastRun = now
|
||||
c.hasRun = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type fieldRange struct {
|
||||
min int
|
||||
max int
|
||||
@ -182,25 +145,25 @@ func (c *Cron) setField(field cronField, input string) error {
|
||||
switch field {
|
||||
case minute:
|
||||
fr = newRange(0, 59)
|
||||
cField = &c.minute
|
||||
cField = &c.Minute
|
||||
case hour:
|
||||
fr = newRange(0, 23)
|
||||
cField = &c.hour
|
||||
cField = &c.Hour
|
||||
case dom:
|
||||
fr = newRange(1, 31)
|
||||
cField = &c.dom
|
||||
cField = &c.DoM
|
||||
case month:
|
||||
fr = newRange(1, 12)
|
||||
cField = &c.month
|
||||
cField = &c.Month
|
||||
case dow:
|
||||
fr = newRange(0, 6)
|
||||
cField = &c.dow
|
||||
cField = &c.DoW
|
||||
default:
|
||||
return errors.New("invalid cron field descriptor")
|
||||
}
|
||||
|
||||
if input == "*" {
|
||||
*cField = &specAny{}
|
||||
*cField = &SpecAny{}
|
||||
} else if strings.HasPrefix(input, "*/") {
|
||||
if parsed64, err = strconv.ParseUint(input[2:], 10, 8); err != nil {
|
||||
return fmt.Errorf("error parse field %+v with input %s: %w", field, input, err)
|
||||
@ -217,7 +180,7 @@ func (c *Cron) setField(field cronField, input string) error {
|
||||
current += parsed
|
||||
}
|
||||
|
||||
*cField = &specMultiOccurrence{
|
||||
*cField = &SpecMultiOccurrence{
|
||||
values: multi,
|
||||
}
|
||||
} else {
|
||||
@ -237,7 +200,7 @@ func (c *Cron) setField(field cronField, input string) error {
|
||||
multi = append(multi, parsed)
|
||||
}
|
||||
|
||||
*cField = &specMultiOccurrence{
|
||||
*cField = &SpecMultiOccurrence{
|
||||
values: multi,
|
||||
}
|
||||
} else {
|
||||
@ -250,7 +213,7 @@ func (c *Cron) setField(field cronField, input string) error {
|
||||
return fmt.Errorf("error parse field %+v with input %s: invalid value", field, input)
|
||||
}
|
||||
|
||||
*cField = &specExact{
|
||||
*cField = &SpecExact{
|
||||
value: parsed,
|
||||
}
|
||||
}
|
||||
@ -259,51 +222,21 @@ func (c *Cron) setField(field cronField, input string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type specAny struct{}
|
||||
type SpecAny struct{}
|
||||
|
||||
func (a *specAny) Type() wingmate.CronTimeType {
|
||||
return wingmate.Any
|
||||
}
|
||||
|
||||
func (a *specAny) Match(u uint8) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type specExact struct {
|
||||
type SpecExact struct {
|
||||
value uint8
|
||||
}
|
||||
|
||||
func (e *specExact) Type() wingmate.CronTimeType {
|
||||
return wingmate.Exact
|
||||
}
|
||||
|
||||
func (e *specExact) Match(u uint8) bool {
|
||||
return u == e.value
|
||||
}
|
||||
|
||||
func (e *specExact) Value() uint8 {
|
||||
func (e *SpecExact) Value() uint8 {
|
||||
return e.value
|
||||
}
|
||||
|
||||
type specMultiOccurrence struct {
|
||||
type SpecMultiOccurrence struct {
|
||||
values []uint8
|
||||
}
|
||||
|
||||
func (m *specMultiOccurrence) Type() wingmate.CronTimeType {
|
||||
return wingmate.MultipleOccurrence
|
||||
}
|
||||
|
||||
func (m *specMultiOccurrence) Match(u uint8) bool {
|
||||
for _, v := range m.values {
|
||||
if v == u {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *specMultiOccurrence) Values() []uint8 {
|
||||
func (m *SpecMultiOccurrence) Values() []uint8 {
|
||||
out := make([]uint8, len(m.values))
|
||||
copy(out, m.values)
|
||||
return out
|
||||
|
||||
129
config/crontab_test.go
Normal file
129
config/crontab_test.go
Normal file
@ -0,0 +1,129 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gitea.suyono.dev/suyono/wingmate"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
crontabFileName = "crontab"
|
||||
)
|
||||
|
||||
func TestCrontab(t *testing.T) {
|
||||
type testEntry struct {
|
||||
name string
|
||||
crontab string
|
||||
wantErr bool
|
||||
}
|
||||
|
||||
_ = wingmate.NewLog(os.Stderr)
|
||||
tests := []testEntry{
|
||||
{
|
||||
name: "positive",
|
||||
crontab: crontabTestCase0,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "with comment",
|
||||
crontab: crontabTestCase1,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "various values",
|
||||
crontab: crontabTestCase2,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "failed to parse",
|
||||
crontab: crontabTestCase3,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
setup(t)
|
||||
defer tear(t)
|
||||
|
||||
writeCrontab(t, tt.crontab)
|
||||
|
||||
cfg, err := Read()
|
||||
if tt.wantErr != (err != nil) {
|
||||
t.Fatalf("wantErr is %v but err is %+v", tt.wantErr, err)
|
||||
}
|
||||
|
||||
t.Logf("cfg: %+v", cfg)
|
||||
for _, c := range cfg.CronV0 {
|
||||
t.Logf("%+v", c)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func writeCrontab(t *testing.T, content string) {
|
||||
var (
|
||||
f *os.File
|
||||
err error
|
||||
)
|
||||
|
||||
if f, err = os.Create(filepath.Join(configDir, crontabFileName)); err != nil {
|
||||
t.Fatal("create crontab file", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
|
||||
if _, err = f.Write([]byte(content)); err != nil {
|
||||
t.Fatal("writing crontab file", err)
|
||||
}
|
||||
}
|
||||
|
||||
const crontabTestCase0 = `* * * * * /path/to/executable`
|
||||
const crontabTestCase1 = `# this is a comment
|
||||
## comment with space
|
||||
* * * * * /path/to/executable
|
||||
* * * * * /path/to/executable # comment as a suffix
|
||||
`
|
||||
|
||||
const crontabTestCase2 = `# first comment
|
||||
*/5 13 3,5,7 * * /path/to/executable`
|
||||
|
||||
const crontabTestCase3 = `a 13 3,5,7 * * /path/to/executable
|
||||
*/5 a 3,5,7 * * /path/to/executable
|
||||
*/5 13 a * * /path/to/executable
|
||||
*/5 13 3,5,7 a * /path/to/executable
|
||||
*/5 13 3,5,7 * a /path/to/executable
|
||||
*/x 13 3,5,7 * * /path/to/executable
|
||||
76 13 3,5,7 * * /path/to/executable
|
||||
*/75 13 3,5,7 * * /path/to/executable
|
||||
*/5 13 3,x,7 * * /path/to/executable
|
||||
*/5 13 3,5,67 * * /path/to/executable
|
||||
*/5 13 * * /path/to/executable
|
||||
*/5 13 3,5,7 * * /path/to/executable`
|
||||
|
||||
func TestSpecExact(t *testing.T) {
|
||||
var val uint8 = 45
|
||||
s := SpecExact{
|
||||
value: val,
|
||||
}
|
||||
|
||||
assert.Equal(t, val, s.Value())
|
||||
}
|
||||
|
||||
func TestSpecMulti(t *testing.T) {
|
||||
val := []uint8{3, 5, 7, 15}
|
||||
s := SpecMultiOccurrence{
|
||||
values: val,
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, val, s.Values())
|
||||
}
|
||||
|
||||
func TestInvalidField(t *testing.T) {
|
||||
c := &Cron{}
|
||||
assert.NotNil(t, c.setField(cronField(99), "x"))
|
||||
}
|
||||
26
config/flags.go
Normal file
26
config/flags.go
Normal file
@ -0,0 +1,26 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.suyono.dev/suyono/wingmate/cmd/cli"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func ParseFlags() {
|
||||
version := cli.Version(fmt.Sprintln(viper.GetString(WingmateVersion)))
|
||||
version.FlagSet(pflag.CommandLine)
|
||||
|
||||
pflag.String(WMPidProxyPathFlag, "", "wmpidproxy path")
|
||||
pflag.String(WMExecPathFlag, "", "wmexec path")
|
||||
pflag.StringP(PathConfigFlag, "c", "", "config path")
|
||||
|
||||
pflag.Parse()
|
||||
|
||||
_ = viper.BindPFlag(PathConfig, pflag.CommandLine.Lookup(PathConfigFlag))
|
||||
_ = viper.BindPFlag(PidProxyPathConfig, pflag.CommandLine.Lookup(WMPidProxyPathFlag))
|
||||
_ = viper.BindPFlag(ExecPathConfig, pflag.CommandLine.Lookup(WMExecPathFlag))
|
||||
|
||||
version.FlagHook()
|
||||
}
|
||||
37
config/util.go
Normal file
37
config/util.go
Normal file
@ -0,0 +1,37 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getVersion(binPath string) (string, error) {
|
||||
var (
|
||||
outBytes []byte
|
||||
err error
|
||||
output string
|
||||
stdout io.ReadCloser
|
||||
n int
|
||||
)
|
||||
cmd := exec.Command(binPath, "version")
|
||||
if stdout, err = cmd.StdoutPipe(); err != nil {
|
||||
return "", fmt.Errorf("setting up stdout reader: %w", err)
|
||||
}
|
||||
|
||||
if err = cmd.Start(); err != nil {
|
||||
return "", fmt.Errorf("starting process: %w", err)
|
||||
}
|
||||
|
||||
outBytes = make([]byte, 1024)
|
||||
if n, err = stdout.Read(outBytes); err != nil {
|
||||
return "", fmt.Errorf("reading stdout: %w", err)
|
||||
}
|
||||
|
||||
_ = cmd.Wait()
|
||||
|
||||
output = string(outBytes[:n])
|
||||
output = strings.TrimRight(output, versionTrimRightCutSet)
|
||||
return output, nil
|
||||
}
|
||||
203
config/yaml.go
Normal file
203
config/yaml.go
Normal file
@ -0,0 +1,203 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.suyono.dev/suyono/wingmate"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
CrontabScheduleRegexPattern = `^\s*(?P<minute>\S+)\s+(?P<hour>\S+)\s+(?P<dom>\S+)\s+(?P<month>\S+)\s+(?P<dow>\S+)\s*$`
|
||||
CrontabScheduleSubMatchLen = 6
|
||||
ServiceConfigGroup = "service"
|
||||
CronConfigGroup = "cron"
|
||||
ServiceKeyFormat = "service.%s"
|
||||
CronKeyFormat = "cron.%s"
|
||||
)
|
||||
|
||||
var (
|
||||
crontabScheduleRegex = regexp.MustCompile(CrontabScheduleRegexPattern)
|
||||
)
|
||||
|
||||
func readConfigYaml(path, name, format string) ([]ServiceTask, []CronTask, error) {
|
||||
var (
|
||||
err error
|
||||
nameMap map[string]any
|
||||
itemName string
|
||||
serviceTask ServiceTask
|
||||
cronTask CronTask
|
||||
item any
|
||||
services []ServiceTask
|
||||
crones []CronTask
|
||||
//findUtils *FindUtils
|
||||
)
|
||||
|
||||
viper.AddConfigPath(path)
|
||||
viper.SetConfigType(format)
|
||||
viper.SetConfigName(name)
|
||||
|
||||
if err = viper.ReadInConfig(); err != nil {
|
||||
return nil, nil, fmt.Errorf("reading config in dir %s, file %s, format %s: %w", path, name, format, err)
|
||||
}
|
||||
|
||||
services = make([]ServiceTask, 0)
|
||||
nameMap = viper.GetStringMap(ServiceConfigGroup)
|
||||
for itemName, item = range nameMap {
|
||||
serviceTask = ServiceTask{}
|
||||
if err = viper.UnmarshalKey(fmt.Sprintf(ServiceKeyFormat, itemName), &serviceTask); err != nil {
|
||||
wingmate.Log().Error().Msgf("failed to parse service %s: %+v | %+v", itemName, err, item)
|
||||
continue
|
||||
}
|
||||
serviceTask.Name = itemName
|
||||
services = append(services, serviceTask)
|
||||
}
|
||||
|
||||
crones = make([]CronTask, 0)
|
||||
nameMap = viper.GetStringMap(CronConfigGroup)
|
||||
for itemName, item = range nameMap {
|
||||
cronTask = CronTask{}
|
||||
if err = viper.UnmarshalKey(fmt.Sprintf(CronKeyFormat, itemName), &cronTask); err != nil {
|
||||
wingmate.Log().Error().Msgf("failed to parse cron %s: %v | %v", itemName, err, item)
|
||||
continue
|
||||
}
|
||||
cronTask.Name = itemName
|
||||
if cronTask.CronSchedule, err = parseYamlSchedule(cronTask.Schedule); err != nil {
|
||||
wingmate.Log().Error().Msgf("parsing cron schedule: %+v", err)
|
||||
continue
|
||||
}
|
||||
crones = append(crones, cronTask)
|
||||
}
|
||||
|
||||
return services, crones, nil
|
||||
}
|
||||
|
||||
func parseYamlSchedule(input string) (schedule CronSchedule, err error) {
|
||||
var (
|
||||
parts []string
|
||||
pSched *CronSchedule
|
||||
)
|
||||
|
||||
parts = crontabScheduleRegex.FindStringSubmatch(input)
|
||||
if len(parts) != CrontabScheduleSubMatchLen {
|
||||
return schedule, fmt.Errorf("invalid schedule: %s", input)
|
||||
}
|
||||
|
||||
pSched = &schedule
|
||||
if err = pSched.setField(minute, parts[1]); err != nil {
|
||||
return schedule, fmt.Errorf("error parsing Minute field: %w", err)
|
||||
}
|
||||
|
||||
if err = pSched.setField(hour, parts[2]); err != nil {
|
||||
return schedule, fmt.Errorf("error parsing Hour field: %w", err)
|
||||
}
|
||||
|
||||
if err = pSched.setField(dom, parts[3]); err != nil {
|
||||
return schedule, fmt.Errorf("error parsing Day of Month field: %w", err)
|
||||
}
|
||||
|
||||
if err = pSched.setField(month, parts[4]); err != nil {
|
||||
return schedule, fmt.Errorf("error parsing Month field: %w", err)
|
||||
}
|
||||
|
||||
if err = pSched.setField(dow, parts[5]); err != nil {
|
||||
return schedule, fmt.Errorf("error parsing Day of Week field: %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CronSchedule) setField(field cronField, input string) error {
|
||||
var (
|
||||
fr *fieldRange
|
||||
cField *CronTimeSpec
|
||||
err error
|
||||
parsed64 uint64
|
||||
parsed uint8
|
||||
multi []uint8
|
||||
current uint8
|
||||
multiStr []string
|
||||
)
|
||||
switch field {
|
||||
case minute:
|
||||
fr = newRange(0, 59)
|
||||
cField = &c.Minute
|
||||
case hour:
|
||||
fr = newRange(0, 23)
|
||||
cField = &c.Hour
|
||||
case dom:
|
||||
fr = newRange(1, 31)
|
||||
cField = &c.DoM
|
||||
case month:
|
||||
fr = newRange(1, 12)
|
||||
cField = &c.Month
|
||||
case dow:
|
||||
fr = newRange(0, 6)
|
||||
cField = &c.DoW
|
||||
default:
|
||||
return errors.New("invalid cron field descriptor")
|
||||
}
|
||||
|
||||
if input == "*" {
|
||||
*cField = &SpecAny{}
|
||||
} else if strings.HasPrefix(input, "*/") {
|
||||
if parsed64, err = strconv.ParseUint(input[2:], 10, 8); err != nil {
|
||||
return fmt.Errorf("error parse field %+v with input %s: %w", field, input, err)
|
||||
}
|
||||
|
||||
parsed = uint8(parsed64)
|
||||
if !fr.valid(parsed) {
|
||||
return fmt.Errorf("error parse field %+v with input %s parsed to %d: invalid value", field, input, parsed)
|
||||
}
|
||||
multi = make([]uint8, 0)
|
||||
current = parsed
|
||||
for fr.valid(current) {
|
||||
multi = append(multi, current)
|
||||
current += parsed
|
||||
}
|
||||
|
||||
*cField = &SpecMultiOccurrence{
|
||||
values: multi,
|
||||
}
|
||||
} else {
|
||||
multiStr = strings.Split(input, ",")
|
||||
if len(multiStr) > 1 {
|
||||
multi = make([]uint8, 0)
|
||||
for _, s := range multiStr {
|
||||
if parsed64, err = strconv.ParseUint(s, 10, 8); err != nil {
|
||||
return fmt.Errorf("error parse field %+v with input %s: %w", field, input, err)
|
||||
}
|
||||
|
||||
parsed = uint8(parsed64)
|
||||
if !fr.valid(parsed) {
|
||||
return fmt.Errorf("error parse field %+v with input %s: invalid value", field, input)
|
||||
}
|
||||
|
||||
multi = append(multi, parsed)
|
||||
}
|
||||
|
||||
*cField = &SpecMultiOccurrence{
|
||||
values: multi,
|
||||
}
|
||||
} else {
|
||||
if parsed64, err = strconv.ParseUint(input, 10, 8); err != nil {
|
||||
return fmt.Errorf("error parse field %+v with input %s: %w", field, input, err)
|
||||
}
|
||||
|
||||
parsed = uint8(parsed64)
|
||||
if !fr.valid(parsed) {
|
||||
return fmt.Errorf("error parse field %+v with input %s: invalid value", field, input)
|
||||
}
|
||||
|
||||
*cField = &SpecExact{
|
||||
value: parsed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
258
config/yaml_test.go
Normal file
258
config/yaml_test.go
Normal file
@ -0,0 +1,258 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"gitea.suyono.dev/suyono/wingmate"
|
||||
)
|
||||
|
||||
const configName = "wingmate.yaml"
|
||||
|
||||
func TestYaml(t *testing.T) {
|
||||
type testEntry struct {
|
||||
name string
|
||||
config string
|
||||
wantErr bool
|
||||
}
|
||||
|
||||
_ = wingmate.NewLog(os.Stderr)
|
||||
tests := []testEntry{
|
||||
{
|
||||
name: "positive",
|
||||
config: yamlTestCase0,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "service only",
|
||||
config: yamlTestCase1,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "cron only",
|
||||
config: yamlTestCase2,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid content - service",
|
||||
config: yamlTestCase3,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range yamlBlobs {
|
||||
tests = append(tests, testEntry{
|
||||
name: fmt.Sprintf("negative - %d", i),
|
||||
config: tc,
|
||||
wantErr: true,
|
||||
})
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
setup(t)
|
||||
defer tear(t)
|
||||
|
||||
writeYaml(t, path.Join(configDir, configName), tt.config)
|
||||
|
||||
cfg, err := Read()
|
||||
if tt.wantErr != (err != nil) {
|
||||
t.Fatalf("wantErr is %v but err is %+v", tt.wantErr, err)
|
||||
}
|
||||
t.Logf("cfg: %+v", cfg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func writeYaml(t *testing.T, path, content string) {
|
||||
var (
|
||||
f *os.File
|
||||
err error
|
||||
)
|
||||
|
||||
if f, err = os.Create(path); err != nil {
|
||||
t.Fatal("create yaml file", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
|
||||
if _, err = f.Write([]byte(content)); err != nil {
|
||||
t.Fatal("write yaml file", err)
|
||||
}
|
||||
}
|
||||
|
||||
const yamlTestCase0 = `version: "1"
|
||||
service:
|
||||
one:
|
||||
command: ["command", "arg0", "arg1"]
|
||||
environ: ["ENV1=value1", "ENV2=valueX"]
|
||||
user: "user1"
|
||||
group: "999"
|
||||
working_dir: "/path/to/working"
|
||||
cron:
|
||||
cron-one:
|
||||
command:
|
||||
- command-cron
|
||||
- arg0
|
||||
- arg1
|
||||
environ: ["ENV1=v1", "ENV2=var2"]
|
||||
user: "1001"
|
||||
group: "978"
|
||||
schedule: "*/5 * * * 2,3"`
|
||||
|
||||
const yamlTestCase1 = `version: "1"
|
||||
service:
|
||||
one:
|
||||
command: ["command", "arg0", "arg1"]
|
||||
environ: ["ENV1=value1", "ENV2=valueX"]
|
||||
user: "user1"
|
||||
group: "999"
|
||||
working_dir: "/path/to/working"`
|
||||
|
||||
const yamlTestCase2 = `version: "1"
|
||||
cron:
|
||||
cron-one:
|
||||
command:
|
||||
- command-cron
|
||||
- arg0
|
||||
- arg1
|
||||
environ: ["ENV1=v1", "ENV2=var2"]
|
||||
user: "1001"
|
||||
group: "978"
|
||||
schedule: "*/5 * * * 2,3"`
|
||||
|
||||
const yamlTestCase3 = `version: "1"
|
||||
service:
|
||||
one:
|
||||
command: 12345
|
||||
environ: ["ENV1=value1", "ENV2=valueX"]
|
||||
user: "user1"
|
||||
group: "999"
|
||||
working_dir: "/path/to/working"`
|
||||
|
||||
var yamlBlobs = []string{
|
||||
`version: "1"
|
||||
cron:
|
||||
cron-one:
|
||||
command:
|
||||
- command-cron
|
||||
- arg0
|
||||
- arg1
|
||||
environ: ["ENV1=v1", "ENV2=var2"]
|
||||
user: "1001"
|
||||
group: "978"
|
||||
schedule: "a 13 3,5,7 * *"`,
|
||||
`version: "1"
|
||||
cron:
|
||||
cron-one:
|
||||
command:
|
||||
- command-cron
|
||||
- arg0
|
||||
- arg1
|
||||
environ: ["ENV1=v1", "ENV2=var2"]
|
||||
user: "1001"
|
||||
group: "978"
|
||||
schedule: "*/5 a 3,5,7 * *"`,
|
||||
`version: "1"
|
||||
cron:
|
||||
cron-one:
|
||||
command:
|
||||
- command-cron
|
||||
- arg0
|
||||
- arg1
|
||||
environ: ["ENV1=v1", "ENV2=var2"]
|
||||
user: "1001"
|
||||
group: "978"
|
||||
schedule: "*/5 13 a * *"`,
|
||||
`version: "1"
|
||||
cron:
|
||||
cron-one:
|
||||
command:
|
||||
- command-cron
|
||||
- arg0
|
||||
- arg1
|
||||
environ: ["ENV1=v1", "ENV2=var2"]
|
||||
user: "1001"
|
||||
group: "978"
|
||||
schedule: "*/5 13 3,5,7 a *"`,
|
||||
`version: "1"
|
||||
cron:
|
||||
cron-one:
|
||||
command:
|
||||
- command-cron
|
||||
- arg0
|
||||
- arg1
|
||||
environ: ["ENV1=v1", "ENV2=var2"]
|
||||
user: "1001"
|
||||
group: "978"
|
||||
schedule: "*/5 13 3,5,7 * a"`,
|
||||
`version: "1"
|
||||
cron:
|
||||
cron-one:
|
||||
command:
|
||||
- command-cron
|
||||
- arg0
|
||||
- arg1
|
||||
environ: ["ENV1=v1", "ENV2=var2"]
|
||||
user: "1001"
|
||||
group: "978"
|
||||
schedule: "*/x 13 3,5,7 * *"`,
|
||||
`version: "1"
|
||||
cron:
|
||||
cron-one:
|
||||
command:
|
||||
- command-cron
|
||||
- arg0
|
||||
- arg1
|
||||
environ: ["ENV1=v1", "ENV2=var2"]
|
||||
user: "1001"
|
||||
group: "978"
|
||||
schedule: "76 13 3,5,7 * *"`,
|
||||
`version: "1"
|
||||
cron:
|
||||
cron-one:
|
||||
command:
|
||||
- command-cron
|
||||
- arg0
|
||||
- arg1
|
||||
environ: ["ENV1=v1", "ENV2=var2"]
|
||||
user: "1001"
|
||||
group: "978"
|
||||
schedule: "*/75 13 3,5,7 * *"`,
|
||||
`version: "1"
|
||||
cron:
|
||||
cron-one:
|
||||
command:
|
||||
- command-cron
|
||||
- arg0
|
||||
- arg1
|
||||
environ: ["ENV1=v1", "ENV2=var2"]
|
||||
user: "1001"
|
||||
group: "978"
|
||||
schedule: "*/5 13 3,x,7 * *"`,
|
||||
`version: "1"
|
||||
cron:
|
||||
cron-one:
|
||||
command:
|
||||
- command-cron
|
||||
- arg0
|
||||
- arg1
|
||||
environ: ["ENV1=v1", "ENV2=var2"]
|
||||
user: "1001"
|
||||
group: "978"
|
||||
schedule: "*/5 13 3,5,67 * *"`,
|
||||
`version: "1"
|
||||
cron:
|
||||
cron-one:
|
||||
command:
|
||||
- command-cron
|
||||
- arg0
|
||||
- arg1
|
||||
environ: ["ENV1=v1", "ENV2=var2"]
|
||||
user: "1001"
|
||||
group: "978"
|
||||
schedule: "*/5 13 * *"`,
|
||||
}
|
||||
@ -1,13 +1,15 @@
|
||||
FROM golang:1.21-alpine as builder
|
||||
FROM golang:1.22-alpine3.20 AS builder
|
||||
|
||||
ADD . /root/wingmate
|
||||
WORKDIR /root/wingmate/
|
||||
ARG TEST_BUILD
|
||||
RUN apk add make build-base && CGO_ENABLED=1 make all && make DESTDIR=/usr/local/bin/wingmate install
|
||||
RUN apk update && apk add git make build-base && \
|
||||
CGO_ENABLED=1 make all && \
|
||||
make DESTDIR=/usr/local/bin/wingmate install
|
||||
|
||||
|
||||
|
||||
FROM alpine:3.18
|
||||
FROM alpine:3.20
|
||||
|
||||
RUN apk add tzdata && ln -s /usr/share/zoneinfo/Australia/Sydney /etc/localtime && \
|
||||
adduser -h /home/user1 -D -s /bin/sh user1 && \
|
||||
|
||||
59
docker/alpine/etc/wingmate/wingmate.yaml
Normal file
59
docker/alpine/etc/wingmate/wingmate.yaml
Normal file
@ -0,0 +1,59 @@
|
||||
service:
|
||||
# one:
|
||||
# command: [ "wmstarter" ]
|
||||
# environ: [ "DUMMY_PATH=/workspace/wingmate/cmd/experiment/dummy/dummy" ]
|
||||
|
||||
spawner:
|
||||
command: [ "wmspawner" ]
|
||||
user: "1200"
|
||||
|
||||
bgtest:
|
||||
command:
|
||||
- "wmstarter"
|
||||
- "--no-wait"
|
||||
- "--"
|
||||
- "wmexec"
|
||||
- "--setsid"
|
||||
- "--"
|
||||
- "wmbg"
|
||||
- "--name"
|
||||
- "test-run"
|
||||
- "--pause"
|
||||
- "10"
|
||||
- "--log-path"
|
||||
- "/var/log/wmbg.log"
|
||||
- "--pid-file"
|
||||
- "/var/run/wmbg.pid"
|
||||
pidfile: "/var/run/wmbg.pid"
|
||||
|
||||
cron:
|
||||
cron1:
|
||||
command:
|
||||
- "wmoneshot"
|
||||
- "--"
|
||||
- "sleep"
|
||||
- "5"
|
||||
schedule: "*/5 * * * *"
|
||||
environ:
|
||||
- "WINGMATE_LOG=/var/log/cron1.log"
|
||||
- "WINGMATE_LOG_MESSAGE=cron executed in minute 5,10,15,20,25,30,35,40,45,50,55"
|
||||
cron2:
|
||||
command:
|
||||
- "wmoneshot"
|
||||
- "--"
|
||||
- "sleep"
|
||||
- "5"
|
||||
schedule: "17,42 */2 * * *"
|
||||
environ:
|
||||
- "WINGMATE_LOG=/var/log/cron2.log"
|
||||
- "WINGMATE_LOG_MESSAGE=cron scheduled using 17,42 */2 * * *"
|
||||
cron3:
|
||||
command:
|
||||
- "wmoneshot"
|
||||
- "--"
|
||||
- "sleep"
|
||||
- "5"
|
||||
schedule: "7,19,23,47 22 * * *"
|
||||
environ:
|
||||
- "WINGMATE_LOG=/var/log/cron3.log"
|
||||
- "WINGMATE_LOG_MESSAGE=cron scheduled using 7,19,23,47 22 * * *"
|
||||
@ -1,4 +1,4 @@
|
||||
FROM golang:1.21-bookworm as builder
|
||||
FROM golang:1.22-bookworm AS builder
|
||||
|
||||
ADD . /root/wingmate
|
||||
WORKDIR /root/wingmate/
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
*/5 * * * * /etc/wingmate/crontab.d/cron1.sh
|
||||
17,42 */2 * * * /etc/wingmate/crontab.d/cron2.sh
|
||||
7,19,23,47 22 * * * /etc/wingmate/crontab.d/cron3.sh
|
||||
@ -1,9 +0,0 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
export WINGMATE_DUMMY_PATH=/usr/local/bin/wmdummy
|
||||
export WINGMATE_LOG=/var/log/cron1.log
|
||||
export WINGMATE_LOG_MESSAGE="cron executed in minute 5,10,15,20,25,30,35,40,45,50,55"
|
||||
|
||||
echo "I'm runnig with dummy=$WINGMATE_DUMMY_PATH, log=$WINGMATE_LOG and mesage=$WINGMATE_LOG_MESSAGE" >> /var/log/debug-cron.log
|
||||
|
||||
exec /usr/local/bin/wmoneshot
|
||||
@ -1,7 +0,0 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
export WINGMATE_DUMMY_PATH=/usr/local/bin/wmdummy
|
||||
export WINGMATE_LOG=/var/log/cron2.log
|
||||
export WINGMATE_LOG_MESSAGE="cron scheduled using 17,42 */2 * * *"
|
||||
|
||||
exec /usr/local/bin/wmoneshot
|
||||
@ -1,7 +0,0 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
export WINGMATE_DUMMY_PATH=/usr/local/bin/wmdummy
|
||||
export WINGMATE_LOG=/var/log/cron3.log
|
||||
export WINGMATE_LOG_MESSAGE="cron entry: 7,19,23,47 22 * * * /etc/wingmate/crontab.d/cron3.sh"
|
||||
|
||||
exec /usr/local/bin/wmoneshot
|
||||
@ -1,4 +0,0 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
export DUMMY_PATH=/usr/local/bin/wmdummy
|
||||
exec /usr/local/bin/wmexec --setsid --user user1:user1 -- /usr/local/bin/wmstarter
|
||||
@ -1,5 +0,0 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
export WINGMATE_ONESHOT_PATH=/usr/local/bin/wmoneshot
|
||||
export WINGMATE_DUMMY_PATH=/usr/local/bin/wmdummy
|
||||
exec /usr/local/bin/wmexec --user 1200 -- /usr/local/bin/wmspawner
|
||||
59
docker/bookworm/etc/wingmate/wingmate.yaml
Normal file
59
docker/bookworm/etc/wingmate/wingmate.yaml
Normal file
@ -0,0 +1,59 @@
|
||||
service:
|
||||
# one:
|
||||
# command: [ "wmstarter" ]
|
||||
# environ: [ "DUMMY_PATH=/workspace/wingmate/cmd/experiment/dummy/dummy" ]
|
||||
|
||||
spawner:
|
||||
command: [ "wmspawner" ]
|
||||
user: "1200"
|
||||
|
||||
bgtest:
|
||||
command:
|
||||
- "wmstarter"
|
||||
- "--no-wait"
|
||||
- "--"
|
||||
- "wmexec"
|
||||
- "--setsid"
|
||||
- "--"
|
||||
- "wmbg"
|
||||
- "--name"
|
||||
- "test-run"
|
||||
- "--pause"
|
||||
- "10"
|
||||
- "--log-path"
|
||||
- "/var/log/wmbg.log"
|
||||
- "--pid-file"
|
||||
- "/var/run/wmbg.pid"
|
||||
pidfile: "/var/run/wmbg.pid"
|
||||
|
||||
cron:
|
||||
cron1:
|
||||
command:
|
||||
- "wmoneshot"
|
||||
- "--"
|
||||
- "sleep"
|
||||
- "5"
|
||||
schedule: "*/5 * * * *"
|
||||
environ:
|
||||
- "WINGMATE_LOG=/var/log/cron1.log"
|
||||
- "WINGMATE_LOG_MESSAGE=cron executed in minute 5,10,15,20,25,30,35,40,45,50,55"
|
||||
cron2:
|
||||
command:
|
||||
- "wmoneshot"
|
||||
- "--"
|
||||
- "sleep"
|
||||
- "5"
|
||||
schedule: "17,42 */2 * * *"
|
||||
environ:
|
||||
- "WINGMATE_LOG=/var/log/cron2.log"
|
||||
- "WINGMATE_LOG_MESSAGE=cron scheduled using 17,42 */2 * * *"
|
||||
cron3:
|
||||
command:
|
||||
- "wmoneshot"
|
||||
- "--"
|
||||
- "sleep"
|
||||
- "5"
|
||||
schedule: "7,19,23,47 22 * * *"
|
||||
environ:
|
||||
- "WINGMATE_LOG=/var/log/cron3.log"
|
||||
- "WINGMATE_LOG_MESSAGE=cron scheduled using 7,19,23,47 22 * * *"
|
||||
8
docker/test/no-config/alpine/Dockerfile
Normal file
8
docker/test/no-config/alpine/Dockerfile
Normal file
@ -0,0 +1,8 @@
|
||||
FROM suyono/wingmate:test AS source
|
||||
|
||||
|
||||
FROM alpine:3.20
|
||||
COPY --from=source /usr/local/bin/ /usr/local/bin/
|
||||
|
||||
ENTRYPOINT [ "/usr/local/bin/entry.sh" ]
|
||||
CMD [ "/usr/local/bin/wingmate" ]
|
||||
8
docker/test/no-config/bookworm/Dockerfile
Normal file
8
docker/test/no-config/bookworm/Dockerfile
Normal file
@ -0,0 +1,8 @@
|
||||
FROM suyono/wingmate:test AS source
|
||||
|
||||
|
||||
FROM debian:bookworm
|
||||
COPY --from=source /usr/local/bin/ /usr/local/bin/
|
||||
|
||||
ENTRYPOINT [ "/usr/local/bin/entry.sh" ]
|
||||
CMD [ "/usr/local/bin/wingmate" ]
|
||||
@ -1,9 +0,0 @@
|
||||
package testconfig
|
||||
|
||||
var One = `service:
|
||||
one:
|
||||
command: "mycommand -o output"
|
||||
two:
|
||||
command: ["cmd", "now"]
|
||||
workdir: /
|
||||
`
|
||||
@ -1,6 +0,0 @@
|
||||
service:
|
||||
one:
|
||||
command: "mycommand -o output"
|
||||
two:
|
||||
command: ["cmd", "now"]
|
||||
workdir: /
|
||||
27
go.mod
27
go.mod
@ -1,13 +1,14 @@
|
||||
module gitea.suyono.dev/suyono/wingmate
|
||||
|
||||
go 1.21
|
||||
go 1.22.7
|
||||
|
||||
require (
|
||||
github.com/rs/zerolog v1.31.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/viper v1.17.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/sys v0.15.0
|
||||
github.com/rs/zerolog v1.33.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/sys v0.25.0
|
||||
)
|
||||
|
||||
require (
|
||||
@ -19,21 +20,17 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.6.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.5.1 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
513
go.sum
513
go.sum
@ -1,543 +1,110 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
|
||||
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
|
||||
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
|
||||
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
|
||||
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
|
||||
github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
|
||||
github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
|
||||
github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
|
||||
github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI=
|
||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
||||
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0=
|
||||
github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No=
|
||||
golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 h1:6R2FC06FonbXQ8pK11/PDFY6N6LWlf9KlzibaCapmqc=
|
||||
golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
||||
35
init/cron.go
35
init/cron.go
@ -2,6 +2,7 @@ package init
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
@ -13,7 +14,7 @@ const (
|
||||
cronTag = "cron"
|
||||
)
|
||||
|
||||
func (i *Init) cron(wg *sync.WaitGroup, cron Cron, exitFlag <-chan any) {
|
||||
func (i *Init) cron(wg *sync.WaitGroup, cron CronTask, exitFlag <-chan any) {
|
||||
defer wg.Done()
|
||||
|
||||
var (
|
||||
@ -21,35 +22,49 @@ func (i *Init) cron(wg *sync.WaitGroup, cron Cron, exitFlag <-chan any) {
|
||||
err error
|
||||
stdout io.ReadCloser
|
||||
stderr io.ReadCloser
|
||||
cmd *exec.Cmd
|
||||
)
|
||||
|
||||
ticker := time.NewTicker(time.Second * 30)
|
||||
cron:
|
||||
for {
|
||||
if cron.TimeToRun(time.Now()) {
|
||||
wingmate.Log().Info().Str(cronTag, cron.Command().Path()).Msg("executing")
|
||||
cmd := exec.Command(cron.Command().Path())
|
||||
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()...)
|
||||
cmd.Env = os.Environ()
|
||||
if cron.EnvLen() > 0 {
|
||||
cmd.Env = append(cmd.Env, cron.Environ()...)
|
||||
}
|
||||
|
||||
if len(cron.WorkingDir()) > 0 {
|
||||
cmd.Dir = cron.WorkingDir()
|
||||
}
|
||||
|
||||
iwg = &sync.WaitGroup{}
|
||||
|
||||
if stdout, err = cmd.StdoutPipe(); err != nil {
|
||||
wingmate.Log().Error().Str(cronTag, cron.Command().Path()).Msgf("stdout pipe: %+v", err)
|
||||
wingmate.Log().Error().Str(cronTag, cron.Name()).Msgf("stdout pipe: %+v", err)
|
||||
goto fail
|
||||
}
|
||||
|
||||
if stderr, err = cmd.StderrPipe(); err != nil {
|
||||
wingmate.Log().Error().Str(cronTag, cron.Command().Path()).Msgf("stderr pipe: %+v", err)
|
||||
wingmate.Log().Error().Str(cronTag, cron.Name()).Msgf("stderr pipe: %+v", err)
|
||||
_ = stdout.Close()
|
||||
goto fail
|
||||
}
|
||||
|
||||
iwg.Add(1)
|
||||
go i.pipeReader(iwg, stdout, cronTag, cron.Command().Path())
|
||||
go i.pipeReader(iwg, stdout, cronTag, cron.Name())
|
||||
|
||||
iwg.Add(1)
|
||||
go i.pipeReader(iwg, stderr, cronTag, cron.Command().Path())
|
||||
go i.pipeReader(iwg, stderr, cronTag, cron.Name())
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
wingmate.Log().Error().Msgf("starting cron %s error %+v", cron.Command().Path(), err)
|
||||
wingmate.Log().Error().Msgf("starting cron %s error %+v", cron.Name(), err)
|
||||
_ = stdout.Close()
|
||||
_ = stderr.Close()
|
||||
iwg.Wait()
|
||||
@ -58,9 +73,7 @@ cron:
|
||||
|
||||
iwg.Wait()
|
||||
|
||||
if err = cmd.Wait(); err != nil {
|
||||
wingmate.Log().Error().Str(cronTag, cron.Command().Path()).Msgf("got error when waiting: %+v", err)
|
||||
}
|
||||
_ = cmd.Wait()
|
||||
}
|
||||
|
||||
fail:
|
||||
|
||||
48
init/init.go
48
init/init.go
@ -6,18 +6,50 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Path interface {
|
||||
Path() string
|
||||
type Tasks interface {
|
||||
List() []Task
|
||||
Services() []ServiceTask
|
||||
Crones() []CronTask
|
||||
Get(string) (Task, error)
|
||||
}
|
||||
|
||||
type Cron interface {
|
||||
Command() Path
|
||||
type UserGroup interface {
|
||||
String() string
|
||||
IsSet() bool
|
||||
}
|
||||
|
||||
type TaskStatus interface {
|
||||
}
|
||||
|
||||
type Task interface {
|
||||
Name() string
|
||||
Command() string
|
||||
Arguments() []string
|
||||
EnvLen() int
|
||||
Environ() []string
|
||||
Setsid() bool
|
||||
UserGroup() UserGroup
|
||||
WorkingDir() string
|
||||
Status() TaskStatus
|
||||
UtilDepCheck() error
|
||||
}
|
||||
|
||||
type CronTask interface {
|
||||
Task
|
||||
TimeToRun(time.Time) bool
|
||||
}
|
||||
|
||||
type ServiceTask interface {
|
||||
Task
|
||||
Background() bool //NOTE: implies using wmpidproxy
|
||||
PidFile() string //NOTE: implies using wmpidproxy
|
||||
StartSecs() uint
|
||||
AutoStart() bool
|
||||
AutoRestart() bool
|
||||
}
|
||||
|
||||
type Config interface {
|
||||
Services() []Path
|
||||
Cron() []Cron
|
||||
Tasks() Tasks
|
||||
}
|
||||
|
||||
type Init struct {
|
||||
@ -49,12 +81,12 @@ func (i *Init) Start() {
|
||||
wg.Add(1)
|
||||
go i.sighandler(wg, signalTrigger, sighandlerExit, sigchld)
|
||||
|
||||
for _, s := range i.config.Services() {
|
||||
for _, s := range i.config.Tasks().Services() {
|
||||
wg.Add(1)
|
||||
go i.service(wg, s, signalTrigger)
|
||||
}
|
||||
|
||||
for _, c := range i.config.Cron() {
|
||||
for _, c := range i.config.Tasks().Crones() {
|
||||
wg.Add(1)
|
||||
go i.cron(wg, c, signalTrigger)
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package init
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
@ -14,7 +15,7 @@ const (
|
||||
serviceTag = "service"
|
||||
)
|
||||
|
||||
func (i *Init) service(wg *sync.WaitGroup, path Path, exitFlag <-chan any) {
|
||||
func (i *Init) service(wg *sync.WaitGroup, task ServiceTask, exitFlag <-chan any) {
|
||||
defer wg.Done()
|
||||
|
||||
var (
|
||||
@ -23,37 +24,52 @@ func (i *Init) service(wg *sync.WaitGroup, path Path, exitFlag <-chan any) {
|
||||
stderr io.ReadCloser
|
||||
stdout io.ReadCloser
|
||||
failStatus bool
|
||||
cmd *exec.Cmd
|
||||
)
|
||||
|
||||
defer func() {
|
||||
wingmate.Log().Info().Str(serviceTag, path.Path()).Msg("stopped")
|
||||
wingmate.Log().Info().Str(serviceTag, task.Name()).Msg("stopped")
|
||||
}()
|
||||
|
||||
service:
|
||||
for {
|
||||
failStatus = false
|
||||
cmd := exec.Command(path.Path())
|
||||
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()...)
|
||||
cmd.Env = os.Environ()
|
||||
if task.EnvLen() > 0 {
|
||||
cmd.Env = append(cmd.Env, task.Environ()...)
|
||||
}
|
||||
|
||||
if len(task.WorkingDir()) > 0 {
|
||||
cmd.Dir = task.WorkingDir()
|
||||
}
|
||||
|
||||
iwg = &sync.WaitGroup{}
|
||||
|
||||
if stdout, err = cmd.StdoutPipe(); err != nil {
|
||||
wingmate.Log().Error().Str(serviceTag, path.Path()).Msgf("stdout pipe: %#v", err)
|
||||
wingmate.Log().Error().Str(serviceTag, task.Name()).Msgf("stdout pipe: %#v", err)
|
||||
failStatus = true
|
||||
goto fail
|
||||
}
|
||||
iwg.Add(1)
|
||||
go i.pipeReader(iwg, stdout, serviceTag, path.Path())
|
||||
go i.pipeReader(iwg, stdout, serviceTag, task.Name())
|
||||
|
||||
if stderr, err = cmd.StderrPipe(); err != nil {
|
||||
wingmate.Log().Error().Str(serviceTag, path.Path()).Msgf("stderr pipe: %#v", err)
|
||||
wingmate.Log().Error().Str(serviceTag, task.Name()).Msgf("stderr pipe: %#v", err)
|
||||
_ = stdout.Close()
|
||||
failStatus = true
|
||||
goto fail
|
||||
}
|
||||
iwg.Add(1)
|
||||
go i.pipeReader(iwg, stderr, serviceTag, path.Path())
|
||||
go i.pipeReader(iwg, stderr, serviceTag, task.Name())
|
||||
|
||||
if err = cmd.Start(); err != nil {
|
||||
wingmate.Log().Error().Msgf("starting service %s error %#v", path.Path(), err)
|
||||
wingmate.Log().Error().Msgf("starting service %s error %#v", task.Name(), err)
|
||||
failStatus = true
|
||||
_ = stdout.Close()
|
||||
_ = stderr.Close()
|
||||
@ -63,9 +79,8 @@ service:
|
||||
|
||||
iwg.Wait()
|
||||
|
||||
if err = cmd.Wait(); err != nil {
|
||||
wingmate.Log().Error().Str(serviceTag, path.Path()).Msgf("got error when waiting: %+v", err)
|
||||
}
|
||||
_ = cmd.Wait()
|
||||
|
||||
fail:
|
||||
if failStatus {
|
||||
time.Sleep(time.Second)
|
||||
|
||||
@ -46,6 +46,10 @@ func (w *wrapper) Error() logger.Content {
|
||||
return (*eventWrapper)(w.log.Error().Time(timeTag, time.Now()))
|
||||
}
|
||||
|
||||
func (w *wrapper) Fatal() logger.Content {
|
||||
return (*eventWrapper)(w.log.Fatal().Time(timeTag, time.Now()))
|
||||
}
|
||||
|
||||
type eventWrapper zerolog.Event
|
||||
|
||||
func (w *eventWrapper) Msg(msg string) {
|
||||
@ -60,3 +64,8 @@ func (w *eventWrapper) Str(key, value string) logger.Content {
|
||||
rv := (*zerolog.Event)(w).Str(key, value)
|
||||
return (*eventWrapper)(rv)
|
||||
}
|
||||
|
||||
func (w *eventWrapper) Err(err error) logger.Content {
|
||||
rv := (*zerolog.Event)(w).Err(err)
|
||||
return (*eventWrapper)(rv)
|
||||
}
|
||||
|
||||
@ -4,12 +4,14 @@ type Content interface {
|
||||
Msg(string)
|
||||
Msgf(string, ...any)
|
||||
Str(string, string) Content
|
||||
Err(error) Content
|
||||
}
|
||||
|
||||
type Level interface {
|
||||
Info() Content
|
||||
Warn() Content
|
||||
Error() Content
|
||||
Fatal() Content
|
||||
}
|
||||
|
||||
type Log interface {
|
||||
|
||||
292
task/cron.go
Normal file
292
task/cron.go
Normal file
@ -0,0 +1,292 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitea.suyono.dev/suyono/wingmate"
|
||||
|
||||
wminit "gitea.suyono.dev/suyono/wingmate/init"
|
||||
)
|
||||
|
||||
type CronSchedule struct {
|
||||
Minute CronTimeSpec
|
||||
Hour CronTimeSpec
|
||||
DoM CronTimeSpec
|
||||
Month CronTimeSpec
|
||||
DoW CronTimeSpec
|
||||
}
|
||||
|
||||
type CronTimeSpec interface {
|
||||
Match(uint8) bool
|
||||
}
|
||||
|
||||
type CronAnySpec struct {
|
||||
}
|
||||
|
||||
func NewCronAnySpec() *CronAnySpec {
|
||||
return &CronAnySpec{}
|
||||
}
|
||||
|
||||
func (cas *CronAnySpec) Match(u uint8) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type CronExactSpec struct {
|
||||
value uint8
|
||||
}
|
||||
|
||||
func NewCronExactSpec(v uint8) *CronExactSpec {
|
||||
return &CronExactSpec{
|
||||
value: v,
|
||||
}
|
||||
}
|
||||
|
||||
func (ces *CronExactSpec) Match(u uint8) bool {
|
||||
return u == ces.value
|
||||
}
|
||||
|
||||
type CronMultiOccurrenceSpec struct {
|
||||
values []uint8
|
||||
}
|
||||
|
||||
func NewCronMultiOccurrenceSpec(v ...uint8) *CronMultiOccurrenceSpec {
|
||||
retval := &CronMultiOccurrenceSpec{}
|
||||
if len(v) > 0 {
|
||||
retval.values = make([]uint8, len(v))
|
||||
copy(retval.values, v)
|
||||
}
|
||||
|
||||
return retval
|
||||
}
|
||||
|
||||
func (cms *CronMultiOccurrenceSpec) Match(u uint8) bool {
|
||||
for _, v := range cms.values {
|
||||
if v == u {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type CronTask struct {
|
||||
CronSchedule
|
||||
userGroup
|
||||
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 {
|
||||
return &CronTask{
|
||||
name: name,
|
||||
hasRun: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CronTask) SetCommand(cmds ...string) *CronTask {
|
||||
c.command = make([]string, len(cmds))
|
||||
copy(c.command, cmds)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CronTask) SetEnv(envs ...string) *CronTask {
|
||||
c.environ = make([]string, len(envs))
|
||||
copy(c.environ, envs)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CronTask) SetFlagSetsid(flag bool) *CronTask {
|
||||
c.setsid = flag
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CronTask) SetWorkingDir(path string) *CronTask {
|
||||
c.workingDir = path
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CronTask) SetUser(user string) *CronTask {
|
||||
c.user = user
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CronTask) SetGroup(group string) *CronTask {
|
||||
c.group = group
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CronTask) SetSchedule(scheduleStr string, schedule CronSchedule) *CronTask {
|
||||
c.cronScheduleString = scheduleStr
|
||||
c.CronSchedule = schedule
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CronTask) SetConfig(config config) *CronTask {
|
||||
c.config = config
|
||||
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: p.Name(),
|
||||
Command: p.Command(),
|
||||
Arguments: p.Arguments(),
|
||||
Environ: p.Environ(),
|
||||
Setsid: p.Setsid(),
|
||||
UserGroup: p.UserGroup().String(),
|
||||
WorkingDir: p.WorkingDir(),
|
||||
Schedule: p.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.cmdLine[0]
|
||||
}
|
||||
|
||||
func (c *CronTask) Arguments() []string {
|
||||
if len(c.cmdLine) == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
retval := make([]string, len(c.cmdLine)-1)
|
||||
copy(retval, c.cmdLine[1:])
|
||||
|
||||
return retval
|
||||
}
|
||||
|
||||
func (c *CronTask) EnvLen() int {
|
||||
return len(c.environ)
|
||||
}
|
||||
|
||||
func (c *CronTask) Environ() []string {
|
||||
retval := make([]string, len(c.environ))
|
||||
copy(retval, c.environ)
|
||||
return retval
|
||||
}
|
||||
|
||||
func (c *CronTask) Setsid() bool {
|
||||
return c.setsid
|
||||
}
|
||||
|
||||
func (c *CronTask) UserGroup() wminit.UserGroup {
|
||||
return &(c.userGroup)
|
||||
}
|
||||
|
||||
func (c *CronTask) WorkingDir() string {
|
||||
return c.workingDir
|
||||
}
|
||||
|
||||
func (c *CronTask) Status() wminit.TaskStatus {
|
||||
//TODO: implement me!
|
||||
panic("not implemented")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CronTask) TimeToRun(now time.Time) bool {
|
||||
if c.Minute.Match(uint8(now.Minute())) &&
|
||||
c.Hour.Match(uint8(now.Hour())) &&
|
||||
c.DoM.Match(uint8(now.Day())) &&
|
||||
c.Month.Match(uint8(now.Month())) &&
|
||||
c.DoW.Match(uint8(now.Weekday())) {
|
||||
|
||||
if c.hasRun {
|
||||
if now.Sub(c.lastRun) <= time.Minute && now.Minute() == c.lastRun.Minute() {
|
||||
return false
|
||||
} else {
|
||||
c.lastRun = now
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
c.lastRun = now
|
||||
c.hasRun = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
374
task/task.go
Normal file
374
task/task.go
Normal file
@ -0,0 +1,374 @@
|
||||
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 {
|
||||
services []wminit.ServiceTask
|
||||
crones []wminit.CronTask
|
||||
}
|
||||
|
||||
func NewTasks() *Tasks {
|
||||
return &Tasks{
|
||||
services: make([]wminit.ServiceTask, 0),
|
||||
crones: make([]wminit.CronTask, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *Tasks) AddV0Service(path string) {
|
||||
ts.AddService(NewServiceTask(path)).SetCommand(path)
|
||||
}
|
||||
|
||||
func (ts *Tasks) AddService(serviceTask *ServiceTask) *ServiceTask {
|
||||
ts.services = append(ts.services, serviceTask)
|
||||
return serviceTask
|
||||
}
|
||||
|
||||
func (ts *Tasks) AddV0Cron(schedule CronSchedule, path string) {
|
||||
ts.AddCron(NewCronTask(path)).SetCommand(path).SetSchedule("", schedule)
|
||||
}
|
||||
|
||||
func (ts *Tasks) AddCron(cronTask *CronTask) *CronTask {
|
||||
ts.crones = append(ts.crones, cronTask)
|
||||
return cronTask
|
||||
}
|
||||
|
||||
func (ts *Tasks) List() []wminit.Task {
|
||||
retval := make([]wminit.Task, 0, len(ts.services)+len(ts.crones))
|
||||
for _, s := range ts.services {
|
||||
retval = append(retval, s.(wminit.Task))
|
||||
}
|
||||
for _, c := range ts.crones {
|
||||
retval = append(retval, c.(wminit.Task))
|
||||
}
|
||||
return retval
|
||||
}
|
||||
|
||||
func (ts *Tasks) Services() []wminit.ServiceTask {
|
||||
return ts.services
|
||||
}
|
||||
|
||||
func (ts *Tasks) Crones() []wminit.CronTask {
|
||||
return ts.crones
|
||||
}
|
||||
|
||||
func (ts *Tasks) Get(name string) (wminit.Task, error) {
|
||||
//TODO: implement me!
|
||||
panic("not implemented")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type ServiceTask struct {
|
||||
name string
|
||||
command []string
|
||||
cmdLine []string
|
||||
environ []string
|
||||
setsid bool
|
||||
background bool
|
||||
workingDir string
|
||||
startSecs uint
|
||||
pidFile string
|
||||
config config
|
||||
userGroup
|
||||
}
|
||||
|
||||
func NewServiceTask(name string) *ServiceTask {
|
||||
return &ServiceTask{
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ServiceTask) SetCommand(cmds ...string) *ServiceTask {
|
||||
t.command = make([]string, len(cmds))
|
||||
copy(t.command, cmds)
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *ServiceTask) SetEnv(envs ...string) *ServiceTask {
|
||||
t.environ = make([]string, len(envs))
|
||||
copy(t.environ, envs)
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *ServiceTask) SetFlagSetsid(flag bool) *ServiceTask {
|
||||
t.setsid = flag
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *ServiceTask) SetWorkingDir(path string) *ServiceTask {
|
||||
t.workingDir = path
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *ServiceTask) SetUser(user string) *ServiceTask {
|
||||
t.user = user
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *ServiceTask) SetGroup(group string) *ServiceTask {
|
||||
t.group = group
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *ServiceTask) SetStartSecs(secs uint) *ServiceTask {
|
||||
t.startSecs = secs
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *ServiceTask) SetPidFile(path string) *ServiceTask {
|
||||
t.pidFile = path
|
||||
if len(path) > 0 {
|
||||
t.background = true
|
||||
} else {
|
||||
t.background = false
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *ServiceTask) SetConfig(config config) *ServiceTask {
|
||||
t.config = config
|
||||
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 */ )
|
||||
}
|
||||
|
||||
func (t *ServiceTask) Name() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
func (t *ServiceTask) prepareCommandLine() []string {
|
||||
if len(t.cmdLine) > 0 {
|
||||
return t.cmdLine
|
||||
}
|
||||
|
||||
t.cmdLine = make([]string, 0)
|
||||
if t.background {
|
||||
t.cmdLine = append(t.cmdLine, t.config.WMPidProxyPath(), "--pid-file", t.pidFile, "--")
|
||||
}
|
||||
|
||||
if t.setsid || t.UserGroup().IsSet() {
|
||||
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 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 {
|
||||
return t.cmdLine[0]
|
||||
}
|
||||
|
||||
func (t *ServiceTask) Arguments() []string {
|
||||
if len(t.cmdLine) == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
retval := make([]string, len(t.cmdLine)-1)
|
||||
copy(retval, t.cmdLine[1:])
|
||||
|
||||
return retval
|
||||
}
|
||||
|
||||
func (t *ServiceTask) EnvLen() int {
|
||||
return len(t.environ)
|
||||
}
|
||||
|
||||
func (t *ServiceTask) Environ() []string {
|
||||
retval := make([]string, len(t.environ))
|
||||
copy(retval, t.environ)
|
||||
return retval
|
||||
}
|
||||
|
||||
func (t *ServiceTask) Setsid() bool {
|
||||
return t.setsid
|
||||
}
|
||||
|
||||
func (t *ServiceTask) UserGroup() wminit.UserGroup {
|
||||
return &(t.userGroup)
|
||||
}
|
||||
|
||||
func (t *ServiceTask) Background() bool {
|
||||
return t.background
|
||||
}
|
||||
|
||||
func (t *ServiceTask) WorkingDir() string {
|
||||
return t.workingDir
|
||||
}
|
||||
|
||||
func (t *ServiceTask) Status() wminit.TaskStatus {
|
||||
//TODO: implement me!
|
||||
panic("not implemented")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ServiceTask) AutoStart() bool {
|
||||
//TODO: implement me!
|
||||
panic("not implemented")
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *ServiceTask) AutoRestart() bool {
|
||||
//TODO: implement me!
|
||||
panic("not implemented")
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *ServiceTask) StartSecs() uint {
|
||||
return t.startSecs
|
||||
}
|
||||
|
||||
func (t *ServiceTask) PidFile() string {
|
||||
return t.pidFile
|
||||
}
|
||||
|
||||
type userGroup struct {
|
||||
user string
|
||||
group string
|
||||
}
|
||||
|
||||
func (ug *userGroup) IsSet() bool {
|
||||
return len(ug.user) > 0 || len(ug.group) > 0
|
||||
}
|
||||
|
||||
func (ug *userGroup) String() string {
|
||||
if len(ug.group) > 0 {
|
||||
return fmt.Sprintf("%s:%s", ug.user, ug.group)
|
||||
}
|
||||
|
||||
return ug.user
|
||||
}
|
||||
|
||||
func validate(validators ...func() error) error {
|
||||
var err error
|
||||
for _, v := range validators {
|
||||
if err = v(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
79
task/task_test.go
Normal file
79
task/task_test.go
Normal file
@ -0,0 +1,79 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
wminit "gitea.suyono.dev/suyono/wingmate/init"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestServicesV0(t *testing.T) {
|
||||
service := "/path/to/executable"
|
||||
tasks := NewTasks()
|
||||
tasks.AddV0Service(service)
|
||||
|
||||
assert.Equal(t, tasks.Services()[0].Name(), service)
|
||||
assert.ElementsMatch(t, tasks.Services()[0].Command(), []string{service})
|
||||
}
|
||||
|
||||
func TestCronV0(t *testing.T) {
|
||||
cron := "/path/to/executable"
|
||||
tasks := NewTasks()
|
||||
tasks.AddV0Cron(CronSchedule{
|
||||
Minute: NewCronAnySpec(),
|
||||
Hour: NewCronAnySpec(),
|
||||
DoM: NewCronAnySpec(),
|
||||
Month: NewCronAnySpec(),
|
||||
DoW: NewCronAnySpec(),
|
||||
}, cron)
|
||||
|
||||
assert.Equal(t, tasks.Crones()[0].Name(), cron)
|
||||
assert.ElementsMatch(t, tasks.Crones()[0].Command(), []string{cron})
|
||||
}
|
||||
|
||||
func TestTasks_List(t *testing.T) {
|
||||
tasks := NewTasks()
|
||||
tasks.services = []wminit.ServiceTask{
|
||||
&ServiceTask{
|
||||
name: "one",
|
||||
command: []string{"/path/to/executable"},
|
||||
},
|
||||
&ServiceTask{
|
||||
name: "two",
|
||||
command: []string{"/path/to/executable"},
|
||||
},
|
||||
}
|
||||
tasks.crones = []wminit.CronTask{
|
||||
&CronTask{
|
||||
CronSchedule: CronSchedule{
|
||||
Minute: NewCronAnySpec(),
|
||||
Hour: NewCronAnySpec(),
|
||||
DoM: NewCronAnySpec(),
|
||||
Month: NewCronAnySpec(),
|
||||
DoW: NewCronAnySpec(),
|
||||
},
|
||||
name: "cron-one",
|
||||
command: []string{"/path/to/executable"},
|
||||
},
|
||||
&CronTask{
|
||||
CronSchedule: CronSchedule{
|
||||
Minute: NewCronAnySpec(),
|
||||
Hour: NewCronAnySpec(),
|
||||
DoM: NewCronAnySpec(),
|
||||
Month: NewCronAnySpec(),
|
||||
DoW: NewCronAnySpec(),
|
||||
},
|
||||
name: "cron-two",
|
||||
command: []string{"/path/to/executable"},
|
||||
},
|
||||
}
|
||||
|
||||
tl := tasks.List()
|
||||
tnames := make([]string, 0)
|
||||
testNames := []string{"one", "two", "cron-one", "cron-two"}
|
||||
|
||||
for _, ti := range tl {
|
||||
tnames = append(tnames, ti.Name())
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, testNames, tnames)
|
||||
}
|
||||
183
wingmate.yaml.md
Normal file
183
wingmate.yaml.md
Normal file
@ -0,0 +1,183 @@
|
||||
YAML Configuration
|
||||
---
|
||||
|
||||
Table of content
|
||||
- [Service](#service)
|
||||
- [Command](#command)
|
||||
- [Environ](#environ)
|
||||
- [User and Group](#user-and-group)
|
||||
- [Working Directory](#working-directory)
|
||||
- [setsid](#setsid)
|
||||
- [PID File](#pid-file)
|
||||
- [Cron](#cron)
|
||||
- [Schedule](#schedule)
|
||||
|
||||
Example
|
||||
```yaml
|
||||
service:
|
||||
spawner:
|
||||
command: [ wmspawner ]
|
||||
user: "1200"
|
||||
working_dir: "/var/run/test"
|
||||
|
||||
bgtest:
|
||||
command:
|
||||
- "wmstarter"
|
||||
- "--no-wait"
|
||||
- "--"
|
||||
- "wmexec"
|
||||
- "--setsid"
|
||||
- "--"
|
||||
- "wmbg"
|
||||
- "--name"
|
||||
- "test-run"
|
||||
- "--pause"
|
||||
- "10"
|
||||
- "--log-path"
|
||||
- "/var/log/wmbg.log"
|
||||
- "--pid-file"
|
||||
- "/var/run/wmbg.pid"
|
||||
pidfile: "/var/run/wmbg.pid"
|
||||
|
||||
cron:
|
||||
cron1:
|
||||
command: ["wmoneshot", "--", "sleep", "5"]
|
||||
schedule: "*/5 * * * *"
|
||||
working_dir: "/var/run/cron"
|
||||
environ:
|
||||
- "WINGMATE_LOG=/var/log/cron1.log"
|
||||
- "WINGMATE_LOG_MESSAGE=cron executed in minute 5,10,15,20,25,30,35,40,45,50,55"
|
||||
cron2:
|
||||
command: ["wmoneshot", "--", "sleep", "5"]
|
||||
schedule: "17,42 */2 * * *"
|
||||
environ:
|
||||
- "WINGMATE_LOG=/var/log/cron2.log"
|
||||
- "WINGMATE_LOG_MESSAGE=cron scheduled using 17,42 */2 * * *"
|
||||
cron3:
|
||||
command:
|
||||
- "wmoneshot"
|
||||
- "--"
|
||||
- "sleep"
|
||||
- "5"
|
||||
schedule: "7,19,23,47 22 * * *"
|
||||
environ:
|
||||
- "WINGMATE_LOG=/var/log/cron3.log"
|
||||
- "WINGMATE_LOG_MESSAGE=cron scheduled using 7,19,23,47 22 * * *"
|
||||
|
||||
```
|
||||
|
||||
At the top-level, there are two possible entries: Service and Cron.
|
||||
|
||||
## Service
|
||||
|
||||
`service` is a top-level element that hosts the definition of services to be started by `wingmate`.
|
||||
|
||||
Example
|
||||
```yaml
|
||||
service:
|
||||
svc1:
|
||||
command: [ some_executable ]
|
||||
user: "1200"
|
||||
working_dir: "/var/run/test"
|
||||
```
|
||||
|
||||
In the example above, we declare a service called `svc1`. `wingmate` will start a process based on all
|
||||
elements defined under `svc1`. To learn more about elements for a service, read below.
|
||||
|
||||
### Command
|
||||
|
||||
`command` element is an array of strings consists of an executable name (optionally with path) and
|
||||
its arguments (if any). `wingmate` will start the service as its child process by executing
|
||||
the executable with its arguments.
|
||||
|
||||
Example
|
||||
|
||||
```yaml
|
||||
command: [ executable1, argument1, argument2 ]
|
||||
```
|
||||
|
||||
Based on YAML standard, the above example can also be written like
|
||||
|
||||
```yaml
|
||||
command:
|
||||
- executable1
|
||||
- argument1
|
||||
- argument2
|
||||
```
|
||||
|
||||
### Environ
|
||||
|
||||
`environ` element is an array of strings. It is a list of environment variables `wingmate` will pass to
|
||||
the child process or service. The format of each environment variable is a pair of key and value separated
|
||||
by `=` sign. By default, the child process or service will inherit all environment variables of its parent.
|
||||
|
||||
Example
|
||||
|
||||
```yaml
|
||||
environ:
|
||||
- "S3_BUCKET=YOURS3BUCKET"
|
||||
- "SECRET_KEY=YOUR_SECRET_KEY"
|
||||
```
|
||||
|
||||
Note: don't worry if an environment variable value has one or more `=` character(s) in it. `wingmate` will
|
||||
separate key and value using the first `=` character only.
|
||||
|
||||
### Working Directory
|
||||
|
||||
`working_dir` is a string contains the path where the child process will be running in. By default, the child
|
||||
process will run in the `wingmate` current directory.
|
||||
|
||||
### User and Group
|
||||
|
||||
Both `user` and `group` take string value. `user` and `group` refer to the operating system's user and group.
|
||||
They can be in the form of name, like username or groupname, or in the form of id, like uid or gid.
|
||||
If they are set, the child process will run as the specified user and group. By default, the child process
|
||||
will run as the same user and group as the `wingmate` process. The `user` and `group` are only effective
|
||||
when the `wingmate` running as privileged user, such as `root`. The `user` and `group` configuration depends
|
||||
on the [wmexec](README.md#wingmate-exec-binary).
|
||||
|
||||
### setsid
|
||||
|
||||
`setsid` takes a boolean value, `true` or `false`. This feature is operating system dependant. If set to `true`,
|
||||
the child process will run in a new session. Read `man setsid` on Linux/UNIX. The `setsid` configuration depends
|
||||
on the [wmexec](README.md#wingmate-exec-binary).
|
||||
|
||||
### PID File
|
||||
|
||||
This feature is designated to handle service that run in the background. This kind of service usually forks a
|
||||
new process, terminate the parent process, and continue running in the background child process. It writes its
|
||||
background process PID in a file. This file is referred as PID file. Put the path of the PID file to this
|
||||
`pidfile` element. It will help `wingmate` to restart the service if its process exited / terminated. The `pidfile`
|
||||
configuration depends on the [wmpidproxy](README.md#wingmate-pid-proxy-binary).
|
||||
|
||||
## Cron
|
||||
|
||||
`cron` is a top-level element that hosts the definition of crones to run by `wingmate` on the specified schedule.
|
||||
Cron shares almost all configuration elements with Service, except `schedule` and `pidfile`. For the following
|
||||
elements, please refer to the [Service](#service) section
|
||||
|
||||
- [Command](#command)
|
||||
- [Environ](#environ)
|
||||
- [Working Directory](#working-directory)
|
||||
- [setsid](#setsid)
|
||||
- [User and Group](#user-and-group)
|
||||
|
||||
`pidfile` is an invalid config parameter for cron because `wingmate` cannot start cron in background mode. This
|
||||
limitation is intentionally built into `wingmate` because it doesn't make any sense to run a periodic cron process
|
||||
in background.
|
||||
|
||||
### Schedule
|
||||
|
||||
The schedule configuration field uses a format similar to the one described in the [README.md](README.md).
|
||||
|
||||
```shell
|
||||
┌───────────── minute (0–59)
|
||||
│ ┌───────────── hour (0–23)
|
||||
│ │ ┌───────────── day of the month (1–31)
|
||||
│ │ │ ┌───────────── month (1–12)
|
||||
│ │ │ │ ┌───────────── day of the week (0–6) (Sunday to Saturday)
|
||||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
* * * * *
|
||||
```
|
||||
Loading…
x
Reference in New Issue
Block a user