preparation for v0.2.0 #3
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
|
||||
@ -1,7 +1,9 @@
|
||||
all:
|
||||
git describe > version.txt
|
||||
go build -v
|
||||
|
||||
clean:
|
||||
rm version.txt
|
||||
go clean -i -cache -testcache
|
||||
|
||||
install:
|
||||
|
||||
6
cmd/experiment/.gitignore
vendored
6
cmd/experiment/.gitignore
vendored
@ -1,4 +1,8 @@
|
||||
/dummy/dummy
|
||||
/dummy/version.txt
|
||||
/starter/starter
|
||||
/starter/version.txt
|
||||
/oneshot/oneshot
|
||||
/spawner/spawner
|
||||
/oneshot/version.txt
|
||||
/spawner/spawner
|
||||
/spawner/version.txt
|
||||
@ -1,7 +1,9 @@
|
||||
all:
|
||||
git describe > version.txt
|
||||
go build -v
|
||||
|
||||
clean:
|
||||
rm version.txt
|
||||
go clean -i -cache -testcache
|
||||
|
||||
install:
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
all:
|
||||
git describe > version.txt
|
||||
go build -v
|
||||
|
||||
clean:
|
||||
rm version.txt
|
||||
go clean -i -cache -testcache
|
||||
|
||||
install:
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
all:
|
||||
git describe > version.txt
|
||||
go build -v
|
||||
|
||||
clean:
|
||||
rm version.txt
|
||||
go clean -i -cache -testcache
|
||||
|
||||
install:
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
all:
|
||||
git describe > version.txt
|
||||
go build -v
|
||||
|
||||
clean:
|
||||
rm version.txt
|
||||
go clean -i -cache -testcache
|
||||
|
||||
install:
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
all:
|
||||
git describe > version.txt
|
||||
go build -v
|
||||
|
||||
clean:
|
||||
rm version.txt
|
||||
go clean -i -cache -testcache
|
||||
|
||||
install:
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
all:
|
||||
git describe > version.txt
|
||||
go build -v
|
||||
|
||||
clean:
|
||||
rm version.txt
|
||||
go clean -i -cache -testcache
|
||||
|
||||
install:
|
||||
|
||||
@ -19,13 +19,13 @@ func convert(cfg *config.Config) *wConfig {
|
||||
tasks: task.NewTasks(),
|
||||
}
|
||||
|
||||
for _, s := range cfg.ServicePaths {
|
||||
for _, s := range cfg.ServiceV0 {
|
||||
retval.tasks.AddV0Service(s)
|
||||
|
||||
}
|
||||
|
||||
var schedule task.CronSchedule
|
||||
for _, c := range cfg.Cron {
|
||||
for _, c := range cfg.CronV0 {
|
||||
schedule.Minute = convertSchedule(c.Minute)
|
||||
schedule.Hour = convertSchedule(c.Hour)
|
||||
schedule.DoM = convertSchedule(c.DoM)
|
||||
|
||||
@ -11,38 +11,44 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
EnvPrefix = "WINGMATE"
|
||||
EnvConfigPath = "CONFIG_PATH"
|
||||
DefaultConfigPath = "/etc/wingmate"
|
||||
ServiceDirName = "service"
|
||||
CrontabFileName = "crontab"
|
||||
EnvPrefix = "WINGMATE"
|
||||
EnvConfigPath = "CONFIG_PATH"
|
||||
DefaultConfigPath = "/etc/wingmate"
|
||||
ServiceDirName = "service"
|
||||
CrontabFileName = "crontab"
|
||||
WingmateConfigFileName = "wingmate"
|
||||
WingmateConfigFileFormat = "yaml"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ServicePaths []string
|
||||
Cron []*Cron
|
||||
ServiceV0 []string
|
||||
CronV0 []*Cron
|
||||
Service []ServiceTask
|
||||
Cron []CronTask
|
||||
}
|
||||
|
||||
type Task struct {
|
||||
Command []string `yaml:"command"`
|
||||
Environ []string `yaml:"environ"`
|
||||
Setsid bool `yaml:"setsid"`
|
||||
User string `yaml:"user"`
|
||||
Group string `yaml:"group"`
|
||||
Background bool `yaml:"background"`
|
||||
WorkingDir string `yaml:"working_dir"`
|
||||
Command []string `mapstructure:"command"`
|
||||
Environ []string `mapstructure:"environ"`
|
||||
Setsid bool `mapstructure:"setsid"`
|
||||
User string `mapstructure:"user"`
|
||||
Group string `mapstructure:"group"`
|
||||
Background bool `mapstructure:"background"`
|
||||
WorkingDir string `mapstructure:"working_dir"`
|
||||
}
|
||||
|
||||
type ServiceTask struct {
|
||||
Task `yaml:",inline"`
|
||||
AutoStart bool `yaml:"autostart"`
|
||||
AutoRestart bool `yaml:"autorestart"`
|
||||
Name string `mapstructure:"-"`
|
||||
Task `mapstructure:",squash"`
|
||||
AutoStart bool `mapstructure:"autostart"`
|
||||
AutoRestart bool `mapstructure:"autorestart"`
|
||||
}
|
||||
|
||||
type CronTask struct {
|
||||
CronSchedule `yaml:"-"`
|
||||
Task `yaml:",inline"`
|
||||
Schedule string `yaml:"schedule"`
|
||||
Name string `mapstructure:"-"`
|
||||
CronSchedule `mapstructure:"-"`
|
||||
Task `mapstructure:",squash"`
|
||||
Schedule string `mapstructure:"schedule"`
|
||||
}
|
||||
|
||||
type CronSchedule struct {
|
||||
@ -59,19 +65,22 @@ func Read() (*Config, error) {
|
||||
viper.SetDefault(EnvConfigPath, DefaultConfigPath)
|
||||
|
||||
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),
|
||||
ServiceV0: make([]string, 0),
|
||||
}
|
||||
configPath := viper.GetString(EnvConfigPath)
|
||||
svcdir = filepath.Join(configPath, ServiceDirName)
|
||||
@ -82,7 +91,7 @@ func Read() (*Config, error) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -94,14 +103,27 @@ func Read() (*Config, error) {
|
||||
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")
|
||||
}
|
||||
|
||||
|
||||
@ -66,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"),
|
||||
@ -86,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"),
|
||||
},
|
||||
@ -104,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"),
|
||||
},
|
||||
|
||||
@ -56,7 +56,7 @@ func TestCrontab(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Logf("cfg: %+v", cfg)
|
||||
for _, c := range cfg.Cron {
|
||||
for _, c := range cfg.CronV0 {
|
||||
t.Logf("%+v", c)
|
||||
}
|
||||
})
|
||||
|
||||
@ -1 +1,61 @@
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user