From c1103c714a053ca3080cc0b8589e43bdaca50813 Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Wed, 20 Nov 2024 00:35:04 +0300 Subject: [PATCH] allow to work with multiple configs Signed-off-by: Vasiliy Tolstov --- cmd/servicechecker/main.go | 63 ++----------------- go.mod | 4 +- go.sum | 8 ++- pkg/config/config.go | 120 +++++++++++++++++++++++++++++++------ 4 files changed, 116 insertions(+), 79 deletions(-) diff --git a/cmd/servicechecker/main.go b/cmd/servicechecker/main.go index 4e2ac34..0928ac9 100644 --- a/cmd/servicechecker/main.go +++ b/cmd/servicechecker/main.go @@ -3,10 +3,8 @@ package main import ( "context" "fmt" - "io/fs" "os" "os/signal" - "path/filepath" "time" openapi_v3 "github.com/google/gnostic/openapiv3" @@ -64,67 +62,14 @@ func main() { l.Fatal(ctx, "failed to init meter", err) } - meters["default"] = m - - l.Info(ctx, "try to parse config.yaml") - f, err := os.Open("config.yaml") - if err != nil { - l.Fatal(ctx, "failed to open config", err) - } + meters[uuid.Nil.String()] = m cfg := &config.Config{} - if err = config.Parse(f, cfg); err != nil { - f.Close() + l.Info(ctx, "try to load config") + if err := config.Load(config.Filesytem, "config.yaml", cfg); err != nil { l.Fatal(ctx, "failed to open config", err) } - f.Close() - - if cfg.App.ChecksDir != "" { - var configFiles []string - - if !filepath.IsAbs(cfg.App.ChecksDir) { - dir, _ := os.Getwd() - cfg.App.ChecksDir = filepath.Clean(filepath.Join(dir, cfg.App.ChecksDir)) - } - - err = filepath.WalkDir(cfg.App.ChecksDir, func(path string, info fs.DirEntry, err error) error { - if err != nil { - return err - } - - if info.IsDir() || !info.Type().IsRegular() { - return nil - } - - if filepath.Ext(info.Name()) != ".yaml" { - return nil - } - - configFiles = append(configFiles, path) - - return nil - }) - if err != nil { - l.Fatal(ctx, fmt.Sprintf("error loading config: %s", cfg.App.ChecksDir), err) - } - - for _, configFile := range configFiles { - l.Info(ctx, "try to parse "+configFile) - f, err := os.Open(configFile) - if err != nil { - l.Fatal(ctx, "failed to open config", err) - } - - checks := []*config.CheckConfig{} - if err = config.Parse(f, &checks); err != nil { - f.Close() - l.Fatal(ctx, "failed to open config", err) - } - f.Close() - cfg.App.Checks = append(cfg.App.Checks, checks...) - } - } if !cfg.App.MultiUser { m.Init( @@ -214,7 +159,7 @@ func main() { var mtr meter.Meter if !cfg.App.MultiUser { - mtr = meters["default"] + mtr = meters[uuid.Nil.String()] } else { if v, ok := meters[check.User]; ok && v != nil { mtr = v diff --git a/go.mod b/go.mod index 8b63217..e0d33ff 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( go.unistack.org/micro-codec-jsonpb/v3 v3.10.3 go.unistack.org/micro-codec-proto/v3 v3.10.2 go.unistack.org/micro-codec-yaml/v3 v3.10.2 + go.unistack.org/micro-config-file/v3 v3.8.10 go.unistack.org/micro-meter-victoriametrics/v3 v3.8.9 go.unistack.org/micro-proto/v3 v3.4.1 go.unistack.org/micro-server-http/v3 v3.11.34 @@ -20,6 +21,7 @@ require ( ) require ( + dario.cat/mergo v1.0.1 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect @@ -29,7 +31,7 @@ require ( golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/text v0.20.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f // indirect google.golang.org/grpc v1.68.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 4082fbf..1e96d97 100644 --- a/go.sum +++ b/go.sum @@ -593,6 +593,8 @@ cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vf cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= @@ -908,6 +910,8 @@ go.unistack.org/micro-codec-proto/v3 v3.10.2 h1:9iUQjBjTsd/RgIqB5rAQMZE0CYWngoW9 go.unistack.org/micro-codec-proto/v3 v3.10.2/go.mod h1:54e1jb6aLL9obJUwJjtVupE5zY4PugTcMSqWDhz9aC4= go.unistack.org/micro-codec-yaml/v3 v3.10.2 h1:02I9XzhaBHqZU8Vd5e2zhf8j4foJ4muPT/x4gdR6E4c= go.unistack.org/micro-codec-yaml/v3 v3.10.2/go.mod h1:A/tYj7x9CRhuin7WxeIvnuo8bMDrZYcJkogVYN8X7rU= +go.unistack.org/micro-config-file/v3 v3.8.10 h1:/IyD/i6I7Ic8jCNq7ZsTpWT8sToNG14gIFkSVPxbNpY= +go.unistack.org/micro-config-file/v3 v3.8.10/go.mod h1:w7uw5KxK3H2OrZwX4p0hQHbp9UzwDODYqJvdofySgxY= go.unistack.org/micro-meter-victoriametrics/v3 v3.8.9 h1:ZXCS0eFiSdvcFYxpxV2Q77gfwAjpIRydwAEI1QBrwuQ= go.unistack.org/micro-meter-victoriametrics/v3 v3.8.9/go.mod h1:xODJQ0Nu/F8k34D/z2ITL91OskI/C674XCkugAxmc3Q= go.unistack.org/micro-proto/v3 v3.4.1 h1:UTjLSRz2YZuaHk9iSlVqqsA50JQNAEK2ZFboGqtEa9Q= @@ -1202,8 +1206,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= 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= diff --git a/pkg/config/config.go b/pkg/config/config.go index 92640ee..a6ec4c8 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,21 +1,22 @@ package config import ( + "fmt" "io" + "io/fs" + "os" + "path/filepath" + "github.com/google/uuid" yamlcodec "go.unistack.org/micro-codec-yaml/v3" mtime "go.unistack.org/micro/v3/util/time" ) -type AppConfig struct { - ChecksDir string `json:"checks_dir,omitempty" yaml:"checks_dir,omitempty"` - Checks []*CheckConfig `json:"checks,omitempty" yaml:"checks,omitempty"` - MultiUser bool `json:"multi_user,omitempty" yaml:"multi_user,omitempty"` -} +var Filesytem fs.FS -type MeterConfig struct { - Addr string `json:"addr,omitempty" yaml:"addr,omitempty"` - Path string `json:"path,omitempty" yaml:"path,omitempty"` +func init() { + dir, _ := os.Getwd() + Filesytem = os.DirFS(dir) } type Config struct { @@ -23,13 +24,25 @@ type Config struct { Meter *MeterConfig `json:"meter,omitempty" yaml:"meter,omitempty"` } +type AppConfig struct { + ChecksFiles []string `json:"checks_files,omitempty" yaml:"checks_files,omitempty"` + Checks []*CheckConfig `json:"checks,omitempty" yaml:"checks,omitempty"` + MultiUser bool `json:"multi_user,omitempty" yaml:"multi_user,omitempty"` +} + +type MeterConfig struct { + Addr string `json:"addr,omitempty" yaml:"addr,omitempty"` + Path string `json:"path,omitempty" yaml:"path,omitempty"` +} + type CheckConfig struct { - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Tasks []*TaskConfig `json:"tasks,omitempty" yaml:"tasks,omitempty"` - Timeout mtime.Duration `json:"timeout,omitempty" yaml:"timeout,omitempty"` - Interval mtime.Duration `json:"interval,omitempty" yaml:"interval,omitempty"` - Active bool `json:"active,omitempty" yaml:"active,omitempty"` - User string `json:"user,omitempty" yaml:"user,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + User string `json:"user,omitempty" yaml:"user,omitempty"` + Tasks []*TaskConfig `json:"tasks,omitempty" yaml:"tasks,omitempty"` + TasksFiles []string `json:"tasks_files,omitempty" yaml:"tasks_files,omitempty"` + Timeout mtime.Duration `json:"timeout,omitempty" yaml:"timeout,omitempty"` + Interval mtime.Duration `json:"interval,omitempty" yaml:"interval,omitempty"` + Active bool `json:"active,omitempty" yaml:"active,omitempty"` } type HTTPConfig struct { @@ -66,11 +79,84 @@ type TaskConfig struct { Active bool `json:"active,omitempty" yaml:"active,omitempty"` } -func Parse(r io.Reader, cfg interface{}) error { - buf, err := io.ReadAll(r) +func Load(fileSystem fs.FS, name string, cfg *Config) error { + if err := load(fileSystem, name, cfg); err != nil { + return err + } + + for _, checksPatternFile := range cfg.App.ChecksFiles { + checkRoot := filepath.Dir(checksPatternFile) + checksFiles := fsWalkDir(fileSystem, checkRoot, checksPatternFile) + for _, checkFile := range checksFiles { + checks := []*CheckConfig{} + if err := load(fileSystem, checkFile, &checks); err != nil { + return err + } + for ckecksIdx := range checks { + for _, tasksPatternFile := range checks[ckecksIdx].TasksFiles { + taskRoot := filepath.Join(filepath.Dir(checksPatternFile), filepath.Dir(tasksPatternFile)) + tasksFiles := fsWalkDir(fileSystem, taskRoot, filepath.Join(filepath.Dir(checksPatternFile), tasksPatternFile)) + for tasksIdx := range tasksFiles { + tasks := []*TaskConfig{} + if err := load(fileSystem, tasksFiles[tasksIdx], &tasks); err != nil { + return err + } + checks[ckecksIdx].Tasks = append(checks[ckecksIdx].Tasks, tasks...) + } + } + cfg.App.Checks = append(cfg.App.Checks, checks[ckecksIdx]) + } + } + } + + if !cfg.App.MultiUser { + for _, check := range cfg.App.Checks { + check.User = uuid.Nil.String() + } + } + + return nil +} + +func fsWalkDir(fileSystem fs.FS, root string, pattern string) []string { + var files []string + fs.WalkDir(fileSystem, root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() || !d.Type().IsRegular() { + return nil + } + var ok bool + if ok, err = filepath.Match(pattern, path); err == nil && ok { + files = append(files, path) + } else { + return err + } + + return nil + }) + return files +} + +func load(fileSystem fs.FS, name string, cfg interface{}) error { + f, err := fileSystem.Open(name) if err != nil { return err } - return yamlcodec.NewCodec().Unmarshal(buf, cfg) + c := yamlcodec.NewCodec() + var buf []byte + + if buf, err = io.ReadAll(f); err == nil { + if err = f.Close(); err == nil { + err = c.Unmarshal(buf, cfg) + } + } + + if err != nil { + return fmt.Errorf("failed to load config %w", err) + } + + return nil }