Prepare API
This commit is contained in:
parent
c85c4f3512
commit
81b65b42b6
24 changed files with 307 additions and 22 deletions
35
api/check_handler.go
Normal file
35
api/check_handler.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/baez90/nurse/check"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ http.Handler = (*CheckHandler)(nil)
|
||||||
|
|
||||||
|
type CheckHandler struct {
|
||||||
|
Timeout time.Duration
|
||||||
|
Check check.SystemChecker
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CheckHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
var (
|
||||||
|
ctx = request.Context()
|
||||||
|
cancel context.CancelFunc
|
||||||
|
)
|
||||||
|
if c.Timeout != 0 {
|
||||||
|
ctx, cancel = context.WithTimeout(ctx, c.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
if err := c.Check.Execute(ctx); err != nil {
|
||||||
|
writer.WriteHeader(http.StatusServiceUnavailable)
|
||||||
|
_, _ = writer.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteHeader(200)
|
||||||
|
return
|
||||||
|
}
|
31
api/mux.go
Normal file
31
api/mux.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/baez90/nurse/check"
|
||||||
|
"github.com/baez90/nurse/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PrepareMux(instance *config.Nurse, modLookup check.ModuleLookup, srvLookup config.ServerLookup) (http.Handler, error) {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
logger := zap.L()
|
||||||
|
|
||||||
|
for route, spec := range instance.Endpoints {
|
||||||
|
logger.Info("Configuring route", zap.String("route", route.String()))
|
||||||
|
chk, err := check.CheckForScript(spec.Checks, modLookup, srvLookup)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mux.Handle(route.String(), CheckHandler{
|
||||||
|
Timeout: spec.Timeout(instance.CheckTimeout),
|
||||||
|
Check: chk,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return mux, nil
|
||||||
|
}
|
|
@ -26,4 +26,12 @@ type (
|
||||||
CallUnmarshaler interface {
|
CallUnmarshaler interface {
|
||||||
UnmarshalCall(c grammar.Call) error
|
UnmarshalCall(c grammar.Call) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CheckerLookup interface {
|
||||||
|
Lookup(c grammar.Check, srvLookup config.ServerLookup) (SystemChecker, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
ModuleLookup interface {
|
||||||
|
Lookup(modName string) (CheckerLookup, error)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
31
check/collection.go
Normal file
31
check/collection.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package check
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
"github.com/baez90/nurse/config"
|
||||||
|
"github.com/baez90/nurse/grammar"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ SystemChecker = (Collection)(nil)
|
||||||
|
|
||||||
|
type Collection []SystemChecker
|
||||||
|
|
||||||
|
func (Collection) UnmarshalCheck(grammar.Check, config.ServerLookup) error {
|
||||||
|
panic("unmarshalling is not supported for a collection")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Collection) Execute(ctx context.Context) error {
|
||||||
|
grp, grpCtx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
|
for i := range c {
|
||||||
|
chk := c[i]
|
||||||
|
grp.Go(func() error {
|
||||||
|
return chk.Execute(grpCtx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return grp.Wait()
|
||||||
|
}
|
27
check/endpoint.go
Normal file
27
check/endpoint.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package check
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/baez90/nurse/config"
|
||||||
|
"github.com/baez90/nurse/grammar"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CheckForScript(script []grammar.Check, lkp ModuleLookup, srvLookup config.ServerLookup) (SystemChecker, error) {
|
||||||
|
compiledChecks := make([]SystemChecker, 0, len(script))
|
||||||
|
|
||||||
|
for i := range script {
|
||||||
|
rawChk := script[i]
|
||||||
|
mod, err := lkp.Lookup(rawChk.Initiator.Module)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
compiledCheck, err := mod.Lookup(rawChk, srvLookup)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
compiledChecks = append(compiledChecks, compiledCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collection(compiledChecks), nil
|
||||||
|
}
|
|
@ -37,8 +37,9 @@ func WithCheck(name string, factory Factory) ModuleOption {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewModule(opts ...ModuleOption) (*Module, error) {
|
func NewModule(name string, opts ...ModuleOption) (*Module, error) {
|
||||||
m := &Module{
|
m := &Module{
|
||||||
|
name: name,
|
||||||
knownChecks: make(map[string]Factory),
|
knownChecks: make(map[string]Factory),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,10 +53,15 @@ func NewModule(opts ...ModuleOption) (*Module, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Module struct {
|
type Module struct {
|
||||||
|
name string
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
knownChecks map[string]Factory
|
knownChecks map[string]Factory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Module) Name() string {
|
||||||
|
return m.name
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Module) Lookup(c grammar.Check, srvLookup config.ServerLookup) (SystemChecker, error) {
|
func (m *Module) Lookup(c grammar.Check, srvLookup config.ServerLookup) (SystemChecker, error) {
|
||||||
m.lock.RLock()
|
m.lock.RLock()
|
||||||
defer m.lock.RUnlock()
|
defer m.lock.RUnlock()
|
||||||
|
|
53
check/registry.go
Normal file
53
check/registry.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package check
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrModuleNameConflict = errors.New("module name conflict")
|
||||||
|
ErrNoSuchModule = errors.New("no module of given name known")
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewRegistry() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
mods: make(map[string]*Module),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
Registry struct {
|
||||||
|
lock sync.RWMutex
|
||||||
|
mods map[string]*Module
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *Registry) Register(module *Module) error {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
|
modName := strings.ToLower(module.Name())
|
||||||
|
|
||||||
|
if _, ok := r.mods[modName]; ok {
|
||||||
|
return fmt.Errorf("%w: %s", ErrModuleNameConflict, modName)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.mods[modName] = module
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) Lookup(modName string) (CheckerLookup, error) {
|
||||||
|
r.lock.RLock()
|
||||||
|
defer r.lock.RUnlock()
|
||||||
|
|
||||||
|
modName = strings.ToLower(modName)
|
||||||
|
|
||||||
|
if mod, ok := r.mods[modName]; !ok {
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrNoSuchModule, modName)
|
||||||
|
} else {
|
||||||
|
return mod, nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,6 +38,18 @@ type Nurse struct {
|
||||||
CheckTimeout time.Duration
|
CheckTimeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n Nurse) ServerLookup() (*ServerRegister, error) {
|
||||||
|
register := NewServerRegister()
|
||||||
|
|
||||||
|
for name, srv := range n.Servers {
|
||||||
|
if err := register.Register(name, srv); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return register, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Merge merges the current Nurse instance with another one
|
// Merge merges the current Nurse instance with another one
|
||||||
// giving the current instance precedence means no set value is overwritten
|
// giving the current instance precedence means no set value is overwritten
|
||||||
func (n Nurse) Merge(other Nurse) Nurse {
|
func (n Nurse) Merge(other Nurse) Nurse {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -10,8 +9,6 @@ import (
|
||||||
"github.com/baez90/nurse/grammar"
|
"github.com/baez90/nurse/grammar"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ encoding.TextUnmarshaler = (*EndpointSpec)(nil)
|
|
||||||
|
|
||||||
type Route string
|
type Route string
|
||||||
|
|
||||||
func (r Route) String() string {
|
func (r Route) String() string {
|
||||||
|
@ -26,17 +23,25 @@ type EndpointSpec struct {
|
||||||
Checks []grammar.Check
|
Checks []grammar.Check
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *EndpointSpec) UnmarshalText(text []byte) error {
|
func (s EndpointSpec) Timeout(fallback time.Duration) time.Duration {
|
||||||
|
if s.CheckTimeout != 0 {
|
||||||
|
return s.CheckTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EndpointSpec) Parse(text string) error {
|
||||||
parser, err := grammar.NewParser[grammar.Script]()
|
parser, err := grammar.NewParser[grammar.Script]()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
script, err := parser.Parse(string(text))
|
script, err := parser.Parse(text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Checks = script.Checks
|
s.Checks = script.Checks
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ func EndpointsFromEnv() (map[Route]EndpointSpec, error) {
|
||||||
|
|
||||||
endpointRoute := path.Join(Split(ToLower(Trim(Replace(key, EndpointKeyPrefix, "", -1), "_")), "_")...)
|
endpointRoute := path.Join(Split(ToLower(Trim(Replace(key, EndpointKeyPrefix, "", -1), "_")), "_")...)
|
||||||
spec := EndpointSpec{}
|
spec := EndpointSpec{}
|
||||||
if err := spec.UnmarshalText([]byte(value)); err != nil {
|
if err := spec.Parse(value); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ func TestEndpointsFromEnv(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: td.Map(make(map[config.Route]config.EndpointSpec), td.MapEntries{
|
want: td.Map(make(map[config.Route]config.EndpointSpec), td.MapEntries{
|
||||||
config.Route("readiness"): td.Struct(config.EndpointSpec{}, td.StructFields{
|
config.Route("readiness"): td.Struct(config.EndpointSpec{}, td.StructFields{
|
||||||
"Checks": td.Len(1),
|
"Script": td.Len(1),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
|
@ -107,7 +107,7 @@ func TestEndpointsFromEnv(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: td.Map(make(map[config.Route]config.EndpointSpec), td.MapEntries{
|
want: td.Map(make(map[config.Route]config.EndpointSpec), td.MapEntries{
|
||||||
config.Route("readiness"): td.Struct(config.EndpointSpec{}, td.StructFields{
|
config.Route("readiness"): td.Struct(config.EndpointSpec{}, td.StructFields{
|
||||||
"Checks": td.Len(2),
|
"Script": td.Len(2),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
|
@ -119,7 +119,7 @@ func TestEndpointsFromEnv(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: td.Map(make(map[config.Route]config.EndpointSpec), td.MapEntries{
|
want: td.Map(make(map[config.Route]config.EndpointSpec), td.MapEntries{
|
||||||
config.Route("readiness/redis"): td.Struct(config.EndpointSpec{}, td.StructFields{
|
config.Route("readiness/redis"): td.Struct(config.EndpointSpec{}, td.StructFields{
|
||||||
"Checks": td.Len(2),
|
"Script": td.Len(2),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
|
@ -132,10 +132,10 @@ func TestEndpointsFromEnv(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: td.Map(make(map[config.Route]config.EndpointSpec), td.MapEntries{
|
want: td.Map(make(map[config.Route]config.EndpointSpec), td.MapEntries{
|
||||||
config.Route("readiness"): td.Struct(config.EndpointSpec{}, td.StructFields{
|
config.Route("readiness"): td.Struct(config.EndpointSpec{}, td.StructFields{
|
||||||
"Checks": td.Len(1),
|
"Script": td.Len(1),
|
||||||
}),
|
}),
|
||||||
config.Route("liveness"): td.Struct(config.EndpointSpec{}, td.StructFields{
|
config.Route("liveness"): td.Struct(config.EndpointSpec{}, td.StructFields{
|
||||||
"Checks": td.Len(1),
|
"Script": td.Len(1),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -11,6 +11,7 @@ require (
|
||||||
github.com/testcontainers/testcontainers-go v0.13.0
|
github.com/testcontainers/testcontainers-go v0.13.0
|
||||||
go.uber.org/zap v1.21.0
|
go.uber.org/zap v1.21.0
|
||||||
golang.org/x/exp v0.0.0-20220428152302-39d4317da171
|
golang.org/x/exp v0.0.0-20220428152302-39d4317da171
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -82,6 +82,7 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd
|
||||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||||
|
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
@ -718,17 +719,15 @@ go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
|
||||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
|
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||||
|
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
||||||
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||||
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
|
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
||||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||||
|
@ -835,6 +834,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
package grammar
|
package grammar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ json.Unmarshaler = (*Check)(nil)
|
||||||
|
_ yaml.Unmarshaler = (*Check)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
type Call struct {
|
type Call struct {
|
||||||
Module string `parser:"(@Module'.')?"`
|
Module string `parser:"(@Module'.')?"`
|
||||||
Name string `parser:"@Ident"`
|
Name string `parser:"@Ident"`
|
||||||
|
@ -15,6 +26,34 @@ type Check struct {
|
||||||
Validators *Filters `parser:"( '=>' @@)?"`
|
Validators *Filters `parser:"( '=>' @@)?"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Check) UnmarshalYAML(value *yaml.Node) error {
|
||||||
|
parser, err := NewParser[Check]()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
chk, err := parser.Parse(value.Value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*c = *chk
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Check) UnmarshalJSON(bytes []byte) error {
|
||||||
|
parser, err := NewParser[Check]()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
chk, err := parser.ParseBytes(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*c = *chk
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Script struct {
|
type Script struct {
|
||||||
Checks []Check `parser:"(@@';'?)*"`
|
Checks []Check `parser:"(@@';'?)*"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,3 +49,12 @@ func (p Parser[T]) Parse(rawRule string) (*T, error) {
|
||||||
|
|
||||||
return into, nil
|
return into, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p Parser[T]) ParseBytes(data []byte) (*T, error) {
|
||||||
|
into := new(T)
|
||||||
|
if err := p.grammarParser.ParseBytes("", data, into); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return into, nil
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var wantParsedScript = td.Struct(new(grammar.Script), td.StructFields{
|
var wantParsedScript = td.Struct(new(grammar.Script), td.StructFields{
|
||||||
"Checks": td.Bag(
|
"Script": td.Bag(
|
||||||
grammar.Check{
|
grammar.Check{
|
||||||
Initiator: &grammar.Call{
|
Initiator: &grammar.Call{
|
||||||
Module: "http",
|
Module: "http",
|
||||||
|
|
35
main.go
35
main.go
|
@ -1,13 +1,18 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
|
"github.com/baez90/nurse/api"
|
||||||
|
"github.com/baez90/nurse/check"
|
||||||
"github.com/baez90/nurse/config"
|
"github.com/baez90/nurse/config"
|
||||||
|
"github.com/baez90/nurse/protocols/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -24,7 +29,7 @@ func main() {
|
||||||
|
|
||||||
logger := zap.L()
|
logger := zap.L()
|
||||||
|
|
||||||
envCfg, err := config.New(
|
nurseInstance, err := config.New(
|
||||||
config.WithValuesFrom(cfg),
|
config.WithValuesFrom(cfg),
|
||||||
config.WithConfigFile(cfgFile),
|
config.WithConfigFile(cfgFile),
|
||||||
config.WithServersFromEnv(),
|
config.WithServersFromEnv(),
|
||||||
|
@ -32,11 +37,33 @@ func main() {
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Failed to load config from environment", zap.Error(err))
|
logger.Fatal("Failed to load config from environment", zap.Error(err))
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug("Loaded config", zap.Any("config", envCfg))
|
logger.Debug("Loaded config", zap.Any("config", nurseInstance))
|
||||||
|
|
||||||
|
chkRegistry := check.NewRegistry()
|
||||||
|
if err := chkRegistry.Register(redis.Module()); err != nil {
|
||||||
|
logger.Fatal("Failed to register Redis module", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
srvLookup, err := nurseInstance.ServerLookup()
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Failed to prepare server lookup", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
mux, err := api.PrepareMux(nurseInstance, chkRegistry, srvLookup)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Failed to prepare server mux", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := http.ListenAndServe(":8080", mux); err != nil {
|
||||||
|
if errors.Is(err, http.ErrServerClosed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Fatal("Failed to serve HTTP", zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupLogging() {
|
func setupLogging() {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
func Module() *check.Module {
|
func Module() *check.Module {
|
||||||
m, _ := check.NewModule(
|
m, _ := check.NewModule(
|
||||||
|
"redis",
|
||||||
check.WithCheck("ping", check.FactoryFunc(func() check.SystemChecker {
|
check.WithCheck("ping", check.FactoryFunc(func() check.SystemChecker {
|
||||||
return new(PingCheck)
|
return new(PingCheck)
|
||||||
})),
|
})),
|
|
@ -12,7 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/baez90/nurse/config"
|
"github.com/baez90/nurse/config"
|
||||||
"github.com/baez90/nurse/grammar"
|
"github.com/baez90/nurse/grammar"
|
||||||
"github.com/baez90/nurse/redis"
|
"github.com/baez90/nurse/protocols/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestChecks_Execute(t *testing.T) {
|
func TestChecks_Execute(t *testing.T) {
|
Loading…
Reference in a new issue