preparation for v0.2.0 #3
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,6 @@
|
|||||||
/cmd/wingmate/wingmate
|
/cmd/wingmate/wingmate
|
||||||
|
/cmd/wingmate/version.txt
|
||||||
/cmd/pidproxy/pidproxy
|
/cmd/pidproxy/pidproxy
|
||||||
/cmd/exec/exec
|
/cmd/pidproxy/version.txt
|
||||||
|
/cmd/exec/exec
|
||||||
|
/cmd/exec/version.txt
|
||||||
@ -1,7 +1,9 @@
|
|||||||
all:
|
all:
|
||||||
|
git describe > version.txt
|
||||||
go build -v
|
go build -v
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
rm version.txt
|
||||||
go clean -i -cache -testcache
|
go clean -i -cache -testcache
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
|||||||
6
cmd/experiment/.gitignore
vendored
6
cmd/experiment/.gitignore
vendored
@ -1,4 +1,8 @@
|
|||||||
/dummy/dummy
|
/dummy/dummy
|
||||||
|
/dummy/version.txt
|
||||||
/starter/starter
|
/starter/starter
|
||||||
|
/starter/version.txt
|
||||||
/oneshot/oneshot
|
/oneshot/oneshot
|
||||||
/spawner/spawner
|
/oneshot/version.txt
|
||||||
|
/spawner/spawner
|
||||||
|
/spawner/version.txt
|
||||||
@ -1,7 +1,9 @@
|
|||||||
all:
|
all:
|
||||||
|
git describe > version.txt
|
||||||
go build -v
|
go build -v
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
rm version.txt
|
||||||
go clean -i -cache -testcache
|
go clean -i -cache -testcache
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
all:
|
all:
|
||||||
|
git describe > version.txt
|
||||||
go build -v
|
go build -v
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
rm version.txt
|
||||||
go clean -i -cache -testcache
|
go clean -i -cache -testcache
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
all:
|
all:
|
||||||
|
git describe > version.txt
|
||||||
go build -v
|
go build -v
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
rm version.txt
|
||||||
go clean -i -cache -testcache
|
go clean -i -cache -testcache
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
all:
|
all:
|
||||||
|
git describe > version.txt
|
||||||
go build -v
|
go build -v
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
rm version.txt
|
||||||
go clean -i -cache -testcache
|
go clean -i -cache -testcache
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
all:
|
all:
|
||||||
|
git describe > version.txt
|
||||||
go build -v
|
go build -v
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
rm version.txt
|
||||||
go clean -i -cache -testcache
|
go clean -i -cache -testcache
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
all:
|
all:
|
||||||
|
git describe > version.txt
|
||||||
go build -v
|
go build -v
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
rm version.txt
|
||||||
go clean -i -cache -testcache
|
go clean -i -cache -testcache
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
|||||||
@ -19,13 +19,13 @@ func convert(cfg *config.Config) *wConfig {
|
|||||||
tasks: task.NewTasks(),
|
tasks: task.NewTasks(),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range cfg.ServicePaths {
|
for _, s := range cfg.ServiceV0 {
|
||||||
retval.tasks.AddV0Service(s)
|
retval.tasks.AddV0Service(s)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var schedule task.CronSchedule
|
var schedule task.CronSchedule
|
||||||
for _, c := range cfg.Cron {
|
for _, c := range cfg.CronV0 {
|
||||||
schedule.Minute = convertSchedule(c.Minute)
|
schedule.Minute = convertSchedule(c.Minute)
|
||||||
schedule.Hour = convertSchedule(c.Hour)
|
schedule.Hour = convertSchedule(c.Hour)
|
||||||
schedule.DoM = convertSchedule(c.DoM)
|
schedule.DoM = convertSchedule(c.DoM)
|
||||||
|
|||||||
@ -11,38 +11,44 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EnvPrefix = "WINGMATE"
|
EnvPrefix = "WINGMATE"
|
||||||
EnvConfigPath = "CONFIG_PATH"
|
EnvConfigPath = "CONFIG_PATH"
|
||||||
DefaultConfigPath = "/etc/wingmate"
|
DefaultConfigPath = "/etc/wingmate"
|
||||||
ServiceDirName = "service"
|
ServiceDirName = "service"
|
||||||
CrontabFileName = "crontab"
|
CrontabFileName = "crontab"
|
||||||
|
WingmateConfigFileName = "wingmate"
|
||||||
|
WingmateConfigFileFormat = "yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ServicePaths []string
|
ServiceV0 []string
|
||||||
Cron []*Cron
|
CronV0 []*Cron
|
||||||
|
Service []ServiceTask
|
||||||
|
Cron []CronTask
|
||||||
}
|
}
|
||||||
|
|
||||||
type Task struct {
|
type Task struct {
|
||||||
Command []string `yaml:"command"`
|
Command []string `mapstructure:"command"`
|
||||||
Environ []string `yaml:"environ"`
|
Environ []string `mapstructure:"environ"`
|
||||||
Setsid bool `yaml:"setsid"`
|
Setsid bool `mapstructure:"setsid"`
|
||||||
User string `yaml:"user"`
|
User string `mapstructure:"user"`
|
||||||
Group string `yaml:"group"`
|
Group string `mapstructure:"group"`
|
||||||
Background bool `yaml:"background"`
|
Background bool `mapstructure:"background"`
|
||||||
WorkingDir string `yaml:"working_dir"`
|
WorkingDir string `mapstructure:"working_dir"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServiceTask struct {
|
type ServiceTask struct {
|
||||||
Task `yaml:",inline"`
|
Name string `mapstructure:"-"`
|
||||||
AutoStart bool `yaml:"autostart"`
|
Task `mapstructure:",squash"`
|
||||||
AutoRestart bool `yaml:"autorestart"`
|
AutoStart bool `mapstructure:"autostart"`
|
||||||
|
AutoRestart bool `mapstructure:"autorestart"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CronTask struct {
|
type CronTask struct {
|
||||||
CronSchedule `yaml:"-"`
|
Name string `mapstructure:"-"`
|
||||||
Task `yaml:",inline"`
|
CronSchedule `mapstructure:"-"`
|
||||||
Schedule string `yaml:"schedule"`
|
Task `mapstructure:",squash"`
|
||||||
|
Schedule string `mapstructure:"schedule"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CronSchedule struct {
|
type CronSchedule struct {
|
||||||
@ -59,19 +65,22 @@ func Read() (*Config, error) {
|
|||||||
viper.SetDefault(EnvConfigPath, DefaultConfigPath)
|
viper.SetDefault(EnvConfigPath, DefaultConfigPath)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dirent []os.DirEntry
|
dirent []os.DirEntry
|
||||||
err error
|
err error
|
||||||
svcdir string
|
svcdir string
|
||||||
serviceAvailable bool
|
serviceAvailable bool
|
||||||
cronAvailable bool
|
cronAvailable bool
|
||||||
cron []*Cron
|
wingmateConfigAvailable bool
|
||||||
crontabfile string
|
cron []*Cron
|
||||||
|
crontabfile string
|
||||||
|
services []ServiceTask
|
||||||
|
crones []CronTask
|
||||||
)
|
)
|
||||||
|
|
||||||
serviceAvailable = false
|
serviceAvailable = false
|
||||||
cronAvailable = false
|
cronAvailable = false
|
||||||
outConfig := &Config{
|
outConfig := &Config{
|
||||||
ServicePaths: make([]string, 0),
|
ServiceV0: make([]string, 0),
|
||||||
}
|
}
|
||||||
configPath := viper.GetString(EnvConfigPath)
|
configPath := viper.GetString(EnvConfigPath)
|
||||||
svcdir = filepath.Join(configPath, ServiceDirName)
|
svcdir = filepath.Join(configPath, ServiceDirName)
|
||||||
@ -82,7 +91,7 @@ func Read() (*Config, error) {
|
|||||||
svcPath := filepath.Join(svcdir, d.Name())
|
svcPath := filepath.Join(svcdir, d.Name())
|
||||||
if err = unix.Access(svcPath, unix.X_OK); err == nil {
|
if err = unix.Access(svcPath, unix.X_OK); err == nil {
|
||||||
serviceAvailable = true
|
serviceAvailable = true
|
||||||
outConfig.ServicePaths = append(outConfig.ServicePaths, svcPath)
|
outConfig.ServiceV0 = append(outConfig.ServiceV0, svcPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,14 +103,27 @@ func Read() (*Config, error) {
|
|||||||
crontabfile = filepath.Join(configPath, CrontabFileName)
|
crontabfile = filepath.Join(configPath, CrontabFileName)
|
||||||
cron, err = readCrontab(crontabfile)
|
cron, err = readCrontab(crontabfile)
|
||||||
if len(cron) > 0 {
|
if len(cron) > 0 {
|
||||||
outConfig.Cron = cron
|
outConfig.CronV0 = cron
|
||||||
cronAvailable = true
|
cronAvailable = true
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wingmate.Log().Error().Msgf("encounter error when reading crontab %s: %+v", crontabfile, err)
|
wingmate.Log().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 nil, errors.New("no config found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -66,7 +66,7 @@ func TestRead(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.ElementsMatch(
|
assert.ElementsMatch(
|
||||||
t,
|
t,
|
||||||
cfg.ServicePaths,
|
cfg.ServiceV0,
|
||||||
[]string{
|
[]string{
|
||||||
path.Join(configDir, serviceDir, "one.sh"),
|
path.Join(configDir, serviceDir, "one.sh"),
|
||||||
path.Join(configDir, serviceDir, "two.sh"),
|
path.Join(configDir, serviceDir, "two.sh"),
|
||||||
@ -86,7 +86,7 @@ func TestRead(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.ElementsMatch(
|
assert.ElementsMatch(
|
||||||
t,
|
t,
|
||||||
cfg.ServicePaths,
|
cfg.ServiceV0,
|
||||||
[]string{
|
[]string{
|
||||||
path.Join(configDir, serviceDir, "two.sh"),
|
path.Join(configDir, serviceDir, "two.sh"),
|
||||||
},
|
},
|
||||||
@ -104,7 +104,7 @@ func TestRead(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.ElementsMatch(
|
assert.ElementsMatch(
|
||||||
t,
|
t,
|
||||||
cfg.ServicePaths,
|
cfg.ServiceV0,
|
||||||
[]string{
|
[]string{
|
||||||
path.Join(configDir, serviceDir, "one.sh"),
|
path.Join(configDir, serviceDir, "one.sh"),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -56,7 +56,7 @@ func TestCrontab(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("cfg: %+v", cfg)
|
t.Logf("cfg: %+v", cfg)
|
||||||
for _, c := range cfg.Cron {
|
for _, c := range cfg.CronV0 {
|
||||||
t.Logf("%+v", c)
|
t.Logf("%+v", c)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1 +1,61 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"gitea.suyono.dev/suyono/wingmate"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ServiceConfigGroup = "service"
|
||||||
|
CronConfigGroup = "cron"
|
||||||
|
ServiceKeyFormat = "service.%s"
|
||||||
|
CronKeyFormat = "cron.%s"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
crones = append(crones, cronTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
return services, crones, nil
|
||||||
|
}
|
||||||
|
|||||||
68
config/yaml_test.go
Normal file
68
config/yaml_test.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitea.suyono.dev/suyono/wingmate"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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"`
|
||||||
@ -3,7 +3,9 @@ FROM golang:1.21-alpine as builder
|
|||||||
ADD . /root/wingmate
|
ADD . /root/wingmate
|
||||||
WORKDIR /root/wingmate/
|
WORKDIR /root/wingmate/
|
||||||
ARG TEST_BUILD
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user