wip: refactor(config): yaml parsed

wip: chore(makefile): prepare version info
This commit is contained in:
Suyono 2024-01-02 23:11:58 +11:00
parent 6a40403434
commit 6dd0a8007c
16 changed files with 213 additions and 40 deletions

5
.gitignore vendored
View File

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

View File

@ -1,7 +1,9 @@
all:
git describe > version.txt
go build -v
clean:
rm version.txt
go clean -i -cache -testcache
install:

View File

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

View File

@ -1,7 +1,9 @@
all:
git describe > version.txt
go build -v
clean:
rm version.txt
go clean -i -cache -testcache
install:

View File

@ -1,7 +1,9 @@
all:
git describe > version.txt
go build -v
clean:
rm version.txt
go clean -i -cache -testcache
install:

View File

@ -1,7 +1,9 @@
all:
git describe > version.txt
go build -v
clean:
rm version.txt
go clean -i -cache -testcache
install:

View File

@ -1,7 +1,9 @@
all:
git describe > version.txt
go build -v
clean:
rm version.txt
go clean -i -cache -testcache
install:

View File

@ -1,7 +1,9 @@
all:
git describe > version.txt
go build -v
clean:
rm version.txt
go clean -i -cache -testcache
install:

View File

@ -1,7 +1,9 @@
all:
git describe > version.txt
go build -v
clean:
rm version.txt
go clean -i -cache -testcache
install:

View File

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

View File

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

View File

@ -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"),
},

View File

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

View File

@ -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
View 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"`

View File

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