diff --git a/.travis.yml b/.travis.yml index 8a6b198..6b10ee8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,6 @@ before_install: install: - mkdir -p $GOPATH/bin - - make deps script: - make all diff --git a/Makefile b/Makefile index 37fc8b9..6b95619 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,12 @@ OK_COLOR=\033[32;01m NO_COLOR=\033[0m -all: deps lint test checks +all: deps lint test lint: @echo "$(OK_COLOR)==> Linting... $(NO_COLOR)" @go vet ./... -test: - @go test -v -cover ./... - deps: @echo "$(OK_COLOR)==> Installing dependencies $(NO_COLOR)" @go get -u gopkg.in/mgo.v2 @@ -18,15 +15,16 @@ deps: @go get -u github.com/streadway/amqp @go get -u github.com/garyburd/redigo/redis -checks: +test: + @echo "$(OK_COLOR)==> Running tests against container deps $(NO_COLOR)" @docker-compose up -d - @sleep 3 - @echo "$(OK_COLOR)==> Running checks tests against container deps $(NO_COLOR)" && \ + @sleep 3 && \ HEALTH_GO_PG_DSN="postgres://test:test@`docker-compose port postgres 5432`/test?sslmode=disable" \ HEALTH_GO_MQ_DSN="amqp://guest:guest@`docker-compose port rabbit 5672`/" \ HEALTH_GO_RD_DSN="redis://`docker-compose port redis 6379`/" \ HEALTH_GO_MG_DSN="`docker-compose port mongo 27017`/" \ HEALTH_GO_MS_DSN="test:test@tcp(`docker-compose port mysql 3306`)/test?charset=utf8" \ - go test -v -cover ./... + HEALTH_GO_HTTP_URL="http://`docker-compose port http 80`/status" \ + go test -cover ./... -.PHONY: all deps test lint checks +.PHONY: all deps test lint diff --git a/README.md b/README.md index b51fbe1..d79c954 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # health-go [![Build Status](https://travis-ci.com/hellofresh/health-go.svg?branch=master)](https://travis-ci.com/hellofresh/health-go) +[![Go Report Card](https://goreportcard.com/badge/github.com/hellofresh/health-go)](https://goreportcard.com/report/github.com/hellofresh/health-go) +[![Go Doc](https://godoc.org/github.com/hellofresh/health-go?status.svg)](https://godoc.org/github.com/hellofresh/health-go) * Exposes an HTTP handler that retrieves health status of the application * Implements some generic checkers for the following services: @@ -49,16 +51,7 @@ func main() { Timeout: time.Second * 2, SkipOnErr: false, Check: healthMysql.New(healthMysql.Config{ - DSN: "test:test@tcp(0.0.0.0:31726)/test?charset=utf8",, - Table: "health_check", - IDColumn: "id", - InsertColumnsFunc: func() map[string]interface{} { - return map[string]interface{}{ - "secret": time.Now().Unix(), - "extra": time.Now().Unix(), - "redirect_uri": "http://localhost", - } - }, + DSN: "test:test@tcp(0.0.0.0:31726)/test?charset=utf8", }, }) @@ -87,7 +80,7 @@ func main() { SkipOnErr: true, Check: func() error { // rabbitmq health check implementation goes here - }, + }), }) health.Register(health.Config{ @@ -103,15 +96,6 @@ func main() { SkipOnErr: false, Check: healthMysql.New(healthMysql.Config{ DSN: "test:test@tcp(0.0.0.0:31726)/test?charset=utf8", - Table: "health_check", - IDColumn: "id", - InsertColumnsFunc: func() map[string]interface{} { - return map[string]interface{}{ - "secret": time.Now().Unix(), - "extra": time.Now().Unix(), - "redirect_uri": "http://localhost", - } - }, }, }) @@ -121,6 +105,7 @@ func main() { } ``` +For more examples please check [here](https://github.com/hellofresh/health-go/blob/master/_examples/server.go) ## API Documentation ### `GET /status` @@ -192,13 +177,6 @@ HTTP/1.1 503 Service Unavailable - Push to the branch (`git push origin my-new-feature`) - Create new Pull Request -## Badges - -[![Build Status](https://travis-ci.org/hellofresh/health-go.svg?branch=master)](https://travis-ci.org/hellofresh/health-go) -[![Go Report Card](https://goreportcard.com/badge/github.com/hellofresh/health-go)](https://goreportcard.com/report/github.com/hellofresh/health-go) -[![Go Doc](https://godoc.org/github.com/hellofresh/health-go?status.svg)](https://godoc.org/github.com/hellofresh/health-go) - --- - > GitHub [@hellofresh](https://github.com/hellofresh)  ·  > Medium [@engineering.hellofresh](https://engineering.hellofresh.com) diff --git a/_examples/server.go b/_examples/server.go index e37e5d9..a5280b6 100644 --- a/_examples/server.go +++ b/_examples/server.go @@ -6,21 +6,56 @@ import ( "time" "github.com/hellofresh/health-go" + healthHttp "github.com/hellofresh/health-go/checks/http" + healthMySql "github.com/hellofresh/health-go/checks/mysql" + healthPg "github.com/hellofresh/health-go/checks/postgres" ) func main() { + // custom health check example (fail) health.Register(health.Config{ - Name: "rabbitmq", + Name: "some-custom-check-fail", Timeout: time.Second * 5, SkipOnErr: true, - Check: func() error { return errors.New("Failed duriung rabbitmq health check") }, + Check: func() error { return errors.New("failed during rabbitmq health check") }, }) + // custom health check example (success) health.Register(health.Config{ - Name: "mongodb", + Name: "some-custom-check-success", Check: func() error { return nil }, }) + // http health check example + health.Register(health.Config{ + Name: "http-check", + Timeout: time.Second * 5, + SkipOnErr: true, + Check: healthHttp.New(healthHttp.Config{ + URL: `http://example.com`, + }), + }) + + // postgres health check example + health.Register(health.Config{ + Name: "postgres-check", + Timeout: time.Second * 5, + SkipOnErr: true, + Check: healthPg.New(healthPg.Config{ + DSN: `postgres://test:test@0.0.0.0:32807/test?sslmode=disable`, + }), + }) + + // mysql health check example + health.Register(health.Config{ + Name: "mysql-check", + Timeout: time.Second * 5, + SkipOnErr: true, + Check: healthMySql.New(healthMySql.Config{ + DSN: `test:test@tcp(0.0.0.0:32802)/test?charset=utf8`, + }), + }) + http.Handle("/status", health.Handler()) http.ListenAndServe(":3000", nil) } diff --git a/checks/http/check_test.go b/checks/http/check_test.go new file mode 100644 index 0000000..29a4e25 --- /dev/null +++ b/checks/http/check_test.go @@ -0,0 +1,22 @@ +package http + +import ( + "os" + "testing" +) + +const httpURLEnv = "HEALTH_GO_HTTP_URL" + +func TestNew(t *testing.T) { + if os.Getenv(httpURLEnv) == "" { + t.SkipNow() + } + + check := New(Config{ + URL: os.Getenv(httpURLEnv), + }) + + if err := check(); err != nil { + t.Fatalf("HTTP check failed: %s", err.Error()) + } +} diff --git a/checks/mysql/check.go b/checks/mysql/check.go index a3f323c..941a0b2 100644 --- a/checks/mysql/check.go +++ b/checks/mysql/check.go @@ -2,9 +2,6 @@ package mysql import ( "database/sql" - "fmt" - "strings" - "errors" // MySQL database driver _ "github.com/go-sql-driver/mysql" @@ -15,17 +12,6 @@ type Config struct { // DSN is the MySQL instance connection DSN. Required. DSN string - // Table is the table name used for testing, must already exist in the DB and - // has insert/select/delete privileges for the connection user. Required. - Table string - - // IDColumn is the primary column for the table used for testing. Required. - IDColumn string - - // InsertColumnsFunc is the callback function that returns map[] - // for testing insert operation. Required. - InsertColumnsFunc func() map[string]interface{} - // LogFunc is the callback function for errors logging during check. // If not set logging is skipped. LogFunc func(err error, details string, extra ...interface{}) @@ -33,9 +19,8 @@ type Config struct { // New creates new MySQL health check that verifies the following: // - connection establishing -// - inserting a row into defined table -// - selecting inserted row -// - deleting inserted row +// - doing the ping command +// - selecting mysql version func New(config Config) func() error { if config.LogFunc == nil { config.LogFunc = func(err error, details string, extra ...interface{}) {} @@ -48,80 +33,24 @@ func New(config Config) func() error { return err } - columns := config.InsertColumnsFunc() - columnNames := []string{} - columnPlaceholders := []string{} - columnValues := []interface{}{} - i := 1 - for column, value := range columns { - columnNames = append(columnNames, column) - columnPlaceholders = append(columnPlaceholders, "?") - columnValues = append(columnValues, value) - - i++ - } - - insertQuery := fmt.Sprintf( - "INSERT INTO %s (%s) VALUES (%s)", - config.Table, - strings.Join(columnNames, ", "), - strings.Join(columnPlaceholders, ", "), - ) + defer func() { + if err = db.Close(); err != nil { + config.LogFunc(err, "MySQL health check failed during connection closing") + } + }() - result, err := db.Exec(insertQuery, columnValues...) + err = db.Ping() if err != nil { - config.LogFunc(err, "MySQL health check failed during insert") + config.LogFunc(err, "MySQL health check failed during ping") return err } - idValue, err := result.LastInsertId() - if err != nil { - config.LogFunc(err, "MySQL health check failed during retrieval of last inserted id") - return err - } - - selectQuery := fmt.Sprintf( - "SELECT %s FROM %s WHERE %s = ?", - strings.Join(columnNames, ", "), - config.Table, - config.IDColumn, - ) - selectRows, err := db.Query(selectQuery, idValue) + _, err = db.Query(`SELECT VERSION()`) if err != nil { config.LogFunc(err, "MySQL health check failed during select") return err } - if !selectRows.Next() { - config.LogFunc(err, "MySQL health check failed during checking select result rows") - return errors.New("looks like select result has 0 rows") - } - err = selectRows.Close() - if err != nil { - config.LogFunc(err, "MySQL health check failed during closing select result") - return err - } - - deleteQuery := fmt.Sprintf( - "DELETE FROM %s WHERE %s = ?", - config.Table, - config.IDColumn, - ) - deleteResult, err := db.Exec(deleteQuery, idValue) - if err != nil { - config.LogFunc(err, "MySQL health check failed during delete") - return err - } - deleted, err := deleteResult.RowsAffected() - if err != nil { - config.LogFunc(err, "MySQL health check failed during extracting delete result") - return err - } - if deleted < 1 { - config.LogFunc(err, "MySQL health check failed during checking delete result") - return errors.New("looks like delete removed 0 rows") - } - return nil } } diff --git a/checks/mysql/check_test.go b/checks/mysql/check_test.go index 68c06b7..eefa14c 100644 --- a/checks/mysql/check_test.go +++ b/checks/mysql/check_test.go @@ -3,7 +3,6 @@ package mysql import ( "os" "testing" - "time" ) const mysqlDSNEnv = "HEALTH_GO_MS_DSN" @@ -14,16 +13,7 @@ func TestNew(t *testing.T) { } check := New(Config{ - DSN: os.Getenv(mysqlDSNEnv), - Table: "test", - IDColumn: "id", - InsertColumnsFunc: func() map[string]interface{} { - return map[string]interface{}{ - "secret": time.Now().Format(time.RFC3339Nano), - "extra": time.Now().Format(time.RFC3339Nano), - "redirect_uri": "http://localhost", - } - }, + DSN: os.Getenv(mysqlDSNEnv), }) if err := check(); err != nil { diff --git a/checks/postgres/check.go b/checks/postgres/check.go index 4ece946..3f62d57 100644 --- a/checks/postgres/check.go +++ b/checks/postgres/check.go @@ -2,10 +2,6 @@ package postgres import ( "database/sql" - "errors" - "fmt" - "strings" - _ "github.com/lib/pq" ) @@ -13,24 +9,14 @@ import ( type Config struct { // DSN is the PostgreSQL instance connection DSN. Required. DSN string - // Table is the table name used for testing, must already exist in the DB and has insert/select/delete - // privileges for the connection user. Required. - Table string - // IDColumn is the primary column for the table used for testing. Required. - IDColumn string - // InsertColumnsFunc is the callback function that returns map[] for testing insert operation. - // Required. - InsertColumnsFunc func() map[string]interface{} - // LogFunc is the callback function for errors logging during check. // If not set logging is skipped. LogFunc func(err error, details string, extra ...interface{}) } // New creates new PostgreSQL health check that verifies the following: // - connection establishing -// - inserting a row into defined table -// - selecting inserted row -// - deleting inserted row +// - doing the ping command +// - selecting postgres version func New(config Config) func() error { if config.LogFunc == nil { config.LogFunc = func(err error, details string, extra ...interface{}) {} @@ -49,74 +35,17 @@ func New(config Config) func() error { } }() - columns := config.InsertColumnsFunc() - columnNames := []string{} - columnPlaceholders := []string{} - columnValues := []interface{}{} - i := 1 - for column, value := range columns { - columnNames = append(columnNames, column) - columnPlaceholders = append(columnPlaceholders, fmt.Sprintf("$%d", i)) - columnValues = append(columnValues, value) - - i++ - } - - insertQuery := fmt.Sprintf( - "INSERT INTO %s (%s) VALUES (%s) RETURNING %s", - config.Table, - strings.Join(columnNames, ", "), - strings.Join(columnPlaceholders, ", "), - config.IDColumn, - ) - - var idValue interface{} - err = db.QueryRow(insertQuery, columnValues...).Scan(&idValue) + err = db.Ping() if err != nil { - config.LogFunc(err, "PostgreSQL health check failed during insert and scan") + config.LogFunc(err, "PostgreSQL health check failed during ping") return err } - selectQuery := fmt.Sprintf( - "SELECT %s FROM %s WHERE %s = $1", - strings.Join(columnNames, ", "), - config.Table, - config.IDColumn, - ) - selectRows, err := db.Query(selectQuery, idValue) + _, err = db.Query(`SELECT VERSION()`) if err != nil { config.LogFunc(err, "PostgreSQL health check failed during select") return err } - if !selectRows.Next() { - config.LogFunc(err, "PostgreSQL health check failed during checking select result rows") - return errors.New("looks like select result has 0 rows") - } - err = selectRows.Close() - if err != nil { - config.LogFunc(err, "PostgreSQL health check failed during closing select result") - return err - } - - deleteQuery := fmt.Sprintf( - "DELETE FROM %s WHERE %s = $1", - config.Table, - config.IDColumn, - ) - deleteResult, err := db.Exec(deleteQuery, idValue) - if err != nil { - config.LogFunc(err, "PostgreSQL health check failed during delete") - return err - } - deleted, err := deleteResult.RowsAffected() - if err != nil { - config.LogFunc(err, "PostgreSQL health check failed during extracting delete result") - return err - } - if deleted < 1 { - config.LogFunc(err, "PostgreSQL health check failed during checking delete result") - return errors.New("looks like delete removed 0 rows") - } return nil } diff --git a/checks/postgres/check_test.go b/checks/postgres/check_test.go index 659497d..d56f7b3 100644 --- a/checks/postgres/check_test.go +++ b/checks/postgres/check_test.go @@ -3,7 +3,6 @@ package postgres import ( "os" "testing" - "time" ) const pgDSNEnv = "HEALTH_GO_PG_DSN" @@ -14,17 +13,7 @@ func TestNew(t *testing.T) { } check := New(Config{ - DSN: os.Getenv(pgDSNEnv), - Table: "test", - IDColumn: "id", - InsertColumnsFunc: func() map[string]interface{} { - return map[string]interface{}{ - "id": time.Now().Format(time.RFC3339Nano), - "secret": time.Now().Format(time.RFC3339Nano), - "extra": time.Now().Format(time.RFC3339Nano), - "redirect_uri": "http://localhost", - } - }, + DSN: os.Getenv(pgDSNEnv), }) if err := check(); err != nil { diff --git a/docker-compose.yml b/docker-compose.yml index 4ad6767..65187e0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,3 +38,8 @@ services: MYSQL_DATABASE: test MYSQL_USER: test MYSQL_PASSWORD: test + + http: + image: pierreprinetti/apimock:latest + ports: + - "80"