From de5ac7546be40dd257f3e095458a1cc405f001b0 Mon Sep 17 00:00:00 2001 From: JordanBrockopp Date: Tue, 21 Jun 2022 20:42:54 -0500 Subject: [PATCH 01/16] feat(database): add user engine --- database/user/count.go | 25 ++++ database/user/count_test.go | 93 ++++++++++++++ database/user/create.go | 48 ++++++++ database/user/create_test.go | 73 +++++++++++ database/user/delete.go | 30 +++++ database/user/delete_test.go | 73 +++++++++++ database/user/get.go | 48 ++++++++ database/user/get_name.go | 51 ++++++++ database/user/get_name_test.go | 86 +++++++++++++ database/user/get_test.go | 86 +++++++++++++ database/user/index.go | 24 ++++ database/user/index_test.go | 59 +++++++++ database/user/list.go | 67 ++++++++++ database/user/list_lite.go | 61 ++++++++++ database/user/list_lite_test.go | 116 ++++++++++++++++++ database/user/list_test.go | 105 ++++++++++++++++ database/user/opts.go | 54 ++++++++ database/user/opts_test.go | 210 ++++++++++++++++++++++++++++++++ database/user/service.go | 45 +++++++ database/user/table.go | 62 ++++++++++ database/user/table_test.go | 59 +++++++++ database/user/update.go | 48 ++++++++ database/user/update_test.go | 75 ++++++++++++ database/user/user.go | 82 +++++++++++++ database/user/user_test.go | 200 ++++++++++++++++++++++++++++++ 25 files changed, 1880 insertions(+) create mode 100644 database/user/count.go create mode 100644 database/user/count_test.go create mode 100644 database/user/create.go create mode 100644 database/user/create_test.go create mode 100644 database/user/delete.go create mode 100644 database/user/delete_test.go create mode 100644 database/user/get.go create mode 100644 database/user/get_name.go create mode 100644 database/user/get_name_test.go create mode 100644 database/user/get_test.go create mode 100644 database/user/index.go create mode 100644 database/user/index_test.go create mode 100644 database/user/list.go create mode 100644 database/user/list_lite.go create mode 100644 database/user/list_lite_test.go create mode 100644 database/user/list_test.go create mode 100644 database/user/opts.go create mode 100644 database/user/opts_test.go create mode 100644 database/user/service.go create mode 100644 database/user/table.go create mode 100644 database/user/table_test.go create mode 100644 database/user/update.go create mode 100644 database/user/update_test.go create mode 100644 database/user/user.go create mode 100644 database/user/user_test.go diff --git a/database/user/count.go b/database/user/count.go new file mode 100644 index 000000000..074a5ef66 --- /dev/null +++ b/database/user/count.go @@ -0,0 +1,25 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "github.com/go-vela/types/constants" +) + +// CountUsers gets the count of all users from the database. +func (e *engine) CountUsers() (int64, error) { + e.logger.Tracef("getting count of all users from the database") + + // variable to store query results + var u int64 + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableUser). + Count(&u). + Error + + return u, err +} diff --git a/database/user/count_test.go b/database/user/count_test.go new file mode 100644 index 000000000..be7cb6437 --- /dev/null +++ b/database/user/count_test.go @@ -0,0 +1,93 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestUser_Engine_CountUsers(t *testing.T) { + // setup types + _userOne := testUser() + _userOne.SetID(1) + _userOne.SetName("foo") + _userOne.SetToken("bar") + _userOne.SetHash("baz") + + _userTwo := testUser() + _userTwo.SetID(2) + _userTwo.SetName("baz") + _userTwo.SetToken("bar") + _userTwo.SetHash("foo") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"count"}).AddRow(2) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT count(*) FROM "users"`).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateUser(_userOne) + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + + err = _sqlite.CreateUser(_userTwo) + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want int64 + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: 2, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: 2, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.CountUsers() + + if test.failure { + if err == nil { + t.Errorf("CountUsers for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CountUsers for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("CountUsers for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/user/create.go b/database/user/create.go new file mode 100644 index 000000000..3cd3001b6 --- /dev/null +++ b/database/user/create.go @@ -0,0 +1,48 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "fmt" + + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// CreateUser creates a new user in the database. +func (e *engine) CreateUser(u *library.User) error { + e.logger.WithFields(logrus.Fields{ + "user": u.GetName(), + }).Tracef("creating user %s in the database", u.GetName()) + + // cast the library type to database type + // + // https://pkg.go.dev/github.com/go-vela/types/database#UserFromLibrary + user := database.UserFromLibrary(u) + + // validate the necessary fields are populated + // + // https://pkg.go.dev/github.com/go-vela/types/database#User.Validate + err := user.Validate() + if err != nil { + return err + } + + // encrypt the fields for the user + // + // https://pkg.go.dev/github.com/go-vela/types/database#User.Encrypt + err = user.Encrypt(e.config.EncryptionKey) + if err != nil { + return fmt.Errorf("unable to encrypt user %s: %w", u.GetName(), err) + } + + // send query to the database + return e.client. + Table(constants.TableUser). + Create(user). + Error +} diff --git a/database/user/create_test.go b/database/user/create_test.go new file mode 100644 index 000000000..12de80f96 --- /dev/null +++ b/database/user/create_test.go @@ -0,0 +1,73 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestUser_Engine_CreateUser(t *testing.T) { + // setup types + _user := testUser() + _user.SetID(1) + _user.SetName("foo") + _user.SetToken("bar") + _user.SetHash("baz") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"id"}).AddRow(1) + + // ensure the mock expects the query + _mock.ExpectQuery(`INSERT INTO "users" +("name","refresh_token","token","hash","favorites","active","admin","id") +VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING "id"`). + WithArgs("foo", AnyArgument{}, AnyArgument{}, AnyArgument{}, nil, false, false, 1). + WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.database.CreateUser(_user) + + if test.failure { + if err == nil { + t.Errorf("CreateUser for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateUser for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/user/delete.go b/database/user/delete.go new file mode 100644 index 000000000..95b77ff64 --- /dev/null +++ b/database/user/delete.go @@ -0,0 +1,30 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// DeleteUser deletes an existing user from the database. +func (e *engine) DeleteUser(u *library.User) error { + e.logger.WithFields(logrus.Fields{ + "user": u.GetName(), + }).Tracef("deleting user %s from the database", u.GetName()) + + // cast the library type to database type + // + // https://pkg.go.dev/github.com/go-vela/types/database#UserFromLibrary + user := database.UserFromLibrary(u) + + // send query to the database + return e.client. + Table(constants.TableUser). + Delete(user). + Error +} diff --git a/database/user/delete_test.go b/database/user/delete_test.go new file mode 100644 index 000000000..0dec0a6b2 --- /dev/null +++ b/database/user/delete_test.go @@ -0,0 +1,73 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestUser_Engine_DeleteUser(t *testing.T) { + // setup types + _user := testUser() + _user.SetID(1) + _user.SetName("foo") + _user.SetToken("bar") + _user.SetHash("baz") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // ensure the mock expects the query + _mock.ExpectExec(`DELETE FROM "users" WHERE "users"."id" = $1`). + WithArgs(1). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateUser(_user) + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err = test.database.DeleteUser(_user) + + if test.failure { + if err == nil { + t.Errorf("DeleteUser for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("DeleteUser for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/user/get.go b/database/user/get.go new file mode 100644 index 000000000..da8b1a0e7 --- /dev/null +++ b/database/user/get.go @@ -0,0 +1,48 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" +) + +// GetUser gets a user by ID from the database. +func (e *engine) GetUser(id int64) (*library.User, error) { + e.logger.Tracef("getting user %d from the database", id) + + // variable to store query results + u := new(database.User) + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableUser). + Where("id = ?", id). + Limit(1). + Scan(u). + Error + if err != nil { + return nil, err + } + + // decrypt the fields for the user + // + // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt + err = u.Decrypt(e.config.EncryptionKey) + if err != nil { + // TODO: remove backwards compatibility before 1.x.x release + // + // ensures that the change is backwards compatible + // by logging the error instead of returning it + // which allows us to fetch unencrypted users + e.logger.Errorf("unable to decrypt user %d: %v", u.ID.Int64, err) + } + + // return the decrypted user + // + // https://pkg.go.dev/github.com/go-vela/types/database#User.ToLibrary + return u.ToLibrary(), nil +} diff --git a/database/user/get_name.go b/database/user/get_name.go new file mode 100644 index 000000000..2244bfb86 --- /dev/null +++ b/database/user/get_name.go @@ -0,0 +1,51 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// GetUserForName gets a user by name from the database. +func (e *engine) GetUserForName(name string) (*library.User, error) { + e.logger.WithFields(logrus.Fields{ + "user": name, + }).Tracef("getting user %s from the database", name) + + // variable to store query results + u := new(database.User) + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableUser). + Where("name = ?", name). + Limit(1). + Scan(u). + Error + if err != nil { + return nil, err + } + + // decrypt the fields for the user + // + // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt + err = u.Decrypt(e.config.EncryptionKey) + if err != nil { + // TODO: remove backwards compatibility before 1.x.x release + // + // ensures that the change is backwards compatible + // by logging the error instead of returning it + // which allows us to fetch unencrypted users + e.logger.Errorf("unable to decrypt user %d: %v", u.ID.Int64, err) + } + + // return the decrypted user + // + // https://pkg.go.dev/github.com/go-vela/types/database#User.ToLibrary + return u.ToLibrary(), nil +} diff --git a/database/user/get_name_test.go b/database/user/get_name_test.go new file mode 100644 index 000000000..4b0a6446b --- /dev/null +++ b/database/user/get_name_test.go @@ -0,0 +1,86 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" +) + +func TestUser_Engine_GetUserForName(t *testing.T) { + // setup types + _user := testUser() + _user.SetID(1) + _user.SetName("foo") + _user.SetToken("bar") + _user.SetHash("baz") + _user.SetFavorites([]string{}) + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows( + []string{"id", "name", "refresh_token", "token", "hash", "favorites", "active", "admin"}). + AddRow(1, "foo", "", "bar", "baz", "{}", false, false) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "users" WHERE name = $1 LIMIT 1`).WithArgs("foo").WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateUser(_user) + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want *library.User + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: _user, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: _user, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.GetUserForName("foo") + + if test.failure { + if err == nil { + t.Errorf("GetUserForName for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("GetUserForName for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("GetUserForName for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/user/get_test.go b/database/user/get_test.go new file mode 100644 index 000000000..4593ce48f --- /dev/null +++ b/database/user/get_test.go @@ -0,0 +1,86 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" +) + +func TestUser_Engine_GetUser(t *testing.T) { + // setup types + _user := testUser() + _user.SetID(1) + _user.SetName("foo") + _user.SetToken("bar") + _user.SetHash("baz") + _user.SetFavorites([]string{}) + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows( + []string{"id", "name", "refresh_token", "token", "hash", "favorites", "active", "admin"}). + AddRow(1, "foo", "", "bar", "baz", "{}", false, false) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "users" WHERE id = $1 LIMIT 1`).WithArgs(1).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateUser(_user) + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want *library.User + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: _user, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: _user, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.GetUser(1) + + if test.failure { + if err == nil { + t.Errorf("GetUser for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("GetUser for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("GetUser for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/user/index.go b/database/user/index.go new file mode 100644 index 000000000..0593e0145 --- /dev/null +++ b/database/user/index.go @@ -0,0 +1,24 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +const ( + // CreateUserRefreshIndex represents a query to create an + // index on the users table for the refresh_token column. + CreateUserRefreshIndex = ` +CREATE INDEX +IF NOT EXISTS +users_refresh +ON users (refresh_token); +` +) + +// CreateIndexes creates the indexes for the users table in the database. +func (e *engine) CreateIndexes() error { + e.logger.Tracef("creating indexes for users table in the database") + + // create the refresh_token column index for the users table + return e.client.Exec(CreateUserRefreshIndex).Error +} diff --git a/database/user/index_test.go b/database/user/index_test.go new file mode 100644 index 000000000..a1e009619 --- /dev/null +++ b/database/user/index_test.go @@ -0,0 +1,59 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestUser_Engine_CreateIndexes(t *testing.T) { + // setup types + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + _mock.ExpectExec(CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.database.CreateIndexes() + + if test.failure { + if err == nil { + t.Errorf("CreateIndexes for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateIndexes for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/user/list.go b/database/user/list.go new file mode 100644 index 000000000..4bc730f27 --- /dev/null +++ b/database/user/list.go @@ -0,0 +1,67 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" +) + +// ListUsers gets a list of all users from the database. +func (e *engine) ListUsers() ([]*library.User, error) { + e.logger.Trace("listing all users from the database") + + // variables to store query results and return value + count := int64(0) + u := new([]database.User) + users := []*library.User{} + + // count the results + count, err := e.CountUsers() + if err != nil { + return nil, err + } + + // short-circuit if there are no results + if count == 0 { + return users, nil + } + + // send query to the database and store result in variable + err = e.client. + Table(constants.TableUser). + Find(&u). + Error + if err != nil { + return nil, err + } + + // iterate through all query results + for _, user := range *u { + // https://golang.org/doc/faq#closures_and_goroutines + tmp := user + + // decrypt the fields for the user + // + // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt + err = tmp.Decrypt(e.config.EncryptionKey) + if err != nil { + // TODO: remove backwards compatibility before 1.x.x release + // + // ensures that the change is backwards compatible + // by logging the error instead of returning it + // which allows us to fetch unencrypted users + e.logger.Errorf("unable to decrypt user %d: %v", tmp.ID.Int64, err) + } + + // convert query result to library type + // + // https://pkg.go.dev/github.com/go-vela/types/database#User.ToLibrary + users = append(users, tmp.ToLibrary()) + } + + return users, nil +} diff --git a/database/user/list_lite.go b/database/user/list_lite.go new file mode 100644 index 000000000..27de2aafe --- /dev/null +++ b/database/user/list_lite.go @@ -0,0 +1,61 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" +) + +// ListLiteUsers gets a lite list of users from the database. +// +// nolint: lll // ignore long line length due to variable names +func (e *engine) ListLiteUsers(page, perPage int) ([]*library.User, int64, error) { + e.logger.Trace("listing lite users from the database") + + // variables to store query results and return values + count := int64(0) + u := new([]database.User) + users := []*library.User{} + + // count the results + count, err := e.CountUsers() + if err != nil { + return users, 0, err + } + + // short-circuit if there are no results + if count == 0 { + return users, 0, nil + } + + // calculate offset for pagination through results + offset := perPage * (page - 1) + + err = e.client. + Table(constants.TableUser). + Select("id", "name"). + Limit(perPage). + Offset(offset). + Find(&u). + Error + if err != nil { + return nil, count, err + } + + // iterate through all query results + for _, user := range *u { + // https://golang.org/doc/faq#closures_and_goroutines + tmp := user + + // convert query result to library type + // + // https://pkg.go.dev/github.com/go-vela/types/database#User.ToLibrary + users = append(users, tmp.ToLibrary()) + } + + return users, count, nil +} diff --git a/database/user/list_lite_test.go b/database/user/list_lite_test.go new file mode 100644 index 000000000..4c44bc20b --- /dev/null +++ b/database/user/list_lite_test.go @@ -0,0 +1,116 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" +) + +func TestUser_Engine_ListLiteUsers(t *testing.T) { + // setup types + _userOne := testUser() + _userOne.SetID(1) + _userOne.SetName("foo") + _userOne.SetToken("bar") + _userOne.SetHash("baz") + _userOne.SetFavorites([]string{}) + + _userTwo := testUser() + _userTwo.SetID(2) + _userTwo.SetName("baz") + _userTwo.SetToken("bar") + _userTwo.SetHash("foo") + _userTwo.SetFavorites([]string{}) + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"count"}).AddRow(1) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT count(*) FROM "users"`).WillReturnRows(_rows) + + // create expected result in mock + _rows = sqlmock.NewRows( + []string{"id", "name"}). + AddRow(1, "foo"). + AddRow(2, "baz") + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT "id","name" FROM "users" LIMIT 10`).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateUser(_userOne) + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + + err = _sqlite.CreateUser(_userTwo) + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + + // empty fields not returned by query + _userOne.RefreshToken = new(string) + _userOne.Token = new(string) + _userOne.Hash = new(string) + _userOne.Favorites = new([]string) + + _userTwo.RefreshToken = new(string) + _userTwo.Token = new(string) + _userTwo.Hash = new(string) + _userTwo.Favorites = new([]string) + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want []*library.User + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: []*library.User{_userOne, _userTwo}, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: []*library.User{_userTwo, _userOne}, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, _, err := test.database.ListLiteUsers(1, 10) + + if test.failure { + if err == nil { + t.Errorf("ListLiteUsers for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("ListLiteUsers for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("ListLiteUsers for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/user/list_test.go b/database/user/list_test.go new file mode 100644 index 000000000..61293d44c --- /dev/null +++ b/database/user/list_test.go @@ -0,0 +1,105 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" +) + +func TestUser_Engine_ListUsers(t *testing.T) { + // setup types + _userOne := testUser() + _userOne.SetID(1) + _userOne.SetName("foo") + _userOne.SetToken("bar") + _userOne.SetHash("baz") + _userOne.SetFavorites([]string{}) + + _userTwo := testUser() + _userTwo.SetID(2) + _userTwo.SetName("baz") + _userTwo.SetToken("bar") + _userTwo.SetHash("foo") + _userTwo.SetFavorites([]string{}) + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"count"}).AddRow(2) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT count(*) FROM "users"`).WillReturnRows(_rows) + + // create expected result in mock + _rows = sqlmock.NewRows( + []string{"id", "name", "refresh_token", "token", "hash", "favorites", "active", "admin"}). + AddRow(1, "foo", "", "bar", "baz", "{}", false, false). + AddRow(2, "baz", "", "bar", "foo", "{}", false, false) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "users"`).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateUser(_userOne) + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + + err = _sqlite.CreateUser(_userTwo) + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want []*library.User + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: []*library.User{_userOne, _userTwo}, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: []*library.User{_userOne, _userTwo}, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.ListUsers() + + if test.failure { + if err == nil { + t.Errorf("ListUsers for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("ListUsers for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("ListUsers for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/user/opts.go b/database/user/opts.go new file mode 100644 index 000000000..58780c317 --- /dev/null +++ b/database/user/opts.go @@ -0,0 +1,54 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "github.com/sirupsen/logrus" + + "gorm.io/gorm" +) + +// EngineOpt represents a configuration option to initialize the database engine for Users. +type EngineOpt func(*engine) error + +// WithClient sets the gorm.io/gorm client in the database engine for Users. +func WithClient(client *gorm.DB) EngineOpt { + return func(e *engine) error { + // set the gorm.io/gorm client in the user engine + e.client = client + + return nil + } +} + +// WithEncryptionKey sets the encryption key in the database engine for Users. +func WithEncryptionKey(key string) EngineOpt { + return func(e *engine) error { + // set the encryption key in the user engine + e.config.EncryptionKey = key + + return nil + } +} + +// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for Users. +func WithLogger(logger *logrus.Entry) EngineOpt { + return func(e *engine) error { + // set the github.com/sirupsen/logrus logger in the user engine + e.logger = logger + + return nil + } +} + +// WithSkipCreation sets the skip creation logic in the database engine for Users. +func WithSkipCreation(skipCreation bool) EngineOpt { + return func(e *engine) error { + // set to skip creating tables and indexes in the user engine + e.config.SkipCreation = skipCreation + + return nil + } +} diff --git a/database/user/opts_test.go b/database/user/opts_test.go new file mode 100644 index 000000000..77fb9ae23 --- /dev/null +++ b/database/user/opts_test.go @@ -0,0 +1,210 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "reflect" + "testing" + + "github.com/sirupsen/logrus" + + "gorm.io/gorm" +) + +func TestUser_EngineOpt_WithClient(t *testing.T) { + // setup types + e := &engine{client: new(gorm.DB)} + + // setup tests + tests := []struct { + failure bool + name string + client *gorm.DB + want *gorm.DB + }{ + { + failure: false, + name: "client set to new database", + client: new(gorm.DB), + want: new(gorm.DB), + }, + { + failure: false, + name: "client set to nil", + client: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithClient(test.client)(e) + + if test.failure { + if err == nil { + t.Errorf("WithClient for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithClient returned err: %v", err) + } + + if !reflect.DeepEqual(e.client, test.want) { + t.Errorf("WithClient is %v, want %v", e.client, test.want) + } + }) + } +} + +func TestUser_EngineOpt_WithEncryptionKey(t *testing.T) { + // setup types + e := &engine{config: new(config)} + + // setup tests + tests := []struct { + failure bool + name string + key string + want string + }{ + { + failure: false, + name: "encryption key set", + key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", + want: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", + }, + { + failure: false, + name: "encryption key not set", + key: "", + want: "", + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithEncryptionKey(test.key)(e) + + if test.failure { + if err == nil { + t.Errorf("WithEncryptionKey for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithEncryptionKey returned err: %v", err) + } + + if !reflect.DeepEqual(e.config.EncryptionKey, test.want) { + t.Errorf("WithEncryptionKey is %v, want %v", e.config.EncryptionKey, test.want) + } + }) + } +} + +func TestUser_EngineOpt_WithLogger(t *testing.T) { + // setup types + e := &engine{logger: new(logrus.Entry)} + + // setup tests + tests := []struct { + failure bool + name string + logger *logrus.Entry + want *logrus.Entry + }{ + { + failure: false, + name: "logger set to new entry", + logger: new(logrus.Entry), + want: new(logrus.Entry), + }, + { + failure: false, + name: "logger set to nil", + logger: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithLogger(test.logger)(e) + + if test.failure { + if err == nil { + t.Errorf("WithLogger for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithLogger returned err: %v", err) + } + + if !reflect.DeepEqual(e.logger, test.want) { + t.Errorf("WithLogger is %v, want %v", e.logger, test.want) + } + }) + } +} + +func TestUser_EngineOpt_WithSkipCreation(t *testing.T) { + // setup types + e := &engine{config: new(config)} + + // setup tests + tests := []struct { + failure bool + name string + skipCreation bool + want bool + }{ + { + failure: false, + name: "skip creation set to true", + skipCreation: true, + want: true, + }, + { + failure: false, + name: "skip creation set to false", + skipCreation: false, + want: false, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithSkipCreation(test.skipCreation)(e) + + if test.failure { + if err == nil { + t.Errorf("WithSkipCreation for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithSkipCreation returned err: %v", err) + } + + if !reflect.DeepEqual(e.config.SkipCreation, test.want) { + t.Errorf("WithSkipCreation is %v, want %v", e.config.SkipCreation, test.want) + } + }) + } +} diff --git a/database/user/service.go b/database/user/service.go new file mode 100644 index 000000000..a14ca97d3 --- /dev/null +++ b/database/user/service.go @@ -0,0 +1,45 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "github.com/go-vela/types/library" +) + +// UserService represents the Vela interface for user +// functions with the supported Database backends. +// +// nolint: revive // ignore name stutter +type UserService interface { + // User Data Definition Language Functions + // + // https://en.wikipedia.org/wiki/Data_definition_language + + // CreateIndexes defines a function that creates the indexes for the users table. + CreateIndexes() error + // CreateTable defines a function that creates the users table. + CreateTable(string) error + + // User Data Manipulation Language Functions + // + // https://en.wikipedia.org/wiki/Data_manipulation_language + + // CountUsers defines a function that gets the count of all users. + CountUsers() (int64, error) + // CreateUser defines a function that creates a new user. + CreateUser(*library.User) error + // DeleteUser defines a function that deletes an existing user. + DeleteUser(*library.User) error + // GetUser defines a function that gets a user by ID. + GetUser(int64) (*library.User, error) + // GetUserForName defines a function that gets a user by name. + GetUserForName(string) (*library.User, error) + // ListUsers defines a function that gets a list of all users. + ListUsers() ([]*library.User, error) + // ListLiteUsers defines a function that gets a lite list of users. + ListLiteUsers(int, int) ([]*library.User, int64, error) + // UpdateUser defines a function that updates an existing user. + UpdateUser(*library.User) error +} diff --git a/database/user/table.go b/database/user/table.go new file mode 100644 index 000000000..cf0c160fa --- /dev/null +++ b/database/user/table.go @@ -0,0 +1,62 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "github.com/go-vela/types/constants" +) + +const ( + // CreatePostgresTable represents a query to create the Postgres users table. + CreatePostgresTable = ` +CREATE TABLE +IF NOT EXISTS +users ( + id SERIAL PRIMARY KEY, + name VARCHAR(250), + refresh_token VARCHAR(500), + token VARCHAR(500), + hash VARCHAR(500), + favorites VARCHAR(5000), + active BOOLEAN, + admin BOOLEAN, + UNIQUE(name) +); +` + + // CreateSqliteTable represents a query to create the Sqlite users table. + CreateSqliteTable = ` +CREATE TABLE +IF NOT EXISTS +users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + refresh_token TEXT, + token TEXT, + hash TEXT, + favorites TEXT, + active BOOLEAN, + admin BOOLEAN, + UNIQUE(name) +); +` +) + +// CreateTable creates the users table in the database. +func (e *engine) CreateTable(driver string) error { + e.logger.Tracef("creating users table in the database") + + // handle the driver provided to create the table + switch driver { + case constants.DriverPostgres: + // create the users table for Postgres + return e.client.Exec(CreatePostgresTable).Error + case constants.DriverSqlite: + fallthrough + default: + // create the users table for Sqlite + return e.client.Exec(CreateSqliteTable).Error + } +} diff --git a/database/user/table_test.go b/database/user/table_test.go new file mode 100644 index 000000000..b56bfe55b --- /dev/null +++ b/database/user/table_test.go @@ -0,0 +1,59 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestUser_Engine_CreateTable(t *testing.T) { + // setup types + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.database.CreateTable(test.name) + + if test.failure { + if err == nil { + t.Errorf("CreateTable for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateTable for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/user/update.go b/database/user/update.go new file mode 100644 index 000000000..e9ccd99e3 --- /dev/null +++ b/database/user/update.go @@ -0,0 +1,48 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "fmt" + + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// UpdateUser updates an existing user in the database. +func (e *engine) UpdateUser(u *library.User) error { + e.logger.WithFields(logrus.Fields{ + "user": u.GetName(), + }).Tracef("updating user %s in the database", u.GetName()) + + // cast the library type to database type + // + // https://pkg.go.dev/github.com/go-vela/types/database#UserFromLibrary + user := database.UserFromLibrary(u) + + // validate the necessary fields are populated + // + // https://pkg.go.dev/github.com/go-vela/types/database#User.Validate + err := user.Validate() + if err != nil { + return err + } + + // encrypt the fields for the user + // + // https://pkg.go.dev/github.com/go-vela/types/database#User.Encrypt + err = user.Encrypt(e.config.EncryptionKey) + if err != nil { + return fmt.Errorf("unable to encrypt user %s: %w", u.GetName(), err) + } + + // send query to the database + return e.client. + Table(constants.TableUser). + Save(user). + Error +} diff --git a/database/user/update_test.go b/database/user/update_test.go new file mode 100644 index 000000000..4253ac435 --- /dev/null +++ b/database/user/update_test.go @@ -0,0 +1,75 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestUser_Engine_UpdateUser(t *testing.T) { + // setup types + _user := testUser() + _user.SetID(1) + _user.SetName("foo") + _user.SetToken("bar") + _user.SetHash("baz") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // ensure the mock expects the query + _mock.ExpectExec(`UPDATE "users" +SET "name"=$1,"refresh_token"=$2,"token"=$3,"hash"=$4,"favorites"=$5,"active"=$6,"admin"=$7 +WHERE "id" = $8`). + WithArgs("foo", AnyArgument{}, AnyArgument{}, AnyArgument{}, nil, false, false, 1). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateUser(_user) + if err != nil { + t.Errorf("unable to create test user for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err = test.database.UpdateUser(_user) + + if test.failure { + if err == nil { + t.Errorf("UpdateUser for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("UpdateUser for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/user/user.go b/database/user/user.go new file mode 100644 index 000000000..8c1c192f8 --- /dev/null +++ b/database/user/user.go @@ -0,0 +1,82 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "fmt" + + "github.com/go-vela/types/constants" + "github.com/sirupsen/logrus" + + "gorm.io/gorm" +) + +type ( + // config represents the settings required to create the engine that implements the UserService interface. + config struct { + // specifies the encryption key to use for the User engine + EncryptionKey string + // specifies to skip creating tables and indexes for the User engine + SkipCreation bool + } + + // engine represents the user functionality that implements the UserService interface. + engine struct { + // engine configuration settings used in user functions + config *config + + // gorm.io/gorm database client used in user functions + // + // https://pkg.go.dev/gorm.io/gorm#DB + client *gorm.DB + + // sirupsen/logrus logger used in user functions + // + // https://pkg.go.dev/github.com/sirupsen/logrus#Entry + logger *logrus.Entry + } +) + +// New creates and returns a Vela service for integrating with users in the database. +// +// nolint: revive // ignore returning unexported engine +func New(opts ...EngineOpt) (*engine, error) { + // create new User engine + e := new(engine) + + // create new fields + e.client = new(gorm.DB) + e.config = new(config) + e.logger = new(logrus.Entry) + + // apply all provided configuration options + for _, opt := range opts { + err := opt(e) + if err != nil { + return nil, err + } + } + + // check if we should skip creating user database objects + if e.config.SkipCreation { + e.logger.Warning("skipping creation of users table and indexes in the database") + + return e, nil + } + + // create the users table + err := e.CreateTable(e.client.Config.Dialector.Name()) + if err != nil { + return nil, fmt.Errorf("unable to create %s table: %w", constants.TableUser, err) + } + + // create the indexes for the users table + err = e.CreateIndexes() + if err != nil { + return nil, fmt.Errorf("unable to create indexes for %s table: %w", constants.TableUser, err) + } + + return e, nil +} diff --git a/database/user/user_test.go b/database/user/user_test.go new file mode 100644 index 000000000..1586103a7 --- /dev/null +++ b/database/user/user_test.go @@ -0,0 +1,200 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package user + +import ( + "database/sql/driver" + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" + + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func TestUser_New(t *testing.T) { + // setup types + logger := logrus.NewEntry(logrus.StandardLogger()) + + _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + if err != nil { + t.Errorf("unable to create new SQL mock: %v", err) + } + defer _sql.Close() + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + + _config := &gorm.Config{SkipDefaultTransaction: true} + + _postgres, err := gorm.Open(postgres.New(postgres.Config{Conn: _sql}), _config) + if err != nil { + t.Errorf("unable to create new postgres database: %v", err) + } + + _sqlite, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), _config) + if err != nil { + t.Errorf("unable to create new sqlite database: %v", err) + } + + defer func() { _sql, _ := _sqlite.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + client *gorm.DB + key string + logger *logrus.Entry + skipCreation bool + want *engine + }{ + { + failure: false, + name: "postgres", + client: _postgres, + key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", + logger: logger, + skipCreation: false, + want: &engine{ + client: _postgres, + config: &config{EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", SkipCreation: false}, + logger: logger, + }, + }, + { + failure: false, + name: "sqlite3", + client: _sqlite, + key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", + logger: logger, + skipCreation: false, + want: &engine{ + client: _sqlite, + config: &config{EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", SkipCreation: false}, + logger: logger, + }, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := New( + WithClient(test.client), + WithEncryptionKey(test.key), + WithLogger(test.logger), + WithSkipCreation(test.skipCreation), + ) + + if test.failure { + if err == nil { + t.Errorf("New for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("New for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("New for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} + +// testPostgres is a helper function to create a Postgres engine for testing. +func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) { + // create the new mock sql database + // + // https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#New + _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + if err != nil { + t.Errorf("unable to create new SQL mock: %v", err) + } + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + + // create the new mock Postgres database client + // + // https://pkg.go.dev/gorm.io/gorm#Open + _postgres, err := gorm.Open( + postgres.New(postgres.Config{Conn: _sql}), + &gorm.Config{SkipDefaultTransaction: true}, + ) + if err != nil { + t.Errorf("unable to create new postgres database: %v", err) + } + + _engine, err := New( + WithClient(_postgres), + WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"), + WithLogger(logrus.NewEntry(logrus.StandardLogger())), + WithSkipCreation(false), + ) + if err != nil { + t.Errorf("unable to create new postgres user engine: %v", err) + } + + return _engine, _mock +} + +// testSqlite is a helper function to create a Sqlite engine for testing. +func testSqlite(t *testing.T) *engine { + _sqlite, err := gorm.Open( + sqlite.Open("file::memory:?cache=shared"), + &gorm.Config{SkipDefaultTransaction: true}, + ) + if err != nil { + t.Errorf("unable to create new sqlite database: %v", err) + } + + _engine, err := New( + WithClient(_sqlite), + WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"), + WithLogger(logrus.NewEntry(logrus.StandardLogger())), + WithSkipCreation(false), + ) + if err != nil { + t.Errorf("unable to create new sqlite user engine: %v", err) + } + + return _engine +} + +// testUser is a test helper function to create a library +// User type with all fields set to their zero values. +func testUser() *library.User { + return &library.User{ + ID: new(int64), + Name: new(string), + RefreshToken: new(string), + Token: new(string), + Hash: new(string), + Favorites: new([]string), + Active: new(bool), + Admin: new(bool), + } +} + +// This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values +// that are otherwise not easily compared. These typically would be values generated +// before adding or updating them in the database. +// +// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime +type AnyArgument struct{} + +// Match satisfies sqlmock.Argument interface. +func (a AnyArgument) Match(v driver.Value) bool { + return true +} From 8f2fce6a83c9f0b87c6ee3737c4b23b46dc3ccdd Mon Sep 17 00:00:00 2001 From: JordanBrockopp Date: Tue, 21 Jun 2022 21:16:30 -0500 Subject: [PATCH 02/16] chore(database): remove old user logic --- database/postgres/ddl/user.go | 34 ---- database/postgres/dml/user.go | 68 -------- database/postgres/user.go | 173 ------------------ database/postgres/user_count.go | 26 --- database/postgres/user_count_test.go | 82 --------- database/postgres/user_list.go | 86 --------- database/postgres/user_list_test.go | 150 ---------------- database/postgres/user_test.go | 250 --------------------------- database/sqlite/ddl/user.go | 34 ---- database/sqlite/dml/user.go | 68 -------- database/sqlite/user.go | 173 ------------------ database/sqlite/user_count.go | 26 --- database/sqlite/user_count_test.go | 97 ----------- database/sqlite/user_list.go | 86 --------- database/sqlite/user_list_test.go | 166 ------------------ database/sqlite/user_test.go | 248 -------------------------- 16 files changed, 1767 deletions(-) delete mode 100644 database/postgres/ddl/user.go delete mode 100644 database/postgres/dml/user.go delete mode 100644 database/postgres/user.go delete mode 100644 database/postgres/user_count.go delete mode 100644 database/postgres/user_count_test.go delete mode 100644 database/postgres/user_list.go delete mode 100644 database/postgres/user_list_test.go delete mode 100644 database/postgres/user_test.go delete mode 100644 database/sqlite/ddl/user.go delete mode 100644 database/sqlite/dml/user.go delete mode 100644 database/sqlite/user.go delete mode 100644 database/sqlite/user_count.go delete mode 100644 database/sqlite/user_count_test.go delete mode 100644 database/sqlite/user_list.go delete mode 100644 database/sqlite/user_list_test.go delete mode 100644 database/sqlite/user_test.go diff --git a/database/postgres/ddl/user.go b/database/postgres/ddl/user.go deleted file mode 100644 index b6fec0794..000000000 --- a/database/postgres/ddl/user.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package ddl - -const ( - // CreateUserTable represents a query to - // create the users table for Vela. - CreateUserTable = ` -CREATE TABLE -IF NOT EXISTS -users ( - id SERIAL PRIMARY KEY, - name VARCHAR(250), - refresh_token VARCHAR(500), - token VARCHAR(500), - hash VARCHAR(500), - favorites VARCHAR(5000), - active BOOLEAN, - admin BOOLEAN, - UNIQUE(name) -); -` - - // CreateUserRefreshIndex represents a query to create an - // index on the users table for the refresh_token column. - CreateUserRefreshIndex = ` -CREATE INDEX -IF NOT EXISTS -users_refresh -ON users (refresh_token); -` -) diff --git a/database/postgres/dml/user.go b/database/postgres/dml/user.go deleted file mode 100644 index 9febce658..000000000 --- a/database/postgres/dml/user.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package dml - -const ( - // ListUsers represents a query to - // list all users in the database. - ListUsers = ` -SELECT * -FROM users; -` - - // ListLiteUsers represents a query to - // list all lite users in the database. - ListLiteUsers = ` -SELECT id, name -FROM users -ORDER BY id DESC -LIMIT ? -OFFSET ?; -` - - // SelectUser represents a query to select - // a user for an id in the database. - SelectUser = ` -SELECT * -FROM users -WHERE id = ? -LIMIT 1; -` - - // SelectUserName represents a query to select - // a user for a name in the database. - SelectUserName = ` -SELECT * -FROM users -WHERE name = ? -LIMIT 1; -` - - // SelectUsersCount represents a query to select - // the count of users in the database. - SelectUsersCount = ` -SELECT count(*) as count -FROM users; -` - - // SelectRefreshToken represents a query to select - // a user for a refresh_token in the database. - // - // nolint: gosec // ignore false positive - SelectRefreshToken = ` -SELECT * -FROM users -WHERE refresh_token = ? -LIMIT 1; -` - - // DeleteUser represents a query to - // remove a user from the database. - DeleteUser = ` -DELETE -FROM users -WHERE id = ?; -` -) diff --git a/database/postgres/user.go b/database/postgres/user.go deleted file mode 100644 index ca36ae49c..000000000 --- a/database/postgres/user.go +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "errors" - "fmt" - - "github.com/sirupsen/logrus" - - "github.com/go-vela/server/database/postgres/dml" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" - - "gorm.io/gorm" -) - -// GetUser gets a user by unique ID from the database. -func (c *client) GetUser(id int64) (*library.User, error) { - c.Logger.Tracef("getting user %d from the database", id) - - // variable to store query results - u := new(database.User) - - // send query to the database and store result in variable - result := c.Postgres. - Table(constants.TableUser). - Raw(dml.SelectUser, id). - Scan(u) - - // check if the query returned a record not found error or no rows were returned - if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 { - return nil, gorm.ErrRecordNotFound - } - - // decrypt the fields for the user - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt - err := u.Decrypt(c.config.EncryptionKey) - if err != nil { - // ensures that the change is backwards compatible - // by logging the error instead of returning it - // which allows us to fetch unencrypted users - c.Logger.Errorf("unable to decrypt user %d: %v", id, err) - - // return the unencrypted user - return u.ToLibrary(), result.Error - } - - // return the decrypted user - return u.ToLibrary(), result.Error -} - -// GetUserName gets a user by name from the database. -func (c *client) GetUserName(name string) (*library.User, error) { - c.Logger.WithFields(logrus.Fields{ - "user": name, - }).Tracef("getting user %s from the database", name) - - // variable to store query results - u := new(database.User) - - // send query to the database and store result in variable - result := c.Postgres. - Table(constants.TableUser). - Raw(dml.SelectUserName, name). - Scan(u) - - // check if the query returned a record not found error or no rows were returned - if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 { - return nil, gorm.ErrRecordNotFound - } - - // decrypt the fields for the user - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt - err := u.Decrypt(c.config.EncryptionKey) - if err != nil { - // ensures that the change is backwards compatible - // by logging the error instead of returning it - // which allows us to fetch unencrypted users - c.Logger.Errorf("unable to decrypt user %s: %v", name, err) - - // return the unencrypted user - return u.ToLibrary(), result.Error - } - - // return the decrypted user - return u.ToLibrary(), result.Error -} - -// CreateUser creates a new user in the database. -// -// nolint: dupl // ignore similar code with update -func (c *client) CreateUser(u *library.User) error { - c.Logger.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Tracef("creating user %s in the database", u.GetName()) - - // cast to database type - // - // https://pkg.go.dev/github.com/go-vela/types/database#UserFromLibrary - user := database.UserFromLibrary(u) - - // validate the necessary fields are populated - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Validate - err := user.Validate() - if err != nil { - return err - } - - // encrypt the fields for the user - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Encrypt - err = user.Encrypt(c.config.EncryptionKey) - if err != nil { - return fmt.Errorf("unable to encrypt user %s: %w", u.GetName(), err) - } - - // send query to the database - return c.Postgres. - Table(constants.TableUser). - Create(user).Error -} - -// UpdateUser updates a user in the database. -// -// nolint: dupl // ignore similar code with create -func (c *client) UpdateUser(u *library.User) error { - c.Logger.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Tracef("updating user %s in the database", u.GetName()) - - // cast to database type - // - // https://pkg.go.dev/github.com/go-vela/types/database#UserFromLibrary - user := database.UserFromLibrary(u) - - // validate the necessary fields are populated - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Validate - err := user.Validate() - if err != nil { - return err - } - - // encrypt the fields for the user - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Encrypt - err = user.Encrypt(c.config.EncryptionKey) - if err != nil { - return fmt.Errorf("unable to encrypt user %s: %w", u.GetName(), err) - } - - // send query to the database - return c.Postgres. - Table(constants.TableUser). - Save(user).Error -} - -// DeleteUser deletes a user by unique ID from the database. -func (c *client) DeleteUser(id int64) error { - c.Logger.Tracef("deleting user %d from the database", id) - - // send query to the database - return c.Postgres. - Table(constants.TableUser). - Exec(dml.DeleteUser, id).Error -} diff --git a/database/postgres/user_count.go b/database/postgres/user_count.go deleted file mode 100644 index 9b41092ab..000000000 --- a/database/postgres/user_count.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "github.com/go-vela/server/database/postgres/dml" - "github.com/go-vela/types/constants" -) - -// GetUserCount gets a count of all users from the database. -func (c *client) GetUserCount() (int64, error) { - c.Logger.Trace("getting count of users from the database") - - // variable to store query results - var u int64 - - // send query to the database and store result in variable - err := c.Postgres. - Table(constants.TableUser). - Raw(dml.SelectUsersCount). - Pluck("count", &u).Error - - return u, err -} diff --git a/database/postgres/user_count_test.go b/database/postgres/user_count_test.go deleted file mode 100644 index 55a0bbaf4..000000000 --- a/database/postgres/user_count_test.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "reflect" - "testing" - - sqlmock "github.com/DATA-DOG/go-sqlmock" - - "github.com/go-vela/server/database/postgres/dml" - - "gorm.io/gorm" -) - -func TestPostgres_Client_GetUserCount(t *testing.T) { - // setup types - _userOne := testUser() - _userOne.SetID(1) - _userOne.SetName("foo") - _userOne.SetToken("bar") - _userOne.SetHash("baz") - - _userTwo := testUser() - _userTwo.SetID(2) - _userTwo.SetName("bar") - _userTwo.SetToken("foo") - _userTwo.SetHash("baz") - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // capture the current expected SQL query - // - // https://gorm.io/docs/sql_builder.html#DryRun-Mode - _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectUsersCount).Statement - - // create expected return in mock - _rows := sqlmock.NewRows([]string{"count"}).AddRow(2) - - // ensure the mock expects the query - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want int64 - }{ - { - failure: false, - want: 2, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetUserCount() - - if test.failure { - if err == nil { - t.Errorf("GetUserCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetUserCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetUserCount is %v, want %v", got, test.want) - } - } -} diff --git a/database/postgres/user_list.go b/database/postgres/user_list.go deleted file mode 100644 index 0dfb227f4..000000000 --- a/database/postgres/user_list.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "github.com/go-vela/server/database/postgres/dml" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" -) - -// GetUserList gets a list of all users from the database. -// -// nolint: dupl // ignore false positive of duplicate code -func (c *client) GetUserList() ([]*library.User, error) { - c.Logger.Trace("listing users from the database") - - // variable to store query results - u := new([]database.User) - - // send query to the database and store result in variable - err := c.Postgres. - Table(constants.TableUser). - Raw(dml.ListUsers). - Scan(u).Error - if err != nil { - return nil, err - } - - // variable we want to return - users := []*library.User{} - // iterate through all query results - for _, user := range *u { - // https://golang.org/doc/faq#closures_and_goroutines - tmp := user - - // decrypt the fields for the user - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt - err = tmp.Decrypt(c.config.EncryptionKey) - if err != nil { - // ensures that the change is backwards compatible - // by logging the error instead of returning it - // which allows us to fetch unencrypted users - c.Logger.Errorf("unable to decrypt user %d: %v", tmp.ID.Int64, err) - } - - // convert query result to library type - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.ToLibrary - users = append(users, tmp.ToLibrary()) - } - - return users, nil -} - -// GetUserLiteList gets a lite list of all users from the database. -func (c *client) GetUserLiteList(page, perPage int) ([]*library.User, error) { - c.Logger.Trace("listing lite users from the database") - - // variable to store query results - u := new([]database.User) - // calculate offset for pagination through results - offset := perPage * (page - 1) - - // send query to the database and store result in variable - err := c.Postgres. - Table(constants.TableUser). - Raw(dml.ListLiteUsers, perPage, offset). - Scan(u).Error - - // variable we want to return - users := []*library.User{} - // iterate through all query results - for _, user := range *u { - // https://golang.org/doc/faq#closures_and_goroutines - tmp := user - - // convert query result to library type - users = append(users, tmp.ToLibrary()) - } - - return users, err -} diff --git a/database/postgres/user_list_test.go b/database/postgres/user_list_test.go deleted file mode 100644 index d83b835b2..000000000 --- a/database/postgres/user_list_test.go +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "reflect" - "testing" - - sqlmock "github.com/DATA-DOG/go-sqlmock" - - "github.com/go-vela/server/database/postgres/dml" - "github.com/go-vela/types/library" - - "gorm.io/gorm" -) - -func TestPostgres_Client_GetUserList(t *testing.T) { - // setup types - _userOne := testUser() - _userOne.SetID(1) - _userOne.SetName("foo") - _userOne.SetToken("bar") - _userOne.SetHash("baz") - - _userTwo := testUser() - _userTwo.SetID(2) - _userTwo.SetName("bar") - _userTwo.SetToken("foo") - _userTwo.SetHash("baz") - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // capture the current expected SQL query - // - // https://gorm.io/docs/sql_builder.html#DryRun-Mode - _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.ListUsers).Statement - - // create expected return in mock - _rows := sqlmock.NewRows( - []string{"id", "name", "refresh_token", "token", "hash", "favorites", "active", "admin"}, - ).AddRow(1, "foo", "", "bar", "baz", "{}", false, false). - AddRow(2, "bar", "", "foo", "baz", "{}", false, false) - - // ensure the mock expects the query - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want []*library.User - }{ - { - failure: false, - want: []*library.User{_userOne, _userTwo}, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetUserList() - - if test.failure { - if err == nil { - t.Errorf("GetUserList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetUserList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetUserList is %v, want %v", got, test.want) - } - } -} - -func TestPostgres_Client_GetUserLiteList(t *testing.T) { - // setup types - _userOne := testUser() - _userOne.SetID(1) - _userOne.SetName("foo") - _userOne.SetFavorites(nil) - - _userTwo := testUser() - _userTwo.SetID(2) - _userTwo.SetName("bar") - _userTwo.SetFavorites(nil) - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // capture the current expected SQL query - // - // https://gorm.io/docs/sql_builder.html#DryRun-Mode - _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.ListLiteUsers, 1, 10).Statement - - // create expected return in mock - _rows := sqlmock.NewRows([]string{"id", "name"}).AddRow(1, "foo").AddRow(2, "bar") - - // ensure the mock expects the query - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want []*library.User - }{ - { - failure: false, - want: []*library.User{_userOne, _userTwo}, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetUserLiteList(1, 10) - - if test.failure { - if err == nil { - t.Errorf("GetUserLiteList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetUserLiteList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetUserLiteList is %v, want %v", got, test.want) - } - } -} diff --git a/database/postgres/user_test.go b/database/postgres/user_test.go deleted file mode 100644 index f17aed522..000000000 --- a/database/postgres/user_test.go +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "reflect" - "testing" - - sqlmock "github.com/DATA-DOG/go-sqlmock" - - "github.com/go-vela/server/database/postgres/dml" - "github.com/go-vela/types/library" - - "gorm.io/gorm" -) - -func TestPostgres_Client_GetUser(t *testing.T) { - // setup types - _user := testUser() - _user.SetID(1) - _user.SetName("foo") - _user.SetToken("bar") - _user.SetHash("baz") - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // capture the current expected SQL query - // - // https://gorm.io/docs/sql_builder.html#DryRun-Mode - _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectUser, 1).Statement - - // create expected return in mock - _rows := sqlmock.NewRows( - []string{"id", "name", "refresh_token", "token", "hash", "favorites", "active", "admin"}, - ).AddRow(1, "foo", "", "bar", "baz", "{}", false, false) - - // ensure the mock expects the query for test case 1 - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - // ensure the mock expects the error for test case 2 - _mock.ExpectQuery(_query.SQL.String()).WillReturnError(gorm.ErrRecordNotFound) - - // setup tests - tests := []struct { - failure bool - want *library.User - }{ - { - failure: false, - want: _user, - }, - { - failure: true, - want: nil, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetUser(1) - - if test.failure { - if err == nil { - t.Errorf("GetUser should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetUser returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetUser is %v, want %v", got, test.want) - } - } -} - -func TestPostgres_Client_CreateUser(t *testing.T) { - // setup types - _user := testUser() - _user.SetID(1) - _user.SetName("foo") - _user.SetToken("bar") - _user.SetHash("baz") - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // create expected return in mock - _rows := sqlmock.NewRows([]string{"id"}).AddRow(1) - - // ensure the mock expects the query - _mock.ExpectQuery(`INSERT INTO "users" ("name","refresh_token","token","hash","favorites","active","admin","id") VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING "id"`). - WithArgs("foo", AnyArgument{}, AnyArgument{}, AnyArgument{}, "{}", false, false, 1). - WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - err := _database.CreateUser(_user) - - if test.failure { - if err == nil { - t.Errorf("CreateUser should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("CreateUser returned err: %v", err) - } - } -} - -func TestPostgres_Client_UpdateUser(t *testing.T) { - // setup types - _user := testUser() - _user.SetID(1) - _user.SetName("foo") - _user.SetToken("bar") - _user.SetHash("baz") - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // ensure the mock expects the query - _mock.ExpectExec(`UPDATE "users" SET "name"=$1,"refresh_token"=$2,"token"=$3,"hash"=$4,"favorites"=$5,"active"=$6,"admin"=$7 WHERE "id" = $8`). - WithArgs("foo", AnyArgument{}, AnyArgument{}, AnyArgument{}, "{}", false, false, 1). - WillReturnResult(sqlmock.NewResult(1, 1)) - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - err := _database.UpdateUser(_user) - - if test.failure { - if err == nil { - t.Errorf("UpdateUser should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("UpdateUser returned err: %v", err) - } - } -} - -func TestPostgres_Client_DeleteUser(t *testing.T) { - // setup types - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // capture the current expected SQL query - // - // https://gorm.io/docs/sql_builder.html#DryRun-Mode - _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Exec(dml.DeleteUser, 1).Statement - - // ensure the mock expects the query - _mock.ExpectExec(_query.SQL.String()).WillReturnResult(sqlmock.NewResult(1, 1)) - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - err := _database.DeleteUser(1) - - if test.failure { - if err == nil { - t.Errorf("DeleteUser should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("DeleteUser returned err: %v", err) - } - } -} - -// testUser is a test helper function to create a -// library User type with all fields set to their -// zero values. -func testUser() *library.User { - i64 := int64(0) - str := "" - arr := []string{} - b := false - - return &library.User{ - ID: &i64, - Name: &str, - RefreshToken: &str, - Token: &str, - Hash: &str, - Favorites: &arr, - Active: &b, - Admin: &b, - } -} diff --git a/database/sqlite/ddl/user.go b/database/sqlite/ddl/user.go deleted file mode 100644 index 3f8d77e90..000000000 --- a/database/sqlite/ddl/user.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package ddl - -const ( - // CreateUserTable represents a query to - // create the users table for Vela. - CreateUserTable = ` -CREATE TABLE -IF NOT EXISTS -users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT, - refresh_token TEXT, - token TEXT, - hash TEXT, - favorites TEXT, - active BOOLEAN, - admin BOOLEAN, - UNIQUE(name) -); -` - - // CreateUserRefreshIndex represents a query to create an - // index on the users table for the refresh_token column. - CreateUserRefreshIndex = ` -CREATE INDEX -IF NOT EXISTS -users_refresh -ON users (refresh_token); -` -) diff --git a/database/sqlite/dml/user.go b/database/sqlite/dml/user.go deleted file mode 100644 index 9febce658..000000000 --- a/database/sqlite/dml/user.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package dml - -const ( - // ListUsers represents a query to - // list all users in the database. - ListUsers = ` -SELECT * -FROM users; -` - - // ListLiteUsers represents a query to - // list all lite users in the database. - ListLiteUsers = ` -SELECT id, name -FROM users -ORDER BY id DESC -LIMIT ? -OFFSET ?; -` - - // SelectUser represents a query to select - // a user for an id in the database. - SelectUser = ` -SELECT * -FROM users -WHERE id = ? -LIMIT 1; -` - - // SelectUserName represents a query to select - // a user for a name in the database. - SelectUserName = ` -SELECT * -FROM users -WHERE name = ? -LIMIT 1; -` - - // SelectUsersCount represents a query to select - // the count of users in the database. - SelectUsersCount = ` -SELECT count(*) as count -FROM users; -` - - // SelectRefreshToken represents a query to select - // a user for a refresh_token in the database. - // - // nolint: gosec // ignore false positive - SelectRefreshToken = ` -SELECT * -FROM users -WHERE refresh_token = ? -LIMIT 1; -` - - // DeleteUser represents a query to - // remove a user from the database. - DeleteUser = ` -DELETE -FROM users -WHERE id = ?; -` -) diff --git a/database/sqlite/user.go b/database/sqlite/user.go deleted file mode 100644 index 033291854..000000000 --- a/database/sqlite/user.go +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "errors" - "fmt" - - "github.com/sirupsen/logrus" - - "github.com/go-vela/server/database/sqlite/dml" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" - - "gorm.io/gorm" -) - -// GetUser gets a user by unique ID from the database. -func (c *client) GetUser(id int64) (*library.User, error) { - c.Logger.Tracef("getting user %d from the database", id) - - // variable to store query results - u := new(database.User) - - // send query to the database and store result in variable - result := c.Sqlite. - Table(constants.TableUser). - Raw(dml.SelectUser, id). - Scan(u) - - // check if the query returned a record not found error or no rows were returned - if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 { - return nil, gorm.ErrRecordNotFound - } - - // decrypt the fields for the user - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt - err := u.Decrypt(c.config.EncryptionKey) - if err != nil { - // ensures that the change is backwards compatible - // by logging the error instead of returning it - // which allows us to fetch unencrypted users - c.Logger.Errorf("unable to decrypt user %d: %v", id, err) - - // return the unencrypted user - return u.ToLibrary(), result.Error - } - - // return the decrypted user - return u.ToLibrary(), result.Error -} - -// GetUserName gets a user by name from the database. -func (c *client) GetUserName(name string) (*library.User, error) { - c.Logger.WithFields(logrus.Fields{ - "user": name, - }).Tracef("getting user %s from the database", name) - - // variable to store query results - u := new(database.User) - - // send query to the database and store result in variable - result := c.Sqlite. - Table(constants.TableUser). - Raw(dml.SelectUserName, name). - Scan(u) - - // check if the query returned a record not found error or no rows were returned - if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 { - return nil, gorm.ErrRecordNotFound - } - - // decrypt the fields for the user - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt - err := u.Decrypt(c.config.EncryptionKey) - if err != nil { - // ensures that the change is backwards compatible - // by logging the error instead of returning it - // which allows us to fetch unencrypted users - c.Logger.Errorf("unable to decrypt user %s: %v", name, err) - - // return the unencrypted user - return u.ToLibrary(), result.Error - } - - // return the decrypted user - return u.ToLibrary(), result.Error -} - -// CreateUser creates a new user in the database. -// -// nolint: dupl // ignore similar code with update -func (c *client) CreateUser(u *library.User) error { - c.Logger.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Tracef("creating user %s in the database", u.GetName()) - - // cast to database type - // - // https://pkg.go.dev/github.com/go-vela/types/database#UserFromLibrary - user := database.UserFromLibrary(u) - - // validate the necessary fields are populated - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Validate - err := user.Validate() - if err != nil { - return err - } - - // encrypt the fields for the user - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Encrypt - err = user.Encrypt(c.config.EncryptionKey) - if err != nil { - return fmt.Errorf("unable to encrypt user %s: %w", u.GetName(), err) - } - - // send query to the database - return c.Sqlite. - Table(constants.TableUser). - Create(user).Error -} - -// UpdateUser updates a user in the database. -// -// nolint: dupl // ignore similar code with create -func (c *client) UpdateUser(u *library.User) error { - c.Logger.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Tracef("updating user %s in the database", u.GetName()) - - // cast to database type - // - // https://pkg.go.dev/github.com/go-vela/types/database#UserFromLibrary - user := database.UserFromLibrary(u) - - // validate the necessary fields are populated - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Validate - err := user.Validate() - if err != nil { - return err - } - - // encrypt the fields for the user - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Encrypt - err = user.Encrypt(c.config.EncryptionKey) - if err != nil { - return fmt.Errorf("unable to encrypt user %s: %w", u.GetName(), err) - } - - // send query to the database - return c.Sqlite. - Table(constants.TableUser). - Save(user).Error -} - -// DeleteUser deletes a user by unique ID from the database. -func (c *client) DeleteUser(id int64) error { - c.Logger.Tracef("deleting user %d from the database", id) - - // send query to the database - return c.Sqlite. - Table(constants.TableUser). - Exec(dml.DeleteUser, id).Error -} diff --git a/database/sqlite/user_count.go b/database/sqlite/user_count.go deleted file mode 100644 index 72b70b003..000000000 --- a/database/sqlite/user_count.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "github.com/go-vela/server/database/sqlite/dml" - "github.com/go-vela/types/constants" -) - -// GetUserCount gets a count of all users from the database. -func (c *client) GetUserCount() (int64, error) { - c.Logger.Trace("getting count of users from the database") - - // variable to store query results - var u int64 - - // send query to the database and store result in variable - err := c.Sqlite. - Table(constants.TableUser). - Raw(dml.SelectUsersCount). - Pluck("count", &u).Error - - return u, err -} diff --git a/database/sqlite/user_count_test.go b/database/sqlite/user_count_test.go deleted file mode 100644 index a991f7f8b..000000000 --- a/database/sqlite/user_count_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "log" - "reflect" - "testing" - - "github.com/go-vela/server/database/sqlite/ddl" - "github.com/go-vela/types/constants" -) - -func init() { - // setup the test database client - _database, err := NewTest() - if err != nil { - log.Fatalf("unable to create new sqlite test database: %v", err) - } - - // create the user table - err = _database.Sqlite.Exec(ddl.CreateUserTable).Error - if err != nil { - log.Fatalf("unable to create %s table: %v", constants.TableUser, err) - } -} - -func TestSqlite_Client_GetUserCount(t *testing.T) { - // setup types - _userOne := testUser() - _userOne.SetID(1) - _userOne.SetName("foo") - _userOne.SetToken("bar") - _userOne.SetHash("baz") - - _userTwo := testUser() - _userTwo.SetID(2) - _userTwo.SetName("bar") - _userTwo.SetToken("foo") - _userTwo.SetHash("baz") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - want int64 - }{ - { - failure: false, - want: 2, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the users table - defer _database.Sqlite.Exec("delete from users;") - - // create the users in the database - err := _database.CreateUser(_userOne) - if err != nil { - t.Errorf("unable to create test user: %v", err) - } - - err = _database.CreateUser(_userTwo) - if err != nil { - t.Errorf("unable to create test user: %v", err) - } - - got, err := _database.GetUserCount() - - if test.failure { - if err == nil { - t.Errorf("GetUserCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetUserCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetUserCount is %v, want %v", got, test.want) - } - } -} diff --git a/database/sqlite/user_list.go b/database/sqlite/user_list.go deleted file mode 100644 index d90863d6b..000000000 --- a/database/sqlite/user_list.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "github.com/go-vela/server/database/sqlite/dml" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" -) - -// GetUserList gets a list of all users from the database. -// -// nolint: dupl // ignore false positive of duplicate code -func (c *client) GetUserList() ([]*library.User, error) { - c.Logger.Trace("listing users from the database") - - // variable to store query results - u := new([]database.User) - - // send query to the database and store result in variable - err := c.Sqlite. - Table(constants.TableUser). - Raw(dml.ListUsers). - Scan(u).Error - if err != nil { - return nil, err - } - - // variable we want to return - users := []*library.User{} - // iterate through all query results - for _, user := range *u { - // https://golang.org/doc/faq#closures_and_goroutines - tmp := user - - // decrypt the fields for the user - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt - err = tmp.Decrypt(c.config.EncryptionKey) - if err != nil { - // ensures that the change is backwards compatible - // by logging the error instead of returning it - // which allows us to fetch unencrypted users - c.Logger.Errorf("unable to decrypt user %d: %v", tmp.ID.Int64, err) - } - - // convert query result to library type - // - // https://pkg.go.dev/github.com/go-vela/types/database#User.ToLibrary - users = append(users, tmp.ToLibrary()) - } - - return users, nil -} - -// GetUserLiteList gets a lite list of all users from the database. -func (c *client) GetUserLiteList(page, perPage int) ([]*library.User, error) { - c.Logger.Trace("listing lite users from the database") - - // variable to store query results - u := new([]database.User) - // calculate offset for pagination through results - offset := perPage * (page - 1) - - // send query to the database and store result in variable - err := c.Sqlite. - Table(constants.TableUser). - Raw(dml.ListLiteUsers, perPage, offset). - Scan(u).Error - - // variable we want to return - users := []*library.User{} - // iterate through all query results - for _, user := range *u { - // https://golang.org/doc/faq#closures_and_goroutines - tmp := user - - // convert query result to library type - users = append(users, tmp.ToLibrary()) - } - - return users, err -} diff --git a/database/sqlite/user_list_test.go b/database/sqlite/user_list_test.go deleted file mode 100644 index 4a626f988..000000000 --- a/database/sqlite/user_list_test.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "log" - "reflect" - "testing" - - "github.com/go-vela/server/database/sqlite/ddl" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" -) - -func init() { - // setup the test database client - _database, err := NewTest() - if err != nil { - log.Fatalf("unable to create new sqlite test database: %v", err) - } - - // create the user table - err = _database.Sqlite.Exec(ddl.CreateUserTable).Error - if err != nil { - log.Fatalf("unable to create %s table: %v", constants.TableUser, err) - } -} - -func TestSqlite_Client_GetUserList(t *testing.T) { - // setup types - _userOne := testUser() - _userOne.SetID(1) - _userOne.SetName("foo") - _userOne.SetToken("bar") - _userOne.SetHash("baz") - - _userTwo := testUser() - _userTwo.SetID(2) - _userTwo.SetName("bar") - _userTwo.SetToken("foo") - _userTwo.SetHash("baz") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - want []*library.User - }{ - { - failure: false, - want: []*library.User{_userOne, _userTwo}, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the users table - defer _database.Sqlite.Exec("delete from users;") - - for _, user := range test.want { - // create the user in the database - err := _database.CreateUser(user) - if err != nil { - t.Errorf("unable to create test user: %v", err) - } - } - - got, err := _database.GetUserList() - - if test.failure { - if err == nil { - t.Errorf("GetUserList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetUserList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetUserList is %v, want %v", got, test.want) - } - } -} - -func TestSqlite_Client_GetUserLiteList(t *testing.T) { - // setup types - _userOne := testUser() - _userOne.SetID(1) - _userOne.SetName("foo") - - _userTwo := testUser() - _userTwo.SetID(2) - _userTwo.SetName("bar") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - want []*library.User - }{ - { - failure: false, - want: []*library.User{_userTwo, _userOne}, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the users table - defer _database.Sqlite.Exec("delete from users;") - - for _, user := range test.want { - // set the required fields for the user - user.SetToken("baz") - user.SetHash("foob") - - // create the user in the database - err := _database.CreateUser(user) - if err != nil { - t.Errorf("unable to create test user: %v", err) - } - - // clear the required fields for the user - // so we get back the expected data - user.SetToken("") - user.SetHash("") - } - - got, err := _database.GetUserLiteList(1, 10) - - if test.failure { - if err == nil { - t.Errorf("GetUserLiteList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetUserLiteList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetUserLiteList is %v, want %v", got, test.want) - } - } -} diff --git a/database/sqlite/user_test.go b/database/sqlite/user_test.go deleted file mode 100644 index 21f6d9543..000000000 --- a/database/sqlite/user_test.go +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "reflect" - "testing" - - "github.com/go-vela/types/library" -) - -func TestSqlite_Client_GetUser(t *testing.T) { - // setup types - _user := testUser() - _user.SetID(1) - _user.SetName("foo") - _user.SetToken("bar") - _user.SetHash("baz") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - want *library.User - }{ - { - failure: false, - want: _user, - }, - { - failure: true, - want: nil, - }, - } - - // run tests - for _, test := range tests { - if test.want != nil { - // create the user in the database - err := _database.CreateUser(test.want) - if err != nil { - t.Errorf("unable to create test user: %v", err) - } - } - - got, err := _database.GetUser(1) - - // cleanup the users table - _ = _database.Sqlite.Exec("DELETE FROM users;") - - if test.failure { - if err == nil { - t.Errorf("GetUser should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetUser returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetUser is %v, want %v", got, test.want) - } - } -} - -func TestSqlite_Client_CreateUser(t *testing.T) { - // setup types - _user := testUser() - _user.SetID(1) - _user.SetName("foo") - _user.SetToken("bar") - _user.SetHash("baz") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the users table - defer _database.Sqlite.Exec("delete from users;") - - err := _database.CreateUser(_user) - - if test.failure { - if err == nil { - t.Errorf("CreateUser should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("CreateUser returned err: %v", err) - } - } -} - -func TestSqlite_Client_UpdateUser(t *testing.T) { - // setup types - _user := testUser() - _user.SetID(1) - _user.SetName("foo") - _user.SetToken("bar") - _user.SetHash("baz") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the users table - defer _database.Sqlite.Exec("delete from users;") - - // create the user in the database - err := _database.CreateUser(_user) - if err != nil { - t.Errorf("unable to create test user: %v", err) - } - - err = _database.UpdateUser(_user) - - if test.failure { - if err == nil { - t.Errorf("UpdateUser should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("UpdateUser returned err: %v", err) - } - } -} - -func TestSqlite_Client_DeleteUser(t *testing.T) { - // setup types - _user := testUser() - _user.SetID(1) - _user.SetName("foo") - _user.SetToken("bar") - _user.SetHash("baz") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the users table - defer _database.Sqlite.Exec("delete from users;") - - // create the user in the database - err := _database.CreateUser(_user) - if err != nil { - t.Errorf("unable to create test user: %v", err) - } - - err = _database.DeleteUser(1) - - if test.failure { - if err == nil { - t.Errorf("DeleteUser should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("DeleteUser returned err: %v", err) - } - } -} - -// testUser is a test helper function to create a -// library User type with all fields set to their -// zero values. -func testUser() *library.User { - i64 := int64(0) - str := "" - b := false - - var arr []string - - return &library.User{ - ID: &i64, - Name: &str, - RefreshToken: &str, - Token: &str, - Hash: &str, - Favorites: &arr, - Active: &b, - Admin: &b, - } -} From 736a0e973355383e17f1885d66351e9a1f38adde Mon Sep 17 00:00:00 2001 From: JordanBrockopp Date: Tue, 21 Jun 2022 21:17:33 -0500 Subject: [PATCH 03/16] refactor(database): restructure DDL functions --- database/pipeline/index.go | 4 ++-- database/pipeline/index_test.go | 8 +++---- database/pipeline/pipeline.go | 4 ++-- database/pipeline/service.go | 8 +++---- database/pipeline/table.go | 4 ++-- database/pipeline/table_test.go | 8 +++---- database/postgres/postgres.go | 34 ++++++++++++++++---------- database/postgres/postgres_test.go | 7 +++--- database/service.go | 32 ++++--------------------- database/sqlite/sqlite.go | 30 +++++++++++++---------- database/sqlite/sqlite_test.go | 38 +++++++++++++++++++++++++++++- database/user/index.go | 4 ++-- database/user/index_test.go | 8 +++---- database/user/service.go | 8 +++---- database/user/table.go | 4 ++-- database/user/table_test.go | 8 +++---- database/user/user.go | 4 ++-- 17 files changed, 120 insertions(+), 93 deletions(-) diff --git a/database/pipeline/index.go b/database/pipeline/index.go index ef8bb4f20..506fddfa8 100644 --- a/database/pipeline/index.go +++ b/database/pipeline/index.go @@ -15,8 +15,8 @@ ON pipelines (repo_id); ` ) -// CreateIndexes creates the indexes for the pipelines table in the database. -func (e *engine) CreateIndexes() error { +// CreatePipelineIndexes creates the indexes for the pipelines table in the database. +func (e *engine) CreatePipelineIndexes() error { e.logger.Tracef("creating indexes for pipelines table in the database") // create the repo_id column index for the pipelines table diff --git a/database/pipeline/index_test.go b/database/pipeline/index_test.go index 75e54f90e..1fa77b7b0 100644 --- a/database/pipeline/index_test.go +++ b/database/pipeline/index_test.go @@ -10,7 +10,7 @@ import ( "github.com/DATA-DOG/go-sqlmock" ) -func TestPipeline_Engine_CreateIndexes(t *testing.T) { +func TestPipeline_Engine_CreatePipelineIndexes(t *testing.T) { // setup types _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -41,18 +41,18 @@ func TestPipeline_Engine_CreateIndexes(t *testing.T) { // run tests for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := test.database.CreateIndexes() + err := test.database.CreatePipelineIndexes() if test.failure { if err == nil { - t.Errorf("CreateIndexes for %s should have returned err", test.name) + t.Errorf("CreatePipelineIndexes for %s should have returned err", test.name) } return } if err != nil { - t.Errorf("CreateIndexes for %s returned err: %v", test.name, err) + t.Errorf("CreatePipelineIndexes for %s returned err: %v", test.name, err) } }) } diff --git a/database/pipeline/pipeline.go b/database/pipeline/pipeline.go index d6978fad0..2a305d8e4 100644 --- a/database/pipeline/pipeline.go +++ b/database/pipeline/pipeline.go @@ -67,13 +67,13 @@ func New(opts ...EngineOpt) (*engine, error) { } // create the pipelines table - err := e.CreateTable(e.client.Config.Dialector.Name()) + err := e.CreatePipelineTable(e.client.Config.Dialector.Name()) if err != nil { return nil, fmt.Errorf("unable to create %s table: %w", constants.TablePipeline, err) } // create the indexes for the pipelines table - err = e.CreateIndexes() + err = e.CreatePipelineIndexes() if err != nil { return nil, fmt.Errorf("unable to create indexes for %s table: %w", constants.TablePipeline, err) } diff --git a/database/pipeline/service.go b/database/pipeline/service.go index a34e75a28..8ff5ec8d5 100644 --- a/database/pipeline/service.go +++ b/database/pipeline/service.go @@ -17,10 +17,10 @@ type PipelineService interface { // // https://en.wikipedia.org/wiki/Data_definition_language - // CreateIndexes creates the indexes for the pipelines table. - CreateIndexes() error - // CreateTable defines a function that creates the pipelines table. - CreateTable(string) error + // CreatePipelineIndexes defines a function that creates the indexes for the pipelines table. + CreatePipelineIndexes() error + // CreatePipelineTable defines a function that creates the pipelines table. + CreatePipelineTable(string) error // Pipeline Data Manipulation Language Functions // diff --git a/database/pipeline/table.go b/database/pipeline/table.go index b4c222e7b..bc463da68 100644 --- a/database/pipeline/table.go +++ b/database/pipeline/table.go @@ -56,8 +56,8 @@ pipelines ( ` ) -// CreateTable creates the pipelines table in the database. -func (e *engine) CreateTable(driver string) error { +// CreatePipelineTable creates the pipelines table in the database. +func (e *engine) CreatePipelineTable(driver string) error { e.logger.Tracef("creating pipelines table in the database") // handle the driver provided to create the table diff --git a/database/pipeline/table_test.go b/database/pipeline/table_test.go index c199f98b5..5b05e4313 100644 --- a/database/pipeline/table_test.go +++ b/database/pipeline/table_test.go @@ -10,7 +10,7 @@ import ( "github.com/DATA-DOG/go-sqlmock" ) -func TestPipeline_Engine_CreateTable(t *testing.T) { +func TestPipeline_Engine_CreatePipelineTable(t *testing.T) { // setup types _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -41,18 +41,18 @@ func TestPipeline_Engine_CreateTable(t *testing.T) { // run tests for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := test.database.CreateTable(test.name) + err := test.database.CreatePipelineTable(test.name) if test.failure { if err == nil { - t.Errorf("CreateTable for %s should have returned err", test.name) + t.Errorf("CreatePipelineTable for %s should have returned err", test.name) } return } if err != nil { - t.Errorf("CreateTable for %s returned err: %v", test.name, err) + t.Errorf("CreatePipelineTable for %s returned err: %v", test.name, err) } }) } diff --git a/database/postgres/postgres.go b/database/postgres/postgres.go index c45b954a2..cedb34c49 100644 --- a/database/postgres/postgres.go +++ b/database/postgres/postgres.go @@ -11,6 +11,7 @@ import ( "github.com/DATA-DOG/go-sqlmock" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/postgres/ddl" + "github.com/go-vela/server/database/user" "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" @@ -37,11 +38,15 @@ type ( } client struct { - config *config + config *config + // https://pkg.go.dev/gorm.io/gorm#DB Postgres *gorm.DB // https://pkg.go.dev/github.com/sirupsen/logrus#Entry Logger *logrus.Entry + // https://pkg.go.dev/github.com/go-vela/server/database/pipeline#PipelineService pipeline.PipelineService + // https://pkg.go.dev/github.com/go-vela/server/database/user#UserService + user.UserService } ) @@ -140,6 +145,8 @@ func NewTest() (*client, sqlmock.Sqlmock, error) { _mock.ExpectExec(pipeline.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(pipeline.CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(user.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(user.CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1)) // create the new mock Postgres database client // @@ -262,12 +269,6 @@ func createTables(c *client) error { return fmt.Errorf("unable to create %s table: %w", constants.TableStep, err) } - // create the users table - err = c.Postgres.Exec(ddl.CreateUserTable).Error - if err != nil { - return fmt.Errorf("unable to create %s table: %w", constants.TableUser, err) - } - // create the workers table err = c.Postgres.Exec(ddl.CreateWorkerTable).Error if err != nil { @@ -342,12 +343,6 @@ func createIndexes(c *client) error { return fmt.Errorf("unable to create secrets_type_org index for the %s table: %w", constants.TableSecret, err) } - // create the users_refresh index for the users table - err = c.Postgres.Exec(ddl.CreateUserRefreshIndex).Error - if err != nil { - return fmt.Errorf("unable to create users_refresh index for the %s table: %w", constants.TableUser, err) - } - // create the workers_hostname_address index for the workers table err = c.Postgres.Exec(ddl.CreateWorkerHostnameAddressIndex).Error if err != nil { @@ -374,5 +369,18 @@ func createServices(c *client) error { return err } + // create the database agnostic user service + // + // https://pkg.go.dev/github.com/go-vela/server/database/user#New + c.UserService, err = user.New( + user.WithClient(c.Postgres), + user.WithEncryptionKey(c.config.EncryptionKey), + user.WithLogger(c.Logger), + user.WithSkipCreation(c.config.SkipCreation), + ) + if err != nil { + return err + } + return nil } diff --git a/database/postgres/postgres_test.go b/database/postgres/postgres_test.go index fd614af9a..e66919618 100644 --- a/database/postgres/postgres_test.go +++ b/database/postgres/postgres_test.go @@ -12,6 +12,7 @@ import ( "github.com/DATA-DOG/go-sqlmock" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/postgres/ddl" + "github.com/go-vela/server/database/user" ) func TestPostgres_New(t *testing.T) { @@ -80,7 +81,6 @@ func TestPostgres_setupDatabase(t *testing.T) { _mock.ExpectExec(ddl.CreateSecretTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateServiceTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateStepTable).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(ddl.CreateUserTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateWorkerTable).WillReturnResult(sqlmock.NewResult(1, 1)) // ensure the mock expects the index queries @@ -94,7 +94,6 @@ func TestPostgres_setupDatabase(t *testing.T) { _mock.ExpectExec(ddl.CreateSecretTypeOrgRepo).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateSecretTypeOrgTeam).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateSecretTypeOrg).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(ddl.CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(pipeline.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(pipeline.CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) @@ -165,7 +164,6 @@ func TestPostgres_createTables(t *testing.T) { _mock.ExpectExec(ddl.CreateSecretTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateServiceTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateStepTable).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(ddl.CreateUserTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateWorkerTable).WillReturnResult(sqlmock.NewResult(1, 1)) tests := []struct { @@ -215,7 +213,6 @@ func TestPostgres_createIndexes(t *testing.T) { _mock.ExpectExec(ddl.CreateSecretTypeOrgRepo).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateSecretTypeOrgTeam).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateSecretTypeOrg).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(ddl.CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) tests := []struct { @@ -257,6 +254,8 @@ func TestPostgres_createServices(t *testing.T) { // ensure the mock expects the index queries _mock.ExpectExec(pipeline.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(pipeline.CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(user.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(user.CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1)) tests := []struct { failure bool diff --git a/database/service.go b/database/service.go index 2c4f880cd..785f65742 100644 --- a/database/service.go +++ b/database/service.go @@ -6,6 +6,7 @@ package database import ( "github.com/go-vela/server/database/pipeline" + "github.com/go-vela/server/database/user" "github.com/go-vela/types/library" ) @@ -239,39 +240,16 @@ type Service interface { // deletes a step by unique ID. DeleteService(int64) error - // User Database Interface Functions - - // GetUser defines a function that - // gets a user by unique ID. - GetUser(int64) (*library.User, error) - // GetUserName defines a function that - // gets a user by name. - GetUserName(string) (*library.User, error) - // GetUserList defines a function that - // gets a list of all users. - GetUserList() ([]*library.User, error) - // GetUserCount defines a function that - // gets the count of users. - GetUserCount() (int64, error) - // GetUserLiteList defines a function - // that gets a lite list of users. - GetUserLiteList(int, int) ([]*library.User, error) - // CreateUser defines a function that - // creates a new user. - CreateUser(*library.User) error - // UpdateUser defines a function that - // updates a user. - UpdateUser(*library.User) error - // DeleteUser defines a function that - // deletes a user by unique ID. - DeleteUser(int64) error + // UserService provides the interface for functionality + // related to users stored in the database. + user.UserService // Worker Database Interface Functions // GetWorker defines a function that // gets a worker by hostname. GetWorker(string) (*library.Worker, error) - // GetWorkerAddress defines a function that + // GetWorkerByAddress defines a function that // gets a worker by address. GetWorkerByAddress(string) (*library.Worker, error) // GetWorkerList defines a function that diff --git a/database/sqlite/sqlite.go b/database/sqlite/sqlite.go index 61935ff56..32f476f16 100644 --- a/database/sqlite/sqlite.go +++ b/database/sqlite/sqlite.go @@ -10,6 +10,7 @@ import ( "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/sqlite/ddl" + "github.com/go-vela/server/database/user" "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" @@ -37,10 +38,14 @@ type ( client struct { config *config + // https://pkg.go.dev/gorm.io/gorm#DB Sqlite *gorm.DB // https://pkg.go.dev/github.com/sirupsen/logrus#Entry Logger *logrus.Entry + // https://pkg.go.dev/github.com/go-vela/server/database/pipeline#PipelineService pipeline.PipelineService + // https://pkg.go.dev/github.com/go-vela/server/database/user#UserService + user.UserService } ) @@ -258,12 +263,6 @@ func createTables(c *client) error { return fmt.Errorf("unable to create %s table: %w", constants.TableStep, err) } - // create the users table - err = c.Sqlite.Exec(ddl.CreateUserTable).Error - if err != nil { - return fmt.Errorf("unable to create %s table: %w", constants.TableUser, err) - } - // create the workers table err = c.Sqlite.Exec(ddl.CreateWorkerTable).Error if err != nil { @@ -338,12 +337,6 @@ func createIndexes(c *client) error { return fmt.Errorf("unable to create secrets_type_org index for the %s table: %w", constants.TableSecret, err) } - // create the users_refresh index for the users table - err = c.Sqlite.Exec(ddl.CreateUserRefreshIndex).Error - if err != nil { - return fmt.Errorf("unable to create users_refresh index for the %s table: %w", constants.TableUser, err) - } - // create the workers_hostname_address index for the workers table err = c.Sqlite.Exec(ddl.CreateWorkerHostnameAddressIndex).Error if err != nil { @@ -370,5 +363,18 @@ func createServices(c *client) error { return err } + // create the database agnostic user service + // + // https://pkg.go.dev/github.com/go-vela/server/database/user#New + c.UserService, err = user.New( + user.WithClient(c.Sqlite), + user.WithEncryptionKey(c.config.EncryptionKey), + user.WithLogger(c.Logger), + user.WithSkipCreation(c.config.SkipCreation), + ) + if err != nil { + return err + } + return nil } diff --git a/database/sqlite/sqlite_test.go b/database/sqlite/sqlite_test.go index 713ce897d..6b31f874a 100644 --- a/database/sqlite/sqlite_test.go +++ b/database/sqlite/sqlite_test.go @@ -145,7 +145,7 @@ func TestSqlite_createTables(t *testing.T) { } } -func TestPostgres_createIndexes(t *testing.T) { +func TestSqlite_createIndexes(t *testing.T) { // setup types // setup the test database client _database, err := NewTest() @@ -180,3 +180,39 @@ func TestPostgres_createIndexes(t *testing.T) { } } } + +func TestSqlite_createServices(t *testing.T) { + // setup types + // setup the test database client + _database, err := NewTest() + if err != nil { + t.Errorf("unable to create new sqlite test database: %v", err) + } + + defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() + + tests := []struct { + failure bool + }{ + { + failure: false, + }, + } + + // run tests + for _, test := range tests { + err := createServices(_database) + + if test.failure { + if err == nil { + t.Errorf("createServices should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("createServices returned err: %v", err) + } + } +} diff --git a/database/user/index.go b/database/user/index.go index 0593e0145..5445e963e 100644 --- a/database/user/index.go +++ b/database/user/index.go @@ -15,8 +15,8 @@ ON users (refresh_token); ` ) -// CreateIndexes creates the indexes for the users table in the database. -func (e *engine) CreateIndexes() error { +// CreateUserIndexes creates the indexes for the users table in the database. +func (e *engine) CreateUserIndexes() error { e.logger.Tracef("creating indexes for users table in the database") // create the refresh_token column index for the users table diff --git a/database/user/index_test.go b/database/user/index_test.go index a1e009619..55728b77a 100644 --- a/database/user/index_test.go +++ b/database/user/index_test.go @@ -10,7 +10,7 @@ import ( "github.com/DATA-DOG/go-sqlmock" ) -func TestUser_Engine_CreateIndexes(t *testing.T) { +func TestUser_Engine_CreateUserIndexes(t *testing.T) { // setup types _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -41,18 +41,18 @@ func TestUser_Engine_CreateIndexes(t *testing.T) { // run tests for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := test.database.CreateIndexes() + err := test.database.CreateUserIndexes() if test.failure { if err == nil { - t.Errorf("CreateIndexes for %s should have returned err", test.name) + t.Errorf("CreateUserIndexes for %s should have returned err", test.name) } return } if err != nil { - t.Errorf("CreateIndexes for %s returned err: %v", test.name, err) + t.Errorf("CreateUserIndexes for %s returned err: %v", test.name, err) } }) } diff --git a/database/user/service.go b/database/user/service.go index a14ca97d3..096cea933 100644 --- a/database/user/service.go +++ b/database/user/service.go @@ -17,10 +17,10 @@ type UserService interface { // // https://en.wikipedia.org/wiki/Data_definition_language - // CreateIndexes defines a function that creates the indexes for the users table. - CreateIndexes() error - // CreateTable defines a function that creates the users table. - CreateTable(string) error + // CreateUserIndexes defines a function that creates the indexes for the users table. + CreateUserIndexes() error + // CreateUserTable defines a function that creates the users table. + CreateUserTable(string) error // User Data Manipulation Language Functions // diff --git a/database/user/table.go b/database/user/table.go index cf0c160fa..456853770 100644 --- a/database/user/table.go +++ b/database/user/table.go @@ -44,8 +44,8 @@ users ( ` ) -// CreateTable creates the users table in the database. -func (e *engine) CreateTable(driver string) error { +// CreateUserTable creates the users table in the database. +func (e *engine) CreateUserTable(driver string) error { e.logger.Tracef("creating users table in the database") // handle the driver provided to create the table diff --git a/database/user/table_test.go b/database/user/table_test.go index b56bfe55b..95a2d4c00 100644 --- a/database/user/table_test.go +++ b/database/user/table_test.go @@ -10,7 +10,7 @@ import ( "github.com/DATA-DOG/go-sqlmock" ) -func TestUser_Engine_CreateTable(t *testing.T) { +func TestUser_Engine_CreateUserTable(t *testing.T) { // setup types _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() @@ -41,18 +41,18 @@ func TestUser_Engine_CreateTable(t *testing.T) { // run tests for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := test.database.CreateTable(test.name) + err := test.database.CreateUserTable(test.name) if test.failure { if err == nil { - t.Errorf("CreateTable for %s should have returned err", test.name) + t.Errorf("CreateUserTable for %s should have returned err", test.name) } return } if err != nil { - t.Errorf("CreateTable for %s returned err: %v", test.name, err) + t.Errorf("CreateUserTable for %s returned err: %v", test.name, err) } }) } diff --git a/database/user/user.go b/database/user/user.go index 8c1c192f8..e37b18644 100644 --- a/database/user/user.go +++ b/database/user/user.go @@ -67,13 +67,13 @@ func New(opts ...EngineOpt) (*engine, error) { } // create the users table - err := e.CreateTable(e.client.Config.Dialector.Name()) + err := e.CreateUserTable(e.client.Config.Dialector.Name()) if err != nil { return nil, fmt.Errorf("unable to create %s table: %w", constants.TableUser, err) } // create the indexes for the users table - err = e.CreateIndexes() + err = e.CreateUserIndexes() if err != nil { return nil, fmt.Errorf("unable to create indexes for %s table: %w", constants.TableUser, err) } From c28ca23b1aa84fdc046b33a908c02e4780165d50 Mon Sep 17 00:00:00 2001 From: JordanBrockopp Date: Tue, 21 Jun 2022 21:18:32 -0500 Subject: [PATCH 04/16] chore: updates for database user engine --- api/admin/user.go | 2 +- api/authenticate.go | 4 ++-- api/metrics.go | 2 +- api/user.go | 26 ++++++++------------------ router/middleware/token/token.go | 2 +- 5 files changed, 13 insertions(+), 23 deletions(-) diff --git a/api/admin/user.go b/api/admin/user.go index e8729b628..324e8912e 100644 --- a/api/admin/user.go +++ b/api/admin/user.go @@ -45,7 +45,7 @@ func AllUsers(c *gin.Context) { logrus.Info("Admin: reading all users") // send API call to capture all users - u, err := database.FromContext(c).GetUserList() + u, err := database.FromContext(c).ListUsers() if err != nil { retErr := fmt.Errorf("unable to capture all users: %w", err) diff --git a/api/authenticate.go b/api/authenticate.go index 4f14465b0..0a518b697 100644 --- a/api/authenticate.go +++ b/api/authenticate.go @@ -99,7 +99,7 @@ func Authenticate(c *gin.Context) { } // send API call to capture the user logging in - u, err := database.FromContext(c).GetUserName(newUser.GetName()) + u, err := database.FromContext(c).GetUserForName(newUser.GetName()) // create a new user account if len(u.GetName()) == 0 || err != nil { // create unique id for the user @@ -314,7 +314,7 @@ func AuthenticateToken(c *gin.Context) { } // check if the user exists - u, err = database.FromContext(c).GetUserName(u.GetName()) + u, err = database.FromContext(c).GetUserForName(u.GetName()) if err != nil { retErr := fmt.Errorf("user %s not found", u.GetName()) diff --git a/api/metrics.go b/api/metrics.go index 4c94cb4c4..90d499b75 100644 --- a/api/metrics.go +++ b/api/metrics.go @@ -74,7 +74,7 @@ func CustomMetrics(c *gin.Context) { // helper function to get the totals of resource types. func recordGauges(c *gin.Context) { // send API call to capture the total number of users - u, err := database.FromContext(c).GetUserCount() + u, err := database.FromContext(c).CountUsers() if err != nil { logrus.Errorf("unable to get count of all users: %v", err) } diff --git a/api/user.go b/api/user.go index 87e0922f1..f22108c58 100644 --- a/api/user.go +++ b/api/user.go @@ -87,7 +87,7 @@ func CreateUser(c *gin.Context) { } // send API call to capture the created user - user, _ := database.FromContext(c).GetUserName(input.GetName()) + user, _ := database.FromContext(c).GetUserForName(input.GetName()) c.JSON(http.StatusCreated, user) } @@ -172,18 +172,8 @@ func GetUsers(c *gin.Context) { // ensure per_page isn't above or below allowed values perPage = util.MaxInt(1, util.MinInt(100, perPage)) - // send API call to capture the total number of users - t, err := database.FromContext(c).GetUserCount() - if err != nil { - retErr := fmt.Errorf("unable to get users count: %w", err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - // send API call to capture the list of users - users, err := database.FromContext(c).GetUserLiteList(page, perPage) + users, t, err := database.FromContext(c).ListLiteUsers(page, perPage) if err != nil { retErr := fmt.Errorf("unable to get users: %w", err) @@ -311,7 +301,7 @@ func UpdateCurrentUser(c *gin.Context) { } // send API call to capture the updated user - u, err = database.FromContext(c).GetUserName(u.GetName()) + u, err = database.FromContext(c).GetUserForName(u.GetName()) if err != nil { retErr := fmt.Errorf("unable to get updated user %s: %w", u.GetName(), err) @@ -363,7 +353,7 @@ func GetUser(c *gin.Context) { }).Infof("reading user %s", user) // send API call to capture the user - u, err := database.FromContext(c).GetUserName(user) + u, err := database.FromContext(c).GetUserForName(user) if err != nil { retErr := fmt.Errorf("unable to get user %s: %w", user, err) @@ -548,7 +538,7 @@ func UpdateUser(c *gin.Context) { } // send API call to capture the user - u, err = database.FromContext(c).GetUserName(user) + u, err = database.FromContext(c).GetUserForName(user) if err != nil { retErr := fmt.Errorf("unable to get user %s: %w", user, err) @@ -584,7 +574,7 @@ func UpdateUser(c *gin.Context) { } // send API call to capture the updated user - u, _ = database.FromContext(c).GetUserName(user) + u, _ = database.FromContext(c).GetUserForName(user) c.JSON(http.StatusOK, u) } @@ -633,7 +623,7 @@ func DeleteUser(c *gin.Context) { }).Infof("deleting user %s", user) // send API call to capture the user - u, err := database.FromContext(c).GetUserName(user) + u, err := database.FromContext(c).GetUserForName(user) if err != nil { retErr := fmt.Errorf("unable to get user %s: %w", user, err) @@ -643,7 +633,7 @@ func DeleteUser(c *gin.Context) { } // send API call to remove the user - err = database.FromContext(c).DeleteUser(u.GetID()) + err = database.FromContext(c).DeleteUser(u) if err != nil { retErr := fmt.Errorf("unable to delete user %s: %w", u.GetName(), err) diff --git a/router/middleware/token/token.go b/router/middleware/token/token.go index cf40b882a..68c53aa99 100644 --- a/router/middleware/token/token.go +++ b/router/middleware/token/token.go @@ -113,7 +113,7 @@ func Parse(t string, db database.Service) (*library.User, error) { // lookup the user in the database logrus.WithField("user", name).Debugf("reading user %s", name) - u, err = db.GetUserName(name) + u, err = db.GetUserForName(name) return []byte(u.GetHash()), err }) From a147ce8020fff1fabaa24efbec16608b8ace6b39 Mon Sep 17 00:00:00 2001 From: JordanBrockopp Date: Mon, 27 Jun 2022 08:47:54 -0500 Subject: [PATCH 05/16] feat(database): add repo engine --- database/repo/count.go | 25 ++++ database/repo/count_org.go | 30 +++++ database/repo/count_org_test.go | 101 +++++++++++++++ database/repo/count_test.go | 99 ++++++++++++++ database/repo/count_user.go | 31 +++++ database/repo/count_user_test.go | 107 +++++++++++++++ database/repo/create.go | 48 +++++++ database/repo/create_test.go | 78 +++++++++++ database/repo/delete.go | 31 +++++ database/repo/delete_test.go | 76 +++++++++++ database/repo/get.go | 53 ++++++++ database/repo/get_name.go | 58 +++++++++ database/repo/get_name_test.go | 89 +++++++++++++ database/repo/get_test.go | 89 +++++++++++++ database/repo/index.go | 24 ++++ database/repo/index_test.go | 59 +++++++++ database/repo/list.go | 67 ++++++++++ database/repo/list_org.go | 80 ++++++++++++ database/repo/list_org_test.go | 115 +++++++++++++++++ database/repo/list_test.go | 111 ++++++++++++++++ database/repo/list_user.go | 79 ++++++++++++ database/repo/list_user_test.go | 118 +++++++++++++++++ database/repo/opts.go | 54 ++++++++ database/repo/opts_test.go | 210 ++++++++++++++++++++++++++++++ database/repo/repo.go | 82 ++++++++++++ database/repo/repo_test.go | 215 +++++++++++++++++++++++++++++++ database/repo/service.go | 51 ++++++++ database/repo/table.go | 90 +++++++++++++ database/repo/table_test.go | 59 +++++++++ database/repo/update.go | 48 +++++++ database/repo/update_test.go | 80 ++++++++++++ go.mod | 3 + 32 files changed, 2460 insertions(+) create mode 100644 database/repo/count.go create mode 100644 database/repo/count_org.go create mode 100644 database/repo/count_org_test.go create mode 100644 database/repo/count_test.go create mode 100644 database/repo/count_user.go create mode 100644 database/repo/count_user_test.go create mode 100644 database/repo/create.go create mode 100644 database/repo/create_test.go create mode 100644 database/repo/delete.go create mode 100644 database/repo/delete_test.go create mode 100644 database/repo/get.go create mode 100644 database/repo/get_name.go create mode 100644 database/repo/get_name_test.go create mode 100644 database/repo/get_test.go create mode 100644 database/repo/index.go create mode 100644 database/repo/index_test.go create mode 100644 database/repo/list.go create mode 100644 database/repo/list_org.go create mode 100644 database/repo/list_org_test.go create mode 100644 database/repo/list_test.go create mode 100644 database/repo/list_user.go create mode 100644 database/repo/list_user_test.go create mode 100644 database/repo/opts.go create mode 100644 database/repo/opts_test.go create mode 100644 database/repo/repo.go create mode 100644 database/repo/repo_test.go create mode 100644 database/repo/service.go create mode 100644 database/repo/table.go create mode 100644 database/repo/table_test.go create mode 100644 database/repo/update.go create mode 100644 database/repo/update_test.go diff --git a/database/repo/count.go b/database/repo/count.go new file mode 100644 index 000000000..032e7d780 --- /dev/null +++ b/database/repo/count.go @@ -0,0 +1,25 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "github.com/go-vela/types/constants" +) + +// CountRepos gets the count of all repos from the database. +func (e *engine) CountRepos() (int64, error) { + e.logger.Tracef("getting count of all repos from the database") + + // variable to store query results + var r int64 + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableRepo). + Count(&r). + Error + + return r, err +} diff --git a/database/repo/count_org.go b/database/repo/count_org.go new file mode 100644 index 000000000..938f151c6 --- /dev/null +++ b/database/repo/count_org.go @@ -0,0 +1,30 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "github.com/go-vela/types/constants" + "github.com/sirupsen/logrus" +) + +// CountReposForOrg gets the count of repos by org name from the database. +func (e *engine) CountReposForOrg(org string, filters map[string]interface{}) (int64, error) { + e.logger.WithFields(logrus.Fields{ + "org": org, + }).Tracef("getting count of repos for org %s from the database", org) + + // variable to store query results + var r int64 + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableRepo). + Where("org = ?", org). + Where(filters). + Count(&r). + Error + + return r, err +} diff --git a/database/repo/count_org_test.go b/database/repo/count_org_test.go new file mode 100644 index 000000000..2ff3278f3 --- /dev/null +++ b/database/repo/count_org_test.go @@ -0,0 +1,101 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestRepo_Engine_CountReposForOrg(t *testing.T) { + // setup types + _repoOne := testRepo() + _repoOne.SetID(1) + _repoOne.SetUserID(1) + _repoOne.SetHash("baz") + _repoOne.SetOrg("foo") + _repoOne.SetName("bar") + _repoOne.SetFullName("foo/bar") + _repoOne.SetVisibility("public") + + _repoTwo := testRepo() + _repoTwo.SetID(2) + _repoTwo.SetUserID(1) + _repoTwo.SetHash("baz") + _repoTwo.SetOrg("bar") + _repoTwo.SetName("foo") + _repoTwo.SetFullName("bar/foo") + _repoTwo.SetVisibility("public") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"count"}).AddRow(1) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT count(*) FROM "repos" WHERE org = $1`).WithArgs("foo").WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateRepo(_repoOne) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + err = _sqlite.CreateRepo(_repoTwo) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want int64 + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: 1, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: 1, + }, + } + + filters := map[string]interface{}{} + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.CountReposForOrg("foo", filters) + + if test.failure { + if err == nil { + t.Errorf("CountReposForOrg for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CountReposForOrg for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("CountReposForOrg for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/repo/count_test.go b/database/repo/count_test.go new file mode 100644 index 000000000..f63a935e8 --- /dev/null +++ b/database/repo/count_test.go @@ -0,0 +1,99 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestRepo_Engine_CountRepos(t *testing.T) { + // setup types + _repoOne := testRepo() + _repoOne.SetID(1) + _repoOne.SetUserID(1) + _repoOne.SetHash("baz") + _repoOne.SetOrg("foo") + _repoOne.SetName("bar") + _repoOne.SetFullName("foo/bar") + _repoOne.SetVisibility("public") + + _repoTwo := testRepo() + _repoTwo.SetID(2) + _repoTwo.SetUserID(1) + _repoTwo.SetHash("baz") + _repoTwo.SetOrg("bar") + _repoTwo.SetName("foo") + _repoTwo.SetFullName("bar/foo") + _repoTwo.SetVisibility("public") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"count"}).AddRow(2) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT count(*) FROM "repos"`).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateRepo(_repoOne) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + err = _sqlite.CreateRepo(_repoTwo) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want int64 + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: 2, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: 2, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.CountRepos() + + if test.failure { + if err == nil { + t.Errorf("CountRepos for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CountRepos for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("CountRepos for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/repo/count_user.go b/database/repo/count_user.go new file mode 100644 index 000000000..dbd226dac --- /dev/null +++ b/database/repo/count_user.go @@ -0,0 +1,31 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// CountReposForUser gets the count of repos by user ID from the database. +func (e *engine) CountReposForUser(u *library.User, filters map[string]interface{}) (int64, error) { + e.logger.WithFields(logrus.Fields{ + "user": u.GetName(), + }).Tracef("getting count of repos for user %s from the database", u.GetName()) + + // variable to store query results + var r int64 + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableRepo). + Where("user_id = ?", u.GetID()). + Where(filters). + Count(&r). + Error + + return r, err +} diff --git a/database/repo/count_user_test.go b/database/repo/count_user_test.go new file mode 100644 index 000000000..223320568 --- /dev/null +++ b/database/repo/count_user_test.go @@ -0,0 +1,107 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "github.com/go-vela/types/library" + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestRepo_Engine_CountReposForUser(t *testing.T) { + // setup types + _repoOne := testRepo() + _repoOne.SetID(1) + _repoOne.SetUserID(1) + _repoOne.SetHash("baz") + _repoOne.SetOrg("foo") + _repoOne.SetName("bar") + _repoOne.SetFullName("foo/bar") + _repoOne.SetVisibility("public") + + _repoTwo := testRepo() + _repoTwo.SetID(2) + _repoTwo.SetUserID(1) + _repoTwo.SetHash("baz") + _repoTwo.SetOrg("bar") + _repoTwo.SetName("foo") + _repoTwo.SetFullName("bar/foo") + _repoTwo.SetVisibility("public") + + _user := new(library.User) + _user.SetID(1) + _user.SetName("foo") + _user.SetToken("bar") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"count"}).AddRow(2) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT count(*) FROM "repos" WHERE user_id = $1`).WithArgs(1).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateRepo(_repoOne) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + err = _sqlite.CreateRepo(_repoTwo) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want int64 + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: 2, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: 2, + }, + } + + filters := map[string]interface{}{} + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.CountReposForUser(_user, filters) + + if test.failure { + if err == nil { + t.Errorf("CountReposForUser for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CountReposForUser for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("CountReposForUser for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/repo/create.go b/database/repo/create.go new file mode 100644 index 000000000..7b8903470 --- /dev/null +++ b/database/repo/create.go @@ -0,0 +1,48 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "fmt" + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// CreateRepo creates a new repo in the database. +func (e *engine) CreateRepo(r *library.Repo) error { + e.logger.WithFields(logrus.Fields{ + "org": r.GetOrg(), + "repo": r.GetName(), + }).Tracef("creating repo %s in the database", r.GetFullName()) + + // cast the library type to database type + // + // https://pkg.go.dev/github.com/go-vela/types/database#RepoFromLibrary + repo := database.RepoFromLibrary(r) + + // validate the necessary fields are populated + // + // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Validate + err := repo.Validate() + if err != nil { + return err + } + + // encrypt the fields for the repo + // + // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Encrypt + err = repo.Encrypt(e.config.EncryptionKey) + if err != nil { + return fmt.Errorf("unable to encrypt repo %s: %w", r.GetFullName(), err) + } + + // send query to the database + return e.client. + Table(constants.TableRepo). + Create(repo). + Error +} diff --git a/database/repo/create_test.go b/database/repo/create_test.go new file mode 100644 index 000000000..e2ab70417 --- /dev/null +++ b/database/repo/create_test.go @@ -0,0 +1,78 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestRepo_Engine_CreateRepo(t *testing.T) { + // setup types + _repo := testRepo() + _repo.SetID(1) + _repo.SetUserID(1) + _repo.SetHash("baz") + _repo.SetOrg("foo") + _repo.SetName("bar") + _repo.SetFullName("foo/bar") + _repo.SetVisibility("public") + _repo.SetPipelineType("yaml") + _repo.SetPreviousName("oldName") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"id"}).AddRow(1) + + // ensure the mock expects the query + _mock.ExpectQuery(`INSERT INTO "repos" +("user_id","hash","org","name","full_name","link","clone","branch","build_limit","timeout","counter","visibility","private","trusted","active","allow_pull","allow_push","allow_deploy","allow_tag","allow_comment","pipeline_type","previous_name","id") +VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23) RETURNING "id"`). + WithArgs(1, AnyArgument{}, "foo", "bar", "foo/bar", nil, nil, nil, AnyArgument{}, AnyArgument{}, AnyArgument{}, "public", false, false, false, false, false, false, false, false, "yaml", "oldName", 1). + WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.database.CreateRepo(_repo) + + if test.failure { + if err == nil { + t.Errorf("CreateRepo for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateRepo for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/repo/delete.go b/database/repo/delete.go new file mode 100644 index 000000000..64a515610 --- /dev/null +++ b/database/repo/delete.go @@ -0,0 +1,31 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// DeleteRepo deletes an existing repo from the database. +func (e *engine) DeleteRepo(r *library.Repo) error { + e.logger.WithFields(logrus.Fields{ + "org": r.GetOrg(), + "repo": r.GetName(), + }).Tracef("deleting repo %s from the database", r.GetFullName()) + + // cast the library type to database type + // + // https://pkg.go.dev/github.com/go-vela/types/database#RepoFromLibrary + repo := database.RepoFromLibrary(r) + + // send query to the database + return e.client. + Table(constants.TableRepo). + Delete(repo). + Error +} diff --git a/database/repo/delete_test.go b/database/repo/delete_test.go new file mode 100644 index 000000000..ccc50eb4a --- /dev/null +++ b/database/repo/delete_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestRepo_Engine_DeleteRepo(t *testing.T) { + // setup types + _repo := testRepo() + _repo.SetID(1) + _repo.SetUserID(1) + _repo.SetHash("baz") + _repo.SetOrg("foo") + _repo.SetName("bar") + _repo.SetFullName("foo/bar") + _repo.SetVisibility("public") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // ensure the mock expects the query + _mock.ExpectExec(`DELETE FROM "repos" WHERE "repos"."id" = $1`). + WithArgs(1). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateRepo(_repo) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err = test.database.DeleteRepo(_repo) + + if test.failure { + if err == nil { + t.Errorf("DeleteRepo for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("DeleteRepo for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/repo/get.go b/database/repo/get.go new file mode 100644 index 000000000..03077ab07 --- /dev/null +++ b/database/repo/get.go @@ -0,0 +1,53 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" +) + +// GetRepo gets a repo by ID from the database. +func (e *engine) GetRepo(id int64) (*library.Repo, error) { + e.logger.Tracef("getting repo %d from the database", id) + + // variable to store query results + r := new(database.Repo) + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableRepo). + Where("id = ?", id). + Limit(1). + Scan(r). + Error + if err != nil { + return nil, err + } + + // decrypt the fields for the repo + // + // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt + err = r.Decrypt(e.config.EncryptionKey) + if err != nil { + // TODO: remove backwards compatibility before 1.x.x release + // + // ensures that the change is backwards compatible + // by logging the error instead of returning it + // which allows us to fetch unencrypted repos + e.logger.Errorf("unable to decrypt repo %d: %v", id, err) + + // return the unencrypted repo + // + // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary + return r.ToLibrary(), nil + } + + // return the decrypted repo + // + // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary + return r.ToLibrary(), nil +} diff --git a/database/repo/get_name.go b/database/repo/get_name.go new file mode 100644 index 000000000..ac1e85e1c --- /dev/null +++ b/database/repo/get_name.go @@ -0,0 +1,58 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// GetRepoForName gets a repo by org and repo name from the database. +func (e *engine) GetRepoForName(org, name string) (*library.Repo, error) { + e.logger.WithFields(logrus.Fields{ + "org": org, + "repo": name, + }).Tracef("getting repo %s/%s from the database", org, name) + + // variable to store query results + r := new(database.Repo) + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableRepo). + Where("org = ?", org). + Where("name = ?", name). + Limit(1). + Scan(r). + Error + if err != nil { + return nil, err + } + + // decrypt the fields for the repo + // + // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt + err = r.Decrypt(e.config.EncryptionKey) + if err != nil { + // TODO: remove backwards compatibility before 1.x.x release + // + // ensures that the change is backwards compatible + // by logging the error instead of returning it + // which allows us to fetch unencrypted repos + e.logger.Errorf("unable to decrypt repo %s/%s: %v", org, name, err) + + // return the unencrypted repo + // + // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary + return r.ToLibrary(), nil + } + + // return the decrypted repo + // + // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary + return r.ToLibrary(), nil +} diff --git a/database/repo/get_name_test.go b/database/repo/get_name_test.go new file mode 100644 index 000000000..01cc5756c --- /dev/null +++ b/database/repo/get_name_test.go @@ -0,0 +1,89 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" +) + +func TestRepo_Engine_GetRepoForName(t *testing.T) { + // setup types + _repo := testRepo() + _repo.SetID(1) + _repo.SetUserID(1) + _repo.SetHash("baz") + _repo.SetOrg("foo") + _repo.SetName("bar") + _repo.SetFullName("foo/bar") + _repo.SetVisibility("public") + _repo.SetPipelineType("yaml") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "") + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE org = $1 AND name = $2 LIMIT 1`).WithArgs("foo", "bar").WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateRepo(_repo) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want *library.Repo + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: _repo, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: _repo, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.GetRepoForName("foo", "bar") + + if test.failure { + if err == nil { + t.Errorf("GetRepoForName for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("GetRepoForName for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("GetRepoForName for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/repo/get_test.go b/database/repo/get_test.go new file mode 100644 index 000000000..d5fd926be --- /dev/null +++ b/database/repo/get_test.go @@ -0,0 +1,89 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" +) + +func TestRepo_Engine_GetRepo(t *testing.T) { + // setup types + _repo := testRepo() + _repo.SetID(1) + _repo.SetUserID(1) + _repo.SetHash("baz") + _repo.SetOrg("foo") + _repo.SetName("bar") + _repo.SetFullName("foo/bar") + _repo.SetVisibility("public") + _repo.SetPipelineType("yaml") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "") + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE id = $1 LIMIT 1`).WithArgs(1).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateRepo(_repo) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want *library.Repo + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: _repo, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: _repo, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.GetRepo(1) + + if test.failure { + if err == nil { + t.Errorf("GetRepo for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("GetRepo for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("GetRepo for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/repo/index.go b/database/repo/index.go new file mode 100644 index 000000000..d112cccad --- /dev/null +++ b/database/repo/index.go @@ -0,0 +1,24 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +const ( + // CreateOrgNameIndex represents a query to create an + // index on the repos table for the org and name columns. + CreateOrgNameIndex = ` +CREATE INDEX +IF NOT EXISTS +repos_org_name +ON repos (org, name); +` +) + +// CreateRepoIndexes creates the indexes for the repos table in the database. +func (e *engine) CreateRepoIndexes() error { + e.logger.Tracef("creating indexes for repos table in the database") + + // create the repo_id column index for the repos table + return e.client.Exec(CreateOrgNameIndex).Error +} diff --git a/database/repo/index_test.go b/database/repo/index_test.go new file mode 100644 index 000000000..90550cae8 --- /dev/null +++ b/database/repo/index_test.go @@ -0,0 +1,59 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestRepo_Engine_CreateRepoIndexes(t *testing.T) { + // setup types + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + _mock.ExpectExec(CreateOrgNameIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.database.CreateRepoIndexes() + + if test.failure { + if err == nil { + t.Errorf("CreateRepoIndexes for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateRepoIndexes for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/repo/list.go b/database/repo/list.go new file mode 100644 index 000000000..1b9b5d11c --- /dev/null +++ b/database/repo/list.go @@ -0,0 +1,67 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" +) + +// ListRepos gets a list of all repos from the database. +func (e *engine) ListRepos() ([]*library.Repo, error) { + e.logger.Trace("listing all repos from the database") + + // variables to store query results and return value + count := int64(0) + r := new([]database.Repo) + repos := []*library.Repo{} + + // count the results + count, err := e.CountRepos() + if err != nil { + return nil, err + } + + // short-circuit if there are no results + if count == 0 { + return repos, nil + } + + // send query to the database and store result in variable + err = e.client. + Table(constants.TableRepo). + Find(&r). + Error + if err != nil { + return nil, err + } + + // iterate through all query results + for _, repo := range *r { + // https://golang.org/doc/faq#closures_and_goroutines + tmp := repo + + // decrypt the fields for the repo + // + // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt + err = tmp.Decrypt(e.config.EncryptionKey) + if err != nil { + // TODO: remove backwards compatibility before 1.x.x release + // + // ensures that the change is backwards compatible + // by logging the error instead of returning it + // which allows us to fetch unencrypted repos + e.logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err) + } + + // convert query result to library type + // + // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary + repos = append(repos, tmp.ToLibrary()) + } + + return repos, nil +} diff --git a/database/repo/list_org.go b/database/repo/list_org.go new file mode 100644 index 000000000..4a613a4b1 --- /dev/null +++ b/database/repo/list_org.go @@ -0,0 +1,80 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// ListReposForOrg gets a list of repos by org name from the database. +// +// nolint: lll // ignore long line length due to variable names +func (e *engine) ListReposForOrg(org string, filters map[string]interface{}, page, perPage int) ([]*library.Repo, int64, error) { + e.logger.WithFields(logrus.Fields{ + "org": org, + }).Tracef("listing repos for org %s from the database", org) + + // variables to store query results and return values + count := int64(0) + r := new([]database.Repo) + repos := []*library.Repo{} + + // count the results + count, err := e.CountReposForOrg(org, filters) + if err != nil { + return repos, 0, err + } + + // short-circuit if there are no results + if count == 0 { + return repos, 0, nil + } + + // calculate offset for pagination through results + offset := perPage * (page - 1) + + // TODO: add support for last update + err = e.client. + Table(constants.TableRepo). + Where("org = ?", org). + Where(filters). + Order("name"). + Limit(perPage). + Offset(offset). + Find(&r). + Error + if err != nil { + return nil, count, err + } + + // iterate through all query results + for _, repo := range *r { + // https://golang.org/doc/faq#closures_and_goroutines + tmp := repo + + // decrypt the fields for the repo + // + // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt + err = tmp.Decrypt(e.config.EncryptionKey) + if err != nil { + // TODO: remove backwards compatibility before 1.x.x release + // + // ensures that the change is backwards compatible + // by logging the error instead of returning it + // which allows us to fetch unencrypted repos + e.logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err) + } + + // convert query result to library type + // + // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary + repos = append(repos, tmp.ToLibrary()) + } + + return repos, count, nil +} diff --git a/database/repo/list_org_test.go b/database/repo/list_org_test.go new file mode 100644 index 000000000..9abe63df0 --- /dev/null +++ b/database/repo/list_org_test.go @@ -0,0 +1,115 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" + "github.com/kr/pretty" +) + +func TestRepo_Engine_ListReposForOrg(t *testing.T) { + // setup types + _repoOne := testRepo() + _repoOne.SetID(1) + _repoOne.SetUserID(1) + _repoOne.SetHash("baz") + _repoOne.SetOrg("foo") + _repoOne.SetName("bar") + _repoOne.SetFullName("foo/bar") + _repoOne.SetVisibility("public") + _repoOne.SetPipelineType("yaml") + + _repoTwo := testRepo() + _repoTwo.SetID(2) + _repoTwo.SetUserID(1) + _repoTwo.SetHash("bar") + _repoTwo.SetOrg("foo") + _repoTwo.SetName("baz") + _repoTwo.SetFullName("foo/baz") + _repoTwo.SetVisibility("public") + _repoTwo.SetPipelineType("yaml") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"count"}).AddRow(2) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT count(*) FROM "repos" WHERE org = $1`).WithArgs("foo").WillReturnRows(_rows) + + // create expected result in mock + _rows = sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil). + AddRow(2, 1, "bar", "foo", "baz", "foo/baz", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE org = $1 ORDER BY name LIMIT 10`).WithArgs("foo").WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateRepo(_repoOne) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + err = _sqlite.CreateRepo(_repoTwo) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want []*library.Repo + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: []*library.Repo{_repoOne, _repoTwo}, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: []*library.Repo{_repoOne, _repoTwo}, + }, + } + + filters := map[string]interface{}{} + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, _, err := test.database.ListReposForOrg("foo", filters, 1, 10) + + if test.failure { + if err == nil { + t.Errorf("ListReposForOrg for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("ListReposForOrg for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + pretty.Ldiff(t, got, test.want) + t.Errorf("ListReposForOrg for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/repo/list_test.go b/database/repo/list_test.go new file mode 100644 index 000000000..86b9968fe --- /dev/null +++ b/database/repo/list_test.go @@ -0,0 +1,111 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" +) + +func TestRepo_Engine_ListRepos(t *testing.T) { + // setup types + _repoOne := testRepo() + _repoOne.SetID(1) + _repoOne.SetUserID(1) + _repoOne.SetHash("baz") + _repoOne.SetOrg("foo") + _repoOne.SetName("bar") + _repoOne.SetFullName("foo/bar") + _repoOne.SetVisibility("public") + _repoOne.SetPipelineType("yaml") + + _repoTwo := testRepo() + _repoTwo.SetID(2) + _repoTwo.SetUserID(1) + _repoTwo.SetHash("baz") + _repoTwo.SetOrg("bar") + _repoTwo.SetName("foo") + _repoTwo.SetFullName("bar/foo") + _repoTwo.SetVisibility("public") + _repoTwo.SetPipelineType("yaml") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"count"}).AddRow(2) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT count(*) FROM "repos"`).WillReturnRows(_rows) + + // create expected result in mock + _rows = sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil). + AddRow(2, 1, "baz", "bar", "foo", "bar/foo", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "repos"`).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateRepo(_repoOne) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + err = _sqlite.CreateRepo(_repoTwo) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want []*library.Repo + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: []*library.Repo{_repoOne, _repoTwo}, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: []*library.Repo{_repoOne, _repoTwo}, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.ListRepos() + + if test.failure { + if err == nil { + t.Errorf("ListRepos for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("ListRepos for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("ListRepos for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/repo/list_user.go b/database/repo/list_user.go new file mode 100644 index 000000000..8ffe57500 --- /dev/null +++ b/database/repo/list_user.go @@ -0,0 +1,79 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// ListReposForUser gets a list of repos by user ID from the database. +// +// nolint: lll // ignore long line length due to variable names +func (e *engine) ListReposForUser(u *library.User, filters map[string]interface{}, page, perPage int) ([]*library.Repo, int64, error) { + e.logger.WithFields(logrus.Fields{ + "user": u.GetName(), + }).Tracef("listing repos for user %s from the database", u.GetName()) + + // variables to store query results and return values + count := int64(0) + r := new([]database.Repo) + repos := []*library.Repo{} + + // count the results + count, err := e.CountReposForUser(u, filters) + if err != nil { + return repos, 0, err + } + + // short-circuit if there are no results + if count == 0 { + return repos, 0, nil + } + + // calculate offset for pagination through results + offset := perPage * (page - 1) + + // TODO: add support for last update + err = e.client. + Table(constants.TableRepo). + Where("user_id = ?", u.GetID()). + Order("name"). + Limit(perPage). + Offset(offset). + Find(&r). + Error + if err != nil { + return nil, count, err + } + + // iterate through all query results + for _, repo := range *r { + // https://golang.org/doc/faq#closures_and_goroutines + tmp := repo + + // decrypt the fields for the repo + // + // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt + err = tmp.Decrypt(e.config.EncryptionKey) + if err != nil { + // TODO: remove backwards compatibility before 1.x.x release + // + // ensures that the change is backwards compatible + // by logging the error instead of returning it + // which allows us to fetch unencrypted repos + e.logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err) + } + + // convert query result to library type + // + // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary + repos = append(repos, tmp.ToLibrary()) + } + + return repos, count, nil +} diff --git a/database/repo/list_user_test.go b/database/repo/list_user_test.go new file mode 100644 index 000000000..0c7322252 --- /dev/null +++ b/database/repo/list_user_test.go @@ -0,0 +1,118 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" +) + +func TestRepo_Engine_ListReposForUser(t *testing.T) { + // setup types + _repoOne := testRepo() + _repoOne.SetID(1) + _repoOne.SetUserID(1) + _repoOne.SetHash("baz") + _repoOne.SetOrg("foo") + _repoOne.SetName("bar") + _repoOne.SetFullName("foo/bar") + _repoOne.SetVisibility("public") + _repoOne.SetPipelineType("yaml") + + _repoTwo := testRepo() + _repoTwo.SetID(2) + _repoTwo.SetUserID(1) + _repoTwo.SetHash("baz") + _repoTwo.SetOrg("bar") + _repoTwo.SetName("foo") + _repoTwo.SetFullName("bar/foo") + _repoTwo.SetVisibility("public") + _repoTwo.SetPipelineType("yaml") + + _user := new(library.User) + _user.SetID(1) + _user.SetName("foo") + _user.SetToken("bar") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"count"}).AddRow(2) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT count(*) FROM "repos" WHERE user_id = $1`).WithArgs(1).WillReturnRows(_rows) + + // create expected result in mock + _rows = sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil). + AddRow(2, 1, "baz", "bar", "foo", "bar/foo", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "repos" WHERE user_id = $1 ORDER BY name LIMIT 10`).WithArgs(1).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateRepo(_repoOne) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + err = _sqlite.CreateRepo(_repoTwo) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want []*library.Repo + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: []*library.Repo{_repoOne, _repoTwo}, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: []*library.Repo{_repoOne, _repoTwo}, + }, + } + + filters := map[string]interface{}{} + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, _, err := test.database.ListReposForUser(_user, filters, 1, 10) + + if test.failure { + if err == nil { + t.Errorf("ListReposForUser for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("ListReposForUser for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("ListReposForUser for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/repo/opts.go b/database/repo/opts.go new file mode 100644 index 000000000..95957a598 --- /dev/null +++ b/database/repo/opts.go @@ -0,0 +1,54 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "github.com/sirupsen/logrus" + + "gorm.io/gorm" +) + +// EngineOpt represents a configuration option to initialize the database engine for Repos. +type EngineOpt func(*engine) error + +// WithClient sets the gorm.io/gorm client in the database engine for Repos. +func WithClient(client *gorm.DB) EngineOpt { + return func(e *engine) error { + // set the gorm.io/gorm client in the repo engine + e.client = client + + return nil + } +} + +// WithEncryptionKey sets the encryption key in the database engine for Repos. +func WithEncryptionKey(key string) EngineOpt { + return func(e *engine) error { + // set the encryption key in the repo engine + e.config.EncryptionKey = key + + return nil + } +} + +// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for Repos. +func WithLogger(logger *logrus.Entry) EngineOpt { + return func(e *engine) error { + // set the github.com/sirupsen/logrus logger in the repo engine + e.logger = logger + + return nil + } +} + +// WithSkipCreation sets the skip creation logic in the database engine for Repos. +func WithSkipCreation(skipCreation bool) EngineOpt { + return func(e *engine) error { + // set to skip creating tables and indexes in the repo engine + e.config.SkipCreation = skipCreation + + return nil + } +} diff --git a/database/repo/opts_test.go b/database/repo/opts_test.go new file mode 100644 index 000000000..78bfb826e --- /dev/null +++ b/database/repo/opts_test.go @@ -0,0 +1,210 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "reflect" + "testing" + + "github.com/sirupsen/logrus" + + "gorm.io/gorm" +) + +func TestRepo_EngineOpt_WithClient(t *testing.T) { + // setup types + e := &engine{client: new(gorm.DB)} + + // setup tests + tests := []struct { + failure bool + name string + client *gorm.DB + want *gorm.DB + }{ + { + failure: false, + name: "client set to new database", + client: new(gorm.DB), + want: new(gorm.DB), + }, + { + failure: false, + name: "client set to nil", + client: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithClient(test.client)(e) + + if test.failure { + if err == nil { + t.Errorf("WithClient for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithClient returned err: %v", err) + } + + if !reflect.DeepEqual(e.client, test.want) { + t.Errorf("WithClient is %v, want %v", e.client, test.want) + } + }) + } +} + +func TestRepo_EngineOpt_WithEncryptionKey(t *testing.T) { + // setup types + e := &engine{config: new(config)} + + // setup tests + tests := []struct { + failure bool + name string + key string + want string + }{ + { + failure: false, + name: "encryption key set", + key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", + want: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", + }, + { + failure: false, + name: "encryption key not set", + key: "", + want: "", + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithEncryptionKey(test.key)(e) + + if test.failure { + if err == nil { + t.Errorf("WithEncryptionKey for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithEncryptionKey returned err: %v", err) + } + + if !reflect.DeepEqual(e.config.EncryptionKey, test.want) { + t.Errorf("WithEncryptionKey is %v, want %v", e.config.EncryptionKey, test.want) + } + }) + } +} + +func TestRepo_EngineOpt_WithLogger(t *testing.T) { + // setup types + e := &engine{logger: new(logrus.Entry)} + + // setup tests + tests := []struct { + failure bool + name string + logger *logrus.Entry + want *logrus.Entry + }{ + { + failure: false, + name: "logger set to new entry", + logger: new(logrus.Entry), + want: new(logrus.Entry), + }, + { + failure: false, + name: "logger set to nil", + logger: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithLogger(test.logger)(e) + + if test.failure { + if err == nil { + t.Errorf("WithLogger for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithLogger returned err: %v", err) + } + + if !reflect.DeepEqual(e.logger, test.want) { + t.Errorf("WithLogger is %v, want %v", e.logger, test.want) + } + }) + } +} + +func TestRepo_EngineOpt_WithSkipCreation(t *testing.T) { + // setup types + e := &engine{config: new(config)} + + // setup tests + tests := []struct { + failure bool + name string + skipCreation bool + want bool + }{ + { + failure: false, + name: "skip creation set to true", + skipCreation: true, + want: true, + }, + { + failure: false, + name: "skip creation set to false", + skipCreation: false, + want: false, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithSkipCreation(test.skipCreation)(e) + + if test.failure { + if err == nil { + t.Errorf("WithSkipCreation for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithSkipCreation returned err: %v", err) + } + + if !reflect.DeepEqual(e.config.SkipCreation, test.want) { + t.Errorf("WithSkipCreation is %v, want %v", e.config.SkipCreation, test.want) + } + }) + } +} diff --git a/database/repo/repo.go b/database/repo/repo.go new file mode 100644 index 000000000..f0c92e25e --- /dev/null +++ b/database/repo/repo.go @@ -0,0 +1,82 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "fmt" + + "github.com/go-vela/types/constants" + "github.com/sirupsen/logrus" + + "gorm.io/gorm" +) + +type ( + // config represents the settings required to create the engine that implements the RepoService interface. + config struct { + // specifies the encryption key to use for the Repo engine + EncryptionKey string + // specifies to skip creating tables and indexes for the Repo engine + SkipCreation bool + } + + // engine represents the repo functionality that implements the RepoService interface. + engine struct { + // engine configuration settings used in repo functions + config *config + + // gorm.io/gorm database client used in repo functions + // + // https://pkg.go.dev/gorm.io/gorm#DB + client *gorm.DB + + // sirupsen/logrus logger used in repo functions + // + // https://pkg.go.dev/github.com/sirupsen/logrus#Entry + logger *logrus.Entry + } +) + +// New creates and returns a Vela service for integrating with repos in the database. +// +// nolint: revive // ignore returning unexported engine +func New(opts ...EngineOpt) (*engine, error) { + // create new Repo engine + e := new(engine) + + // create new fields + e.client = new(gorm.DB) + e.config = new(config) + e.logger = new(logrus.Entry) + + // apply all provided configuration options + for _, opt := range opts { + err := opt(e) + if err != nil { + return nil, err + } + } + + // check if we should skip creating repo database objects + if e.config.SkipCreation { + e.logger.Warning("skipping creation of repos table and indexes in the database") + + return e, nil + } + + // create the repos table + err := e.CreateRepoTable(e.client.Config.Dialector.Name()) + if err != nil { + return nil, fmt.Errorf("unable to create %s table: %w", constants.TableRepo, err) + } + + // create the indexes for the repos table + err = e.CreateRepoIndexes() + if err != nil { + return nil, fmt.Errorf("unable to create indexes for %s table: %w", constants.TableRepo, err) + } + + return e, nil +} diff --git a/database/repo/repo_test.go b/database/repo/repo_test.go new file mode 100644 index 000000000..8729a56f7 --- /dev/null +++ b/database/repo/repo_test.go @@ -0,0 +1,215 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "database/sql/driver" + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" + + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func TestRepo_New(t *testing.T) { + // setup types + logger := logrus.NewEntry(logrus.StandardLogger()) + + _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + if err != nil { + t.Errorf("unable to create new SQL mock: %v", err) + } + defer _sql.Close() + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(CreateOrgNameIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + + _config := &gorm.Config{SkipDefaultTransaction: true} + + _postgres, err := gorm.Open(postgres.New(postgres.Config{Conn: _sql}), _config) + if err != nil { + t.Errorf("unable to create new postgres database: %v", err) + } + + _sqlite, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), _config) + if err != nil { + t.Errorf("unable to create new sqlite database: %v", err) + } + + defer func() { _sql, _ := _sqlite.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + client *gorm.DB + key string + logger *logrus.Entry + skipCreation bool + want *engine + }{ + { + failure: false, + name: "postgres", + client: _postgres, + key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", + logger: logger, + skipCreation: false, + want: &engine{ + client: _postgres, + config: &config{EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", SkipCreation: false}, + logger: logger, + }, + }, + { + failure: false, + name: "sqlite3", + client: _sqlite, + key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", + logger: logger, + skipCreation: false, + want: &engine{ + client: _sqlite, + config: &config{EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", SkipCreation: false}, + logger: logger, + }, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := New( + WithClient(test.client), + WithEncryptionKey(test.key), + WithLogger(test.logger), + WithSkipCreation(test.skipCreation), + ) + + if test.failure { + if err == nil { + t.Errorf("New for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("New for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("New for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} + +// testPostgres is a helper function to create a Postgres engine for testing. +func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) { + // create the new mock sql database + // + // https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#New + _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + if err != nil { + t.Errorf("unable to create new SQL mock: %v", err) + } + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(CreateOrgNameIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + + // create the new mock Postgres database client + // + // https://pkg.go.dev/gorm.io/gorm#Open + _postgres, err := gorm.Open( + postgres.New(postgres.Config{Conn: _sql}), + &gorm.Config{SkipDefaultTransaction: true}, + ) + if err != nil { + t.Errorf("unable to create new postgres database: %v", err) + } + + _engine, err := New( + WithClient(_postgres), + WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"), + WithLogger(logrus.NewEntry(logrus.StandardLogger())), + WithSkipCreation(false), + ) + if err != nil { + t.Errorf("unable to create new postgres repo engine: %v", err) + } + + return _engine, _mock +} + +// testSqlite is a helper function to create a Sqlite engine for testing. +func testSqlite(t *testing.T) *engine { + _sqlite, err := gorm.Open( + sqlite.Open("file::memory:?cache=shared"), + &gorm.Config{SkipDefaultTransaction: true}, + ) + if err != nil { + t.Errorf("unable to create new sqlite database: %v", err) + } + + _engine, err := New( + WithClient(_sqlite), + WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"), + WithLogger(logrus.NewEntry(logrus.StandardLogger())), + WithSkipCreation(false), + ) + if err != nil { + t.Errorf("unable to create new sqlite repo engine: %v", err) + } + + return _engine +} + +// testRepo is a test helper function to create a library +// Repo type with all fields set to their zero values. +func testRepo() *library.Repo { + return &library.Repo{ + ID: new(int64), + UserID: new(int64), + BuildLimit: new(int64), + Timeout: new(int64), + Counter: new(int), + PipelineType: new(string), + Hash: new(string), + Org: new(string), + Name: new(string), + FullName: new(string), + Link: new(string), + Clone: new(string), + Branch: new(string), + Visibility: new(string), + PreviousName: new(string), + Private: new(bool), + Trusted: new(bool), + Active: new(bool), + AllowPull: new(bool), + AllowPush: new(bool), + AllowDeploy: new(bool), + AllowTag: new(bool), + AllowComment: new(bool), + } +} + +// This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values +// that are otherwise not easily compared. These typically would be values generated +// before adding or updating them in the database. +// +// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime +type AnyArgument struct{} + +// Match satisfies sqlmock.Argument interface. +func (a AnyArgument) Match(v driver.Value) bool { + return true +} diff --git a/database/repo/service.go b/database/repo/service.go new file mode 100644 index 000000000..fb242541a --- /dev/null +++ b/database/repo/service.go @@ -0,0 +1,51 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "github.com/go-vela/types/library" +) + +// RepoService represents the Vela interface for repo +// functions with the supported Database backends. +// +// nolint: revive // ignore name stutter +type RepoService interface { + // Repo Data Definition Language Functions + // + // https://en.wikipedia.org/wiki/Data_definition_language + + // CreateRepoIndexes defines a function that creates the indexes for the repos table. + CreateRepoIndexes() error + // CreateRepoTable defines a function that creates the repos table. + CreateRepoTable(string) error + + // Repo Data Manipulation Language Functions + // + // https://en.wikipedia.org/wiki/Data_manipulation_language + + // CountRepos defines a function that gets the count of all repos. + CountRepos() (int64, error) + // CountReposForOrg defines a function that gets the count of repos by org name. + CountReposForOrg(string, map[string]interface{}) (int64, error) + // CountReposForUser defines a function that gets the count of repos by user ID. + CountReposForUser(*library.User, map[string]interface{}) (int64, error) + // CreateRepo defines a function that creates a new repo. + CreateRepo(*library.Repo) error + // DeleteRepo defines a function that deletes an existing repo. + DeleteRepo(*library.Repo) error + // GetRepo defines a function that gets a repo by ID. + GetRepo(int64) (*library.Repo, error) + // GetRepoForName defines a function that gets a repo by org and repo name. + GetRepoForName(string, string) (*library.Repo, error) + // ListRepos defines a function that gets a list of all repos. + ListRepos() ([]*library.Repo, error) + // ListReposForOrg defines a function that gets a list of repos by org name. + ListReposForOrg(string, map[string]interface{}, int, int) ([]*library.Repo, int64, error) + // ListReposForUser defines a function that gets a list of repos by user ID. + ListReposForUser(*library.User, map[string]interface{}, int, int) ([]*library.Repo, int64, error) + // UpdateRepo defines a function that updates an existing repo. + UpdateRepo(*library.Repo) error +} diff --git a/database/repo/table.go b/database/repo/table.go new file mode 100644 index 000000000..fc55a4a56 --- /dev/null +++ b/database/repo/table.go @@ -0,0 +1,90 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import "github.com/go-vela/types/constants" + +const ( + // CreatePostgresTable represents a query to create the Postgres repos table. + CreatePostgresTable = ` +CREATE TABLE +IF NOT EXISTS +repos ( + id SERIAL PRIMARY KEY, + user_id INTEGER, + hash VARCHAR(500), + org VARCHAR(250), + name VARCHAR(250), + full_name VARCHAR(500), + link VARCHAR(1000), + clone VARCHAR(1000), + branch VARCHAR(250), + build_limit INTEGER, + timeout INTEGER, + counter INTEGER, + visibility TEXT, + private BOOLEAN, + trusted BOOLEAN, + active BOOLEAN, + allow_pull BOOLEAN, + allow_push BOOLEAN, + allow_deploy BOOLEAN, + allow_tag BOOLEAN, + allow_comment BOOLEAN, + pipeline_type TEXT, + previous_name VARCHAR(100), + UNIQUE(full_name) +); +` + + // CreateSqliteTable represents a query to create the Sqlite repos table. + CreateSqliteTable = ` +CREATE TABLE +IF NOT EXISTS +repos ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER, + hash TEXT, + org TEXT, + name TEXT, + full_name TEXT, + link TEXT, + clone TEXT, + branch TEXT, + build_limit INTEGER, + timeout INTEGER, + counter INTEGER, + visibility TEXT, + private BOOLEAN, + trusted BOOLEAN, + active BOOLEAN, + allow_pull BOOLEAN, + allow_push BOOLEAN, + allow_deploy BOOLEAN, + allow_tag BOOLEAN, + allow_comment BOOLEAN, + pipeline_type TEXT, + previous_name TEXT, + UNIQUE(full_name) +); +` +) + +// CreateRepoTable creates the repos table in the database. +func (e *engine) CreateRepoTable(driver string) error { + e.logger.Tracef("creating repos table in the database") + + // handle the driver provided to create the table + switch driver { + case constants.DriverPostgres: + // create the repos table for Postgres + return e.client.Exec(CreatePostgresTable).Error + case constants.DriverSqlite: + fallthrough + default: + // create the repos table for Sqlite + return e.client.Exec(CreateSqliteTable).Error + } +} diff --git a/database/repo/table_test.go b/database/repo/table_test.go new file mode 100644 index 000000000..7680c02d6 --- /dev/null +++ b/database/repo/table_test.go @@ -0,0 +1,59 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestRepo_Engine_CreateRepoTable(t *testing.T) { + // setup types + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.database.CreateRepoTable(test.name) + + if test.failure { + if err == nil { + t.Errorf("CreateRepoTable for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateRepoTable for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/repo/update.go b/database/repo/update.go new file mode 100644 index 000000000..5352e9808 --- /dev/null +++ b/database/repo/update.go @@ -0,0 +1,48 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "fmt" + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// UpdateRepo updates an existing repo in the database. +func (e *engine) UpdateRepo(r *library.Repo) error { + e.logger.WithFields(logrus.Fields{ + "org": r.GetOrg(), + "repo": r.GetName(), + }).Tracef("creating repo %s in the database", r.GetFullName()) + + // cast the library type to database type + // + // https://pkg.go.dev/github.com/go-vela/types/database#RepoFromLibrary + repo := database.RepoFromLibrary(r) + + // validate the necessary fields are populated + // + // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Validate + err := repo.Validate() + if err != nil { + return err + } + + // encrypt the fields for the repo + // + // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Encrypt + err = repo.Encrypt(e.config.EncryptionKey) + if err != nil { + return fmt.Errorf("unable to encrypt repo %s: %w", r.GetFullName(), err) + } + + // send query to the database + return e.client. + Table(constants.TableRepo). + Save(repo). + Error +} diff --git a/database/repo/update_test.go b/database/repo/update_test.go new file mode 100644 index 000000000..db29a91dd --- /dev/null +++ b/database/repo/update_test.go @@ -0,0 +1,80 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestRepo_Engine_UpdateRepo(t *testing.T) { + // setup types + _repo := testRepo() + _repo.SetID(1) + _repo.SetUserID(1) + _repo.SetHash("baz") + _repo.SetOrg("foo") + _repo.SetName("bar") + _repo.SetFullName("foo/bar") + _repo.SetVisibility("public") + _repo.SetPipelineType("yaml") + _repo.SetPreviousName("oldName") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // ensure the mock expects the query + _mock.ExpectExec(`UPDATE "repos" +SET "user_id"=$1,"hash"=$2,"org"=$3,"name"=$4,"full_name"=$5,"link"=$6,"clone"=$7,"branch"=$8,"build_limit"=$9,"timeout"=$10,"counter"=$11,"visibility"=$12,"private"=$13,"trusted"=$14,"active"=$15,"allow_pull"=$16,"allow_push"=$17,"allow_deploy"=$18,"allow_tag"=$19,"allow_comment"=$20,"pipeline_type"=$21,"previous_name"=$22 +WHERE "id" = $23`). + WithArgs(1, AnyArgument{}, "foo", "bar", "foo/bar", nil, nil, nil, AnyArgument{}, AnyArgument{}, AnyArgument{}, "public", false, false, false, false, false, false, false, false, "yaml", "oldName", 1). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateRepo(_repo) + if err != nil { + t.Errorf("unable to create test repo for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err = test.database.UpdateRepo(_repo) + + if test.failure { + if err == nil { + t.Errorf("UpdateRepo for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("UpdateRepo for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/go.mod b/go.mod index 9935cdf40..0c305132a 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.1 github.com/hashicorp/vault/api v1.7.2 github.com/joho/godotenv v1.4.0 + github.com/kr/pretty v0.3.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.12.2 github.com/sirupsen/logrus v1.8.1 @@ -97,6 +98,7 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/lib/pq v1.10.6 // indirect github.com/mattn/go-colorable v0.1.8 // indirect @@ -117,6 +119,7 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect + github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect From ff5b8e18a094ecf4b74c8c9e4d5670a8c1d20829 Mon Sep 17 00:00:00 2001 From: JordanBrockopp Date: Sat, 27 Aug 2022 15:07:27 -0500 Subject: [PATCH 06/16] chore: save work --- api/admin/repo.go | 2 +- api/metrics.go | 2 +- api/repo.go | 53 +- api/scm.go | 14 +- api/user.go | 4 +- api/webhook.go | 6 +- database/postgres/ddl/repo.go | 49 -- database/postgres/dml/repo.go | 76 --- database/postgres/postgres.go | 30 +- database/postgres/postgres_test.go | 48 +- database/postgres/repo.go | 132 ----- database/postgres/repo_count.go | 66 --- database/postgres/repo_count_test.go | 301 ------------ database/postgres/repo_list.go | 162 ------ database/postgres/repo_list_test.go | 407 ---------------- database/postgres/repo_test.go | 280 ----------- database/repo/{get_name.go => get_org.go} | 4 +- .../{get_name_test.go => get_org_test.go} | 8 +- database/repo/list_org.go | 48 +- database/repo/list_user.go | 47 +- database/repo/service.go | 4 +- database/service.go | 36 +- database/sqlite/ddl/repo.go | 49 -- database/sqlite/dml/repo.go | 76 --- database/sqlite/repo.go | 132 ----- database/sqlite/repo_count.go | 66 --- database/sqlite/repo_count_test.go | 341 ------------- database/sqlite/repo_list.go | 162 ------ database/sqlite/repo_list_test.go | 461 ------------------ database/sqlite/repo_test.go | 278 ----------- database/sqlite/sqlite.go | 28 +- database/sqlite/sqlite_test.go | 37 ++ router/middleware/repo/repo.go | 2 +- 33 files changed, 239 insertions(+), 3172 deletions(-) delete mode 100644 database/postgres/ddl/repo.go delete mode 100644 database/postgres/dml/repo.go delete mode 100644 database/postgres/repo.go delete mode 100644 database/postgres/repo_count.go delete mode 100644 database/postgres/repo_count_test.go delete mode 100644 database/postgres/repo_list.go delete mode 100644 database/postgres/repo_list_test.go delete mode 100644 database/postgres/repo_test.go rename database/repo/{get_name.go => get_org.go} (91%) rename database/repo/{get_name_test.go => get_org_test.go} (87%) delete mode 100644 database/sqlite/ddl/repo.go delete mode 100644 database/sqlite/dml/repo.go delete mode 100644 database/sqlite/repo.go delete mode 100644 database/sqlite/repo_count.go delete mode 100644 database/sqlite/repo_count_test.go delete mode 100644 database/sqlite/repo_list.go delete mode 100644 database/sqlite/repo_list_test.go delete mode 100644 database/sqlite/repo_test.go diff --git a/api/admin/repo.go b/api/admin/repo.go index 81916a05f..3eb3e955f 100644 --- a/api/admin/repo.go +++ b/api/admin/repo.go @@ -45,7 +45,7 @@ func AllRepos(c *gin.Context) { logrus.Info("Admin: reading all repos") // send API call to capture all repos - r, err := database.FromContext(c).GetRepoList() + r, err := database.FromContext(c).ListRepos() if err != nil { retErr := fmt.Errorf("unable to capture all repos: %w", err) diff --git a/api/metrics.go b/api/metrics.go index e9aac3820..12a35b0f9 100644 --- a/api/metrics.go +++ b/api/metrics.go @@ -216,7 +216,7 @@ func recordGauges(c *gin.Context) { // repo_count if q.RepoCount { // send API call to capture the total number of repos - r, err := database.FromContext(c).GetRepoCount() + r, err := database.FromContext(c).CountRepos() if err != nil { logrus.Errorf("unable to get count of all repos: %v", err) } diff --git a/api/repo.go b/api/repo.go index 889bf0d50..64d6c8b04 100644 --- a/api/repo.go +++ b/api/repo.go @@ -206,7 +206,7 @@ func CreateRepo(c *gin.Context) { } // send API call to capture the repo from the database - dbRepo, err := database.FromContext(c).GetRepo(r.GetOrg(), r.GetName()) + dbRepo, err := database.FromContext(c).GetRepoForOrg(r.GetOrg(), r.GetName()) if err == nil && dbRepo.GetActive() { retErr := fmt.Errorf("unable to activate repo: %s is already active", r.GetFullName()) @@ -262,7 +262,7 @@ func CreateRepo(c *gin.Context) { } // send API call to capture the updated repo - r, _ = database.FromContext(c).GetRepo(dbRepo.GetOrg(), dbRepo.GetName()) + r, _ = database.FromContext(c).GetRepoForOrg(dbRepo.GetOrg(), dbRepo.GetName()) } else { // send API call to create the repo err = database.FromContext(c).CreateRepo(r) @@ -275,7 +275,7 @@ func CreateRepo(c *gin.Context) { } // send API call to capture the created repo - r, _ = database.FromContext(c).GetRepo(r.GetOrg(), r.GetName()) + r, _ = database.FromContext(c).GetRepoForOrg(r.GetOrg(), r.GetName()) } c.JSON(http.StatusCreated, r) @@ -361,18 +361,17 @@ func GetRepos(c *gin.Context) { // ensure per_page isn't above or below allowed values perPage = util.MaxInt(1, util.MinInt(100, perPage)) - // send API call to capture the total number of repos for the user - t, err := database.FromContext(c).GetUserRepoCount(u) - if err != nil { - retErr := fmt.Errorf("unable to get repo count for user %s: %w", u.GetName(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return + // capture the query parameters if present: + // + // * active + // * sort_by + filters := map[string]interface{}{ + "active": util.QueryParameter(c, "active", "true"), + "sort_by": util.QueryParameter(c, "sort_by", "name"), } // send API call to capture the list of repos for the user - r, err := database.FromContext(c).GetUserRepoList(u, page, perPage) + r, t, err := database.FromContext(c).ListReposForUser(u, filters, page, perPage) if err != nil { retErr := fmt.Errorf("unable to get repos for user %s: %w", u.GetName(), err) @@ -493,35 +492,27 @@ func GetOrgRepos(c *gin.Context) { // ensure per_page isn't above or below allowed values perPage = util.MaxInt(1, util.MinInt(100, perPage)) - // capture the sort_by query parameter if present - sortBy := util.QueryParameter(c, "sort_by", "name") + // capture the query parameters if present: + // + // * active + // * sort_by + filters := map[string]interface{}{ + "active": util.QueryParameter(c, "active", "true"), + "sort_by": util.QueryParameter(c, "sort_by", "name"), + } // See if the user is an org admin to bypass individual permission checks perm, err := scm.FromContext(c).OrgAccess(u, o) if err != nil { logrus.Errorf("unable to get user %s access level for org %s", u.GetName(), o) } - - filters := map[string]string{} // Only show public repos to non-admins if perm != "admin" { - filters["visibility"] = "public" - } - - filters["active"] = util.QueryParameter(c, "active", "true") - - // send API call to capture the total number of repos for the org - t, err := database.FromContext(c).GetOrgRepoCount(o, filters) - if err != nil { - retErr := fmt.Errorf("unable to get repo count for org %s: %w", o, err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return + filters["visibility"] = constants.VisibilityPublic } // send API call to capture the list of repos for the org - r, err := database.FromContext(c).GetOrgRepoList(o, filters, page, perPage, sortBy) + r, t, err := database.FromContext(c).ListReposForOrg(o, filters, page, perPage) if err != nil { retErr := fmt.Errorf("unable to get repos for org %s: %w", o, err) @@ -806,7 +797,7 @@ func UpdateRepo(c *gin.Context) { } // send API call to capture the updated repo - r, _ = database.FromContext(c).GetRepo(r.GetOrg(), r.GetName()) + r, _ = database.FromContext(c).GetRepoForOrg(r.GetOrg(), r.GetName()) c.JSON(http.StatusOK, r) } diff --git a/api/scm.go b/api/scm.go index 4ad82015c..778ea0419 100644 --- a/api/scm.go +++ b/api/scm.go @@ -6,6 +6,7 @@ package api import ( "fmt" + "github.com/go-vela/types/constants" "net/http" "github.com/gin-gonic/gin" @@ -69,14 +70,19 @@ func SyncRepos(c *gin.Context) { logger.Errorf("unable to get user %s access level for org %s", u.GetName(), o) } - filters := map[string]string{} + // capture the query parameters if present: + // + // * sort_by + filters := map[string]interface{}{ + "sort_by": util.QueryParameter(c, "sort_by", "name"), + } // Only show public repos to non-admins if perm != "admin" { - filters["visibility"] = "public" + filters["visibility"] = constants.VisibilityPublic } // send API call to capture the total number of repos for the org - t, err := database.FromContext(c).GetOrgRepoCount(o, filters) + t, err := database.FromContext(c).CountReposForOrg(o, filters) if err != nil { retErr := fmt.Errorf("unable to get repo count for org %s: %w", o, err) @@ -89,7 +95,7 @@ func SyncRepos(c *gin.Context) { page := 0 // capture all repos belonging to a certain org in database for orgRepos := int64(0); orgRepos < t; orgRepos += 100 { - r, err := database.FromContext(c).GetOrgRepoList(o, filters, page, 100, "name") + r, _, err := database.FromContext(c).ListReposForOrg(o, filters, page, 100) if err != nil { retErr := fmt.Errorf("unable to get repo count for org %s: %w", o, err) diff --git a/api/user.go b/api/user.go index f22108c58..f8adf72aa 100644 --- a/api/user.go +++ b/api/user.go @@ -432,11 +432,11 @@ func GetUserSourceRepos(c *gin.Context) { for org := range output { // capture source repos from the database backend, grouped by org page := 1 - filters := map[string]string{} + filters := map[string]interface{}{} for page > 0 { // send API call to capture the list of repos for the org - dbReposPart, err := database.FromContext(c).GetOrgRepoList(org, filters, page, 100, "name") + dbReposPart, _, err := database.FromContext(c).ListReposForOrg(org, filters, page, 100) if err != nil { retErr := fmt.Errorf("unable to get repos for org %s: %w", org, err) diff --git a/api/webhook.go b/api/webhook.go index 7376e754f..36c4196a9 100644 --- a/api/webhook.go +++ b/api/webhook.go @@ -172,7 +172,7 @@ func PostWebhook(c *gin.Context) { } // send API call to capture parsed repo from webhook - r, err = database.FromContext(c).GetRepo(r.GetOrg(), r.GetName()) + r, err = database.FromContext(c).GetRepoForOrg(r.GetOrg(), r.GetName()) if err != nil { retErr := fmt.Errorf("%s: failed to get repo %s: %w", baseErr, r.GetFullName(), err) util.HandleError(c, http.StatusBadRequest, retErr) @@ -414,7 +414,7 @@ func PostWebhook(c *gin.Context) { } // send API call to capture repo for the counter - r, err = database.FromContext(c).GetRepo(r.GetOrg(), r.GetName()) + r, err = database.FromContext(c).GetRepoForOrg(r.GetOrg(), r.GetName()) if err != nil { retErr := fmt.Errorf("%s: unable to get repo %s: %w", baseErr, r.GetFullName(), err) @@ -729,7 +729,7 @@ func renameRepository(h *library.Hook, r *library.Repo, c *gin.Context, m *types // get the old name of the repo previousName := r.GetPreviousName() // get the repo from the database that matches the old name - dbR, err := database.FromContext(c).GetRepo(r.GetOrg(), previousName) + dbR, err := database.FromContext(c).GetRepoForOrg(r.GetOrg(), previousName) if err != nil { retErr := fmt.Errorf("%s: failed to get repo %s/%s from database", baseErr, r.GetOrg(), previousName) util.HandleError(c, http.StatusBadRequest, retErr) diff --git a/database/postgres/ddl/repo.go b/database/postgres/ddl/repo.go deleted file mode 100644 index f9b01d274..000000000 --- a/database/postgres/ddl/repo.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package ddl - -const ( - // CreateRepoTable represents a query to - // create the repos table for Vela. - CreateRepoTable = ` -CREATE TABLE -IF NOT EXISTS -repos ( - id SERIAL PRIMARY KEY, - user_id INTEGER, - hash VARCHAR(500), - org VARCHAR(250), - name VARCHAR(250), - full_name VARCHAR(500), - link VARCHAR(1000), - clone VARCHAR(1000), - branch VARCHAR(250), - build_limit INTEGER, - timeout INTEGER, - counter INTEGER, - visibility TEXT, - private BOOLEAN, - trusted BOOLEAN, - active BOOLEAN, - allow_pull BOOLEAN, - allow_push BOOLEAN, - allow_deploy BOOLEAN, - allow_tag BOOLEAN, - allow_comment BOOLEAN, - pipeline_type TEXT, - previous_name VARCHAR(100), - UNIQUE(full_name) -); -` - - // CreateRepoOrgNameIndex represents a query to create an - // index on the repos table for the org and name columns. - CreateRepoOrgNameIndex = ` -CREATE INDEX -IF NOT EXISTS -repos_org_name -ON repos (org, name); -` -) diff --git a/database/postgres/dml/repo.go b/database/postgres/dml/repo.go deleted file mode 100644 index 5a1f96cfe..000000000 --- a/database/postgres/dml/repo.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package dml - -const ( - // ListRepos represents a query to - // list all repos in the database. - ListRepos = ` -SELECT * -FROM repos; -` - - // ListUserRepos represents a query to list - // all repos for a user_id in the database. - ListUserRepos = ` -SELECT * -FROM repos -WHERE user_id = ? -ORDER BY id DESC -LIMIT ? -OFFSET ?; -` - - // SelectRepo represents a query to select a - // repo for an org and name in the database. - SelectRepo = ` -SELECT * -FROM repos -WHERE org = ? -AND name = ? -LIMIT 1; -` - - // SelectUserReposCount represents a query to select - // the count of repos for a user_id in the database. - SelectUserReposCount = ` -SELECT count(*) as count -FROM repos -WHERE user_id = ?; -` - - // SelectReposCount represents a query to select - // the count of repos in the database. - SelectReposCount = ` -SELECT count(*) as count -FROM repos; -` - - // DeleteRepo represents a query to - // remove a repo from the database. - DeleteRepo = ` -DELETE -FROM repos -WHERE id = ?; -` - - // ListReposByLastUpdate represents a query to list - // all repos in an org, ordered by latest activity. - // In this case, latest activity is synonymous with - // the created timestamp of the last build for the repo. - ListReposByLastUpdate = ` -SELECT r.* -FROM repos r LEFT JOIN ( - SELECT repos.id, MAX(builds.created) as latest_build - FROM builds INNER JOIN repos - ON builds.repo_id = repos.id - WHERE repos.org = ? - GROUP BY repos.id) t -ON r.id = t.id -ORDER BY latest_build DESC NULLS LAST -LIMIT ? -OFFSET ?; -` -) diff --git a/database/postgres/postgres.go b/database/postgres/postgres.go index cedb34c49..ff635bb88 100644 --- a/database/postgres/postgres.go +++ b/database/postgres/postgres.go @@ -11,6 +11,7 @@ import ( "github.com/DATA-DOG/go-sqlmock" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/postgres/ddl" + "github.com/go-vela/server/database/repo" "github.com/go-vela/server/database/user" "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" @@ -45,6 +46,8 @@ type ( Logger *logrus.Entry // https://pkg.go.dev/github.com/go-vela/server/database/pipeline#PipelineService pipeline.PipelineService + // https://pkg.go.dev/github.com/go-vela/server/database/repo#RepoService + repo.RepoService // https://pkg.go.dev/github.com/go-vela/server/database/user#UserService user.UserService } @@ -145,6 +148,8 @@ func NewTest() (*client, sqlmock.Sqlmock, error) { _mock.ExpectExec(pipeline.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(pipeline.CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(repo.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(repo.CreateOrgNameIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(user.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(user.CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1)) @@ -245,12 +250,6 @@ func createTables(c *client) error { return fmt.Errorf("unable to create %s table: %w", constants.TableLog, err) } - // create the repos table - err = c.Postgres.Exec(ddl.CreateRepoTable).Error - if err != nil { - return fmt.Errorf("unable to create %s table: %w", constants.TableRepo, err) - } - // create the secrets table err = c.Postgres.Exec(ddl.CreateSecretTable).Error if err != nil { @@ -319,12 +318,6 @@ func createIndexes(c *client) error { return fmt.Errorf("unable to create logs_build_id index for the %s table: %w", constants.TableLog, err) } - // create the repos_org_name index for the repos table - err = c.Postgres.Exec(ddl.CreateRepoOrgNameIndex).Error - if err != nil { - return fmt.Errorf("unable to create repos_org_name index for the %s table: %w", constants.TableRepo, err) - } - // create the secrets_type_org_repo index for the secrets table err = c.Postgres.Exec(ddl.CreateSecretTypeOrgRepo).Error if err != nil { @@ -369,6 +362,19 @@ func createServices(c *client) error { return err } + // create the database agnostic repo service + // + // https://pkg.go.dev/github.com/go-vela/server/database/repo#New + c.RepoService, err = repo.New( + repo.WithClient(c.Postgres), + repo.WithEncryptionKey(c.config.EncryptionKey), + repo.WithLogger(c.Logger), + repo.WithSkipCreation(c.config.SkipCreation), + ) + if err != nil { + return err + } + // create the database agnostic user service // // https://pkg.go.dev/github.com/go-vela/server/database/user#New diff --git a/database/postgres/postgres_test.go b/database/postgres/postgres_test.go index e66919618..53375a1e7 100644 --- a/database/postgres/postgres_test.go +++ b/database/postgres/postgres_test.go @@ -12,7 +12,9 @@ import ( "github.com/DATA-DOG/go-sqlmock" "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/postgres/ddl" + "github.com/go-vela/server/database/repo" "github.com/go-vela/server/database/user" + "github.com/go-vela/types/library" ) func TestPostgres_New(t *testing.T) { @@ -77,7 +79,6 @@ func TestPostgres_setupDatabase(t *testing.T) { _mock.ExpectExec(ddl.CreateBuildTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateHookTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateLogTable).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(ddl.CreateRepoTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateSecretTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateServiceTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateStepTable).WillReturnResult(sqlmock.NewResult(1, 1)) @@ -90,13 +91,16 @@ func TestPostgres_setupDatabase(t *testing.T) { _mock.ExpectExec(ddl.CreateBuildSourceIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateHookRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateLogBuildIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(ddl.CreateRepoOrgNameIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateSecretTypeOrgRepo).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateSecretTypeOrgTeam).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateSecretTypeOrg).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(pipeline.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(pipeline.CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(repo.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(repo.CreateOrgNameIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(user.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(user.CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1)) // setup the skip test database client _skipDatabase, _skipMock, err := NewTest() @@ -160,7 +164,6 @@ func TestPostgres_createTables(t *testing.T) { _mock.ExpectExec(ddl.CreateBuildTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateHookTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateLogTable).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(ddl.CreateRepoTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateSecretTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateServiceTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateStepTable).WillReturnResult(sqlmock.NewResult(1, 1)) @@ -209,7 +212,6 @@ func TestPostgres_createIndexes(t *testing.T) { _mock.ExpectExec(ddl.CreateBuildSourceIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateHookRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateLogBuildIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(ddl.CreateRepoOrgNameIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateSecretTypeOrgRepo).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateSecretTypeOrgTeam).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateSecretTypeOrg).WillReturnResult(sqlmock.NewResult(1, 1)) @@ -254,6 +256,8 @@ func TestPostgres_createServices(t *testing.T) { // ensure the mock expects the index queries _mock.ExpectExec(pipeline.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(pipeline.CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(repo.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(repo.CreateOrgNameIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(user.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(user.CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1)) @@ -295,3 +299,39 @@ type AnyArgument struct{} func (a AnyArgument) Match(v driver.Value) bool { return true } + +// testRepo is a test helper function to create a +// library Repo type with all fields set to their +// zero values. +func testRepo() *library.Repo { + i64 := int64(0) + i := 0 + str := "" + b := false + + return &library.Repo{ + ID: &i64, + PipelineType: &str, + UserID: &i64, + Hash: &str, + Org: &str, + Name: &str, + FullName: &str, + Link: &str, + Clone: &str, + Branch: &str, + BuildLimit: &i64, + Timeout: &i64, + Counter: &i, + Visibility: &str, + Private: &b, + Trusted: &b, + Active: &b, + AllowPull: &b, + AllowPush: &b, + AllowDeploy: &b, + AllowTag: &b, + AllowComment: &b, + PreviousName: &str, + } +} diff --git a/database/postgres/repo.go b/database/postgres/repo.go deleted file mode 100644 index 409a2b7ef..000000000 --- a/database/postgres/repo.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "errors" - "fmt" - - "github.com/sirupsen/logrus" - - "github.com/go-vela/server/database/postgres/dml" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" - - "gorm.io/gorm" -) - -// GetRepo gets a repo by org and name from the database. -func (c *client) GetRepo(org, name string) (*library.Repo, error) { - c.Logger.WithFields(logrus.Fields{ - "org": org, - "repo": name, - }).Tracef("getting repo %s/%s from the database", org, name) - - // variable to store query results - r := new(database.Repo) - - // send query to the database and store result in variable - result := c.Postgres. - Table(constants.TableRepo). - Raw(dml.SelectRepo, org, name). - Scan(r) - - // check if the query returned a record not found error or no rows were returned - if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 { - return nil, gorm.ErrRecordNotFound - } - - // decrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt - err := r.Decrypt(c.config.EncryptionKey) - if err != nil { - // ensures that the change is backwards compatible - // by logging the error instead of returning it - // which allows us to fetch unencrypted repos - c.Logger.Errorf("unable to decrypt repo %s/%s: %v", org, name, err) - - // return the unencrypted repo - return r.ToLibrary(), result.Error - } - - // return the decrypted repo - return r.ToLibrary(), result.Error -} - -// CreateRepo creates a new repo in the database. -// -// nolint: dupl // ignore similar code with update -func (c *client) CreateRepo(r *library.Repo) error { - c.Logger.WithFields(logrus.Fields{ - "org": r.GetOrg(), - "repo": r.GetName(), - }).Tracef("creating repo %s in the database", r.GetFullName()) - - // cast to database type - repo := database.RepoFromLibrary(r) - - // validate the necessary fields are populated - err := repo.Validate() - if err != nil { - return err - } - - // encrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Encrypt - err = repo.Encrypt(c.config.EncryptionKey) - if err != nil { - return fmt.Errorf("unable to encrypt repo %s: %w", r.GetFullName(), err) - } - - // send query to the database - return c.Postgres. - Table(constants.TableRepo). - Create(repo).Error -} - -// UpdateRepo updates a repo in the database. -// -// nolint: dupl // ignore similar code with create -func (c *client) UpdateRepo(r *library.Repo) error { - c.Logger.WithFields(logrus.Fields{ - "org": r.GetOrg(), - "repo": r.GetName(), - }).Tracef("updating repo %s in the database", r.GetFullName()) - - // cast to database type - repo := database.RepoFromLibrary(r) - - // validate the necessary fields are populated - err := repo.Validate() - if err != nil { - return err - } - - // encrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Encrypt - err = repo.Encrypt(c.config.EncryptionKey) - if err != nil { - return fmt.Errorf("unable to encrypt repo %s: %w", r.GetFullName(), err) - } - - // send query to the database - return c.Postgres. - Table(constants.TableRepo). - Save(repo).Error -} - -// DeleteRepo deletes a repo by unique ID from the database. -func (c *client) DeleteRepo(id int64) error { - c.Logger.Tracef("deleting repo %d in the database", id) - - // send query to the database - return c.Postgres. - Table(constants.TableRepo). - Exec(dml.DeleteRepo, id).Error -} diff --git a/database/postgres/repo_count.go b/database/postgres/repo_count.go deleted file mode 100644 index aa3408c1a..000000000 --- a/database/postgres/repo_count.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "github.com/go-vela/server/database/postgres/dml" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" -) - -// GetRepoCount gets a count of all repos from the database. -func (c *client) GetRepoCount() (int64, error) { - c.Logger.Trace("getting count of repos from the database") - - // variable to store query results - var r int64 - - // send query to the database and store result in variable - err := c.Postgres. - Table(constants.TableRepo). - Raw(dml.SelectReposCount). - Pluck("count", &r).Error - - return r, err -} - -// GetOrgRepoCount gets a count of all repos for a specific org from the database. -func (c *client) GetOrgRepoCount(org string, filters map[string]string) (int64, error) { - c.Logger.WithFields(logrus.Fields{ - "org": org, - }).Tracef("getting count of repos for org %s from the database", org) - - // variable to store query results - var r int64 - - // send query to the database and store result in variable - err := c.Postgres. - Table(constants.TableRepo). - Select("count(*)"). - Where("org = ?", org). - Where(filters). - Pluck("count", &r).Error - - return r, err -} - -// GetUserRepoCount gets a count of all repos for a specific user from the database. -func (c *client) GetUserRepoCount(u *library.User) (int64, error) { - c.Logger.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Tracef("getting count of repos for user %s in the database", u.GetName()) - - // variable to store query results - var r int64 - - // send query to the database and store result in variable - err := c.Postgres. - Table(constants.TableRepo). - Raw(dml.SelectUserReposCount, u.GetID()). - Pluck("count", &r).Error - - return r, err -} diff --git a/database/postgres/repo_count_test.go b/database/postgres/repo_count_test.go deleted file mode 100644 index 324915eb5..000000000 --- a/database/postgres/repo_count_test.go +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "reflect" - "testing" - - sqlmock "github.com/DATA-DOG/go-sqlmock" - - "github.com/go-vela/server/database/postgres/dml" - "github.com/go-vela/types/library" - - "gorm.io/gorm" -) - -func TestPostgres_Client_GetRepoCount(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - - _repoTwo := testRepo() - _repoTwo.SetID(1) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("bar") - _repoTwo.SetName("foo") - _repoTwo.SetFullName("bar/foo") - _repoTwo.SetVisibility("public") - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // capture the current expected SQL query - // - // https://gorm.io/docs/sql_builder.html#DryRun-Mode - _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectReposCount).Statement - - // create expected return in mock - _rows := sqlmock.NewRows([]string{"count"}).AddRow(2) - - // ensure the mock expects the query - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want int64 - }{ - { - failure: false, - want: 2, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetRepoCount() - - if test.failure { - if err == nil { - t.Errorf("GetRepoCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetRepoCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetRepoCount is %v, want %v", got, test.want) - } - } -} - -func TestPostgres_Client_GetUserRepoCount(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - - _repoTwo := testRepo() - _repoTwo.SetID(1) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("bar") - _repoTwo.SetName("foo") - _repoTwo.SetFullName("bar/foo") - _repoTwo.SetVisibility("public") - - _user := new(library.User) - _user.SetID(1) - _user.SetName("foo") - _user.SetToken("bar") - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // capture the current expected SQL query - // - // https://gorm.io/docs/sql_builder.html#DryRun-Mode - _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectUserReposCount, 1).Statement - - // create expected return in mock - _rows := sqlmock.NewRows([]string{"count"}).AddRow(2) - - // ensure the mock expects the query - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want int64 - }{ - { - failure: false, - want: 2, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetUserRepoCount(_user) - - if test.failure { - if err == nil { - t.Errorf("GetUserRepoCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetUserRepoCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetUserRepoCount is %v, want %v", got, test.want) - } - } -} - -func TestPostgres_Client_GetOrgRepoCount(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - - _repoTwo := testRepo() - _repoTwo.SetID(1) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("bar") - _repoTwo.SetName("foo") - _repoTwo.SetFullName("bar/foo") - _repoTwo.SetVisibility("public") - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // create expected return in mock - _rows := sqlmock.NewRows([]string{"count"}).AddRow(1) - - // ensure the mock expects the query - _mock.ExpectQuery("SELECT count(*) FROM \"repos\" WHERE org = $1").WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want int64 - }{ - { - failure: false, - want: 1, - }, - } - filters := map[string]string{} - // run tests - for _, test := range tests { - got, err := _database.GetOrgRepoCount("foo", filters) - - if test.failure { - if err == nil { - t.Errorf("GetRepoCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetRepoCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetRepoCount is %v, want %v", got, test.want) - } - } -} - -func TestPostgres_Client_GetOrgRepoCount_NonAdmin(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - - _repoTwo := testRepo() - _repoTwo.SetID(1) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("bar") - _repoTwo.SetName("foo") - _repoTwo.SetFullName("bar/foo") - _repoTwo.SetVisibility("private") - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // create expected return in mock - _rows := sqlmock.NewRows([]string{"count"}).AddRow(1) - - // ensure the mock expects the query - _mock.ExpectQuery("SELECT count(*) FROM \"repos\" WHERE org = $1 AND \"visibility\" = $2").WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want int64 - }{ - { - failure: false, - want: 1, - }, - } - filters := map[string]string{} - filters["visibility"] = "private" - // run tests - for _, test := range tests { - got, err := _database.GetOrgRepoCount("foo", filters) - - if test.failure { - if err == nil { - t.Errorf("GetRepoCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetRepoCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetRepoCount is %v, want %v", got, test.want) - } - } -} diff --git a/database/postgres/repo_list.go b/database/postgres/repo_list.go deleted file mode 100644 index 3cafb1a43..000000000 --- a/database/postgres/repo_list.go +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "github.com/go-vela/server/database/postgres/dml" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" -) - -// GetRepoList gets a list of all repos from the database. -// -// nolint: dupl // ignore false positive of duplicate code -func (c *client) GetRepoList() ([]*library.Repo, error) { - c.Logger.Trace("listing repos from the database") - - // variable to store query results - r := new([]database.Repo) - - // send query to the database and store result in variable - err := c.Postgres. - Table(constants.TableRepo). - Raw(dml.ListRepos). - Scan(r).Error - if err != nil { - return nil, err - } - - // variable we want to return - repos := []*library.Repo{} - // iterate through all query results - for _, repo := range *r { - // https://golang.org/doc/faq#closures_and_goroutines - tmp := repo - - // decrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt - err = tmp.Decrypt(c.config.EncryptionKey) - if err != nil { - // ensures that the change is backwards compatible - // by logging the error instead of returning it - // which allows us to fetch unencrypted repos - c.Logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err) - } - - // convert query result to library type - repos = append(repos, tmp.ToLibrary()) - } - - return repos, nil -} - -// GetOrgRepoList gets a list of all repos by org from the database. -func (c *client) GetOrgRepoList(org string, filters map[string]string, page, perPage int, sortBy string) ([]*library.Repo, error) { - c.Logger.WithFields(logrus.Fields{ - "org": org, - }).Tracef("listing repos for org %s from the database", org) - - // variable to store query results - r := new([]database.Repo) - - // calculate offset for pagination through results - offset := perPage * (page - 1) - - // send query to the database and store result in variable - switch sortBy { - case "latest": - err := c.Postgres. - Table(constants.TableRepo). - Raw(dml.ListReposByLastUpdate, org, perPage, offset). - Scan(r).Error - if err != nil { - return nil, err - } - default: - err := c.Postgres. - Table(constants.TableRepo). - Where("org = ?", org). - Where(filters). - Order("name"). - Limit(perPage). - Offset(offset). - Scan(r).Error - if err != nil { - return nil, err - } - } - - // variable we want to return - repos := []*library.Repo{} - // iterate through all query results - for _, repo := range *r { - // https://golang.org/doc/faq#closures_and_goroutines - tmp := repo - - // decrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt - err := tmp.Decrypt(c.config.EncryptionKey) - if err != nil { - // ensures that the change is backwards compatible - // by logging the error instead of returning it - // which allows us to fetch unencrypted repos - c.Logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err) - } - - // convert query result to library type - repos = append(repos, tmp.ToLibrary()) - } - - return repos, nil -} - -// GetUserRepoList gets a list of all repos by user ID from the database. -func (c *client) GetUserRepoList(u *library.User, page, perPage int) ([]*library.Repo, error) { - c.Logger.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Tracef("listing repos for user %s from the database", u.GetName()) - - // variable to store query results - r := new([]database.Repo) - // calculate offset for pagination through results - offset := perPage * (page - 1) - - // send query to the database and store result in variable - err := c.Postgres. - Table(constants.TableRepo). - Raw(dml.ListUserRepos, u.GetID(), perPage, offset). - Scan(r).Error - if err != nil { - return nil, err - } - - // variable we want to return - repos := []*library.Repo{} - // iterate through all query results - for _, repo := range *r { - // https://golang.org/doc/faq#closures_and_goroutines - tmp := repo - - // decrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt - err = tmp.Decrypt(c.config.EncryptionKey) - if err != nil { - // ensures that the change is backwards compatible - // by logging the error instead of returning it - // which allows us to fetch unencrypted repos - c.Logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err) - } - - // convert query result to library type - repos = append(repos, tmp.ToLibrary()) - } - - return repos, nil -} diff --git a/database/postgres/repo_list_test.go b/database/postgres/repo_list_test.go deleted file mode 100644 index 65871d82f..000000000 --- a/database/postgres/repo_list_test.go +++ /dev/null @@ -1,407 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "reflect" - "testing" - - sqlmock "github.com/DATA-DOG/go-sqlmock" - - "github.com/go-vela/server/database/postgres/dml" - "github.com/go-vela/types/library" - - "gorm.io/gorm" -) - -func TestPostgres_Client_GetRepoList(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - _repoOne.SetPipelineType("yaml") - _repoOne.SetPreviousName("") - - _repoTwo := testRepo() - _repoTwo.SetID(1) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("bar") - _repoTwo.SetName("foo") - _repoTwo.SetFullName("bar/foo") - _repoTwo.SetVisibility("public") - _repoTwo.SetPipelineType("yaml") - _repoTwo.SetPreviousName("oldName") - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // capture the current expected SQL query - // - // https://gorm.io/docs/sql_builder.html#DryRun-Mode - _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.ListRepos).Statement - - // create expected return in mock - _rows := sqlmock.NewRows( - []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}, - ).AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", ""). - AddRow(1, 1, "baz", "bar", "foo", "bar/foo", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "oldName") - - // ensure the mock expects the query - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want []*library.Repo - }{ - { - failure: false, - want: []*library.Repo{_repoOne, _repoTwo}, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetRepoList() - - if test.failure { - if err == nil { - t.Errorf("GetRepoList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetRepoList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetRepoList is %v, want %v", got, test.want) - } - } -} - -func TestPostgres_Client_GetOrgRepoList(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - _repoOne.SetPipelineType("yaml") - _repoOne.SetPreviousName("") - - _repoTwo := testRepo() - _repoTwo.SetID(1) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("foo") - _repoTwo.SetName("baz") - _repoTwo.SetFullName("foo/baz") - _repoTwo.SetVisibility("public") - _repoTwo.SetPipelineType("yaml") - _repoTwo.SetPreviousName("oldName") - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // create expected return in mock - _rows := sqlmock.NewRows( - []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}, - ).AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", ""). - AddRow(1, 1, "baz", "foo", "baz", "foo/baz", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "oldName") - - // ensure the mock expects the query - _mock.ExpectQuery("SELECT * FROM \"repos\" WHERE org = $1 ORDER BY name LIMIT 10").WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want []*library.Repo - }{ - { - failure: false, - want: []*library.Repo{_repoOne, _repoTwo}, - }, - } - filters := map[string]string{} - // run tests - for _, test := range tests { - got, err := _database.GetOrgRepoList("foo", filters, 1, 10, "name") - - if test.failure { - if err == nil { - t.Errorf("GetOrgRepoList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetOrgRepoList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetOrgRepoList is %v, want %v", got, test.want) - } - } -} - -func TestPostgres_Client_GetOrgRepoList_LastUpdate(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - _repoOne.SetPipelineType("yaml") - _repoOne.SetPreviousName("") - - _repoTwo := testRepo() - _repoTwo.SetID(1) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("foo") - _repoTwo.SetName("baz") - _repoTwo.SetFullName("foo/baz") - _repoTwo.SetVisibility("public") - _repoTwo.SetPipelineType("yaml") - _repoTwo.SetPreviousName("oldName") - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // capture the current expected SQL query - // - // https://gorm.io/docs/sql_builder.html#DryRun-Mode - _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.ListReposByLastUpdate, "foo", "10", "1").Statement - - // create expected return in mock - _rows := sqlmock.NewRows( - []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}, - ).AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", ""). - AddRow(1, 1, "baz", "foo", "baz", "foo/baz", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "oldName") - - // ensure the mock expects the query for test case - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want []*library.Repo - }{ - { - failure: false, - want: []*library.Repo{_repoOne, _repoTwo}, - }, - } - filters := map[string]string{} - // run tests - for _, test := range tests { - got, err := _database.GetOrgRepoList("foo", filters, 1, 10, "latest") - - if test.failure { - if err == nil { - t.Errorf("GetOrgRepoList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetOrgRepoList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetOrgRepoList is %v, want %v", got, test.want) - } - } -} - -func TestPostgres_Client_GetOrgRepoList_NonAdmin(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - _repoOne.SetPipelineType("yaml") - _repoOne.SetPreviousName("") - - _repoTwo := testRepo() - _repoTwo.SetID(1) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("foo") - _repoTwo.SetName("baz") - _repoTwo.SetFullName("foo/baz") - _repoTwo.SetVisibility("private") - _repoTwo.SetPipelineType("yaml") - _repoTwo.SetPreviousName("oldName") - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // create expected return in mock - _rows := sqlmock.NewRows( - []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}, - ).AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "") - - // ensure the mock expects the query - _mock.ExpectQuery("SELECT * FROM \"repos\" WHERE org = $1 AND \"visibility\" = $2 ORDER BY name LIMIT 10").WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want []*library.Repo - }{ - { - failure: false, - want: []*library.Repo{_repoOne}, - }, - } - filters := map[string]string{} - filters["visibility"] = "public" - // run tests - for _, test := range tests { - got, err := _database.GetOrgRepoList("foo", filters, 1, 10, "name") - - if test.failure { - if err == nil { - t.Errorf("GetOrgRepoList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetOrgRepoList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetOrgRepoList is %v, want %v", got, test.want) - } - } -} - -func TestPostgres_Client_GetUserRepoList(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - _repoOne.SetPipelineType("yaml") - _repoOne.SetPreviousName("") - - _repoTwo := testRepo() - _repoTwo.SetID(1) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("bar") - _repoTwo.SetName("foo") - _repoTwo.SetFullName("bar/foo") - _repoTwo.SetVisibility("public") - _repoTwo.SetPipelineType("yaml") - _repoTwo.SetPreviousName("") - - _user := new(library.User) - _user.SetID(1) - _user.SetName("foo") - _user.SetToken("bar") - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // capture the current expected SQL query - // - // https://gorm.io/docs/sql_builder.html#DryRun-Mode - _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.ListUserRepos, 1, 1, 10).Statement - - // create expected return in mock - _rows := sqlmock.NewRows( - []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}, - ).AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", ""). - AddRow(1, 1, "baz", "bar", "foo", "bar/foo", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "") - - // ensure the mock expects the query - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want []*library.Repo - }{ - { - failure: false, - want: []*library.Repo{_repoOne, _repoTwo}, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetUserRepoList(_user, 1, 10) - - if test.failure { - if err == nil { - t.Errorf("GetUserRepoList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetUserRepoList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetUserRepoList is %v, want %v", got, test.want) - } - } -} diff --git a/database/postgres/repo_test.go b/database/postgres/repo_test.go deleted file mode 100644 index b95d3ecc3..000000000 --- a/database/postgres/repo_test.go +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "reflect" - "testing" - - "github.com/DATA-DOG/go-sqlmock" - - "github.com/go-vela/server/database/postgres/dml" - "github.com/go-vela/types/library" - - "gorm.io/gorm" -) - -func TestPostgres_Client_GetRepo(t *testing.T) { - // setup types - _repo := testRepo() - _repo.SetID(1) - _repo.SetUserID(1) - _repo.SetHash("baz") - _repo.SetOrg("foo") - _repo.SetName("bar") - _repo.SetFullName("foo/bar") - _repo.SetVisibility("public") - _repo.SetPipelineType("yaml") - _repo.SetPreviousName("") - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // capture the current expected SQL query - // - // https://gorm.io/docs/sql_builder.html#DryRun-Mode - _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectRepo, "foo", "bar").Statement - - // create expected return in mock - _rows := sqlmock.NewRows( - []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}, - ).AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "") - - // ensure the mock expects the query for test case 1 - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - // ensure the mock expects the error for test case 2 - _mock.ExpectQuery(_query.SQL.String()).WillReturnError(gorm.ErrRecordNotFound) - - // setup tests - tests := []struct { - failure bool - want *library.Repo - }{ - { - failure: false, - want: _repo, - }, - { - failure: true, - want: nil, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetRepo("foo", "bar") - - if test.failure { - if err == nil { - t.Errorf("GetRepo should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetRepo returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetRepo is %v, want %v", got, test.want) - } - } -} - -func TestPostgres_Client_CreateRepo(t *testing.T) { - // setup types - _repo := testRepo() - _repo.SetID(1) - _repo.SetUserID(1) - _repo.SetHash("baz") - _repo.SetOrg("foo") - _repo.SetName("bar") - _repo.SetFullName("foo/bar") - _repo.SetVisibility("public") - _repo.SetPipelineType("yaml") - _repo.SetPreviousName("oldName") - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // create expected return in mock - _rows := sqlmock.NewRows([]string{"id"}).AddRow(1) - - // ensure the mock expects the query - _mock.ExpectQuery(`INSERT INTO "repos" ("user_id","hash","org","name","full_name","link","clone","branch","build_limit","timeout","counter","visibility","private","trusted","active","allow_pull","allow_push","allow_deploy","allow_tag","allow_comment","pipeline_type","previous_name","id") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23) RETURNING "id"`). - WithArgs(1, AnyArgument{}, "foo", "bar", "foo/bar", nil, nil, nil, AnyArgument{}, AnyArgument{}, AnyArgument{}, "public", false, false, false, false, false, false, false, false, "yaml", "oldName", 1). - WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - err := _database.CreateRepo(_repo) - - if test.failure { - if err == nil { - t.Errorf("CreateRepo should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("CreateRepo returned err: %v", err) - } - } -} - -func TestPostgres_Client_UpdateRepo(t *testing.T) { - // setup types - _repo := testRepo() - _repo.SetID(1) - _repo.SetUserID(1) - _repo.SetHash("baz") - _repo.SetOrg("foo") - _repo.SetName("bar") - _repo.SetFullName("foo/bar") - _repo.SetVisibility("public") - _repo.SetPipelineType("yaml") - _repo.SetPreviousName("oldName") - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // ensure the mock expects the query - _mock.ExpectExec(`UPDATE "repos" SET "user_id"=$1,"hash"=$2,"org"=$3,"name"=$4,"full_name"=$5,"link"=$6,"clone"=$7,"branch"=$8,"build_limit"=$9,"timeout"=$10,"counter"=$11,"visibility"=$12,"private"=$13,"trusted"=$14,"active"=$15,"allow_pull"=$16,"allow_push"=$17,"allow_deploy"=$18,"allow_tag"=$19,"allow_comment"=$20,"pipeline_type"=$21,"previous_name"=$22 WHERE "id" = $23`). - WithArgs(1, AnyArgument{}, "foo", "bar", "foo/bar", nil, nil, nil, AnyArgument{}, AnyArgument{}, AnyArgument{}, "public", false, false, false, false, false, false, false, false, "yaml", "oldName", 1). - WillReturnResult(sqlmock.NewResult(1, 1)) - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - err := _database.UpdateRepo(_repo) - - if test.failure { - if err == nil { - t.Errorf("UpdateRepo should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("UpdateRepo returned err: %v", err) - } - } -} - -func TestPostgres_Client_DeleteRepo(t *testing.T) { - // setup types - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // capture the current expected SQL query - // - // https://gorm.io/docs/sql_builder.html#DryRun-Mode - _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Exec(dml.DeleteRepo, 1).Statement - - // ensure the mock expects the query - _mock.ExpectExec(_query.SQL.String()).WillReturnResult(sqlmock.NewResult(1, 1)) - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - err := _database.DeleteRepo(1) - - if test.failure { - if err == nil { - t.Errorf("DeleteRepo should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("DeleteRepo returned err: %v", err) - } - } -} - -// testRepo is a test helper function to create a -// library Repo type with all fields set to their -// zero values. -func testRepo() *library.Repo { - i64 := int64(0) - i := 0 - str := "" - b := false - - return &library.Repo{ - ID: &i64, - PipelineType: &str, - UserID: &i64, - Hash: &str, - Org: &str, - Name: &str, - FullName: &str, - Link: &str, - Clone: &str, - Branch: &str, - BuildLimit: &i64, - Timeout: &i64, - Counter: &i, - Visibility: &str, - Private: &b, - Trusted: &b, - Active: &b, - AllowPull: &b, - AllowPush: &b, - AllowDeploy: &b, - AllowTag: &b, - AllowComment: &b, - PreviousName: &str, - } -} diff --git a/database/repo/get_name.go b/database/repo/get_org.go similarity index 91% rename from database/repo/get_name.go rename to database/repo/get_org.go index ac1e85e1c..85e2707c2 100644 --- a/database/repo/get_name.go +++ b/database/repo/get_org.go @@ -11,8 +11,8 @@ import ( "github.com/sirupsen/logrus" ) -// GetRepoForName gets a repo by org and repo name from the database. -func (e *engine) GetRepoForName(org, name string) (*library.Repo, error) { +// GetRepoForOrg gets a repo by org and repo name from the database. +func (e *engine) GetRepoForOrg(org, name string) (*library.Repo, error) { e.logger.WithFields(logrus.Fields{ "org": org, "repo": name, diff --git a/database/repo/get_name_test.go b/database/repo/get_org_test.go similarity index 87% rename from database/repo/get_name_test.go rename to database/repo/get_org_test.go index 01cc5756c..e5b545c1f 100644 --- a/database/repo/get_name_test.go +++ b/database/repo/get_org_test.go @@ -67,22 +67,22 @@ func TestRepo_Engine_GetRepoForName(t *testing.T) { // run tests for _, test := range tests { t.Run(test.name, func(t *testing.T) { - got, err := test.database.GetRepoForName("foo", "bar") + got, err := test.database.GetRepoForOrg("foo", "bar") if test.failure { if err == nil { - t.Errorf("GetRepoForName for %s should have returned err", test.name) + t.Errorf("GetRepoForOrg for %s should have returned err", test.name) } return } if err != nil { - t.Errorf("GetRepoForName for %s returned err: %v", test.name, err) + t.Errorf("GetRepoForOrg for %s returned err: %v", test.name, err) } if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetRepoForName for %s is %v, want %v", test.name, got, test.want) + t.Errorf("GetRepoForOrg for %s is %v, want %v", test.name, got, test.want) } }) } diff --git a/database/repo/list_org.go b/database/repo/list_org.go index 4a613a4b1..1226fd39c 100644 --- a/database/repo/list_org.go +++ b/database/repo/list_org.go @@ -38,18 +38,42 @@ func (e *engine) ListReposForOrg(org string, filters map[string]interface{}, pag // calculate offset for pagination through results offset := perPage * (page - 1) - // TODO: add support for last update - err = e.client. - Table(constants.TableRepo). - Where("org = ?", org). - Where(filters). - Order("name"). - Limit(perPage). - Offset(offset). - Find(&r). - Error - if err != nil { - return nil, count, err + switch filters["sort_by"] { + case "latest": + query := e.client. + Table(constants.TableBuild). + Select("repos.id, MAX(builds.created) AS latest_build"). + Joins("INNER JOIN repos repos ON builds.repo_id = repos.id"). + Where("repos.org = ?", org). + Group("repos.id") + + err = e.client. + Table(constants.TableRepo). + Select("repos.*"). + Joins("LEFT JOIN (?) t on repos.id = t.id", query). + Order("latest_build DESC NULLS LAST"). + Limit(perPage). + Offset(offset). + Find(&r). + Error + if err != nil { + return nil, count, err + } + case "name": + fallthrough + default: + err = e.client. + Table(constants.TableRepo). + Where("org = ?", org). + Where(filters). + Order("name"). + Limit(perPage). + Offset(offset). + Find(&r). + Error + if err != nil { + return nil, count, err + } } // iterate through all query results diff --git a/database/repo/list_user.go b/database/repo/list_user.go index 8ffe57500..b16ea3e11 100644 --- a/database/repo/list_user.go +++ b/database/repo/list_user.go @@ -38,17 +38,42 @@ func (e *engine) ListReposForUser(u *library.User, filters map[string]interface{ // calculate offset for pagination through results offset := perPage * (page - 1) - // TODO: add support for last update - err = e.client. - Table(constants.TableRepo). - Where("user_id = ?", u.GetID()). - Order("name"). - Limit(perPage). - Offset(offset). - Find(&r). - Error - if err != nil { - return nil, count, err + switch filters["sort_by"] { + case "latest": + query := e.client. + Table(constants.TableBuild). + Select("repos.id, MAX(builds.created) AS latest_build"). + Joins("INNER JOIN repos repos ON builds.repo_id = repos.id"). + Where("repos.user_id = ?", u.GetID()). + Group("repos.id") + + err = e.client. + Table(constants.TableRepo). + Select("repos.*"). + Joins("LEFT JOIN (?) t on repos.id = t.id", query). + Order("latest_build DESC NULLS LAST"). + Limit(perPage). + Offset(offset). + Find(&r). + Error + if err != nil { + return nil, count, err + } + case "name": + fallthrough + default: + err = e.client. + Table(constants.TableRepo). + Where("user_id = ?", u.GetID()). + Where(filters). + Order("name"). + Limit(perPage). + Offset(offset). + Find(&r). + Error + if err != nil { + return nil, count, err + } } // iterate through all query results diff --git a/database/repo/service.go b/database/repo/service.go index fb242541a..237e41027 100644 --- a/database/repo/service.go +++ b/database/repo/service.go @@ -38,8 +38,8 @@ type RepoService interface { DeleteRepo(*library.Repo) error // GetRepo defines a function that gets a repo by ID. GetRepo(int64) (*library.Repo, error) - // GetRepoForName defines a function that gets a repo by org and repo name. - GetRepoForName(string, string) (*library.Repo, error) + // GetRepoForOrg defines a function that gets a repo by org and repo name. + GetRepoForOrg(string, string) (*library.Repo, error) // ListRepos defines a function that gets a list of all repos. ListRepos() ([]*library.Repo, error) // ListReposForOrg defines a function that gets a list of repos by org name. diff --git a/database/service.go b/database/service.go index 785f65742..649dc0056 100644 --- a/database/service.go +++ b/database/service.go @@ -6,6 +6,7 @@ package database import ( "github.com/go-vela/server/database/pipeline" + "github.com/go-vela/server/database/repo" "github.com/go-vela/server/database/user" "github.com/go-vela/types/library" ) @@ -119,38 +120,9 @@ type Service interface { // related to pipelines stored in the database. pipeline.PipelineService - // Repo Database Interface Functions - - // GetRepo defines a function that - // gets a repo by org and name. - GetRepo(string, string) (*library.Repo, error) - // GetRepoList defines a function that - // gets a list of all repos. - GetRepoList() ([]*library.Repo, error) - // GetOrgRepoList defines a function that - // gets a list of all repos by org excluding repos specified. - GetOrgRepoList(string, map[string]string, int, int, string) ([]*library.Repo, error) - // GetOrgRepoCount defines a function that - // gets the count of repos for an org. - GetOrgRepoCount(string, map[string]string) (int64, error) - // GetRepoCount defines a function that - // gets the count of repos. - GetRepoCount() (int64, error) - // GetUserRepoList defines a function - // that gets a list of repos by user ID. - GetUserRepoList(*library.User, int, int) ([]*library.Repo, error) - // GetUserRepoCount defines a function that - // gets the count of repos for a user. - GetUserRepoCount(*library.User) (int64, error) - // CreateRepo defines a function that - // creates a new repo. - CreateRepo(*library.Repo) error - // UpdateRepo defines a function that - // updates a repo. - UpdateRepo(*library.Repo) error - // DeleteRepo defines a function that - // deletes a repo by unique ID. - DeleteRepo(int64) error + // RepoService provides the interface for functionality + // related to repos stored in the database. + repo.RepoService // Secret Database Interface Functions diff --git a/database/sqlite/ddl/repo.go b/database/sqlite/ddl/repo.go deleted file mode 100644 index 2fcb723c1..000000000 --- a/database/sqlite/ddl/repo.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package ddl - -const ( - // CreateRepoTable represents a query to - // create the repos table for Vela. - CreateRepoTable = ` -CREATE TABLE -IF NOT EXISTS -repos ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER, - hash TEXT, - org TEXT, - name TEXT, - full_name TEXT, - link TEXT, - clone TEXT, - branch TEXT, - build_limit INTEGER, - timeout INTEGER, - counter INTEGER, - visibility TEXT, - private BOOLEAN, - trusted BOOLEAN, - active BOOLEAN, - allow_pull BOOLEAN, - allow_push BOOLEAN, - allow_deploy BOOLEAN, - allow_tag BOOLEAN, - allow_comment BOOLEAN, - pipeline_type TEXT, - previous_name TEXT, - UNIQUE(full_name) -); -` - - // CreateRepoOrgNameIndex represents a query to create an - // index on the repos table for the org and name columns. - CreateRepoOrgNameIndex = ` -CREATE INDEX -IF NOT EXISTS -repos_org_name -ON repos (org, name); -` -) diff --git a/database/sqlite/dml/repo.go b/database/sqlite/dml/repo.go deleted file mode 100644 index 5a1f96cfe..000000000 --- a/database/sqlite/dml/repo.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package dml - -const ( - // ListRepos represents a query to - // list all repos in the database. - ListRepos = ` -SELECT * -FROM repos; -` - - // ListUserRepos represents a query to list - // all repos for a user_id in the database. - ListUserRepos = ` -SELECT * -FROM repos -WHERE user_id = ? -ORDER BY id DESC -LIMIT ? -OFFSET ?; -` - - // SelectRepo represents a query to select a - // repo for an org and name in the database. - SelectRepo = ` -SELECT * -FROM repos -WHERE org = ? -AND name = ? -LIMIT 1; -` - - // SelectUserReposCount represents a query to select - // the count of repos for a user_id in the database. - SelectUserReposCount = ` -SELECT count(*) as count -FROM repos -WHERE user_id = ?; -` - - // SelectReposCount represents a query to select - // the count of repos in the database. - SelectReposCount = ` -SELECT count(*) as count -FROM repos; -` - - // DeleteRepo represents a query to - // remove a repo from the database. - DeleteRepo = ` -DELETE -FROM repos -WHERE id = ?; -` - - // ListReposByLastUpdate represents a query to list - // all repos in an org, ordered by latest activity. - // In this case, latest activity is synonymous with - // the created timestamp of the last build for the repo. - ListReposByLastUpdate = ` -SELECT r.* -FROM repos r LEFT JOIN ( - SELECT repos.id, MAX(builds.created) as latest_build - FROM builds INNER JOIN repos - ON builds.repo_id = repos.id - WHERE repos.org = ? - GROUP BY repos.id) t -ON r.id = t.id -ORDER BY latest_build DESC NULLS LAST -LIMIT ? -OFFSET ?; -` -) diff --git a/database/sqlite/repo.go b/database/sqlite/repo.go deleted file mode 100644 index 76767c277..000000000 --- a/database/sqlite/repo.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "errors" - "fmt" - - "github.com/sirupsen/logrus" - - "github.com/go-vela/server/database/sqlite/dml" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" - - "gorm.io/gorm" -) - -// GetRepo gets a repo by org and name from the database. -func (c *client) GetRepo(org, name string) (*library.Repo, error) { - c.Logger.WithFields(logrus.Fields{ - "org": org, - "repo": name, - }).Tracef("getting repo %s/%s from the database", org, name) - - // variable to store query results - r := new(database.Repo) - - // send query to the database and store result in variable - result := c.Sqlite. - Table(constants.TableRepo). - Raw(dml.SelectRepo, org, name). - Scan(r) - - // check if the query returned a record not found error or no rows were returned - if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 { - return nil, gorm.ErrRecordNotFound - } - - // decrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt - err := r.Decrypt(c.config.EncryptionKey) - if err != nil { - // ensures that the change is backwards compatible - // by logging the error instead of returning it - // which allows us to fetch unencrypted repos - c.Logger.Errorf("unable to decrypt repo %s/%s: %v", org, name, err) - - // return the unencrypted repo - return r.ToLibrary(), result.Error - } - - // return the decrypted repo - return r.ToLibrary(), result.Error -} - -// CreateRepo creates a new repo in the database. -// -// nolint: dupl // ignore similar code with update -func (c *client) CreateRepo(r *library.Repo) error { - c.Logger.WithFields(logrus.Fields{ - "org": r.GetOrg(), - "repo": r.GetName(), - }).Tracef("creating repo %s in the database", r.GetFullName()) - - // cast to database type - repo := database.RepoFromLibrary(r) - - // validate the necessary fields are populated - err := repo.Validate() - if err != nil { - return err - } - - // encrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Encrypt - err = repo.Encrypt(c.config.EncryptionKey) - if err != nil { - return fmt.Errorf("unable to encrypt repo %s: %w", r.GetFullName(), err) - } - - // send query to the database - return c.Sqlite. - Table(constants.TableRepo). - Create(repo).Error -} - -// UpdateRepo updates a repo in the database. -// -// nolint: dupl // ignore similar code with create -func (c *client) UpdateRepo(r *library.Repo) error { - c.Logger.WithFields(logrus.Fields{ - "org": r.GetOrg(), - "repo": r.GetName(), - }).Tracef("updating repo %s in the database", r.GetFullName()) - - // cast to database type - repo := database.RepoFromLibrary(r) - - // validate the necessary fields are populated - err := repo.Validate() - if err != nil { - return err - } - - // encrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Encrypt - err = repo.Encrypt(c.config.EncryptionKey) - if err != nil { - return fmt.Errorf("unable to encrypt repo %s: %w", r.GetFullName(), err) - } - - // send query to the database - return c.Sqlite. - Table(constants.TableRepo). - Save(repo).Error -} - -// DeleteRepo deletes a repo by unique ID from the database. -func (c *client) DeleteRepo(id int64) error { - c.Logger.Tracef("deleting repo %d in the database", id) - - // send query to the database - return c.Sqlite. - Table(constants.TableRepo). - Exec(dml.DeleteRepo, id).Error -} diff --git a/database/sqlite/repo_count.go b/database/sqlite/repo_count.go deleted file mode 100644 index 8438db1ee..000000000 --- a/database/sqlite/repo_count.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "github.com/go-vela/server/database/sqlite/dml" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" -) - -// GetRepoCount gets a count of all repos from the database. -func (c *client) GetRepoCount() (int64, error) { - c.Logger.Trace("getting count of repos from the database") - - // variable to store query results - var r int64 - - // send query to the database and store result in variable - err := c.Sqlite. - Table(constants.TableRepo). - Raw(dml.SelectReposCount). - Pluck("count", &r).Error - - return r, err -} - -// GetOrgRepoCount gets a count of all repos for a specific org from the database. -func (c *client) GetOrgRepoCount(org string, filters map[string]string) (int64, error) { - c.Logger.WithFields(logrus.Fields{ - "org": org, - }).Tracef("getting count of repos for org %s from the database", org) - - // variable to store query results - var r int64 - - // send query to the database and store result in variable - err := c.Sqlite. - Table(constants.TableRepo). - Select("count(*)"). - Where("org = ?", org). - Where(filters). - Pluck("count", &r).Error - - return r, err -} - -// GetUserRepoCount gets a count of all repos for a specific user from the database. -func (c *client) GetUserRepoCount(u *library.User) (int64, error) { - c.Logger.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Tracef("getting count of repos for user %s in the database", u.GetName()) - - // variable to store query results - var r int64 - - // send query to the database and store result in variable - err := c.Sqlite. - Table(constants.TableRepo). - Raw(dml.SelectUserReposCount, u.GetID()). - Pluck("count", &r).Error - - return r, err -} diff --git a/database/sqlite/repo_count_test.go b/database/sqlite/repo_count_test.go deleted file mode 100644 index dbb4ac10e..000000000 --- a/database/sqlite/repo_count_test.go +++ /dev/null @@ -1,341 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "log" - "reflect" - "testing" - - "github.com/go-vela/server/database/sqlite/ddl" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" -) - -func init() { - // setup the test database client - _database, err := NewTest() - if err != nil { - log.Fatalf("unable to create new sqlite test database: %v", err) - } - - // create the repo table - err = _database.Sqlite.Exec(ddl.CreateRepoTable).Error - if err != nil { - log.Fatalf("unable to create %s table: %v", constants.TableRepo, err) - } -} - -func TestSqlite_Client_GetRepoCount(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - - _repoTwo := testRepo() - _repoTwo.SetID(2) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("bar") - _repoTwo.SetName("foo") - _repoTwo.SetFullName("bar/foo") - _repoTwo.SetVisibility("public") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - want int64 - }{ - { - failure: false, - want: 2, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the repos table - defer _database.Sqlite.Exec("delete from repos;") - - // create the repos in the database - err := _database.CreateRepo(_repoOne) - if err != nil { - t.Errorf("unable to create test repo: %v", err) - } - - err = _database.CreateRepo(_repoTwo) - if err != nil { - t.Errorf("unable to create test repo: %v", err) - } - - got, err := _database.GetRepoCount() - - if test.failure { - if err == nil { - t.Errorf("GetRepoCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetRepoCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetRepoCount is %v, want %v", got, test.want) - } - } -} - -func TestSqlite_Client_GetUserRepoCount(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - - _repoTwo := testRepo() - _repoTwo.SetID(2) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("bar") - _repoTwo.SetName("foo") - _repoTwo.SetFullName("bar/foo") - _repoTwo.SetVisibility("public") - - _user := new(library.User) - _user.SetID(1) - _user.SetName("foo") - _user.SetToken("bar") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - want int64 - }{ - { - failure: false, - want: 2, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the repos table - defer _database.Sqlite.Exec("delete from repos;") - - // create the repos in the database - err := _database.CreateRepo(_repoOne) - if err != nil { - t.Errorf("unable to create test repo: %v", err) - } - - err = _database.CreateRepo(_repoTwo) - if err != nil { - t.Errorf("unable to create test repo: %v", err) - } - - got, err := _database.GetUserRepoCount(_user) - - if test.failure { - if err == nil { - t.Errorf("GetUserRepoCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetUserRepoCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetUserRepoCount is %v, want %v", got, test.want) - } - } -} - -func TestSqlite_Client_GetOrgRepoCount(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - - _repoTwo := testRepo() - _repoTwo.SetID(2) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("bar") - _repoTwo.SetName("foo") - _repoTwo.SetFullName("bar/foo") - _repoTwo.SetVisibility("public") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - want int64 - }{ - { - failure: false, - want: 1, - }, - } - filters := map[string]string{} - // run tests - for _, test := range tests { - // defer cleanup of the repos table - defer _database.Sqlite.Exec("delete from repos;") - - // create the repos in the database - err := _database.CreateRepo(_repoOne) - if err != nil { - t.Errorf("unable to create test repo: %v", err) - } - - err = _database.CreateRepo(_repoTwo) - if err != nil { - t.Errorf("unable to create test repo: %v", err) - } - - got, err := _database.GetOrgRepoCount("foo", filters) - - if test.failure { - if err == nil { - t.Errorf("GetRepoCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetRepoCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetRepoCount is %v, want %v", got, test.want) - } - } -} - -func TestSqlite_Client_GetOrgRepoCount_NonAdmin(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - - _repoTwo := testRepo() - _repoTwo.SetID(2) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("foo") - _repoTwo.SetName("foo") - _repoTwo.SetFullName("foo/foo") - _repoTwo.SetVisibility("private") - - _repoThree := testRepo() - _repoThree.SetID(3) - _repoThree.SetUserID(1) - _repoThree.SetHash("baz") - _repoThree.SetOrg("bar") - _repoThree.SetName("foo") - _repoThree.SetFullName("bar/foo") - _repoThree.SetVisibility("private") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - want int64 - }{ - { - failure: false, - want: 1, - }, - } - filters := map[string]string{} - filters["visibility"] = "public" - // run tests - for _, test := range tests { - // defer cleanup of the repos table - defer _database.Sqlite.Exec("delete from repos;") - - for _, repo := range []*library.Repo{_repoOne, _repoTwo, _repoThree} { - // create the repos in the database - err := _database.CreateRepo(repo) - if err != nil { - t.Errorf("unable to create test repo: %v", err) - } - } - - got, err := _database.GetOrgRepoCount("foo", filters) - - if test.failure { - if err == nil { - t.Errorf("GetRepoCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetRepoCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetRepoCount is %v, want %v", got, test.want) - } - } -} diff --git a/database/sqlite/repo_list.go b/database/sqlite/repo_list.go deleted file mode 100644 index b4807d1ca..000000000 --- a/database/sqlite/repo_list.go +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "github.com/go-vela/server/database/sqlite/dml" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" - "github.com/sirupsen/logrus" -) - -// GetRepoList gets a list of all repos from the database. -// -// nolint: dupl // ignore false positive of duplicate code -func (c *client) GetRepoList() ([]*library.Repo, error) { - c.Logger.Trace("listing repos from the database") - - // variable to store query results - r := new([]database.Repo) - - // send query to the database and store result in variable - err := c.Sqlite. - Table(constants.TableRepo). - Raw(dml.ListRepos). - Scan(r).Error - if err != nil { - return nil, err - } - - // variable we want to return - repos := []*library.Repo{} - // iterate through all query results - for _, repo := range *r { - // https://golang.org/doc/faq#closures_and_goroutines - tmp := repo - - // decrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt - err = tmp.Decrypt(c.config.EncryptionKey) - if err != nil { - // ensures that the change is backwards compatible - // by logging the error instead of returning it - // which allows us to fetch unencrypted repos - c.Logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err) - } - - // convert query result to library type - repos = append(repos, tmp.ToLibrary()) - } - - return repos, nil -} - -// GetOrgRepoList gets a list of all repos by org from the database. -func (c *client) GetOrgRepoList(org string, filters map[string]string, page, perPage int, sortBy string) ([]*library.Repo, error) { - c.Logger.WithFields(logrus.Fields{ - "org": org, - }).Tracef("listing repos for org %s from the database", org) - - // variable to store query results - r := new([]database.Repo) - - // calculate offset for pagination through results - offset := perPage * (page - 1) - - // send query to the database and store result in variable - switch sortBy { - case "latest": - err := c.Sqlite. - Table(constants.TableRepo). - Raw(dml.ListReposByLastUpdate, org, perPage, offset). - Scan(r).Error - if err != nil { - return nil, err - } - default: - err := c.Sqlite. - Table(constants.TableRepo). - Where("org = ?", org). - Where(filters). - Order("name"). - Limit(perPage). - Offset(offset). - Scan(r).Error - if err != nil { - return nil, err - } - } - - // variable we want to return - repos := []*library.Repo{} - // iterate through all query results - for _, repo := range *r { - // https://golang.org/doc/faq#closures_and_goroutines - tmp := repo - - // decrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt - err := tmp.Decrypt(c.config.EncryptionKey) - if err != nil { - // ensures that the change is backwards compatible - // by logging the error instead of returning it - // which allows us to fetch unencrypted repos - c.Logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err) - } - - // convert query result to library type - repos = append(repos, tmp.ToLibrary()) - } - - return repos, nil -} - -// GetUserRepoList gets a list of all repos by user ID from the database. -func (c *client) GetUserRepoList(u *library.User, page, perPage int) ([]*library.Repo, error) { - c.Logger.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Tracef("listing repos for user %s from the database", u.GetName()) - - // variable to store query results - r := new([]database.Repo) - // calculate offset for pagination through results - offset := perPage * (page - 1) - - // send query to the database and store result in variable - err := c.Sqlite. - Table(constants.TableRepo). - Raw(dml.ListUserRepos, u.GetID(), perPage, offset). - Scan(r).Error - if err != nil { - return nil, err - } - - // variable we want to return - repos := []*library.Repo{} - // iterate through all query results - for _, repo := range *r { - // https://golang.org/doc/faq#closures_and_goroutines - tmp := repo - - // decrypt the fields for the repo - // - // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt - err = tmp.Decrypt(c.config.EncryptionKey) - if err != nil { - // ensures that the change is backwards compatible - // by logging the error instead of returning it - // which allows us to fetch unencrypted repos - c.Logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err) - } - - // convert query result to library type - repos = append(repos, tmp.ToLibrary()) - } - - return repos, nil -} diff --git a/database/sqlite/repo_list_test.go b/database/sqlite/repo_list_test.go deleted file mode 100644 index 882e5b3e1..000000000 --- a/database/sqlite/repo_list_test.go +++ /dev/null @@ -1,461 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "log" - "reflect" - "testing" - - "github.com/go-vela/server/database/sqlite/ddl" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" -) - -func init() { - // setup the test database client - _database, err := NewTest() - if err != nil { - log.Fatalf("unable to create new sqlite test database: %v", err) - } - - // create the repo table - err = _database.Sqlite.Exec(ddl.CreateRepoTable).Error - if err != nil { - log.Fatalf("unable to create %s table: %v", constants.TableRepo, err) - } -} - -func TestSqlite_Client_GetRepoList(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - _repoOne.SetPipelineType("yaml") - _repoOne.SetPreviousName("") - - _repoTwo := testRepo() - _repoTwo.SetID(2) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("bar") - _repoTwo.SetName("foo") - _repoTwo.SetFullName("bar/foo") - _repoTwo.SetVisibility("public") - _repoTwo.SetPipelineType("yaml") - _repoTwo.SetPreviousName("oldName") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - want []*library.Repo - }{ - { - failure: false, - want: []*library.Repo{_repoOne, _repoTwo}, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the repos table - defer _database.Sqlite.Exec("delete from repos;") - - for _, repo := range test.want { - // create the repo in the database - err := _database.CreateRepo(repo) - if err != nil { - t.Errorf("unable to create test repo: %v", err) - } - } - - got, err := _database.GetRepoList() - - if test.failure { - if err == nil { - t.Errorf("GetRepoList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetRepoList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetRepoList is %v, want %v", got, test.want) - } - } -} - -func TestSqlite_Client_GetOrgRepoList(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - _repoOne.SetPipelineType("yaml") - _repoOne.SetPreviousName("oldName") - - _repoTwo := testRepo() - _repoTwo.SetID(2) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("foo") - _repoTwo.SetName("baz") - _repoTwo.SetFullName("foo/baz") - _repoTwo.SetVisibility("public") - _repoTwo.SetPipelineType("yaml") - _repoTwo.SetPreviousName("") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - want []*library.Repo - }{ - { - failure: false, - want: []*library.Repo{_repoOne, _repoTwo}, - }, - } - filters := map[string]string{} - // run tests - for _, test := range tests { - // defer cleanup of the repos table - defer _database.Sqlite.Exec("delete from repos;") - - for _, repo := range test.want { - // create the repo in the database - err := _database.CreateRepo(repo) - if err != nil { - t.Errorf("unable to create test repo: %v", err) - } - } - - got, err := _database.GetOrgRepoList("foo", filters, 1, 10, "name") - - if test.failure { - if err == nil { - t.Errorf("GetOrgRepoList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetOrgRepoList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetOrgRepoList is %v, want %v", got, test.want) - } - } -} - -func TestSqlite_Client_GetOrgRepoList_NonAdmin(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - _repoOne.SetPipelineType("yaml") - _repoOne.SetPreviousName("") - - _repoTwo := testRepo() - _repoTwo.SetID(2) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("foo") - _repoTwo.SetName("baz") - _repoTwo.SetFullName("foo/baz") - _repoTwo.SetVisibility("private") - _repoTwo.SetPipelineType("yaml") - _repoTwo.SetPreviousName("") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - want []*library.Repo - }{ - { - failure: false, - want: []*library.Repo{_repoOne}, - }, - } - filters := map[string]string{} - filters["visibility"] = "public" - // run tests - for _, test := range tests { - // defer cleanup of the repos table - defer _database.Sqlite.Exec("delete from repos;") - - for _, repo := range test.want { - // create the repo in the database - err := _database.CreateRepo(repo) - if err != nil { - t.Errorf("unable to create test repo: %v", err) - } - } - - got, err := _database.GetOrgRepoList("foo", filters, 1, 10, "name") - - if test.failure { - if err == nil { - t.Errorf("GetOrgRepoList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetOrgRepoList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetOrgRepoList is %v, want %v", got, test.want) - } - } -} - -func TestSqlite_Client_GetOrgRepoList_LastUpdate(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - _repoOne.SetPipelineType("yaml") - _repoOne.SetPreviousName("") - - _repoTwo := testRepo() - _repoTwo.SetID(2) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("foo") - _repoTwo.SetName("baz") - _repoTwo.SetFullName("foo/baz") - _repoTwo.SetVisibility("public") - _repoTwo.SetPipelineType("yaml") - _repoTwo.SetPreviousName("oldName") - - _repoThree := testRepo() - _repoThree.SetID(3) - _repoThree.SetUserID(1) - _repoThree.SetHash("baz") - _repoThree.SetOrg("foo") - _repoThree.SetName("bat") - _repoThree.SetFullName("foo/bat") - _repoThree.SetVisibility("public") - _repoThree.SetPipelineType("yaml") - _repoThree.SetPreviousName("") - - _buildOne := testBuild() - _buildOne.SetID(1) - _buildOne.SetCreated(1) - _buildOne.SetNumber(1) - _buildOne.SetRepoID(2) - - _buildTwo := testBuild() - _buildTwo.SetID(2) - _buildTwo.SetCreated(2) - _buildTwo.SetNumber(1) - _buildTwo.SetRepoID(1) - - _buildThree := testBuild() - _buildThree.SetID(3) - _buildThree.SetCreated(3) - _buildThree.SetNumber(1) - _buildThree.SetRepoID(3) - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - err = _database.CreateBuild(_buildOne) - if err != nil { - t.Errorf("unable to create build: %v", err) - } - - err = _database.CreateBuild(_buildTwo) - if err != nil { - t.Errorf("unable to create build: %v", err) - } - - err = _database.CreateBuild(_buildThree) - if err != nil { - t.Errorf("unable to create build: %v", err) - } - - // setup tests - tests := []struct { - failure bool - want []*library.Repo - }{ - { - failure: false, - want: []*library.Repo{_repoThree, _repoOne, _repoTwo}, - }, - } - - filters := map[string]string{} - - // run tests - for _, test := range tests { - // defer cleanup of the repos table - defer _database.Sqlite.Exec("delete from repos;") - - for _, repo := range test.want { - // create the repo in the database - err := _database.CreateRepo(repo) - if err != nil { - t.Errorf("unable to create test repo: %v", err) - } - } - - got, err := _database.GetOrgRepoList("foo", filters, 1, 10, "latest") - - if test.failure { - if err == nil { - t.Errorf("GetOrgRepoList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetOrgRepoList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetOrgRepoList is %v, want %v", got, test.want) - } - } -} - -func TestSqlite_Client_GetUserRepoList(t *testing.T) { - // setup types - _repoOne := testRepo() - _repoOne.SetID(1) - _repoOne.SetUserID(1) - _repoOne.SetHash("baz") - _repoOne.SetOrg("foo") - _repoOne.SetName("bar") - _repoOne.SetFullName("foo/bar") - _repoOne.SetVisibility("public") - _repoOne.SetPipelineType("yaml") - _repoOne.SetPreviousName("") - - _repoTwo := testRepo() - _repoTwo.SetID(2) - _repoTwo.SetUserID(1) - _repoTwo.SetHash("baz") - _repoTwo.SetOrg("bar") - _repoTwo.SetName("foo") - _repoTwo.SetFullName("bar/foo") - _repoTwo.SetVisibility("public") - _repoTwo.SetPipelineType("yaml") - _repoTwo.SetPreviousName("") - - _user := new(library.User) - _user.SetID(1) - _user.SetName("foo") - _user.SetToken("bar") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - want []*library.Repo - }{ - { - failure: false, - want: []*library.Repo{_repoTwo, _repoOne}, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the repos table - defer _database.Sqlite.Exec("delete from repos;") - - for _, repo := range test.want { - // create the repo in the database - err := _database.CreateRepo(repo) - if err != nil { - t.Errorf("unable to create test repo: %v", err) - } - } - - got, err := _database.GetUserRepoList(_user, 1, 10) - - if test.failure { - if err == nil { - t.Errorf("GetUserRepoList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetUserRepoList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetUserRepoList is %v, want %v", got, test.want) - } - } -} diff --git a/database/sqlite/repo_test.go b/database/sqlite/repo_test.go deleted file mode 100644 index 2c0cb3081..000000000 --- a/database/sqlite/repo_test.go +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "reflect" - "testing" - - "github.com/go-vela/types/library" -) - -func TestSqlite_Client_GetRepo(t *testing.T) { - // setup types - _repo := testRepo() - _repo.SetID(1) - _repo.SetUserID(1) - _repo.SetHash("baz") - _repo.SetOrg("foo") - _repo.SetName("bar") - _repo.SetFullName("foo/bar") - _repo.SetVisibility("public") - _repo.SetPipelineType("yaml") - _repo.SetPreviousName("") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - want *library.Repo - }{ - { - failure: false, - want: _repo, - }, - { - failure: true, - want: nil, - }, - } - - // run tests - for _, test := range tests { - if test.want != nil { - // create the repo in the database - err := _database.CreateRepo(test.want) - if err != nil { - t.Errorf("unable to create test repo: %v", err) - } - } - - got, err := _database.GetRepo("foo", "bar") - - // cleanup the repos table - _ = _database.Sqlite.Exec("DELETE FROM repos;") - - if test.failure { - if err == nil { - t.Errorf("GetRepo should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetRepo returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetRepo is %v, want %v", got, test.want) - } - } -} - -func TestSqlite_Client_CreateRepo(t *testing.T) { - // setup types - _repo := testRepo() - _repo.SetID(1) - _repo.SetUserID(1) - _repo.SetHash("baz") - _repo.SetOrg("foo") - _repo.SetName("bar") - _repo.SetFullName("foo/bar") - _repo.SetVisibility("public") - _repo.SetPreviousName("") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the repos table - defer _database.Sqlite.Exec("delete from repos;") - - err := _database.CreateRepo(_repo) - - if test.failure { - if err == nil { - t.Errorf("CreateRepo should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("CreateRepo returned err: %v", err) - } - } -} - -func TestSqlite_Client_UpdateRepo(t *testing.T) { - // setup types - _repo := testRepo() - _repo.SetID(1) - _repo.SetUserID(1) - _repo.SetHash("baz") - _repo.SetOrg("foo") - _repo.SetName("bar") - _repo.SetFullName("foo/bar") - _repo.SetVisibility("public") - _repo.SetPreviousName("") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the repos table - defer _database.Sqlite.Exec("delete from repos;") - - // create the repo in the database - err := _database.CreateRepo(_repo) - if err != nil { - t.Errorf("unable to create test repo: %v", err) - } - - err = _database.UpdateRepo(_repo) - - if test.failure { - if err == nil { - t.Errorf("UpdateRepo should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("UpdateRepo returned err: %v", err) - } - } -} - -func TestSqlite_Client_DeleteRepo(t *testing.T) { - // setup types - _repo := testRepo() - _repo.SetID(1) - _repo.SetUserID(1) - _repo.SetHash("baz") - _repo.SetOrg("foo") - _repo.SetName("bar") - _repo.SetFullName("foo/bar") - _repo.SetVisibility("public") - _repo.SetPreviousName("") - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the repos table - defer _database.Sqlite.Exec("delete from repos;") - - // create the repo in the database - err = _database.CreateRepo(_repo) - if err != nil { - t.Errorf("unable to create test repo: %v", err) - } - - err := _database.DeleteRepo(1) - - if test.failure { - if err == nil { - t.Errorf("DeleteRepo should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("DeleteRepo returned err: %v", err) - } - } -} - -// testRepo is a test helper function to create a -// library Repo type with all fields set to their -// zero values. -func testRepo() *library.Repo { - i64 := int64(0) - i := 0 - str := "" - b := false - - return &library.Repo{ - ID: &i64, - UserID: &i64, - Hash: &str, - Org: &str, - Name: &str, - FullName: &str, - Link: &str, - Clone: &str, - Branch: &str, - BuildLimit: &i64, - Timeout: &i64, - Counter: &i, - Visibility: &str, - Private: &b, - Trusted: &b, - Active: &b, - AllowPull: &b, - AllowPush: &b, - AllowDeploy: &b, - AllowTag: &b, - AllowComment: &b, - PreviousName: &str, - } -} diff --git a/database/sqlite/sqlite.go b/database/sqlite/sqlite.go index 32f476f16..dc926a014 100644 --- a/database/sqlite/sqlite.go +++ b/database/sqlite/sqlite.go @@ -9,6 +9,7 @@ import ( "time" "github.com/go-vela/server/database/pipeline" + "github.com/go-vela/server/database/repo" "github.com/go-vela/server/database/sqlite/ddl" "github.com/go-vela/server/database/user" "github.com/go-vela/types/constants" @@ -44,6 +45,8 @@ type ( Logger *logrus.Entry // https://pkg.go.dev/github.com/go-vela/server/database/pipeline#PipelineService pipeline.PipelineService + // https://pkg.go.dev/github.com/go-vela/server/database/repo#RepoService + repo.RepoService // https://pkg.go.dev/github.com/go-vela/server/database/user#UserService user.UserService } @@ -239,12 +242,6 @@ func createTables(c *client) error { return fmt.Errorf("unable to create %s table: %w", constants.TableLog, err) } - // create the repos table - err = c.Sqlite.Exec(ddl.CreateRepoTable).Error - if err != nil { - return fmt.Errorf("unable to create %s table: %w", constants.TableRepo, err) - } - // create the secrets table err = c.Sqlite.Exec(ddl.CreateSecretTable).Error if err != nil { @@ -313,12 +310,6 @@ func createIndexes(c *client) error { return fmt.Errorf("unable to create logs_build_id index for the %s table: %w", constants.TableLog, err) } - // create the repos_org_name index for the repos table - err = c.Sqlite.Exec(ddl.CreateRepoOrgNameIndex).Error - if err != nil { - return fmt.Errorf("unable to create repos_org_name index for the %s table: %w", constants.TableRepo, err) - } - // create the secrets_type_org_repo index for the secrets table err = c.Sqlite.Exec(ddl.CreateSecretTypeOrgRepo).Error if err != nil { @@ -363,6 +354,19 @@ func createServices(c *client) error { return err } + // create the database agnostic repo service + // + // https://pkg.go.dev/github.com/go-vela/server/database/repo#New + c.RepoService, err = repo.New( + repo.WithClient(c.Sqlite), + repo.WithEncryptionKey(c.config.EncryptionKey), + repo.WithLogger(c.Logger), + repo.WithSkipCreation(c.config.SkipCreation), + ) + if err != nil { + return err + } + // create the database agnostic user service // // https://pkg.go.dev/github.com/go-vela/server/database/user#New diff --git a/database/sqlite/sqlite_test.go b/database/sqlite/sqlite_test.go index 6b31f874a..48ff34902 100644 --- a/database/sqlite/sqlite_test.go +++ b/database/sqlite/sqlite_test.go @@ -7,6 +7,8 @@ package sqlite import ( "testing" "time" + + "github.com/go-vela/types/library" ) func TestSqlite_New(t *testing.T) { @@ -216,3 +218,38 @@ func TestSqlite_createServices(t *testing.T) { } } } + +// testRepo is a test helper function to create a +// library Repo type with all fields set to their +// zero values. +func testRepo() *library.Repo { + i64 := int64(0) + i := 0 + str := "" + b := false + + return &library.Repo{ + ID: &i64, + UserID: &i64, + Hash: &str, + Org: &str, + Name: &str, + FullName: &str, + Link: &str, + Clone: &str, + Branch: &str, + BuildLimit: &i64, + Timeout: &i64, + Counter: &i, + Visibility: &str, + Private: &b, + Trusted: &b, + Active: &b, + AllowPull: &b, + AllowPush: &b, + AllowDeploy: &b, + AllowTag: &b, + AllowComment: &b, + PreviousName: &str, + } +} diff --git a/router/middleware/repo/repo.go b/router/middleware/repo/repo.go index 94d85a4c3..d8cc5447e 100644 --- a/router/middleware/repo/repo.go +++ b/router/middleware/repo/repo.go @@ -45,7 +45,7 @@ func Establish() gin.HandlerFunc { "user": u.GetName(), }).Debugf("reading repo %s/%s", o, rParam) - r, err := database.FromContext(c).GetRepo(o, rParam) + r, err := database.FromContext(c).GetRepoForOrg(o, rParam) if err != nil { retErr := fmt.Errorf("unable to read repo %s/%s: %w", o, rParam, err) util.HandleError(c, http.StatusNotFound, retErr) From ab2f213b75e21928cbadebf1b471f143de4f08bd Mon Sep 17 00:00:00 2001 From: JordanBrockopp Date: Sat, 27 Aug 2022 15:29:16 -0500 Subject: [PATCH 07/16] fix: Scan() -> Take() --- database/pipeline/get.go | 2 +- database/pipeline/get_repo.go | 2 +- database/repo/get.go | 2 +- database/repo/get_org.go | 2 +- database/repo/list_org_test.go | 2 -- database/user/get.go | 2 +- database/user/get_name.go | 2 +- go.mod | 3 --- 8 files changed, 6 insertions(+), 11 deletions(-) diff --git a/database/pipeline/get.go b/database/pipeline/get.go index 14f1b35a8..b5f3cf23d 100644 --- a/database/pipeline/get.go +++ b/database/pipeline/get.go @@ -22,7 +22,7 @@ func (e *engine) GetPipeline(id int64) (*library.Pipeline, error) { Table(constants.TablePipeline). Where("id = ?", id). Limit(1). - Scan(p). + Take(p). Error if err != nil { return nil, err diff --git a/database/pipeline/get_repo.go b/database/pipeline/get_repo.go index 6df601b08..99b086a30 100644 --- a/database/pipeline/get_repo.go +++ b/database/pipeline/get_repo.go @@ -28,7 +28,7 @@ func (e *engine) GetPipelineForRepo(commit string, r *library.Repo) (*library.Pi Where("repo_id = ?", r.GetID()). Where("\"commit\" = ?", commit). Limit(1). - Scan(p). + Take(p). Error if err != nil { return nil, err diff --git a/database/repo/get.go b/database/repo/get.go index 03077ab07..ee4f8cf24 100644 --- a/database/repo/get.go +++ b/database/repo/get.go @@ -22,7 +22,7 @@ func (e *engine) GetRepo(id int64) (*library.Repo, error) { Table(constants.TableRepo). Where("id = ?", id). Limit(1). - Scan(r). + Take(r). Error if err != nil { return nil, err diff --git a/database/repo/get_org.go b/database/repo/get_org.go index 85e2707c2..aeb7beae4 100644 --- a/database/repo/get_org.go +++ b/database/repo/get_org.go @@ -27,7 +27,7 @@ func (e *engine) GetRepoForOrg(org, name string) (*library.Repo, error) { Where("org = ?", org). Where("name = ?", name). Limit(1). - Scan(r). + Take(r). Error if err != nil { return nil, err diff --git a/database/repo/list_org_test.go b/database/repo/list_org_test.go index 9abe63df0..b4a13fd8a 100644 --- a/database/repo/list_org_test.go +++ b/database/repo/list_org_test.go @@ -10,7 +10,6 @@ import ( "github.com/DATA-DOG/go-sqlmock" "github.com/go-vela/types/library" - "github.com/kr/pretty" ) func TestRepo_Engine_ListReposForOrg(t *testing.T) { @@ -107,7 +106,6 @@ func TestRepo_Engine_ListReposForOrg(t *testing.T) { } if !reflect.DeepEqual(got, test.want) { - pretty.Ldiff(t, got, test.want) t.Errorf("ListReposForOrg for %s is %v, want %v", test.name, got, test.want) } }) diff --git a/database/user/get.go b/database/user/get.go index da8b1a0e7..26fa624f4 100644 --- a/database/user/get.go +++ b/database/user/get.go @@ -22,7 +22,7 @@ func (e *engine) GetUser(id int64) (*library.User, error) { Table(constants.TableUser). Where("id = ?", id). Limit(1). - Scan(u). + Take(u). Error if err != nil { return nil, err diff --git a/database/user/get_name.go b/database/user/get_name.go index 2244bfb86..7ba262cde 100644 --- a/database/user/get_name.go +++ b/database/user/get_name.go @@ -25,7 +25,7 @@ func (e *engine) GetUserForName(name string) (*library.User, error) { Table(constants.TableUser). Where("name = ?", name). Limit(1). - Scan(u). + Take(u). Error if err != nil { return nil, err diff --git a/go.mod b/go.mod index 92ca01cbc..0c0edd4cf 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,6 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.1 github.com/hashicorp/vault/api v1.7.2 github.com/joho/godotenv v1.4.0 - github.com/kr/pretty v0.3.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.13.0 github.com/sirupsen/logrus v1.9.0 @@ -98,7 +97,6 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/lib/pq v1.10.6 // indirect github.com/mattn/go-colorable v0.1.8 // indirect @@ -119,7 +117,6 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect - github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect From 68cf3283aeec9f4ead2966ace676077c02f34b42 Mon Sep 17 00:00:00 2001 From: JordanBrockopp Date: Sat, 10 Sep 2022 15:47:36 -0500 Subject: [PATCH 08/16] fix: sort by for listing repos --- api/repo.go | 18 ++++---- api/scm.go | 9 +--- api/user.go | 2 +- database/repo/list_org.go | 4 +- database/repo/list_org_test.go | 76 ++++++++++++++++++++++++++++++--- database/repo/list_user.go | 4 +- database/repo/list_user_test.go | 76 ++++++++++++++++++++++++++++++--- database/repo/service.go | 4 +- 8 files changed, 157 insertions(+), 36 deletions(-) diff --git a/api/repo.go b/api/repo.go index 64d6c8b04..4c7cdb46c 100644 --- a/api/repo.go +++ b/api/repo.go @@ -361,17 +361,18 @@ func GetRepos(c *gin.Context) { // ensure per_page isn't above or below allowed values perPage = util.MaxInt(1, util.MinInt(100, perPage)) + // capture the sort_by query parameter if present + sortBy := util.QueryParameter(c, "sort_by", "name") + // capture the query parameters if present: // // * active - // * sort_by filters := map[string]interface{}{ - "active": util.QueryParameter(c, "active", "true"), - "sort_by": util.QueryParameter(c, "sort_by", "name"), + "active": util.QueryParameter(c, "active", "true"), } // send API call to capture the list of repos for the user - r, t, err := database.FromContext(c).ListReposForUser(u, filters, page, perPage) + r, t, err := database.FromContext(c).ListReposForUser(u, sortBy, filters, page, perPage) if err != nil { retErr := fmt.Errorf("unable to get repos for user %s: %w", u.GetName(), err) @@ -492,13 +493,14 @@ func GetOrgRepos(c *gin.Context) { // ensure per_page isn't above or below allowed values perPage = util.MaxInt(1, util.MinInt(100, perPage)) + // capture the sort_by query parameter if present + sortBy := util.QueryParameter(c, "sort_by", "name") + // capture the query parameters if present: // // * active - // * sort_by filters := map[string]interface{}{ - "active": util.QueryParameter(c, "active", "true"), - "sort_by": util.QueryParameter(c, "sort_by", "name"), + "active": util.QueryParameter(c, "active", "true"), } // See if the user is an org admin to bypass individual permission checks @@ -512,7 +514,7 @@ func GetOrgRepos(c *gin.Context) { } // send API call to capture the list of repos for the org - r, t, err := database.FromContext(c).ListReposForOrg(o, filters, page, perPage) + r, t, err := database.FromContext(c).ListReposForOrg(o, sortBy, filters, page, perPage) if err != nil { retErr := fmt.Errorf("unable to get repos for org %s: %w", o, err) diff --git a/api/scm.go b/api/scm.go index 778ea0419..124aa5d56 100644 --- a/api/scm.go +++ b/api/scm.go @@ -70,12 +70,7 @@ func SyncRepos(c *gin.Context) { logger.Errorf("unable to get user %s access level for org %s", u.GetName(), o) } - // capture the query parameters if present: - // - // * sort_by - filters := map[string]interface{}{ - "sort_by": util.QueryParameter(c, "sort_by", "name"), - } + filters := map[string]interface{}{} // Only show public repos to non-admins if perm != "admin" { filters["visibility"] = constants.VisibilityPublic @@ -95,7 +90,7 @@ func SyncRepos(c *gin.Context) { page := 0 // capture all repos belonging to a certain org in database for orgRepos := int64(0); orgRepos < t; orgRepos += 100 { - r, _, err := database.FromContext(c).ListReposForOrg(o, filters, page, 100) + r, _, err := database.FromContext(c).ListReposForOrg(o, "name", filters, page, 100) if err != nil { retErr := fmt.Errorf("unable to get repo count for org %s: %w", o, err) diff --git a/api/user.go b/api/user.go index f8adf72aa..37c66d82d 100644 --- a/api/user.go +++ b/api/user.go @@ -436,7 +436,7 @@ func GetUserSourceRepos(c *gin.Context) { for page > 0 { // send API call to capture the list of repos for the org - dbReposPart, _, err := database.FromContext(c).ListReposForOrg(org, filters, page, 100) + dbReposPart, _, err := database.FromContext(c).ListReposForOrg(org, "name", filters, page, 100) if err != nil { retErr := fmt.Errorf("unable to get repos for org %s: %w", org, err) diff --git a/database/repo/list_org.go b/database/repo/list_org.go index 1226fd39c..27ecb8b21 100644 --- a/database/repo/list_org.go +++ b/database/repo/list_org.go @@ -14,7 +14,7 @@ import ( // ListReposForOrg gets a list of repos by org name from the database. // // nolint: lll // ignore long line length due to variable names -func (e *engine) ListReposForOrg(org string, filters map[string]interface{}, page, perPage int) ([]*library.Repo, int64, error) { +func (e *engine) ListReposForOrg(org, sortBy string, filters map[string]interface{}, page, perPage int) ([]*library.Repo, int64, error) { e.logger.WithFields(logrus.Fields{ "org": org, }).Tracef("listing repos for org %s from the database", org) @@ -38,7 +38,7 @@ func (e *engine) ListReposForOrg(org string, filters map[string]interface{}, pag // calculate offset for pagination through results offset := perPage * (page - 1) - switch filters["sort_by"] { + switch sortBy { case "latest": query := e.client. Table(constants.TableBuild). diff --git a/database/repo/list_org_test.go b/database/repo/list_org_test.go index b4a13fd8a..fdfa80779 100644 --- a/database/repo/list_org_test.go +++ b/database/repo/list_org_test.go @@ -7,13 +7,28 @@ package repo import ( "reflect" "testing" + "time" "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" "github.com/go-vela/types/library" ) func TestRepo_Engine_ListReposForOrg(t *testing.T) { // setup types + _buildOne := new(library.Build) + _buildOne.SetID(1) + _buildOne.SetRepoID(1) + _buildOne.SetNumber(1) + _buildOne.SetCreated(time.Now().UTC().Unix()) + + _buildTwo := new(library.Build) + _buildTwo.SetID(2) + _buildTwo.SetRepoID(2) + _buildTwo.SetNumber(1) + _buildTwo.SetCreated(time.Now().UTC().Unix()) + _repoOne := testRepo() _repoOne.SetID(1) _repoOne.SetUserID(1) @@ -37,21 +52,36 @@ func TestRepo_Engine_ListReposForOrg(t *testing.T) { _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() - // create expected result in mock + // create expected name count query result in mock _rows := sqlmock.NewRows([]string{"count"}).AddRow(2) - // ensure the mock expects the query + // ensure the mock expects the name count query _mock.ExpectQuery(`SELECT count(*) FROM "repos" WHERE org = $1`).WithArgs("foo").WillReturnRows(_rows) - // create expected result in mock + // create expected name query result in mock _rows = sqlmock.NewRows( []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}). AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil). AddRow(2, 1, "bar", "foo", "baz", "foo/baz", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil) - // ensure the mock expects the query + // ensure the mock expects the name query _mock.ExpectQuery(`SELECT * FROM "repos" WHERE org = $1 ORDER BY name LIMIT 10`).WithArgs("foo").WillReturnRows(_rows) + // create expected latest count query result in mock + _rows = sqlmock.NewRows([]string{"count"}).AddRow(2) + + // ensure the mock expects the latest count query + _mock.ExpectQuery(`SELECT count(*) FROM "repos" WHERE org = $1`).WithArgs("foo").WillReturnRows(_rows) + + // create expected latest query result in mock + _rows = sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil). + AddRow(2, 1, "bar", "foo", "baz", "foo/baz", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil) + + // ensure the mock expects the latest query + _mock.ExpectQuery(`SELECT repos.* FROM "repos" LEFT JOIN (SELECT repos.id, MAX(builds.created) AS latest_build FROM "builds" INNER JOIN repos repos ON builds.repo_id = repos.id WHERE repos.org = $1 GROUP BY "repos"."id") t on repos.id = t.id ORDER BY latest_build DESC NULLS LAST LIMIT 10`).WithArgs("foo").WillReturnRows(_rows) + _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -65,23 +95,55 @@ func TestRepo_Engine_ListReposForOrg(t *testing.T) { t.Errorf("unable to create test repo for sqlite: %v", err) } + err = _sqlite.client.AutoMigrate(&database.Build{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableBuild).Create(database.BuildFromLibrary(_buildOne).Crop()).Error + if err != nil { + t.Errorf("unable to create test build for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableBuild).Create(database.BuildFromLibrary(_buildTwo).Crop()).Error + if err != nil { + t.Errorf("unable to create test build for sqlite: %v", err) + } + // setup tests tests := []struct { failure bool name string + sort string database *engine want []*library.Repo }{ { failure: false, - name: "postgres", + name: "postgres with name", + database: _postgres, + sort: "name", + want: []*library.Repo{_repoOne, _repoTwo}, + }, + { + failure: false, + name: "postgres with latest", database: _postgres, + sort: "latest", + want: []*library.Repo{_repoOne, _repoTwo}, + }, + { + failure: false, + name: "sqlite with name", + database: _sqlite, + sort: "name", want: []*library.Repo{_repoOne, _repoTwo}, }, { failure: false, - name: "sqlite3", + name: "sqlite with latest", database: _sqlite, + sort: "latest", want: []*library.Repo{_repoOne, _repoTwo}, }, } @@ -91,7 +153,7 @@ func TestRepo_Engine_ListReposForOrg(t *testing.T) { // run tests for _, test := range tests { t.Run(test.name, func(t *testing.T) { - got, _, err := test.database.ListReposForOrg("foo", filters, 1, 10) + got, _, err := test.database.ListReposForOrg("foo", test.sort, filters, 1, 10) if test.failure { if err == nil { diff --git a/database/repo/list_user.go b/database/repo/list_user.go index b16ea3e11..0b17e30e6 100644 --- a/database/repo/list_user.go +++ b/database/repo/list_user.go @@ -14,7 +14,7 @@ import ( // ListReposForUser gets a list of repos by user ID from the database. // // nolint: lll // ignore long line length due to variable names -func (e *engine) ListReposForUser(u *library.User, filters map[string]interface{}, page, perPage int) ([]*library.Repo, int64, error) { +func (e *engine) ListReposForUser(u *library.User, sortBy string, filters map[string]interface{}, page, perPage int) ([]*library.Repo, int64, error) { e.logger.WithFields(logrus.Fields{ "user": u.GetName(), }).Tracef("listing repos for user %s from the database", u.GetName()) @@ -38,7 +38,7 @@ func (e *engine) ListReposForUser(u *library.User, filters map[string]interface{ // calculate offset for pagination through results offset := perPage * (page - 1) - switch filters["sort_by"] { + switch sortBy { case "latest": query := e.client. Table(constants.TableBuild). diff --git a/database/repo/list_user_test.go b/database/repo/list_user_test.go index 0c7322252..0e1cc8336 100644 --- a/database/repo/list_user_test.go +++ b/database/repo/list_user_test.go @@ -7,13 +7,28 @@ package repo import ( "reflect" "testing" + "time" "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" "github.com/go-vela/types/library" ) func TestRepo_Engine_ListReposForUser(t *testing.T) { // setup types + _buildOne := new(library.Build) + _buildOne.SetID(1) + _buildOne.SetRepoID(1) + _buildOne.SetNumber(1) + _buildOne.SetCreated(time.Now().UTC().Unix()) + + _buildTwo := new(library.Build) + _buildTwo.SetID(2) + _buildTwo.SetRepoID(2) + _buildTwo.SetNumber(1) + _buildTwo.SetCreated(time.Now().UTC().Unix()) + _repoOne := testRepo() _repoOne.SetID(1) _repoOne.SetUserID(1) @@ -42,21 +57,36 @@ func TestRepo_Engine_ListReposForUser(t *testing.T) { _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() - // create expected result in mock + // create expected name count query result in mock _rows := sqlmock.NewRows([]string{"count"}).AddRow(2) - // ensure the mock expects the query + // ensure the mock expects the name count query _mock.ExpectQuery(`SELECT count(*) FROM "repos" WHERE user_id = $1`).WithArgs(1).WillReturnRows(_rows) - // create expected result in mock + // create expected name query result in mock _rows = sqlmock.NewRows( []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}). AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil). AddRow(2, 1, "baz", "bar", "foo", "bar/foo", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil) - // ensure the mock expects the query + // ensure the mock expects the name query _mock.ExpectQuery(`SELECT * FROM "repos" WHERE user_id = $1 ORDER BY name LIMIT 10`).WithArgs(1).WillReturnRows(_rows) + // create expected latest count query result in mock + _rows = sqlmock.NewRows([]string{"count"}).AddRow(2) + + // ensure the mock expects the latest count query + _mock.ExpectQuery(`SELECT count(*) FROM "repos" WHERE user_id = $1`).WithArgs(1).WillReturnRows(_rows) + + // create expected latest query result in mock + _rows = sqlmock.NewRows( + []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}). + AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil). + AddRow(2, 1, "baz", "bar", "foo", "bar/foo", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil) + + // ensure the mock expects the latest query + _mock.ExpectQuery(`SELECT repos.* FROM "repos" LEFT JOIN (SELECT repos.id, MAX(builds.created) AS latest_build FROM "builds" INNER JOIN repos repos ON builds.repo_id = repos.id WHERE repos.user_id = $1 GROUP BY "repos"."id") t on repos.id = t.id ORDER BY latest_build DESC NULLS LAST LIMIT 10`).WithArgs(1).WillReturnRows(_rows) + _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() @@ -70,23 +100,55 @@ func TestRepo_Engine_ListReposForUser(t *testing.T) { t.Errorf("unable to create test repo for sqlite: %v", err) } + err = _sqlite.client.AutoMigrate(&database.Build{}) + if err != nil { + t.Errorf("unable to create build table for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableBuild).Create(database.BuildFromLibrary(_buildOne).Crop()).Error + if err != nil { + t.Errorf("unable to create test build for sqlite: %v", err) + } + + err = _sqlite.client.Table(constants.TableBuild).Create(database.BuildFromLibrary(_buildTwo).Crop()).Error + if err != nil { + t.Errorf("unable to create test build for sqlite: %v", err) + } + // setup tests tests := []struct { failure bool name string + sort string database *engine want []*library.Repo }{ { failure: false, - name: "postgres", + name: "postgres with name", + database: _postgres, + sort: "name", + want: []*library.Repo{_repoOne, _repoTwo}, + }, + { + failure: false, + name: "postgres with latest", database: _postgres, + sort: "latest", + want: []*library.Repo{_repoOne, _repoTwo}, + }, + { + failure: false, + name: "sqlite with name", + database: _sqlite, + sort: "name", want: []*library.Repo{_repoOne, _repoTwo}, }, { failure: false, - name: "sqlite3", + name: "sqlite with latest", database: _sqlite, + sort: "latest", want: []*library.Repo{_repoOne, _repoTwo}, }, } @@ -96,7 +158,7 @@ func TestRepo_Engine_ListReposForUser(t *testing.T) { // run tests for _, test := range tests { t.Run(test.name, func(t *testing.T) { - got, _, err := test.database.ListReposForUser(_user, filters, 1, 10) + got, _, err := test.database.ListReposForUser(_user, test.sort, filters, 1, 10) if test.failure { if err == nil { diff --git a/database/repo/service.go b/database/repo/service.go index 237e41027..ba0d962c4 100644 --- a/database/repo/service.go +++ b/database/repo/service.go @@ -43,9 +43,9 @@ type RepoService interface { // ListRepos defines a function that gets a list of all repos. ListRepos() ([]*library.Repo, error) // ListReposForOrg defines a function that gets a list of repos by org name. - ListReposForOrg(string, map[string]interface{}, int, int) ([]*library.Repo, int64, error) + ListReposForOrg(string, string, map[string]interface{}, int, int) ([]*library.Repo, int64, error) // ListReposForUser defines a function that gets a list of repos by user ID. - ListReposForUser(*library.User, map[string]interface{}, int, int) ([]*library.Repo, int64, error) + ListReposForUser(*library.User, string, map[string]interface{}, int, int) ([]*library.Repo, int64, error) // UpdateRepo defines a function that updates an existing repo. UpdateRepo(*library.Repo) error } From 8e628be428efae2d904f26d9089590b38bfd4082 Mon Sep 17 00:00:00 2001 From: JordanBrockopp Date: Sat, 10 Sep 2022 22:54:28 -0500 Subject: [PATCH 09/16] feat(database): add worker engine --- database/worker/count.go | 25 ++++ database/worker/count_test.go | 93 ++++++++++++++ database/worker/create.go | 39 ++++++ database/worker/create_test.go | 73 +++++++++++ database/worker/delete.go | 30 +++++ database/worker/delete_test.go | 73 +++++++++++ database/worker/get.go | 35 ++++++ database/worker/get_hostname.go | 38 ++++++ database/worker/get_hostname_test.go | 85 +++++++++++++ database/worker/get_test.go | 85 +++++++++++++ database/worker/index.go | 24 ++++ database/worker/index_test.go | 59 +++++++++ database/worker/list.go | 54 ++++++++ database/worker/list_test.go | 103 +++++++++++++++ database/worker/opts.go | 44 +++++++ database/worker/opts_test.go | 161 ++++++++++++++++++++++++ database/worker/service.go | 43 +++++++ database/worker/table.go | 60 +++++++++ database/worker/table_test.go | 59 +++++++++ database/worker/update.go | 39 ++++++ database/worker/update_test.go | 75 +++++++++++ database/worker/worker.go | 80 ++++++++++++ database/worker/worker_test.go | 181 +++++++++++++++++++++++++++ 23 files changed, 1558 insertions(+) create mode 100644 database/worker/count.go create mode 100644 database/worker/count_test.go create mode 100644 database/worker/create.go create mode 100644 database/worker/create_test.go create mode 100644 database/worker/delete.go create mode 100644 database/worker/delete_test.go create mode 100644 database/worker/get.go create mode 100644 database/worker/get_hostname.go create mode 100644 database/worker/get_hostname_test.go create mode 100644 database/worker/get_test.go create mode 100644 database/worker/index.go create mode 100644 database/worker/index_test.go create mode 100644 database/worker/list.go create mode 100644 database/worker/list_test.go create mode 100644 database/worker/opts.go create mode 100644 database/worker/opts_test.go create mode 100644 database/worker/service.go create mode 100644 database/worker/table.go create mode 100644 database/worker/table_test.go create mode 100644 database/worker/update.go create mode 100644 database/worker/update_test.go create mode 100644 database/worker/worker.go create mode 100644 database/worker/worker_test.go diff --git a/database/worker/count.go b/database/worker/count.go new file mode 100644 index 000000000..8ac0f3eb5 --- /dev/null +++ b/database/worker/count.go @@ -0,0 +1,25 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "github.com/go-vela/types/constants" +) + +// CountWorkers gets the count of all workers from the database. +func (e *engine) CountWorkers() (int64, error) { + e.logger.Tracef("getting count of all workers from the database") + + // variable to store query results + var w int64 + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableWorker). + Count(&w). + Error + + return w, err +} diff --git a/database/worker/count_test.go b/database/worker/count_test.go new file mode 100644 index 000000000..bd9d4c4ac --- /dev/null +++ b/database/worker/count_test.go @@ -0,0 +1,93 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestWorker_Engine_CountWorkers(t *testing.T) { + // setup types + _workerOne := testWorker() + _workerOne.SetID(1) + _workerOne.SetHostname("worker_0") + _workerOne.SetAddress("localhost") + _workerOne.SetActive(true) + + _workerTwo := testWorker() + _workerTwo.SetID(2) + _workerTwo.SetHostname("worker_1") + _workerTwo.SetAddress("localhost") + _workerTwo.SetActive(true) + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"count"}).AddRow(2) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT count(*) FROM "workers"`).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateWorker(_workerOne) + if err != nil { + t.Errorf("unable to create test worker for sqlite: %v", err) + } + + err = _sqlite.CreateWorker(_workerTwo) + if err != nil { + t.Errorf("unable to create test worker for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want int64 + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: 2, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: 2, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.CountWorkers() + + if test.failure { + if err == nil { + t.Errorf("CountWorkers for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CountWorkers for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("CountWorkers for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/worker/create.go b/database/worker/create.go new file mode 100644 index 000000000..13e23edbc --- /dev/null +++ b/database/worker/create.go @@ -0,0 +1,39 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +// nolint: dupl // ignore similar code in update.go +package worker + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// CreateWorker creates a new worker in the database. +func (e *engine) CreateWorker(w *library.Worker) error { + e.logger.WithFields(logrus.Fields{ + "worker": w.GetHostname(), + }).Tracef("creating worker %s in the database", w.GetHostname()) + + // cast the library type to database type + // + // https://pkg.go.dev/github.com/go-vela/types/database#WorkerFromLibrary + worker := database.WorkerFromLibrary(w) + + // validate the necessary fields are populated + // + // https://pkg.go.dev/github.com/go-vela/types/database#Worker.Validate + err := worker.Validate() + if err != nil { + return err + } + + // send query to the database + return e.client. + Table(constants.TableWorker). + Create(worker). + Error +} diff --git a/database/worker/create_test.go b/database/worker/create_test.go new file mode 100644 index 000000000..38c276d83 --- /dev/null +++ b/database/worker/create_test.go @@ -0,0 +1,73 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestWorker_Engine_CreateWorker(t *testing.T) { + // setup types + _worker := testWorker() + _worker.SetID(1) + _worker.SetHostname("worker_0") + _worker.SetAddress("localhost") + _worker.SetActive(true) + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"id"}).AddRow(1) + + // ensure the mock expects the query + _mock.ExpectQuery(`INSERT INTO "workers" +("hostname","address","routes","active","last_checked_in","build_limit","id") +VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING "id"`). + WithArgs("worker_0", "localhost", nil, true, nil, nil, 1). + WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.database.CreateWorker(_worker) + + if test.failure { + if err == nil { + t.Errorf("CreateWorker for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateWorker for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/worker/delete.go b/database/worker/delete.go new file mode 100644 index 000000000..a04ebb13e --- /dev/null +++ b/database/worker/delete.go @@ -0,0 +1,30 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// DeleteWorker deletes an existing worker from the database. +func (e *engine) DeleteWorker(w *library.Worker) error { + e.logger.WithFields(logrus.Fields{ + "worker": w.GetHostname(), + }).Tracef("deleting worker %s from the database", w.GetHostname()) + + // cast the library type to database type + // + // https://pkg.go.dev/github.com/go-vela/types/database#WorkerFromLibrary + worker := database.WorkerFromLibrary(w) + + // send query to the database + return e.client. + Table(constants.TableWorker). + Delete(worker). + Error +} diff --git a/database/worker/delete_test.go b/database/worker/delete_test.go new file mode 100644 index 000000000..c8a9bd1be --- /dev/null +++ b/database/worker/delete_test.go @@ -0,0 +1,73 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestWorker_Engine_DeleteWorker(t *testing.T) { + // setup types + _worker := testWorker() + _worker.SetID(1) + _worker.SetHostname("worker_0") + _worker.SetAddress("localhost") + _worker.SetActive(true) + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // ensure the mock expects the query + _mock.ExpectExec(`DELETE FROM "workers" WHERE "workers"."id" = $1`). + WithArgs(1). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateWorker(_worker) + if err != nil { + t.Errorf("unable to create test worker for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err = test.database.DeleteWorker(_worker) + + if test.failure { + if err == nil { + t.Errorf("DeleteWorker for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("DeleteWorker for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/worker/get.go b/database/worker/get.go new file mode 100644 index 000000000..c567cafd7 --- /dev/null +++ b/database/worker/get.go @@ -0,0 +1,35 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" +) + +// GetWorker gets a worker by ID from the database. +func (e *engine) GetWorker(id int64) (*library.Worker, error) { + e.logger.Tracef("getting worker %d from the database", id) + + // variable to store query results + w := new(database.Worker) + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableWorker). + Where("id = ?", id). + Limit(1). + Take(w). + Error + if err != nil { + return nil, err + } + + // return the worker + // + // https://pkg.go.dev/github.com/go-vela/types/database#Worker.ToLibrary + return w.ToLibrary(), nil +} diff --git a/database/worker/get_hostname.go b/database/worker/get_hostname.go new file mode 100644 index 000000000..bf4227dcb --- /dev/null +++ b/database/worker/get_hostname.go @@ -0,0 +1,38 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// GetWorkerForHostname gets a worker by hostname from the database. +func (e *engine) GetWorkerForHostname(hostname string) (*library.Worker, error) { + e.logger.WithFields(logrus.Fields{ + "worker": hostname, + }).Tracef("getting worker %s from the database", hostname) + + // variable to store query results + w := new(database.Worker) + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableWorker). + Where("hostname = ?", hostname). + Limit(1). + Take(w). + Error + if err != nil { + return nil, err + } + + // return the worker + // + // https://pkg.go.dev/github.com/go-vela/types/database#Worker.ToLibrary + return w.ToLibrary(), nil +} diff --git a/database/worker/get_hostname_test.go b/database/worker/get_hostname_test.go new file mode 100644 index 000000000..833afdc15 --- /dev/null +++ b/database/worker/get_hostname_test.go @@ -0,0 +1,85 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" +) + +func TestWorker_Engine_GetWorkerForName(t *testing.T) { + // setup types + _worker := testWorker() + _worker.SetID(1) + _worker.SetHostname("worker_0") + _worker.SetAddress("localhost") + _worker.SetActive(true) + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows( + []string{"id", "hostname", "address", "routes", "active", "last_checked_in", "build_limit"}). + AddRow(1, "worker_0", "localhost", nil, true, 0, 0) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "workers" WHERE hostname = $1 LIMIT 1`).WithArgs("worker_0").WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateWorker(_worker) + if err != nil { + t.Errorf("unable to create test worker for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want *library.Worker + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: _worker, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: _worker, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.GetWorkerForHostname("worker_0") + + if test.failure { + if err == nil { + t.Errorf("GetWorkerForHostname for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("GetWorkerForHostname for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("GetWorkerForHostname for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/worker/get_test.go b/database/worker/get_test.go new file mode 100644 index 000000000..17fd03739 --- /dev/null +++ b/database/worker/get_test.go @@ -0,0 +1,85 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" +) + +func TestWorker_Engine_GetWorker(t *testing.T) { + // setup types + _worker := testWorker() + _worker.SetID(1) + _worker.SetHostname("worker_0") + _worker.SetAddress("localhost") + _worker.SetActive(true) + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows( + []string{"id", "hostname", "address", "routes", "active", "last_checked_in", "build_limit"}). + AddRow(1, "worker_0", "localhost", nil, true, 0, 0) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "workers" WHERE id = $1 LIMIT 1`).WithArgs(1).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateWorker(_worker) + if err != nil { + t.Errorf("unable to create test worker for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want *library.Worker + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: _worker, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: _worker, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.GetWorker(1) + + if test.failure { + if err == nil { + t.Errorf("GetWorker for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("GetWorker for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("GetWorker for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/worker/index.go b/database/worker/index.go new file mode 100644 index 000000000..8fc79da3d --- /dev/null +++ b/database/worker/index.go @@ -0,0 +1,24 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +const ( + // CreateWorkerHostnameAddressIndex represents a query to create an + // index on the workers table for the hostname and address columns. + CreateWorkerHostnameAddressIndex = ` +CREATE INDEX +IF NOT EXISTS +workers_hostname_address +ON workers (hostname, address); +` +) + +// CreateWorkerIndexes creates the indexes for the workers table in the database. +func (e *engine) CreateWorkerIndexes() error { + e.logger.Tracef("creating indexes for workers table in the database") + + // create the hostname and address columns index for the workers table + return e.client.Exec(CreateWorkerHostnameAddressIndex).Error +} diff --git a/database/worker/index_test.go b/database/worker/index_test.go new file mode 100644 index 000000000..76f5cbb94 --- /dev/null +++ b/database/worker/index_test.go @@ -0,0 +1,59 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestWorker_Engine_CreateWorkerIndexes(t *testing.T) { + // setup types + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + _mock.ExpectExec(CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.database.CreateWorkerIndexes() + + if test.failure { + if err == nil { + t.Errorf("CreateWorkerIndexes for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateWorkerIndexes for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/worker/list.go b/database/worker/list.go new file mode 100644 index 000000000..4ec11ef3d --- /dev/null +++ b/database/worker/list.go @@ -0,0 +1,54 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" +) + +// ListWorkers gets a list of all workers from the database. +func (e *engine) ListWorkers() ([]*library.Worker, error) { + e.logger.Trace("listing all workers from the database") + + // variables to store query results and return value + count := int64(0) + w := new([]database.Worker) + workers := []*library.Worker{} + + // count the results + count, err := e.CountWorkers() + if err != nil { + return nil, err + } + + // short-circuit if there are no results + if count == 0 { + return workers, nil + } + + // send query to the database and store result in variable + err = e.client. + Table(constants.TableWorker). + Find(&w). + Error + if err != nil { + return nil, err + } + + // iterate through all query results + for _, worker := range *w { + // https://golang.org/doc/faq#closures_and_goroutines + tmp := worker + + // convert query result to library type + // + // https://pkg.go.dev/github.com/go-vela/types/database#Worker.ToLibrary + workers = append(workers, tmp.ToLibrary()) + } + + return workers, nil +} diff --git a/database/worker/list_test.go b/database/worker/list_test.go new file mode 100644 index 000000000..b44c9c3d9 --- /dev/null +++ b/database/worker/list_test.go @@ -0,0 +1,103 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" +) + +func TestWorker_Engine_ListWorkers(t *testing.T) { + // setup types + _workerOne := testWorker() + _workerOne.SetID(1) + _workerOne.SetHostname("worker_0") + _workerOne.SetAddress("localhost") + _workerOne.SetActive(true) + + _workerTwo := testWorker() + _workerTwo.SetID(2) + _workerTwo.SetHostname("worker_1") + _workerTwo.SetAddress("localhost") + _workerTwo.SetActive(true) + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"count"}).AddRow(2) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT count(*) FROM "workers"`).WillReturnRows(_rows) + + // create expected result in mock + _rows = sqlmock.NewRows( + []string{"id", "hostname", "address", "routes", "active", "last_checked_in", "build_limit"}). + AddRow(1, "worker_0", "localhost", nil, true, 0, 0). + AddRow(2, "worker_1", "localhost", nil, true, 0, 0) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "workers"`).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateWorker(_workerOne) + if err != nil { + t.Errorf("unable to create test worker for sqlite: %v", err) + } + + err = _sqlite.CreateWorker(_workerTwo) + if err != nil { + t.Errorf("unable to create test worker for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want []*library.Worker + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: []*library.Worker{_workerOne, _workerTwo}, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: []*library.Worker{_workerOne, _workerTwo}, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.ListWorkers() + + if test.failure { + if err == nil { + t.Errorf("ListWorkers for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("ListWorkers for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("ListWorkers for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/worker/opts.go b/database/worker/opts.go new file mode 100644 index 000000000..c9891ba94 --- /dev/null +++ b/database/worker/opts.go @@ -0,0 +1,44 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "github.com/sirupsen/logrus" + + "gorm.io/gorm" +) + +// EngineOpt represents a configuration option to initialize the database engine for Workers. +type EngineOpt func(*engine) error + +// WithClient sets the gorm.io/gorm client in the database engine for Workers. +func WithClient(client *gorm.DB) EngineOpt { + return func(e *engine) error { + // set the gorm.io/gorm client in the worker engine + e.client = client + + return nil + } +} + +// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for Workers. +func WithLogger(logger *logrus.Entry) EngineOpt { + return func(e *engine) error { + // set the github.com/sirupsen/logrus logger in the worker engine + e.logger = logger + + return nil + } +} + +// WithSkipCreation sets the skip creation logic in the database engine for Workers. +func WithSkipCreation(skipCreation bool) EngineOpt { + return func(e *engine) error { + // set to skip creating tables and indexes in the worker engine + e.config.SkipCreation = skipCreation + + return nil + } +} diff --git a/database/worker/opts_test.go b/database/worker/opts_test.go new file mode 100644 index 000000000..a0ebf6aa5 --- /dev/null +++ b/database/worker/opts_test.go @@ -0,0 +1,161 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "reflect" + "testing" + + "github.com/sirupsen/logrus" + + "gorm.io/gorm" +) + +func TestWorker_EngineOpt_WithClient(t *testing.T) { + // setup types + e := &engine{client: new(gorm.DB)} + + // setup tests + tests := []struct { + failure bool + name string + client *gorm.DB + want *gorm.DB + }{ + { + failure: false, + name: "client set to new database", + client: new(gorm.DB), + want: new(gorm.DB), + }, + { + failure: false, + name: "client set to nil", + client: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithClient(test.client)(e) + + if test.failure { + if err == nil { + t.Errorf("WithClient for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithClient returned err: %v", err) + } + + if !reflect.DeepEqual(e.client, test.want) { + t.Errorf("WithClient is %v, want %v", e.client, test.want) + } + }) + } +} + +func TestWorker_EngineOpt_WithLogger(t *testing.T) { + // setup types + e := &engine{logger: new(logrus.Entry)} + + // setup tests + tests := []struct { + failure bool + name string + logger *logrus.Entry + want *logrus.Entry + }{ + { + failure: false, + name: "logger set to new entry", + logger: new(logrus.Entry), + want: new(logrus.Entry), + }, + { + failure: false, + name: "logger set to nil", + logger: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithLogger(test.logger)(e) + + if test.failure { + if err == nil { + t.Errorf("WithLogger for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithLogger returned err: %v", err) + } + + if !reflect.DeepEqual(e.logger, test.want) { + t.Errorf("WithLogger is %v, want %v", e.logger, test.want) + } + }) + } +} + +func TestWorker_EngineOpt_WithSkipCreation(t *testing.T) { + // setup types + e := &engine{config: new(config)} + + // setup tests + tests := []struct { + failure bool + name string + skipCreation bool + want bool + }{ + { + failure: false, + name: "skip creation set to true", + skipCreation: true, + want: true, + }, + { + failure: false, + name: "skip creation set to false", + skipCreation: false, + want: false, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := WithSkipCreation(test.skipCreation)(e) + + if test.failure { + if err == nil { + t.Errorf("WithSkipCreation for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("WithSkipCreation returned err: %v", err) + } + + if !reflect.DeepEqual(e.config.SkipCreation, test.want) { + t.Errorf("WithSkipCreation is %v, want %v", e.config.SkipCreation, test.want) + } + }) + } +} diff --git a/database/worker/service.go b/database/worker/service.go new file mode 100644 index 000000000..523764b34 --- /dev/null +++ b/database/worker/service.go @@ -0,0 +1,43 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "github.com/go-vela/types/library" +) + +// WorkerService represents the Vela interface for worker +// functions with the supported Database backends. +// +// nolint: revive // ignore name stutter +type WorkerService interface { + // Worker Data Definition Language Functions + // + // https://en.wikipedia.org/wiki/Data_definition_language + + // CreateWorkerIndexes defines a function that creates the indexes for the workers table. + CreateWorkerIndexes() error + // CreateWorkerTable defines a function that creates the workers table. + CreateWorkerTable(string) error + + // Worker Data Manipulation Language Functions + // + // https://en.wikipedia.org/wiki/Data_manipulation_language + + // CountWorkers defines a function that gets the count of all workers. + CountWorkers() (int64, error) + // CreateWorker defines a function that creates a new worker. + CreateWorker(*library.Worker) error + // DeleteWorker defines a function that deletes an existing worker. + DeleteWorker(*library.Worker) error + // GetWorker defines a function that gets a worker by ID. + GetWorker(int64) (*library.Worker, error) + // GetWorkerForHostname defines a function that gets a worker by hostname. + GetWorkerForHostname(string) (*library.Worker, error) + // ListWorkers defines a function that gets a list of all workers. + ListWorkers() ([]*library.Worker, error) + // UpdateWorker defines a function that updates an existing worker. + UpdateWorker(*library.Worker) error +} diff --git a/database/worker/table.go b/database/worker/table.go new file mode 100644 index 000000000..5cecf109d --- /dev/null +++ b/database/worker/table.go @@ -0,0 +1,60 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "github.com/go-vela/types/constants" +) + +const ( + // CreatePostgresTable represents a query to create the Postgres workers table. + CreatePostgresTable = ` +CREATE TABLE +IF NOT EXISTS +workers ( + id SERIAL PRIMARY KEY, + hostname VARCHAR(250), + address VARCHAR(250), + routes VARCHAR(1000), + active BOOLEAN, + last_checked_in INTEGER, + build_limit INTEGER, + UNIQUE(hostname) +); +` + + // CreateSqliteTable represents a query to create the Sqlite workers table. + CreateSqliteTable = ` +CREATE TABLE +IF NOT EXISTS +workers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + hostname TEXT, + address TEXT, + routes TEXT, + active BOOLEAN, + last_checked_in INTEGER, + build_limit INTEGER, + UNIQUE(hostname) +); +` +) + +// CreateWorkerTable creates the workers table in the database. +func (e *engine) CreateWorkerTable(driver string) error { + e.logger.Tracef("creating workers table in the database") + + // handle the driver provided to create the table + switch driver { + case constants.DriverPostgres: + // create the workers table for Postgres + return e.client.Exec(CreatePostgresTable).Error + case constants.DriverSqlite: + fallthrough + default: + // create the workers table for Sqlite + return e.client.Exec(CreateSqliteTable).Error + } +} diff --git a/database/worker/table_test.go b/database/worker/table_test.go new file mode 100644 index 000000000..681a267f2 --- /dev/null +++ b/database/worker/table_test.go @@ -0,0 +1,59 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestWorker_Engine_CreateWorkerTable(t *testing.T) { + // setup types + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.database.CreateWorkerTable(test.name) + + if test.failure { + if err == nil { + t.Errorf("CreateWorkerTable for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateWorkerTable for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/worker/update.go b/database/worker/update.go new file mode 100644 index 000000000..06f07eb57 --- /dev/null +++ b/database/worker/update.go @@ -0,0 +1,39 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +// nolint: dupl // ignore similar code in create.go +package worker + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// UpdateWorker updates an existing worker in the database. +func (e *engine) UpdateWorker(w *library.Worker) error { + e.logger.WithFields(logrus.Fields{ + "worker": w.GetHostname(), + }).Tracef("updating worker %s in the database", w.GetHostname()) + + // cast the library type to database type + // + // https://pkg.go.dev/github.com/go-vela/types/database#WorkerFromLibrary + worker := database.WorkerFromLibrary(w) + + // validate the necessary fields are populated + // + // https://pkg.go.dev/github.com/go-vela/types/database#Worker.Validate + err := worker.Validate() + if err != nil { + return err + } + + // send query to the database + return e.client. + Table(constants.TableWorker). + Save(worker). + Error +} diff --git a/database/worker/update_test.go b/database/worker/update_test.go new file mode 100644 index 000000000..88644678a --- /dev/null +++ b/database/worker/update_test.go @@ -0,0 +1,75 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestWorker_Engine_UpdateWorker(t *testing.T) { + // setup types + _worker := testWorker() + _worker.SetID(1) + _worker.SetHostname("worker_0") + _worker.SetAddress("localhost") + _worker.SetActive(true) + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // ensure the mock expects the query + _mock.ExpectExec(`UPDATE "workers" +SET "hostname"=$1,"address"=$2,"routes"=$3,"active"=$4,"last_checked_in"=$5,"build_limit"=$6 +WHERE "id" = $7`). + WithArgs("worker_0", "localhost", nil, true, nil, nil, 1). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateWorker(_worker) + if err != nil { + t.Errorf("unable to create test worker for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + }{ + { + failure: false, + name: "postgres", + database: _postgres, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err = test.database.UpdateWorker(_worker) + + if test.failure { + if err == nil { + t.Errorf("UpdateWorker for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("UpdateWorker for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/worker/worker.go b/database/worker/worker.go new file mode 100644 index 000000000..fed6f81f2 --- /dev/null +++ b/database/worker/worker.go @@ -0,0 +1,80 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "fmt" + + "github.com/go-vela/types/constants" + "github.com/sirupsen/logrus" + + "gorm.io/gorm" +) + +type ( + // config represents the settings required to create the engine that implements the WorkerService interface. + config struct { + // specifies to skip creating tables and indexes for the Worker engine + SkipCreation bool + } + + // engine represents the worker functionality that implements the WorkerService interface. + engine struct { + // engine configuration settings used in worker functions + config *config + + // gorm.io/gorm database client used in worker functions + // + // https://pkg.go.dev/gorm.io/gorm#DB + client *gorm.DB + + // sirupsen/logrus logger used in worker functions + // + // https://pkg.go.dev/github.com/sirupsen/logrus#Entry + logger *logrus.Entry + } +) + +// New creates and returns a Vela service for integrating with workers in the database. +// +// nolint: revive // ignore returning unexported engine +func New(opts ...EngineOpt) (*engine, error) { + // create new Worker engine + e := new(engine) + + // create new fields + e.client = new(gorm.DB) + e.config = new(config) + e.logger = new(logrus.Entry) + + // apply all provided configuration options + for _, opt := range opts { + err := opt(e) + if err != nil { + return nil, err + } + } + + // check if we should skip creating worker database objects + if e.config.SkipCreation { + e.logger.Warning("skipping creation of workers table and indexes in the database") + + return e, nil + } + + // create the workers table + err := e.CreateWorkerTable(e.client.Config.Dialector.Name()) + if err != nil { + return nil, fmt.Errorf("unable to create %s table: %w", constants.TableWorker, err) + } + + // create the indexes for the workers table + err = e.CreateWorkerIndexes() + if err != nil { + return nil, fmt.Errorf("unable to create indexes for %s table: %w", constants.TableWorker, err) + } + + return e, nil +} diff --git a/database/worker/worker_test.go b/database/worker/worker_test.go new file mode 100644 index 000000000..bc23afb5d --- /dev/null +++ b/database/worker/worker_test.go @@ -0,0 +1,181 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package worker + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" + + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func TestWorker_New(t *testing.T) { + // setup types + logger := logrus.NewEntry(logrus.StandardLogger()) + + _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + if err != nil { + t.Errorf("unable to create new SQL mock: %v", err) + } + defer _sql.Close() + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + + _config := &gorm.Config{SkipDefaultTransaction: true} + + _postgres, err := gorm.Open(postgres.New(postgres.Config{Conn: _sql}), _config) + if err != nil { + t.Errorf("unable to create new postgres database: %v", err) + } + + _sqlite, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), _config) + if err != nil { + t.Errorf("unable to create new sqlite database: %v", err) + } + + defer func() { _sql, _ := _sqlite.DB(); _sql.Close() }() + + // setup tests + tests := []struct { + failure bool + name string + client *gorm.DB + key string + logger *logrus.Entry + skipCreation bool + want *engine + }{ + { + failure: false, + name: "postgres", + client: _postgres, + logger: logger, + skipCreation: false, + want: &engine{ + client: _postgres, + config: &config{SkipCreation: false}, + logger: logger, + }, + }, + { + failure: false, + name: "sqlite3", + client: _sqlite, + logger: logger, + skipCreation: false, + want: &engine{ + client: _sqlite, + config: &config{SkipCreation: false}, + logger: logger, + }, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := New( + WithClient(test.client), + WithLogger(test.logger), + WithSkipCreation(test.skipCreation), + ) + + if test.failure { + if err == nil { + t.Errorf("New for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("New for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("New for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} + +// testPostgres is a helper function to create a Postgres engine for testing. +func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) { + // create the new mock sql database + // + // https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#New + _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + if err != nil { + t.Errorf("unable to create new SQL mock: %v", err) + } + + _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + + // create the new mock Postgres database client + // + // https://pkg.go.dev/gorm.io/gorm#Open + _postgres, err := gorm.Open( + postgres.New(postgres.Config{Conn: _sql}), + &gorm.Config{SkipDefaultTransaction: true}, + ) + if err != nil { + t.Errorf("unable to create new postgres database: %v", err) + } + + _engine, err := New( + WithClient(_postgres), + WithLogger(logrus.NewEntry(logrus.StandardLogger())), + WithSkipCreation(false), + ) + if err != nil { + t.Errorf("unable to create new postgres worker engine: %v", err) + } + + return _engine, _mock +} + +// testSqlite is a helper function to create a Sqlite engine for testing. +func testSqlite(t *testing.T) *engine { + _sqlite, err := gorm.Open( + sqlite.Open("file::memory:?cache=shared"), + &gorm.Config{SkipDefaultTransaction: true}, + ) + if err != nil { + t.Errorf("unable to create new sqlite database: %v", err) + } + + _engine, err := New( + WithClient(_sqlite), + WithLogger(logrus.NewEntry(logrus.StandardLogger())), + WithSkipCreation(false), + ) + if err != nil { + t.Errorf("unable to create new sqlite worker engine: %v", err) + } + + return _engine +} + +// testWorker is a test helper function to create a library +// Worker type with all fields set to their zero values. +func testWorker() *library.Worker { + return &library.Worker{ + ID: new(int64), + Hostname: new(string), + Address: new(string), + Routes: new([]string), + Active: new(bool), + BuildLimit: new(int64), + LastCheckedIn: new(int64), + } +} From a8277905e0c5596388535f75d1515cf3e6095bf5 Mon Sep 17 00:00:00 2001 From: JordanBrockopp Date: Sat, 10 Sep 2022 23:16:35 -0500 Subject: [PATCH 10/16] chore(database): remove old worker logic --- database/postgres/ddl/worker.go | 33 --- database/postgres/dml/worker.go | 47 ---- database/postgres/worker.go | 114 --------- database/postgres/worker_count.go | 26 -- database/postgres/worker_count_test.go | 82 ------- database/postgres/worker_list.go | 39 --- database/postgres/worker_list_test.go | 86 ------- database/postgres/worker_test.go | 316 ------------------------- database/sqlite/ddl/worker.go | 33 --- database/sqlite/dml/worker.go | 47 ---- database/sqlite/worker.go | 114 --------- database/sqlite/worker_count.go | 26 -- database/sqlite/worker_count_test.go | 97 -------- database/sqlite/worker_list.go | 39 --- database/sqlite/worker_list_test.go | 95 -------- database/sqlite/worker_test.go | 311 ------------------------ 16 files changed, 1505 deletions(-) delete mode 100644 database/postgres/ddl/worker.go delete mode 100644 database/postgres/dml/worker.go delete mode 100644 database/postgres/worker.go delete mode 100644 database/postgres/worker_count.go delete mode 100644 database/postgres/worker_count_test.go delete mode 100644 database/postgres/worker_list.go delete mode 100644 database/postgres/worker_list_test.go delete mode 100644 database/postgres/worker_test.go delete mode 100644 database/sqlite/ddl/worker.go delete mode 100644 database/sqlite/dml/worker.go delete mode 100644 database/sqlite/worker.go delete mode 100644 database/sqlite/worker_count.go delete mode 100644 database/sqlite/worker_count_test.go delete mode 100644 database/sqlite/worker_list.go delete mode 100644 database/sqlite/worker_list_test.go delete mode 100644 database/sqlite/worker_test.go diff --git a/database/postgres/ddl/worker.go b/database/postgres/ddl/worker.go deleted file mode 100644 index dd8658e7c..000000000 --- a/database/postgres/ddl/worker.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package ddl - -const ( - // CreateWorkerTable represents a query to - // create the workers table for Vela. - CreateWorkerTable = ` -CREATE TABLE -IF NOT EXISTS -workers ( - id SERIAL PRIMARY KEY, - hostname VARCHAR(250), - address VARCHAR(250), - routes VARCHAR(1000), - active BOOLEAN, - last_checked_in INTEGER, - build_limit INTEGER, - UNIQUE(hostname) -); -` - - // CreateWorkerHostnameAddressIndex represents a query to create an - // index on the workers table for the hostname and address columns. - CreateWorkerHostnameAddressIndex = ` -CREATE INDEX -IF NOT EXISTS -workers_hostname_address -ON workers (hostname, address); -` -) diff --git a/database/postgres/dml/worker.go b/database/postgres/dml/worker.go deleted file mode 100644 index 3ac74fd2e..000000000 --- a/database/postgres/dml/worker.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package dml - -const ( - // ListWorkers represents a query to - // list all workers in the database. - ListWorkers = ` -SELECT * -FROM workers; -` - - // SelectWorkersCount represents a query to select the - // count of workers in the database. - SelectWorkersCount = ` -SELECT count(*) as count -FROM workers; -` - - // SelectWorker represents a query to select a - // worker by hostname in the database. - SelectWorker = ` -SELECT * -FROM workers -WHERE hostname = ? -LIMIT 1; -` - - // SelectWorkerByAddress represents a query to select a - // worker by address in the database. - SelectWorkerByAddress = ` -SELECT * -FROM workers -WHERE address = ? -LIMIT 1; -` - - // DeleteWorker represents a query to - // remove a worker from the database. - DeleteWorker = ` -DELETE -FROM workers -WHERE id = ?; -` -) diff --git a/database/postgres/worker.go b/database/postgres/worker.go deleted file mode 100644 index 1d871f596..000000000 --- a/database/postgres/worker.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "errors" - - "github.com/sirupsen/logrus" - - "github.com/go-vela/server/database/postgres/dml" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" - - "gorm.io/gorm" -) - -// GetWorker gets a worker by hostname from the database. -func (c *client) GetWorker(hostname string) (*library.Worker, error) { - c.Logger.WithFields(logrus.Fields{ - "worker": hostname, - }).Tracef("getting worker %s from the database", hostname) - - // variable to store query results - w := new(database.Worker) - - // send query to the database and store result in variable - result := c.Postgres. - Table(constants.TableWorker). - Raw(dml.SelectWorker, hostname). - Scan(w) - - // check if the query returned a record not found error or no rows were returned - if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 { - return nil, gorm.ErrRecordNotFound - } - - return w.ToLibrary(), result.Error -} - -// GetWorker gets a worker by address from the database. -func (c *client) GetWorkerByAddress(address string) (*library.Worker, error) { - c.Logger.Tracef("getting worker by address %s from the database", address) - - // variable to store query results - w := new(database.Worker) - - // send query to the database and store result in variable - result := c.Postgres. - Table(constants.TableWorker). - Raw(dml.SelectWorkerByAddress, address). - Scan(w) - - // check if the query returned a record not found error or no rows were returned - if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 { - return nil, gorm.ErrRecordNotFound - } - - return w.ToLibrary(), result.Error -} - -// CreateWorker creates a new worker in the database. -func (c *client) CreateWorker(w *library.Worker) error { - c.Logger.WithFields(logrus.Fields{ - "worker": w.GetHostname(), - }).Tracef("creating worker %s in the database", w.GetHostname()) - - // cast to database type - worker := database.WorkerFromLibrary(w) - - // validate the necessary fields are populated - err := worker.Validate() - if err != nil { - return err - } - - // send query to the database - return c.Postgres. - Table(constants.TableWorker). - Create(worker).Error -} - -// UpdateWorker updates a worker in the database. -func (c *client) UpdateWorker(w *library.Worker) error { - c.Logger.WithFields(logrus.Fields{ - "worker": w.GetHostname(), - }).Tracef("updating worker %s in the database", w.GetHostname()) - - // cast to database type - worker := database.WorkerFromLibrary(w) - - // validate the necessary fields are populated - err := worker.Validate() - if err != nil { - return err - } - - // send query to the database - return c.Postgres. - Table(constants.TableWorker). - Save(worker).Error -} - -// DeleteWorker deletes a worker by unique ID from the database. -func (c *client) DeleteWorker(id int64) error { - c.Logger.Tracef("deleting worker %d in the database", id) - - // send query to the database - return c.Postgres. - Table(constants.TableWorker). - Exec(dml.DeleteWorker, id).Error -} diff --git a/database/postgres/worker_count.go b/database/postgres/worker_count.go deleted file mode 100644 index d5f867988..000000000 --- a/database/postgres/worker_count.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "github.com/go-vela/server/database/postgres/dml" - "github.com/go-vela/types/constants" -) - -// GetWorkerCount gets a count of all workers from the database. -func (c *client) GetWorkerCount() (int64, error) { - c.Logger.Trace("getting count of workers from the database") - - // variable to store query results - var w int64 - - // send query to the database and store result in variable - err := c.Postgres. - Table(constants.TableWorker). - Raw(dml.SelectWorkersCount). - Pluck("count", &w).Error - - return w, err -} diff --git a/database/postgres/worker_count_test.go b/database/postgres/worker_count_test.go deleted file mode 100644 index 61645f5ee..000000000 --- a/database/postgres/worker_count_test.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "reflect" - "testing" - - sqlmock "github.com/DATA-DOG/go-sqlmock" - - "github.com/go-vela/server/database/postgres/dml" - - "gorm.io/gorm" -) - -func TestPostgres_Client_GetWorkerCount(t *testing.T) { - // setup types - _workerOne := testWorker() - _workerOne.SetID(1) - _workerOne.SetHostname("worker_0") - _workerOne.SetAddress("localhost") - _workerOne.SetActive(true) - - _workerTwo := testWorker() - _workerTwo.SetID(2) - _workerTwo.SetHostname("worker_1") - _workerTwo.SetAddress("localhost") - _workerTwo.SetActive(true) - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // capture the current expected SQL query - // - // https://gorm.io/docs/sql_builder.html#DryRun-Mode - _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectWorkersCount).Statement - - // create expected return in mock - _rows := sqlmock.NewRows([]string{"count"}).AddRow(2) - - // ensure the mock expects the query - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want int64 - }{ - { - failure: false, - want: 2, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetWorkerCount() - - if test.failure { - if err == nil { - t.Errorf("GetWorkerCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetWorkerCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetWorkerCount is %v, want %v", got, test.want) - } - } -} diff --git a/database/postgres/worker_list.go b/database/postgres/worker_list.go deleted file mode 100644 index 87bc66e7b..000000000 --- a/database/postgres/worker_list.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "github.com/go-vela/server/database/postgres/dml" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" -) - -// GetWorkerList gets a list of all workers from the database. -func (c *client) GetWorkerList() ([]*library.Worker, error) { - c.Logger.Trace("listing workers from the database") - - // variable to store query results - w := new([]database.Worker) - - // send query to the database and store result in variable - err := c.Postgres. - Table(constants.TableWorker). - Raw(dml.ListWorkers). - Scan(w).Error - - // variable we want to return - workers := []*library.Worker{} - // iterate through all query results - for _, worker := range *w { - // https://golang.org/doc/faq#closures_and_goroutines - tmp := worker - - // convert query result to library type - workers = append(workers, tmp.ToLibrary()) - } - - return workers, err -} diff --git a/database/postgres/worker_list_test.go b/database/postgres/worker_list_test.go deleted file mode 100644 index b6b3684cc..000000000 --- a/database/postgres/worker_list_test.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "reflect" - "testing" - - sqlmock "github.com/DATA-DOG/go-sqlmock" - - "github.com/go-vela/server/database/postgres/dml" - "github.com/go-vela/types/library" - - "gorm.io/gorm" -) - -func TestPostgres_Client_GetWorkerList(t *testing.T) { - // setup types - _workerOne := testWorker() - _workerOne.SetID(1) - _workerOne.SetHostname("worker_0") - _workerOne.SetAddress("localhost") - _workerOne.SetActive(true) - - _workerTwo := testWorker() - _workerTwo.SetID(2) - _workerTwo.SetHostname("worker_1") - _workerTwo.SetAddress("localhost") - _workerTwo.SetActive(true) - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // capture the current expected SQL query - // - // https://gorm.io/docs/sql_builder.html#DryRun-Mode - _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.ListWorkers).Statement - - // create expected return in mock - _rows := sqlmock.NewRows( - []string{"id", "hostname", "address", "routes", "active", "last_checked_in", "build_limit"}, - ).AddRow(1, "worker_0", "localhost", "{}", true, 0, 0). - AddRow(2, "worker_1", "localhost", "{}", true, 0, 0) - - // ensure the mock expects the query - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want []*library.Worker - }{ - { - failure: false, - want: []*library.Worker{_workerOne, _workerTwo}, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetWorkerList() - - if test.failure { - if err == nil { - t.Errorf("GetWorkerList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetWorkerList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetWorkerList is %v, want %v", got, test.want) - } - } -} diff --git a/database/postgres/worker_test.go b/database/postgres/worker_test.go deleted file mode 100644 index dcc983203..000000000 --- a/database/postgres/worker_test.go +++ /dev/null @@ -1,316 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package postgres - -import ( - "reflect" - "testing" - - sqlmock "github.com/DATA-DOG/go-sqlmock" - "gorm.io/gorm" - - "github.com/go-vela/server/database/postgres/dml" - "github.com/go-vela/types/library" -) - -func TestPostgres_Client_GetWorker(t *testing.T) { - // setup types - _worker := testWorker() - _worker.SetID(1) - _worker.SetHostname("worker_0") - _worker.SetAddress("localhost") - _worker.SetActive(true) - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // capture the current expected SQL query - // - // https://gorm.io/docs/sql_builder.html#DryRun-Mode - _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectWorker, "worker_0").Statement - - // create expected return in mock - _rows := sqlmock.NewRows( - []string{"id", "hostname", "address", "routes", "active", "last_checked_in", "build_limit"}, - ).AddRow(1, "worker_0", "localhost", "{}", true, 0, 0) - - // ensure the mock expects the query for test case 1 - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - // ensure the mock expects the error for test case 2 - _mock.ExpectQuery(_query.SQL.String()).WillReturnError(gorm.ErrRecordNotFound) - - // setup tests - tests := []struct { - failure bool - want *library.Worker - }{ - { - failure: false, - want: _worker, - }, - { - failure: true, - want: nil, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetWorker("worker_0") - - if test.failure { - if err == nil { - t.Errorf("GetWorker should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetWorker returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetWorker is %v, want %v", got, test.want) - } - } -} - -func TestPostgres_Client_GetWorkerByAddress(t *testing.T) { - // setup types - _worker := testWorker() - _worker.SetID(1) - _worker.SetHostname("worker_0") - _worker.SetAddress("localhost") - _worker.SetActive(true) - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // capture the current expected SQL query - // - // https://gorm.io/docs/sql_builder.html#DryRun-Mode - _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectWorkerByAddress, "localhost").Statement - - // create expected return in mock - _rows := sqlmock.NewRows( - []string{"id", "hostname", "address", "routes", "active", "last_checked_in", "build_limit"}, - ).AddRow(1, "worker_0", "localhost", "{}", true, 0, 0) - - // ensure the mock expects the query for test case 1 - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - // ensure the mock expects the error for test case 2 - _mock.ExpectQuery(_query.SQL.String()).WillReturnError(gorm.ErrRecordNotFound) - - // setup tests - tests := []struct { - failure bool - want *library.Worker - }{ - { - failure: false, - want: _worker, - }, - { - failure: true, - want: nil, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetWorkerByAddress("localhost") - - if test.failure { - if err == nil { - t.Errorf("GetWorkerByAddress should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetWorkerByAddress returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetWorkerByAddress is %v, want %v", got, test.want) - } - } -} - -func TestPostgres_Client_CreateWorker(t *testing.T) { - // setup types - _worker := testWorker() - _worker.SetID(1) - _worker.SetHostname("worker_0") - _worker.SetAddress("localhost") - _worker.SetActive(true) - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // create expected return in mock - _rows := sqlmock.NewRows([]string{"id"}).AddRow(1) - - // ensure the mock expects the query - _mock.ExpectQuery(`INSERT INTO "workers" ("hostname","address","routes","active","last_checked_in","build_limit","id") VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING "id"`). - WithArgs("worker_0", "localhost", "{}", true, nil, nil, 1). - WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - err := _database.CreateWorker(_worker) - - if test.failure { - if err == nil { - t.Errorf("CreateWorker should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("CreateWorker returned err: %v", err) - } - } -} - -func TestPostgres_Client_UpdateWorker(t *testing.T) { - // setup types - _worker := testWorker() - _worker.SetID(1) - _worker.SetHostname("worker_0") - _worker.SetAddress("localhost") - _worker.SetActive(true) - - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // ensure the mock expects the query - _mock.ExpectExec(`UPDATE "workers" SET "hostname"=$1,"address"=$2,"routes"=$3,"active"=$4,"last_checked_in"=$5,"build_limit"=$6 WHERE "id" = $7`). - WithArgs("worker_0", "localhost", "{}", true, nil, nil, 1). - WillReturnResult(sqlmock.NewResult(1, 1)) - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - err := _database.UpdateWorker(_worker) - - if test.failure { - if err == nil { - t.Errorf("UpdateWorker should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("UpdateWorker returned err: %v", err) - } - } -} - -func TestPostgres_Client_DeleteWorker(t *testing.T) { - // setup types - // setup the test database client - _database, _mock, err := NewTest() - if err != nil { - t.Errorf("unable to create new postgres test database: %v", err) - } - - defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - - // capture the current expected SQL query - // - // https://gorm.io/docs/sql_builder.html#DryRun-Mode - _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Exec(dml.DeleteWorker, 1).Statement - - // ensure the mock expects the query - _mock.ExpectExec(_query.SQL.String()).WillReturnResult(sqlmock.NewResult(1, 1)) - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - err := _database.DeleteWorker(1) - - if test.failure { - if err == nil { - t.Errorf("DeleteWorker should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("DeleteWorker returned err: %v", err) - } - } -} - -// testWorker is a test helper function to create a -// library Worker type with all fields set to their -// zero values. -func testWorker() *library.Worker { - i64 := int64(0) - str := "" - arr := []string{} - b := false - - return &library.Worker{ - ID: &i64, - Hostname: &str, - Address: &str, - Routes: &arr, - Active: &b, - LastCheckedIn: &i64, - BuildLimit: &i64, - } -} diff --git a/database/sqlite/ddl/worker.go b/database/sqlite/ddl/worker.go deleted file mode 100644 index 0e5ba8f4a..000000000 --- a/database/sqlite/ddl/worker.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package ddl - -const ( - // CreateWorkerTable represents a query to - // create the workers table for Vela. - CreateWorkerTable = ` -CREATE TABLE -IF NOT EXISTS -workers ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - hostname TEXT, - address TEXT, - routes TEXT, - active TEXT, - last_checked_in INTEGER, - build_limit INTEGER, - UNIQUE(hostname) -); -` - - // CreateWorkerHostnameAddressIndex represents a query to create an - // index on the workers table for the hostname and address columns. - CreateWorkerHostnameAddressIndex = ` -CREATE INDEX -IF NOT EXISTS -workers_hostname_address -ON workers (hostname, address); -` -) diff --git a/database/sqlite/dml/worker.go b/database/sqlite/dml/worker.go deleted file mode 100644 index 64967c9b9..000000000 --- a/database/sqlite/dml/worker.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package dml - -const ( - // ListWorkers represents a query to - // list all workers in the database. - ListWorkers = ` -SELECT * -FROM workers; -` - - // SelectWorkersCount represents a query to select the - // count of workers in the database. - SelectWorkersCount = ` -SELECT count(*) as count -FROM workers; -` - - // SelectWorker represents a query to select a - // worker in the database. - SelectWorker = ` -SELECT * -FROM workers -WHERE hostname = ? -LIMIT 1; -` - - // SelectWorkerByAddress represents a query to select a - // worker by address in the database. - SelectWorkerByAddress = ` -SELECT * -FROM workers -WHERE address = ? -LIMIT 1; -` - - // DeleteWorker represents a query to - // remove a worker from the database. - DeleteWorker = ` -DELETE -FROM workers -WHERE id = ?; -` -) diff --git a/database/sqlite/worker.go b/database/sqlite/worker.go deleted file mode 100644 index 25ad1392b..000000000 --- a/database/sqlite/worker.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "errors" - - "github.com/sirupsen/logrus" - - "github.com/go-vela/server/database/sqlite/dml" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" - - "gorm.io/gorm" -) - -// GetWorker gets a worker by hostname from the database. -func (c *client) GetWorker(hostname string) (*library.Worker, error) { - c.Logger.WithFields(logrus.Fields{ - "worker": hostname, - }).Tracef("getting worker %s from the database", hostname) - - // variable to store query results - w := new(database.Worker) - - // send query to the database and store result in variable - result := c.Sqlite. - Table(constants.TableWorker). - Raw(dml.SelectWorker, hostname). - Scan(w) - - // check if the query returned a record not found error or no rows were returned - if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 { - return nil, gorm.ErrRecordNotFound - } - - return w.ToLibrary(), result.Error -} - -// GetWorker gets a worker by address from the database. -func (c *client) GetWorkerByAddress(address string) (*library.Worker, error) { - c.Logger.Tracef("getting worker by address %s from the database", address) - - // variable to store query results - w := new(database.Worker) - - // send query to the database and store result in variable - result := c.Sqlite. - Table(constants.TableWorker). - Raw(dml.SelectWorkerByAddress, address). - Scan(w) - - // check if the query returned a record not found error or no rows were returned - if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 { - return nil, gorm.ErrRecordNotFound - } - - return w.ToLibrary(), result.Error -} - -// CreateWorker creates a new worker in the database. -func (c *client) CreateWorker(w *library.Worker) error { - c.Logger.WithFields(logrus.Fields{ - "worker": w.GetHostname(), - }).Tracef("creating worker %s in the database", w.GetHostname()) - - // cast to database type - worker := database.WorkerFromLibrary(w) - - // validate the necessary fields are populated - err := worker.Validate() - if err != nil { - return err - } - - // send query to the database - return c.Sqlite. - Table(constants.TableWorker). - Create(worker).Error -} - -// UpdateWorker updates a worker in the database. -func (c *client) UpdateWorker(w *library.Worker) error { - c.Logger.WithFields(logrus.Fields{ - "worker": w.GetHostname(), - }).Tracef("updating worker %s in the database", w.GetHostname()) - - // cast to database type - worker := database.WorkerFromLibrary(w) - - // validate the necessary fields are populated - err := worker.Validate() - if err != nil { - return err - } - - // send query to the database - return c.Sqlite. - Table(constants.TableWorker). - Save(worker).Error -} - -// DeleteWorker deletes a worker by unique ID from the database. -func (c *client) DeleteWorker(id int64) error { - c.Logger.Tracef("deleting worker %d in the database", id) - - // send query to the database - return c.Sqlite. - Table(constants.TableWorker). - Exec(dml.DeleteWorker, id).Error -} diff --git a/database/sqlite/worker_count.go b/database/sqlite/worker_count.go deleted file mode 100644 index 38b2cf7b9..000000000 --- a/database/sqlite/worker_count.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "github.com/go-vela/server/database/sqlite/dml" - "github.com/go-vela/types/constants" -) - -// GetWorkerCount gets a count of all workers from the database. -func (c *client) GetWorkerCount() (int64, error) { - c.Logger.Trace("getting count of workers from the database") - - // variable to store query results - var w int64 - - // send query to the database and store result in variable - err := c.Sqlite. - Table(constants.TableWorker). - Raw(dml.SelectWorkersCount). - Pluck("count", &w).Error - - return w, err -} diff --git a/database/sqlite/worker_count_test.go b/database/sqlite/worker_count_test.go deleted file mode 100644 index dbbdcfc18..000000000 --- a/database/sqlite/worker_count_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "log" - "reflect" - "testing" - - "github.com/go-vela/server/database/sqlite/ddl" - "github.com/go-vela/types/constants" -) - -func init() { - // setup the test database client - _database, err := NewTest() - if err != nil { - log.Fatalf("unable to create new sqlite test database: %v", err) - } - - // create the worker table - err = _database.Sqlite.Exec(ddl.CreateWorkerTable).Error - if err != nil { - log.Fatalf("unable to create %s table: %v", constants.TableWorker, err) - } -} - -func TestSqlite_Client_GetWorkerCount(t *testing.T) { - // setup types - _workerOne := testWorker() - _workerOne.SetID(1) - _workerOne.SetHostname("worker_0") - _workerOne.SetAddress("localhost") - _workerOne.SetActive(true) - - _workerTwo := testWorker() - _workerTwo.SetID(2) - _workerTwo.SetHostname("worker_1") - _workerTwo.SetAddress("localhost") - _workerTwo.SetActive(true) - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - want int64 - }{ - { - failure: false, - want: 2, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the workers table - defer _database.Sqlite.Exec("delete from workers;") - - // create the workers in the database - err := _database.CreateWorker(_workerOne) - if err != nil { - t.Errorf("unable to create test worker: %v", err) - } - - err = _database.CreateWorker(_workerTwo) - if err != nil { - t.Errorf("unable to create test worker: %v", err) - } - - got, err := _database.GetWorkerCount() - - if test.failure { - if err == nil { - t.Errorf("GetWorkerCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetWorkerCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetWorkerCount is %v, want %v", got, test.want) - } - } -} diff --git a/database/sqlite/worker_list.go b/database/sqlite/worker_list.go deleted file mode 100644 index e16d99071..000000000 --- a/database/sqlite/worker_list.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "github.com/go-vela/server/database/sqlite/dml" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/database" - "github.com/go-vela/types/library" -) - -// GetWorkerList gets a list of all workers from the database. -func (c *client) GetWorkerList() ([]*library.Worker, error) { - c.Logger.Trace("listing workers from the database") - - // variable to store query results - w := new([]database.Worker) - - // send query to the database and store result in variable - err := c.Sqlite. - Table(constants.TableWorker). - Raw(dml.ListWorkers). - Scan(w).Error - - // variable we want to return - workers := []*library.Worker{} - // iterate through all query results - for _, worker := range *w { - // https://golang.org/doc/faq#closures_and_goroutines - tmp := worker - - // convert query result to library type - workers = append(workers, tmp.ToLibrary()) - } - - return workers, err -} diff --git a/database/sqlite/worker_list_test.go b/database/sqlite/worker_list_test.go deleted file mode 100644 index 72916385e..000000000 --- a/database/sqlite/worker_list_test.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "log" - "reflect" - "testing" - - "github.com/go-vela/server/database/sqlite/ddl" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" -) - -func init() { - // setup the test database client - _database, err := NewTest() - if err != nil { - log.Fatalf("unable to create new sqlite test database: %v", err) - } - - // create the worker table - err = _database.Sqlite.Exec(ddl.CreateWorkerTable).Error - if err != nil { - log.Fatalf("unable to create %s table: %v", constants.TableWorker, err) - } -} - -func TestSqlite_Client_GetWorkerList(t *testing.T) { - // setup types - _workerOne := testWorker() - _workerOne.SetID(1) - _workerOne.SetHostname("worker_0") - _workerOne.SetAddress("localhost") - _workerOne.SetActive(true) - - _workerTwo := testWorker() - _workerTwo.SetID(2) - _workerTwo.SetHostname("worker_1") - _workerTwo.SetAddress("localhost") - _workerTwo.SetActive(true) - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - want []*library.Worker - }{ - { - failure: false, - want: []*library.Worker{_workerOne, _workerTwo}, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the workers table - defer _database.Sqlite.Exec("delete from workers;") - - for _, worker := range test.want { - // create the worker in the database - err := _database.CreateWorker(worker) - if err != nil { - t.Errorf("unable to create test worker: %v", err) - } - } - - got, err := _database.GetWorkerList() - - if test.failure { - if err == nil { - t.Errorf("GetWorkerList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetWorkerList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetWorkerList is %v, want %v", got, test.want) - } - } -} diff --git a/database/sqlite/worker_test.go b/database/sqlite/worker_test.go deleted file mode 100644 index 50c6087db..000000000 --- a/database/sqlite/worker_test.go +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package sqlite - -import ( - "reflect" - "testing" - - "github.com/go-vela/types/library" -) - -func TestSqlite_Client_GetWorker(t *testing.T) { - // setup types - _worker := testWorker() - _worker.SetID(1) - _worker.SetHostname("worker_0") - _worker.SetAddress("localhost") - _worker.SetActive(true) - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - want *library.Worker - }{ - { - failure: false, - want: _worker, - }, - { - failure: true, - want: nil, - }, - } - - // run tests - for _, test := range tests { - if test.want != nil { - // create the worker in the database - err := _database.CreateWorker(test.want) - if err != nil { - t.Errorf("unable to create test worker: %v", err) - } - } - - got, err := _database.GetWorker("worker_0") - - // cleanup the workers table - _ = _database.Sqlite.Exec("DELETE FROM workers;") - - if test.failure { - if err == nil { - t.Errorf("GetWorker should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetWorker returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetWorker is %v, want %v", got, test.want) - } - } -} - -func TestSqlite_Client_GetWorkerByAddress(t *testing.T) { - // setup types - _worker := testWorker() - _worker.SetID(1) - _worker.SetHostname("worker_0") - _worker.SetAddress("localhost") - _worker.SetActive(true) - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - want *library.Worker - }{ - { - failure: false, - want: _worker, - }, - { - failure: true, - want: nil, - }, - } - - // run tests - for _, test := range tests { - if test.want != nil { - // create the worker in the database - err := _database.CreateWorker(test.want) - if err != nil { - t.Errorf("unable to create test worker: %v", err) - } - } - - got, err := _database.GetWorkerByAddress("localhost") - - // cleanup the workers table - _ = _database.Sqlite.Exec("DELETE FROM workers;") - - if test.failure { - if err == nil { - t.Errorf("GetWorkerByAddress should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetWorkerByAddress returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetWorkerByAddress is %v, want %v", got, test.want) - } - } -} - -func TestSqlite_Client_CreateWorker(t *testing.T) { - // setup types - _worker := testWorker() - _worker.SetID(1) - _worker.SetHostname("worker_0") - _worker.SetAddress("localhost") - _worker.SetActive(true) - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the workers table - defer _database.Sqlite.Exec("delete from workers;") - - err := _database.CreateWorker(_worker) - - if test.failure { - if err == nil { - t.Errorf("CreateWorker should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("CreateWorker returned err: %v", err) - } - } -} - -func TestSqlite_Client_UpdateWorker(t *testing.T) { - // setup types - _worker := testWorker() - _worker.SetID(1) - _worker.SetHostname("worker_0") - _worker.SetAddress("localhost") - _worker.SetActive(true) - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the workers table - defer _database.Sqlite.Exec("delete from workers;") - - // create the worker in the database - err := _database.CreateWorker(_worker) - if err != nil { - t.Errorf("unable to create test worker: %v", err) - } - - err = _database.UpdateWorker(_worker) - - if test.failure { - if err == nil { - t.Errorf("UpdateWorker should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("UpdateWorker returned err: %v", err) - } - } -} - -func TestSqlite_Client_DeleteWorker(t *testing.T) { - // setup types - _worker := testWorker() - _worker.SetID(1) - _worker.SetHostname("worker_0") - _worker.SetAddress("localhost") - _worker.SetActive(true) - - // setup the test database client - _database, err := NewTest() - if err != nil { - t.Errorf("unable to create new sqlite test database: %v", err) - } - - defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }() - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the workers table - defer _database.Sqlite.Exec("delete from workers;") - - // create the worker in the database - err := _database.CreateWorker(_worker) - if err != nil { - t.Errorf("unable to create test worker: %v", err) - } - - err = _database.DeleteWorker(1) - - if test.failure { - if err == nil { - t.Errorf("DeleteWorker should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("DeleteWorker returned err: %v", err) - } - } -} - -// testWorker is a test helper function to create a -// library Worker type with all fields set to their -// zero values. -func testWorker() *library.Worker { - i64 := int64(0) - str := "" - b := false - - var arr []string - - return &library.Worker{ - ID: &i64, - Hostname: &str, - Address: &str, - Routes: &arr, - Active: &b, - LastCheckedIn: &i64, - BuildLimit: &i64, - } -} From a960f5dc2b9fd54ea4799f510560c7c4ccf8a6cb Mon Sep 17 00:00:00 2001 From: JordanBrockopp Date: Sat, 10 Sep 2022 23:17:01 -0500 Subject: [PATCH 11/16] chore: updates for database worker engine --- api/build.go | 2 +- api/metrics.go | 2 +- api/worker.go | 8 +++---- database/postgres/postgres.go | 29 ++++++++++++++---------- database/postgres/postgres_test.go | 19 ++++++++++++---- database/service.go | 27 ++++------------------ database/sqlite/sqlite.go | 27 ++++++++++++---------- router/middleware/executors/executors.go | 2 +- router/middleware/worker/worker.go | 2 +- 9 files changed, 58 insertions(+), 60 deletions(-) diff --git a/api/build.go b/api/build.go index 075a80c36..b74909214 100644 --- a/api/build.go +++ b/api/build.go @@ -1634,7 +1634,7 @@ func CancelBuild(c *gin.Context) { } // retrieve the worker info - w, err := database.FromContext(c).GetWorker(b.GetHost()) + w, err := database.FromContext(c).GetWorkerForHostname(b.GetHost()) if err != nil { retErr := fmt.Errorf("unable to get worker for build %s: %w", entry, err) util.HandleError(c, http.StatusNotFound, retErr) diff --git a/api/metrics.go b/api/metrics.go index 12a35b0f9..f933a1e73 100644 --- a/api/metrics.go +++ b/api/metrics.go @@ -364,7 +364,7 @@ func recordGauges(c *gin.Context) { // worker_build_limit, active_worker_count, inactive_worker_count if q.WorkerBuildLimit || q.ActiveWorkerCount || q.InactiveWorkerCount { // send API call to capture the workers - workers, err := database.FromContext(c).GetWorkerList() + workers, err := database.FromContext(c).ListWorkers() if err != nil { logrus.Errorf("unable to get workers: %v", err) } diff --git a/api/worker.go b/api/worker.go index 6f5b65636..4951b182a 100644 --- a/api/worker.go +++ b/api/worker.go @@ -122,7 +122,7 @@ func GetWorkers(c *gin.Context) { "user": u.GetName(), }).Info("reading workers") - w, err := database.FromContext(c).GetWorkerList() + w, err := database.FromContext(c).ListWorkers() if err != nil { retErr := fmt.Errorf("unable to get workers: %w", err) @@ -174,7 +174,7 @@ func GetWorker(c *gin.Context) { "worker": w.GetHostname(), }).Infof("reading worker %s", w.GetHostname()) - w, err := database.FromContext(c).GetWorker(w.GetHostname()) + w, err := database.FromContext(c).GetWorkerForHostname(w.GetHostname()) if err != nil { retErr := fmt.Errorf("unable to get workers: %w", err) @@ -283,7 +283,7 @@ func UpdateWorker(c *gin.Context) { } // send API call to capture the updated worker - w, _ = database.FromContext(c).GetWorker(w.GetHostname()) + w, _ = database.FromContext(c).GetWorkerForHostname(w.GetHostname()) c.JSON(http.StatusOK, w) } @@ -329,7 +329,7 @@ func DeleteWorker(c *gin.Context) { }).Infof("deleting worker %s", w.GetHostname()) // send API call to remove the step - err := database.FromContext(c).DeleteWorker(w.GetID()) + err := database.FromContext(c).DeleteWorker(w) if err != nil { retErr := fmt.Errorf("unable to delete worker %s: %w", w.GetHostname(), err) diff --git a/database/postgres/postgres.go b/database/postgres/postgres.go index ff635bb88..a6e4a4ada 100644 --- a/database/postgres/postgres.go +++ b/database/postgres/postgres.go @@ -13,6 +13,7 @@ import ( "github.com/go-vela/server/database/postgres/ddl" "github.com/go-vela/server/database/repo" "github.com/go-vela/server/database/user" + "github.com/go-vela/server/database/worker" "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" @@ -50,6 +51,8 @@ type ( repo.RepoService // https://pkg.go.dev/github.com/go-vela/server/database/user#UserService user.UserService + // https://pkg.go.dev/github.com/go-vela/server/database/worker#WorkerService + worker.WorkerService } ) @@ -152,6 +155,8 @@ func NewTest() (*client, sqlmock.Sqlmock, error) { _mock.ExpectExec(repo.CreateOrgNameIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(user.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(user.CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(worker.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(worker.CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) // create the new mock Postgres database client // @@ -268,12 +273,6 @@ func createTables(c *client) error { return fmt.Errorf("unable to create %s table: %w", constants.TableStep, err) } - // create the workers table - err = c.Postgres.Exec(ddl.CreateWorkerTable).Error - if err != nil { - return fmt.Errorf("unable to create %s table: %w", constants.TableWorker, err) - } - return nil } @@ -336,12 +335,6 @@ func createIndexes(c *client) error { return fmt.Errorf("unable to create secrets_type_org index for the %s table: %w", constants.TableSecret, err) } - // create the workers_hostname_address index for the workers table - err = c.Postgres.Exec(ddl.CreateWorkerHostnameAddressIndex).Error - if err != nil { - return fmt.Errorf("unable to create workers_hostname_address index for the %s table: %w", constants.TableWorker, err) - } - return nil } @@ -388,5 +381,17 @@ func createServices(c *client) error { return err } + // create the database agnostic worker service + // + // https://pkg.go.dev/github.com/go-vela/server/database/worker#New + c.WorkerService, err = worker.New( + worker.WithClient(c.Postgres), + worker.WithLogger(c.Logger), + worker.WithSkipCreation(c.config.SkipCreation), + ) + if err != nil { + return err + } + return nil } diff --git a/database/postgres/postgres_test.go b/database/postgres/postgres_test.go index 53375a1e7..6ab864337 100644 --- a/database/postgres/postgres_test.go +++ b/database/postgres/postgres_test.go @@ -14,6 +14,7 @@ import ( "github.com/go-vela/server/database/postgres/ddl" "github.com/go-vela/server/database/repo" "github.com/go-vela/server/database/user" + "github.com/go-vela/server/database/worker" "github.com/go-vela/types/library" ) @@ -82,7 +83,6 @@ func TestPostgres_setupDatabase(t *testing.T) { _mock.ExpectExec(ddl.CreateSecretTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateServiceTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateStepTable).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(ddl.CreateWorkerTable).WillReturnResult(sqlmock.NewResult(1, 1)) // ensure the mock expects the index queries _mock.ExpectExec(ddl.CreateBuildRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) @@ -94,13 +94,19 @@ func TestPostgres_setupDatabase(t *testing.T) { _mock.ExpectExec(ddl.CreateSecretTypeOrgRepo).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateSecretTypeOrgTeam).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateSecretTypeOrg).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(ddl.CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + + // ensure the mock expects the pipeline queries _mock.ExpectExec(pipeline.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(pipeline.CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + // ensure the mock expects the repo queries _mock.ExpectExec(repo.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(repo.CreateOrgNameIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + // ensure the mock expects the user queries _mock.ExpectExec(user.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(user.CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + // ensure the mock expects the worker queries + _mock.ExpectExec(worker.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(worker.CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) // setup the skip test database client _skipDatabase, _skipMock, err := NewTest() @@ -167,7 +173,6 @@ func TestPostgres_createTables(t *testing.T) { _mock.ExpectExec(ddl.CreateSecretTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateServiceTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateStepTable).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(ddl.CreateWorkerTable).WillReturnResult(sqlmock.NewResult(1, 1)) tests := []struct { failure bool @@ -215,7 +220,6 @@ func TestPostgres_createIndexes(t *testing.T) { _mock.ExpectExec(ddl.CreateSecretTypeOrgRepo).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateSecretTypeOrgTeam).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateSecretTypeOrg).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(ddl.CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) tests := []struct { failure bool @@ -253,13 +257,18 @@ func TestPostgres_createServices(t *testing.T) { defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }() - // ensure the mock expects the index queries + // ensure the mock expects the pipeline queries _mock.ExpectExec(pipeline.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(pipeline.CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + // ensure the mock expects the repo queries _mock.ExpectExec(repo.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(repo.CreateOrgNameIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + // ensure the mock expects the user queries _mock.ExpectExec(user.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(user.CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + // ensure the mock expects the worker queries + _mock.ExpectExec(worker.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(worker.CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) tests := []struct { failure bool diff --git a/database/service.go b/database/service.go index 649dc0056..2191ec1c6 100644 --- a/database/service.go +++ b/database/service.go @@ -8,6 +8,7 @@ import ( "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/repo" "github.com/go-vela/server/database/user" + "github.com/go-vela/server/database/worker" "github.com/go-vela/types/library" ) @@ -216,27 +217,7 @@ type Service interface { // related to users stored in the database. user.UserService - // Worker Database Interface Functions - - // GetWorker defines a function that - // gets a worker by hostname. - GetWorker(string) (*library.Worker, error) - // GetWorkerByAddress defines a function that - // gets a worker by address. - GetWorkerByAddress(string) (*library.Worker, error) - // GetWorkerList defines a function that - // gets a list of all workers. - GetWorkerList() ([]*library.Worker, error) - // GetWorkerCount defines a function that - // gets the count of workers. - GetWorkerCount() (int64, error) - // CreateWorker defines a function that - // creates a new worker. - CreateWorker(*library.Worker) error - // UpdateWorker defines a function that - // updates a worker by unique ID. - UpdateWorker(*library.Worker) error - // DeleteWorker defines a function that - // deletes a worker by hostname. - DeleteWorker(int64) error + // WorkerService provides the interface for functionality + // related to workers stored in the database. + worker.WorkerService } diff --git a/database/sqlite/sqlite.go b/database/sqlite/sqlite.go index dc926a014..16fa11c7c 100644 --- a/database/sqlite/sqlite.go +++ b/database/sqlite/sqlite.go @@ -12,6 +12,7 @@ import ( "github.com/go-vela/server/database/repo" "github.com/go-vela/server/database/sqlite/ddl" "github.com/go-vela/server/database/user" + "github.com/go-vela/server/database/worker" "github.com/go-vela/types/constants" "github.com/sirupsen/logrus" @@ -49,6 +50,8 @@ type ( repo.RepoService // https://pkg.go.dev/github.com/go-vela/server/database/user#UserService user.UserService + // https://pkg.go.dev/github.com/go-vela/server/database/worker#WorkerService + worker.WorkerService } ) @@ -260,12 +263,6 @@ func createTables(c *client) error { return fmt.Errorf("unable to create %s table: %w", constants.TableStep, err) } - // create the workers table - err = c.Sqlite.Exec(ddl.CreateWorkerTable).Error - if err != nil { - return fmt.Errorf("unable to create %s table: %w", constants.TableWorker, err) - } - return nil } @@ -328,12 +325,6 @@ func createIndexes(c *client) error { return fmt.Errorf("unable to create secrets_type_org index for the %s table: %w", constants.TableSecret, err) } - // create the workers_hostname_address index for the workers table - err = c.Sqlite.Exec(ddl.CreateWorkerHostnameAddressIndex).Error - if err != nil { - return fmt.Errorf("unable to create workers_hostname_address index for the %s table: %w", constants.TableWorker, err) - } - return nil } @@ -380,5 +371,17 @@ func createServices(c *client) error { return err } + // create the database agnostic worker service + // + // https://pkg.go.dev/github.com/go-vela/server/database/worker#New + c.WorkerService, err = worker.New( + worker.WithClient(c.Sqlite), + worker.WithLogger(c.Logger), + worker.WithSkipCreation(c.config.SkipCreation), + ) + if err != nil { + return err + } + return nil } diff --git a/router/middleware/executors/executors.go b/router/middleware/executors/executors.go index ac775ac27..c322b004c 100644 --- a/router/middleware/executors/executors.go +++ b/router/middleware/executors/executors.go @@ -30,7 +30,7 @@ func Establish() gin.HandlerFunc { e := new([]library.Executor) b := build.Retrieve(c) // retrieve the worker - w, err := database.FromContext(c).GetWorker(b.GetHost()) + w, err := database.FromContext(c).GetWorkerForHostname(b.GetHost()) if err != nil { retErr := fmt.Errorf("unable to get worker: %w", err) util.HandleError(c, http.StatusNotFound, retErr) diff --git a/router/middleware/worker/worker.go b/router/middleware/worker/worker.go index 2f68b811f..5afc8b3bf 100644 --- a/router/middleware/worker/worker.go +++ b/router/middleware/worker/worker.go @@ -33,7 +33,7 @@ func Establish() gin.HandlerFunc { logrus.Debugf("Reading worker %s", wParam) - w, err := database.FromContext(c).GetWorker(wParam) + w, err := database.FromContext(c).GetWorkerForHostname(wParam) if err != nil { retErr := fmt.Errorf("unable to read worker %s: %w", wParam, err) util.HandleError(c, http.StatusNotFound, retErr) From acad4fd6326d74a948b43587e75347ce2e698677 Mon Sep 17 00:00:00 2001 From: JordanBrockopp Date: Fri, 30 Sep 2022 08:38:51 -0500 Subject: [PATCH 12/16] fix: issues from merge conflict --- api/scm.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api/scm.go b/api/scm.go index 8808f658a..b497768cd 100644 --- a/api/scm.go +++ b/api/scm.go @@ -6,7 +6,6 @@ package api import ( "fmt" - "github.com/go-vela/types/constants" "net/http" "github.com/gin-gonic/gin" From 6fcc762a72f6857f16434e2d580f7a3085d29255 Mon Sep 17 00:00:00 2001 From: JordanBrockopp Date: Fri, 30 Sep 2022 08:44:18 -0500 Subject: [PATCH 13/16] chore: address linter feedback --- database/worker/create.go | 2 +- database/worker/service.go | 2 +- database/worker/update.go | 2 +- database/worker/worker.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/database/worker/create.go b/database/worker/create.go index 13e23edbc..60ac033c0 100644 --- a/database/worker/create.go +++ b/database/worker/create.go @@ -2,7 +2,7 @@ // // Use of this source code is governed by the LICENSE file in this repository. -// nolint: dupl // ignore similar code in update.go +//nolint:dupl // ignore similar code in update.go package worker import ( diff --git a/database/worker/service.go b/database/worker/service.go index 523764b34..b02c6f854 100644 --- a/database/worker/service.go +++ b/database/worker/service.go @@ -11,7 +11,7 @@ import ( // WorkerService represents the Vela interface for worker // functions with the supported Database backends. // -// nolint: revive // ignore name stutter +//nolint:revive // ignore name stutter type WorkerService interface { // Worker Data Definition Language Functions // diff --git a/database/worker/update.go b/database/worker/update.go index 06f07eb57..e15a9e213 100644 --- a/database/worker/update.go +++ b/database/worker/update.go @@ -2,7 +2,7 @@ // // Use of this source code is governed by the LICENSE file in this repository. -// nolint: dupl // ignore similar code in create.go +//nolint:dupl // ignore similar code in create.go package worker import ( diff --git a/database/worker/worker.go b/database/worker/worker.go index fed6f81f2..d870fb1ba 100644 --- a/database/worker/worker.go +++ b/database/worker/worker.go @@ -39,7 +39,7 @@ type ( // New creates and returns a Vela service for integrating with workers in the database. // -// nolint: revive // ignore returning unexported engine +//nolint:revive // ignore returning unexported engine func New(opts ...EngineOpt) (*engine, error) { // create new Worker engine e := new(engine) From 57206f982b236935b68902a299670b09e3f575e5 Mon Sep 17 00:00:00 2001 From: JordanBrockopp Date: Fri, 30 Sep 2022 08:49:50 -0500 Subject: [PATCH 14/16] chore: address linter feedback v2 --- database/worker/create.go | 1 - database/worker/update.go | 1 - 2 files changed, 2 deletions(-) diff --git a/database/worker/create.go b/database/worker/create.go index 60ac033c0..6c62b30b6 100644 --- a/database/worker/create.go +++ b/database/worker/create.go @@ -2,7 +2,6 @@ // // Use of this source code is governed by the LICENSE file in this repository. -//nolint:dupl // ignore similar code in update.go package worker import ( diff --git a/database/worker/update.go b/database/worker/update.go index e15a9e213..b0e475273 100644 --- a/database/worker/update.go +++ b/database/worker/update.go @@ -2,7 +2,6 @@ // // Use of this source code is governed by the LICENSE file in this repository. -//nolint:dupl // ignore similar code in create.go package worker import ( From 286e06ef2e1fcf6c25b25a8e88ee4e0915689743 Mon Sep 17 00:00:00 2001 From: JordanBrockopp Date: Fri, 7 Oct 2022 18:40:39 -0500 Subject: [PATCH 15/16] chore: remove Limit(1) with Take() --- database/worker/get.go | 1 - database/worker/get_hostname.go | 1 - 2 files changed, 2 deletions(-) diff --git a/database/worker/get.go b/database/worker/get.go index c567cafd7..dd2b07ecc 100644 --- a/database/worker/get.go +++ b/database/worker/get.go @@ -21,7 +21,6 @@ func (e *engine) GetWorker(id int64) (*library.Worker, error) { err := e.client. Table(constants.TableWorker). Where("id = ?", id). - Limit(1). Take(w). Error if err != nil { diff --git a/database/worker/get_hostname.go b/database/worker/get_hostname.go index bf4227dcb..6bcf42a2b 100644 --- a/database/worker/get_hostname.go +++ b/database/worker/get_hostname.go @@ -24,7 +24,6 @@ func (e *engine) GetWorkerForHostname(hostname string) (*library.Worker, error) err := e.client. Table(constants.TableWorker). Where("hostname = ?", hostname). - Limit(1). Take(w). Error if err != nil { From 7991f03903622a820698678037cbe482828d72a2 Mon Sep 17 00:00:00 2001 From: JordanBrockopp Date: Fri, 7 Oct 2022 19:00:13 -0500 Subject: [PATCH 16/16] refactor(database/worker): name of index --- database/postgres/postgres.go | 2 +- database/postgres/postgres_test.go | 4 ++-- database/worker/index.go | 6 +++--- database/worker/index_test.go | 2 +- database/worker/worker_test.go | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/database/postgres/postgres.go b/database/postgres/postgres.go index 99965ff68..04d844675 100644 --- a/database/postgres/postgres.go +++ b/database/postgres/postgres.go @@ -156,7 +156,7 @@ func NewTest() (*client, sqlmock.Sqlmock, error) { _mock.ExpectExec(user.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(user.CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(worker.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(worker.CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(worker.CreateHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) // create the new mock Postgres database client // diff --git a/database/postgres/postgres_test.go b/database/postgres/postgres_test.go index 6ab864337..ec80173f1 100644 --- a/database/postgres/postgres_test.go +++ b/database/postgres/postgres_test.go @@ -106,7 +106,7 @@ func TestPostgres_setupDatabase(t *testing.T) { _mock.ExpectExec(user.CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1)) // ensure the mock expects the worker queries _mock.ExpectExec(worker.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(worker.CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(worker.CreateHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) // setup the skip test database client _skipDatabase, _skipMock, err := NewTest() @@ -268,7 +268,7 @@ func TestPostgres_createServices(t *testing.T) { _mock.ExpectExec(user.CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1)) // ensure the mock expects the worker queries _mock.ExpectExec(worker.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(worker.CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(worker.CreateHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) tests := []struct { failure bool diff --git a/database/worker/index.go b/database/worker/index.go index 8fc79da3d..f8f01a4b6 100644 --- a/database/worker/index.go +++ b/database/worker/index.go @@ -5,9 +5,9 @@ package worker const ( - // CreateWorkerHostnameAddressIndex represents a query to create an + // CreateHostnameAddressIndex represents a query to create an // index on the workers table for the hostname and address columns. - CreateWorkerHostnameAddressIndex = ` + CreateHostnameAddressIndex = ` CREATE INDEX IF NOT EXISTS workers_hostname_address @@ -20,5 +20,5 @@ func (e *engine) CreateWorkerIndexes() error { e.logger.Tracef("creating indexes for workers table in the database") // create the hostname and address columns index for the workers table - return e.client.Exec(CreateWorkerHostnameAddressIndex).Error + return e.client.Exec(CreateHostnameAddressIndex).Error } diff --git a/database/worker/index_test.go b/database/worker/index_test.go index 76f5cbb94..ead204e5c 100644 --- a/database/worker/index_test.go +++ b/database/worker/index_test.go @@ -15,7 +15,7 @@ func TestWorker_Engine_CreateWorkerIndexes(t *testing.T) { _postgres, _mock := testPostgres(t) defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() - _mock.ExpectExec(CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(CreateHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _sqlite := testSqlite(t) defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() diff --git a/database/worker/worker_test.go b/database/worker/worker_test.go index bc23afb5d..48deabe54 100644 --- a/database/worker/worker_test.go +++ b/database/worker/worker_test.go @@ -28,7 +28,7 @@ func TestWorker_New(t *testing.T) { defer _sql.Close() _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(CreateHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) _config := &gorm.Config{SkipDefaultTransaction: true} @@ -119,7 +119,7 @@ func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) { } _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) + _mock.ExpectExec(CreateHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1)) // create the new mock Postgres database client //