diff --git a/Makefile b/Makefile index 8a85ed87..115307fe 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ create-alias: @go run -race main.go create $(alias) install-linter: - @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)bin v1.43.0 + @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)bin v1.44.2 install-swagger-gen: @go get -d github.com/swaggo/swag/cmd/swag diff --git a/cmd/internal/internal.go b/cmd/internal/internal.go index 6c4647c6..e842f774 100644 --- a/cmd/internal/internal.go +++ b/cmd/internal/internal.go @@ -6,6 +6,7 @@ import ( "net/url" "time" + "github.com/InVisionApp/go-health/v2" "github.com/gorilla/websocket" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" @@ -13,6 +14,7 @@ import ( "github.com/libsv/payd/config" paydSQL "github.com/libsv/payd/data/sqlite" "github.com/libsv/payd/docs" + "github.com/libsv/payd/dpp" "github.com/libsv/payd/log" "github.com/libsv/payd/service" thttp "github.com/libsv/payd/transports/http" @@ -107,8 +109,16 @@ func SetupSocketServer(cfg config.Socket, e *echo.Echo) *server.SocketServer { } // SetupHealthEndpoint setup the health check. -func SetupHealthEndpoint(cfg config.Config, g *echo.Group, c *client.Client) { - thttp.NewHealthHandler(service.NewHealthService(c, cfg.P4)).RegisterRoutes(g) +func SetupHealthEndpoint(cfg config.Config, g *echo.Group, c *client.Client, deps *SocketDeps) error { + h := health.New() + + if err := dpp.NewHealthCheck(h, c, deps.InvoiceService, deps.ConnectService, cfg.P4).Start(); err != nil { + return errors.Wrap(err, "failed to start dpp health check") + } + + thttp.NewHealthHandler(service.NewHealthService(h)).RegisterRoutes(g) + + return errors.Wrap(h.Start(), "failed to start health checker") } // ResumeActiveChannels resume listening to active peer channels. @@ -142,13 +152,13 @@ func ResumeSocketConnections(deps *SocketDeps, cfg *config.P4) error { } ctx := context.Background() - invoices, err := deps.InvoiceService.Invoices(ctx) + invoices, err := deps.InvoiceService.InvoicesPending(ctx) if err != nil { return errors.Wrap(err, "failed to retrieve invoices") } for _, invoice := range invoices { - if time.Now().UTC().Unix() <= invoice.ExpiresAt.Time.UTC().Unix() && invoice.State == payd.StateInvoicePending { + if time.Now().UTC().Unix() <= invoice.ExpiresAt.Time.UTC().Unix() { if err := deps.ConnectService.Connect(ctx, payd.ConnectArgs{ InvoiceID: invoice.ID, }); err != nil { diff --git a/cmd/server/main.go b/cmd/server/main.go index f3628fa2..ede8ce42 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -96,7 +96,9 @@ func main() { internal.SetupSocketClient(*cfg, deps, c) // setup socket endpoints internal.SetupSocketHTTPEndpoints(*cfg.Deployment, deps, g) - internal.SetupHealthEndpoint(*cfg, g, c) + if err := internal.SetupHealthEndpoint(*cfg, g, c, deps); err != nil { + log.Fatal(err, "failed to create health checks") + } if err := internal.ResumeActiveChannels(deps); err != nil { log.Fatal(err, "failed to resume active peer channels") diff --git a/data/sqlite/invoice.go b/data/sqlite/invoice.go index db54c81e..5537accc 100644 --- a/data/sqlite/invoice.go +++ b/data/sqlite/invoice.go @@ -31,6 +31,12 @@ const ( WHERE state != 'deleted' ` + sqlPendingInvoices = ` + SELECT invoice_id, satoshis, description, spv_required, payment_reference, payment_received_at, expires_at, state, refund_to, refunded_at, created_at, updated_at, deleted_at + FROM invoices + WHERE state == 'pending' + ` + // TODO - sort updates when working on rest of Invoice API. sqlInvoiceUpdate = ` UPDATE invoices @@ -69,6 +75,18 @@ func (s *sqliteStore) Invoices(ctx context.Context) ([]payd.Invoice, error) { return resp, nil } +// InvoicesPending will return any invoices that have the status 'pending`. +func (s *sqliteStore) InvoicesPending(ctx context.Context) ([]payd.Invoice, error) { + var resp []payd.Invoice + if err := s.db.SelectContext(ctx, &resp, sqlPendingInvoices); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, lathos.NewErrNotFound(errcodes.ErrInvoicesNotFound, "no invoices found") + } + return nil, errors.Wrapf(err, "failed to get invoices") + } + return resp, nil +} + // Create will persist a new Invoice in the data store. func (s *sqliteStore) InvoiceCreate(ctx context.Context, req payd.InvoiceCreate) (*payd.Invoice, error) { tx, err := s.newTx(ctx) diff --git a/dpp/healthcheck.go b/dpp/healthcheck.go new file mode 100644 index 00000000..f3cbd050 --- /dev/null +++ b/dpp/healthcheck.go @@ -0,0 +1,143 @@ +package dpp + +import ( + "context" + "net/url" + "time" + + "github.com/InVisionApp/go-health/v2" + "github.com/libsv/payd" + "github.com/libsv/payd/config" + "github.com/pkg/errors" + "github.com/theflyingcodr/lathos" + "github.com/theflyingcodr/sockets" + "github.com/theflyingcodr/sockets/client" +) + +type healthCheck struct { + h health.IHealth + c *client.Client + cfg *config.P4 + invSvc payd.InvoiceService + connSvc payd.ConnectService +} + +// NewHealthCheck return a new DPP health check. +func NewHealthCheck(h health.IHealth, c *client.Client, invSvc payd.InvoiceService, connSvc payd.ConnectService, cfg *config.P4) payd.HealthCheck { + return &healthCheck{ + h: h, + c: c, + cfg: cfg, + invSvc: invSvc, + connSvc: connSvc, + } +} + +// Start the health check. +func (h *healthCheck) Start() error { + u, err := url.Parse(h.cfg.ServerHost) + if err != nil { + return err + } + if u.Scheme != "ws" && u.Scheme != "wss" { + return nil + } + + if err := h.commsCheck(); err != nil { + return errors.Wrap(err, "failed to start comms health check") + } + if err := h.channelCheck(); err != nil { + return errors.Wrap(err, "failed to start channel health check") + } + return nil +} + +func (h *healthCheck) commsCheck() error { + if err := h.h.AddCheck(&health.Config{ + Name: "p4-comms", + Checker: &commsCheck{ + c: h.c, + host: h.cfg.ServerHost, + }, + Interval: time.Duration(2) * time.Second, + }); err != nil { + return errors.Wrap(err, "failed to create p4-comms healthcheck") + } + if err := h.h.AddCheck(&health.Config{ + Name: "p4-channel-conn", + Checker: &channelCheck{ + c: h.c, + host: h.cfg.ServerHost, + invSvc: h.invSvc, + connSvc: h.connSvc, + }, + Interval: time.Duration(10) * time.Second, + }); err != nil { + return errors.Wrap(err, "failed to create p4-channel-conn healthcheck") + } + return nil +} + +func (h *healthCheck) channelCheck() error { + return nil +} + +type commsCheck struct { + c *client.Client + host string +} + +// Status of communication. +func (ch *commsCheck) Status() (interface{}, error) { + if err := ch.c.JoinChannel(ch.host, "health", nil, map[string]string{ + "internal": "true", + }); err != nil { + return nil, errors.Wrap(err, "failed to join p4 health channel") + } + if err := ch.c.Publish(sockets.Request{ + ChannelID: "health", + MessageKey: "my-p4", + Body: "ping", + }); err != nil { + return nil, errors.Wrap(err, "failed to ping p4") + } + ch.c.LeaveChannel("health", nil) + return nil, nil +} + +type channelCheck struct { + c *client.Client + host string + invSvc payd.InvoiceService + connSvc payd.ConnectService +} + +// Status of channels. +func (ch *channelCheck) Status() (interface{}, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*2) + defer cancel() + + invoices, err := ch.invSvc.InvoicesPending(context.Background()) + if err != nil { + if lathos.IsNotFound(err) { + return nil, nil + } + return nil, errors.Wrap(err, "failed to get invoices for channel check") + } + for _, invoice := range invoices { + if invoice.ExpiresAt.Time.UTC().Before(time.Now().UTC()) { + continue + } + if ch.c.HasChannel(invoice.ID) { + continue + } + + if err := ch.connSvc.Connect(ctx, payd.ConnectArgs{ + InvoiceID: invoice.ID, + }); err != nil { + return nil, errors.Wrapf(err, "failed reconnecting to channel for invoice '%s'", invoice.ID) + } + + } + return nil, nil +} diff --git a/go.mod b/go.mod index dfb2036d..5ff2fda6 100644 --- a/go.mod +++ b/go.mod @@ -33,10 +33,11 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/libsv/go-bc v0.1.8 github.com/rs/zerolog v1.26.1 - github.com/theflyingcodr/sockets v0.0.11-beta.0.20220222160101-76100ef886b5 + github.com/theflyingcodr/sockets v0.0.11-beta.0.20220225103542-c6eecb16f586 ) require ( + github.com/InVisionApp/go-logger v1.0.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect @@ -90,6 +91,7 @@ require ( ) require ( + github.com/InVisionApp/go-health/v2 v2.1.2 github.com/libsv/go-p4 v0.0.8 github.com/libsv/go-spvchannels v0.0.1 ) diff --git a/go.sum b/go.sum index 02b8915f..ddbfb72b 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,13 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go v3.7.1+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/InVisionApp/go-health/v2 v2.1.2 h1:rWTIgU3XdMTn/oBJgIrCnrso1pHcI65biN+CUOpknq0= +github.com/InVisionApp/go-health/v2 v2.1.2/go.mod h1:Iz2FZRfK3sJecRvGCIgyBsKOjILdKTdLGiGFaO+JDYc= +github.com/InVisionApp/go-logger v1.0.1 h1:WFL19PViM1mHUmUWfsv5zMo379KSWj2MRmBlzMFDRiE= +github.com/InVisionApp/go-logger v1.0.1/go.mod h1:+cGTDSn+P8105aZkeOfIhdd7vFO5X1afUHcjvanY0L8= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= @@ -70,6 +75,7 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/afex/hystrix-go v0.0.0-20180209013831-27fae8d30f1a/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs= github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= @@ -78,6 +84,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/arrow v0.0.0-20200601151325-b2287a20f230/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0= github.com/apache/arrow/go/arrow v0.0.0-20210521153258-78c88a9f517b/go.mod h1:R4hW3Ug0s+n4CUsWHKOj00Pu01ZqU4x/hSF5kXUcXKQ= @@ -124,8 +132,10 @@ github.com/bitcoinsv/bsvutil v0.0.0-20181216182056-1d77cf353ea9/go.mod h1:p44KuN github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI= github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -196,6 +206,7 @@ github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWp github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -205,6 +216,7 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -215,6 +227,7 @@ github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= @@ -294,6 +307,7 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= @@ -387,6 +401,7 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -574,8 +589,11 @@ github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1: github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= @@ -647,6 +665,7 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -657,6 +676,7 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= @@ -707,8 +727,8 @@ github.com/theflyingcodr/lathos v0.0.6 h1:xIHMZTinurvodmFOgvSGD+OrDhSj42+Xz+FOXY github.com/theflyingcodr/lathos v0.0.6/go.mod h1:68tGFEbAqAzydWDb1KEJZPQY57l3hH32GXO11Hf1zGQ= github.com/theflyingcodr/migrate/v4 v4.15.1-0.20210927160112-79da889ca18e h1:gfOQ8DVRKwc97bXeR6I6ogosWhFi4mredpUtZcx/gvg= github.com/theflyingcodr/migrate/v4 v4.15.1-0.20210927160112-79da889ca18e/go.mod h1:g9qbiDvB47WyrRnNu2t2gMZFNHKnatsYRxsGZbCi4EM= -github.com/theflyingcodr/sockets v0.0.11-beta.0.20220222160101-76100ef886b5 h1:39/z+O7p2ND6GgvOHZAr7o1gjm6UGls8FCcki8hbXRE= -github.com/theflyingcodr/sockets v0.0.11-beta.0.20220222160101-76100ef886b5/go.mod h1:u4PMKd3yqHkt9Jn0VgQRZ33PG9ynL8/j53csVO1huyk= +github.com/theflyingcodr/sockets v0.0.11-beta.0.20220225103542-c6eecb16f586 h1:iLfIixptGNYjsK5nxMWro9693mjKt3mzux20409EHzg= +github.com/theflyingcodr/sockets v0.0.11-beta.0.20220225103542-c6eecb16f586/go.mod h1:u4PMKd3yqHkt9Jn0VgQRZ33PG9ynL8/j53csVO1huyk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tonicpow/go-minercraft v0.4.0 h1:o7ndFm0NDIWZ6ml5qXGzGIh7xhGDWQhQixjPCJec2PY= github.com/tonicpow/go-minercraft v0.4.0/go.mod h1:+mJZAtlRy89vbL/gLAH4kft46lxueHUGMhsBJF2E9Fg= @@ -732,6 +752,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= +github.com/zaffka/mongodb-boltdb-mock v0.0.0-20180816124423-49954d88fa3e/go.mod h1:GsDD1qsG+86MeeCG7ndi6Ei3iGthKL3wQ7PTFigDfNY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= @@ -920,6 +942,7 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1255,6 +1278,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/guregu/null.v3 v3.5.0 h1:xTcasT8ETfMcUHn0zTvIYtQud/9Mx5dJqD554SZct0o= gopkg.in/guregu/null.v3 v3.5.0/go.mod h1:E4tX2Qe3h7QdL+uZ3a0vqvYwKQsRSQKM5V4YltdgH9Y= @@ -1262,6 +1286,7 @@ gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:a gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/health.go b/health.go index 4bb170d8..bc6671d3 100644 --- a/health.go +++ b/health.go @@ -2,7 +2,12 @@ package payd import "context" -// HealthService for checking health. +// HealthService for checking the overall health of payd. type HealthService interface { Health(ctx context.Context) error } + +// HealthCheck for checking the health of a specific component. +type HealthCheck interface { + Start() error +} diff --git a/invoices.go b/invoices.go index 79d029c7..364ca158 100644 --- a/invoices.go +++ b/invoices.go @@ -125,6 +125,7 @@ func (i *InvoiceArgs) Validate() error { type InvoiceService interface { Invoice(ctx context.Context, args InvoiceArgs) (*Invoice, error) Invoices(ctx context.Context) ([]Invoice, error) + InvoicesPending(ctx context.Context) ([]Invoice, error) Create(ctx context.Context, req InvoiceCreate) (*Invoice, error) Delete(ctx context.Context, args InvoiceArgs) error } @@ -152,4 +153,5 @@ type InvoiceReader interface { Invoice(ctx context.Context, args InvoiceArgs) (*Invoice, error) // Invoices returns all currently stored invoices TODO: update to support search args Invoices(ctx context.Context) ([]Invoice, error) + InvoicesPending(ctx context.Context) ([]Invoice, error) } diff --git a/mocks/invoice_reader_writer.go b/mocks/invoice_reader_writer.go index 7fa59cf3..f3fc123b 100644 --- a/mocks/invoice_reader_writer.go +++ b/mocks/invoice_reader_writer.go @@ -34,6 +34,9 @@ var _ payd.InvoiceReaderWriter = &InvoiceReaderWriterMock{} // InvoicesFunc: func(ctx context.Context) ([]payd.Invoice, error) { // panic("mock out the Invoices method") // }, +// InvoicesPendingFunc: func(ctx context.Context) ([]payd.Invoice, error) { +// panic("mock out the InvoicesPending method") +// }, // } // // // use mockedInvoiceReaderWriter in code that requires payd.InvoiceReaderWriter @@ -56,6 +59,9 @@ type InvoiceReaderWriterMock struct { // InvoicesFunc mocks the Invoices method. InvoicesFunc func(ctx context.Context) ([]payd.Invoice, error) + // InvoicesPendingFunc mocks the InvoicesPending method. + InvoicesPendingFunc func(ctx context.Context) ([]payd.Invoice, error) + // calls tracks calls to the methods. calls struct { // Invoice holds details about calls to the Invoice method. @@ -93,12 +99,18 @@ type InvoiceReaderWriterMock struct { // Ctx is the ctx argument value. Ctx context.Context } + // InvoicesPending holds details about calls to the InvoicesPending method. + InvoicesPending []struct { + // Ctx is the ctx argument value. + Ctx context.Context + } } - lockInvoice sync.RWMutex - lockInvoiceCreate sync.RWMutex - lockInvoiceDelete sync.RWMutex - lockInvoiceUpdate sync.RWMutex - lockInvoices sync.RWMutex + lockInvoice sync.RWMutex + lockInvoiceCreate sync.RWMutex + lockInvoiceDelete sync.RWMutex + lockInvoiceUpdate sync.RWMutex + lockInvoices sync.RWMutex + lockInvoicesPending sync.RWMutex } // Invoice calls InvoiceFunc. @@ -275,3 +287,34 @@ func (mock *InvoiceReaderWriterMock) InvoicesCalls() []struct { mock.lockInvoices.RUnlock() return calls } + +// InvoicesPending calls InvoicesPendingFunc. +func (mock *InvoiceReaderWriterMock) InvoicesPending(ctx context.Context) ([]payd.Invoice, error) { + if mock.InvoicesPendingFunc == nil { + panic("InvoiceReaderWriterMock.InvoicesPendingFunc: method is nil but InvoiceReaderWriter.InvoicesPending was just called") + } + callInfo := struct { + Ctx context.Context + }{ + Ctx: ctx, + } + mock.lockInvoicesPending.Lock() + mock.calls.InvoicesPending = append(mock.calls.InvoicesPending, callInfo) + mock.lockInvoicesPending.Unlock() + return mock.InvoicesPendingFunc(ctx) +} + +// InvoicesPendingCalls gets all the calls that were made to InvoicesPending. +// Check the length with: +// len(mockedInvoiceReaderWriter.InvoicesPendingCalls()) +func (mock *InvoiceReaderWriterMock) InvoicesPendingCalls() []struct { + Ctx context.Context +} { + var calls []struct { + Ctx context.Context + } + mock.lockInvoicesPending.RLock() + calls = mock.calls.InvoicesPending + mock.lockInvoicesPending.RUnlock() + return calls +} diff --git a/mocks/private_key_service.go b/mocks/private_key_service.go index e61b0a1e..cae3ed33 100644 --- a/mocks/private_key_service.go +++ b/mocks/private_key_service.go @@ -20,10 +20,10 @@ var _ payd.PrivateKeyService = &PrivateKeyServiceMock{} // // // make and configure a mocked payd.PrivateKeyService // mockedPrivateKeyService := &PrivateKeyServiceMock{ -// CreateFunc: func(ctx context.Context, keyName string) error { +// CreateFunc: func(ctx context.Context, keyName string, userID uint64) error { // panic("mock out the Create method") // }, -// PrivateKeyFunc: func(ctx context.Context, keyName string) (*bip32.ExtendedKey, error) { +// PrivateKeyFunc: func(ctx context.Context, keyName string, userID uint64) (*bip32.ExtendedKey, error) { // panic("mock out the PrivateKey method") // }, // } @@ -47,6 +47,8 @@ type PrivateKeyServiceMock struct { Ctx context.Context // KeyName is the keyName argument value. KeyName string + // UserID is the userID argument value. + UserID uint64 } // PrivateKey holds details about calls to the PrivateKey method. PrivateKey []struct { @@ -54,6 +56,8 @@ type PrivateKeyServiceMock struct { Ctx context.Context // KeyName is the keyName argument value. KeyName string + // UserID is the userID argument value. + UserID uint64 } } lockCreate sync.RWMutex @@ -68,9 +72,11 @@ func (mock *PrivateKeyServiceMock) Create(ctx context.Context, keyName string, u callInfo := struct { Ctx context.Context KeyName string + UserID uint64 }{ Ctx: ctx, KeyName: keyName, + UserID: userID, } mock.lockCreate.Lock() mock.calls.Create = append(mock.calls.Create, callInfo) @@ -84,10 +90,12 @@ func (mock *PrivateKeyServiceMock) Create(ctx context.Context, keyName string, u func (mock *PrivateKeyServiceMock) CreateCalls() []struct { Ctx context.Context KeyName string + UserID uint64 } { var calls []struct { Ctx context.Context KeyName string + UserID uint64 } mock.lockCreate.RLock() calls = mock.calls.Create @@ -103,9 +111,11 @@ func (mock *PrivateKeyServiceMock) PrivateKey(ctx context.Context, keyName strin callInfo := struct { Ctx context.Context KeyName string + UserID uint64 }{ Ctx: ctx, KeyName: keyName, + UserID: userID, } mock.lockPrivateKey.Lock() mock.calls.PrivateKey = append(mock.calls.PrivateKey, callInfo) @@ -119,10 +129,12 @@ func (mock *PrivateKeyServiceMock) PrivateKey(ctx context.Context, keyName strin func (mock *PrivateKeyServiceMock) PrivateKeyCalls() []struct { Ctx context.Context KeyName string + UserID uint64 } { var calls []struct { Ctx context.Context KeyName string + UserID uint64 } mock.lockPrivateKey.RLock() calls = mock.calls.PrivateKey diff --git a/service/health.go b/service/health.go index cc0fa181..aa235d6f 100644 --- a/service/health.go +++ b/service/health.go @@ -2,48 +2,36 @@ package service import ( "context" - "net/url" + "github.com/InVisionApp/go-health/v2" "github.com/libsv/payd" - "github.com/libsv/payd/config" - "github.com/theflyingcodr/sockets" - "github.com/theflyingcodr/sockets/client" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" ) type healthSvc struct { - c *client.Client - cfg *config.P4 + h health.IHealth } // NewHealthService (NHS) will setup and return a new health service. -func NewHealthService(c *client.Client, cfg *config.P4) payd.HealthService { +func NewHealthService(h health.IHealth) payd.HealthService { return &healthSvc{ - c: c, - cfg: cfg, + h: h, } } // Health will return an error if the application is in an unhealthy state. func (h *healthSvc) Health(ctx context.Context) error { - u, err := url.Parse(h.cfg.ServerHost) + status, failed, err := h.h.State() if err != nil { - return err + return errors.Wrap(err, "failed to check health state") } - switch u.Scheme { - case "ws", "wss": - if err := h.c.JoinChannel(h.cfg.ServerHost, "health", nil, map[string]string{ - "internal": "true", - }); err != nil { - return err - } - if err := h.c.Publish(sockets.Request{ - ChannelID: "health", - MessageKey: "my-p4", - Body: "ping", - }); err != nil { - return err - } - h.c.LeaveChannel("health", nil) + if len(status) == 0 { + return nil + } + if failed { + log.Error().Interface("key check failed", status) + return errors.New("all healthchecks failed") } return nil diff --git a/service/invoice.go b/service/invoice.go index e67ac0cd..b76e5031 100644 --- a/service/invoice.go +++ b/service/invoice.go @@ -71,6 +71,15 @@ func (i *invoice) Invoices(ctx context.Context) ([]payd.Invoice, error) { return ii, nil } +// InvoicesPending will return all currently stored invoices. +func (i *invoice) InvoicesPending(ctx context.Context) ([]payd.Invoice, error) { + ii, err := i.store.Invoices(ctx) + if err != nil { + return nil, errors.WithMessage(err, "failed to get invoices") + } + return ii, nil +} + // Create will add a new invoice to the system. func (i *invoice) Create(ctx context.Context, req payd.InvoiceCreate) (*payd.Invoice, error) { if err := req.Validate(); err != nil { diff --git a/vendor/github.com/InVisionApp/go-health/v2/.gitignore b/vendor/github.com/InVisionApp/go-health/v2/.gitignore new file mode 100644 index 00000000..d3ca9fb6 --- /dev/null +++ b/vendor/github.com/InVisionApp/go-health/v2/.gitignore @@ -0,0 +1,7 @@ +*.sketch +/vendor/ +.DS_Store +c.out +.cover* +.idea +.vscode diff --git a/vendor/github.com/InVisionApp/go-health/v2/.travis.yml b/vendor/github.com/InVisionApp/go-health/v2/.travis.yml new file mode 100644 index 00000000..3433b403 --- /dev/null +++ b/vendor/github.com/InVisionApp/go-health/v2/.travis.yml @@ -0,0 +1,25 @@ +env: + global: + - CC_TEST_REPORTER_ID=aa591e48dd3f08c8dd398d5dac90c4a4d4d32f0e359a932c035ea65a2b005efd + +language: go + +services: + - mongodb + +before_install: + - go get -t -v ./... + +before_script: + # Let code climate know that we are preparing to send a report + - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter + - chmod +x ./cc-test-reporter + - ./cc-test-reporter before-build + +script: + # Run tests + generate coverage + - make test/cc + +after_script: + # Send coverage report to code climate + - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT \ No newline at end of file diff --git a/vendor/github.com/InVisionApp/go-health/v2/LICENSE b/vendor/github.com/InVisionApp/go-health/v2/LICENSE new file mode 100644 index 00000000..d3147646 --- /dev/null +++ b/vendor/github.com/InVisionApp/go-health/v2/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 InVision + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/InVisionApp/go-health/v2/Makefile b/vendor/github.com/InVisionApp/go-health/v2/Makefile new file mode 100644 index 00000000..95049fb1 --- /dev/null +++ b/vendor/github.com/InVisionApp/go-health/v2/Makefile @@ -0,0 +1,78 @@ +# Some things this makefile could make use of: +# +# - test coverage target(s) +# - profiler target(s) +# + +OUTPUT_DIR = build +TMP_DIR := .tmp +RELEASE_VER := $(shell git rev-parse --short HEAD) +NAME = default +COVERMODE = atomic + +TEST_PACKAGES := $(shell go list ./... | grep -v vendor | grep -v fakes) + +.PHONY: help +.DEFAULT_GOAL := help + +# Under the hood, `go test -tags ...` also runs the "default" (unit) test case +# in addition to the specified tags +test: installdeps test/integration ## Perform both unit and integration tests + +testv: installdeps testv/integration ## Perform both unit and integration tests (with verbose flags) + +test/unit: ## Perform unit tests + go test -cover $(TEST_PACKAGES) + +testv/unit: ## Perform unit tests (with verbose flag) + go test -v -cover $(TEST_PACKAGES) + +test/integration: ## Perform integration tests + go test -cover -tags integration $(TEST_PACKAGES) + +testv/integration: ## Perform integration tests + go test -v -cover -tags integration $(TEST_PACKAGES) + +test/race: ## Perform unit tests and enable the race detector + go test -race -cover $(TEST_PACKAGES) + +test/cover: ## Run all tests + open coverage report for all packages + echo 'mode: $(COVERMODE)' > .coverage + for PKG in $(TEST_PACKAGES); do \ + go test -coverprofile=.coverage.tmp -tags "integration" $$PKG; \ + grep -v -E '^mode:' .coverage.tmp >> .coverage; \ + done + go tool cover -html=.coverage + $(RM) .coverage .coverage.tmp + +test/cc: ## Run all tests + create coverage report for code climate + echo 'mode: $(COVERMODE)' > c.out + for PKG in $(TEST_PACKAGES); do \ + go test -covermode=$(COVERMODE) -coverprofile=.coverage.tmp $$PKG; \ + if [ -f .coverage.tmp ]; then\ + grep -v -E '^mode:' .coverage.tmp >> c.out; \ + rm .coverage.tmp;\ + fi;\ + done + $(RM) .coverage.tmp + +test/codecov: ## Run all tests + open coverage report for all packages + for PKG in $(TEST_PACKAGES); do \ + go test -covermode=$(COVERMODE) -coverprofile=profile.out $$PKG; \ + if [ -f profile.out ]; then\ + cat profile.out >> coverage.txt;\ + rm profile.out;\ + fi;\ + done + $(RM) profile.out + +installdeps: ## Install needed dependencies for various middlewares + go get -t -v ./... + +generate: ## Run generate for non-vendor packages only + go list ./... | xargs go generate + +help: ## Display this help message + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_\/-]+:.*?## / {printf "\033[34m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | \ + sort | \ + grep -v '#' diff --git a/vendor/github.com/InVisionApp/go-health/v2/README.md b/vendor/github.com/InVisionApp/go-health/v2/README.md new file mode 100644 index 00000000..7fab273d --- /dev/null +++ b/vendor/github.com/InVisionApp/go-health/v2/README.md @@ -0,0 +1,137 @@ +[![LICENSE](https://img.shields.io/badge/license-MIT-orange.svg)](LICENSE) +[![Build Status](https://travis-ci.org/InVisionApp/go-health.svg?branch=master)](https://travis-ci.org/InVisionApp/go-health) +[![Maintainability](https://api.codeclimate.com/v1/badges/973b603c7f6ad3a59d0a/maintainability)](https://codeclimate.com/github/InVisionApp/go-health/maintainability) +[![Test Coverage](https://api.codeclimate.com/v1/badges/973b603c7f6ad3a59d0a/test_coverage)](https://codeclimate.com/github/InVisionApp/go-health/test_coverage) +[![Go Report Card](https://goreportcard.com/badge/github.com/InVisionApp/go-health)](https://goreportcard.com/report/github.com/InVisionApp/go-health) +[![Godocs](https://img.shields.io/badge/golang-documentation-blue.svg)](https://godoc.org/github.com/InVisionApp/go-health) + + + +# go-health +A library that enables *async* dependency health checking for services running on an orchestrated container platform such as kubernetes or mesos. + +## Why is this important? +Container orchestration platforms require that the underlying service(s) expose a "health check" which is used by the platform to determine whether the container is in a good or bad state. + +While this can be achieved by simply exposing a `/status` endpoint that performs synchronous checks against its dependencies (followed by returning a `200` or `non-200` status code), it is not optimal for a number of reasons: + +* **It does not scale** + + The more dependencies you add, the longer your health check will take to complete (and potentially cause your service to be killed off by the orchestration platform). + + Depending on the complexity of a given dependency, your check may be fairly involved where it is _okay_ for it to take `30s+` to complete. +* **It adds unnecessary load on yours deps or at worst, becomes a DoS target** + + **Non-malicious scenario** + + Thundering herd problem -- in the event of a deployment (or restart, etc.), all of your service containers are likely to have their `/status` endpoints checked by the orchestration platform as soon as they come up. Depending on the complexity of the checks, running that many simultaneous checks against your dependencies could cause at worst the dependencies to experience problems and at minimum add unnecessary load. + + Security scanners -- if your organization runs periodic security scans, they may hit your `/status` endpoint and trigger unnecessary dep checks. + + **Malicious scenario** + + Loading up any basic HTTP benchmarking tool and pointing it at your `/status` endpoint could choke your dependencies (and potentially your service). + +With that said, not everyone _needs_ asynchronous checks. If your service has one dependency (and that is unlikely to change), it is trivial to write a basic, synchronous check and it will probably suffice. + +However, if you anticipate that your service will have several dependencies, with varying degrees of complexity for determining their health state - you should probably think about introducing asynchronous health checks. + +## How does this library help? +Writing an async health checking framework for your service is not a trivial task, especially if Go is not your primary language. + +This library: + +* Allows you to define how to check your dependencies. +* Allows you to define warning and fatal thresholds. +* Will run your dependency checks on a given interval, in the background. **[1]** +* Exposes a way for you to gather the check results in a *fast* and *thread-safe* manner to help determine the final status of your `/status` endpoint. **[2]** +* Comes bundled w/ [pre-built checkers](/checkers) for well-known dependencies such as `Redis`, `Mongo`, `HTTP` and more. +* Makes it simple to implement and provide your own checkers (by adhering to the checker interface). +* Allows you to trigger listener functions when your health checks fail or recover using the [`IStatusListener` interface](#oncomplete-hook-vs-istatuslistener). +* Allows you to run custom logic when a specific health check completes by using the [`OnComplete` hook](#oncomplete-hook-vs-istatuslistener). + +**[1]** Make sure to run your checks on a "sane" interval - ie. if you are checking your +Redis dependency once every five minutes, your service is essentially running _blind_ +for about 4.59/5 minutes. Unless you have a really good reason, check your dependencies +every X _seconds_, rather than X _minutes_. + +**[2]** `go-health` continuously writes dependency health state data and allows +you to query that data via `.State()`. Alternatively, you can use one of the +pre-built HTTP handlers for your `/healthcheck` endpoint (and thus not have to +manually inspect the state data). + +## Example + +For _full_ examples, look through the [examples dir](examples/) + +1. Create an instance of `health` and configure a checker (or two) + +```golang +import ( + health "github.com/InVisionApp/go-health/v2" + "github.com/InVisionApp/go-health/v2/checkers" + "github.com/InVisionApp/go-health/v2/handlers" +) + +// Create a new health instance +h := health.New() + +// Create a checker +myURL, _ := url.Parse("https://google.com") +myCheck, _ := checkers.NewHTTP(&checkers.HTTPConfig{ + URL: myURL, +}) +``` + +2. Register your check with your `health` instance + +```golang +h.AddChecks([]*health.Config{ + { + Name: "my-check", + Checker: myCheck, + Interval: time.Duration(2) * time.Second, + Fatal: true, + }, +}) +``` + +3. Start the health check + +```golang +h.Start() +``` + +From here on, you can either configure an endpoint such as `/healthcheck` to use a built-in handler such as `handlers.NewJSONHandlerFunc()` or get the current health state of all your deps by traversing the data returned by `h.State()`. + +## Sample /healthcheck output +Assuming you have configured `go-health` with two `HTTP` checkers, your `/healthcheck` +output would look something like this: + +```json +{ + "details": { + "bad-check": { + "name": "bad-check", + "status": "failed", + "error": "Ran into error while performing 'GET' request: Get google.com: unsupported protocol scheme \"\"", + "check_time": "2017-12-30T16:20:13.732240871-08:00" + }, + "good-check": { + "name": "good-check", + "status": "ok", + "check_time": "2017-12-30T16:20:13.80109931-08:00" + } + }, + "status": "ok" +} +``` + +## Additional Documentation +* [Examples](/examples) + * [Status Listeners](/examples/status-listener) + * [OnComplete Hook](/examples/on-complete-hook) +* [Checkers](/checkers) + +## OnComplete Hook VS IStatusListener +At first glance it may seem that these two features provide the same functionality. However, they are meant for two different use cases: + +The `IStatusListener` is useful when you want to run a custom function in the event that the overall status of your health checks change. I.E. if `go-health` is currently checking the health for two different dependencies A and B, you may want to trip a circuit breaker for A and/or B. You could also put your service in a state where it will notify callers that it is not currently operating correctly. The opposite can be done when your service recovers. + +The `OnComplete` hook is called whenever a health check for an individual dependency is complete. This means that the function you register with the hook gets called every single time `go-health` completes the check. It's completely possible to register different functions with each configured health check or not to hook into the completion of certain health checks entirely. For instance, this can be useful if you want to perform cleanup after a complex health check or if you want to send metrics to your APM software when a health check completes. It is important to keep in mind that this hook effectively gets called on roughly the same interval you define for the health check. + +## Contributing +All PR's are welcome, as long as they are well tested. Follow the typical fork->branch->pr flow. diff --git a/vendor/github.com/InVisionApp/go-health/v2/health.go b/vendor/github.com/InVisionApp/go-health/v2/health.go new file mode 100644 index 00000000..f5422363 --- /dev/null +++ b/vendor/github.com/InVisionApp/go-health/v2/health.go @@ -0,0 +1,364 @@ +// Package health is a library that enables *async* dependency health checking for services running on an orchestrated container platform such as kubernetes or mesos. +// +// For additional overview, documentation and contribution guidelines, refer to the project's "README.md". +// +// For example usage, refer to https://github.com/InVisionApp/go-health/tree/master/examples/simple-http-server. +package health + +import ( + "errors" + "sync" + "time" + + "github.com/InVisionApp/go-logger" +) + +//go:generate counterfeiter -o ./fakes/icheckable.go . ICheckable + +var ( + // ErrNoAddCfgWhenActive is returned when you attempt to add check(s) to an already active healthcheck instance + ErrNoAddCfgWhenActive = errors.New("Unable to add new check configuration(s) while healthcheck is active") + + // ErrAlreadyRunning is returned when you attempt to "h.Start()" an already running healthcheck + ErrAlreadyRunning = errors.New("Healthcheck is already running - nothing to start") + + // ErrAlreadyStopped is returned when you attempt to "h.Stop()" a non-running healthcheck instance + ErrAlreadyStopped = errors.New("Healthcheck is not running - nothing to stop") + + // ErrEmptyConfigs is returned when you attempt to add an empty slice of configs via "h.AddChecks()" + ErrEmptyConfigs = errors.New("Configs appears to be empty - nothing to add") +) + +// The IHealth interface can be useful if you plan on replacing the actual health +// checker with a mock during testing. Otherwise, you can set "hc.Disable = true" +// after instantiation. +type IHealth interface { + AddChecks(cfgs []*Config) error + AddCheck(cfg *Config) error + Start() error + Stop() error + State() (map[string]State, bool, error) + Failed() bool +} + +// ICheckable is an interface implemented by a number of bundled checkers such +// as "MySQLChecker", "RedisChecker" and "HTTPChecker". By implementing the +// interface, you can feed your own custom checkers into the health library. +type ICheckable interface { + // Status allows you to return additional data as an "interface{}" and "error" + // to signify that the check has failed. If "interface{}" is non-nil, it will + // be exposed under "State.Details" for that particular check. + Status() (interface{}, error) +} + +// IStatusListener is an interface that handles health check failures and +// recoveries, primarily for stats recording purposes +type IStatusListener interface { + // HealthCheckFailed is a function that handles the failure of a health + // check event. This function is called when a health check state + // transitions from passing to failing. + // * entry - The recorded state of the health check that triggered the failure + HealthCheckFailed(entry *State) + + // HealthCheckRecovered is a function that handles the recovery of a failed + // health check. + // * entry - The recorded state of the health check that triggered the recovery + // * recordedFailures - the total failed health checks that lapsed + // between the failure and recovery + // * failureDurationSeconds - the lapsed time, in seconds, of the recovered failure + HealthCheckRecovered(entry *State, recordedFailures int64, failureDurationSeconds float64) +} + +// Config is a struct used for defining and configuring checks. +type Config struct { + // Name of the check + Name string + + // Checker instance used to perform health check + Checker ICheckable + + // Interval between health checks + Interval time.Duration + + // Fatal marks a failing health check so that the + // entire health check request fails with a 500 error + Fatal bool + + // Hook that gets called when this health check is complete + OnComplete func(state *State) +} + +// State is a struct that contains the results of the latest +// run of a particular check. +type State struct { + // Name of the health check + Name string `json:"name"` + + // Status of the health check state ("ok" or "failed") + Status string `json:"status"` + + // Err is the error returned from a failed health check + Err string `json:"error,omitempty"` + + // Fatal shows if the check will affect global result + Fatal bool `json:"fatal,omitempty"` + + // Details contains more contextual detail about a + // failing health check. + Details interface{} `json:"details,omitempty"` // contains JSON message (that can be marshaled) + + // CheckTime is the time of the last health check + CheckTime time.Time `json:"check_time"` + + ContiguousFailures int64 `json:"num_failures"` // the number of failures that occurred in a row + TimeOfFirstFailure time.Time `json:"first_failure_at"` // the time of the initial transitional failure for any given health check +} + +// indicates state is failure +func (s *State) isFailure() bool { + return s.Status == "failed" +} + +// Health contains internal go-health internal structures. +type Health struct { + Logger log.Logger + + // StatusListener will report failures and recoveries + StatusListener IStatusListener + + active *sBool // indicates whether the healthcheck is actively running + configs []*Config + states map[string]State + statesLock sync.Mutex + runners map[string]chan struct{} // contains map of active runners w/ a stop channel +} + +// New returns a new instance of the Health struct. +func New() *Health { + return &Health{ + Logger: log.NewSimple(), + configs: make([]*Config, 0), + states: make(map[string]State, 0), + runners: make(map[string]chan struct{}, 0), + active: newBool(), + statesLock: sync.Mutex{}, + } +} + +// DisableLogging will disable all logging by inserting the noop logger. +func (h *Health) DisableLogging() { + h.Logger = log.NewNoop() +} + +// AddChecks is used for adding multiple check definitions at once (as opposed +// to adding them sequentially via "AddCheck()"). +func (h *Health) AddChecks(cfgs []*Config) error { + if h.active.val() { + return ErrNoAddCfgWhenActive + } + + h.configs = append(h.configs, cfgs...) + + return nil +} + +// AddCheck is used for adding a single check definition to the current health +// instance. +func (h *Health) AddCheck(cfg *Config) error { + if h.active.val() { + return ErrNoAddCfgWhenActive + } + + h.configs = append(h.configs, cfg) + return nil +} + +// Start will start all of the defined health checks. Each of the checks run in +// their own goroutines (as "time.Ticker"). +func (h *Health) Start() error { + if h.active.val() { + return ErrAlreadyRunning + } + + // if there are no check configs, this is a noop + if len(h.configs) < 1 { + return nil + } + + for _, c := range h.configs { + h.Logger.WithFields(log.Fields{"name": c.Name}).Debug("Starting checker") + ticker := time.NewTicker(c.Interval) + stop := make(chan struct{}) + + h.startRunner(c, ticker, stop) + + h.runners[c.Name] = stop + } + + // Checkers are now actively running + h.active.setTrue() + + return nil +} + +// Stop will cause all of the running health checks to be stopped. Additionally, +// all existing check states will be reset. +func (h *Health) Stop() error { + if !h.active.val() { + return ErrAlreadyStopped + } + + for name, stop := range h.runners { + h.Logger.WithFields(log.Fields{"name": name}).Debug("Stopping checker") + close(stop) + } + + // Reset runner map + h.runners = make(map[string]chan struct{}, 0) + + // Reset states + h.safeResetStates() + + return nil +} + +// State will return a map of all current healthcheck states (thread-safe), a +// bool indicating whether the healthcheck has fully failed and a potential error. +// +// The returned structs can be used for figuring out additional analytics or +// used for building your own status handler (as opposed to using the built-in +// "hc.HandlerBasic" or "hc.HandlerJSON"). +// +// The map key is the name of the check. +func (h *Health) State() (map[string]State, bool, error) { + return h.safeGetStates(), h.Failed(), nil +} + +// Failed will return the basic state of overall health. This should be used when +// details about the failure are not needed +func (h *Health) Failed() bool { + for _, val := range h.safeGetStates() { + if val.Fatal && val.isFailure() { + return true + } + } + return false +} + +func (h *Health) startRunner(cfg *Config, ticker *time.Ticker, stop <-chan struct{}) { + + // function to execute and collect check data + checkFunc := func() { + data, err := cfg.Checker.Status() + + stateEntry := &State{ + Name: cfg.Name, + Status: "ok", + Details: data, + CheckTime: time.Now(), + Fatal: cfg.Fatal, + } + + if err != nil { + h.Logger.WithFields(log.Fields{ + "check": cfg.Name, + "fatal": cfg.Fatal, + "err": err, + }).Error("healthcheck has failed") + + stateEntry.Err = err.Error() + stateEntry.Status = "failed" + } + + h.safeUpdateState(stateEntry) + + if cfg.OnComplete != nil { + go cfg.OnComplete(stateEntry) + } + } + + go func() { + defer ticker.Stop() + + // execute once so that it is immediate + checkFunc() + + // all following executions + RunLoop: + for { + select { + case <-ticker.C: + checkFunc() + case <-stop: + break RunLoop + } + } + + h.Logger.WithFields(log.Fields{"name": cfg.Name}).Debug("Checker exiting") + }() +} + +// resets the states in a concurrency-safe manner +func (h *Health) safeResetStates() { + h.statesLock.Lock() + defer h.statesLock.Unlock() + h.states = make(map[string]State, 0) +} + +// updates the check state in a concurrency-safe manner +func (h *Health) safeUpdateState(stateEntry *State) { + // dispatch any status listeners + h.handleStatusListener(stateEntry) + + // update states here + h.statesLock.Lock() + defer h.statesLock.Unlock() + + h.states[stateEntry.Name] = *stateEntry +} + +// get all states in a concurrency-safe manner +func (h *Health) safeGetStates() map[string]State { + h.statesLock.Lock() + defer h.statesLock.Unlock() + + // deep copy h.states to avoid race + statesCopy := make(map[string]State, 0) + + for k, v := range h.states { + statesCopy[k] = v + } + + return statesCopy +} + +// if a status listener is attached +func (h *Health) handleStatusListener(stateEntry *State) { + // get the previous state + h.statesLock.Lock() + prevState := h.states[stateEntry.Name] + h.statesLock.Unlock() + + // state is failure + if stateEntry.isFailure() { + if !prevState.isFailure() { + // new failure: previous state was ok + if h.StatusListener != nil { + go h.StatusListener.HealthCheckFailed(stateEntry) + } + + stateEntry.TimeOfFirstFailure = time.Now() + } else { + // carry the time of first failure from the previous state + stateEntry.TimeOfFirstFailure = prevState.TimeOfFirstFailure + } + stateEntry.ContiguousFailures = prevState.ContiguousFailures + 1 + } else if prevState.isFailure() { + // recovery, previous state was failure + failureSeconds := time.Now().Sub(prevState.TimeOfFirstFailure).Seconds() + + if h.StatusListener != nil { + go h.StatusListener.HealthCheckRecovered(stateEntry, prevState.ContiguousFailures, failureSeconds) + } + } +} diff --git a/vendor/github.com/InVisionApp/go-health/v2/in-repo.yaml b/vendor/github.com/InVisionApp/go-health/v2/in-repo.yaml new file mode 100644 index 00000000..de530fd8 --- /dev/null +++ b/vendor/github.com/InVisionApp/go-health/v2/in-repo.yaml @@ -0,0 +1,4 @@ +owner: + active: + team: core + since: 2017-12-01 \ No newline at end of file diff --git a/vendor/github.com/InVisionApp/go-health/v2/safe.go b/vendor/github.com/InVisionApp/go-health/v2/safe.go new file mode 100644 index 00000000..aecb185d --- /dev/null +++ b/vendor/github.com/InVisionApp/go-health/v2/safe.go @@ -0,0 +1,48 @@ +package health + +import "sync" + +//################# +//Thread Safe Types +//################# + +type sBool struct { + v bool + mu sync.Mutex +} + +func newBool() *sBool { + return &sBool{v: false} +} + +func (b *sBool) set(v bool) { + b.mu.Lock() + defer b.mu.Unlock() + b.v = v +} + +func (b *sBool) setFalse() { + b.mu.Lock() + defer b.mu.Unlock() + b.v = false +} + +func (b *sBool) setTrue() { + b.mu.Lock() + defer b.mu.Unlock() + b.v = true +} + +func (b *sBool) val() bool { + b.mu.Lock() + defer b.mu.Unlock() + return b.v +} + +func (b *sBool) String() string { + if b.val() { + return "true" + } + + return "false" +} diff --git a/vendor/github.com/InVisionApp/go-logger/.gitignore b/vendor/github.com/InVisionApp/go-logger/.gitignore new file mode 100644 index 00000000..aafda3a6 --- /dev/null +++ b/vendor/github.com/InVisionApp/go-logger/.gitignore @@ -0,0 +1,2 @@ +.vscode +.idea diff --git a/vendor/github.com/InVisionApp/go-logger/.travis.yml b/vendor/github.com/InVisionApp/go-logger/.travis.yml new file mode 100644 index 00000000..309d10a1 --- /dev/null +++ b/vendor/github.com/InVisionApp/go-logger/.travis.yml @@ -0,0 +1,10 @@ +language: go + +before_install: + - go get -t -v ./... + +script: + - make test/codecov + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/InVisionApp/go-logger/LICENSE b/vendor/github.com/InVisionApp/go-logger/LICENSE new file mode 100644 index 00000000..d3147646 --- /dev/null +++ b/vendor/github.com/InVisionApp/go-logger/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 InVision + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/InVisionApp/go-logger/Makefile b/vendor/github.com/InVisionApp/go-logger/Makefile new file mode 100644 index 00000000..812a32fb --- /dev/null +++ b/vendor/github.com/InVisionApp/go-logger/Makefile @@ -0,0 +1,62 @@ +OUTPUT_DIR = build +TMP_DIR := .tmp +RELEASE_VER := $(shell git rev-parse --short HEAD) +NAME = default +COVERMODE = atomic + +TEST_PACKAGES := $(shell go list ./... | grep -v vendor | grep -v fakes) + +.PHONY: help +.DEFAULT_GOAL := help + +test: getdeps test/integration ## Perform both unit and integration tests + +testv: getdeps testv/integration ## Perform both unit and integration tests (with verbose flags) + +test/unit: ## Perform unit tests + go test -cover $(TEST_PACKAGES) + +testv/unit: ## Perform unit tests (with verbose flag) + go test -v -cover $(TEST_PACKAGES) + +test/integration: ## Perform integration tests + go test -cover -tags integration $(TEST_PACKAGES) + +testv/integration: ## Perform integration tests + go test -v -cover -tags integration $(TEST_PACKAGES) + +test/race: ## Perform unit tests and enable the race detector + go test -race -cover $(TEST_PACKAGES) + +test/cover: ## Run all tests + open coverage report for all packages + echo 'mode: $(COVERMODE)' > .coverage + for PKG in $(TEST_PACKAGES); do \ + go test -coverprofile=.coverage.tmp -tags "integration" $$PKG; \ + grep -v -E '^mode:' .coverage.tmp >> .coverage; \ + done + go tool cover -html=.coverage + $(RM) .coverage .coverage.tmp + +test/codecov: ## Run all tests + open coverage report for all packages + ECODE=0; \ + for PKG in $(TEST_PACKAGES); do \ + go test -covermode=$(COVERMODE) -coverprofile=profile.out $$PKG; \ + ECODE=$$((ECODE+$$?));\ + if [ -f profile.out ]; then\ + cat profile.out >> coverage.txt;\ + rm profile.out;\ + fi;\ + done;\ + $(RM) profile.out ;\ + exit $$ECODE;\ + +getdeps: ## Install needed dependencies for various middlewares + go get -t -v ./... + +generate: ## Run generate for non-vendor packages only + go list ./... | grep -v vendor | xargs go generate + +help: ## Display this help message + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_\/-]+:.*?## / {printf "\033[34m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | \ + sort | \ + grep -v '#' diff --git a/vendor/github.com/InVisionApp/go-logger/README.md b/vendor/github.com/InVisionApp/go-logger/README.md new file mode 100644 index 00000000..fcf1a517 --- /dev/null +++ b/vendor/github.com/InVisionApp/go-logger/README.md @@ -0,0 +1,133 @@ +[![LICENSE](https://img.shields.io/badge/license-MIT-orange.svg)](LICENSE) +[![Build Status](https://travis-ci.com/InVisionApp/go-logger.svg?token=KosA43m1X3ikri8JEukQ&branch=master)](https://travis-ci.com/InVisionApp/go-logger) +[![codecov](https://codecov.io/gh/InVisionApp/go-logger/branch/master/graph/badge.svg?token=hhqA1l88kx)](https://codecov.io/gh/InVisionApp/go-logger) +[![Go Report Card](https://goreportcard.com/badge/github.com/InVisionApp/go-logger)](https://goreportcard.com/report/github.com/InVisionApp/go-logger) +[![Godocs](https://img.shields.io/badge/golang-documentation-blue.svg)](https://godoc.org/github.com/InVisionApp/go-logger) + + + + + +# go-logger +This package provides a standard interface for logging in any go application. +Logger interface allows you to maintain a unified interface while using a custom logger. This allows you to write log statements without dictating the specific underlying library used for logging. You can avoid vendoring of logging libraries, which is especially useful when writing shared code such as a library. +This package also contains a simple logger and a no-op logger which both implement the interface. The simple logger is a wrapper for the standard logging library which meets this logger interface. The no-op logger can be used to easily silence all logging. +This library is also supplemented with some additional helpers/shims for other common logging libraries such as logrus to allow them to meet the logger interface. + +## Usage +The logger interface defines 4 levels of logging: `Debug`, `Info`, `Warn`, and `Error`. These will accept a variadic list of strings as in `fmt.Println`. All the string parameters will be concatenated into a single message. +Additionally, each of the log levels offers a formatted string as well: `Debugf`, `Infof`, `Warnf`, and `Errorf`. These functions, like `fmt.Printf` and offer the ability to define a format string and parameters to populate it. +Finally, there is a `WithFields(Fields)` method that will allow you to define a set of fields that will always be logged with evey message. This method returns copy of the logger and appends all fields to any preexisting fields. + +## Implementations + +### Simple Logger +The simple logger is a wrapper for the standard logging library which meets this logger interface. It provides very basic logging functionality with log levels in messages. + +```go +import "github.com/InVisionApp/go-logger" + +logger := log.NewSimple() +logger.Debug("this is a debug message") +``` +output: +``` +2018/03/04 12:55:08 [DEBUG] Simplelogger +``` + +### No-op Logger +If you do not wish to perform any sort of logging whatsoever, you can point to a noop logger. This is useful for silencing logs in tests, or allowing users to turn of logging in your library. + +```go +import "github.com/InVisionApp/go-logger" + +logger := log.NewNoop() +logger.Debug("this is a debug message") +``` +_no output_ + +### Logrus Logger +This shim allows you to use logrus as your logger implementation. If you wish to use the standard logrus logger, pass `nil` to the constructor. Otherwise, pass in your own `logrus.Logger`. + +```go +import "github.com/InVisionApp/go-logger/shims/logrus" + +// Default logrus logger +logger := logrus.New(nil) +logger.Debug("this is a debug message") +``` + +Or alternatively, you can provide your own logrus logger: +```go +import ( + lgrs "github.com/sirupsen/logrus" + "github.com/InVisionApp/go-logger/shims/logrus" +) + +myLogrus := lgrs.New() +myLogrus.Out = &bytes.Buffer{} +logger := logrus.New(myLogrus) +logger.Debug("this is a debug message") +``` + +output: +``` +time="2018-03-04T13:12:35-08:00" level=debug msg="this is a debug message" +``` + +### Zerolog Logger +This shim allows you to use [zerolog](https://github.com/rs/zerolog) as your logging implementation. If you pass `nil` into `New(...)`, +you will get a default `zerolog.Logger` writing to `stdout` with a timestamp attached. + +Alternatively, you can pass your own instance of `zerolog.Logger` to `New(...)`. + +Using the `zerolog` default logger: +```go +import "github.com/InVisionApp/go-logger/shims/zerolog" + +func main() { + logger := zerolog.New(nil) + logger.Debug("this is a debug message!") +} + +``` + +Using your own logger: +```go +import ( + "os" + + zl "github.com/rs/zerolog" + "github.com/InVisionApp/go-logger/shims/zerolog" +) + +func main() { + // zerolog is a structured logger by default + structuredLogger := zl.New(os.Stdout).Logger() + sLogger := zerolog.New(structuredLogger) + sLogger.Debug("debug message") + // {"level":"debug", "message":"debug message"} + + // If you want to use zerolog for human-readable console logging, + // you create a ConsoleWriter and use it as your io.Writer implementation + consoleLogger := zl.New(zl.ConsoleWriter{ + Out: os.Stdout, + }) + cLogger := zerolog.New(consoleLogger) + cLogger.Debug("debug message") + // |DEBUG| debug message +} +``` + +### Test Logger +The test logger is for capturing logs during the execution of a test. It writes the logs to a byte buffer which can be dumped and inspected. It also tracks a call count of the total number of times the logger has been called. +**_Note:_** this logger is not meant to be used in production. It is purely designed for use in tests. + +### Fake Logger +A generated fake that meets the logger interface. This is useful if you want to stub out your own functionality for the logger in tests. This logger is meant for use in tests and not in production. If you simply want to silence logs, use the no-op logger. + +--- + +#### \[Credit\] +The go-logger gopher image by [talpert](https://github.com/talpert) +Original artwork designed by Renée French diff --git a/vendor/github.com/InVisionApp/go-logger/in-repo.yaml b/vendor/github.com/InVisionApp/go-logger/in-repo.yaml new file mode 100644 index 00000000..e7bfffef --- /dev/null +++ b/vendor/github.com/InVisionApp/go-logger/in-repo.yaml @@ -0,0 +1,4 @@ +owner: + active: + team: core + since: 2018-02-04 diff --git a/vendor/github.com/InVisionApp/go-logger/log.go b/vendor/github.com/InVisionApp/go-logger/log.go new file mode 100644 index 00000000..f3ecc4ff --- /dev/null +++ b/vendor/github.com/InVisionApp/go-logger/log.go @@ -0,0 +1,201 @@ +package log + +import ( + "fmt" + stdlog "log" +) + +//go:generate counterfeiter -o shims/fake/fake_logger.go . Logger + +// Logger interface allows you to maintain a unified interface while using a +// custom logger. This allows you to write log statements without dictating +// the specific underlying library used for logging. You can avoid vendoring +// of logging libraries, which is especially useful when writing shared code +// such as a library. This package contains a simple logger and a no-op logger +// which both implement this interface. It is also supplemented with some +// additional helpers/shims for other common logging libraries such as logrus +type Logger interface { + Debug(msg ...interface{}) + Info(msg ...interface{}) + Warn(msg ...interface{}) + Error(msg ...interface{}) + + Debugln(msg ...interface{}) + Infoln(msg ...interface{}) + Warnln(msg ...interface{}) + Errorln(msg ...interface{}) + + Debugf(format string, args ...interface{}) + Infof(format string, args ...interface{}) + Warnf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) + + WithFields(Fields) Logger +} + +// Fields is used to define structured fields which are appended to log messages +type Fields map[string]interface{} + +/************** + Simple Logger +**************/ + +type simple struct { + fields map[string]interface{} +} + +// NewSimple creates a basic logger that wraps the core log library. +func NewSimple() Logger { + return &simple{} +} + +// WithFields will return a new logger based on the original logger +// with the additional supplied fields +func (b *simple) WithFields(fields Fields) Logger { + cp := &simple{} + + if b.fields == nil { + cp.fields = fields + return cp + } + + cp.fields = make(map[string]interface{}, len(b.fields)+len(fields)) + for k, v := range b.fields { + cp.fields[k] = v + } + + for k, v := range fields { + cp.fields[k] = v + } + + return cp +} + +// Debug log message +func (b *simple) Debug(msg ...interface{}) { + stdlog.Printf("[DEBUG] %s %s", fmt.Sprint(msg...), pretty(b.fields)) +} + +// Info log message +func (b *simple) Info(msg ...interface{}) { + stdlog.Printf("[INFO] %s %s", fmt.Sprint(msg...), pretty(b.fields)) +} + +// Warn log message +func (b *simple) Warn(msg ...interface{}) { + stdlog.Printf("[WARN] %s %s", fmt.Sprint(msg...), pretty(b.fields)) +} + +// Error log message +func (b *simple) Error(msg ...interface{}) { + stdlog.Printf("[ERROR] %s %s", fmt.Sprint(msg...), pretty(b.fields)) +} + +// Debugln log line message +func (b *simple) Debugln(msg ...interface{}) { + a := fmt.Sprintln(msg...) + stdlog.Println("[DEBUG]", a[:len(a)-1], pretty(b.fields)) +} + +// Infoln log line message +func (b *simple) Infoln(msg ...interface{}) { + a := fmt.Sprintln(msg...) + stdlog.Println("[INFO]", a[:len(a)-1], pretty(b.fields)) +} + +// Warnln log line message +func (b *simple) Warnln(msg ...interface{}) { + a := fmt.Sprintln(msg...) + stdlog.Println("[WARN]", a[:len(a)-1], pretty(b.fields)) +} + +// Errorln log line message +func (b *simple) Errorln(msg ...interface{}) { + a := fmt.Sprintln(msg...) + stdlog.Println("[ERROR]", a[:len(a)-1], pretty(b.fields)) +} + +// Debugf log message with formatting +func (b *simple) Debugf(format string, args ...interface{}) { + stdlog.Print(fmt.Sprintf("[DEBUG] "+format, args...), " ", pretty(b.fields)) +} + +// Infof log message with formatting +func (b *simple) Infof(format string, args ...interface{}) { + stdlog.Print(fmt.Sprintf("[INFO] "+format, args...), " ", pretty(b.fields)) +} + +// Warnf log message with formatting +func (b *simple) Warnf(format string, args ...interface{}) { + stdlog.Print(fmt.Sprintf("[WARN] "+format, args...), " ", pretty(b.fields)) +} + +// Errorf log message with formatting +func (b *simple) Errorf(format string, args ...interface{}) { + stdlog.Print(fmt.Sprintf("[ERROR] "+format, args...), " ", pretty(b.fields)) +} + +// helper for pretty printing of fields +func pretty(m map[string]interface{}) string { + if len(m) < 1 { + return "" + } + + s := "" + for k, v := range m { + s += fmt.Sprintf("%s=%v ", k, v) + } + + return s[:len(s)-1] +} + +/************* + No-Op Logger +*************/ + +type noop struct{} + +// NewNoop creates a no-op logger that can be used to silence +// all logging from this library. Also useful in tests. +func NewNoop() Logger { + return &noop{} +} + +// Debug log message no-op +func (n *noop) Debug(msg ...interface{}) {} + +// Info log message no-op +func (n *noop) Info(msg ...interface{}) {} + +// Warn log message no-op +func (n *noop) Warn(msg ...interface{}) {} + +// Error log message no-op +func (n *noop) Error(msg ...interface{}) {} + +// Debugln line log message no-op +func (n *noop) Debugln(msg ...interface{}) {} + +// Infoln line log message no-op +func (n *noop) Infoln(msg ...interface{}) {} + +// Warnln line log message no-op +func (n *noop) Warnln(msg ...interface{}) {} + +// Errorln line log message no-op +func (n *noop) Errorln(msg ...interface{}) {} + +// Debugf log message with formatting no-op +func (n *noop) Debugf(format string, args ...interface{}) {} + +// Infof log message with formatting no-op +func (n *noop) Infof(format string, args ...interface{}) {} + +// Warnf log message with formatting no-op +func (n *noop) Warnf(format string, args ...interface{}) {} + +// Errorf log message with formatting no-op +func (n *noop) Errorf(format string, args ...interface{}) {} + +// WithFields no-op +func (n *noop) WithFields(fields Fields) Logger { return n } diff --git a/vendor/github.com/theflyingcodr/sockets/Makefile b/vendor/github.com/theflyingcodr/sockets/Makefile index 3e580c27..0fb0249d 100644 --- a/vendor/github.com/theflyingcodr/sockets/Makefile +++ b/vendor/github.com/theflyingcodr/sockets/Makefile @@ -27,7 +27,7 @@ run-linter: @golangci-lint run --deadline=480s --skip-dirs=vendor --tests install-linter: - @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)bin v1.43.0 + @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)bin v1.44.2 go-doc-mac: @open http://localhost:6060 && \ diff --git a/vendor/github.com/theflyingcodr/sockets/client/client.go b/vendor/github.com/theflyingcodr/sockets/client/client.go index 3fd9e76e..770cfc19 100644 --- a/vendor/github.com/theflyingcodr/sockets/client/client.go +++ b/vendor/github.com/theflyingcodr/sockets/client/client.go @@ -116,6 +116,7 @@ type Client struct { channelJoin chan *connection channelLeave chan string channelReconnect chan reconnectChannel + channelChecker chan internal.ChannelCheck join chan joinSuccess opts *opts sync.RWMutex @@ -147,6 +148,7 @@ func New(opts ...OptFunc) *Client { channelJoin: make(chan *connection, 1), channelLeave: make(chan string, 1), channelReconnect: make(chan reconnectChannel, 1), + channelChecker: make(chan internal.ChannelCheck, 256), join: make(chan joinSuccess, 1), RWMutex: sync.RWMutex{}, opts: o, @@ -281,6 +283,22 @@ func (c *Client) LeaveChannel(channelID string, headers http.Header) { c.channelLeave <- channelID } +// HasChannel will check to see if a client is connected to a channel. +func (c *Client) HasChannel(channelID string) bool { + log.Debug().Msgf("checking if channel %s exists", channelID) + exists := make(chan bool) + defer close(exists) + + c.channelChecker <- internal.ChannelCheck{ + ID: channelID, + Exists: exists, + } + + result := <-exists + log.Debug().Msgf("channel %s exists: %t", channelID, result) + return result +} + func (c *Client) channelManager() { for { select { @@ -330,6 +348,9 @@ func (c *Client) channelManager() { continue } ch.ws = r.conn + case e := <-c.channelChecker: + _, ok := c.conn[e.ID] + e.Exists <- ok } } } diff --git a/vendor/github.com/theflyingcodr/sockets/internal/sockets.go b/vendor/github.com/theflyingcodr/sockets/internal/sockets.go index 7382f4ce..3803dec6 100644 --- a/vendor/github.com/theflyingcodr/sockets/internal/sockets.go +++ b/vendor/github.com/theflyingcodr/sockets/internal/sockets.go @@ -17,3 +17,9 @@ func WriteJSON(ws *websocket.Conn, timeout time.Duration, payload interface{}) e _ = ws.SetWriteDeadline(time.Now().Add(timeout)) return ws.WriteJSON(payload) } + +// ChannelCheck for sending channel check messages. +type ChannelCheck struct { + ID string + Exists chan bool +} diff --git a/vendor/github.com/theflyingcodr/sockets/server/server.go b/vendor/github.com/theflyingcodr/sockets/server/server.go index 49c145f0..eb0b9cdd 100644 --- a/vendor/github.com/theflyingcodr/sockets/server/server.go +++ b/vendor/github.com/theflyingcodr/sockets/server/server.go @@ -12,6 +12,7 @@ import ( "github.com/rs/zerolog/log" "github.com/theflyingcodr/sockets" + "github.com/theflyingcodr/sockets/internal" "github.com/theflyingcodr/sockets/middleware" ) @@ -112,7 +113,7 @@ type SocketServer struct { register chan register channelSender chan sender directSender chan sender - channelChecker chan checker + channelChecker chan internal.ChannelCheck close chan struct{} done chan struct{} channelCloser chan string @@ -143,7 +144,7 @@ func New(opts ...OptFunc) *SocketServer { register: make(chan register, 1), channelSender: make(chan sender, 256), directSender: make(chan sender, 256), - channelChecker: make(chan checker, 256), + channelChecker: make(chan internal.ChannelCheck, 256), close: make(chan struct{}, 1), done: make(chan struct{}, 1), opts: defaults, @@ -262,7 +263,7 @@ func (s *SocketServer) channelManager() { } case c := <-s.channelChecker: _, ok := s.channels[c.ID] - c.exists <- ok + c.Exists <- ok case <-ticker.C: for channelID, channel := range s.channels { if channel.expires.IsZero() { // doesn't expire @@ -491,9 +492,9 @@ func (s *SocketServer) HasChannel(channelID string) bool { exists := make(chan bool) defer close(exists) - s.channelChecker <- checker{ + s.channelChecker <- internal.ChannelCheck{ ID: channelID, - exists: exists, + Exists: exists, } result := <-exists @@ -545,8 +546,3 @@ type sender struct { ID string msg interface{} } - -type checker struct { - ID string - exists chan bool -} diff --git a/vendor/modules.txt b/vendor/modules.txt index ee14ea59..8a050625 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,3 +1,9 @@ +# github.com/InVisionApp/go-health/v2 v2.1.2 +## explicit; go 1.13 +github.com/InVisionApp/go-health/v2 +# github.com/InVisionApp/go-logger v1.0.1 +## explicit +github.com/InVisionApp/go-logger # github.com/KyleBanks/depth v1.2.1 ## explicit github.com/KyleBanks/depth @@ -245,7 +251,7 @@ github.com/theflyingcodr/govalidator ## explicit; go 1.17 github.com/theflyingcodr/lathos github.com/theflyingcodr/lathos/errs -# github.com/theflyingcodr/sockets v0.0.11-beta.0.20220222160101-76100ef886b5 +# github.com/theflyingcodr/sockets v0.0.11-beta.0.20220225103542-c6eecb16f586 ## explicit; go 1.17 github.com/theflyingcodr/sockets github.com/theflyingcodr/sockets/client