diff --git a/cmd/servicechecker/main.go b/cmd/servicechecker/main.go index 90bdb66..01ac0ac 100644 --- a/cmd/servicechecker/main.go +++ b/cmd/servicechecker/main.go @@ -59,14 +59,12 @@ func main() { l.Fatal(ctx, "failed to init meter", err) } - meters["default"] = m - f, err := os.Open("config.yaml") - if err != nil { - l.Fatal(ctx, "failed to open config", err) - } - defer f.Close() + meters[uuid.Nil.String()] = m + cfg := &config.Config{} - if err = cfg.Parse(f); err != nil { + + 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) } @@ -150,7 +148,7 @@ func main() { } clients["http"] = hcli - for _, check := range cfg.Checks { + for _, check := range cfg.App.Checks { l.Info(ctx, fmt.Sprintf("check %#+v", check)) if !check.Active { continue @@ -158,7 +156,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 @@ -183,6 +181,10 @@ func main() { fn, args, err = newGRPCTask(ctx, l, mtr, p, check.Name, task) case task.HTTP != nil: fn, args, err = newHTTPTask(ctx, l, mtr, check.Name, task) + case task.GraphQL != nil: + fn, args, err = newGraphQLTask(ctx, l, mtr, check.Name, task) + default: + err = fmt.Errorf("unknown task type") } if err != nil { l.Error(ctx, "failed to create task", err) @@ -375,3 +377,96 @@ func newGRPCTask(ctx context.Context, l logger.Logger, m meter.Meter, p *protose return fn, nil, nil } + +func newGraphQLTask(ctx context.Context, l logger.Logger, m meter.Meter, check string, task *config.TaskConfig) (any, []any, error) { + /* + var err error + + c, ok := clients["http"] + if !ok { + err = fmt.Errorf("unknown client http") + l.Error(ctx, "failed to get client", err) + return nil, nil, err + } + + var req interface{} + var rsp interface{} + var treq client.Request + var opts []client.CallOption + var labels []string + + if task.HTTP.OpenAPI != "" { + + openapiBuf, err := os.ReadFile(task.HTTP.OpenAPI) + if err != nil { + l.Error(ctx, "failed to unmarshal openapi file", err) + return nil, nil, err + } + + doc, err := openapi_v3.ParseDocument(openapiBuf) + if err != nil { + l.Error(ctx, "failed to unmarshal openapi file", err) + return nil, nil, err + } + _ = doc + + errmap := make(map[string]interface{}, 1) + errmap["default"] = &codecpb.Frame{} + opts = []client.CallOption{ + httpcli.ErrorMap(errmap), + httpcli.Method(task.HTTP.Method), + httpcli.Path(task.HTTP.Endpoint), + + // client.WithContentType("application/json"), + } + + req = &codecpb.Frame{Data: []byte(task.HTTP.Data)} + rsp = &codecpb.Frame{} + + treq = c.NewRequest(task.Name, task.Name, req) + + labels = []string{"check", check, "task", task.Name, "service", task.Name, "endpoint", task.Name} + } + + fn := func() { + var cerr error + + metadata := make(map[string]string, len(task.HTTP.Metadata)) + var rquid string + for k, v := range task.HTTP.Metadata { + if k == "x-request-id" && v == "generate" { + uid, err := uuid.NewV7() + if err != nil { + l.Error(ctx, "failed to generate x-request-id", err) + uid = uuid.Nil + } else { + v = uid.String() + } + } + metadata[k] = v + rquid = v + } + + l.Info(ctx, fmt.Sprintf("call %s.%s endpoint %s", treq.Service(), treq.Method(), treq.Endpoint()), "x-request-id", rquid) + m.Counter(semconv.ClientRequestInflight, labels...).Inc() + ts := time.Now() + cerr = httpconn.Call(ctx, rquid, l, c, task.HTTP.Addr, time.Duration(task.Timeout), + treq, + rsp, + append(opts, client.WithRequestMetadata(metadata))..., + ) + te := time.Since(ts) + m.Counter(semconv.ClientRequestInflight, labels...).Dec() + + m.Summary(semconv.ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds()) + m.Histogram(semconv.ClientRequestDurationSeconds, labels...).Update(te.Seconds()) + + if cerr != nil { + m.Counter(semconv.ClientRequestTotal, append(labels, "status", "failure")...).Inc() + } else { + m.Counter(semconv.ClientRequestTotal, append(labels, "status", "success")...).Inc() + } + } + */ + return nil, nil, nil +} diff --git a/go.mod b/go.mod index 3094e5c..8bef879 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,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 @@ -21,6 +22,7 @@ require ( ) require ( + dario.cat/mergo v1.0.1 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect @@ -33,7 +35,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 7b09e37..cb59d68 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= @@ -916,6 +918,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= @@ -1210,8 +1214,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 a1c9007..a6ec4c8 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,14 +1,33 @@ 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" ) +var Filesytem fs.FS + +func init() { + dir, _ := os.Getwd() + Filesytem = os.DirFS(dir) +} + +type Config struct { + App *AppConfig `json:"app,omitempty" yaml:"app,omitempty"` + Meter *MeterConfig `json:"meter,omitempty" yaml:"meter,omitempty"` +} + type AppConfig struct { - MultiUser bool `json:"multi_user,omitempty" yaml:"multi_user,omitempty"` + 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 { @@ -16,19 +35,14 @@ type MeterConfig struct { Path string `json:"path,omitempty" yaml:"path,omitempty"` } -type Config struct { - App *AppConfig `json:"app,omitempty" yaml:"app,omitempty"` - Meter *MeterConfig `json:"meter,omitempty" yaml:"meter,omitempty"` - Checks []*CheckConfig `json:"checks,omitempty" yaml:"checks,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 { @@ -65,11 +79,84 @@ type TaskConfig struct { Active bool `json:"active,omitempty" yaml:"active,omitempty"` } -func (cfg *Config) Parse(r io.Reader) 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 } diff --git a/pkg/grpcconn/protoset.go b/pkg/grpcconn/protoset.go index b17b68c..c6ca835 100644 --- a/pkg/grpcconn/protoset.go +++ b/pkg/grpcconn/protoset.go @@ -3,8 +3,8 @@ package grpcconn import ( - "github.com/emicklei/proto" "github.com/jhump/protoreflect/desc" + "google.golang.org/protobuf/proto" ) var protoSets = map[string]*desc.FileDescriptor