From 5faed5c8ba5ce5c4b807017104e7ca17700e32bc Mon Sep 17 00:00:00 2001 From: Tobias Persson Date: Tue, 24 Aug 2021 10:22:47 +0200 Subject: [PATCH 1/9] Base implementation of the structure of Eiffel-Goer. Only one handler and endpoint implemented currently, this is the v1alpha1/events/id/ endpoint. The other endpoints shall be implemented in future PRs Only added support for MongoDB, more databases can be implemented easily in the future. No tests for the MongoDB implementation. I will add them after this PR. --- .gitignore | 12 + Makefile | 27 ++ api/openapi-spec.yaml | 473 +++++++++++++++++++ cmd/goer/main.go | 40 ++ configs/development.env | 3 + deploy/goer/Dockerfile | 9 + deploy/goer/Dockerfile.dev | 10 + deploy/goer/docker-compose.yml | 35 ++ go.mod | 13 + go.sum | 126 +++++ internal/config/config.go | 62 +++ internal/config/config_test.go | 75 +++ internal/database/database.go | 57 +++ internal/database/database_test.go | 39 ++ internal/database/drivers/mongodb/mongodb.go | 99 ++++ internal/logger/logger.go | 30 ++ internal/responses/responses.go | 35 ++ internal/responses/responses_test.go | 62 +++ pkg/application/application.go | 95 ++++ pkg/application/application_test.go | 242 ++++++++++ pkg/schema/schema.go | 66 +++ pkg/server/server.go | 114 +++++ pkg/server/server_test.go | 111 +++++ pkg/v1alpha1/api/api.go | 41 ++ pkg/v1alpha1/api/api_test.go | 147 ++++++ pkg/v1alpha1/handlers/events/events.go | 68 +++ pkg/v1alpha1/handlers/events/events_test.go | 148 ++++++ pkg/v1alpha1/handlers/search/search.go | 48 ++ pkg/v1alpha1/handlers/search/test_search.go | 16 + scripts/entrypoint.sh | 8 + test/test.go | 22 + 31 files changed, 2333 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 api/openapi-spec.yaml create mode 100644 cmd/goer/main.go create mode 100644 configs/development.env create mode 100644 deploy/goer/Dockerfile create mode 100644 deploy/goer/Dockerfile.dev create mode 100644 deploy/goer/docker-compose.yml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/config/config.go create mode 100644 internal/config/config_test.go create mode 100644 internal/database/database.go create mode 100644 internal/database/database_test.go create mode 100644 internal/database/drivers/mongodb/mongodb.go create mode 100644 internal/logger/logger.go create mode 100644 internal/responses/responses.go create mode 100644 internal/responses/responses_test.go create mode 100644 pkg/application/application.go create mode 100644 pkg/application/application_test.go create mode 100644 pkg/schema/schema.go create mode 100644 pkg/server/server.go create mode 100644 pkg/server/server_test.go create mode 100644 pkg/v1alpha1/api/api.go create mode 100644 pkg/v1alpha1/api/api_test.go create mode 100644 pkg/v1alpha1/handlers/events/events.go create mode 100644 pkg/v1alpha1/handlers/events/events_test.go create mode 100644 pkg/v1alpha1/handlers/search/search.go create mode 100644 pkg/v1alpha1/handlers/search/test_search.go create mode 100644 scripts/entrypoint.sh create mode 100644 test/test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..18a97e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +.env +bin +!bin/README.md +data/ +vendor/ +.vscode/ +pkg/**/**/test +cmd/**/**/test +internal/**/**/test +testdata/ +test/testdata/ +test/mock_* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..076611c --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +export RELEASE_VERSION ?= $(shell git show -q --format=%h) +export DOCKER_REGISTRY ?= registry.nordix.org/eiffel +export DEPLOY ?= goer + +all: test build start +gen: + go generate ./... +build: gen + go build -o bin/goer ./cmd/goer +clean: + rm ./bin/* || true + docker-compose --project-directory . -f deploy/$(DEPLOY)/docker-compose.yml rm || true + docker volume rm goer-volume || true +test: gen + go test -cover -timeout 30s -race $(shell go list ./... | grep -v test) + +# Start a development docker with a database that restarts on file changes. +start: + docker-compose --project-directory . -f deploy/$(DEPLOY)/docker-compose.yml up +stop: + docker-compose --project-directory . -f deploy/$(DEPLOY)/docker-compose.yml down + +# Build a docker using the production Dockerfile +docker: + docker build -t $(DOCKER_REGISTRY)/$(DEPLOY):$(RELEASE_VERSION) -f ./deploy/$(DEPLOY)/Dockerfile . +push: + docker push $(DOCKER_REGISTRY)/$(DEPLOY):$(RELEASE_VERSION) diff --git a/api/openapi-spec.yaml b/api/openapi-spec.yaml new file mode 100644 index 0000000..059518b --- /dev/null +++ b/api/openapi-spec.yaml @@ -0,0 +1,473 @@ +openapi: 3.0.1 +info: + title: Event Repository REST API + description: Event Repository REST API for retrieving the event information + contact: {} + version: 0.0.1 +tags: +- name: event-resource + description: The Event Resource API for getting single event information +- name: search-resource + description: The search api provides methods for querying up/down stream given an + event id. +- name: events-resource + description: The Events Resource API for getting all events information +paths: + /events: + get: + tags: + - events-resource + summary: To get all events information + operationId: getEventsUsingGET + parameters: + - name: pageNo + in: query + description: "Page to display if results span across multiple pages." + schema: + type: integer + format: int32 + default: 1 + - name: pageSize + in: query + description: "The number of events to be displayed per page." + schema: + type: integer + format: int32 + default: 500 + - name: pageStartItem + in: query + description: "Intended to skip few items at the start of result, Should\ + \ be used only if `pageNo=1` ie. first page of results." + schema: + type: integer + format: int32 + default: 1 + - name: shallow + in: query + description: "Determines if external ER's should be used to compile\ + \ the results of query. Use `false` to use External ER's." + schema: + type: boolean + default: false + - name: lazy + in: query + description: "If lazy is `true`, it implies that when the events limit\ + \ is reached according to pazesize no additional request is performed and the search will stop." + schema: + type: boolean + default: false + - name: readable + in: query + description: | + Determines if event time should be in milliseconds, or in Human readable time format. Ex: + + `false` 1499076742982 + + `true` 2018-10-31T13:36:00.824Z. + schema: + type: boolean + default: false + - name: params + in: query + description: | + To search for specific events or artifacts, filtering with parameters is supported. + + **Syntax:** + + `?key[.key ...]value[&key[.key ...]value ...]` + + is one of the Filter comparators described below. To traverse into nested structures and filter on their keys, namespacing with + `.` (dot) is used. + + ``` + = - equal to + > - greater than + < - less than + >= - greater than or equal to + <= - less than or equal to + != - not equal to + ``` + + **Examples, single key:** + + `/events/?meta.type=EiffelActivityStartedEvent` + + **Examples multiple keys:** + + ``` + /events/?key1=value1&key2=value2 + /events/?key1>=value1&key2!=value2&key3value1&key1<=value2&key1!=value3 #Multiple constraints on one key + ``` + + **Examples nested structures:** + ``` + /events/?data.identity=pkg:maven/my.namespace/my-name@1.0.0 + /events/?meta.source.domainId=my.domain&data.identity=pkg:maven/my.namespace/my-name@1.0.0 #Multiple keys and nested structures + ``` + + Note that mutiple keys only is allowed with logical AND (via `&`). There are no support for logical OR. + + Filters also allow for regex to be used. This means you can make more complex matchings or use partial matching. In regex `.*` matches anything and basically works like wildcards. + + **Examples of regex:** + ``` + /events/?data.identity=my-artifact@1.0.0 #Partial match using artifact name and version + /events/?data.identity=my.namespace #Partial match using artifact namespace + /events/?data.identity=my.namespace/.*@1.0.0 #Matches any version 1.0.0 artifact within the namespace 'my.namespace' + /events/?data.identity=my.namespace/my-name@.* #Matches any version of the artifact 'my-artifact' within the namepsace 'my-namespace' + /events/?data.identity=my.namespace/.* #Matches any artifact within the namespace 'my.namespace' + ``` + **Data types:** + + By default, all data types are treated as strings. It is possible to specify the data type by including it explicitly in the query: + + ``` + /events/?int(key1)>=value1&key2!=value2&double(key3) Date: Thu, 26 Aug 2021 17:01:08 +0200 Subject: [PATCH 2/9] Updates after review comments. * Removed the 'Get' prefix on all getters in config. * Changed to 'git describe --always' for tags in makefile. * Removed mockgen from Dockerfile. * Changed CMD to ENTRYPOINT in Dockerfile. * Moved COPY to the last operation in Dockerfile. * Added newline at the end of Dockerfile. * Removed database name configuration option. * MongoDB now uses the connection URL for db name. * Merged 'get' and 'Get' functions in database.go. * Cleaned up import order in all files. * Now pass the contexts around instead of storing it in DB drivers. * Changed to table-driven testing in events and api tests. * Updated documentation to begin with the identifier. * Removed 'readable' parameter for now. --- Makefile | 2 +- README.md | 2 +- cmd/goer/main.go | 7 +- configs/development.env | 1 - deploy/goer/Dockerfile | 3 +- internal/config/config.go | 26 +--- internal/config/config_test.go | 27 +--- internal/database/database.go | 27 ++-- internal/database/database_test.go | 6 +- internal/database/drivers/mongodb/mongodb.go | 40 ++--- internal/responses/responses.go | 4 +- pkg/application/application.go | 28 ++-- pkg/application/application_test.go | 60 ++++---- pkg/server/server.go | 12 +- pkg/server/server_test.go | 4 +- pkg/v1alpha1/api/api.go | 3 +- pkg/v1alpha1/api/api_test.go | 147 +++++-------------- pkg/v1alpha1/handlers/events/events.go | 16 +- pkg/v1alpha1/handlers/events/events_test.go | 131 ++++++----------- pkg/v1alpha1/handlers/search/search.go | 6 +- 20 files changed, 208 insertions(+), 344 deletions(-) diff --git a/Makefile b/Makefile index 076611c..219a53d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -export RELEASE_VERSION ?= $(shell git show -q --format=%h) +export RELEASE_VERSION ?= $(shell git describe --always) export DOCKER_REGISTRY ?= registry.nordix.org/eiffel export DEPLOY ?= goer diff --git a/README.md b/README.md index c175d1e..0b0be57 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Eiffel Goer implements the event repository API and is intended as an open sourc ### Docker - docker run -e CONNECTION_STRING=yourdb -e DATABASE_NAME=dbname -e API_PORT=8080 registry.nordix.org/eiffel/goer + docker run -e CONNECTION_STRING=yourdb -e API_PORT=8080 registry.nordix.org/eiffel/goer ### Running a development server locally for testing. Will restart on code changes. diff --git a/cmd/goer/main.go b/cmd/goer/main.go index 4cd5756..5620e67 100644 --- a/cmd/goer/main.go +++ b/cmd/goer/main.go @@ -16,6 +16,8 @@ package main import ( + "context" + "github.com/eiffel-community/eiffel-goer/internal/config" "github.com/eiffel-community/eiffel-goer/internal/logger" "github.com/eiffel-community/eiffel-goer/pkg/application" @@ -23,8 +25,8 @@ import ( // Start up the Goer application. func main() { - logger.Debug.Println("Starting up.") cfg := config.Get() + ctx := context.Background() app, err := application.Get(cfg) if err != nil { @@ -33,7 +35,8 @@ func main() { app.LoadV1Alpha1Routes() - err = app.Start() + logger.Debug.Println("Starting up.") + err = app.Start(ctx) if err != nil { logger.Error.Panic(err) } diff --git a/configs/development.env b/configs/development.env index 2adcaf3..2118f56 100644 --- a/configs/development.env +++ b/configs/development.env @@ -1,3 +1,2 @@ CONNECTION_STRING=mongodb://db:27017/eiffel -DATABASE_NAME=eiffel API_PORT=9090 diff --git a/deploy/goer/Dockerfile b/deploy/goer/Dockerfile index 0ea1fd7..cbcca40 100644 --- a/deploy/goer/Dockerfile +++ b/deploy/goer/Dockerfile @@ -1,9 +1,8 @@ FROM golang:1.16.3-alpine AS build WORKDIR /tmp/goer COPY . . -RUN go install github.com/golang/mock/mockgen@v1.6.0 RUN go build -o ./bin/goer ./cmd/goer FROM alpine:3.13.5 +ENTRYPOINT ["/app/goer"] COPY --from=build /tmp/goer/bin/goer /app/goer -CMD ["/app/goer"] \ No newline at end of file diff --git a/internal/config/config.go b/internal/config/config.go index 9ab21a6..22eb540 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -21,42 +21,32 @@ import ( ) type Config interface { - GetDBConnectionString() string - GetDatabaseName() string - GetAPIPort() string + DBConnectionString() string + APIPort() string } type Cfg struct { connectionString string - databaseName string - - apiPort string + apiPort string } -// Parse input parameters to program and return a config with them set. +// Get parses input parameters to program and return a config with them set. func Get() Config { conf := &Cfg{} flag.StringVar(&conf.connectionString, "connectionstring", os.Getenv("CONNECTION_STRING"), "Database connection string.") - flag.StringVar(&conf.databaseName, "databasename", os.Getenv("DATABASE_NAME"), "Database name.") - flag.StringVar(&conf.apiPort, "apiport", os.Getenv("API_PORT"), "API port.") flag.Parse() return conf } -// Get the connection string for a database. -func (c *Cfg) GetDBConnectionString() string { +// DBConnectionString returns the connection string for a database. +func (c *Cfg) DBConnectionString() string { return c.connectionString } -// Get the name of the database to connect to. -func (c *Cfg) GetDatabaseName() string { - return c.databaseName -} - -// Get API port with a ":" prepended. -func (c *Cfg) GetAPIPort() string { +// APIPort returns the API port with a ":" prepended. +func (c *Cfg) APIPort() string { return ":" + c.apiPort } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 7b3af24..fe8e299 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -24,9 +24,7 @@ import ( func TestGet(t *testing.T) { port := "8080" connectionString := "connection string" - databaseName := "database name" os.Setenv("CONNECTION_STRING", connectionString) - os.Setenv("DATABASE_NAME", databaseName) os.Setenv("API_PORT", port) cfg, ok := Get().(*Cfg) @@ -36,40 +34,27 @@ func TestGet(t *testing.T) { if cfg.connectionString != connectionString { t.Error("connection string not set to environment variable CONNECTION_STRING") } - if cfg.databaseName != databaseName { - t.Error("database name not set to environment variable DATABASE_NAME") - } if cfg.apiPort != port { t.Error("api port not set to environment variable API_PORT") } } -// Test that GetDBConnectionString return the connectionString value from the Cfg struct -func TestGetDBConnectionString(t *testing.T) { +// Test that DBConnectionString return the connectionString value from the Cfg struct +func TestDBConnectionString(t *testing.T) { cfg := &Cfg{ connectionString: "connectionString", } - if cfg.GetDBConnectionString() != "connectionString" { + if cfg.DBConnectionString() != "connectionString" { t.Error("function does not return the connectionString from Cfg struct") } } -// Test that GetDatabaseName return the databaseName value from the Cfg struct -func TestGetDatabaseName(t *testing.T) { - cfg := &Cfg{ - databaseName: "databaseName", - } - if cfg.GetDatabaseName() != "databaseName" { - t.Error("function does not return the databaseName from Cfg struct") - } -} - -// Test that GetAPIPort return the value from Cfg struct with a ':' at the start -func TestGetAPIPort(t *testing.T) { +// Test that APIPort return the value from Cfg struct with a ':' at the start +func TestAPIPort(t *testing.T) { cfg := &Cfg{ apiPort: "8080", } - if cfg.GetAPIPort() != ":8080" { + if cfg.APIPort() != ":8080" { t.Error("function does not return the apiPort from Cfg struct") } } diff --git a/internal/database/database.go b/internal/database/database.go index 397499f..711abc8 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -26,32 +26,23 @@ import ( type Database interface { Connect(context.Context) error - GetEvents() ([]schema.EiffelEvent, error) - SearchEvent(string) (schema.EiffelEvent, error) - UpstreamDownstreamSearch(string) ([]schema.EiffelEvent, error) - GetEventByID(string) (schema.EiffelEvent, error) - Close() error + GetEvents(context.Context) ([]schema.EiffelEvent, error) + SearchEvent(context.Context, string) (schema.EiffelEvent, error) + UpstreamDownstreamSearch(context.Context, string) ([]schema.EiffelEvent, error) + GetEventByID(context.Context, string) (schema.EiffelEvent, error) + Close(context.Context) error } // Get a new Database. -func Get(connectionString string, databaseName string) (Database, error) { - db, err := get(connectionString, databaseName) - if err != nil { - return nil, err - } - var database Database = db - return database, nil -} - -// Get a database driver based on the connectionURL scheme supplied in the configuration. -func get(connectionString string, databaseName string) (Database, error) { +func Get(connectionString string) (Database, error) { connectionURL, err := url.Parse(connectionString) if err != nil { return nil, err } switch connectionURL.Scheme { case "mongodb": - return mongodb.Get(connectionString, databaseName) + return mongodb.Get(connectionURL) + default: + return nil, fmt.Errorf("cannot find database for scheme %q", connectionURL.Scheme) } - return nil, fmt.Errorf("cannot find database for scheme '%s'", connectionURL.Scheme) } diff --git a/internal/database/database_test.go b/internal/database/database_test.go index 7f06b82..d4476c4 100644 --- a/internal/database/database_test.go +++ b/internal/database/database_test.go @@ -18,21 +18,21 @@ package database import "testing" func TestGet(t *testing.T) { - _, err := Get("mongodb://test", "test") + _, err := Get("mongodb://db/test") if err != nil { t.Error(err) } } func TestGetUnknownScheme(t *testing.T) { - _, err := Get("unknown://test", "test") + _, err := Get("unknown://db/test") if err == nil { t.Error("possible to get a database with unknown:// scheme") } } func TestGetUnparsableScheme(t *testing.T) { - _, err := Get("://", "test") + _, err := Get("://") if err == nil { t.Error("possible to get a database with an unparsable scheme") } diff --git a/internal/database/drivers/mongodb/mongodb.go b/internal/database/drivers/mongodb/mongodb.go index 4e391bd..11d3a8e 100644 --- a/internal/database/drivers/mongodb/mongodb.go +++ b/internal/database/drivers/mongodb/mongodb.go @@ -23,26 +23,30 @@ import ( "context" "errors" "fmt" + "net/url" + "strings" - "github.com/eiffel-community/eiffel-goer/pkg/schema" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/readpref" + + "github.com/eiffel-community/eiffel-goer/pkg/schema" ) type MongoDB struct { Client *mongo.Client Database *mongo.Database - Context context.Context } // Get creates a new database.Database interface against MongoDB. -func Get(connectionString, databaseName string) (*MongoDB, error) { - client, err := mongo.NewClient(options.Client().ApplyURI(connectionString)) +func Get(connectionURL *url.URL) (*MongoDB, error) { + client, err := mongo.NewClient(options.Client().ApplyURI(connectionURL.String())) if err != nil { return nil, err } + // The Path value from url.URL always has a '/' prepended. + databaseName := strings.Split(connectionURL.Path, "/")[1] return &MongoDB{ Client: client, Database: client.Database(databaseName), @@ -55,35 +59,34 @@ func (m *MongoDB) Connect(ctx context.Context) error { if err != nil { return err } - m.Context = ctx return m.Client.Ping(ctx, readpref.Primary()) } -// Get all events information. -func (m *MongoDB) GetEvents() ([]schema.EiffelEvent, error) { +// GetEvents gets all events information. +func (m *MongoDB) GetEvents(ctx context.Context) ([]schema.EiffelEvent, error) { return nil, errors.New("not yet implemented") } -// Search for event based on event ID. -func (m *MongoDB) SearchEvent(id string) (schema.EiffelEvent, error) { +// SearchEvent searches for an event based on event ID. +func (m *MongoDB) SearchEvent(ctx context.Context, id string) (schema.EiffelEvent, error) { return schema.EiffelEvent{}, errors.New("not yet implemented") } -// Upstream/Downstream search for events. -func (m *MongoDB) UpstreamDownstreamSearch(id string) ([]schema.EiffelEvent, error) { +// UpstreamDownstreamSearch searches for events upstream and/or downstream of event by ID. +func (m *MongoDB) UpstreamDownstreamSearch(ctx context.Context, id string) ([]schema.EiffelEvent, error) { return nil, errors.New("not yet implemented") } -// Get an event by ID in all collections. -func (m *MongoDB) GetEventByID(id string) (schema.EiffelEvent, error) { - collections, err := m.Database.ListCollectionNames(m.Context, bson.D{}) +// GetEventByID gets an event by ID in all collections. +func (m *MongoDB) GetEventByID(ctx context.Context, id string) (schema.EiffelEvent, error) { + collections, err := m.Database.ListCollectionNames(ctx, bson.D{}) if err != nil { return schema.EiffelEvent{}, err } filter := bson.D{{"meta.id", id}} for _, collection := range collections { var event schema.EiffelEvent - singleResult := m.Database.Collection(collection).FindOne(m.Context, filter) + singleResult := m.Database.Collection(collection).FindOne(ctx, filter) err := singleResult.Decode(&event) if err != nil { continue @@ -91,9 +94,10 @@ func (m *MongoDB) GetEventByID(id string) (schema.EiffelEvent, error) { return event, nil } } - return schema.EiffelEvent{}, fmt.Errorf("'%s' not found in any collection", id) + return schema.EiffelEvent{}, fmt.Errorf("%q not found in any collection", id) } -func (m *MongoDB) Close() error { - return m.Client.Disconnect(m.Context) +// Close the database connection. +func (m *MongoDB) Close(ctx context.Context) error { + return m.Client.Disconnect(ctx) } diff --git a/internal/responses/responses.go b/internal/responses/responses.go index 6890462..f0d0704 100644 --- a/internal/responses/responses.go +++ b/internal/responses/responses.go @@ -20,7 +20,7 @@ import ( "net/http" ) -// Write a JSON response with a status code to the HTTP ResponseWriter. +// RespondWithJSON writes a JSON response with a status code to the HTTP ResponseWriter. func RespondWithJSON(w http.ResponseWriter, code int, payload interface{}) { response, _ := json.Marshal(payload) @@ -29,7 +29,7 @@ func RespondWithJSON(w http.ResponseWriter, code int, payload interface{}) { w.Write(response) } -// Write a JSON response with an error message and status code to the HTTP ResponseWriter. +// RespondWithError writes a JSON response with an error message and status code to the HTTP ResponseWriter. func RespondWithError(w http.ResponseWriter, code int, message string) { RespondWithJSON(w, code, map[string]string{"error": message}) } diff --git a/pkg/application/application.go b/pkg/application/application.go index deac43a..070afd2 100644 --- a/pkg/application/application.go +++ b/pkg/application/application.go @@ -18,11 +18,12 @@ package application import ( "context" + "github.com/gorilla/mux" + "github.com/eiffel-community/eiffel-goer/internal/config" "github.com/eiffel-community/eiffel-goer/internal/database" "github.com/eiffel-community/eiffel-goer/pkg/server" v1alpha1 "github.com/eiffel-community/eiffel-goer/pkg/v1alpha1/api" - "github.com/gorilla/mux" ) type Application struct { @@ -33,14 +34,14 @@ type Application struct { V1Alpha1 *v1alpha1.V1Alpha1Application } -// Create a new Goer application. +// Get a new Goer application. func Get(cfg config.Config) (*Application, error) { application := &Application{ Config: cfg, Router: mux.NewRouter(), Server: server.Get(), } - if cfg.GetDBConnectionString() != "" { + if cfg.DBConnectionString() != "" { db, err := application.getDB() if err != nil { return nil, err @@ -50,11 +51,10 @@ func Get(cfg config.Config) (*Application, error) { return application, nil } -// Get, but don't connect to, a database. +// getDB gets, but does not connect to, a database. func (app *Application) getDB() (database.Database, error) { db, err := database.Get( - app.Config.GetDBConnectionString(), - app.Config.GetDatabaseName(), + app.Config.DBConnectionString(), ) if err != nil { return nil, err @@ -62,7 +62,7 @@ func (app *Application) getDB() (database.Database, error) { return db, nil } -// Load routes for the /v1alpha1/ endpoint. +// LoadV1Alpha1Routes loads routes for the /v1alpha1/ endpoint. func (app *Application) LoadV1Alpha1Routes() { app.V1Alpha1 = &v1alpha1.V1Alpha1Application{ Config: app.Config, @@ -72,15 +72,15 @@ func (app *Application) LoadV1Alpha1Routes() { app.V1Alpha1.AddRoutes(subrouter) } -// Connect to the database and start the webserver. +// Start connects to the database and starts the webserver. // This is a blocking function, waiting for the webserver to shut down. -func (app *Application) Start() error { - srv := app.Server.WithAddr(app.Config.GetAPIPort()).WithRouter(app.Router) - err := app.Database.Connect(context.Background()) +func (app *Application) Start(ctx context.Context) error { + srv := app.Server.WithAddr(app.Config.APIPort()).WithRouter(app.Router) + err := app.Database.Connect(ctx) if err != nil { return err } - defer app.Stop() + defer app.Stop(ctx) err = srv.Start() if err != nil { return err @@ -90,6 +90,6 @@ func (app *Application) Start() error { } // Stop the application and close the database connection. -func (app *Application) Stop() error { - return app.Database.Close() +func (app *Application) Stop(ctx context.Context) error { + return app.Database.Close(ctx) } diff --git a/pkg/application/application_test.go b/pkg/application/application_test.go index e10b0ee..e86952b 100644 --- a/pkg/application/application_test.go +++ b/pkg/application/application_test.go @@ -16,22 +16,23 @@ package application import ( + "context" "errors" "testing" + "github.com/golang/mock/gomock" + "github.com/eiffel-community/eiffel-goer/internal/database" "github.com/eiffel-community/eiffel-goer/test/mock_config" "github.com/eiffel-community/eiffel-goer/test/mock_database" "github.com/eiffel-community/eiffel-goer/test/mock_server" - "github.com/golang/mock/gomock" ) // Test that it is possible to get an application. func TestGet(t *testing.T) { ctrl := gomock.NewController(t) mockCfg := mock_config.NewMockConfig(ctrl) - mockCfg.EXPECT().GetDBConnectionString().Return("mongodb://testdb").Times(2) - mockCfg.EXPECT().GetDatabaseName().Return("testdb") + mockCfg.EXPECT().DBConnectionString().Return("mongodb://testdb/testdb").Times(2) app, err := Get(mockCfg) if err != nil { @@ -55,7 +56,7 @@ func TestGet(t *testing.T) { func TestGetNoDB(t *testing.T) { ctrl := gomock.NewController(t) mockCfg := mock_config.NewMockConfig(ctrl) - mockCfg.EXPECT().GetDBConnectionString().Return("") + mockCfg.EXPECT().DBConnectionString().Return("") app, err := Get(mockCfg) if err != nil { @@ -79,8 +80,7 @@ func TestGetNoDB(t *testing.T) { func TestGetDBError(t *testing.T) { ctrl := gomock.NewController(t) mockCfg := mock_config.NewMockConfig(ctrl) - mockCfg.EXPECT().GetDBConnectionString().Return("invalid").Times(2) - mockCfg.EXPECT().GetDatabaseName().Return("testdb") + mockCfg.EXPECT().DBConnectionString().Return("invalid://testdb").Times(2) _, err := Get(mockCfg) if err == nil { @@ -92,8 +92,7 @@ func TestGetDBError(t *testing.T) { func TestGetDB(t *testing.T) { ctrl := gomock.NewController(t) mockCfg := mock_config.NewMockConfig(ctrl) - mockCfg.EXPECT().GetDBConnectionString().Return("mongodb://testdb").Times(2) - mockCfg.EXPECT().GetDatabaseName().Return("testdb") + mockCfg.EXPECT().DBConnectionString().Return("mongodb://testdb/testdb").Times(2) application := &Application{ Config: mockCfg, @@ -112,8 +111,7 @@ func TestGetDB(t *testing.T) { func TestLoadV1Alpha1Routes(t *testing.T) { ctrl := gomock.NewController(t) mockCfg := mock_config.NewMockConfig(ctrl) - mockCfg.EXPECT().GetDBConnectionString().Return("mongodb://testdb").Times(2) - mockCfg.EXPECT().GetDatabaseName().Return("testdb") + mockCfg.EXPECT().DBConnectionString().Return("mongodb://testdb/testdb").Times(2) app, err := Get(mockCfg) if err != nil { t.Error(err) @@ -132,18 +130,18 @@ func TestStart(t *testing.T) { mockCfg := mock_config.NewMockConfig(ctrl) mockDB := mock_database.NewMockDatabase(ctrl) mockServer := mock_server.NewMockServer(ctrl) + ctx := context.Background() - mockCfg.EXPECT().GetDBConnectionString().Return("mongodb://testdb").Times(2) - mockCfg.EXPECT().GetDatabaseName().Return("testdb") - mockCfg.EXPECT().GetAPIPort().Return(":8080") + mockCfg.EXPECT().DBConnectionString().Return("mongodb://testdb/testdb").Times(2) + mockCfg.EXPECT().APIPort().Return(":8080") app, err := Get(mockCfg) if err != nil { t.Error(err) } - mockDB.EXPECT().Connect(gomock.Any()).Return(nil) - mockDB.EXPECT().Close().Return(nil) + mockDB.EXPECT().Connect(ctx).Return(nil) + mockDB.EXPECT().Close(ctx).Return(nil) mockServer.EXPECT().WithAddr(":8080").Return(mockServer) mockServer.EXPECT().WithRouter(app.Router).Return(mockServer) mockServer.EXPECT().Start().Return(nil) @@ -153,7 +151,7 @@ func TestStart(t *testing.T) { app.Database = mockDB app.Server = mockServer - err = app.Start() + err = app.Start(ctx) if err != nil { t.Error(err) } @@ -166,10 +164,10 @@ func TestStartAbort(t *testing.T) { mockCfg := mock_config.NewMockConfig(ctrl) mockDB := mock_database.NewMockDatabase(ctrl) mockServer := mock_server.NewMockServer(ctrl) + ctx := context.Background() - mockCfg.EXPECT().GetDBConnectionString().Return("mongodb://testdb").Times(2) - mockCfg.EXPECT().GetDatabaseName().Return("testdb") - mockCfg.EXPECT().GetAPIPort().Return(":8080") + mockCfg.EXPECT().DBConnectionString().Return("mongodb://testdb/testdb").Times(2) + mockCfg.EXPECT().APIPort().Return(":8080") app, err := Get(mockCfg) if err != nil { @@ -180,12 +178,12 @@ func TestStartAbort(t *testing.T) { mockServer.EXPECT().WithRouter(app.Router).Return(mockServer) mockServer.EXPECT().WaitStopped().Return(true) mockServer.EXPECT().Error().Return(nil) - mockDB.EXPECT().Connect(gomock.Any()).Return(errors.New("did not work")) + mockDB.EXPECT().Connect(ctx).Return(errors.New("did not work")) app.Database = mockDB app.Server = mockServer - err = app.Start() + err = app.Start(ctx) if err == nil { t.Error("application did not abort start after error on database.Connect") } @@ -197,18 +195,18 @@ func TestStartFail(t *testing.T) { mockCfg := mock_config.NewMockConfig(ctrl) mockDB := mock_database.NewMockDatabase(ctrl) mockServer := mock_server.NewMockServer(ctrl) + ctx := context.Background() - mockCfg.EXPECT().GetDBConnectionString().Return("mongodb://testdb").Times(2) - mockCfg.EXPECT().GetDatabaseName().Return("testdb") - mockCfg.EXPECT().GetAPIPort().Return("") + mockCfg.EXPECT().DBConnectionString().Return("mongodb://testdb/testdb").Times(2) + mockCfg.EXPECT().APIPort().Return("") app, err := Get(mockCfg) if err != nil { t.Error(err) } - mockDB.EXPECT().Connect(gomock.Any()).Return(nil) - mockDB.EXPECT().Close().Return(nil) + mockDB.EXPECT().Connect(ctx).Return(nil) + mockDB.EXPECT().Close(ctx).Return(nil) mockServer.EXPECT().WithAddr("").Return(mockServer) mockServer.EXPECT().WithRouter(app.Router).Return(mockServer) mockServer.EXPECT().Start().Return(errors.New("error starting")) @@ -216,7 +214,7 @@ func TestStartFail(t *testing.T) { app.Database = mockDB app.Server = mockServer - err = app.Start() + err = app.Start(ctx) if err == nil { t.Error("application start did not abort when server.Start failed") } @@ -227,16 +225,16 @@ func TestStop(t *testing.T) { ctrl := gomock.NewController(t) mockCfg := mock_config.NewMockConfig(ctrl) mockDB := mock_database.NewMockDatabase(ctrl) + ctx := context.Background() - mockCfg.EXPECT().GetDBConnectionString().Return("mongodb://testdb").Times(2) - mockCfg.EXPECT().GetDatabaseName().Return("testdb") + mockCfg.EXPECT().DBConnectionString().Return("mongodb://testdb/testdb").Times(2) app, err := Get(mockCfg) if err != nil { t.Error(err) } - mockDB.EXPECT().Close().Return(nil) + mockDB.EXPECT().Close(ctx).Return(nil) app.Database = mockDB - app.Stop() + app.Stop(ctx) } diff --git a/pkg/server/server.go b/pkg/server/server.go index 6d39856..ab17e95 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -50,19 +50,19 @@ func Get() Server { } } -// Add an address to the server. +// WithAddr adds an address to the server. func (s *WebServer) WithAddr(addr string) Server { s.server.Addr = addr return s } -// Add an error logger to the server. +// WithErrLogger adds an error logger to the server. func (s *WebServer) WithErrLogger(l *log.Logger) Server { s.server.ErrorLog = l return s } -// Add a router to the server. +// WithRouter adds a router to the server. func (s *WebServer) WithRouter(router *mux.Router) Server { s.server.Handler = router return s @@ -82,18 +82,18 @@ func (s *WebServer) Start() error { return nil } -// Get error message from the webserver. +// Error gets error message from the webserver. func (s *WebServer) Error() error { return s.err } -// Wait for the webserver to start running. +// WaitRunning waits for the webserver to start running. // Note that this will send 'true' first, and then 'false' if the server crashes. func (s *WebServer) WaitRunning() bool { return <-s.running } -// Wait for the webserver to stop. +// WaitStopped waits for the webserver to stop. func (s *WebServer) WaitStopped() bool { return <-s.stopped } diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index fb4d0f5..83c8f04 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -89,7 +89,7 @@ func TestStartNoAddr(t *testing.T) { t.Error(err) } if err.Error() != "server missing address" { - t.Errorf("error message does not specify it's missing address: '%s'", err.Error()) + t.Errorf("error message does not specify it's missing address: %q", err.Error()) } if server.WaitRunning() { t.Error("server started when missing addr") @@ -103,7 +103,7 @@ func TestStartNoHandler(t *testing.T) { t.Error(err) } if err.Error() != "server missing handler" { - t.Errorf("error message does not specify it's missing handler: '%s'", err.Error()) + t.Errorf("error message does not specify it's missing handler: %q", err.Error()) } if server.WaitRunning() { t.Error("server started when missing handler") diff --git a/pkg/v1alpha1/api/api.go b/pkg/v1alpha1/api/api.go index 20ca5df..e551256 100644 --- a/pkg/v1alpha1/api/api.go +++ b/pkg/v1alpha1/api/api.go @@ -16,11 +16,12 @@ package api import ( + "github.com/gorilla/mux" + "github.com/eiffel-community/eiffel-goer/internal/config" "github.com/eiffel-community/eiffel-goer/internal/database" "github.com/eiffel-community/eiffel-goer/pkg/v1alpha1/handlers/events" "github.com/eiffel-community/eiffel-goer/pkg/v1alpha1/handlers/search" - "github.com/gorilla/mux" ) type V1Alpha1Application struct { diff --git a/pkg/v1alpha1/api/api_test.go b/pkg/v1alpha1/api/api_test.go index bf17ba8..a28b62f 100644 --- a/pkg/v1alpha1/api/api_test.go +++ b/pkg/v1alpha1/api/api_test.go @@ -23,125 +23,58 @@ import ( "net/http/httptest" "testing" + "github.com/golang/mock/gomock" + "github.com/eiffel-community/eiffel-goer/pkg/application" "github.com/eiffel-community/eiffel-goer/pkg/schema" "github.com/eiffel-community/eiffel-goer/test/mock_config" "github.com/eiffel-community/eiffel-goer/test/mock_database" - "github.com/golang/mock/gomock" ) -// Test that the endpoint /events for a single event is added properly. -func TestAddedRoutesEventRead(t *testing.T) { - ctrl := gomock.NewController(t) - mockCfg := mock_config.NewMockConfig(ctrl) - mockDB := mock_database.NewMockDatabase(ctrl) - +// Test that all v1alpha1 endpoints are added properly. +func TestRoutes(t *testing.T) { eventID := "3fabaa6b-5343-4d74-8af9-dc2e4c1f2827" - - mockCfg.EXPECT().GetDBConnectionString().Return("") - mockCfg.EXPECT().GetAPIPort().Return(":8080") - mockDB.EXPECT().GetEventByID(eventID).Return(schema.EiffelEvent{}, nil) - - app, err := application.Get(mockCfg) - if err != nil { - t.Error(err) - } - app.Database = mockDB - app.LoadV1Alpha1Routes() - - responseRecorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, "/v1alpha1/events/"+eventID, nil) - app.Router.ServeHTTP(responseRecorder, request) - expectedStatusCode := http.StatusOK - if responseRecorder.Code != expectedStatusCode { - t.Errorf("Want status '%d' for '/v1alpha1/events/%s', got '%d'", expectedStatusCode, eventID, responseRecorder.Code) + tests := []struct { + name string + url string + httpMethod string + statusCode int + }{ + {name: "EventsRead", httpMethod: http.MethodGet, url: "/v1alpha1/events/" + eventID, statusCode: http.StatusOK}, + {name: "EventsReadAll", httpMethod: http.MethodGet, url: "/v1alpha1/events", statusCode: http.StatusNotImplemented}, + {name: "SearchRead", httpMethod: http.MethodGet, url: "/v1alpha1/search/" + eventID, statusCode: http.StatusNotImplemented}, + {name: "SearchUpstreamDownstream", httpMethod: http.MethodPost, url: "/v1alpha1/search/" + eventID, statusCode: http.StatusNotImplemented}, } -} -// Test that the endpoint /events for a many events is added properly. -func TestAddedRoutesEventReadAll(t *testing.T) { ctrl := gomock.NewController(t) mockCfg := mock_config.NewMockConfig(ctrl) mockDB := mock_database.NewMockDatabase(ctrl) - mockCfg.EXPECT().GetDBConnectionString().Return("") - mockCfg.EXPECT().GetAPIPort().Return(":8080") - mockDB.EXPECT().GetEvents().Return([]schema.EiffelEvent{}, nil) - - app, err := application.Get(mockCfg) - if err != nil { - t.Error(err) - } - app.Database = mockDB - app.LoadV1Alpha1Routes() - - responseRecorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, "/v1alpha1/events", nil) - app.Router.ServeHTTP(responseRecorder, request) - expectedStatusCode := http.StatusNotImplemented - if responseRecorder.Code != expectedStatusCode { - t.Errorf("Want status '%d' for '/v1alpha1/events', got '%d'", expectedStatusCode, responseRecorder.Code) - } -} - -// Test that the endpoint /search is added properly. -func TestAddedRoutesSearchRead(t *testing.T) { - ctrl := gomock.NewController(t) - mockCfg := mock_config.NewMockConfig(ctrl) - mockDB := mock_database.NewMockDatabase(ctrl) - - eventID := "3fabaa6b-5343-4d74-8af9-dc2e4c1f2827" - - // Setting DBConnection string to "" so that application.Get does not - // attempt to create its own database connection. After initializing - // the application, add the MockDB - mockCfg.EXPECT().GetDBConnectionString().Return("") - mockCfg.EXPECT().GetAPIPort().Return(":8080") - mockDB.EXPECT().SearchEvent("id").Return(schema.EiffelEvent{}, nil) - - app, err := application.Get(mockCfg) - if err != nil { - t.Error(err) - } - app.Database = mockDB - app.LoadV1Alpha1Routes() - - responseRecorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, "/v1alpha1/search/"+eventID, nil) - app.Router.ServeHTTP(responseRecorder, request) - expectedStatusCode := http.StatusNotImplemented - if responseRecorder.Code != expectedStatusCode { - t.Errorf("Want status '%d' for '/v1alpha1/search/%s', got '%d'", expectedStatusCode, eventID, responseRecorder.Code) - } -} - -// Test that the post endpoint /search is added properly. -func TestAddedRoutesSearchUpstreamDownstream(t *testing.T) { - ctrl := gomock.NewController(t) - mockCfg := mock_config.NewMockConfig(ctrl) - mockDB := mock_database.NewMockDatabase(ctrl) - - eventID := "3fabaa6b-5343-4d74-8af9-dc2e4c1f2827" - - // Setting DBConnection string to "" so that application.Get does not - // attempt to create its own database connection. After initializing - // the application, add the MockDB - mockCfg.EXPECT().GetDBConnectionString().Return("") - mockCfg.EXPECT().GetAPIPort().Return(":8080") - mockDB.EXPECT().UpstreamDownstreamSearch("id").Return([]schema.EiffelEvent{}, nil) - - app, err := application.Get(mockCfg) - if err != nil { - t.Error(err) - } - app.Database = mockDB - app.LoadV1Alpha1Routes() - - responseRecorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodPost, "/v1alpha1/search/"+eventID, nil) - app.Router.ServeHTTP(responseRecorder, request) - expectedStatusCode := http.StatusNotImplemented - if responseRecorder.Code != expectedStatusCode { - t.Errorf("Want status '%d' for '/v1alpha1/search/%s', got '%d'", expectedStatusCode, eventID, responseRecorder.Code) + mockCfg.EXPECT().DBConnectionString().Return("").AnyTimes() + mockCfg.EXPECT().APIPort().Return(":8080").AnyTimes() + // Have to use 'gomock.Any()' for the context as mux adds values to the request context. + mockDB.EXPECT().GetEventByID(gomock.Any(), eventID).Return(schema.EiffelEvent{}, nil) + mockDB.EXPECT().GetEvents(gomock.Any()).Return([]schema.EiffelEvent{}, nil) + mockDB.EXPECT().UpstreamDownstreamSearch(gomock.Any(), "id").Return([]schema.EiffelEvent{}, nil) + + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + app, err := application.Get(mockCfg) + if err != nil { + t.Error(err) + } + app.Database = mockDB + app.LoadV1Alpha1Routes() + + responseRecorder := httptest.NewRecorder() + request := httptest.NewRequest(testCase.httpMethod, testCase.url, nil) + + app.Router.ServeHTTP(responseRecorder, request) + expectedStatusCode := testCase.statusCode + if responseRecorder.Code != expectedStatusCode { + t.Errorf("Want status '%d' for %q, got '%d'", expectedStatusCode, testCase.url, responseRecorder.Code) + } + + }) } } diff --git a/pkg/v1alpha1/handlers/events/events.go b/pkg/v1alpha1/handlers/events/events.go index 598e55b..24282fe 100644 --- a/pkg/v1alpha1/handlers/events/events.go +++ b/pkg/v1alpha1/handlers/events/events.go @@ -18,11 +18,12 @@ package events import ( "net/http" + "github.com/gorilla/mux" + "github.com/gorilla/schema" + "github.com/eiffel-community/eiffel-goer/internal/config" "github.com/eiffel-community/eiffel-goer/internal/database" "github.com/eiffel-community/eiffel-goer/internal/responses" - "github.com/gorilla/mux" - "github.com/gorilla/schema" ) type EventHandler struct { @@ -38,12 +39,11 @@ func Get(cfg config.Config, db database.Database) *EventHandler { } type EventsSingleRequest struct { - ID string `schema:"id"` - Shallow bool `schema:"shallow"` // TODO: Unused - Readable bool `schema:"readable"` // TODO: NYI + ID string `schema:"id"` + Shallow bool `schema:"shallow"` // TODO: Unused } -// Handle GET requests against the /events/{id} endpoint. +// Read handles GET requests against the /events/{id} endpoint. // To get single event information func (h *EventHandler) Read(w http.ResponseWriter, r *http.Request) { var request EventsSingleRequest @@ -53,7 +53,7 @@ func (h *EventHandler) Read(w http.ResponseWriter, r *http.Request) { } vars := mux.Vars(r) request.ID = vars["id"] - event, err := h.Database.GetEventByID(request.ID) + event, err := h.Database.GetEventByID(r.Context(), request.ID) if err != nil { responses.RespondWithError(w, http.StatusNotFound, err.Error()) return @@ -61,7 +61,7 @@ func (h *EventHandler) Read(w http.ResponseWriter, r *http.Request) { responses.RespondWithJSON(w, http.StatusOK, event) } -// Handle GET requests against the /events/ +// ReadAll handles GET requests against the /events/ endpoint. // To get all events information func (h *EventHandler) ReadAll(w http.ResponseWriter, r *http.Request) { responses.RespondWithError(w, http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) diff --git a/pkg/v1alpha1/handlers/events/events_test.go b/pkg/v1alpha1/handlers/events/events_test.go index 5870900..97bb45d 100644 --- a/pkg/v1alpha1/handlers/events/events_test.go +++ b/pkg/v1alpha1/handlers/events/events_test.go @@ -22,11 +22,12 @@ import ( "net/http/httptest" "testing" + "github.com/golang/mock/gomock" + "github.com/gorilla/mux" + "github.com/eiffel-community/eiffel-goer/pkg/schema" "github.com/eiffel-community/eiffel-goer/test/mock_config" "github.com/eiffel-community/eiffel-goer/test/mock_database" - "github.com/golang/mock/gomock" - "github.com/gorilla/mux" ) var activityJSON = []byte(` @@ -53,96 +54,56 @@ func loadEvent() (schema.EiffelEvent, error) { return event, nil } -func TestRead(t *testing.T) { - - ctrl := gomock.NewController(t) - mockCfg := mock_config.NewMockConfig(ctrl) - mockDB := mock_database.NewMockDatabase(ctrl) - - event, err := loadEvent() - if err != nil { - t.Error(err) - } - eventID := event.Meta.ID - mockDB.EXPECT().GetEventByID(eventID).Return(event, nil) - - app := Get(mockCfg, mockDB) - handler := mux.NewRouter() - handler.HandleFunc("/events/{id:[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}}", app.Read) - - responseRecorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, "/events/"+eventID, nil) - - handler.ServeHTTP(responseRecorder, request) - - expectedStatusCode := http.StatusOK - if responseRecorder.Code != expectedStatusCode { - t.Errorf("Want status '%d' for '/events/%s', got '%d'", expectedStatusCode, eventID, responseRecorder.Code) - } - eventFromResponse := schema.EiffelEvent{} - err = json.Unmarshal(responseRecorder.Body.Bytes(), &eventFromResponse) - if err != nil { - t.Error(err) - } - if eventFromResponse.Meta.ID != event.Meta.ID { - t.Error("event returned with response is not the same as the one in DB") - } -} - -func TestReadBadQuery(t *testing.T) { - ctrl := gomock.NewController(t) - mockCfg := mock_config.NewMockConfig(ctrl) - mockDB := mock_database.NewMockDatabase(ctrl) - +// Test that the events/{id} endpoint work as expected. +func TestEvents(t *testing.T) { event, err := loadEvent() if err != nil { t.Error(err) } eventID := event.Meta.ID - mockDB.EXPECT().GetEventByID(eventID).Return(event, nil) - - app := Get(mockCfg, mockDB) - handler := mux.NewRouter() - handler.HandleFunc("/events/{id:[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}}", app.Read) - - responseRecorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, "/events/"+eventID, nil) - q := request.URL.Query() + badRequest := httptest.NewRequest(http.MethodGet, "/events/"+eventID, nil) + q := badRequest.URL.Query() q.Add("nah", "hello") - request.URL.RawQuery = q.Encode() - - handler.ServeHTTP(responseRecorder, request) - - expectedStatusCode := http.StatusBadRequest - if responseRecorder.Code != expectedStatusCode { - t.Errorf("Want status '%d' for '/events/%s', got '%d'", expectedStatusCode, eventID, responseRecorder.Code) - } -} - -func TestReadEventNotFound(t *testing.T) { - - ctrl := gomock.NewController(t) - mockCfg := mock_config.NewMockConfig(ctrl) - mockDB := mock_database.NewMockDatabase(ctrl) - - event, err := loadEvent() - if err != nil { - t.Error(err) + badRequest.URL.RawQuery = q.Encode() + + tests := []struct { + name string + request *http.Request + statusCode int + eventID string + mockError error + }{ + {name: "Read", request: httptest.NewRequest(http.MethodGet, "/events/"+eventID, nil), statusCode: http.StatusOK, eventID: eventID}, + {name: "ReadBadRequest", request: badRequest, statusCode: http.StatusBadRequest, eventID: ""}, + {name: "ReadNotFound", request: httptest.NewRequest(http.MethodGet, "/events/"+eventID, nil), statusCode: http.StatusNotFound, eventID: "", mockError: errors.New("not found")}, } - eventID := event.Meta.ID - mockDB.EXPECT().GetEventByID(eventID).Return(schema.EiffelEvent{}, errors.New("does not exist")) - - app := Get(mockCfg, mockDB) - handler := mux.NewRouter() - handler.HandleFunc("/events/{id:[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}}", app.Read) - - responseRecorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, "/events/"+eventID, nil) - - handler.ServeHTTP(responseRecorder, request) - expectedStatusCode := http.StatusNotFound - if responseRecorder.Code != expectedStatusCode { - t.Errorf("Want status '%d' for '/events/%s', got '%d'", expectedStatusCode, eventID, responseRecorder.Code) + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + mockCfg := mock_config.NewMockConfig(ctrl) + mockDB := mock_database.NewMockDatabase(ctrl) + mockDB.EXPECT().GetEventByID(gomock.Any(), eventID).Return(event, testCase.mockError) + app := Get(mockCfg, mockDB) + handler := mux.NewRouter() + handler.HandleFunc("/events/{id:[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}}", app.Read) + + responseRecorder := httptest.NewRecorder() + handler.ServeHTTP(responseRecorder, testCase.request) + + expectedStatusCode := testCase.statusCode + if responseRecorder.Code != expectedStatusCode { + t.Errorf("Want status '%d' for %q, got '%d'", expectedStatusCode, testCase.request.URL.String(), responseRecorder.Code) + } + eventFromResponse := schema.EiffelEvent{} + err = json.Unmarshal(responseRecorder.Body.Bytes(), &eventFromResponse) + if err != nil { + t.Error(err) + } + if eventFromResponse.Meta.ID != testCase.eventID { + t.Error("event returned with response is not the same as the one in DB") + } + + }) } } diff --git a/pkg/v1alpha1/handlers/search/search.go b/pkg/v1alpha1/handlers/search/search.go index f683b11..1ff3874 100644 --- a/pkg/v1alpha1/handlers/search/search.go +++ b/pkg/v1alpha1/handlers/search/search.go @@ -28,20 +28,20 @@ type SearchHandler struct { Database database.Database } -// Create a new handler for the search endpoint. +// Get a new handler for the search endpoint. func Get(cfg config.Config, db database.Database) *SearchHandler { return &SearchHandler{ cfg, db, } } -// Handle GET requests against the /search/{id} endpoint. +// Read handles GET requests against the /search/{id} endpoint. // To get an event based on eventId passed func (h *SearchHandler) Read(w http.ResponseWriter, r *http.Request) { responses.RespondWithError(w, http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } -// Handle POST requests against the /search/{id} endpoint. +// UpstreamDownstream handles POST requests against the /search/{id} endpoint. // To get upstream/downstream events for an event based on the searchParameters passed func (h *SearchHandler) UpstreamDownstream(w http.ResponseWriter, r *http.Request) { responses.RespondWithError(w, http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) From 508772ef1a049899eeddad4388caec1eb10d0a91 Mon Sep 17 00:00:00 2001 From: Tobias Persson Date: Fri, 27 Aug 2021 14:03:48 +0200 Subject: [PATCH 3/9] Added structured logging and version information --- Makefile | 3 +- cmd/goer/main.go | 23 ++++++++-- go.mod | 3 ++ go.sum | 9 ++++ internal/config/config.go | 19 ++++++++ internal/config/config_test.go | 48 ++++++++++++++------ internal/database/database.go | 6 ++- internal/database/database_test.go | 12 +++-- internal/database/drivers/mongodb/mongodb.go | 5 +- internal/logger/logger.go | 40 ++++++++++++---- pkg/application/application.go | 7 ++- pkg/application/application_test.go | 17 +++---- pkg/v1alpha1/api/api.go | 7 ++- pkg/v1alpha1/api/api_test.go | 3 +- pkg/v1alpha1/handlers/events/events.go | 6 ++- pkg/v1alpha1/handlers/events/events_test.go | 3 +- pkg/v1alpha1/handlers/search/search.go | 7 ++- 17 files changed, 165 insertions(+), 53 deletions(-) diff --git a/Makefile b/Makefile index 219a53d..debe311 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,8 @@ all: test build start gen: go generate ./... build: gen - go build -o bin/goer ./cmd/goer + go get github.com/ahmetb/govvv + govvv build -o bin/goer ./cmd/goer clean: rm ./bin/* || true docker-compose --project-directory . -f deploy/$(DEPLOY)/docker-compose.yml rm || true diff --git a/cmd/goer/main.go b/cmd/goer/main.go index 5620e67..90a5356 100644 --- a/cmd/goer/main.go +++ b/cmd/goer/main.go @@ -17,27 +17,40 @@ package main import ( "context" + "os" "github.com/eiffel-community/eiffel-goer/internal/config" "github.com/eiffel-community/eiffel-goer/internal/logger" "github.com/eiffel-community/eiffel-goer/pkg/application" + log "github.com/sirupsen/logrus" ) +// GitSummary contains "git describe" output and is automatically +// populated via linker options when building with govvv. +var GitSummary = "(unknown)" + // Start up the Goer application. func main() { cfg := config.Get() ctx := context.Background() - - app, err := application.Get(cfg) + if err := logger.Setup(cfg); err != nil { + log.Fatal(err) + } + log := log.WithFields(log.Fields{ + "hostname": os.Getenv("HOSTNAME"), + "application": "eiffel-goer", + "version": GitSummary, + }) + app, err := application.Get(cfg, log) if err != nil { - logger.Error.Panic(err) + log.Panic(err) } app.LoadV1Alpha1Routes() - logger.Debug.Println("Starting up.") + log.Debug("Starting up.") err = app.Start(ctx) if err != nil { - logger.Error.Panic(err) + log.Panic(err) } } diff --git a/go.mod b/go.mod index ca967f3..7f7d0c4 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,11 @@ require ( github.com/golang/mock v1.4.4 github.com/gorilla/mux v1.8.0 github.com/gorilla/schema v1.2.0 + github.com/sirupsen/logrus v1.4.2 + github.com/snowzach/rotatefilehook v0.0.0-20180327172521-2f64f265f58c go.mongodb.org/mongo-driver v1.7.1 golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 // indirect golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect ) diff --git a/go.sum b/go.sum index 7bed155..a45e7af 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -45,6 +46,7 @@ github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0Lh github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -64,7 +66,10 @@ github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/snowzach/rotatefilehook v0.0.0-20180327172521-2f64f265f58c h1:iUEy7/LRto3JqR/GLXDTEFP+s+qIjWw4pM8yzMfXC9A= +github.com/snowzach/rotatefilehook v0.0.0-20180327172521-2f64f265f58c/go.mod h1:ZLVe3VfhAuMYLYWliGEydMBoRnfib8EFSqkBYu1ck9E= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -105,6 +110,7 @@ golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2 h1:T5DasATyLQfmbTpfEXx/IOL9vfjzW6up+ZDkmHvIf2s= golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= @@ -121,6 +127,9 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config/config.go b/internal/config/config.go index 22eb540..718e6fd 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -23,11 +23,15 @@ import ( type Config interface { DBConnectionString() string APIPort() string + LogLevel() string + LogFilePath() string } type Cfg struct { connectionString string apiPort string + logLevel string + logFilePath string } // Get parses input parameters to program and return a config with them set. @@ -36,6 +40,8 @@ func Get() Config { flag.StringVar(&conf.connectionString, "connectionstring", os.Getenv("CONNECTION_STRING"), "Database connection string.") flag.StringVar(&conf.apiPort, "apiport", os.Getenv("API_PORT"), "API port.") + flag.StringVar(&conf.logLevel, "loglevel", os.Getenv("LOGLEVEL"), "Log level (TRACE, DEBUG, INFO, WARNING, ERROR, FATAL, PANIC).") + flag.StringVar(&conf.logFilePath, "logfilepath", os.Getenv("LOG_FILE_PATH"), "Path, including filename, for the log files to create.") flag.Parse() return conf @@ -50,3 +56,16 @@ func (c *Cfg) DBConnectionString() string { func (c *Cfg) APIPort() string { return ":" + c.apiPort } + +// LogLevel returns the log level. Default is INFO. +func (c *Cfg) LogLevel() string { + if c.logLevel == "" { + c.logLevel = "INFO" + } + return c.logLevel +} + +// LogFilePath returns the path to where log files should be stored, including filename. +func (c *Cfg) LogFilePath() string { + return c.logFilePath +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go index fe8e299..b50b1f8 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -24,8 +24,12 @@ import ( func TestGet(t *testing.T) { port := "8080" connectionString := "connection string" + logLevel := "DEBUG" + logFilePath := "path/to/a/file" os.Setenv("CONNECTION_STRING", connectionString) os.Setenv("API_PORT", port) + os.Setenv("LOGLEVEL", logLevel) + os.Setenv("LOG_FILE_PATH", logFilePath) cfg, ok := Get().(*Cfg) if !ok { @@ -37,24 +41,42 @@ func TestGet(t *testing.T) { if cfg.apiPort != port { t.Error("api port not set to environment variable API_PORT") } -} - -// Test that DBConnectionString return the connectionString value from the Cfg struct -func TestDBConnectionString(t *testing.T) { - cfg := &Cfg{ - connectionString: "connectionString", + if cfg.logLevel != logLevel { + t.Error("log level not set to environment variable LOGLEVEL") } - if cfg.DBConnectionString() != "connectionString" { - t.Error("function does not return the connectionString from Cfg struct") + if cfg.logFilePath != logFilePath { + t.Error("log file path not set to environment variable LOG_FILE_PATH") } } -// Test that APIPort return the value from Cfg struct with a ':' at the start -func TestAPIPort(t *testing.T) { +type getter func() string + +// Test that the getters in the Cfg struct return the values from the struct. +func TestGetters(t *testing.T) { cfg := &Cfg{ - apiPort: "8080", + connectionString: "something://db/test", + apiPort: "8080", + logLevel: "TRACE", + logFilePath: "a/file/path.json", + } + emptyCfg := &Cfg{} + tests := []struct { + name string + cfg *Cfg + function getter + value string + }{ + {name: "DBConnectionString", cfg: cfg, function: cfg.DBConnectionString, value: cfg.connectionString}, + {name: "APIPort", cfg: cfg, function: cfg.APIPort, value: ":" + cfg.apiPort}, + {name: "LogLevel", cfg: cfg, function: cfg.LogLevel, value: cfg.logLevel}, + {name: "LogLevelDefault", cfg: emptyCfg, function: emptyCfg.LogLevel, value: "INFO"}, + {name: "LogFilePath", cfg: cfg, function: cfg.LogFilePath, value: cfg.logFilePath}, } - if cfg.APIPort() != ":8080" { - t.Error("function does not return the apiPort from Cfg struct") + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + if testCase.function() != testCase.value { + t.Errorf("function does not return %q from Cfg struct", testCase.value) + } + }) } } diff --git a/internal/database/database.go b/internal/database/database.go index 711abc8..e7b3828 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -20,6 +20,8 @@ import ( "fmt" "net/url" + log "github.com/sirupsen/logrus" + "github.com/eiffel-community/eiffel-goer/internal/database/drivers/mongodb" "github.com/eiffel-community/eiffel-goer/pkg/schema" ) @@ -34,14 +36,14 @@ type Database interface { } // Get a new Database. -func Get(connectionString string) (Database, error) { +func Get(connectionString string, logger *log.Entry) (Database, error) { connectionURL, err := url.Parse(connectionString) if err != nil { return nil, err } switch connectionURL.Scheme { case "mongodb": - return mongodb.Get(connectionURL) + return mongodb.Get(connectionURL, logger) default: return nil, fmt.Errorf("cannot find database for scheme %q", connectionURL.Scheme) } diff --git a/internal/database/database_test.go b/internal/database/database_test.go index d4476c4..afbb419 100644 --- a/internal/database/database_test.go +++ b/internal/database/database_test.go @@ -15,24 +15,28 @@ // limitations under the License. package database -import "testing" +import ( + "testing" + + log "github.com/sirupsen/logrus" +) func TestGet(t *testing.T) { - _, err := Get("mongodb://db/test") + _, err := Get("mongodb://db/test", &log.Entry{}) if err != nil { t.Error(err) } } func TestGetUnknownScheme(t *testing.T) { - _, err := Get("unknown://db/test") + _, err := Get("unknown://db/test", &log.Entry{}) if err == nil { t.Error("possible to get a database with unknown:// scheme") } } func TestGetUnparsableScheme(t *testing.T) { - _, err := Get("://") + _, err := Get("://", &log.Entry{}) if err == nil { t.Error("possible to get a database with an unparsable scheme") } diff --git a/internal/database/drivers/mongodb/mongodb.go b/internal/database/drivers/mongodb/mongodb.go index 11d3a8e..cd29030 100644 --- a/internal/database/drivers/mongodb/mongodb.go +++ b/internal/database/drivers/mongodb/mongodb.go @@ -26,6 +26,7 @@ import ( "net/url" "strings" + log "github.com/sirupsen/logrus" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -37,10 +38,11 @@ import ( type MongoDB struct { Client *mongo.Client Database *mongo.Database + Logger *log.Entry } // Get creates a new database.Database interface against MongoDB. -func Get(connectionURL *url.URL) (*MongoDB, error) { +func Get(connectionURL *url.URL, logger *log.Entry) (*MongoDB, error) { client, err := mongo.NewClient(options.Client().ApplyURI(connectionURL.String())) if err != nil { return nil, err @@ -50,6 +52,7 @@ func Get(connectionURL *url.URL) (*MongoDB, error) { return &MongoDB{ Client: client, Database: client.Database(databaseName), + Logger: logger, }, nil } diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 07fc948..2310cd4 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -16,15 +16,35 @@ package logger import ( - "log" - "os" -) + "github.com/sirupsen/logrus" + "github.com/snowzach/rotatefilehook" -// Add some logging handlers that can be used to append -// log level to the log message. Also redirect Error to Stderr. -var ( - Debug = log.New(os.Stdout, "DEBUG\t", log.Ldate|log.Ltime) - Info = log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime) - Warning = log.New(os.Stdout, "WARNING\t", log.Ldate|log.Ltime) - Error = log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile) + "github.com/eiffel-community/eiffel-goer/internal/config" ) + +// Setup sets up logging to file with a JSON format and to stdout in text format. +func Setup(cfg config.Config) error { + logLevel, err := logrus.ParseLevel(cfg.LogLevel()) + if err != nil { + return err + } + filePath := cfg.LogFilePath() + if filePath != "" { + // TODO: Make these parameters configurable. + rotateFileHook, err := rotatefilehook.NewRotateFileHook(rotatefilehook.RotateFileConfig{ + Filename: filePath, + MaxSize: 10, // megabytes + MaxBackups: 3, + MaxAge: 0, // days + Level: logrus.DebugLevel, + Formatter: &logrus.JSONFormatter{}, + }) + if err != nil { + return err + } + logrus.AddHook(rotateFileHook) + } + logrus.SetLevel(logLevel) + logrus.SetFormatter(&logrus.TextFormatter{}) + return nil +} diff --git a/pkg/application/application.go b/pkg/application/application.go index 070afd2..93a1a69 100644 --- a/pkg/application/application.go +++ b/pkg/application/application.go @@ -19,6 +19,7 @@ import ( "context" "github.com/gorilla/mux" + log "github.com/sirupsen/logrus" "github.com/eiffel-community/eiffel-goer/internal/config" "github.com/eiffel-community/eiffel-goer/internal/database" @@ -32,14 +33,16 @@ type Application struct { Router *mux.Router Server server.Server V1Alpha1 *v1alpha1.V1Alpha1Application + Logger *log.Entry } // Get a new Goer application. -func Get(cfg config.Config) (*Application, error) { +func Get(cfg config.Config, logger *log.Entry) (*Application, error) { application := &Application{ Config: cfg, Router: mux.NewRouter(), Server: server.Get(), + Logger: logger, } if cfg.DBConnectionString() != "" { db, err := application.getDB() @@ -55,6 +58,7 @@ func Get(cfg config.Config) (*Application, error) { func (app *Application) getDB() (database.Database, error) { db, err := database.Get( app.Config.DBConnectionString(), + app.Logger, ) if err != nil { return nil, err @@ -67,6 +71,7 @@ func (app *Application) LoadV1Alpha1Routes() { app.V1Alpha1 = &v1alpha1.V1Alpha1Application{ Config: app.Config, Database: app.Database, + Logger: app.Logger, } subrouter := app.Router.PathPrefix("/v1alpha1").Name("v1alpha1").Subrouter() app.V1Alpha1.AddRoutes(subrouter) diff --git a/pkg/application/application_test.go b/pkg/application/application_test.go index e86952b..836b36e 100644 --- a/pkg/application/application_test.go +++ b/pkg/application/application_test.go @@ -21,6 +21,7 @@ import ( "testing" "github.com/golang/mock/gomock" + log "github.com/sirupsen/logrus" "github.com/eiffel-community/eiffel-goer/internal/database" "github.com/eiffel-community/eiffel-goer/test/mock_config" @@ -34,7 +35,7 @@ func TestGet(t *testing.T) { mockCfg := mock_config.NewMockConfig(ctrl) mockCfg.EXPECT().DBConnectionString().Return("mongodb://testdb/testdb").Times(2) - app, err := Get(mockCfg) + app, err := Get(mockCfg, &log.Entry{}) if err != nil { t.Error(err) } @@ -58,7 +59,7 @@ func TestGetNoDB(t *testing.T) { mockCfg := mock_config.NewMockConfig(ctrl) mockCfg.EXPECT().DBConnectionString().Return("") - app, err := Get(mockCfg) + app, err := Get(mockCfg, &log.Entry{}) if err != nil { t.Error(err) } @@ -82,7 +83,7 @@ func TestGetDBError(t *testing.T) { mockCfg := mock_config.NewMockConfig(ctrl) mockCfg.EXPECT().DBConnectionString().Return("invalid://testdb").Times(2) - _, err := Get(mockCfg) + _, err := Get(mockCfg, &log.Entry{}) if err == nil { t.Error("application should have raised error due to invalid database connection string") } @@ -112,7 +113,7 @@ func TestLoadV1Alpha1Routes(t *testing.T) { ctrl := gomock.NewController(t) mockCfg := mock_config.NewMockConfig(ctrl) mockCfg.EXPECT().DBConnectionString().Return("mongodb://testdb/testdb").Times(2) - app, err := Get(mockCfg) + app, err := Get(mockCfg, &log.Entry{}) if err != nil { t.Error(err) } @@ -135,7 +136,7 @@ func TestStart(t *testing.T) { mockCfg.EXPECT().DBConnectionString().Return("mongodb://testdb/testdb").Times(2) mockCfg.EXPECT().APIPort().Return(":8080") - app, err := Get(mockCfg) + app, err := Get(mockCfg, &log.Entry{}) if err != nil { t.Error(err) } @@ -169,7 +170,7 @@ func TestStartAbort(t *testing.T) { mockCfg.EXPECT().DBConnectionString().Return("mongodb://testdb/testdb").Times(2) mockCfg.EXPECT().APIPort().Return(":8080") - app, err := Get(mockCfg) + app, err := Get(mockCfg, &log.Entry{}) if err != nil { t.Error(err) } @@ -200,7 +201,7 @@ func TestStartFail(t *testing.T) { mockCfg.EXPECT().DBConnectionString().Return("mongodb://testdb/testdb").Times(2) mockCfg.EXPECT().APIPort().Return("") - app, err := Get(mockCfg) + app, err := Get(mockCfg, &log.Entry{}) if err != nil { t.Error(err) } @@ -229,7 +230,7 @@ func TestStop(t *testing.T) { mockCfg.EXPECT().DBConnectionString().Return("mongodb://testdb/testdb").Times(2) - app, err := Get(mockCfg) + app, err := Get(mockCfg, &log.Entry{}) if err != nil { t.Error(err) } diff --git a/pkg/v1alpha1/api/api.go b/pkg/v1alpha1/api/api.go index e551256..b43fa77 100644 --- a/pkg/v1alpha1/api/api.go +++ b/pkg/v1alpha1/api/api.go @@ -18,6 +18,8 @@ package api import ( "github.com/gorilla/mux" + log "github.com/sirupsen/logrus" + "github.com/eiffel-community/eiffel-goer/internal/config" "github.com/eiffel-community/eiffel-goer/internal/database" "github.com/eiffel-community/eiffel-goer/pkg/v1alpha1/handlers/events" @@ -27,12 +29,13 @@ import ( type V1Alpha1Application struct { Database database.Database Config config.Config + Logger *log.Entry } // Add routes for all handlers to the router. func (app *V1Alpha1Application) AddRoutes(router *mux.Router) { - eventHandler := events.Get(app.Config, app.Database) - searchHandler := search.Get(app.Config, app.Database) + eventHandler := events.Get(app.Config, app.Database, app.Logger) + searchHandler := search.Get(app.Config, app.Database, app.Logger) router.HandleFunc("/events", eventHandler.ReadAll).Methods("GET", "OPTIONS") router.HandleFunc("/events/{id:[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}}", eventHandler.Read).Methods("GET", "OPTIONS") diff --git a/pkg/v1alpha1/api/api_test.go b/pkg/v1alpha1/api/api_test.go index a28b62f..1648af9 100644 --- a/pkg/v1alpha1/api/api_test.go +++ b/pkg/v1alpha1/api/api_test.go @@ -24,6 +24,7 @@ import ( "testing" "github.com/golang/mock/gomock" + log "github.com/sirupsen/logrus" "github.com/eiffel-community/eiffel-goer/pkg/application" "github.com/eiffel-community/eiffel-goer/pkg/schema" @@ -59,7 +60,7 @@ func TestRoutes(t *testing.T) { for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { - app, err := application.Get(mockCfg) + app, err := application.Get(mockCfg, &log.Entry{}) if err != nil { t.Error(err) } diff --git a/pkg/v1alpha1/handlers/events/events.go b/pkg/v1alpha1/handlers/events/events.go index 24282fe..30ca718 100644 --- a/pkg/v1alpha1/handlers/events/events.go +++ b/pkg/v1alpha1/handlers/events/events.go @@ -20,6 +20,7 @@ import ( "github.com/gorilla/mux" "github.com/gorilla/schema" + log "github.com/sirupsen/logrus" "github.com/eiffel-community/eiffel-goer/internal/config" "github.com/eiffel-community/eiffel-goer/internal/database" @@ -29,12 +30,13 @@ import ( type EventHandler struct { Config config.Config Database database.Database + Logger *log.Entry } // Create a new handler for the event endpoint. -func Get(cfg config.Config, db database.Database) *EventHandler { +func Get(cfg config.Config, db database.Database, logger *log.Entry) *EventHandler { return &EventHandler{ - cfg, db, + cfg, db, logger, } } diff --git a/pkg/v1alpha1/handlers/events/events_test.go b/pkg/v1alpha1/handlers/events/events_test.go index 97bb45d..f7d1c21 100644 --- a/pkg/v1alpha1/handlers/events/events_test.go +++ b/pkg/v1alpha1/handlers/events/events_test.go @@ -24,6 +24,7 @@ import ( "github.com/golang/mock/gomock" "github.com/gorilla/mux" + log "github.com/sirupsen/logrus" "github.com/eiffel-community/eiffel-goer/pkg/schema" "github.com/eiffel-community/eiffel-goer/test/mock_config" @@ -84,7 +85,7 @@ func TestEvents(t *testing.T) { mockCfg := mock_config.NewMockConfig(ctrl) mockDB := mock_database.NewMockDatabase(ctrl) mockDB.EXPECT().GetEventByID(gomock.Any(), eventID).Return(event, testCase.mockError) - app := Get(mockCfg, mockDB) + app := Get(mockCfg, mockDB, &log.Entry{}) handler := mux.NewRouter() handler.HandleFunc("/events/{id:[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}}", app.Read) diff --git a/pkg/v1alpha1/handlers/search/search.go b/pkg/v1alpha1/handlers/search/search.go index 1ff3874..5887b61 100644 --- a/pkg/v1alpha1/handlers/search/search.go +++ b/pkg/v1alpha1/handlers/search/search.go @@ -18,6 +18,8 @@ package search import ( "net/http" + log "github.com/sirupsen/logrus" + "github.com/eiffel-community/eiffel-goer/internal/config" "github.com/eiffel-community/eiffel-goer/internal/database" "github.com/eiffel-community/eiffel-goer/internal/responses" @@ -26,12 +28,13 @@ import ( type SearchHandler struct { Config config.Config Database database.Database + Logger *log.Entry } // Get a new handler for the search endpoint. -func Get(cfg config.Config, db database.Database) *SearchHandler { +func Get(cfg config.Config, db database.Database, logger *log.Entry) *SearchHandler { return &SearchHandler{ - cfg, db, + cfg, db, logger, } } From 56e0a89034bc0035f736d5333e0563801d4ddac4 Mon Sep 17 00:00:00 2001 From: Tobias Persson Date: Fri, 27 Aug 2021 14:13:13 +0200 Subject: [PATCH 4/9] Remove ID from EventsSingleRequest struct --- pkg/v1alpha1/handlers/events/events.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/v1alpha1/handlers/events/events.go b/pkg/v1alpha1/handlers/events/events.go index 30ca718..bc10b35 100644 --- a/pkg/v1alpha1/handlers/events/events.go +++ b/pkg/v1alpha1/handlers/events/events.go @@ -41,8 +41,7 @@ func Get(cfg config.Config, db database.Database, logger *log.Entry) *EventHandl } type EventsSingleRequest struct { - ID string `schema:"id"` - Shallow bool `schema:"shallow"` // TODO: Unused + Shallow bool `schema:"shallow"` // TODO: Unused } // Read handles GET requests against the /events/{id} endpoint. @@ -54,8 +53,8 @@ func (h *EventHandler) Read(w http.ResponseWriter, r *http.Request) { return } vars := mux.Vars(r) - request.ID = vars["id"] - event, err := h.Database.GetEventByID(r.Context(), request.ID) + ID := vars["id"] + event, err := h.Database.GetEventByID(r.Context(), ID) if err != nil { responses.RespondWithError(w, http.StatusNotFound, err.Error()) return From 0e73646587aad91fdb26e8170b3a395a8468f6dd Mon Sep 17 00:00:00 2001 From: Tobias Persson Date: Mon, 30 Aug 2021 09:59:52 +0200 Subject: [PATCH 5/9] Set static version on Govvv --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index debe311..2649762 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ all: test build start gen: go generate ./... build: gen - go get github.com/ahmetb/govvv + go get github.com/ahmetb/govvv@v0.3.0 govvv build -o bin/goer ./cmd/goer clean: rm ./bin/* || true From 4368be5a30a9b3d9ab1fa43d3396e15286735b94 Mon Sep 17 00:00:00 2001 From: Tobias Persson Date: Mon, 30 Aug 2021 10:00:11 +0200 Subject: [PATCH 6/9] Use os.Hostname instead of os.Getenv("HOSTNAME") --- cmd/goer/main.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/goer/main.go b/cmd/goer/main.go index 90a5356..4ee7216 100644 --- a/cmd/goer/main.go +++ b/cmd/goer/main.go @@ -36,8 +36,12 @@ func main() { if err := logger.Setup(cfg); err != nil { log.Fatal(err) } + hostname, err := os.Hostname() + if err != nil { + log.Fatal(err) + } log := log.WithFields(log.Fields{ - "hostname": os.Getenv("HOSTNAME"), + "hostname": hostname, "application": "eiffel-goer", "version": GitSummary, }) From 76cd0e83dd58db38e4fd9fcbef47231802aadb14 Mon Sep 17 00:00:00 2001 From: Tobias Persson Date: Mon, 30 Aug 2021 10:00:37 +0200 Subject: [PATCH 7/9] Update so that the database drivers decide if they support a scheme --- internal/database/database.go | 26 +++++--------- internal/database/drivers/drivers.go | 36 ++++++++++++++++++++ internal/database/drivers/mongodb/mongodb.go | 31 ++++++++++++----- pkg/application/application.go | 5 +-- pkg/application/application_test.go | 14 ++++---- pkg/v1alpha1/api/api.go | 4 +-- pkg/v1alpha1/api/api_test.go | 4 +-- pkg/v1alpha1/handlers/events/events.go | 6 ++-- pkg/v1alpha1/handlers/events/events_test.go | 4 +-- pkg/v1alpha1/handlers/search/search.go | 6 ++-- test/test.go | 2 +- 11 files changed, 90 insertions(+), 48 deletions(-) create mode 100644 internal/database/drivers/drivers.go diff --git a/internal/database/database.go b/internal/database/database.go index e7b3828..ad424b6 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -16,35 +16,27 @@ package database import ( - "context" "fmt" "net/url" log "github.com/sirupsen/logrus" + "github.com/eiffel-community/eiffel-goer/internal/database/drivers" "github.com/eiffel-community/eiffel-goer/internal/database/drivers/mongodb" - "github.com/eiffel-community/eiffel-goer/pkg/schema" ) -type Database interface { - Connect(context.Context) error - GetEvents(context.Context) ([]schema.EiffelEvent, error) - SearchEvent(context.Context, string) (schema.EiffelEvent, error) - UpstreamDownstreamSearch(context.Context, string) ([]schema.EiffelEvent, error) - GetEventByID(context.Context, string) (schema.EiffelEvent, error) - Close(context.Context) error -} - // Get a new Database. -func Get(connectionString string, logger *log.Entry) (Database, error) { +func Get(connectionString string, logger *log.Entry) (drivers.DatabaseDriver, error) { + var drivers = []drivers.DatabaseDriver{&mongodb.Driver{}} + connectionURL, err := url.Parse(connectionString) if err != nil { return nil, err } - switch connectionURL.Scheme { - case "mongodb": - return mongodb.Get(connectionURL, logger) - default: - return nil, fmt.Errorf("cannot find database for scheme %q", connectionURL.Scheme) + for _, driver := range drivers { + if driver.SupportsScheme(connectionURL.Scheme) { + return driver.Get(connectionURL, logger) + } } + return nil, fmt.Errorf("cannot find database for scheme %q", connectionURL.Scheme) } diff --git a/internal/database/drivers/drivers.go b/internal/database/drivers/drivers.go new file mode 100644 index 0000000..d5c5615 --- /dev/null +++ b/internal/database/drivers/drivers.go @@ -0,0 +1,36 @@ +// Copyright 2021 Axis Communications AB. +// +// For a full list of individual contributors, please see the commit history. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package drivers + +import ( + "context" + "net/url" + + log "github.com/sirupsen/logrus" + + "github.com/eiffel-community/eiffel-goer/pkg/schema" +) + +type DatabaseDriver interface { + Get(*url.URL, *log.Entry) (DatabaseDriver, error) + SupportsScheme(string) bool + Connect(context.Context) error + GetEvents(context.Context) ([]schema.EiffelEvent, error) + SearchEvent(context.Context, string) (schema.EiffelEvent, error) + UpstreamDownstreamSearch(context.Context, string) ([]schema.EiffelEvent, error) + GetEventByID(context.Context, string) (schema.EiffelEvent, error) + Close(context.Context) error +} diff --git a/internal/database/drivers/mongodb/mongodb.go b/internal/database/drivers/mongodb/mongodb.go index cd29030..95c1bc0 100644 --- a/internal/database/drivers/mongodb/mongodb.go +++ b/internal/database/drivers/mongodb/mongodb.go @@ -32,32 +32,45 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/readpref" + "github.com/eiffel-community/eiffel-goer/internal/database/drivers" "github.com/eiffel-community/eiffel-goer/pkg/schema" ) -type MongoDB struct { +type Driver struct { Client *mongo.Client Database *mongo.Database Logger *log.Entry } // Get creates a new database.Database interface against MongoDB. -func Get(connectionURL *url.URL, logger *log.Entry) (*MongoDB, error) { +func (m *Driver) Get(connectionURL *url.URL, logger *log.Entry) (drivers.DatabaseDriver, error) { client, err := mongo.NewClient(options.Client().ApplyURI(connectionURL.String())) if err != nil { return nil, err } // The Path value from url.URL always has a '/' prepended. databaseName := strings.Split(connectionURL.Path, "/")[1] - return &MongoDB{ + return &Driver{ Client: client, Database: client.Database(databaseName), Logger: logger, }, nil } +// Test whether the MongoDB driver supports a scheme. +func (m *Driver) SupportsScheme(scheme string) bool { + switch scheme { + case "mongodb": + return true + case "mongodb+srv": + return true + default: + return false + } +} + // Connect to the MongoDB database and ping it to make sure it works. -func (m *MongoDB) Connect(ctx context.Context) error { +func (m *Driver) Connect(ctx context.Context) error { err := m.Client.Connect(ctx) if err != nil { return err @@ -66,22 +79,22 @@ func (m *MongoDB) Connect(ctx context.Context) error { } // GetEvents gets all events information. -func (m *MongoDB) GetEvents(ctx context.Context) ([]schema.EiffelEvent, error) { +func (m *Driver) GetEvents(ctx context.Context) ([]schema.EiffelEvent, error) { return nil, errors.New("not yet implemented") } // SearchEvent searches for an event based on event ID. -func (m *MongoDB) SearchEvent(ctx context.Context, id string) (schema.EiffelEvent, error) { +func (m *Driver) SearchEvent(ctx context.Context, id string) (schema.EiffelEvent, error) { return schema.EiffelEvent{}, errors.New("not yet implemented") } // UpstreamDownstreamSearch searches for events upstream and/or downstream of event by ID. -func (m *MongoDB) UpstreamDownstreamSearch(ctx context.Context, id string) ([]schema.EiffelEvent, error) { +func (m *Driver) UpstreamDownstreamSearch(ctx context.Context, id string) ([]schema.EiffelEvent, error) { return nil, errors.New("not yet implemented") } // GetEventByID gets an event by ID in all collections. -func (m *MongoDB) GetEventByID(ctx context.Context, id string) (schema.EiffelEvent, error) { +func (m *Driver) GetEventByID(ctx context.Context, id string) (schema.EiffelEvent, error) { collections, err := m.Database.ListCollectionNames(ctx, bson.D{}) if err != nil { return schema.EiffelEvent{}, err @@ -101,6 +114,6 @@ func (m *MongoDB) GetEventByID(ctx context.Context, id string) (schema.EiffelEve } // Close the database connection. -func (m *MongoDB) Close(ctx context.Context) error { +func (m *Driver) Close(ctx context.Context) error { return m.Client.Disconnect(ctx) } diff --git a/pkg/application/application.go b/pkg/application/application.go index 93a1a69..2be6e39 100644 --- a/pkg/application/application.go +++ b/pkg/application/application.go @@ -23,12 +23,13 @@ import ( "github.com/eiffel-community/eiffel-goer/internal/config" "github.com/eiffel-community/eiffel-goer/internal/database" + "github.com/eiffel-community/eiffel-goer/internal/database/drivers" "github.com/eiffel-community/eiffel-goer/pkg/server" v1alpha1 "github.com/eiffel-community/eiffel-goer/pkg/v1alpha1/api" ) type Application struct { - Database database.Database + Database drivers.DatabaseDriver Config config.Config Router *mux.Router Server server.Server @@ -55,7 +56,7 @@ func Get(cfg config.Config, logger *log.Entry) (*Application, error) { } // getDB gets, but does not connect to, a database. -func (app *Application) getDB() (database.Database, error) { +func (app *Application) getDB() (drivers.DatabaseDriver, error) { db, err := database.Get( app.Config.DBConnectionString(), app.Logger, diff --git a/pkg/application/application_test.go b/pkg/application/application_test.go index 836b36e..1a1be70 100644 --- a/pkg/application/application_test.go +++ b/pkg/application/application_test.go @@ -23,9 +23,9 @@ import ( "github.com/golang/mock/gomock" log "github.com/sirupsen/logrus" - "github.com/eiffel-community/eiffel-goer/internal/database" + "github.com/eiffel-community/eiffel-goer/internal/database/drivers" "github.com/eiffel-community/eiffel-goer/test/mock_config" - "github.com/eiffel-community/eiffel-goer/test/mock_database" + "github.com/eiffel-community/eiffel-goer/test/mock_drivers" "github.com/eiffel-community/eiffel-goer/test/mock_server" ) @@ -102,7 +102,7 @@ func TestGetDB(t *testing.T) { if err != nil { t.Error(err) } - _, ok := db.(database.Database) + _, ok := db.(drivers.DatabaseDriver) if !ok { t.Error("database from 'getDB' is not a Database interface") } @@ -129,7 +129,7 @@ func TestLoadV1Alpha1Routes(t *testing.T) { func TestStart(t *testing.T) { ctrl := gomock.NewController(t) mockCfg := mock_config.NewMockConfig(ctrl) - mockDB := mock_database.NewMockDatabase(ctrl) + mockDB := mock_drivers.NewMockDatabaseDriver(ctrl) mockServer := mock_server.NewMockServer(ctrl) ctx := context.Background() @@ -163,7 +163,7 @@ func TestStartAbort(t *testing.T) { ctrl := gomock.NewController(t) mockCfg := mock_config.NewMockConfig(ctrl) - mockDB := mock_database.NewMockDatabase(ctrl) + mockDB := mock_drivers.NewMockDatabaseDriver(ctrl) mockServer := mock_server.NewMockServer(ctrl) ctx := context.Background() @@ -194,7 +194,7 @@ func TestStartAbort(t *testing.T) { func TestStartFail(t *testing.T) { ctrl := gomock.NewController(t) mockCfg := mock_config.NewMockConfig(ctrl) - mockDB := mock_database.NewMockDatabase(ctrl) + mockDB := mock_drivers.NewMockDatabaseDriver(ctrl) mockServer := mock_server.NewMockServer(ctrl) ctx := context.Background() @@ -225,7 +225,7 @@ func TestStartFail(t *testing.T) { func TestStop(t *testing.T) { ctrl := gomock.NewController(t) mockCfg := mock_config.NewMockConfig(ctrl) - mockDB := mock_database.NewMockDatabase(ctrl) + mockDB := mock_drivers.NewMockDatabaseDriver(ctrl) ctx := context.Background() mockCfg.EXPECT().DBConnectionString().Return("mongodb://testdb/testdb").Times(2) diff --git a/pkg/v1alpha1/api/api.go b/pkg/v1alpha1/api/api.go index b43fa77..1f49930 100644 --- a/pkg/v1alpha1/api/api.go +++ b/pkg/v1alpha1/api/api.go @@ -21,13 +21,13 @@ import ( log "github.com/sirupsen/logrus" "github.com/eiffel-community/eiffel-goer/internal/config" - "github.com/eiffel-community/eiffel-goer/internal/database" + "github.com/eiffel-community/eiffel-goer/internal/database/drivers" "github.com/eiffel-community/eiffel-goer/pkg/v1alpha1/handlers/events" "github.com/eiffel-community/eiffel-goer/pkg/v1alpha1/handlers/search" ) type V1Alpha1Application struct { - Database database.Database + Database drivers.DatabaseDriver Config config.Config Logger *log.Entry } diff --git a/pkg/v1alpha1/api/api_test.go b/pkg/v1alpha1/api/api_test.go index 1648af9..2a5c4c0 100644 --- a/pkg/v1alpha1/api/api_test.go +++ b/pkg/v1alpha1/api/api_test.go @@ -29,7 +29,7 @@ import ( "github.com/eiffel-community/eiffel-goer/pkg/application" "github.com/eiffel-community/eiffel-goer/pkg/schema" "github.com/eiffel-community/eiffel-goer/test/mock_config" - "github.com/eiffel-community/eiffel-goer/test/mock_database" + "github.com/eiffel-community/eiffel-goer/test/mock_drivers" ) // Test that all v1alpha1 endpoints are added properly. @@ -49,7 +49,7 @@ func TestRoutes(t *testing.T) { ctrl := gomock.NewController(t) mockCfg := mock_config.NewMockConfig(ctrl) - mockDB := mock_database.NewMockDatabase(ctrl) + mockDB := mock_drivers.NewMockDatabaseDriver(ctrl) mockCfg.EXPECT().DBConnectionString().Return("").AnyTimes() mockCfg.EXPECT().APIPort().Return(":8080").AnyTimes() diff --git a/pkg/v1alpha1/handlers/events/events.go b/pkg/v1alpha1/handlers/events/events.go index bc10b35..2500bbc 100644 --- a/pkg/v1alpha1/handlers/events/events.go +++ b/pkg/v1alpha1/handlers/events/events.go @@ -23,18 +23,18 @@ import ( log "github.com/sirupsen/logrus" "github.com/eiffel-community/eiffel-goer/internal/config" - "github.com/eiffel-community/eiffel-goer/internal/database" + "github.com/eiffel-community/eiffel-goer/internal/database/drivers" "github.com/eiffel-community/eiffel-goer/internal/responses" ) type EventHandler struct { Config config.Config - Database database.Database + Database drivers.DatabaseDriver Logger *log.Entry } // Create a new handler for the event endpoint. -func Get(cfg config.Config, db database.Database, logger *log.Entry) *EventHandler { +func Get(cfg config.Config, db drivers.DatabaseDriver, logger *log.Entry) *EventHandler { return &EventHandler{ cfg, db, logger, } diff --git a/pkg/v1alpha1/handlers/events/events_test.go b/pkg/v1alpha1/handlers/events/events_test.go index f7d1c21..e000c5a 100644 --- a/pkg/v1alpha1/handlers/events/events_test.go +++ b/pkg/v1alpha1/handlers/events/events_test.go @@ -28,7 +28,7 @@ import ( "github.com/eiffel-community/eiffel-goer/pkg/schema" "github.com/eiffel-community/eiffel-goer/test/mock_config" - "github.com/eiffel-community/eiffel-goer/test/mock_database" + "github.com/eiffel-community/eiffel-goer/test/mock_drivers" ) var activityJSON = []byte(` @@ -83,7 +83,7 @@ func TestEvents(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { ctrl := gomock.NewController(t) mockCfg := mock_config.NewMockConfig(ctrl) - mockDB := mock_database.NewMockDatabase(ctrl) + mockDB := mock_drivers.NewMockDatabaseDriver(ctrl) mockDB.EXPECT().GetEventByID(gomock.Any(), eventID).Return(event, testCase.mockError) app := Get(mockCfg, mockDB, &log.Entry{}) handler := mux.NewRouter() diff --git a/pkg/v1alpha1/handlers/search/search.go b/pkg/v1alpha1/handlers/search/search.go index 5887b61..1c40140 100644 --- a/pkg/v1alpha1/handlers/search/search.go +++ b/pkg/v1alpha1/handlers/search/search.go @@ -21,18 +21,18 @@ import ( log "github.com/sirupsen/logrus" "github.com/eiffel-community/eiffel-goer/internal/config" - "github.com/eiffel-community/eiffel-goer/internal/database" + "github.com/eiffel-community/eiffel-goer/internal/database/drivers" "github.com/eiffel-community/eiffel-goer/internal/responses" ) type SearchHandler struct { Config config.Config - Database database.Database + Database drivers.DatabaseDriver Logger *log.Entry } // Get a new handler for the search endpoint. -func Get(cfg config.Config, db database.Database, logger *log.Entry) *SearchHandler { +func Get(cfg config.Config, db drivers.DatabaseDriver, logger *log.Entry) *SearchHandler { return &SearchHandler{ cfg, db, logger, } diff --git a/test/test.go b/test/test.go index 7012fef..8983178 100644 --- a/test/test.go +++ b/test/test.go @@ -17,6 +17,6 @@ // This package is only for generating the mocks that are used in test. package test -//go:generate mockgen -destination mock_database/mock_database.go github.com/eiffel-community/eiffel-goer/internal/database Database +//go:generate mockgen -destination mock_drivers/mock_drivers.go github.com/eiffel-community/eiffel-goer/internal/database/drivers DatabaseDriver //go:generate mockgen -destination mock_config/mock_config.go github.com/eiffel-community/eiffel-goer/internal/config Config //go:generate mockgen -destination mock_server/mock_server.go github.com/eiffel-community/eiffel-goer/pkg/server Server From be90d6dc285c2c8235fe292e8a6ce79f5d03eb56 Mon Sep 17 00:00:00 2001 From: Tobias Persson Date: Mon, 30 Aug 2021 10:11:14 +0200 Subject: [PATCH 8/9] Use connstring.Parse instead of strings.Split for database name --- internal/database/drivers/mongodb/mongodb.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/database/drivers/mongodb/mongodb.go b/internal/database/drivers/mongodb/mongodb.go index 95c1bc0..d6e5178 100644 --- a/internal/database/drivers/mongodb/mongodb.go +++ b/internal/database/drivers/mongodb/mongodb.go @@ -24,13 +24,13 @@ import ( "errors" "fmt" "net/url" - "strings" log "github.com/sirupsen/logrus" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/readpref" + "go.mongodb.org/mongo-driver/x/mongo/driver/connstring" "github.com/eiffel-community/eiffel-goer/internal/database/drivers" "github.com/eiffel-community/eiffel-goer/pkg/schema" @@ -48,11 +48,13 @@ func (m *Driver) Get(connectionURL *url.URL, logger *log.Entry) (drivers.Databas if err != nil { return nil, err } - // The Path value from url.URL always has a '/' prepended. - databaseName := strings.Split(connectionURL.Path, "/")[1] + connectionString, err := connstring.Parse(connectionURL.String()) + if err != nil { + return nil, err + } return &Driver{ Client: client, - Database: client.Database(databaseName), + Database: client.Database(connectionString.Database), Logger: logger, }, nil } From 6acb5ca590a9b3220095238dfb645f8dd4e9e10a Mon Sep 17 00:00:00 2001 From: Tobias Persson Date: Tue, 31 Aug 2021 08:04:12 +0200 Subject: [PATCH 9/9] Move schema to internal instead of pkg since it's temporary --- internal/database/drivers/drivers.go | 2 +- internal/database/drivers/mongodb/mongodb.go | 2 +- {pkg => internal}/schema/schema.go | 2 +- pkg/v1alpha1/api/api_test.go | 2 +- pkg/v1alpha1/handlers/events/events_test.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename {pkg => internal}/schema/schema.go (96%) diff --git a/internal/database/drivers/drivers.go b/internal/database/drivers/drivers.go index d5c5615..4894427 100644 --- a/internal/database/drivers/drivers.go +++ b/internal/database/drivers/drivers.go @@ -21,7 +21,7 @@ import ( log "github.com/sirupsen/logrus" - "github.com/eiffel-community/eiffel-goer/pkg/schema" + "github.com/eiffel-community/eiffel-goer/internal/schema" ) type DatabaseDriver interface { diff --git a/internal/database/drivers/mongodb/mongodb.go b/internal/database/drivers/mongodb/mongodb.go index d6e5178..61a040f 100644 --- a/internal/database/drivers/mongodb/mongodb.go +++ b/internal/database/drivers/mongodb/mongodb.go @@ -33,7 +33,7 @@ import ( "go.mongodb.org/mongo-driver/x/mongo/driver/connstring" "github.com/eiffel-community/eiffel-goer/internal/database/drivers" - "github.com/eiffel-community/eiffel-goer/pkg/schema" + "github.com/eiffel-community/eiffel-goer/internal/schema" ) type Driver struct { diff --git a/pkg/schema/schema.go b/internal/schema/schema.go similarity index 96% rename from pkg/schema/schema.go rename to internal/schema/schema.go index f0f8215..275a6d7 100644 --- a/pkg/schema/schema.go +++ b/internal/schema/schema.go @@ -14,7 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// This package shall be removed then the Eiffel Go SDK is finished. +// This package shall be removed when the Eiffel Go SDK is finished. package schema type EiffelEvent struct { diff --git a/pkg/v1alpha1/api/api_test.go b/pkg/v1alpha1/api/api_test.go index 2a5c4c0..1e1e599 100644 --- a/pkg/v1alpha1/api/api_test.go +++ b/pkg/v1alpha1/api/api_test.go @@ -26,8 +26,8 @@ import ( "github.com/golang/mock/gomock" log "github.com/sirupsen/logrus" + "github.com/eiffel-community/eiffel-goer/internal/schema" "github.com/eiffel-community/eiffel-goer/pkg/application" - "github.com/eiffel-community/eiffel-goer/pkg/schema" "github.com/eiffel-community/eiffel-goer/test/mock_config" "github.com/eiffel-community/eiffel-goer/test/mock_drivers" ) diff --git a/pkg/v1alpha1/handlers/events/events_test.go b/pkg/v1alpha1/handlers/events/events_test.go index e000c5a..e77a7ec 100644 --- a/pkg/v1alpha1/handlers/events/events_test.go +++ b/pkg/v1alpha1/handlers/events/events_test.go @@ -26,7 +26,7 @@ import ( "github.com/gorilla/mux" log "github.com/sirupsen/logrus" - "github.com/eiffel-community/eiffel-goer/pkg/schema" + "github.com/eiffel-community/eiffel-goer/internal/schema" "github.com/eiffel-community/eiffel-goer/test/mock_config" "github.com/eiffel-community/eiffel-goer/test/mock_drivers" )