diff --git a/api/build.go b/api/build.go index 523825a26..94cb83d30 100644 --- a/api/build.go +++ b/api/build.go @@ -1837,7 +1837,7 @@ func CancelBuild(c *gin.Context) { for page > 0 { // retrieve build steps (per page) from the database - stepsPart, err := database.FromContext(c).GetBuildStepList(b, page, perPage) + stepsPart, _, err := database.FromContext(c).ListStepsForBuild(b, map[string]interface{}{}, page, perPage) if err != nil { retErr := fmt.Errorf("unable to retrieve steps for build %s: %w", entry, err) util.HandleError(c, http.StatusNotFound, retErr) diff --git a/api/metrics.go b/api/metrics.go index 14a277be6..4bdeb044e 100644 --- a/api/metrics.go +++ b/api/metrics.go @@ -324,7 +324,7 @@ func recordGauges(c *gin.Context) { // step_image_count if q.StepImageCount { // send API call to capture the total number of step images - stepImageMap, err := database.FromContext(c).GetStepImageCount() + stepImageMap, err := database.FromContext(c).ListStepImageCount() if err != nil { logrus.Errorf("unable to get count of all step images: %v", err) } @@ -337,7 +337,7 @@ func recordGauges(c *gin.Context) { // step_status_count if q.StepStatusCount { // send API call to capture the total number of step statuses - stepStatusMap, err := database.FromContext(c).GetStepStatusCount() + stepStatusMap, err := database.FromContext(c).ListStepStatusCount() if err != nil { logrus.Errorf("unable to get count of all step statuses: %v", err) } diff --git a/api/step.go b/api/step.go index ba7ec6119..465ce89f0 100644 --- a/api/step.go +++ b/api/step.go @@ -127,7 +127,7 @@ func CreateStep(c *gin.Context) { } // send API call to capture the created step - s, _ := database.FromContext(c).GetStep(input.GetNumber(), b) + s, _ := database.FromContext(c).GetStepForBuild(b, input.GetNumber()) c.JSON(http.StatusCreated, s) } @@ -235,18 +235,8 @@ func GetSteps(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 steps for the build - t, err := database.FromContext(c).GetBuildStepCount(b) - if err != nil { - retErr := fmt.Errorf("unable to get steps count for build %s: %w", entry, err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - // send API call to capture the list of steps for the build - s, err := database.FromContext(c).GetBuildStepList(b, page, perPage) + s, t, err := database.FromContext(c).ListStepsForBuild(b, map[string]interface{}{}, page, perPage) if err != nil { retErr := fmt.Errorf("unable to get steps for build %s: %w", entry, err) @@ -464,7 +454,7 @@ func UpdateStep(c *gin.Context) { } // send API call to capture the updated step - s, _ = database.FromContext(c).GetStep(s.GetNumber(), b) + s, _ = database.FromContext(c).GetStepForBuild(b, s.GetNumber()) c.JSON(http.StatusOK, s) } @@ -535,7 +525,7 @@ func DeleteStep(c *gin.Context) { }).Infof("deleting step %s", entry) // send API call to remove the step - err := database.FromContext(c).DeleteStep(s.GetID()) + err := database.FromContext(c).DeleteStep(s) if err != nil { retErr := fmt.Errorf("unable to delete step %s: %w", entry, err) @@ -576,7 +566,7 @@ func planSteps(database database.Service, p *pipeline.Build, b *library.Build) ( } // send API call to capture the created step - s, err = database.GetStep(s.GetNumber(), b) + s, err = database.GetStepForBuild(b, s.GetNumber()) if err != nil { return steps, fmt.Errorf("unable to get step %s: %w", s.GetName(), err) } @@ -625,7 +615,7 @@ func planSteps(database database.Service, p *pipeline.Build, b *library.Build) ( } // send API call to capture the created step - s, err = database.GetStep(s.GetNumber(), b) + s, err = database.GetStepForBuild(b, s.GetNumber()) if err != nil { return steps, fmt.Errorf("unable to get step %s: %w", s.GetName(), err) } diff --git a/database/postgres/ddl/step.go b/database/postgres/ddl/step.go deleted file mode 100644 index 8e5540e95..000000000 --- a/database/postgres/ddl/step.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 ( - // CreateStepTable represents a query to - // create the steps table for Vela. - CreateStepTable = ` -CREATE TABLE -IF NOT EXISTS -steps ( - id SERIAL PRIMARY KEY, - repo_id INTEGER, - build_id INTEGER, - number INTEGER, - name VARCHAR(250), - image VARCHAR(500), - stage VARCHAR(250), - status VARCHAR(250), - error VARCHAR(500), - exit_code INTEGER, - created INTEGER, - started INTEGER, - finished INTEGER, - host VARCHAR(250), - runtime VARCHAR(250), - distribution VARCHAR(250), - UNIQUE(build_id, number) -); -` -) diff --git a/database/postgres/dml/step.go b/database/postgres/dml/step.go deleted file mode 100644 index 7aa82b29a..000000000 --- a/database/postgres/dml/step.go +++ /dev/null @@ -1,67 +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 ( - // ListSteps represents a query to - // list all steps in the database. - ListSteps = ` -SELECT * -FROM steps; -` - - // ListBuildSteps represents a query to list - // all steps for a build_id in the database. - ListBuildSteps = ` -SELECT * -FROM steps -WHERE build_id = ? -ORDER BY id DESC -LIMIT ? -OFFSET ?; -` - - // SelectBuildStepsCount represents a query to select - // the count of steps for a build_id in the database. - SelectBuildStepsCount = ` -SELECT count(*) as count -FROM steps -WHERE build_id = ? -` - - // SelectStepImagesCount represents a query to select - // the count of an images appearances in the database. - SelectStepImagesCount = ` -SELECT image, count(image) as count -FROM steps -GROUP BY image; -` - - // SelectStepStatusesCount represents a query to select - // the count of a statuses appearances in the database. - SelectStepStatusesCount = ` -SELECT status, count(status) as count -FROM steps -GROUP BY status; -` - - // SelectBuildStep represents a query to select a - // step for a build_id and number in the database. - SelectBuildStep = ` -SELECT * -FROM steps -WHERE build_id = ? -AND number = ? -LIMIT 1; -` - - // DeleteStep represents a query to - // remove a step from the database. - DeleteStep = ` -DELETE -FROM steps -WHERE id = ?; -` -) diff --git a/database/postgres/postgres.go b/database/postgres/postgres.go index 7aa273a8f..c96883b59 100644 --- a/database/postgres/postgres.go +++ b/database/postgres/postgres.go @@ -15,6 +15,7 @@ import ( "github.com/go-vela/server/database/postgres/ddl" "github.com/go-vela/server/database/repo" "github.com/go-vela/server/database/secret" + "github.com/go-vela/server/database/step" "github.com/go-vela/server/database/user" "github.com/go-vela/server/database/worker" "github.com/go-vela/types/constants" @@ -58,6 +59,8 @@ type ( repo.RepoService // https://pkg.go.dev/github.com/go-vela/server/database/secret#SecretService secret.SecretService + // https://pkg.go.dev/github.com/go-vela/server/database/step#StepService + step.StepService // 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 @@ -175,6 +178,8 @@ func NewTest() (*client, sqlmock.Sqlmock, error) { _mock.ExpectExec(secret.CreateTypeOrgRepo).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(secret.CreateTypeOrgTeam).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(secret.CreateTypeOrg).WillReturnResult(sqlmock.NewResult(1, 1)) + // ensure the mock expects the step queries + _mock.ExpectExec(step.CreatePostgresTable).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)) @@ -273,12 +278,6 @@ func createTables(c *client) error { return fmt.Errorf("unable to create %s table: %w", constants.TableService, err) } - // create the steps table - err = c.Postgres.Exec(ddl.CreateStepTable).Error - if err != nil { - return fmt.Errorf("unable to create %s table: %w", constants.TableStep, err) - } - return nil } @@ -382,6 +381,18 @@ func createServices(c *client) error { return err } + // create the database agnostic step service + // + // https://pkg.go.dev/github.com/go-vela/server/database/repo#New + c.StepService, err = step.New( + step.WithClient(c.Postgres), + step.WithLogger(c.Logger), + step.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 6db511a83..03c49a7d6 100644 --- a/database/postgres/postgres_test.go +++ b/database/postgres/postgres_test.go @@ -16,6 +16,7 @@ import ( "github.com/go-vela/server/database/postgres/ddl" "github.com/go-vela/server/database/repo" "github.com/go-vela/server/database/secret" + "github.com/go-vela/server/database/step" "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) { // ensure the mock expects the table queries _mock.ExpectExec(ddl.CreateBuildTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateServiceTable).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(ddl.CreateStepTable).WillReturnResult(sqlmock.NewResult(1, 1)) // ensure the mock expects the index queries _mock.ExpectExec(ddl.CreateBuildRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1)) @@ -107,6 +107,8 @@ func TestPostgres_setupDatabase(t *testing.T) { _mock.ExpectExec(secret.CreateTypeOrgRepo).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(secret.CreateTypeOrgTeam).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(secret.CreateTypeOrg).WillReturnResult(sqlmock.NewResult(1, 1)) + // ensure the mock expects the step queries + _mock.ExpectExec(step.CreatePostgresTable).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)) @@ -175,7 +177,6 @@ func TestPostgres_createTables(t *testing.T) { // ensure the mock expects the table queries _mock.ExpectExec(ddl.CreateBuildTable).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(ddl.CreateServiceTable).WillReturnResult(sqlmock.NewResult(1, 1)) - _mock.ExpectExec(ddl.CreateStepTable).WillReturnResult(sqlmock.NewResult(1, 1)) tests := []struct { failure bool @@ -272,6 +273,8 @@ func TestPostgres_createServices(t *testing.T) { _mock.ExpectExec(secret.CreateTypeOrgRepo).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(secret.CreateTypeOrgTeam).WillReturnResult(sqlmock.NewResult(1, 1)) _mock.ExpectExec(secret.CreateTypeOrg).WillReturnResult(sqlmock.NewResult(1, 1)) + // ensure the mock expects the step queries + _mock.ExpectExec(step.CreatePostgresTable).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)) diff --git a/database/postgres/step.go b/database/postgres/step.go deleted file mode 100644 index 5f7ecd50e..000000000 --- a/database/postgres/step.go +++ /dev/null @@ -1,94 +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" -) - -// GetStep gets a step by number and build ID from the database. -func (c *client) GetStep(number int, b *library.Build) (*library.Step, error) { - c.Logger.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "step": number, - }).Tracef("getting step %d for build %d from the database", number, b.GetNumber()) - - // variable to store query results - s := new(database.Step) - - // send query to the database and store result in variable - result := c.Postgres. - Table(constants.TableStep). - Raw(dml.SelectBuildStep, b.GetID(), number). - Scan(s) - - // 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 s.ToLibrary(), result.Error -} - -// CreateStep creates a new step in the database. -func (c *client) CreateStep(s *library.Step) error { - c.Logger.WithFields(logrus.Fields{ - "step": s.GetNumber(), - }).Tracef("creating step %s in the database", s.GetName()) - - // cast to database type - step := database.StepFromLibrary(s) - - // validate the necessary fields are populated - err := step.Validate() - if err != nil { - return err - } - - // send query to the database - return c.Postgres. - Table(constants.TableStep). - Create(step).Error -} - -// UpdateStep updates a step in the database. -func (c *client) UpdateStep(s *library.Step) error { - c.Logger.WithFields(logrus.Fields{ - "step": s.GetNumber(), - }).Tracef("updating step %s in the database", s.GetName()) - - // cast to database type - step := database.StepFromLibrary(s) - - // validate the necessary fields are populated - err := step.Validate() - if err != nil { - return err - } - - // send query to the database - return c.Postgres. - Table(constants.TableStep). - Save(step).Error -} - -// DeleteStep deletes a step by unique ID from the database. -func (c *client) DeleteStep(id int64) error { - c.Logger.Tracef("deleting step %d from the database", id) - - // send query to the database - return c.Postgres. - Table(constants.TableStep). - Exec(dml.DeleteStep, id).Error -} diff --git a/database/postgres/step_count.go b/database/postgres/step_count.go deleted file mode 100644 index 64fe6952b..000000000 --- a/database/postgres/step_count.go +++ /dev/null @@ -1,90 +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" -) - -// GetBuildStepCount gets a count of all steps by build ID from the database. -func (c *client) GetBuildStepCount(b *library.Build) (int64, error) { - c.Logger.WithFields(logrus.Fields{ - "build": b.GetNumber(), - }).Tracef("getting count of steps for build %d from the database", b.GetNumber()) - - // variable to store query results - var s int64 - - // send query to the database and store result in variable - err := c.Postgres. - Table(constants.TableStep). - Raw(dml.SelectBuildStepsCount, b.GetID()). - Pluck("count", &s).Error - - return s, err -} - -// GetStepImageCount gets a count of all step images -// and the count of their occurrence in the database. -func (c *client) GetStepImageCount() (map[string]float64, error) { - c.Logger.Tracef("getting count of all images for steps from the database") - - type imageCount struct { - Image string - Count int - } - - // variable to store query results - images := new([]imageCount) - counts := make(map[string]float64) - - // send query to the database and store result in variable - err := c.Postgres. - Table(constants.TableStep). - Raw(dml.SelectStepImagesCount). - Scan(images).Error - - for _, image := range *images { - counts[image.Image] = float64(image.Count) - } - - return counts, err -} - -// GetStepStatusCount gets a list of all step statuses -// and the count of their occurrence in the database. -func (c *client) GetStepStatusCount() (map[string]float64, error) { - c.Logger.Trace("getting count of all statuses for steps from the database") - - type statusCount struct { - Status string - Count int - } - - // variable to store query results - s := new([]statusCount) - counts := map[string]float64{ - "pending": 0, - "failure": 0, - "killed": 0, - "running": 0, - "success": 0, - } - - // send query to the database and store result in variable - err := c.Postgres. - Table(constants.TableStep). - Raw(dml.SelectStepStatusesCount). - Scan(s).Error - - for _, status := range *s { - counts[status.Status] = float64(status.Count) - } - - return counts, err -} diff --git a/database/postgres/step_count_test.go b/database/postgres/step_count_test.go deleted file mode 100644 index e065ade38..000000000 --- a/database/postgres/step_count_test.go +++ /dev/null @@ -1,194 +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_GetBuildStepCount(t *testing.T) { - // setup types - _build := testBuild() - _build.SetID(1) - _build.SetRepoID(1) - _build.SetNumber(1) - - // 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.SelectBuildStepsCount, 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.GetBuildStepCount(_build) - - if test.failure { - if err == nil { - t.Errorf("GetBuildStepCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetBuildStepCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetBuildStepCount is %v, want %v", got, test.want) - } - } -} - -func TestPostgres_Client_GetStepImageCount(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}).Raw(dml.SelectStepImagesCount).Statement - - // create expected return in mock - _rows := sqlmock.NewRows([]string{"image", "count"}).AddRow("foo", 0) - - // ensure the mock expects the query - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want map[string]float64 - }{ - { - failure: false, - want: map[string]float64{"foo": 0}, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetStepImageCount() - - if test.failure { - if err == nil { - t.Errorf("GetStepImageCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetStepImageCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetStepImageCount is %v, want %v", got, test.want) - } - } -} - -func TestPostgres_Client_GetStepStatusCount(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}).Raw(dml.SelectStepStatusesCount).Statement - - // create expected return in mock - _rows := sqlmock.NewRows([]string{"status", "count"}). - AddRow("failure", 0). - AddRow("killed", 0). - AddRow("pending", 0). - AddRow("running", 0). - AddRow("success", 0) - - // ensure the mock expects the query - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want map[string]float64 - }{ - { - failure: false, - want: map[string]float64{ - "pending": 0, - "failure": 0, - "killed": 0, - "running": 0, - "success": 0, - }, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetStepStatusCount() - - if test.failure { - if err == nil { - t.Errorf("GetStepStatusCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetStepStatusCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetStepStatusCount is %v, want %v", got, test.want) - } - } -} diff --git a/database/postgres/step_list.go b/database/postgres/step_list.go deleted file mode 100644 index 8e678f382..000000000 --- a/database/postgres/step_list.go +++ /dev/null @@ -1,71 +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" -) - -// GetStepList gets a list of all steps from the database. -func (c *client) GetStepList() ([]*library.Step, error) { - c.Logger.Trace("listing steps from the database") - - // variable to store query results - s := new([]database.Step) - - // send query to the database and store result in variable - err := c.Postgres. - Table(constants.TableStep). - Raw(dml.ListSteps). - Scan(s).Error - - // variable we want to return - steps := []*library.Step{} - // iterate through all query results - for _, step := range *s { - // https://golang.org/doc/faq#closures_and_goroutines - tmp := step - - // convert query result to library type - steps = append(steps, tmp.ToLibrary()) - } - - return steps, err -} - -// GetBuildStepList gets a list of steps by build ID from the database. -func (c *client) GetBuildStepList(b *library.Build, page, perPage int) ([]*library.Step, error) { - c.Logger.WithFields(logrus.Fields{ - "build": b.GetNumber(), - }).Tracef("listing steps for build %d from the database", b.GetNumber()) - - // variable to store query results - s := new([]database.Step) - // 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.TableStep). - Raw(dml.ListBuildSteps, b.GetID(), perPage, offset). - Scan(s).Error - - // variable we want to return - steps := []*library.Step{} - // iterate through all query results - for _, step := range *s { - // https://golang.org/doc/faq#closures_and_goroutines - tmp := step - - // convert query result to library type - steps = append(steps, tmp.ToLibrary()) - } - - return steps, err -} diff --git a/database/postgres/step_list_test.go b/database/postgres/step_list_test.go deleted file mode 100644 index f5985690f..000000000 --- a/database/postgres/step_list_test.go +++ /dev/null @@ -1,168 +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_GetStepList(t *testing.T) { - // setup types - _stepOne := testStep() - _stepOne.SetID(1) - _stepOne.SetRepoID(1) - _stepOne.SetBuildID(1) - _stepOne.SetNumber(1) - _stepOne.SetName("foo") - _stepOne.SetImage("bar") - - _stepTwo := testStep() - _stepTwo.SetID(2) - _stepTwo.SetRepoID(1) - _stepTwo.SetBuildID(1) - _stepTwo.SetNumber(1) - _stepTwo.SetName("bar") - _stepTwo.SetImage("foo") - - // 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.ListSteps).Statement - - // create expected return in mock - _rows := sqlmock.NewRows( - []string{"id", "repo_id", "build_id", "number", "name", "image", "stage", "status", "error", "exit_code", "created", "started", "finished", "host", "runtime", "distribution"}, - ).AddRow(1, 1, 1, 1, "foo", "bar", "", "", "", 0, 0, 0, 0, "", "", ""). - AddRow(2, 1, 1, 1, "bar", "foo", "", "", "", 0, 0, 0, 0, "", "", "") - - // ensure the mock expects the query - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want []*library.Step - }{ - { - failure: false, - want: []*library.Step{_stepOne, _stepTwo}, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetStepList() - - if test.failure { - if err == nil { - t.Errorf("GetStepList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetStepList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetStepList is %v, want %v", got, test.want) - } - } -} - -func TestPostgres_Client_GetBuildStepList(t *testing.T) { - // setup types - _build := testBuild() - _build.SetID(1) - _build.SetRepoID(1) - _build.SetNumber(1) - - _stepOne := testStep() - _stepOne.SetID(1) - _stepOne.SetRepoID(1) - _stepOne.SetBuildID(1) - _stepOne.SetNumber(1) - _stepOne.SetName("foo") - _stepOne.SetImage("bar") - - _stepTwo := testStep() - _stepTwo.SetID(2) - _stepTwo.SetRepoID(1) - _stepTwo.SetBuildID(1) - _stepTwo.SetNumber(1) - _stepTwo.SetName("bar") - _stepTwo.SetImage("foo") - - // 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.ListBuildSteps, 1, 1, 10).Statement - - // create expected return in mock - _rows := sqlmock.NewRows( - []string{"id", "repo_id", "build_id", "number", "name", "image", "stage", "status", "error", "exit_code", "created", "started", "finished", "host", "runtime", "distribution"}, - ).AddRow(1, 1, 1, 1, "foo", "bar", "", "", "", 0, 0, 0, 0, "", "", ""). - AddRow(2, 1, 1, 1, "bar", "foo", "", "", "", 0, 0, 0, 0, "", "", "") - - // ensure the mock expects the query - _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - want []*library.Step - }{ - { - failure: false, - want: []*library.Step{_stepOne, _stepTwo}, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetBuildStepList(_build, 1, 10) - - if test.failure { - if err == nil { - t.Errorf("GetBuildStepList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetBuildStepList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetBuildStepList is %v, want %v", got, test.want) - } - } -} diff --git a/database/postgres/step_test.go b/database/postgres/step_test.go deleted file mode 100644 index 1b0e79388..000000000 --- a/database/postgres/step_test.go +++ /dev/null @@ -1,268 +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_GetStep(t *testing.T) { - // setup types - _build := testBuild() - _build.SetID(1) - _build.SetRepoID(1) - _build.SetNumber(1) - - _step := testStep() - _step.SetID(1) - _step.SetRepoID(1) - _step.SetBuildID(1) - _step.SetNumber(1) - _step.SetName("foo") - _step.SetImage("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.SelectBuildStep, 1, 1).Statement - - // create expected return in mock - _rows := sqlmock.NewRows( - []string{"id", "repo_id", "build_id", "number", "name", "image", "stage", "status", "error", "exit_code", "created", "started", "finished", "host", "runtime", "distribution"}, - ).AddRow(1, 1, 1, 1, "foo", "bar", "", "", "", 0, 0, 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.Step - }{ - { - failure: false, - want: _step, - }, - { - failure: true, - want: nil, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetStep(1, _build) - - if test.failure { - if err == nil { - t.Errorf("GetStep should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetStep returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetStep is %v, want %v", got, test.want) - } - } -} - -func TestPostgres_Client_CreateStep(t *testing.T) { - // setup types - _step := testStep() - _step.SetID(1) - _step.SetRepoID(1) - _step.SetBuildID(1) - _step.SetNumber(1) - _step.SetName("foo") - _step.SetImage("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() }() - - // create expected return in mock - _rows := sqlmock.NewRows([]string{"id"}).AddRow(1) - - // ensure the mock expects the query - _mock.ExpectQuery(`INSERT INTO "steps" ("build_id","repo_id","number","name","image","stage","status","error","exit_code","created","started","finished","host","runtime","distribution","id") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16) RETURNING "id"`). - WithArgs(1, 1, 1, "foo", "bar", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1). - WillReturnRows(_rows) - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - err := _database.CreateStep(_step) - - if test.failure { - if err == nil { - t.Errorf("CreateStep should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("CreateStep returned err: %v", err) - } - } -} - -func TestPostgres_Client_UpdateStep(t *testing.T) { - // setup types - _step := testStep() - _step.SetID(1) - _step.SetRepoID(1) - _step.SetBuildID(1) - _step.SetNumber(1) - _step.SetName("foo") - _step.SetImage("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() }() - - // ensure the mock expects the query - _mock.ExpectExec(`UPDATE "steps" SET "build_id"=$1,"repo_id"=$2,"number"=$3,"name"=$4,"image"=$5,"stage"=$6,"status"=$7,"error"=$8,"exit_code"=$9,"created"=$10,"started"=$11,"finished"=$12,"host"=$13,"runtime"=$14,"distribution"=$15 WHERE "id" = $16`). - WithArgs(1, 1, 1, "foo", "bar", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1). - WillReturnResult(sqlmock.NewResult(1, 1)) - - // setup tests - tests := []struct { - failure bool - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - err := _database.UpdateStep(_step) - - if test.failure { - if err == nil { - t.Errorf("UpdateStep should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("UpdateStep returned err: %v", err) - } - } -} - -func TestPostgres_Client_DeleteStep(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.DeleteStep, 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.DeleteStep(1) - - if test.failure { - if err == nil { - t.Errorf("DeleteStep should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("DeleteStep returned err: %v", err) - } - } -} - -// testStep is a test helper function to create a -// library Step type with all fields set to their -// zero values. -func testStep() *library.Step { - i64 := int64(0) - i := 0 - str := "" - - return &library.Step{ - ID: &i64, - BuildID: &i64, - RepoID: &i64, - Number: &i, - Name: &str, - Image: &str, - Stage: &str, - Status: &str, - Error: &str, - ExitCode: &i, - Created: &i64, - Started: &i64, - Finished: &i64, - Host: &str, - Runtime: &str, - Distribution: &str, - } -} diff --git a/database/service.go b/database/service.go index 81d8ad960..306c77720 100644 --- a/database/service.go +++ b/database/service.go @@ -10,6 +10,7 @@ import ( "github.com/go-vela/server/database/pipeline" "github.com/go-vela/server/database/repo" "github.com/go-vela/server/database/secret" + "github.com/go-vela/server/database/step" "github.com/go-vela/server/database/user" "github.com/go-vela/server/database/worker" "github.com/go-vela/types/library" @@ -95,37 +96,9 @@ type Service interface { // related to secrets stored in the database. secret.SecretService - // Step Database Interface Functions - - // GetStep defines a function that - // gets a step by number and build ID. - GetStep(int, *library.Build) (*library.Step, error) - // GetStepList defines a function that - // gets a list of all steps. - GetStepList() ([]*library.Step, error) - // GetBuildStepList defines a function that - // gets a list of steps by build ID. - GetBuildStepList(*library.Build, int, int) ([]*library.Step, error) - // GetBuildStepCount defines a function that - // gets the count of steps by build ID. - GetBuildStepCount(*library.Build) (int64, error) - // GetStepImageCount defines a function that - // gets a list of all step images and the - // count of their occurrence. - GetStepImageCount() (map[string]float64, error) - // GetStepStatusCount defines a function that - // gets a list of all step statuses and the - // count of their occurrence. - GetStepStatusCount() (map[string]float64, error) - // CreateStep defines a function that - // creates a new step. - CreateStep(*library.Step) error - // UpdateStep defines a function that - // updates a step. - UpdateStep(*library.Step) error - // DeleteStep defines a function that - // deletes a step by unique ID. - DeleteStep(int64) error + // StepService provides the interface for functionality + // related to steps stored in the database. + step.StepService // Service Database Interface Functions diff --git a/database/sqlite/ddl/step.go b/database/sqlite/ddl/step.go deleted file mode 100644 index 7809dcc3f..000000000 --- a/database/sqlite/ddl/step.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 ( - // CreateStepTable represents a query to - // create the steps table for Vela. - CreateStepTable = ` -CREATE TABLE -IF NOT EXISTS -steps ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - repo_id INTEGER, - build_id INTEGER, - number INTEGER, - name TEXT, - image TEXT, - stage TEXT, - status TEXT, - error TEXT, - exit_code INTEGER, - created INTEGER, - started INTEGER, - finished INTEGER, - host TEXT, - runtime TEXT, - distribution TEXT, - UNIQUE(build_id, number) -); -` -) diff --git a/database/sqlite/dml/step.go b/database/sqlite/dml/step.go deleted file mode 100644 index 862ea2a67..000000000 --- a/database/sqlite/dml/step.go +++ /dev/null @@ -1,67 +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 ( - // ListSteps represents a query to - // list all steps in the database. - ListSteps = ` -SELECT * -FROM steps; -` - - // ListBuildSteps represents a query to list - // all steps for a build_id in the database. - ListBuildSteps = ` -SELECT * -FROM steps -WHERE build_id = ? -ORDER BY id DESC -LIMIT ? -OFFSET ?; -` - - // SelectBuildStepsCount represents a query to select - // the count of steps for a build_id in the database. - SelectBuildStepsCount = ` -SELECT count(*) as count -FROM steps -WHERE build_id = ? -` - - // SelectStepImagesCount represents a query to select - // the count of an images appearances in the database. - SelectStepImagesCount = ` -SELECT image, count(image) as count -FROM steps -GROUP BY image; -` - - // SelectStepStatusesCount represents a query to select - // the count of step status' appearances in the database. - SelectStepStatusesCount = ` -SELECT status, count(status) as count -FROM steps -GROUP BY status; -` - - // SelectBuildStep represents a query to select a - // step for a build_id and number in the database. - SelectBuildStep = ` -SELECT * -FROM steps -WHERE build_id = ? -AND number = ? -LIMIT 1; -` - - // DeleteStep represents a query to - // remove a step from the database. - DeleteStep = ` -DELETE -FROM steps -WHERE id = ?; -` -) diff --git a/database/sqlite/sqlite.go b/database/sqlite/sqlite.go index 87645f323..abc382a37 100644 --- a/database/sqlite/sqlite.go +++ b/database/sqlite/sqlite.go @@ -14,6 +14,7 @@ import ( "github.com/go-vela/server/database/repo" "github.com/go-vela/server/database/secret" "github.com/go-vela/server/database/sqlite/ddl" + "github.com/go-vela/server/database/step" "github.com/go-vela/server/database/user" "github.com/go-vela/server/database/worker" "github.com/go-vela/types/constants" @@ -57,6 +58,8 @@ type ( repo.RepoService // https://pkg.go.dev/github.com/go-vela/server/database/secret#SecretService secret.SecretService + // https://pkg.go.dev/github.com/go-vela/server/database/step#StepService + step.StepService // 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 @@ -248,12 +251,6 @@ func createTables(c *client) error { return fmt.Errorf("unable to create %s table: %w", constants.TableService, err) } - // create the steps table - err = c.Sqlite.Exec(ddl.CreateStepTable).Error - if err != nil { - return fmt.Errorf("unable to create %s table: %w", constants.TableStep, err) - } - return nil } @@ -357,6 +354,18 @@ func createServices(c *client) error { return err } + // create the database agnostic step service + // + // https://pkg.go.dev/github.com/go-vela/server/database/step#New + c.StepService, err = step.New( + step.WithClient(c.Sqlite), + step.WithLogger(c.Logger), + step.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/step.go b/database/sqlite/step.go deleted file mode 100644 index f055b2ebe..000000000 --- a/database/sqlite/step.go +++ /dev/null @@ -1,94 +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" -) - -// GetStep gets a step by number and build ID from the database. -func (c *client) GetStep(number int, b *library.Build) (*library.Step, error) { - c.Logger.WithFields(logrus.Fields{ - "build": b.GetNumber(), - "step": number, - }).Tracef("getting step %d for build %d from the database", number, b.GetNumber()) - - // variable to store query results - s := new(database.Step) - - // send query to the database and store result in variable - result := c.Sqlite. - Table(constants.TableStep). - Raw(dml.SelectBuildStep, b.GetID(), number). - Scan(s) - - // 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 s.ToLibrary(), result.Error -} - -// CreateStep creates a new step in the database. -func (c *client) CreateStep(s *library.Step) error { - c.Logger.WithFields(logrus.Fields{ - "step": s.GetNumber(), - }).Tracef("creating step %s in the database", s.GetName()) - - // cast to database type - step := database.StepFromLibrary(s) - - // validate the necessary fields are populated - err := step.Validate() - if err != nil { - return err - } - - // send query to the database - return c.Sqlite. - Table(constants.TableStep). - Create(step).Error -} - -// UpdateStep updates a step in the database. -func (c *client) UpdateStep(s *library.Step) error { - c.Logger.WithFields(logrus.Fields{ - "step": s.GetNumber(), - }).Tracef("updating step %s in the database", s.GetName()) - - // cast to database type - step := database.StepFromLibrary(s) - - // validate the necessary fields are populated - err := step.Validate() - if err != nil { - return err - } - - // send query to the database - return c.Sqlite. - Table(constants.TableStep). - Save(step).Error -} - -// DeleteStep deletes a step by unique ID from the database. -func (c *client) DeleteStep(id int64) error { - c.Logger.Tracef("deleting step %d from the database", id) - - // send query to the database - return c.Sqlite. - Table(constants.TableStep). - Exec(dml.DeleteStep, id).Error -} diff --git a/database/sqlite/step_count.go b/database/sqlite/step_count.go deleted file mode 100644 index 1d7e45c87..000000000 --- a/database/sqlite/step_count.go +++ /dev/null @@ -1,90 +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" -) - -// GetBuildStepCount gets a count of all steps by build ID from the database. -func (c *client) GetBuildStepCount(b *library.Build) (int64, error) { - c.Logger.WithFields(logrus.Fields{ - "build": b.GetNumber(), - }).Tracef("getting count of steps for build %d from the database", b.GetNumber()) - - // variable to store query results - var s int64 - - // send query to the database and store result in variable - err := c.Sqlite. - Table(constants.TableStep). - Raw(dml.SelectBuildStepsCount, b.GetID()). - Pluck("count", &s).Error - - return s, err -} - -// GetStepImageCount gets a count of all step images -// and the count of their occurrence in the database. -func (c *client) GetStepImageCount() (map[string]float64, error) { - c.Logger.Tracef("getting count of all images for steps from the database") - - type imageCount struct { - Image string - Count int - } - - // variable to store query results - images := new([]imageCount) - counts := make(map[string]float64) - - // send query to the database and store result in variable - err := c.Sqlite. - Table(constants.TableStep). - Raw(dml.SelectStepImagesCount). - Scan(images).Error - - for _, image := range *images { - counts[image.Image] = float64(image.Count) - } - - return counts, err -} - -// GetStepStatusCount gets a list of all step statuses -// and the count of their occurrence in the database. -func (c *client) GetStepStatusCount() (map[string]float64, error) { - c.Logger.Trace("getting count of all statuses for steps from the database") - - type statusCount struct { - Status string - Count int - } - - // variable to store query results - s := new([]statusCount) - counts := map[string]float64{ - "pending": 0, - "failure": 0, - "killed": 0, - "running": 0, - "success": 0, - } - - // send query to the database and store result in variable - err := c.Sqlite. - Table(constants.TableStep). - Raw(dml.SelectStepStatusesCount). - Scan(s).Error - - for _, status := range *s { - counts[status.Status] = float64(status.Count) - } - - return counts, err -} diff --git a/database/sqlite/step_count_test.go b/database/sqlite/step_count_test.go deleted file mode 100644 index 83bf7138a..000000000 --- a/database/sqlite/step_count_test.go +++ /dev/null @@ -1,198 +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 step table - err = _database.Sqlite.Exec(ddl.CreateStepTable).Error - if err != nil { - log.Fatalf("unable to create %s table: %v", constants.TableStep, err) - } -} - -func TestSqlite_Client_GetBuildStepCount(t *testing.T) { - // setup types - _build := testBuild() - _build.SetID(1) - _build.SetRepoID(1) - _build.SetNumber(1) - - _stepOne := testStep() - _stepOne.SetID(1) - _stepOne.SetRepoID(1) - _stepOne.SetBuildID(1) - _stepOne.SetNumber(1) - _stepOne.SetName("foo") - _stepOne.SetImage("bar") - - _stepTwo := testStep() - _stepTwo.SetID(2) - _stepTwo.SetRepoID(1) - _stepTwo.SetBuildID(1) - _stepTwo.SetNumber(2) - _stepTwo.SetName("bar") - _stepTwo.SetImage("foo") - - // 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 steps table - defer _database.Sqlite.Exec("delete from steps;") - - // create the steps in the database - err := _database.CreateStep(_stepOne) - if err != nil { - t.Errorf("unable to create test step: %v", err) - } - - err = _database.CreateStep(_stepTwo) - if err != nil { - t.Errorf("unable to create test step: %v", err) - } - - got, err := _database.GetBuildStepCount(_build) - - if test.failure { - if err == nil { - t.Errorf("GetBuildStepCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetBuildStepCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetBuildStepCount is %v, want %v", got, test.want) - } - } -} - -func TestSqlite_Client_GetStepImageCount(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() }() - - // setup tests - tests := []struct { - failure bool - want map[string]float64 - }{ - { - failure: false, - want: map[string]float64{}, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetStepImageCount() - - if test.failure { - if err == nil { - t.Errorf("GetStepImageCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetStepImageCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetStepImageCount is %v, want %v", got, test.want) - } - } -} - -func TestSqlite_Client_GetStepStatusCount(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() }() - - // setup tests - tests := []struct { - failure bool - want map[string]float64 - }{ - { - failure: false, - want: map[string]float64{ - "pending": 0, - "failure": 0, - "killed": 0, - "running": 0, - "success": 0, - }, - }, - } - - // run tests - for _, test := range tests { - got, err := _database.GetStepStatusCount() - - if test.failure { - if err == nil { - t.Errorf("GetStepStatusCount should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetStepStatusCount returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetStepStatusCount is %v, want %v", got, test.want) - } - } -} diff --git a/database/sqlite/step_list.go b/database/sqlite/step_list.go deleted file mode 100644 index 4c82fa858..000000000 --- a/database/sqlite/step_list.go +++ /dev/null @@ -1,71 +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" -) - -// GetStepList gets a list of all steps from the database. -func (c *client) GetStepList() ([]*library.Step, error) { - c.Logger.Trace("listing steps from the database") - - // variable to store query results - s := new([]database.Step) - - // send query to the database and store result in variable - err := c.Sqlite. - Table(constants.TableStep). - Raw(dml.ListSteps). - Scan(s).Error - - // variable we want to return - steps := []*library.Step{} - // iterate through all query results - for _, step := range *s { - // https://golang.org/doc/faq#closures_and_goroutines - tmp := step - - // convert query result to library type - steps = append(steps, tmp.ToLibrary()) - } - - return steps, err -} - -// GetBuildStepList gets a list of steps by build ID from the database. -func (c *client) GetBuildStepList(b *library.Build, page, perPage int) ([]*library.Step, error) { - c.Logger.WithFields(logrus.Fields{ - "build": b.GetNumber(), - }).Tracef("listing steps for build %d from the database", b.GetNumber()) - - // variable to store query results - s := new([]database.Step) - // 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.TableStep). - Raw(dml.ListBuildSteps, b.GetID(), perPage, offset). - Scan(s).Error - - // variable we want to return - steps := []*library.Step{} - // iterate through all query results - for _, step := range *s { - // https://golang.org/doc/faq#closures_and_goroutines - tmp := step - - // convert query result to library type - steps = append(steps, tmp.ToLibrary()) - } - - return steps, err -} diff --git a/database/sqlite/step_list_test.go b/database/sqlite/step_list_test.go deleted file mode 100644 index 75956b54f..000000000 --- a/database/sqlite/step_list_test.go +++ /dev/null @@ -1,174 +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 step table - err = _database.Sqlite.Exec(ddl.CreateStepTable).Error - if err != nil { - log.Fatalf("unable to create %s table: %v", constants.TableStep, err) - } -} - -func TestSqlite_Client_GetStepList(t *testing.T) { - // setup types - _stepOne := testStep() - _stepOne.SetID(1) - _stepOne.SetRepoID(1) - _stepOne.SetBuildID(1) - _stepOne.SetNumber(1) - _stepOne.SetName("foo") - _stepOne.SetImage("bar") - - _stepTwo := testStep() - _stepTwo.SetID(2) - _stepTwo.SetRepoID(1) - _stepTwo.SetBuildID(1) - _stepTwo.SetNumber(2) - _stepTwo.SetName("bar") - _stepTwo.SetImage("foo") - - // 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.Step - }{ - { - failure: false, - want: []*library.Step{_stepOne, _stepTwo}, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the steps table - defer _database.Sqlite.Exec("delete from steps;") - - for _, step := range test.want { - // create the step in the database - err := _database.CreateStep(step) - if err != nil { - t.Errorf("unable to create test step: %v", err) - } - } - - got, err := _database.GetStepList() - - if test.failure { - if err == nil { - t.Errorf("GetStepList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetStepList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetStepList is %v, want %v", got, test.want) - } - } -} - -func TestSqlite_Client_GetBuildStepList(t *testing.T) { - // setup types - _build := testBuild() - _build.SetID(1) - _build.SetRepoID(1) - _build.SetNumber(1) - - _stepOne := testStep() - _stepOne.SetID(1) - _stepOne.SetRepoID(1) - _stepOne.SetBuildID(1) - _stepOne.SetNumber(1) - _stepOne.SetName("foo") - _stepOne.SetImage("bar") - - _stepTwo := testStep() - _stepTwo.SetID(2) - _stepTwo.SetRepoID(1) - _stepTwo.SetBuildID(1) - _stepTwo.SetNumber(2) - _stepTwo.SetName("bar") - _stepTwo.SetImage("foo") - - // 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.Step - }{ - { - failure: false, - want: []*library.Step{_stepTwo, _stepOne}, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the steps table - defer _database.Sqlite.Exec("delete from steps;") - - for _, step := range test.want { - // create the step in the database - err := _database.CreateStep(step) - if err != nil { - t.Errorf("unable to create test step: %v", err) - } - } - - got, err := _database.GetBuildStepList(_build, 1, 10) - - if test.failure { - if err == nil { - t.Errorf("GetBuildStepList should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetBuildStepList returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetBuildStepList is %v, want %v", got, test.want) - } - } -} diff --git a/database/sqlite/step_test.go b/database/sqlite/step_test.go deleted file mode 100644 index 7c7b91500..000000000 --- a/database/sqlite/step_test.go +++ /dev/null @@ -1,267 +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_GetStep(t *testing.T) { - // setup types - _build := testBuild() - _build.SetID(1) - _build.SetRepoID(1) - _build.SetNumber(1) - - _step := testStep() - _step.SetID(1) - _step.SetRepoID(1) - _step.SetBuildID(1) - _step.SetNumber(1) - _step.SetName("foo") - _step.SetImage("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.Step - }{ - { - failure: false, - want: _step, - }, - { - failure: true, - want: nil, - }, - } - - // run tests - for _, test := range tests { - if test.want != nil { - // create the step in the database - err := _database.CreateStep(test.want) - if err != nil { - t.Errorf("unable to create test step: %v", err) - } - } - - got, err := _database.GetStep(1, _build) - - // cleanup the steps table - _ = _database.Sqlite.Exec("DELETE FROM steps;") - - if test.failure { - if err == nil { - t.Errorf("GetStep should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("GetStep returned err: %v", err) - } - - if !reflect.DeepEqual(got, test.want) { - t.Errorf("GetStep is %v, want %v", got, test.want) - } - } -} - -func TestSqlite_Client_CreateStep(t *testing.T) { - // setup types - _step := testStep() - _step.SetID(1) - _step.SetRepoID(1) - _step.SetBuildID(1) - _step.SetNumber(1) - _step.SetName("foo") - _step.SetImage("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 - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the steps table - defer _database.Sqlite.Exec("delete from steps;") - - err := _database.CreateStep(_step) - - if test.failure { - if err == nil { - t.Errorf("CreateStep should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("CreateStep returned err: %v", err) - } - } -} - -func TestSqlite_Client_UpdateStep(t *testing.T) { - // setup types - _step := testStep() - _step.SetID(1) - _step.SetRepoID(1) - _step.SetBuildID(1) - _step.SetNumber(1) - _step.SetName("foo") - _step.SetImage("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 - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the steps table - defer _database.Sqlite.Exec("delete from steps;") - - // create the step in the database - err := _database.CreateStep(_step) - if err != nil { - t.Errorf("unable to create test step: %v", err) - } - - err = _database.UpdateStep(_step) - - if test.failure { - if err == nil { - t.Errorf("UpdateStep should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("UpdateStep returned err: %v", err) - } - } -} - -func TestSqlite_Client_DeleteStep(t *testing.T) { - // setup types - _step := testStep() - _step.SetID(1) - _step.SetRepoID(1) - _step.SetBuildID(1) - _step.SetNumber(1) - _step.SetName("foo") - _step.SetImage("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 - }{ - { - failure: false, - }, - } - - // run tests - for _, test := range tests { - // defer cleanup of the steps table - defer _database.Sqlite.Exec("delete from steps;") - - // create the step in the database - err := _database.CreateStep(_step) - if err != nil { - t.Errorf("unable to create test step: %v", err) - } - - err = _database.DeleteStep(1) - - if test.failure { - if err == nil { - t.Errorf("DeleteStep should have returned err") - } - - continue - } - - if err != nil { - t.Errorf("DeleteStep returned err: %v", err) - } - } -} - -// testStep is a test helper function to create a -// library Step type with all fields set to their -// zero values. -func testStep() *library.Step { - i64 := int64(0) - i := 0 - str := "" - - return &library.Step{ - ID: &i64, - BuildID: &i64, - RepoID: &i64, - Number: &i, - Name: &str, - Image: &str, - Stage: &str, - Status: &str, - Error: &str, - ExitCode: &i, - Created: &i64, - Started: &i64, - Finished: &i64, - Host: &str, - Runtime: &str, - Distribution: &str, - } -} diff --git a/database/step/count.go b/database/step/count.go new file mode 100644 index 000000000..6f8a665b4 --- /dev/null +++ b/database/step/count.go @@ -0,0 +1,25 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "github.com/go-vela/types/constants" +) + +// CountSteps gets the count of all steps from the database. +func (e *engine) CountSteps() (int64, error) { + e.logger.Tracef("getting count of all steps from the database") + + // variable to store query results + var s int64 + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableStep). + Count(&s). + Error + + return s, err +} diff --git a/database/step/count_build.go b/database/step/count_build.go new file mode 100644 index 000000000..448e04d7d --- /dev/null +++ b/database/step/count_build.go @@ -0,0 +1,31 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// CountStepsForBuild gets the count of steps by build ID from the database. +func (e *engine) CountStepsForBuild(b *library.Build, filters map[string]interface{}) (int64, error) { + e.logger.WithFields(logrus.Fields{ + "build": b.GetNumber(), + }).Tracef("getting count of steps for build %d from the database", b.GetNumber()) + + // variable to store query results + var s int64 + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableStep). + Where("build_id = ?", b.GetID()). + Where(filters). + Count(&s). + Error + + return s, err +} diff --git a/database/step/count_build_test.go b/database/step/count_build_test.go new file mode 100644 index 000000000..401e1d6b3 --- /dev/null +++ b/database/step/count_build_test.go @@ -0,0 +1,104 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestStep_Engine_CountStepsForBuild(t *testing.T) { + // setup types + _build := testBuild() + _build.SetID(1) + _build.SetRepoID(1) + _build.SetNumber(1) + + _stepOne := testStep() + _stepOne.SetID(1) + _stepOne.SetRepoID(1) + _stepOne.SetBuildID(1) + _stepOne.SetNumber(1) + _stepOne.SetName("foo") + _stepOne.SetImage("bar") + + _stepTwo := testStep() + _stepTwo.SetID(2) + _stepTwo.SetRepoID(1) + _stepTwo.SetBuildID(2) + _stepTwo.SetNumber(1) + _stepTwo.SetName("foo") + _stepTwo.SetImage("bar") + + _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 "steps" WHERE build_id = $1`).WithArgs(1).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateStep(_stepOne) + if err != nil { + t.Errorf("unable to create test step for sqlite: %v", err) + } + + err = _sqlite.CreateStep(_stepTwo) + if err != nil { + t.Errorf("unable to create test step 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.CountStepsForBuild(_build, filters) + + if test.failure { + if err == nil { + t.Errorf("CountStepsForBuild for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CountStepsForBuild for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("CountStepsForBuild for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/step/count_test.go b/database/step/count_test.go new file mode 100644 index 000000000..eff1fec8c --- /dev/null +++ b/database/step/count_test.go @@ -0,0 +1,97 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestStep_Engine_CountSteps(t *testing.T) { + // setup types + _stepOne := testStep() + _stepOne.SetID(1) + _stepOne.SetRepoID(1) + _stepOne.SetBuildID(1) + _stepOne.SetNumber(1) + _stepOne.SetName("foo") + _stepOne.SetImage("bar") + + _stepTwo := testStep() + _stepTwo.SetID(2) + _stepTwo.SetRepoID(1) + _stepTwo.SetBuildID(2) + _stepTwo.SetNumber(1) + _stepTwo.SetName("foo") + _stepTwo.SetImage("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 "steps"`).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateStep(_stepOne) + if err != nil { + t.Errorf("unable to create test step for sqlite: %v", err) + } + + err = _sqlite.CreateStep(_stepTwo) + if err != nil { + t.Errorf("unable to create test step 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.CountSteps() + + if test.failure { + if err == nil { + t.Errorf("CountSteps for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CountSteps for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("CountSteps for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/step/create.go b/database/step/create.go new file mode 100644 index 000000000..03ec3a953 --- /dev/null +++ b/database/step/create.go @@ -0,0 +1,38 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// CreateStep creates a new step in the database. +func (e *engine) CreateStep(s *library.Step) error { + e.logger.WithFields(logrus.Fields{ + "step": s.GetNumber(), + }).Tracef("creating step %s in the database", s.GetName()) + + // cast the library type to database type + // + // https://pkg.go.dev/github.com/go-vela/types/database#StepFromLibrary + step := database.StepFromLibrary(s) + + // validate the necessary fields are populated + // + // https://pkg.go.dev/github.com/go-vela/types/database#Step.Validate + err := step.Validate() + if err != nil { + return err + } + + // send query to the database + return e.client. + Table(constants.TableStep). + Create(step). + Error +} diff --git a/database/step/create_test.go b/database/step/create_test.go new file mode 100644 index 000000000..1f7b3e1b8 --- /dev/null +++ b/database/step/create_test.go @@ -0,0 +1,75 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestStep_Engine_CreateStep(t *testing.T) { + // setup types + _step := testStep() + _step.SetID(1) + _step.SetRepoID(1) + _step.SetBuildID(1) + _step.SetNumber(1) + _step.SetName("foo") + _step.SetImage("bar") + + _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 "steps" +("build_id","repo_id","number","name","image","stage","status","error","exit_code","created","started","finished","host","runtime","distribution","id") +VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16) RETURNING "id"`). + WithArgs(1, 1, 1, "foo", "bar", nil, nil, nil, nil, nil, nil, nil, nil, 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.CreateStep(_step) + + if test.failure { + if err == nil { + t.Errorf("CreateStep for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateStep for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/step/delete.go b/database/step/delete.go new file mode 100644 index 000000000..d5107dea8 --- /dev/null +++ b/database/step/delete.go @@ -0,0 +1,30 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// DeleteStep deletes an existing step from the database. +func (e *engine) DeleteStep(s *library.Step) error { + e.logger.WithFields(logrus.Fields{ + "step": s.GetNumber(), + }).Tracef("deleting step %s from the database", s.GetName()) + + // cast the library type to database type + // + // https://pkg.go.dev/github.com/go-vela/types/database#StepFromLibrary + step := database.StepFromLibrary(s) + + // send query to the database + return e.client. + Table(constants.TableStep). + Delete(step). + Error +} diff --git a/database/step/delete_test.go b/database/step/delete_test.go new file mode 100644 index 000000000..c3b35ee8a --- /dev/null +++ b/database/step/delete_test.go @@ -0,0 +1,75 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestStep_Engine_DeleteStep(t *testing.T) { + // setup types + _step := testStep() + _step.SetID(1) + _step.SetRepoID(1) + _step.SetBuildID(1) + _step.SetNumber(1) + _step.SetName("foo") + _step.SetImage("bar") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // ensure the mock expects the query + _mock.ExpectExec(`DELETE FROM "steps" WHERE "steps"."id" = $1`). + WithArgs(1). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateStep(_step) + if err != nil { + t.Errorf("unable to create test step 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.DeleteStep(_step) + + if test.failure { + if err == nil { + t.Errorf("DeleteStep for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("DeleteStep for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/step/get.go b/database/step/get.go new file mode 100644 index 000000000..1b07d1f6a --- /dev/null +++ b/database/step/get.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" +) + +// GetStep gets a step by ID from the database. +func (e *engine) GetStep(id int64) (*library.Step, error) { + e.logger.Tracef("getting step %d from the database", id) + + // variable to store query results + s := new(database.Step) + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableStep). + Where("id = ?", id). + Take(s). + Error + if err != nil { + return nil, err + } + + // return the step + // + // https://pkg.go.dev/github.com/go-vela/types/database#Step.ToLibrary + return s.ToLibrary(), nil +} diff --git a/database/step/get_build.go b/database/step/get_build.go new file mode 100644 index 000000000..49aed7551 --- /dev/null +++ b/database/step/get_build.go @@ -0,0 +1,39 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// GetStepForBuild gets a step by number and build ID from the database. +func (e *engine) GetStepForBuild(b *library.Build, number int) (*library.Step, error) { + e.logger.WithFields(logrus.Fields{ + "build": b.GetNumber(), + "step": number, + }).Tracef("getting step %d from the database", number) + + // variable to store query results + s := new(database.Step) + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableStep). + Where("build_id = ?", b.GetID()). + Where("number = ?", number). + Take(s). + Error + if err != nil { + return nil, err + } + + // return the step + // + // https://pkg.go.dev/github.com/go-vela/types/database#Step.ToLibrary + return s.ToLibrary(), nil +} diff --git a/database/step/get_build_test.go b/database/step/get_build_test.go new file mode 100644 index 000000000..ce4bfff37 --- /dev/null +++ b/database/step/get_build_test.go @@ -0,0 +1,92 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" +) + +func TestStep_Engine_GetStepForBuild(t *testing.T) { + // setup types + _build := testBuild() + _build.SetID(1) + _build.SetRepoID(1) + _build.SetNumber(1) + + _step := testStep() + _step.SetID(1) + _step.SetRepoID(1) + _step.SetBuildID(1) + _step.SetNumber(1) + _step.SetName("foo") + _step.SetImage("bar") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows( + []string{"id", "repo_id", "build_id", "number", "name", "image", "stage", "status", "error", "exit_code", "created", "started", "finished", "host", "runtime", "distribution"}). + AddRow(1, 1, 1, 1, "foo", "bar", "", "", "", 0, 0, 0, 0, "", "", "") + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "steps" WHERE build_id = $1 AND number = $2 LIMIT 1`).WithArgs(1, 1).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateStep(_step) + if err != nil { + t.Errorf("unable to create test step for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want *library.Step + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: _step, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: _step, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.GetStepForBuild(_build, 1) + + if test.failure { + if err == nil { + t.Errorf("GetStepForBuild for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("GetStepForBuild for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("GetStepForBuild for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/step/get_test.go b/database/step/get_test.go new file mode 100644 index 000000000..382fa0b7c --- /dev/null +++ b/database/step/get_test.go @@ -0,0 +1,87 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" +) + +func TestStep_Engine_GetStep(t *testing.T) { + // setup types + _step := testStep() + _step.SetID(1) + _step.SetRepoID(1) + _step.SetBuildID(1) + _step.SetNumber(1) + _step.SetName("foo") + _step.SetImage("bar") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows( + []string{"id", "repo_id", "build_id", "number", "name", "image", "stage", "status", "error", "exit_code", "created", "started", "finished", "host", "runtime", "distribution"}). + AddRow(1, 1, 1, 1, "foo", "bar", "", "", "", 0, 0, 0, 0, "", "", "") + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "steps" WHERE id = $1 LIMIT 1`).WithArgs(1).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateStep(_step) + if err != nil { + t.Errorf("unable to create test step for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want *library.Step + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: _step, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: _step, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.GetStep(1) + + if test.failure { + if err == nil { + t.Errorf("GetStep for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("GetStep for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("GetStep for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/step/list.go b/database/step/list.go new file mode 100644 index 000000000..3c7fedbc9 --- /dev/null +++ b/database/step/list.go @@ -0,0 +1,54 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" +) + +// ListSteps gets a list of all steps from the database. +func (e *engine) ListSteps() ([]*library.Step, error) { + e.logger.Trace("listing all steps from the database") + + // variables to store query results and return value + count := int64(0) + w := new([]database.Step) + steps := []*library.Step{} + + // count the results + count, err := e.CountSteps() + if err != nil { + return nil, err + } + + // short-circuit if there are no results + if count == 0 { + return steps, nil + } + + // send query to the database and store result in variable + err = e.client. + Table(constants.TableStep). + Find(&w). + Error + if err != nil { + return nil, err + } + + // iterate through all query results + for _, step := range *w { + // https://golang.org/doc/faq#closures_and_goroutines + tmp := step + + // convert query result to library type + // + // https://pkg.go.dev/github.com/go-vela/types/database#Step.ToLibrary + steps = append(steps, tmp.ToLibrary()) + } + + return steps, nil +} diff --git a/database/step/list_build.go b/database/step/list_build.go new file mode 100644 index 000000000..f338b1374 --- /dev/null +++ b/database/step/list_build.go @@ -0,0 +1,65 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// ListStepsForBuild gets a list of all steps from the database. +func (e *engine) ListStepsForBuild(b *library.Build, filters map[string]interface{}, page int, perPage int) ([]*library.Step, int64, error) { + e.logger.WithFields(logrus.Fields{ + "build": b.GetNumber(), + }).Tracef("listing steps for build %d from the database", b.GetNumber()) + + // variables to store query results and return value + count := int64(0) + s := new([]database.Step) + steps := []*library.Step{} + + // count the results + count, err := e.CountStepsForBuild(b, filters) + if err != nil { + return steps, 0, err + } + + // short-circuit if there are no results + if count == 0 { + return steps, 0, nil + } + + // calculate offset for pagination through results + offset := perPage * (page - 1) + + // send query to the database and store result in variable + err = e.client. + Table(constants.TableStep). + Where("build_id = ?", b.GetID()). + Where(filters). + Order("id DESC"). + Limit(perPage). + Offset(offset). + Find(&s). + Error + if err != nil { + return nil, count, err + } + + // iterate through all query results + for _, step := range *s { + // https://golang.org/doc/faq#closures_and_goroutines + tmp := step + + // convert query result to library type + // + // https://pkg.go.dev/github.com/go-vela/types/database#Step.ToLibrary + steps = append(steps, tmp.ToLibrary()) + } + + return steps, count, nil +} diff --git a/database/step/list_build_test.go b/database/step/list_build_test.go new file mode 100644 index 000000000..c79100eae --- /dev/null +++ b/database/step/list_build_test.go @@ -0,0 +1,114 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" +) + +func TestStep_Engine_ListStepsForBuild(t *testing.T) { + // setup types + _build := testBuild() + _build.SetID(1) + _build.SetRepoID(1) + _build.SetNumber(1) + + _stepOne := testStep() + _stepOne.SetID(1) + _stepOne.SetRepoID(1) + _stepOne.SetBuildID(1) + _stepOne.SetNumber(1) + _stepOne.SetName("foo") + _stepOne.SetImage("bar") + + _stepTwo := testStep() + _stepTwo.SetID(2) + _stepTwo.SetRepoID(1) + _stepTwo.SetBuildID(1) + _stepTwo.SetNumber(2) + _stepTwo.SetName("foo") + _stepTwo.SetImage("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 "steps" WHERE build_id = $1`).WithArgs(1).WillReturnRows(_rows) + + // create expected result in mock + _rows = sqlmock.NewRows( + []string{"id", "repo_id", "build_id", "number", "name", "image", "stage", "status", "error", "exit_code", "created", "started", "finished", "host", "runtime", "distribution"}). + AddRow(2, 1, 1, 2, "foo", "bar", "", "", "", 0, 0, 0, 0, "", "", ""). + AddRow(1, 1, 1, 1, "foo", "bar", "", "", "", 0, 0, 0, 0, "", "", "") + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "steps" WHERE build_id = $1 ORDER BY id DESC LIMIT 10`).WithArgs(1).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateStep(_stepOne) + if err != nil { + t.Errorf("unable to create test step for sqlite: %v", err) + } + + err = _sqlite.CreateStep(_stepTwo) + if err != nil { + t.Errorf("unable to create test step for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want []*library.Step + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: []*library.Step{_stepTwo, _stepOne}, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: []*library.Step{_stepTwo, _stepOne}, + }, + } + + filters := map[string]interface{}{} + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, _, err := test.database.ListStepsForBuild(_build, filters, 1, 10) + + if test.failure { + if err == nil { + t.Errorf("ListStepsForBuild for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("ListStepsForBuild for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("ListStepsForBuild for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/step/list_image.go b/database/step/list_image.go new file mode 100644 index 000000000..cd02af894 --- /dev/null +++ b/database/step/list_image.go @@ -0,0 +1,44 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "database/sql" + + "github.com/go-vela/types/constants" +) + +// ListStepImageCount gets a list of all step images and the count of their occurrence from the database. +func (e *engine) ListStepImageCount() (map[string]float64, error) { + e.logger.Tracef("getting count of all images for steps from the database") + + // variables to store query results and return value + s := []struct { + Image sql.NullString + Count sql.NullInt32 + }{} + images := make(map[string]float64) + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableStep). + Select("image", " count(image) as count"). + Group("image"). + Find(&s). + Error + if err != nil { + return nil, err + } + + // iterate through all query results + for _, value := range s { + // check if the image returned is not empty + if value.Image.Valid { + images[value.Image.String] = float64(value.Count.Int32) + } + } + + return images, nil +} diff --git a/database/step/list_image_test.go b/database/step/list_image_test.go new file mode 100644 index 000000000..574545493 --- /dev/null +++ b/database/step/list_image_test.go @@ -0,0 +1,97 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestStep_Engine_ListStepImageCount(t *testing.T) { + // setup types + _stepOne := testStep() + _stepOne.SetID(1) + _stepOne.SetRepoID(1) + _stepOne.SetBuildID(1) + _stepOne.SetNumber(1) + _stepOne.SetName("foo") + _stepOne.SetImage("bar") + + _stepTwo := testStep() + _stepTwo.SetID(2) + _stepTwo.SetRepoID(1) + _stepTwo.SetBuildID(1) + _stepTwo.SetNumber(2) + _stepTwo.SetName("foo") + _stepTwo.SetImage("bar") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"image", "count"}).AddRow("bar", 2) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT "image", count(image) as count FROM "steps" GROUP BY "image"`).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateStep(_stepOne) + if err != nil { + t.Errorf("unable to create test step for sqlite: %v", err) + } + + err = _sqlite.CreateStep(_stepTwo) + if err != nil { + t.Errorf("unable to create test step for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want map[string]float64 + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: map[string]float64{"bar": 2}, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: map[string]float64{"bar": 2}, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.ListStepImageCount() + + if test.failure { + if err == nil { + t.Errorf("ListStepImageCount for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("ListStepImageCount for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("ListStepImageCount for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/step/list_status.go b/database/step/list_status.go new file mode 100644 index 000000000..9b54e3b7f --- /dev/null +++ b/database/step/list_status.go @@ -0,0 +1,50 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "database/sql" + + "github.com/go-vela/types/constants" +) + +// ListStepStatusCount gets a list of all step statuses and the count of their occurrence from the database. +func (e *engine) ListStepStatusCount() (map[string]float64, error) { + e.logger.Tracef("getting count of all statuses for steps from the database") + + // variables to store query results and return value + s := []struct { + Status sql.NullString + Count sql.NullInt32 + }{} + statuses := map[string]float64{ + "pending": 0, + "failure": 0, + "killed": 0, + "running": 0, + "success": 0, + } + + // send query to the database and store result in variable + err := e.client. + Table(constants.TableStep). + Select("status", " count(status) as count"). + Group("status"). + Find(&s). + Error + if err != nil { + return nil, err + } + + // iterate through all query results + for _, value := range s { + // check if the status returned is not empty + if value.Status.Valid { + statuses[value.Status.String] = float64(value.Count.Int32) + } + } + + return statuses, nil +} diff --git a/database/step/list_status_test.go b/database/step/list_status_test.go new file mode 100644 index 000000000..57f500650 --- /dev/null +++ b/database/step/list_status_test.go @@ -0,0 +1,114 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestStep_Engine_ListStepStatusCount(t *testing.T) { + // setup types + _stepOne := testStep() + _stepOne.SetID(1) + _stepOne.SetRepoID(1) + _stepOne.SetBuildID(1) + _stepOne.SetNumber(1) + _stepOne.SetName("foo") + _stepOne.SetImage("bar") + + _stepTwo := testStep() + _stepTwo.SetID(2) + _stepTwo.SetRepoID(1) + _stepTwo.SetBuildID(1) + _stepTwo.SetNumber(2) + _stepTwo.SetName("foo") + _stepTwo.SetImage("bar") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // create expected result in mock + _rows := sqlmock.NewRows([]string{"status", "count"}). + AddRow("pending", 0). + AddRow("failure", 0). + AddRow("killed", 0). + AddRow("running", 0). + AddRow("success", 0) + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT "status", count(status) as count FROM "steps" GROUP BY "status"`).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateStep(_stepOne) + if err != nil { + t.Errorf("unable to create test step for sqlite: %v", err) + } + + err = _sqlite.CreateStep(_stepTwo) + if err != nil { + t.Errorf("unable to create test step for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want map[string]float64 + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: map[string]float64{ + "pending": 0, + "failure": 0, + "killed": 0, + "running": 0, + "success": 0, + }, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: map[string]float64{ + "pending": 0, + "failure": 0, + "killed": 0, + "running": 0, + "success": 0, + }, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.ListStepStatusCount() + + if test.failure { + if err == nil { + t.Errorf("ListStepStatusCount for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("ListStepStatusCount for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("ListStepStatusCount for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/step/list_test.go b/database/step/list_test.go new file mode 100644 index 000000000..cb1c05cb2 --- /dev/null +++ b/database/step/list_test.go @@ -0,0 +1,107 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "reflect" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-vela/types/library" +) + +func TestStep_Engine_ListSteps(t *testing.T) { + // setup types + _stepOne := testStep() + _stepOne.SetID(1) + _stepOne.SetRepoID(1) + _stepOne.SetBuildID(1) + _stepOne.SetNumber(1) + _stepOne.SetName("foo") + _stepOne.SetImage("bar") + + _stepTwo := testStep() + _stepTwo.SetID(2) + _stepTwo.SetRepoID(1) + _stepTwo.SetBuildID(2) + _stepTwo.SetNumber(1) + _stepTwo.SetName("bar") + _stepTwo.SetImage("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 "steps"`).WillReturnRows(_rows) + + // create expected result in mock + _rows = sqlmock.NewRows( + []string{"id", "repo_id", "build_id", "number", "name", "image", "stage", "status", "error", "exit_code", "created", "started", "finished", "host", "runtime", "distribution"}). + AddRow(1, 1, 1, 1, "foo", "bar", "", "", "", 0, 0, 0, 0, "", "", ""). + AddRow(2, 1, 2, 1, "bar", "foo", "", "", "", 0, 0, 0, 0, "", "", "") + + // ensure the mock expects the query + _mock.ExpectQuery(`SELECT * FROM "steps"`).WillReturnRows(_rows) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateStep(_stepOne) + if err != nil { + t.Errorf("unable to create test step for sqlite: %v", err) + } + + err = _sqlite.CreateStep(_stepTwo) + if err != nil { + t.Errorf("unable to create test step for sqlite: %v", err) + } + + // setup tests + tests := []struct { + failure bool + name string + database *engine + want []*library.Step + }{ + { + failure: false, + name: "postgres", + database: _postgres, + want: []*library.Step{_stepOne, _stepTwo}, + }, + { + failure: false, + name: "sqlite3", + database: _sqlite, + want: []*library.Step{_stepOne, _stepTwo}, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := test.database.ListSteps() + + if test.failure { + if err == nil { + t.Errorf("ListSteps for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("ListSteps for %s returned err: %v", test.name, err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("ListSteps for %s is %v, want %v", test.name, got, test.want) + } + }) + } +} diff --git a/database/step/opts.go b/database/step/opts.go new file mode 100644 index 000000000..80db4689c --- /dev/null +++ b/database/step/opts.go @@ -0,0 +1,44 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "github.com/sirupsen/logrus" + + "gorm.io/gorm" +) + +// EngineOpt represents a configuration option to initialize the database engine for Steps. +type EngineOpt func(*engine) error + +// WithClient sets the gorm.io/gorm client in the database engine for Steps. +func WithClient(client *gorm.DB) EngineOpt { + return func(e *engine) error { + // set the gorm.io/gorm client in the step engine + e.client = client + + return nil + } +} + +// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for Steps. +func WithLogger(logger *logrus.Entry) EngineOpt { + return func(e *engine) error { + // set the github.com/sirupsen/logrus logger in the step engine + e.logger = logger + + return nil + } +} + +// WithSkipCreation sets the skip creation logic in the database engine for Steps. +func WithSkipCreation(skipCreation bool) EngineOpt { + return func(e *engine) error { + // set to skip creating tables and indexes in the step engine + e.config.SkipCreation = skipCreation + + return nil + } +} diff --git a/database/step/opts_test.go b/database/step/opts_test.go new file mode 100644 index 000000000..4ea1d64c0 --- /dev/null +++ b/database/step/opts_test.go @@ -0,0 +1,161 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "reflect" + "testing" + + "github.com/sirupsen/logrus" + + "gorm.io/gorm" +) + +func TestStep_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 TestStep_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 TestStep_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/step/service.go b/database/step/service.go new file mode 100644 index 000000000..4007e537a --- /dev/null +++ b/database/step/service.go @@ -0,0 +1,49 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "github.com/go-vela/types/library" +) + +// StepService represents the Vela interface for step +// functions with the supported Database backends. +// +//nolint:revive // ignore name stutter +type StepService interface { + // Step Data Definition Language Functions + // + // https://en.wikipedia.org/wiki/Data_definition_language + + // CreateStepTable defines a function that creates the steps table. + CreateStepTable(string) error + + // Step Data Manipulation Language Functions + // + // https://en.wikipedia.org/wiki/Data_manipulation_language + + // CountSteps defines a function that gets the count of all steps. + CountSteps() (int64, error) + // CountStepsForBuild defines a function that gets the count of steps by build ID. + CountStepsForBuild(*library.Build, map[string]interface{}) (int64, error) + // CreateStep defines a function that creates a new step. + CreateStep(*library.Step) error + // DeleteStep defines a function that deletes an existing step. + DeleteStep(*library.Step) error + // GetStep defines a function that gets a step by ID. + GetStep(int64) (*library.Step, error) + // GetStepForBuild defines a function that gets a step by number and build ID. + GetStepForBuild(*library.Build, int) (*library.Step, error) + // ListSteps defines a function that gets a list of all steps. + ListSteps() ([]*library.Step, error) + // ListStepsForBuild defines a function that gets a list of steps by build ID. + ListStepsForBuild(*library.Build, map[string]interface{}, int, int) ([]*library.Step, int64, error) + // ListStepImageCount defines a function that gets a list of all step images and the count of their occurrence. + ListStepImageCount() (map[string]float64, error) + // ListStepStatusCount defines a function that gets a list of all step statuses and the count of their occurrence. + ListStepStatusCount() (map[string]float64, error) + // UpdateStep defines a function that updates an existing step. + UpdateStep(*library.Step) error +} diff --git a/database/step/step.go b/database/step/step.go new file mode 100644 index 000000000..94017b04c --- /dev/null +++ b/database/step/step.go @@ -0,0 +1,74 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +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 StepService interface. + config struct { + // specifies to skip creating tables and indexes for the Step engine + SkipCreation bool + } + + // engine represents the step functionality that implements the StepService interface. + engine struct { + // engine configuration settings used in step functions + config *config + + // gorm.io/gorm database client used in step functions + // + // https://pkg.go.dev/gorm.io/gorm#DB + client *gorm.DB + + // sirupsen/logrus logger used in step functions + // + // https://pkg.go.dev/github.com/sirupsen/logrus#Entry + logger *logrus.Entry + } +) + +// New creates and returns a Vela service for integrating with steps in the database. +// +//nolint:revive // ignore returning unexported engine +func New(opts ...EngineOpt) (*engine, error) { + // create new Step 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 step database objects + if e.config.SkipCreation { + e.logger.Warning("skipping creation of steps table in the database") + + return e, nil + } + + // create the steps table + err := e.CreateStepTable(e.client.Config.Dialector.Name()) + if err != nil { + return nil, fmt.Errorf("unable to create %s table: %w", constants.TableStep, err) + } + + return e, nil +} diff --git a/database/step/step_test.go b/database/step/step_test.go new file mode 100644 index 000000000..26edfa5e1 --- /dev/null +++ b/database/step/step_test.go @@ -0,0 +1,225 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +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 TestStep_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)) + + _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)) + + // 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 step 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 step engine: %v", err) + } + + return _engine +} + +// testBuild is a test helper function to create a library +// Build type with all fields set to their zero values. +func testBuild() *library.Build { + return &library.Build{ + ID: new(int64), + RepoID: new(int64), + PipelineID: new(int64), + Number: new(int), + Parent: new(int), + Event: new(string), + EventAction: new(string), + Status: new(string), + Error: new(string), + Enqueued: new(int64), + Created: new(int64), + Started: new(int64), + Finished: new(int64), + Deploy: new(string), + Clone: new(string), + Source: new(string), + Title: new(string), + Message: new(string), + Commit: new(string), + Sender: new(string), + Author: new(string), + Email: new(string), + Link: new(string), + Branch: new(string), + Ref: new(string), + BaseRef: new(string), + HeadRef: new(string), + Host: new(string), + Runtime: new(string), + Distribution: new(string), + } +} + +// testStep is a test helper function to create a library +// Step type with all fields set to their zero values. +func testStep() *library.Step { + return &library.Step{ + ID: new(int64), + BuildID: new(int64), + RepoID: new(int64), + Number: new(int), + Name: new(string), + Image: new(string), + Stage: new(string), + Status: new(string), + Error: new(string), + ExitCode: new(int), + Created: new(int64), + Started: new(int64), + Finished: new(int64), + Host: new(string), + Runtime: new(string), + Distribution: new(string), + } +} diff --git a/database/step/table.go b/database/step/table.go new file mode 100644 index 000000000..ffd44c6c6 --- /dev/null +++ b/database/step/table.go @@ -0,0 +1,78 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "github.com/go-vela/types/constants" +) + +const ( + // CreatePostgresTable represents a query to create the Postgres steps table. + CreatePostgresTable = ` +CREATE TABLE +IF NOT EXISTS +steps ( + id SERIAL PRIMARY KEY, + repo_id INTEGER, + build_id INTEGER, + number INTEGER, + name VARCHAR(250), + image VARCHAR(500), + stage VARCHAR(250), + status VARCHAR(250), + error VARCHAR(500), + exit_code INTEGER, + created INTEGER, + started INTEGER, + finished INTEGER, + host VARCHAR(250), + runtime VARCHAR(250), + distribution VARCHAR(250), + UNIQUE(build_id, number) +); +` + + // CreateSqliteTable represents a query to create the Sqlite steps table. + CreateSqliteTable = ` +CREATE TABLE +IF NOT EXISTS +steps ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + repo_id INTEGER, + build_id INTEGER, + number INTEGER, + name TEXT, + image TEXT, + stage TEXT, + status TEXT, + error TEXT, + exit_code INTEGER, + created INTEGER, + started INTEGER, + finished INTEGER, + host TEXT, + runtime TEXT, + distribution TEXT, + UNIQUE(build_id, number) +); +` +) + +// CreateStepTable creates the steps table in the database. +func (e *engine) CreateStepTable(driver string) error { + e.logger.Tracef("creating steps table in the database") + + // handle the driver provided to create the table + switch driver { + case constants.DriverPostgres: + // create the steps table for Postgres + return e.client.Exec(CreatePostgresTable).Error + case constants.DriverSqlite: + fallthrough + default: + // create the steps table for Sqlite + return e.client.Exec(CreateSqliteTable).Error + } +} diff --git a/database/step/table_test.go b/database/step/table_test.go new file mode 100644 index 000000000..08900c753 --- /dev/null +++ b/database/step/table_test.go @@ -0,0 +1,59 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestStep_Engine_CreateStepTable(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.CreateStepTable(test.name) + + if test.failure { + if err == nil { + t.Errorf("CreateStepTable for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("CreateStepTable for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/database/step/update.go b/database/step/update.go new file mode 100644 index 000000000..87f8d2aa9 --- /dev/null +++ b/database/step/update.go @@ -0,0 +1,38 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "github.com/go-vela/types/constants" + "github.com/go-vela/types/database" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// UpdateStep updates an existing step in the database. +func (e *engine) UpdateStep(s *library.Step) error { + e.logger.WithFields(logrus.Fields{ + "step": s.GetNumber(), + }).Tracef("updating step %s in the database", s.GetName()) + + // cast the library type to database type + // + // https://pkg.go.dev/github.com/go-vela/types/database#StepFromLibrary + step := database.StepFromLibrary(s) + + // validate the necessary fields are populated + // + // https://pkg.go.dev/github.com/go-vela/types/database#Step.Validate + err := step.Validate() + if err != nil { + return err + } + + // send query to the database + return e.client. + Table(constants.TableStep). + Save(step). + Error +} diff --git a/database/step/update_test.go b/database/step/update_test.go new file mode 100644 index 000000000..c2a10a113 --- /dev/null +++ b/database/step/update_test.go @@ -0,0 +1,77 @@ +// Copyright (c) 2023 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestStep_Engine_UpdateStep(t *testing.T) { + // setup types + _step := testStep() + _step.SetID(1) + _step.SetRepoID(1) + _step.SetBuildID(1) + _step.SetNumber(1) + _step.SetName("foo") + _step.SetImage("bar") + + _postgres, _mock := testPostgres(t) + defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }() + + // ensure the mock expects the query + _mock.ExpectExec(`UPDATE "steps" +SET "build_id"=$1,"repo_id"=$2,"number"=$3,"name"=$4,"image"=$5,"stage"=$6,"status"=$7,"error"=$8,"exit_code"=$9,"created"=$10,"started"=$11,"finished"=$12,"host"=$13,"runtime"=$14,"distribution"=$15 +WHERE "id" = $16`). + WithArgs(1, 1, 1, "foo", "bar", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1). + WillReturnResult(sqlmock.NewResult(1, 1)) + + _sqlite := testSqlite(t) + defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }() + + err := _sqlite.CreateStep(_step) + if err != nil { + t.Errorf("unable to create test step 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.UpdateStep(_step) + + if test.failure { + if err == nil { + t.Errorf("UpdateStep for %s should have returned err", test.name) + } + + return + } + + if err != nil { + t.Errorf("UpdateStep for %s returned err: %v", test.name, err) + } + }) + } +} diff --git a/router/middleware/step/step.go b/router/middleware/step/step.go index 7dbd2d6c5..27d517efe 100644 --- a/router/middleware/step/step.go +++ b/router/middleware/step/step.go @@ -75,7 +75,7 @@ func Establish() gin.HandlerFunc { "user": u.GetName(), }).Debugf("reading step %s/%d/%d", r.GetFullName(), b.GetNumber(), number) - s, err := database.FromContext(c).GetStep(number, b) + s, err := database.FromContext(c).GetStepForBuild(b, number) if err != nil { retErr := fmt.Errorf("unable to read step %s/%d/%d: %w", r.GetFullName(), b.GetNumber(), number, err) util.HandleError(c, http.StatusNotFound, retErr)