" >> .env
```
## Start
@@ -93,15 +93,15 @@ In order to run a build in Vela, you'll need to add a repo to the locally runnin
1. Navigate to the `Source Repositories` page @ http://localhost:8888/account/source-repos
- * For conveinence, you can reference our documentation to [learn how to enable a repo](https://go-vela.github.io/docs/usage/enable_repo/).
+ * For convenience, you can reference our documentation to [learn how to enable a repo](https://go-vela.github.io/docs/usage/enable_repo/).
-2. Click the blue drop down arrow on the left side next to the org that contains the repo you want to enable.
+2. Click the blue drop-down arrow on the left side next to the org that contains the repo you want to enable.
-3. Find the repo you want to enable in the drop down list and click the blue `Enable` button on the right side.
- * You should received a `success` message telling you `/ enabled.`
+3. Find the repo you want to enable in the drop-down list and click the blue `Enable` button on the right side.
+ * You should receive a `success` message telling you `/ enabled.`
4. Click the blue `View` button to navigate directly to the repo.
- * You should be redirected to http://localhost:8888//
+ * You should be redirected to http://localhost:8888//
@@ -116,7 +116,7 @@ In order to run a build in Vela, you'll need to add a pipeline to the repo that
1. Create a Vela [pipeline](https://go-vela.github.io/docs/tour/) to define a workflow for Vela to run.
- * For convenience, you can reference our documentation to use [one of our example pipelines](https://go-vela.github.io/docs/usage/examples/).
+ * For convenience, you can reference our documentation to use [one of our example pipelines](https://go-vela.github.io/docs/usage/examples/).
2. Add the pipeline to the repo that was enabled above.
@@ -137,8 +137,8 @@ In order to run a build in Vela, you'll need to capture a valid webhook payload
2. Find the [recent delivery](https://developer.github.com/webhooks/testing/#listing-recent-deliveries) for the pipeline that was added to your repo.
3. Create a request locally for http://localhost:8080/webhook and replicate all parts from the recent delivery.
- * You should use whatever tool feels most comfortable and natural to you (`curl`, `Postman`, `Insomnia` etc.).
- * You should replicate all the request headers and the request body from the recent delivery.
+ * You should use whatever tool feels most comfortable and natural to you (`curl`, `Postman`, `Insomnia` etc.).
+ * You should replicate all the request headers and the request body from the recent delivery.
4. Send the request and navigate directly to the repo (http://localhost:8888//) to watch the build run live.
@@ -156,19 +156,33 @@ This section covers the different services in the stack when the Vela applicatio
The `server` Docker compose service hosts the Vela server and API.
-This component is used for processing web requests and managing resources in the database and publishing builds to the FIFO queue.
+Known as the brains of the Vela application, this service is responsible for managing the state of application resources.
+
+This includes managing resources in the system (repositories, users etc.) and storing resource data in the database.
+
+Additionally, the server responds to event-driven requests (webhooks) which creates new builds to run on a worker.
+
+For more information, please review [the official documentation](https://go-vela.github.io/docs/administration/server/).
### Worker
The `worker` Docker compose service hosts the Vela build daemon.
-This component is used for pulling builds from the FIFO queue and executing them based off their configuration.
+Known as the brawn of the Vela application, this service is responsible for managing the state of build resources.
+
+This includes pulling the build, provided by the server, from the queue to be run.
+
+For more information, please review [the official documentation](https://go-vela.github.io/docs/administration/worker/).
### UI
The `ui` Docker compose service hosts the Vela UI.
-This component is used for providing a user-friendly interface for triggering actions in the Vela system.
+Known as the user interface for the Vela application, often referred to as the Vela UI, this service provides a means for utilizing and interacting with the Vela platform.
+
+The Vela UI aims to provide users with an easy-to-use toolbox that supplies most of the functionality necessary for managing, investigating, and successfully troubleshooting Vela pipelines.
+
+For more information, please review [the official documentation](https://go-vela.github.io/docs/administration/ui/).
### Redis
diff --git a/Dockerfile b/Dockerfile
index 98ecc5101..79beef6fb 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,7 +2,7 @@
#
# Use of this source code is governed by the LICENSE file in this repository.
-FROM alpine as certs
+FROM alpine:3.18.2@sha256:25fad2a32ad1f6f510e528448ae1ec69a28ef81916a004d3629874104f8a7f70 as certs
RUN apk add --update --no-cache ca-certificates
diff --git a/Dockerfile-alpine b/Dockerfile-alpine
index f11d181f3..38ce8fded 100644
--- a/Dockerfile-alpine
+++ b/Dockerfile-alpine
@@ -2,7 +2,7 @@
#
# Use of this source code is governed by the LICENSE file in this repository.
-FROM alpine
+FROM alpine:3.18.2@sha256:25fad2a32ad1f6f510e528448ae1ec69a28ef81916a004d3629874104f8a7f70
RUN apk add --update --no-cache ca-certificates
diff --git a/Makefile b/Makefile
index ec24f2fd1..f46e05a3f 100644
--- a/Makefile
+++ b/Makefile
@@ -91,6 +91,13 @@ fix:
@echo "### Fixing Go Code"
@go fix ./...
+# The `integration-test` target is intended to run all integration tests for the Go source code.
+.PHONY: integration-test
+integration-test:
+ @echo
+ @echo "### Integration Testing"
+ INTEGRATION=1 go test -run TestDatabase_Integration ./...
+
# The `test` target is intended to run
# the tests for the Go source code.
#
@@ -99,7 +106,7 @@ fix:
test:
@echo
@echo "### Testing Go Code"
- @go test -race ./...
+ @go test -race -covermode=atomic -coverprofile=coverage.out ./...
# The `test-cover` target is intended to run
# the tests for the Go source code and then
@@ -107,10 +114,7 @@ test:
#
# Usage: `make test-cover`
.PHONY: test-cover
-test-cover:
- @echo
- @echo "### Creating test coverage report"
- @go test -race -covermode=atomic -coverprofile=coverage.out ./...
+test-cover: test
@echo
@echo "### Opening test coverage report"
@go tool cover -html=coverage.out
@@ -267,7 +271,7 @@ spec-install:
@apt-get update
@apt-get install -y jq moreutils
@echo "### Downloading and installing go-swagger"
- @curl -o /usr/local/bin/swagger -L "https://github.com/go-swagger/go-swagger/releases/download/v0.27.0/swagger_linux_amd64"
+ @curl -o /usr/local/bin/swagger -L "https://github.com/go-swagger/go-swagger/releases/download/v0.30.2/swagger_linux_amd64"
@chmod +x /usr/local/bin/swagger
# The `spec-gen` target is intended to create an api-spec
@@ -310,4 +314,14 @@ spec-version-update:
#
# Usage: `make spec`
.PHONY: spec
-spec: spec-gen spec-version-update spec-validate
\ No newline at end of file
+spec: spec-gen spec-version-update spec-validate
+
+# The `lint` target is intended to lint the
+# Go source code with golangci-lint.
+#
+# Usage: `make lint`
+.PHONY: lint
+lint:
+ @echo
+ @echo "### Linting Go Code"
+ @golangci-lint run ./...
\ No newline at end of file
diff --git a/api/admin/build.go b/api/admin/build.go
index c61e1995c..2be805ef2 100644
--- a/api/admin/build.go
+++ b/api/admin/build.go
@@ -2,7 +2,7 @@
//
// Use of this source code is governed by the LICENSE file in this repository.
-// nolint: dupl // ignore similar code
+//nolint:dupl // ignore similar code
package admin
import (
@@ -20,45 +20,6 @@ import (
"github.com/sirupsen/logrus"
)
-// swagger:operation GET /api/v1/admin/builds admin AdminAllBuilds
-//
-// Get all of the builds in the database
-//
-// ---
-// produces:
-// - application/json
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved all builds from the database
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/Build"
-// '500':
-// description: Unable to retrieve all builds from the database
-// schema:
-// "$ref": "#/definitions/Error"
-
-// AllBuilds represents the API handler to
-// captures all builds stored in the database.
-func AllBuilds(c *gin.Context) {
- logrus.Info("Admin: reading all builds")
-
- // send API call to capture all builds
- b, err := database.FromContext(c).GetBuildList()
- if err != nil {
- retErr := fmt.Errorf("unable to capture all builds: %w", err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, b)
-}
-
// swagger:operation GET /api/v1/admin/builds/queue admin AllBuildsQueue
//
// Get all of the running and pending builds in the database
@@ -89,14 +50,16 @@ func AllBuilds(c *gin.Context) {
// AllBuildsQueue represents the API handler to
// captures all running and pending builds stored in the database.
func AllBuildsQueue(c *gin.Context) {
+ // capture middleware values
+ ctx := c.Request.Context()
+
logrus.Info("Admin: reading running and pending builds")
// default timestamp to 24 hours ago if user did not provide it as query parameter
- // nolint: gomnd // ignore magic number
after := c.DefaultQuery("after", strconv.FormatInt(time.Now().UTC().Add(-24*time.Hour).Unix(), 10))
// send API call to capture pending and running builds
- b, err := database.FromContext(c).GetPendingAndRunningBuilds(after)
+ b, err := database.FromContext(c).ListPendingAndRunningBuilds(ctx, after)
if err != nil {
retErr := fmt.Errorf("unable to capture all running and pending builds: %w", err)
@@ -143,6 +106,9 @@ func AllBuildsQueue(c *gin.Context) {
func UpdateBuild(c *gin.Context) {
logrus.Info("Admin: updating build in database")
+ // capture middleware values
+ ctx := c.Request.Context()
+
// capture body from API request
input := new(library.Build)
@@ -156,7 +122,7 @@ func UpdateBuild(c *gin.Context) {
}
// send API call to update the build
- err = database.FromContext(c).UpdateBuild(input)
+ b, err := database.FromContext(c).UpdateBuild(ctx, input)
if err != nil {
retErr := fmt.Errorf("unable to update build %d: %w", input.GetID(), err)
@@ -165,5 +131,5 @@ func UpdateBuild(c *gin.Context) {
return
}
- c.JSON(http.StatusOK, input)
+ c.JSON(http.StatusOK, b)
}
diff --git a/api/admin/clean.go b/api/admin/clean.go
new file mode 100644
index 000000000..89884c8d5
--- /dev/null
+++ b/api/admin/clean.go
@@ -0,0 +1,138 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package admin
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+ "time"
+
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types"
+ "github.com/go-vela/types/constants"
+
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation PUT /api/v1/admin/clean admin AdminCleanResources
+//
+// Update pending build resources to error status before a given time
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: query
+// name: before
+// description: filter pending resources created before a certain time
+// required: true
+// type: integer
+// - in: body
+// name: body
+// description: Payload containing error message
+// required: true
+// schema:
+// "$ref": "#/definitions/Error"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully updated pending resources with error message
+// schema:
+// type: string
+// '400':
+// description: Unable to update resources â bad request
+// schema:
+// "$ref": "#/definitions/Error"
+// '401':
+// description: Unable to update resources â unauthorized
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to update resources
+// schema:
+// "$ref": "#/definitions/Error"
+
+// CleanResources represents the API handler to
+// update any user stored in the database.
+func CleanResources(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ logrus.Infof("platform admin %s: updating pending resources in database", u.GetName())
+
+ // default error message
+ msg := "build cleaned by platform admin"
+
+ // capture body from API request
+ input := new(types.Error)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for error message: %w", err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // if a message is provided, set the error message to that
+ if input.Message != nil {
+ msg = *input.Message
+ }
+
+ // capture before query parameter, default to max build timeout
+ before, err := strconv.ParseInt(c.DefaultQuery("before", fmt.Sprint((time.Now().Add(-(time.Minute * (constants.BuildTimeoutMax + 5)))).Unix())), 10, 64)
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert before query parameter %s to int64: %w", c.Query("before"), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // send API call to clean builds
+ builds, err := database.FromContext(c).CleanBuilds(ctx, msg, before)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update builds: %w", err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ logrus.Infof("platform admin %s: cleaned %d builds in database", u.GetName(), builds)
+
+ // clean services
+ services, err := database.FromContext(c).CleanServices(msg, before)
+ if err != nil {
+ retErr := fmt.Errorf("%d builds cleaned. unable to update services: %w", builds, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ logrus.Infof("platform admin %s: cleaned %d services in database", u.GetName(), services)
+
+ // clean steps
+ steps, err := database.FromContext(c).CleanSteps(msg, before)
+ if err != nil {
+ retErr := fmt.Errorf("%d builds cleaned. %d services cleaned. unable to update steps: %w", builds, services, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ logrus.Infof("platform admin %s: cleaned %d steps in database", u.GetName(), steps)
+
+ c.JSON(http.StatusOK, fmt.Sprintf("%d builds cleaned. %d services cleaned. %d steps cleaned.", builds, services, steps))
+}
diff --git a/api/admin/deployment.go b/api/admin/deployment.go
index 8d3f2b367..063407b6d 100644
--- a/api/admin/deployment.go
+++ b/api/admin/deployment.go
@@ -10,27 +10,6 @@ import (
"github.com/gin-gonic/gin"
)
-// swagger:operation GET /api/v1/admin/deployments admin AdminAllDeployments
-//
-// Get all of the deployments in the database (Not Implemented)
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// responses:
-// '501':
-// description: This endpoint is not implemented
-// schema:
-// type: string
-
-// AllDeployments represents the API handler to
-// captures all deployments stored in the database.
-func AllDeployments(c *gin.Context) {
- // nolint: lll // ignore long line length due to return message
- c.JSON(http.StatusNotImplemented, "The server does not support the functionality required to fulfill the request.")
-}
-
// swagger:operation PUT /api/v1/admin/deployment admin AdminUpdateDeployment
//
// Get All (Not Implemented)
@@ -48,6 +27,5 @@ func AllDeployments(c *gin.Context) {
// UpdateDeployment represents the API handler to
// update any deployment stored in the database.
func UpdateDeployment(c *gin.Context) {
- // nolint: lll // ignore long line length due to return message
c.JSON(http.StatusNotImplemented, "The server does not support the functionality required to fulfill the request.")
}
diff --git a/api/admin/doc.go b/api/admin/doc.go
index 414fca7e4..565520dbf 100644
--- a/api/admin/doc.go
+++ b/api/admin/doc.go
@@ -6,5 +6,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/api/admin"
+// import "github.com/go-vela/server/api/admin"
package admin
diff --git a/api/admin/hook.go b/api/admin/hook.go
index 55fa8ccad..789a74a25 100644
--- a/api/admin/hook.go
+++ b/api/admin/hook.go
@@ -2,61 +2,20 @@
//
// Use of this source code is governed by the LICENSE file in this repository.
-// nolint: dupl // ignore similar code
+//nolint:dupl // ignore similar code
package admin
import (
"fmt"
"net/http"
+ "github.com/gin-gonic/gin"
"github.com/go-vela/server/database"
"github.com/go-vela/server/util"
-
"github.com/go-vela/types/library"
-
- "github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
-// swagger:operation GET /api/v1/admin/hooks admin AdminAllHooks
-//
-// Get all of the webhooks stored in the database
-//
-// ---
-// produces:
-// - application/json
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved all hooks from the database
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/Webhook"
-// '500':
-// description: Unable to retrieve all hooks
-// schema:
-// "$ref": "#/definitions/Error"
-
-// AllHooks represents the API handler to
-// captures all hooks stored in the database.
-func AllHooks(c *gin.Context) {
- logrus.Info("Admin: reading all hooks")
-
- // send API call to capture all hooks
- r, err := database.FromContext(c).GetHookList()
- if err != nil {
- retErr := fmt.Errorf("unable to capture all hooks: %w", err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, r)
-}
-
// swagger:operation PUT /api/v1/admin/hook admin AdminUpdateHook
//
// Update a hook in the database
@@ -105,7 +64,7 @@ func UpdateHook(c *gin.Context) {
}
// send API call to update the hook
- err = database.FromContext(c).UpdateHook(input)
+ h, err := database.FromContext(c).UpdateHook(input)
if err != nil {
retErr := fmt.Errorf("unable to update hook %d: %w", input.GetID(), err)
@@ -114,5 +73,5 @@ func UpdateHook(c *gin.Context) {
return
}
- c.JSON(http.StatusOK, input)
+ c.JSON(http.StatusOK, h)
}
diff --git a/api/admin/repo.go b/api/admin/repo.go
index 81916a05f..b24fb7013 100644
--- a/api/admin/repo.go
+++ b/api/admin/repo.go
@@ -2,7 +2,7 @@
//
// Use of this source code is governed by the LICENSE file in this repository.
-// nolint: dupl // ignore similar code
+//nolint:dupl // ignore similar code
package admin
import (
@@ -18,45 +18,6 @@ import (
"github.com/sirupsen/logrus"
)
-// swagger:operation GET /api/v1/admin/repos admin AdminAllRepos
-//
-// Get all of the repos in the database
-//
-// ---
-// produces:
-// - application/json
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved all repos from the database
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/Repo"
-// '500':
-// description: Unable to retrieve all repos from the database
-// schema:
-// "$ref": "#/definitions/Error"
-
-// AllRepos represents the API handler to
-// captures all repos stored in the database.
-func AllRepos(c *gin.Context) {
- logrus.Info("Admin: reading all repos")
-
- // send API call to capture all repos
- r, err := database.FromContext(c).GetRepoList()
- if err != nil {
- retErr := fmt.Errorf("unable to capture all repos: %w", err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, r)
-}
-
// swagger:operation PUT /api/v1/admin/repo admin AdminUpdateRepo
//
// Update a repo in the database
@@ -92,6 +53,9 @@ func AllRepos(c *gin.Context) {
func UpdateRepo(c *gin.Context) {
logrus.Info("Admin: updating repo in database")
+ // capture middleware values
+ ctx := c.Request.Context()
+
// capture body from API request
input := new(library.Repo)
@@ -105,7 +69,7 @@ func UpdateRepo(c *gin.Context) {
}
// send API call to update the repo
- err = database.FromContext(c).UpdateRepo(input)
+ r, err := database.FromContext(c).UpdateRepo(ctx, input)
if err != nil {
retErr := fmt.Errorf("unable to update repo %d: %w", input.GetID(), err)
@@ -114,5 +78,5 @@ func UpdateRepo(c *gin.Context) {
return
}
- c.JSON(http.StatusOK, input)
+ c.JSON(http.StatusOK, r)
}
diff --git a/api/admin/secret.go b/api/admin/secret.go
index 5ac5d57ca..c9e1d8e73 100644
--- a/api/admin/secret.go
+++ b/api/admin/secret.go
@@ -2,7 +2,7 @@
//
// Use of this source code is governed by the LICENSE file in this repository.
-// nolint: dupl // ignore similar code
+//nolint:dupl // ignore similar code
package admin
import (
@@ -18,45 +18,6 @@ import (
"github.com/sirupsen/logrus"
)
-// swagger:operation GET /api/v1/admin/secrets admin AdminAllSecrets
-//
-// Get all of the secrets in the database
-//
-// ---
-// produces:
-// - application/json
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved all secrets from the database
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/Secret"
-// '500':
-// description: Unable to retrieve all secrets from the database
-// schema:
-// "$ref": "#/definitions/Error"
-
-// AllSecrets represents the API handler to
-// captures all secrets stored in the database.
-func AllSecrets(c *gin.Context) {
- logrus.Info("Admin: reading all secrets")
-
- // send API call to capture all secrets
- s, err := database.FromContext(c).GetSecretList()
- if err != nil {
- retErr := fmt.Errorf("unable to capture all secrets: %w", err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, s)
-}
-
// swagger:operation PUT /api/v1/admin/secret admin AdminUpdateSecret
//
// Update a secret in the database
@@ -105,7 +66,7 @@ func UpdateSecret(c *gin.Context) {
}
// send API call to update the secret
- err = database.FromContext(c).UpdateSecret(input)
+ s, err := database.FromContext(c).UpdateSecret(input)
if err != nil {
retErr := fmt.Errorf("unable to update secret %d: %w", input.GetID(), err)
@@ -114,5 +75,5 @@ func UpdateSecret(c *gin.Context) {
return
}
- c.JSON(http.StatusOK, input)
+ c.JSON(http.StatusOK, s)
}
diff --git a/api/admin/service.go b/api/admin/service.go
index f44e7c6a3..24ddae3dc 100644
--- a/api/admin/service.go
+++ b/api/admin/service.go
@@ -2,7 +2,7 @@
//
// Use of this source code is governed by the LICENSE file in this repository.
-// nolint: dupl // ignore similar code
+//nolint:dupl // ignore similar code
package admin
import (
@@ -18,45 +18,6 @@ import (
"github.com/sirupsen/logrus"
)
-// swagger:operation GET /api/v1/admin/services admin AdminAllServices
-//
-// Get all of the services in the database
-//
-// ---
-// produces:
-// - application/json
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved all services from the database
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/Service"
-// '500':
-// description: Unable to retrieve all services from the database
-// schema:
-// "$ref": "#/definitions/Error"
-
-// AllServices represents the API handler to
-// captures all services stored in the database.
-func AllServices(c *gin.Context) {
- logrus.Info("Admin: reading all services")
-
- // send API call to capture all services
- s, err := database.FromContext(c).GetServiceList()
- if err != nil {
- retErr := fmt.Errorf("unable to capture all services: %w", err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, s)
-}
-
// swagger:operation PUT /api/v1/admin/service admin AdminUpdateService
//
// Update a hook in the database
@@ -106,7 +67,7 @@ func UpdateService(c *gin.Context) {
}
// send API call to update the service
- err = database.FromContext(c).UpdateService(input)
+ s, err := database.FromContext(c).UpdateService(input)
if err != nil {
retErr := fmt.Errorf("unable to update service %d: %w", input.GetID(), err)
@@ -115,5 +76,5 @@ func UpdateService(c *gin.Context) {
return
}
- c.JSON(http.StatusOK, input)
+ c.JSON(http.StatusOK, s)
}
diff --git a/api/admin/step.go b/api/admin/step.go
index fe097e2f0..2da12ece1 100644
--- a/api/admin/step.go
+++ b/api/admin/step.go
@@ -2,7 +2,7 @@
//
// Use of this source code is governed by the LICENSE file in this repository.
-// nolint: dupl // ignore similar code
+//nolint:dupl // ignore similar code
package admin
import (
@@ -18,45 +18,6 @@ import (
"github.com/sirupsen/logrus"
)
-// swagger:operation GET /api/v1/admin/steps admin AdminAllSteps
-//
-// Get all of the steps in the database
-//
-// ---
-// produces:
-// - application/json
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved all steps from the database
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/Step"
-// '500':
-// description: Unable to retrieve all steps from the database
-// schema:
-// "$ref": "#/definitions/Error"
-
-// AllSteps represents the API handler to
-// captures all steps stored in the database.
-func AllSteps(c *gin.Context) {
- logrus.Info("Admin: reading all steps")
-
- // send API call to capture all steps
- s, err := database.FromContext(c).GetStepList()
- if err != nil {
- retErr := fmt.Errorf("unable to capture all steps: %w", err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, s)
-}
-
// swagger:operation PUT /api/v1/admin/step admin AdminUpdateStep
//
// Update a step in the database
@@ -105,7 +66,7 @@ func UpdateStep(c *gin.Context) {
}
// send API call to update the step
- err = database.FromContext(c).UpdateStep(input)
+ s, err := database.FromContext(c).UpdateStep(input)
if err != nil {
retErr := fmt.Errorf("unable to update step %d: %w", input.GetID(), err)
@@ -114,5 +75,5 @@ func UpdateStep(c *gin.Context) {
return
}
- c.JSON(http.StatusOK, input)
+ c.JSON(http.StatusOK, s)
}
diff --git a/api/admin/user.go b/api/admin/user.go
index e8729b628..f7ef70ba3 100644
--- a/api/admin/user.go
+++ b/api/admin/user.go
@@ -2,7 +2,7 @@
//
// Use of this source code is governed by the LICENSE file in this repository.
-// nolint: dupl // ignore similar code
+//nolint:dupl // ignore similar code
package admin
import (
@@ -18,45 +18,6 @@ import (
"github.com/sirupsen/logrus"
)
-// swagger:operation GET /api/v1/admin/users admin AdminAllUsers
-//
-// Get all of the users in the database
-//
-// ---
-// produces:
-// - application/json
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved all users from the database
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/User"
-// '500':
-// description: Unable to retrieve all users from the database
-// schema:
-// type: string
-
-// AllUsers represents the API handler to
-// captures all users stored in the database.
-func AllUsers(c *gin.Context) {
- logrus.Info("Admin: reading all users")
-
- // send API call to capture all users
- u, err := database.FromContext(c).GetUserList()
- if err != nil {
- retErr := fmt.Errorf("unable to capture all users: %w", err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, u)
-}
-
// swagger:operation PUT /api/v1/admin/user admin AdminUpdateUser
//
// Update a user in the database
diff --git a/api/admin/worker.go b/api/admin/worker.go
new file mode 100644
index 000000000..8c0f9978a
--- /dev/null
+++ b/api/admin/worker.go
@@ -0,0 +1,73 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package admin
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/go-vela/server/internal/token"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/admin/workers/{worker}/register-token admin RegisterToken
+//
+// Get a worker registration token
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: worker
+// description: Hostname of the worker
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully generated registration token
+// schema:
+// "$ref": "#/definitions/Token"
+// '401':
+// description: Unauthorized
+// schema:
+// "$ref": "#/definitions/Error"
+
+// RegisterToken represents the API handler to
+// generate a registration token for onboarding a worker.
+func RegisterToken(c *gin.Context) {
+ // retrieve user from context
+ u := user.Retrieve(c)
+
+ logrus.Infof("Platform admin %s: generating registration token", u.GetName())
+
+ host := util.PathParameter(c, "worker")
+
+ tm := c.MustGet("token-manager").(*token.Manager)
+ rmto := &token.MintTokenOpts{
+ Hostname: host,
+ TokenType: constants.WorkerRegisterTokenType,
+ TokenDuration: tm.WorkerRegisterTokenDuration,
+ }
+
+ rt, err := tm.MintToken(rmto)
+ if err != nil {
+ retErr := fmt.Errorf("unable to generate registration token: %w", err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, library.Token{Token: &rt})
+}
diff --git a/api/auth/doc.go b/api/auth/doc.go
new file mode 100644
index 000000000..c200b5faf
--- /dev/null
+++ b/api/auth/doc.go
@@ -0,0 +1,10 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+// Package auth provides the auth handlers (authenticate, login, ...) for the Vela API.
+//
+// Usage:
+//
+// import "github.com/go-vela/server/api/auth"
+package auth
diff --git a/api/auth/get_token.go b/api/auth/get_token.go
new file mode 100644
index 000000000..29829f072
--- /dev/null
+++ b/api/auth/get_token.go
@@ -0,0 +1,168 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package auth
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/internal/token"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+)
+
+// swagger:operation GET /authenticate authenticate GetAuthToken
+//
+// Start OAuth flow or exchange tokens
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: query
+// name: code
+// description: the code received after identity confirmation
+// type: string
+// - in: query
+// name: state
+// description: a random string
+// type: string
+// - in: query
+// name: redirect_uri
+// description: the url where the user will be sent after authorization
+// type: string
+// responses:
+// '200':
+// description: Successfully authenticated
+// headers:
+// Set-Cookie:
+// type: string
+// schema:
+// "$ref": "#/definitions/Token"
+// '307':
+// description: Redirected for authentication
+// '401':
+// description: Unable to authenticate
+// schema:
+// "$ref": "#/definitions/Error"
+// '503':
+// description: Service unavailable
+// schema:
+// "$ref": "#/definitions/Error"
+
+// GetAuthToken represents the API handler to
+// process a user logging in to Vela from
+// the API or UI.
+func GetAuthToken(c *gin.Context) {
+ var err error
+
+ tm := c.MustGet("token-manager").(*token.Manager)
+
+ // capture the OAuth state if present
+ oAuthState := c.Request.FormValue("state")
+
+ // capture the OAuth code if present
+ code := c.Request.FormValue("code")
+ if len(code) == 0 {
+ // start the initial OAuth workflow
+ oAuthState, err = scm.FromContext(c).Login(c.Writer, c.Request)
+ if err != nil {
+ retErr := fmt.Errorf("unable to login user: %w", err)
+
+ util.HandleError(c, http.StatusUnauthorized, retErr)
+
+ return
+ }
+ }
+
+ // complete the OAuth workflow and authenticates the user
+ newUser, err := scm.FromContext(c).Authenticate(c.Writer, c.Request, oAuthState)
+ if err != nil {
+ retErr := fmt.Errorf("unable to authenticate user: %w", err)
+
+ util.HandleError(c, http.StatusUnauthorized, retErr)
+
+ return
+ }
+
+ // this will happen if the user is redirected by the
+ // source provider as part of the authorization workflow.
+ if newUser == nil {
+ return
+ }
+
+ // send API call to capture the user logging in
+ u, err := database.FromContext(c).GetUserForName(newUser.GetName())
+ // create a new user account
+ if len(u.GetName()) == 0 || err != nil {
+ // create the user account
+ u := new(library.User)
+ u.SetName(newUser.GetName())
+ u.SetToken(newUser.GetToken())
+ u.SetActive(true)
+ u.SetAdmin(false)
+
+ // compose jwt tokens for user
+ rt, at, err := tm.Compose(c, u)
+ if err != nil {
+ retErr := fmt.Errorf("unable to compose token for user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusServiceUnavailable, retErr)
+
+ return
+ }
+
+ // store the refresh token with the user object
+ u.SetRefreshToken(rt)
+
+ // send API call to create the user in the database
+ err = database.FromContext(c).CreateUser(u)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusServiceUnavailable, retErr)
+
+ return
+ }
+
+ // return the jwt access token
+ c.JSON(http.StatusOK, library.Token{Token: &at})
+
+ return
+ }
+
+ // update the user account
+ u.SetToken(newUser.GetToken())
+ u.SetActive(true)
+
+ // compose jwt tokens for user
+ rt, at, err := tm.Compose(c, u)
+ if err != nil {
+ retErr := fmt.Errorf("unable to compose token for user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusServiceUnavailable, retErr)
+
+ return
+ }
+
+ // store the refresh token with the user object
+ u.SetRefreshToken(rt)
+
+ // send API call to update the user in the database
+ err = database.FromContext(c).UpdateUser(u)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusServiceUnavailable, retErr)
+
+ return
+ }
+
+ // return the user with their jwt access token
+ c.JSON(http.StatusOK, library.Token{Token: &at})
+}
diff --git a/api/login.go b/api/auth/login.go
similarity index 90%
rename from api/login.go
rename to api/auth/login.go
index 1aa816730..36b977009 100644
--- a/api/login.go
+++ b/api/auth/login.go
@@ -1,17 +1,17 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
-package api
+package auth
import (
"fmt"
"net/http"
"net/url"
- "github.com/go-vela/types"
-
"github.com/gin-gonic/gin"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types"
"github.com/sirupsen/logrus"
)
@@ -43,8 +43,8 @@ func Login(c *gin.Context) {
m := c.MustGet("metadata").(*types.Metadata)
// capture query params
- t := c.Request.FormValue("type")
- p := c.Request.FormValue("port")
+ t := util.FormParameter(c, "type")
+ p := util.FormParameter(c, "port")
// temp variable to hold redirect destination
r := ""
diff --git a/api/logout.go b/api/auth/logout.go
similarity index 96%
rename from api/logout.go
rename to api/auth/logout.go
index 5d5d39556..8accdf42e 100644
--- a/api/logout.go
+++ b/api/auth/logout.go
@@ -1,8 +1,8 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
-package api
+package auth
import (
"fmt"
diff --git a/api/auth/post_token.go b/api/auth/post_token.go
new file mode 100644
index 000000000..8b386bfcf
--- /dev/null
+++ b/api/auth/post_token.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 auth
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/internal/token"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+)
+
+// swagger:operation POST /authenticate/token authenticate PostAuthToken
+//
+// Authenticate to Vela via personal access token
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: header
+// name: Token
+// type: string
+// required: true
+// description: >
+// scopes: repo, repo:status, user:email, read:user, and read:org
+// responses:
+// '200':
+// description: Successfully authenticated
+// schema:
+// "$ref": "#/definitions/Token"
+// '401':
+// description: Unable to authenticate
+// schema:
+// "$ref": "#/definitions/Error"
+// '503':
+// description: Service unavailable
+// schema:
+// "$ref": "#/definitions/Error"
+
+// PostAuthToken represents the API handler to
+// process a user logging in using PAT to Vela from
+// the API.
+func PostAuthToken(c *gin.Context) {
+ // attempt to get user from source
+ u, err := scm.FromContext(c).AuthenticateToken(c.Request)
+ if err != nil {
+ retErr := fmt.Errorf("unable to authenticate user: %w", err)
+
+ util.HandleError(c, http.StatusUnauthorized, retErr)
+
+ return
+ }
+
+ // check if the user exists
+ u, err = database.FromContext(c).GetUserForName(u.GetName())
+ if err != nil {
+ retErr := fmt.Errorf("user %s not found", u.GetName())
+
+ util.HandleError(c, http.StatusUnauthorized, retErr)
+
+ return
+ }
+
+ // We don't need refresh token for this scenario
+ // We only need access token and are configured based on the config defined
+ tm := c.MustGet("token-manager").(*token.Manager)
+
+ // mint token options for access token
+ amto := &token.MintTokenOpts{
+ User: u,
+ TokenType: constants.UserAccessTokenType,
+ TokenDuration: tm.UserAccessTokenDuration,
+ }
+ at, err := tm.MintToken(amto)
+
+ if err != nil {
+ retErr := fmt.Errorf("unable to compose token for user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusServiceUnavailable, retErr)
+ }
+
+ // return the user with their jwt access token
+ c.JSON(http.StatusOK, library.Token{Token: &at})
+}
diff --git a/api/auth/redirect.go b/api/auth/redirect.go
new file mode 100644
index 000000000..32b486393
--- /dev/null
+++ b/api/auth/redirect.go
@@ -0,0 +1,102 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package auth
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /authenticate/web authenticate GetAuthenticateTypeWeb
+//
+// Authentication entrypoint that builds the right post-auth
+// redirect URL for web authentication requests
+// and redirects to /authenticate after
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: query
+// name: code
+// description: the code received after identity confirmation
+// type: string
+// - in: query
+// name: state
+// description: a random string
+// type: string
+// responses:
+// '307':
+// description: Redirected for authentication
+
+// swagger:operation GET /authenticate/cli/{port} authenticate GetAuthenticateTypeCLI
+//
+// Authentication entrypoint that builds the right post-auth
+// redirect URL for CLI authentication requests
+// and redirects to /authenticate after
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: port
+// required: true
+// description: the port number
+// type: integer
+// - in: query
+// name: code
+// description: the code received after identity confirmation
+// type: string
+// - in: query
+// name: state
+// description: a random string
+// type: string
+// responses:
+// '307':
+// description: Redirected for authentication
+
+// GetAuthRedirect handles cases where the OAuth callback was
+// overridden by supplying a redirect_uri in the login process.
+// It will send the user to the destination to handle the last leg
+// in the auth flow - exchanging "code" and "state" for a token.
+// This will only handle non-headless flows (ie. web or cli).
+func GetAuthRedirect(c *gin.Context) {
+ // load the metadata
+ m := c.MustGet("metadata").(*types.Metadata)
+
+ logrus.Info("redirecting for final auth flow destination")
+
+ // capture the path elements
+ t := util.PathParameter(c, "type")
+ p := util.PathParameter(c, "port")
+
+ // capture the current query parameters -
+ // they should contain the "code" and "state" values
+ q := c.Request.URL.Query()
+
+ // default redirect location if a user ended up here
+ // by providing an unsupported type
+ r := fmt.Sprintf("%s/authenticate", m.Vela.Address)
+
+ switch t {
+ // cli auth flow
+ case "cli":
+ r = fmt.Sprintf("http://127.0.0.1:%s", p)
+ // web auth flow
+ case "web":
+ r = fmt.Sprintf("%s%s", m.Vela.WebAddress, m.Vela.WebOauthCallbackPath)
+ }
+
+ // append the code and state values
+ r = fmt.Sprintf("%s?%s", r, q.Encode())
+
+ c.Redirect(http.StatusTemporaryRedirect, r)
+}
diff --git a/api/token.go b/api/auth/refresh.go
similarity index 74%
rename from api/token.go
rename to api/auth/refresh.go
index 1225f149f..0c12425cc 100644
--- a/api/token.go
+++ b/api/auth/refresh.go
@@ -1,19 +1,18 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
-package api
+package auth
import (
"fmt"
"net/http"
- "github.com/go-vela/server/router/middleware/token"
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/internal/token"
+ "github.com/go-vela/server/router/middleware/auth"
"github.com/go-vela/server/util"
-
"github.com/go-vela/types/library"
-
- "github.com/gin-gonic/gin"
)
// swagger:operation GET /token-refresh authenticate GetRefreshAccessToken
@@ -29,7 +28,7 @@ import (
// '200':
// description: Successfully refreshed a token
// schema:
-// "$ref": "#/definitions/Login"
+// "$ref": "#/definitions/Token"
// '401':
// description: Unauthorized
// schema:
@@ -41,7 +40,7 @@ func RefreshAccessToken(c *gin.Context) {
// capture the refresh token
// TODO: move this into token package and do it internally
// since we are already passsing context
- rt, err := token.RetrieveRefreshToken(c.Request)
+ rt, err := auth.RetrieveRefreshToken(c.Request)
if err != nil {
retErr := fmt.Errorf("refresh token error: %w", err)
@@ -50,8 +49,10 @@ func RefreshAccessToken(c *gin.Context) {
return
}
+ tm := c.MustGet("token-manager").(*token.Manager)
+
// validate the refresh token and return a new access token
- newAccessToken, err := token.Refresh(c, rt)
+ newAccessToken, err := tm.Refresh(c, rt)
if err != nil {
retErr := fmt.Errorf("unable to refresh token: %w", err)
@@ -60,5 +61,5 @@ func RefreshAccessToken(c *gin.Context) {
return
}
- c.JSON(http.StatusOK, library.Login{Token: &newAccessToken})
+ c.JSON(http.StatusOK, library.Token{Token: &newAccessToken})
}
diff --git a/api/auth/validate.go b/api/auth/validate.go
new file mode 100644
index 000000000..d78057193
--- /dev/null
+++ b/api/auth/validate.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 auth
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/router/middleware/claims"
+ "github.com/go-vela/server/util"
+)
+
+// swagger:operation GET /validate-token authenticate ValidateServerToken
+//
+// Validate a server token
+//
+// ---
+// produces:
+// - application/json
+// security:
+// - CookieAuth: []
+// responses:
+// '200':
+// description: Successfully validated a token
+// schema:
+// type: string
+// '401':
+// description: Unauthorized
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ValidateServerToken will validate if a token was issued by the server
+// if it is provided in the auth header.
+func ValidateServerToken(c *gin.Context) {
+ cl := claims.Retrieve(c)
+
+ if !strings.EqualFold(cl.Subject, "vela-server") {
+ retErr := fmt.Errorf("token is not a valid server token")
+
+ util.HandleError(c, http.StatusUnauthorized, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, "valid server token")
+}
diff --git a/api/authenticate.go b/api/authenticate.go
deleted file mode 100644
index b484a4643..000000000
--- a/api/authenticate.go
+++ /dev/null
@@ -1,343 +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 api
-
-import (
- "encoding/base64"
- "fmt"
- "net/http"
-
- "github.com/go-vela/server/database"
- "github.com/go-vela/server/router/middleware/token"
- "github.com/go-vela/server/scm"
- "github.com/go-vela/server/util"
-
- "github.com/go-vela/types"
- "github.com/go-vela/types/library"
-
- "github.com/gin-gonic/gin"
- "github.com/google/uuid"
- "github.com/sirupsen/logrus"
-)
-
-// swagger:operation GET /authenticate authenticate GetAuthenticate
-//
-// Start OAuth flow or exchange tokens
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: query
-// name: code
-// description: the code received after identity confirmation
-// type: string
-// - in: query
-// name: state
-// description: a random string
-// type: string
-// - in: query
-// name: redirect_uri
-// description: the url where the user will be sent after authorization
-// type: string
-// responses:
-// '200':
-// description: Successfully authenticated
-// headers:
-// Set-Cookie:
-// type: string
-// schema:
-// "$ref": "#/definitions/Login"
-// '307':
-// description: Redirected for authentication
-// '401':
-// description: Unable to authenticate
-// schema:
-// "$ref": "#/definitions/Error"
-// '503':
-// description: Service unavailable
-// schema:
-// "$ref": "#/definitions/Error"
-
-// Authenticate represents the API handler to
-// process a user logging in to Vela from
-// the API or UI.
-//
-// nolint: funlen // ignore function length due to comments
-func Authenticate(c *gin.Context) {
- var err error
-
- // capture the OAuth state if present
- oAuthState := c.Request.FormValue("state")
-
- // capture the OAuth code if present
- code := c.Request.FormValue("code")
- if len(code) == 0 {
- // start the initial OAuth workflow
- oAuthState, err = scm.FromContext(c).Login(c.Writer, c.Request)
- if err != nil {
- retErr := fmt.Errorf("unable to login user: %w", err)
-
- util.HandleError(c, http.StatusUnauthorized, retErr)
-
- return
- }
- }
-
- // complete the OAuth workflow and authenticates the user
- newUser, err := scm.FromContext(c).Authenticate(c.Writer, c.Request, oAuthState)
- if err != nil {
- retErr := fmt.Errorf("unable to authenticate user: %w", err)
-
- util.HandleError(c, http.StatusUnauthorized, retErr)
-
- return
- }
-
- // this will happen if the user is redirected by the
- // source provider as part of the authorization workflow.
- if newUser == nil {
- return
- }
-
- // send API call to capture the user logging in
- u, err := database.FromContext(c).GetUserName(newUser.GetName())
- // create a new user account
- if len(u.GetName()) == 0 || err != nil {
- // create unique id for the user
- uid, err := uuid.NewRandom()
- if err != nil {
- retErr := fmt.Errorf("unable to create UID for user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusServiceUnavailable, retErr)
-
- return
- }
-
- // create the user account
- u := new(library.User)
- u.SetName(newUser.GetName())
- u.SetToken(newUser.GetToken())
- u.SetHash(
- base64.StdEncoding.EncodeToString(
- []byte(uid.String()),
- ),
- )
- u.SetActive(true)
- u.SetAdmin(false)
-
- // compose jwt tokens for user
- rt, at, err := token.Compose(c, u)
- if err != nil {
- retErr := fmt.Errorf("unable to compose token for user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusServiceUnavailable, retErr)
-
- return
- }
-
- // store the refresh token with the user object
- u.SetRefreshToken(rt)
-
- // send API call to create the user in the database
- err = database.FromContext(c).CreateUser(u)
- if err != nil {
- retErr := fmt.Errorf("unable to create user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusServiceUnavailable, retErr)
-
- return
- }
-
- // return the jwt access token
- c.JSON(http.StatusOK, library.Login{Token: &at})
-
- return
- }
-
- // update the user account
- u.SetToken(newUser.GetToken())
- u.SetActive(true)
-
- // compose jwt tokens for user
- rt, at, err := token.Compose(c, u)
- if err != nil {
- retErr := fmt.Errorf("unable to compose token for user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusServiceUnavailable, retErr)
-
- return
- }
-
- // store the refresh token with the user object
- u.SetRefreshToken(rt)
-
- // send API call to update the user in the database
- err = database.FromContext(c).UpdateUser(u)
- if err != nil {
- retErr := fmt.Errorf("unable to update user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusServiceUnavailable, retErr)
-
- return
- }
-
- // return the user with their jwt access token
- c.JSON(http.StatusOK, library.Login{Token: &at})
-}
-
-// swagger:operation GET /authenticate/web authenticate GetAuthenticateTypeWeb
-//
-// Authentication entrypoint that builds the right post-auth
-// redirect URL for web authentication requests
-// and redirects to /authenticate after
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: query
-// name: code
-// description: the code received after identity confirmation
-// type: string
-// - in: query
-// name: state
-// description: a random string
-// type: string
-// responses:
-// '307':
-// description: Redirected for authentication
-
-// swagger:operation GET /authenticate/cli/{port} authenticate GetAuthenticateTypeCLI
-//
-// Authentication entrypoint that builds the right post-auth
-// redirect URL for CLI authentication requests
-// and redirects to /authenticate after
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: port
-// required: true
-// description: the port number
-// type: integer
-// - in: query
-// name: code
-// description: the code received after identity confirmation
-// type: string
-// - in: query
-// name: state
-// description: a random string
-// type: string
-// responses:
-// '307':
-// description: Redirected for authentication
-
-// AuthenticateType handles cases where the OAuth callback was
-// overridden by supplying a redirect_uri in the login process.
-// It will send the user to the destination to handle the last leg
-// in the auth flow - exchanging "code" and "state" for a token.
-// This will only handle non-headless flows (ie. web or cli).
-func AuthenticateType(c *gin.Context) {
- // load the metadata
- m := c.MustGet("metadata").(*types.Metadata)
-
- logrus.Info("redirecting for final auth flow destination")
-
- // capture the path elements
- t := c.Param("type")
- p := c.Param("port")
-
- // capture the current query parameters -
- // they should contain the "code" and "state" values
- q := c.Request.URL.Query()
-
- // default redirect location if a user ended up here
- // by providing an unsupported type
- r := fmt.Sprintf("%s/authenticate", m.Vela.Address)
-
- switch t {
- // cli auth flow
- case "cli":
- r = fmt.Sprintf("http://127.0.0.1:%s", p)
- // web auth flow
- case "web":
- r = fmt.Sprintf("%s%s", m.Vela.WebAddress, m.Vela.WebOauthCallbackPath)
- }
-
- // append the code and state values
- r = fmt.Sprintf("%s?%s", r, q.Encode())
-
- c.Redirect(http.StatusTemporaryRedirect, r)
-}
-
-// swagger:operation POST /authenticate/token authenticate PostAuthenticateToken
-//
-// Authenticate to Vela via personal access token
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: header
-// name: Token
-// type: string
-// required: true
-// description: >
-// scopes: repo, repo:status, user:email, read:user, and read:org
-// responses:
-// '200':
-// description: Successfully authenticated
-// schema:
-// "$ref": "#/definitions/Login"
-// '401':
-// description: Unable to authenticate
-// schema:
-// "$ref": "#/definitions/Error"
-// '503':
-// description: Service unavailable
-// schema:
-// "$ref": "#/definitions/Error"
-
-// AuthenticateToken represents the API handler to
-// process a user logging in using PAT to Vela from
-// the API.
-func AuthenticateToken(c *gin.Context) {
- // attempt to get user from source
- u, err := scm.FromContext(c).AuthenticateToken(c.Request)
- if err != nil {
- retErr := fmt.Errorf("unable to authenticate user: %w", err)
-
- util.HandleError(c, http.StatusUnauthorized, retErr)
-
- return
- }
-
- // check if the user exists
- u, err = database.FromContext(c).GetUserName(u.GetName())
- if err != nil {
- retErr := fmt.Errorf("user %s not found", u.GetName())
-
- util.HandleError(c, http.StatusUnauthorized, retErr)
-
- return
- }
-
- // We don't need refresh token for this scenario
- // We only need access token and are configured based on the config defined
- m := c.MustGet("metadata").(*types.Metadata)
- at, err := token.CreateAccessToken(u, m.Vela.AccessTokenDuration)
-
- if err != nil {
- retErr := fmt.Errorf("unable to compose token for user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusServiceUnavailable, retErr)
- }
-
- // return the user with their jwt access token
- c.JSON(http.StatusOK, library.Login{Token: &at})
-}
diff --git a/api/badge.go b/api/badge.go
index 79ef4e008..bb84d2c48 100644
--- a/api/badge.go
+++ b/api/badge.go
@@ -7,13 +7,12 @@ package api
import (
"net/http"
+ "github.com/gin-gonic/gin"
"github.com/go-vela/server/database"
"github.com/go-vela/server/router/middleware/org"
"github.com/go-vela/server/router/middleware/repo"
-
+ "github.com/go-vela/server/util"
"github.com/go-vela/types/constants"
-
- "github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
@@ -47,7 +46,9 @@ func GetBadge(c *gin.Context) {
// capture middleware values
o := org.Retrieve(c)
r := repo.Retrieve(c)
- branch := c.DefaultQuery("branch", r.GetBranch())
+ ctx := c.Request.Context()
+
+ branch := util.QueryParameter(c, "branch", r.GetBranch())
// update engine logger with API metadata
//
@@ -58,7 +59,7 @@ func GetBadge(c *gin.Context) {
}).Infof("creating latest build badge for repo %s on branch %s", r.GetFullName(), branch)
// send API call to capture the last build for the repo and branch
- b, err := database.FromContext(c).GetLastBuildByBranch(r, branch)
+ b, err := database.FromContext(c).LastBuildForRepo(ctx, r, branch)
if err != nil {
c.String(http.StatusOK, constants.BadgeUnknown)
return
diff --git a/api/build.go b/api/build.go
deleted file mode 100644
index 0c5fb03fc..000000000
--- a/api/build.go
+++ /dev/null
@@ -1,1643 +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 api
-
-import (
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net/http"
- "strconv"
- "strings"
- "time"
-
- "github.com/go-vela/server/router/middleware/org"
-
- "github.com/go-vela/server/compiler"
- "github.com/go-vela/server/database"
- "github.com/go-vela/server/queue"
- "github.com/go-vela/server/router/middleware/build"
- "github.com/go-vela/server/router/middleware/executors"
- "github.com/go-vela/server/router/middleware/repo"
- "github.com/go-vela/server/router/middleware/user"
- "github.com/go-vela/server/scm"
- "github.com/go-vela/server/util"
-
- "github.com/go-vela/types"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/library"
- "github.com/go-vela/types/pipeline"
-
- "github.com/gin-gonic/gin"
- "github.com/sirupsen/logrus"
-)
-
-// swagger:operation POST /api/v1/repos/{org}/{repo}/builds builds CreateBuild
-//
-// Create a build in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: body
-// name: body
-// description: Payload containing the build to update
-// required: true
-// schema:
-// "$ref": "#/definitions/Build"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Request processed but build was skipped
-// schema:
-// type: string
-// '201':
-// description: Successfully created the build
-// type: json
-// schema:
-// "$ref": "#/definitions/Build"
-// '400':
-// description: Unable to create the build
-// schema:
-// "$ref": "#/definitions/Error"
-// '404':
-// description: Unable to create the build
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to create the build
-// schema:
-// "$ref": "#/definitions/Error"
-
-// CreateBuild represents the API handler to
-// create a build in the configured backend.
-//
-// nolint: funlen // ignore function length due to comments
-func CreateBuild(c *gin.Context) {
- // capture middleware values
- m := c.MustGet("metadata").(*types.Metadata)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logger := logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- })
-
- logger.Infof("creating new build for repo %s", r.GetFullName())
-
- // capture body from API request
- input := new(library.Build)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for new build for repo %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // verify the build has a valid event and the repo allows that event type
- if (input.GetEvent() == constants.EventPush && !r.GetAllowPush()) ||
- (input.GetEvent() == constants.EventPull && !r.GetAllowPull()) ||
- (input.GetEvent() == constants.EventTag && !r.GetAllowTag()) ||
- (input.GetEvent() == constants.EventDeploy && !r.GetAllowDeploy()) {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to create new build: %s does not have %s events enabled", r.GetFullName(), input.GetEvent())
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // send API call to capture the repo owner
- u, err = database.FromContext(c).GetUser(r.GetUserID())
- if err != nil {
- retErr := fmt.Errorf("unable to get owner for %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // create SQL filters for querying pending and running builds for repo
- filters := map[string]interface{}{
- "status": []string{constants.StatusPending, constants.StatusRunning},
- }
-
- // send API call to capture the number of pending or running builds for the repo
- builds, err := database.FromContext(c).GetRepoBuildCount(r, filters)
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to create new build: unable to get count of builds for repo %s", r.GetFullName())
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // check if the number of pending and running builds exceeds the limit for the repo
- if builds >= r.GetBuildLimit() {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to create new build: repo %s has exceeded the concurrent build limit of %d", r.GetFullName(), r.GetBuildLimit())
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // send API call to capture the last build for the repo
- lastBuild, err := database.FromContext(c).GetLastBuild(r)
- if err != nil {
- retErr := fmt.Errorf("unable to get last build for %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // update fields in build object
- input.SetRepoID(r.GetID())
- input.SetStatus(constants.StatusPending)
- input.SetCreated(time.Now().UTC().Unix())
- input.SetNumber(1)
- input.SetParent(input.GetNumber())
-
- if lastBuild != nil {
- input.SetNumber(
- lastBuild.GetNumber() + 1,
- )
- input.SetParent(lastBuild.GetNumber())
- }
-
- // populate the build link if a web address is provided
- if len(m.Vela.WebAddress) > 0 {
- input.SetLink(
- fmt.Sprintf("%s/%s/%d", m.Vela.WebAddress, r.GetFullName(), input.GetNumber()),
- )
- }
-
- // variable to store changeset files
- var files []string
- // check if the build event is not pull_request
- if !strings.EqualFold(input.GetEvent(), constants.EventPull) {
- // send API call to capture list of files changed for the commit
- files, err = scm.FromContext(c).Changeset(u, r, input.GetCommit())
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to create new build: failed to get changeset for %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
- }
-
- // handle getting changeset from a pull_request
- if strings.EqualFold(input.GetEvent(), constants.EventPull) {
- // capture number from build
- number, err := getPRNumberFromBuild(input)
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to create new build: failed to get pull_request number for %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture list of files changed for the pull request
- files, err = scm.FromContext(c).ChangesetPR(u, r, number)
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to create new build: failed to get changeset for %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
- }
-
- // send API call to capture the pipeline configuration file
- config, err := scm.FromContext(c).ConfigBackoff(u, r, input.GetCommit())
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to get pipeline configuration for %s/%d: %w", r.GetFullName(), input.GetNumber(), err)
-
- util.HandleError(c, http.StatusNotFound, retErr)
-
- return
- }
-
- // parse and compile the pipeline configuration file
- p, err := compiler.FromContext(c).
- WithBuild(input).
- WithFiles(files).
- WithMetadata(m).
- WithRepo(r).
- WithUser(u).
- Compile(config)
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to compile pipeline configuration for %s/%d: %w", r.GetFullName(), input.GetNumber(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // skip the build if only the init or clone steps are found
- skip := skipEmptyBuild(p)
- if skip != "" {
- // set build to successful status
- input.SetStatus(constants.StatusSuccess)
-
- // send API call to set the status on the commit
- err = scm.FromContext(c).Status(u, input, r.GetOrg(), r.GetName())
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- logger.Errorf("unable to set commit status for %s/%d: %v", r.GetFullName(), input.GetNumber(), err)
- }
-
- c.JSON(http.StatusOK, skip)
- return
- }
-
- // create the objects from the pipeline in the database
- err = planBuild(database.FromContext(c), p, input, r)
- if err != nil {
- util.HandleError(c, http.StatusInternalServerError, err)
-
- return
- }
-
- // send API call to capture the created build
- input, _ = database.FromContext(c).GetBuild(input.GetNumber(), r)
-
- c.JSON(http.StatusCreated, input)
-
- // send API call to set the status on the commit
- err = scm.FromContext(c).Status(u, input, r.GetOrg(), r.GetName())
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- logger.Errorf("unable to set commit status for build %s/%d: %v", r.GetFullName(), input.GetNumber(), err)
- }
-
- // publish the build to the queue
- go publishToQueue(
- queue.FromGinContext(c),
- database.FromContext(c),
- p,
- input,
- r,
- u,
- )
-}
-
-// skipEmptyBuild checks if the build should be skipped due to it
-// not containing any steps besides init or clone.
-//
-// nolint: goconst // ignore init and clone constants
-func skipEmptyBuild(p *pipeline.Build) string {
- if len(p.Stages) == 1 {
- if p.Stages[0].Name == "init" {
- return "skipping build since only init stage found"
- }
- }
-
- // nolint: gomnd // ignore magic number
- if len(p.Stages) == 2 {
- if p.Stages[0].Name == "init" && p.Stages[1].Name == "clone" {
- return "skipping build since only init and clone stages found"
- }
- }
-
- if len(p.Steps) == 1 {
- if p.Steps[0].Name == "init" {
- return "skipping build since only init step found"
- }
- }
-
- // nolint: gomnd // ignore magic number
- if len(p.Steps) == 2 {
- if p.Steps[0].Name == "init" && p.Steps[1].Name == "clone" {
- return "skipping build since only init and clone steps found"
- }
- }
-
- return ""
-}
-
-// swagger:operation GET /api/v1/repos/{org}/{repo}/builds builds GetBuilds
-//
-// Get builds from the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: query
-// name: event
-// description: Filter by build event
-// type: string
-// enum:
-// - push
-// - pull_request
-// - tag
-// - deployment
-// - comment
-// - in: query
-// name: commit
-// description: Filter builds based on the commit hash
-// type: string
-// - in: query
-// name: branch
-// description: Filter builds by branch
-// type: string
-// - in: query
-// name: status
-// description: Filter by build status
-// type: string
-// enum:
-// - canceled
-// - error
-// - failure
-// - killed
-// - pending
-// - running
-// - success
-// - in: query
-// name: page
-// description: The page of results to retrieve
-// type: integer
-// default: 1
-// - in: query
-// name: per_page
-// description: How many results per page to return
-// type: integer
-// maximum: 100
-// default: 10
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the builds
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/Build"
-// headers:
-// X-Total-Count:
-// description: Total number of results
-// type: integer
-// Link:
-// description: see https://tools.ietf.org/html/rfc5988
-// type: string
-// '400':
-// description: Unable to retrieve the list of builds
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to retrieve the list of builds
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetBuilds represents the API handler to capture a
-// list of builds for a repo from the configured backend.
-//
-// nolint: funlen // ignore function length due to comments
-func GetBuilds(c *gin.Context) {
- // variables that will hold the build list, build list filters and total count
- var (
- filters = map[string]interface{}{}
- b []*library.Build
- t int64
- )
-
- // capture middleware values
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("reading builds for repo %s", r.GetFullName())
-
- // capture the branch name parameter
- branch := c.Query("branch")
- // capture the event type parameter
- event := c.Query("event")
- // capture the status type parameter
- status := c.Query("status")
- // capture the commit hash parameter
- commit := c.Query("commit")
-
- // check if branch filter was provided
- if len(branch) > 0 {
- // add branch to filters map
- filters["branch"] = branch
- }
- // check if event filter was provided
- if len(event) > 0 {
- // verify the event provided is a valid event type
- if event != constants.EventComment && event != constants.EventDeploy &&
- event != constants.EventPush && event != constants.EventPull &&
- event != constants.EventTag {
- retErr := fmt.Errorf("unable to process event %s: invalid event type provided", event)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // add event to filters map
- filters["event"] = event
- }
- // check if status filter was provided
- if len(status) > 0 {
- // verify the status provided is a valid status type
- if status != constants.StatusCanceled && status != constants.StatusError &&
- status != constants.StatusFailure && status != constants.StatusKilled &&
- status != constants.StatusPending && status != constants.StatusRunning &&
- status != constants.StatusSuccess {
- retErr := fmt.Errorf("unable to process status %s: invalid status type provided", status)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // add status to filters map
- filters["status"] = status
- }
-
- // check if commit hash filter was provided
- if len(commit) > 0 {
- // add commit to filters map
- filters["commit"] = commit
- }
-
- // capture page query parameter if present
- page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to convert page query parameter for repo %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // capture per_page query parameter if present
- perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to convert per_page query parameter for repo %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // ensure per_page isn't above or below allowed values
- //
- // nolint: gomnd // ignore magic number
- perPage = util.MaxInt(1, util.MinInt(100, perPage))
-
- b, t, err = database.FromContext(c).GetRepoBuildList(r, filters, page, perPage)
- if err != nil {
- retErr := fmt.Errorf("unable to get builds for repo %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // create pagination object
- pagination := Pagination{
- Page: page,
- PerPage: perPage,
- Total: t,
- }
- // set pagination headers
- pagination.SetHeaderLink(c)
-
- c.JSON(http.StatusOK, b)
-}
-
-// swagger:operation GET /api/v1/repos/{org} builds GetOrgBuilds
-//
-// Get a list of builds by org in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: query
-// name: page
-// description: The page of results to retrieve
-// type: integer
-// default: 1
-// - in: query
-// name: per_page
-// description: How many results per page to return
-// type: integer
-// maximum: 100
-// default: 10
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved build list
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/Build"
-// headers:
-// X-Total-Count:
-// description: Total number of results
-// type: integer
-// Link:
-// description: see https://tools.ietf.org/html/rfc5988
-// type: string
-// '400':
-// description: Unable to retrieve the list of builds
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to retrieve the list of builds
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetOrgBuilds represents the API handler to capture a
-// list of builds associated with an org from the configured backend.
-//
-// nolint: funlen // ignore function length due to comments
-func GetOrgBuilds(c *gin.Context) {
- // variables that will hold the build list, build list filters and total count
- var (
- filters = map[string]interface{}{}
- b []*library.Build
- t int64
- )
-
- // capture middleware values
- o := org.Retrieve(c)
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "user": u.GetName(),
- }).Infof("reading builds for org %s", o)
-
- // capture the branch name parameter
- branch := c.Query("branch")
- // capture the event type parameter
- event := c.Query("event")
- // capture the status type parameter
- status := c.Query("status")
-
- // check if branch filter was provided
- if len(branch) > 0 {
- // add branch to filters map
- filters["branch"] = branch
- }
- // check if event filter was provided
- if len(event) > 0 {
- // verify the event provided is a valid event type
- if event != constants.EventComment && event != constants.EventDeploy &&
- event != constants.EventPush && event != constants.EventPull &&
- event != constants.EventTag {
- retErr := fmt.Errorf("unable to process event %s: invalid event type provided", event)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // add event to filters map
- filters["event"] = event
- }
- // check if status filter was provided
- if len(status) > 0 {
- // verify the status provided is a valid status type
- if status != constants.StatusCanceled && status != constants.StatusError &&
- status != constants.StatusFailure && status != constants.StatusKilled &&
- status != constants.StatusPending && status != constants.StatusRunning &&
- status != constants.StatusSuccess {
- retErr := fmt.Errorf("unable to process status %s: invalid status type provided", status)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // add status to filters map
- filters["status"] = status
- }
-
- // capture page query parameter if present
- page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
- if err != nil {
- retErr := fmt.Errorf("unable to convert page query parameter for org %s: %w", o, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // capture per_page query parameter if present
- perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
- if err != nil {
- retErr := fmt.Errorf("unable to convert per_page query parameter for Org %s: %w", o, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // ensure per_page isn't above or below allowed values
- //
- // nolint: gomnd // ignore magic number
- perPage = util.MaxInt(1, util.MinInt(100, perPage))
-
- // See if the user is an org admin to bypass individual permission checks
- perm, err := scm.FromContext(c).OrgAccess(u, o)
- if err != nil {
- logrus.Errorf("unable to get user %s access level for org %s", u.GetName(), o)
- }
- // Only show public repos to non-admins
- //
- // nolint: goconst // ignore admin constant
- if perm != "admin" {
- filters["visibility"] = constants.VisibilityPublic
- }
-
- // send API call to capture the list of builds for the org (and event type if passed in)
- b, t, err = database.FromContext(c).GetOrgBuildList(o, filters, page, perPage)
-
- if err != nil {
- retErr := fmt.Errorf("unable to get builds for org %s: %w", o, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // create pagination object
- pagination := Pagination{
- Page: page,
- PerPage: perPage,
- Total: t,
- }
- // set pagination headers
- pagination.SetHeaderLink(c)
-
- c.JSON(http.StatusOK, b)
-}
-
-// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build} builds GetBuild
-//
-// Get a build in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number to retrieve
-// required: true
-// type: integer
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the build
-// type: json
-// schema:
-// "$ref": "#/definitions/Build"
-
-// GetBuild represents the API handler to capture
-// a build for a repo from the configured backend.
-func GetBuild(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("reading build %s/%d", r.GetFullName(), b.GetNumber())
-
- c.JSON(http.StatusOK, b)
-}
-
-// swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build} builds RestartBuild
-//
-// Restart a build in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number to restart
-// required: true
-// type: integer
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Request processed but build was skipped
-// schema:
-// type: string
-// '201':
-// description: Successfully restarted the build
-// schema:
-// "$ref": "#/definitions/Build"
-// '400':
-// description: Unable to restart the build
-// schema:
-// "$ref": "#/definitions/Error"
-// '404':
-// description: Unable to restart the build
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to restart the build
-// schema:
-// "$ref": "#/definitions/Error"
-
-// RestartBuild represents the API handler to
-// restart an existing build in the configured backend.
-//
-// nolint: funlen // ignore function length due to comments
-func RestartBuild(c *gin.Context) {
- // capture middleware values
- m := c.MustGet("metadata").(*types.Metadata)
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logger := logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- })
-
- logger.Infof("restarting build %s", entry)
-
- // send API call to capture the repo owner
- u, err := database.FromContext(c).GetUser(r.GetUserID())
- if err != nil {
- retErr := fmt.Errorf("unable to get owner for %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // create SQL filters for querying pending and running builds for repo
- filters := map[string]interface{}{
- "status": []string{constants.StatusPending, constants.StatusRunning},
- }
-
- // send API call to capture the number of pending or running builds for the repo
- builds, err := database.FromContext(c).GetRepoBuildCount(r, filters)
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to restart build: unable to get count of builds for repo %s", r.GetFullName())
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // check if the number of pending and running builds exceeds the limit for the repo
- if builds >= r.GetBuildLimit() {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to restart build: repo %s has exceeded the concurrent build limit of %d", r.GetFullName(), r.GetBuildLimit())
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // send API call to capture the last build for the repo
- lastBuild, err := database.FromContext(c).GetLastBuild(r)
- if err != nil {
- retErr := fmt.Errorf("unable to get last build for %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // update the build numbers based off repo counter
- inc := r.GetCounter() + 1
-
- r.SetCounter(inc)
- b.SetNumber(inc)
-
- // update fields in build object
- b.SetID(0)
- b.SetParent(lastBuild.GetNumber())
- b.SetCreated(time.Now().UTC().Unix())
- b.SetEnqueued(0)
- b.SetStarted(0)
- b.SetFinished(0)
- b.SetStatus(constants.StatusPending)
- b.SetHost("")
- b.SetRuntime("")
- b.SetDistribution("")
-
- // populate the build link if a web address is provided
- if len(m.Vela.WebAddress) > 0 {
- b.SetLink(
- fmt.Sprintf("%s/%s/%d", m.Vela.WebAddress, r.GetFullName(), b.GetNumber()),
- )
- }
-
- // variable to store changeset files
- var files []string
- // check if the build event is not pull_request
- if !strings.EqualFold(b.GetEvent(), constants.EventPull) {
- // send API call to capture list of files changed for the commit
- files, err = scm.FromContext(c).Changeset(u, r, b.GetCommit())
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to process webhook: failed to get changeset for %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
- }
-
- // handle getting changeset from a pull_request
- if strings.EqualFold(b.GetEvent(), constants.EventPull) {
- // capture number from build
- number, err := getPRNumberFromBuild(b)
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to restart build: failed to get pull_request number for %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture list of files changed for the pull request
- files, err = scm.FromContext(c).ChangesetPR(u, r, number)
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to restart build: failed to get changeset for %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
- }
-
- // send API call to capture the pipeline configuration file
- config, err := scm.FromContext(c).ConfigBackoff(u, r, b.GetCommit())
- if err != nil {
- retErr := fmt.Errorf("unable to get pipeline configuration for %s: %w", entry, err)
-
- util.HandleError(c, http.StatusNotFound, retErr)
-
- return
- }
-
- // parse and compile the pipeline configuration file
- p, err := compiler.FromContext(c).
- WithBuild(b).
- WithFiles(files).
- WithMetadata(m).
- WithRepo(r).
- WithUser(u).
- Compile(config)
- if err != nil {
- retErr := fmt.Errorf("unable to compile pipeline configuration for %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // skip the build if only the init or clone steps are found
- skip := skipEmptyBuild(p)
- if skip != "" {
- // set build to successful status
- b.SetStatus(constants.StatusSkipped)
-
- // send API call to set the status on the commit
- err = scm.FromContext(c).Status(u, b, r.GetOrg(), r.GetName())
- if err != nil {
- logger.Errorf("unable to set commit status for %s: %v", entry, err)
- }
-
- c.JSON(http.StatusOK, skip)
- return
- }
-
- // create the objects from the pipeline in the database
- err = planBuild(database.FromContext(c), p, b, r)
- if err != nil {
- util.HandleError(c, http.StatusInternalServerError, err)
-
- return
- }
-
- // send API call to update repo for ensuring counter is incremented
- err = database.FromContext(c).UpdateRepo(r)
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to restart build: failed to update repo %s: %v", r.GetFullName(), err)
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // send API call to capture the restarted build
- b, _ = database.FromContext(c).GetBuild(b.GetNumber(), r)
-
- c.JSON(http.StatusCreated, b)
-
- // send API call to set the status on the commit
- err = scm.FromContext(c).Status(u, b, r.GetOrg(), r.GetName())
- if err != nil {
- logger.Errorf("unable to set commit status for build %s: %v", entry, err)
- }
-
- // publish the build to the queue
- go publishToQueue(
- queue.FromGinContext(c),
- database.FromContext(c),
- p,
- b,
- r,
- u,
- )
-}
-
-// swagger:operation PUT /api/v1/repos/{org}/{repo}/builds/{build} builds UpdateBuild
-//
-// Updates a build in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number to update
-// required: true
-// type: integer
-// - in: body
-// name: body
-// description: Payload containing the build to update
-// required: true
-// schema:
-// "$ref": "#/definitions/Build"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully updated the build
-// schema:
-// "$ref": "#/definitions/Build"
-// '404':
-// description: Unable to update the build
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to update the build
-// schema:
-// "$ref": "#/definitions/Error"
-
-// UpdateBuild represents the API handler to update
-// a build for a repo in the configured backend.
-// nolint: funlen // ignore long function line length
-func UpdateBuild(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("updating build %s", entry)
-
- // capture body from API request
- input := new(library.Build)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for build %s: %w", entry, err)
-
- util.HandleError(c, http.StatusNotFound, retErr)
-
- return
- }
-
- // update build fields if provided
- if len(input.GetStatus()) > 0 {
- // update status if set
- b.SetStatus(input.GetStatus())
- }
-
- if len(input.GetError()) > 0 {
- // update error if set
- b.SetError(input.GetError())
- }
-
- if input.GetEnqueued() > 0 {
- // update enqueued if set
- b.SetEnqueued(input.GetEnqueued())
- }
-
- if input.GetStarted() > 0 {
- // update started if set
- b.SetStarted(input.GetStarted())
- }
-
- if input.GetFinished() > 0 {
- // update finished if set
- b.SetFinished(input.GetFinished())
- }
-
- if len(input.GetTitle()) > 0 {
- // update title if set
- b.SetTitle(input.GetTitle())
- }
-
- if len(input.GetMessage()) > 0 {
- // update message if set
- b.SetMessage(input.GetMessage())
- }
-
- if len(input.GetHost()) > 0 {
- // update host if set
- b.SetHost(input.GetHost())
- }
-
- if len(input.GetRuntime()) > 0 {
- // update runtime if set
- b.SetRuntime(input.GetRuntime())
- }
-
- if len(input.GetDistribution()) > 0 {
- // update distribution if set
- b.SetDistribution(input.GetDistribution())
- }
-
- // send API call to update the build
- err = database.FromContext(c).UpdateBuild(b)
- if err != nil {
- retErr := fmt.Errorf("unable to update build %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the updated build
- b, _ = database.FromContext(c).GetBuild(b.GetNumber(), r)
-
- c.JSON(http.StatusOK, b)
-
- // check if the build is in a "final" state
- if b.GetStatus() == constants.StatusSuccess ||
- b.GetStatus() == constants.StatusFailure ||
- b.GetStatus() == constants.StatusCanceled ||
- b.GetStatus() == constants.StatusKilled ||
- b.GetStatus() == constants.StatusError {
- // send API call to capture the repo owner
- u, err := database.FromContext(c).GetUser(r.GetUserID())
- if err != nil {
- logrus.Errorf("unable to get owner for build %s: %v", entry, err)
- }
-
- // send API call to set the status on the commit
- err = scm.FromContext(c).Status(u, b, r.GetOrg(), r.GetName())
- if err != nil {
- logrus.Errorf("unable to set commit status for build %s: %v", entry, err)
- }
- }
-}
-
-// swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build} builds DeleteBuild
-//
-// Delete a build in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number to delete
-// required: true
-// type: integer
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully deleted the build
-// schema:
-// type: string
-// '400':
-// description: Unable to delete the build
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to delete the build
-// schema:
-// "$ref": "#/definitions/Error"
-
-// DeleteBuild represents the API handler to remove
-// a build for a repo from the configured backend.
-func DeleteBuild(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("deleting build %s", entry)
-
- // send API call to remove the build
- err := database.FromContext(c).DeleteBuild(b.GetID())
- if err != nil {
- retErr := fmt.Errorf("unable to delete build %s: %v", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, fmt.Sprintf("build %s deleted", entry))
-}
-
-// getPRNumberFromBuild is a helper function to
-// extract the pull request number from a Build.
-func getPRNumberFromBuild(b *library.Build) (int, error) {
- // parse out pull request number from base ref
- //
- // pattern: refs/pull/1/head
- var parts []string
- if strings.HasPrefix(b.GetRef(), "refs/pull/") {
- parts = strings.Split(b.GetRef(), "/")
- }
-
- // just being safe to avoid out of range index errors
- //
- // nolint:gomnd // magic number of 3 used once
- if len(parts) < 3 {
- return 0, fmt.Errorf("invalid ref: %s", b.GetRef())
- }
-
- // return the results of converting number to string
- return strconv.Atoi(parts[2])
-}
-
-// planBuild is a helper function to plan the build for
-// execution. This creates all resources, like steps
-// and services, for the build in the configured backend.
-//
-// nolint: lll // ignore long line length due to variable names
-func planBuild(database database.Service, p *pipeline.Build, b *library.Build, r *library.Repo) error {
- // update fields in build object
- b.SetCreated(time.Now().UTC().Unix())
-
- // send API call to create the build
- err := database.CreateBuild(b)
- if err != nil {
- // clean up the objects from the pipeline in the database
- cleanBuild(database, b, nil, nil)
-
- return fmt.Errorf("unable to create new build for %s: %v", r.GetFullName(), err)
- }
-
- // send API call to capture the created build
- b, _ = database.GetBuild(b.GetNumber(), r)
-
- // plan all services for the build
- services, err := planServices(database, p, b)
- if err != nil {
- // clean up the objects from the pipeline in the database
- cleanBuild(database, b, services, nil)
-
- return err
- }
-
- // plan all steps for the build
- steps, err := planSteps(database, p, b)
- if err != nil {
- // clean up the objects from the pipeline in the database
- cleanBuild(database, b, services, steps)
-
- return err
- }
-
- return nil
-}
-
-// cleanBuild is a helper function to kill the build
-// without execution. This will kill all resources,
-// like steps and services, for the build in the
-// configured backend.
-//
-// nolint: lll // ignore long line length due to variable names
-func cleanBuild(database database.Service, b *library.Build, services []*library.Service, steps []*library.Step) {
- // update fields in build object
- b.SetError("unable to publish build to queue")
- b.SetStatus(constants.StatusError)
- b.SetFinished(time.Now().UTC().Unix())
-
- // send API call to update the build
- err := database.UpdateBuild(b)
- if err != nil {
- logrus.Errorf("unable to kill build %d: %v", b.GetNumber(), err)
- }
-
- for _, s := range services {
- // update fields in service object
- s.SetStatus(constants.StatusKilled)
- s.SetFinished(time.Now().UTC().Unix())
-
- // send API call to update the service
- err := database.UpdateService(s)
- if err != nil {
- logrus.Errorf("unable to kill service %s for build %d: %v", s.GetName(), b.GetNumber(), err)
- }
- }
-
- for _, s := range steps {
- // update fields in step object
- s.SetStatus(constants.StatusKilled)
- s.SetFinished(time.Now().UTC().Unix())
-
- // send API call to update the step
- err := database.UpdateStep(s)
- if err != nil {
- logrus.Errorf("unable to kill step %s for build %d: %v", s.GetName(), b.GetNumber(), err)
- }
- }
-}
-
-// swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build}/cancel builds CancelBuild
-//
-// Cancel a running build
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number to cancel
-// required: true
-// type: integer
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully canceled the build
-// schema:
-// type: string
-// '400':
-// description: Unable to cancel build
-// schema:
-// "$ref": "#/definitions/Error"
-// '404':
-// description: Unable to cancel build
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to cancel build
-// schema:
-// "$ref": "#/definitions/Error"
-
-// CancelBuild represents the API handler to
-// cancel a running build.
-//
-// nolint: funlen // ignore function length due to comments
-func CancelBuild(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- e := executors.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("canceling build %s", entry)
-
- // TODO: add support for removing builds from the queue
- //
- // check to see if build is not running
- if !strings.EqualFold(b.GetStatus(), constants.StatusRunning) {
- retErr := fmt.Errorf("found build %s but its status was %s", entry, b.GetStatus())
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // retrieve the worker info
- w, err := database.FromContext(c).GetWorker(b.GetHost())
- if err != nil {
- retErr := fmt.Errorf("unable to get worker for build %s: %w", entry, err)
- util.HandleError(c, http.StatusNotFound, retErr)
- return
- }
-
- for _, executor := range e {
- // check each executor on the worker running the build
- // to see if it's running the build we want to cancel
- //
- // nolint:whitespace // ignore leading newline to improve readability
- if strings.EqualFold(executor.Repo.GetFullName(), r.GetFullName()) &&
- *executor.GetBuild().Number == b.GetNumber() {
-
- // prepare the request to the worker
- client := http.DefaultClient
- client.Timeout = 30 * time.Second
-
- // set the API endpoint path we send the request to
- u := fmt.Sprintf("%s/api/v1/executors/%d/build/cancel", w.GetAddress(), executor.GetID())
- req, err := http.NewRequest("DELETE", u, nil)
- if err != nil {
- retErr := fmt.Errorf("unable to form a request to %s: %w", u, err)
- util.HandleError(c, http.StatusBadRequest, retErr)
- return
- }
-
- // add the token to authenticate to the worker
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.MustGet("secret").(string)))
-
- // perform the request to the worker
- resp, err := client.Do(req)
- if err != nil {
- retErr := fmt.Errorf("unable to connect to %s: %w", u, err)
- util.HandleError(c, http.StatusBadRequest, retErr)
- return
- }
- defer resp.Body.Close()
-
- // Read Response Body
- respBody, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- retErr := fmt.Errorf("unable to read response from %s: %w", u, err)
- util.HandleError(c, http.StatusBadRequest, retErr)
- return
- }
-
- err = json.Unmarshal(respBody, b)
- if err != nil {
- retErr := fmt.Errorf("unable to parse response from %s: %w", u, err)
- util.HandleError(c, http.StatusBadRequest, retErr)
- return
- }
-
- c.JSON(resp.StatusCode, b)
- return
- }
- }
-
- // build has been abandoned
- // update the status in the build table
- b.SetStatus(constants.StatusCanceled)
- err = database.FromContext(c).UpdateBuild(b)
- if err != nil {
- retErr := fmt.Errorf("unable to update status for build %s: %w", entry, err)
- util.HandleError(c, http.StatusInternalServerError, retErr)
- return
- }
-
- // retrieve the steps for the build from the step table
- steps := []*library.Step{}
- page := 1
- perPage := 100
- for page > 0 {
- // retrieve build steps (per page) from the database
- stepsPart, err := database.FromContext(c).GetBuildStepList(b, page, perPage)
- if err != nil {
- retErr := fmt.Errorf("unable to retrieve steps for build %s: %w", entry, err)
- util.HandleError(c, http.StatusNotFound, retErr)
- return
- }
-
- // add page of steps to list steps
- steps = append(steps, stepsPart...)
-
- // assume no more pages exist if under 100 results are returned
- //
- // nolint: gomnd // ignore magic number
- if len(stepsPart) < 100 {
- page = 0
- } else {
- page++
- }
- }
-
- // iterate over each step for the build
- // setting anything running or pending to canceled
- for _, step := range steps {
- if step.GetStatus() == constants.StatusRunning ||
- step.GetStatus() == constants.StatusPending {
- step.SetStatus(constants.StatusCanceled)
- err = database.FromContext(c).UpdateStep(step)
- if err != nil {
- retErr := fmt.Errorf("unable to update step %s for build %s: %w", step.GetName(), entry, err)
- util.HandleError(c, http.StatusNotFound, retErr)
- return
- }
- }
- }
-
- // retrieve the services for the build from the service table
- services := []*library.Service{}
- page = 1
- for page > 0 {
- // retrieve build services (per page) from the database
- servicesPart, err := database.FromContext(c).GetBuildServiceList(b, page, perPage)
- if err != nil {
- retErr := fmt.Errorf("unable to retrieve services for build %s: %w", entry, err)
- util.HandleError(c, http.StatusNotFound, retErr)
- return
- }
-
- // add page of services to the list of services
- services = append(services, servicesPart...)
-
- // assume no more pages exist if under 100 results are returned
- //
- // nolint: gomnd // ignore magic number
- if len(servicesPart) < 100 {
- page = 0
- } else {
- page++
- }
- }
-
- // iterate over each service for the build
- // setting anything running or pending to canceled
- for _, service := range services {
- if service.GetStatus() == constants.StatusRunning ||
- service.GetStatus() == constants.StatusPending {
- service.SetStatus(constants.StatusCanceled)
- err = database.FromContext(c).UpdateService(service)
- if err != nil {
- retErr := fmt.Errorf("unable to update service %s for build %s: %w",
- service.GetName(),
- entry,
- err,
- )
- util.HandleError(c, http.StatusNotFound, retErr)
- return
- }
- }
- }
-
- c.JSON(http.StatusOK, b)
-}
diff --git a/api/build/cancel.go b/api/build/cancel.go
new file mode 100644
index 000000000..55dc7cecf
--- /dev/null
+++ b/api/build/cancel.go
@@ -0,0 +1,290 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/internal/token"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/executors"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build}/cancel builds CancelBuild
+//
+// Cancel a running build
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number to cancel
+// required: true
+// type: integer
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully canceled the build
+// schema:
+// type: string
+// '400':
+// description: Unable to cancel build
+// schema:
+// "$ref": "#/definitions/Error"
+// '404':
+// description: Unable to cancel build
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to cancel build
+// schema:
+// "$ref": "#/definitions/Error"
+
+// CancelBuild represents the API handler to cancel a running build.
+//
+//nolint:funlen // ignore statement count
+func CancelBuild(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ e := executors.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("canceling build %s", entry)
+
+ switch b.GetStatus() {
+ case constants.StatusRunning:
+ // retrieve the worker info
+ w, err := database.FromContext(c).GetWorkerForHostname(b.GetHost())
+ if err != nil {
+ retErr := fmt.Errorf("unable to get worker for build %s: %w", entry, err)
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ for _, executor := range e {
+ // check each executor on the worker running the build to see if it's running the build we want to cancel
+ if strings.EqualFold(executor.Repo.GetFullName(), r.GetFullName()) && *executor.GetBuild().Number == b.GetNumber() {
+ // prepare the request to the worker
+ client := http.DefaultClient
+ client.Timeout = 30 * time.Second
+
+ // set the API endpoint path we send the request to
+ u := fmt.Sprintf("%s/api/v1/executors/%d/build/cancel", w.GetAddress(), executor.GetID())
+
+ req, err := http.NewRequestWithContext(context.Background(), "DELETE", u, nil)
+ if err != nil {
+ retErr := fmt.Errorf("unable to form a request to %s: %w", u, err)
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ tm := c.MustGet("token-manager").(*token.Manager)
+
+ // set mint token options
+ mto := &token.MintTokenOpts{
+ Hostname: "vela-server",
+ TokenType: constants.WorkerAuthTokenType,
+ TokenDuration: time.Minute * 1,
+ }
+
+ // mint token
+ tkn, err := tm.MintToken(mto)
+ if err != nil {
+ retErr := fmt.Errorf("unable to generate auth token: %w", err)
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // add the token to authenticate to the worker
+ req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tkn))
+
+ // perform the request to the worker
+ resp, err := client.Do(req)
+ if err != nil {
+ retErr := fmt.Errorf("unable to connect to %s: %w", u, err)
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+ defer resp.Body.Close()
+
+ // Read Response Body
+ respBody, err := io.ReadAll(resp.Body)
+ if err != nil {
+ retErr := fmt.Errorf("unable to read response from %s: %w", u, err)
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ err = json.Unmarshal(respBody, b)
+ if err != nil {
+ retErr := fmt.Errorf("unable to parse response from %s: %w", u, err)
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ c.JSON(resp.StatusCode, b)
+
+ return
+ }
+ }
+ case constants.StatusPending:
+ break
+
+ default:
+ retErr := fmt.Errorf("found build %s but its status was %s", entry, b.GetStatus())
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // build has been abandoned
+ // update the status in the build table
+ b.SetStatus(constants.StatusCanceled)
+
+ b, err := database.FromContext(c).UpdateBuild(ctx, b)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update status for build %s: %w", entry, err)
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // retrieve the steps for the build from the step table
+ steps := []*library.Step{}
+ page := 1
+ perPage := 100
+
+ for page > 0 {
+ // retrieve build steps (per page) from the database
+ 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)
+
+ return
+ }
+
+ // add page of steps to list steps
+ steps = append(steps, stepsPart...)
+
+ // assume no more pages exist if under 100 results are returned
+ if len(stepsPart) < 100 {
+ page = 0
+ } else {
+ page++
+ }
+ }
+
+ // iterate over each step for the build
+ // setting anything running or pending to canceled
+ for _, step := range steps {
+ if step.GetStatus() == constants.StatusRunning || step.GetStatus() == constants.StatusPending {
+ step.SetStatus(constants.StatusCanceled)
+
+ _, err = database.FromContext(c).UpdateStep(step)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update step %s for build %s: %w", step.GetName(), entry, err)
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+ }
+ }
+
+ // retrieve the services for the build from the service table
+ services := []*library.Service{}
+ page = 1
+
+ for page > 0 {
+ // retrieve build services (per page) from the database
+ servicesPart, _, err := database.FromContext(c).ListServicesForBuild(b, map[string]interface{}{}, page, perPage)
+ if err != nil {
+ retErr := fmt.Errorf("unable to retrieve services for build %s: %w", entry, err)
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ // add page of services to the list of services
+ services = append(services, servicesPart...)
+
+ // assume no more pages exist if under 100 results are returned
+ if len(servicesPart) < 100 {
+ page = 0
+ } else {
+ page++
+ }
+ }
+
+ // iterate over each service for the build
+ // setting anything running or pending to canceled
+ for _, service := range services {
+ if service.GetStatus() == constants.StatusRunning || service.GetStatus() == constants.StatusPending {
+ service.SetStatus(constants.StatusCanceled)
+
+ _, err = database.FromContext(c).UpdateService(service)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update service %s for build %s: %w",
+ service.GetName(),
+ entry,
+ err,
+ )
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+ }
+ }
+
+ c.JSON(http.StatusOK, b)
+}
diff --git a/api/build/clean.go b/api/build/clean.go
new file mode 100644
index 000000000..0dd9ea81e
--- /dev/null
+++ b/api/build/clean.go
@@ -0,0 +1,57 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// cleanBuild is a helper function to kill the build
+// without execution. This will kill all resources,
+// like steps and services, for the build in the
+// configured backend.
+func CleanBuild(ctx context.Context, database database.Interface, b *library.Build, services []*library.Service, steps []*library.Step, e error) {
+ // update fields in build object
+ b.SetError(fmt.Sprintf("unable to publish to queue: %s", e.Error()))
+ b.SetStatus(constants.StatusError)
+ b.SetFinished(time.Now().UTC().Unix())
+
+ // send API call to update the build
+ b, err := database.UpdateBuild(ctx, b)
+ if err != nil {
+ logrus.Errorf("unable to kill build %d: %v", b.GetNumber(), err)
+ }
+
+ for _, s := range services {
+ // update fields in service object
+ s.SetStatus(constants.StatusKilled)
+ s.SetFinished(time.Now().UTC().Unix())
+
+ // send API call to update the service
+ _, err := database.UpdateService(s)
+ if err != nil {
+ logrus.Errorf("unable to kill service %s for build %d: %v", s.GetName(), b.GetNumber(), err)
+ }
+ }
+
+ for _, s := range steps {
+ // update fields in step object
+ s.SetStatus(constants.StatusKilled)
+ s.SetFinished(time.Now().UTC().Unix())
+
+ // send API call to update the step
+ _, err := database.UpdateStep(s)
+ if err != nil {
+ logrus.Errorf("unable to kill step %s for build %d: %v", s.GetName(), b.GetNumber(), err)
+ }
+ }
+}
diff --git a/api/build/create.go b/api/build/create.go
new file mode 100644
index 000000000..ee0d0f64b
--- /dev/null
+++ b/api/build/create.go
@@ -0,0 +1,383 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/compiler"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/queue"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/go-vela/types/pipeline"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/repos/{org}/{repo}/builds builds CreateBuild
+//
+// Create a build in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: body
+// name: body
+// description: Payload containing the build to create
+// required: true
+// schema:
+// "$ref": "#/definitions/Build"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Request processed but build was skipped
+// schema:
+// type: string
+// '201':
+// description: Successfully created the build
+// type: json
+// schema:
+// "$ref": "#/definitions/Build"
+// '400':
+// description: Unable to create the build
+// schema:
+// "$ref": "#/definitions/Error"
+// '404':
+// description: Unable to create the build
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to create the build
+// schema:
+// "$ref": "#/definitions/Error"
+
+// CreateBuild represents the API handler to create a build in the configured backend.
+//
+//nolint:funlen,gocyclo // ignore function length and cyclomatic complexity
+func CreateBuild(c *gin.Context) {
+ // capture middleware values
+ m := c.MustGet("metadata").(*types.Metadata)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logger := logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ })
+
+ logger.Infof("creating new build for repo %s", r.GetFullName())
+
+ // capture body from API request
+ input := new(library.Build)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for new build for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // verify the build has a valid event and the repo allows that event type
+ if (input.GetEvent() == constants.EventPush && !r.GetAllowPush()) ||
+ (input.GetEvent() == constants.EventPull && !r.GetAllowPull()) ||
+ (input.GetEvent() == constants.EventTag && !r.GetAllowTag()) ||
+ (input.GetEvent() == constants.EventDeploy && !r.GetAllowDeploy()) {
+ retErr := fmt.Errorf("unable to create new build: %s does not have %s events enabled", r.GetFullName(), input.GetEvent())
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // send API call to capture the repo owner
+ u, err = database.FromContext(c).GetUser(r.GetUserID())
+ if err != nil {
+ retErr := fmt.Errorf("unable to get owner for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // create SQL filters for querying pending and running builds for repo
+ filters := map[string]interface{}{
+ "status": []string{constants.StatusPending, constants.StatusRunning},
+ }
+
+ // send API call to capture the number of pending or running builds for the repo
+ builds, err := database.FromContext(c).CountBuildsForRepo(ctx, r, filters)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create new build: unable to get count of builds for repo %s", r.GetFullName())
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // check if the number of pending and running builds exceeds the limit for the repo
+ if builds >= r.GetBuildLimit() {
+ retErr := fmt.Errorf("unable to create new build: repo %s has exceeded the concurrent build limit of %d", r.GetFullName(), r.GetBuildLimit())
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update fields in build object
+ input.SetRepoID(r.GetID())
+ input.SetStatus(constants.StatusPending)
+ input.SetCreated(time.Now().UTC().Unix())
+
+ // set the parent equal to the current repo counter
+ input.SetParent(r.GetCounter())
+ // check if the parent is set to 0
+ if input.GetParent() == 0 {
+ // parent should be "1" if it's the first build ran
+ input.SetParent(1)
+ }
+
+ // update the build numbers based off repo counter
+ inc := r.GetCounter() + 1
+ r.SetCounter(inc)
+ input.SetNumber(inc)
+
+ // populate the build link if a web address is provided
+ if len(m.Vela.WebAddress) > 0 {
+ input.SetLink(
+ fmt.Sprintf("%s/%s/%d", m.Vela.WebAddress, r.GetFullName(), input.GetNumber()),
+ )
+ }
+
+ // variable to store changeset files
+ var files []string
+ // check if the build event is not issue_comment or pull_request
+ if !strings.EqualFold(input.GetEvent(), constants.EventComment) &&
+ !strings.EqualFold(input.GetEvent(), constants.EventPull) {
+ // send API call to capture list of files changed for the commit
+ files, err = scm.FromContext(c).Changeset(u, r, input.GetCommit())
+ if err != nil {
+ retErr := fmt.Errorf("unable to create new build: failed to get changeset for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ }
+
+ // check if the build event is a pull_request
+ if strings.EqualFold(input.GetEvent(), constants.EventPull) {
+ // capture number from build
+ number, err := getPRNumberFromBuild(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create new build: failed to get pull_request number for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // send API call to capture list of files changed for the pull request
+ files, err = scm.FromContext(c).ChangesetPR(u, r, number)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create new build: failed to get changeset for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ }
+
+ var (
+ // variable to store the raw pipeline configuration
+ config []byte
+ // variable to store executable pipeline
+ p *pipeline.Build
+ // variable to store pipeline configuration
+ pipeline *library.Pipeline
+ // variable to store the pipeline type for the repository
+ pipelineType = r.GetPipelineType()
+ )
+
+ // send API call to attempt to capture the pipeline
+ pipeline, err = database.FromContext(c).GetPipelineForRepo(ctx, input.GetCommit(), r)
+ if err != nil { // assume the pipeline doesn't exist in the database yet
+ // send API call to capture the pipeline configuration file
+ config, err = scm.FromContext(c).ConfigBackoff(u, r, input.GetCommit())
+ if err != nil {
+ retErr := fmt.Errorf("unable to create new build: failed to get pipeline configuration for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+ } else {
+ config = pipeline.GetData()
+ }
+
+ // ensure we use the expected pipeline type when compiling
+ //
+ // The pipeline type for a repo can change at any time which can break compiling
+ // existing pipelines in the system for that repo. To account for this, we update
+ // the repo pipeline type to match what was defined for the existing pipeline
+ // before compiling. After we're done compiling, we reset the pipeline type.
+ if len(pipeline.GetType()) > 0 {
+ r.SetPipelineType(pipeline.GetType())
+ }
+
+ var compiled *library.Pipeline
+ // parse and compile the pipeline configuration file
+ p, compiled, err = compiler.FromContext(c).
+ Duplicate().
+ WithBuild(input).
+ WithFiles(files).
+ WithMetadata(m).
+ WithRepo(r).
+ WithUser(u).
+ Compile(config)
+ if err != nil {
+ retErr := fmt.Errorf("unable to compile pipeline configuration for %s/%d: %w", r.GetFullName(), input.GetNumber(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ // reset the pipeline type for the repo
+ //
+ // The pipeline type for a repo can change at any time which can break compiling
+ // existing pipelines in the system for that repo. To account for this, we update
+ // the repo pipeline type to match what was defined for the existing pipeline
+ // before compiling. After we're done compiling, we reset the pipeline type.
+ r.SetPipelineType(pipelineType)
+
+ // skip the build if only the init or clone steps are found
+ skip := SkipEmptyBuild(p)
+ if skip != "" {
+ // set build to successful status
+ input.SetStatus(constants.StatusSuccess)
+
+ // send API call to set the status on the commit
+ err = scm.FromContext(c).Status(u, input, r.GetOrg(), r.GetName())
+ if err != nil {
+ logger.Errorf("unable to set commit status for %s/%d: %v", r.GetFullName(), input.GetNumber(), err)
+ }
+
+ c.JSON(http.StatusOK, skip)
+
+ return
+ }
+
+ // check if the pipeline did not already exist in the database
+ //
+ //nolint:dupl // ignore duplicate code
+ if pipeline == nil {
+ pipeline = compiled
+ pipeline.SetRepoID(r.GetID())
+ pipeline.SetCommit(input.GetCommit())
+ pipeline.SetRef(input.GetRef())
+
+ // send API call to create the pipeline
+ pipeline, err = database.FromContext(c).CreatePipeline(ctx, pipeline)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create new build: failed to create pipeline for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+ }
+
+ input.SetPipelineID(pipeline.GetID())
+
+ // create the objects from the pipeline in the database
+ err = PlanBuild(ctx, database.FromContext(c), p, input, r)
+ if err != nil {
+ util.HandleError(c, http.StatusInternalServerError, err)
+
+ return
+ }
+
+ // send API call to update repo for ensuring counter is incremented
+ r, err = database.FromContext(c).UpdateRepo(ctx, r)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create new build: failed to update repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // send API call to capture the created build
+ input, _ = database.FromContext(c).GetBuildForRepo(ctx, r, input.GetNumber())
+
+ c.JSON(http.StatusCreated, input)
+
+ // send API call to set the status on the commit
+ err = scm.FromContext(c).Status(u, input, r.GetOrg(), r.GetName())
+ if err != nil {
+ logger.Errorf("unable to set commit status for build %s/%d: %v", r.GetFullName(), input.GetNumber(), err)
+ }
+
+ // publish the build to the queue
+ go PublishToQueue(
+ ctx,
+ queue.FromGinContext(c),
+ database.FromContext(c),
+ p,
+ input,
+ r,
+ u,
+ )
+}
+
+// getPRNumberFromBuild is a helper function to
+// extract the pull request number from a Build.
+func getPRNumberFromBuild(b *library.Build) (int, error) {
+ // parse out pull request number from base ref
+ //
+ // pattern: refs/pull/1/head
+ var parts []string
+ if strings.HasPrefix(b.GetRef(), "refs/pull/") {
+ parts = strings.Split(b.GetRef(), "/")
+ }
+
+ // just being safe to avoid out of range index errors
+ if len(parts) < 3 {
+ return 0, fmt.Errorf("invalid ref: %s", b.GetRef())
+ }
+
+ // return the results of converting number to string
+ return strconv.Atoi(parts[2])
+}
diff --git a/api/build/delete.go b/api/build/delete.go
new file mode 100644
index 000000000..ffc9a7ac4
--- /dev/null
+++ b/api/build/delete.go
@@ -0,0 +1,93 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build} builds DeleteBuild
+//
+// Delete a build in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number to delete
+// required: true
+// type: integer
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully deleted the build
+// schema:
+// type: string
+// '400':
+// description: Unable to delete the build
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to delete the build
+// schema:
+// "$ref": "#/definitions/Error"
+
+// DeleteBuild represents the API handler to remove
+// a build for a repo from the configured backend.
+func DeleteBuild(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("deleting build %s", entry)
+
+ // send API call to remove the build
+ err := database.FromContext(c).DeleteBuild(ctx, b)
+ if err != nil {
+ retErr := fmt.Errorf("unable to delete build %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("build %s deleted", entry))
+}
diff --git a/api/build/doc.go b/api/build/doc.go
new file mode 100644
index 000000000..94b6571e3
--- /dev/null
+++ b/api/build/doc.go
@@ -0,0 +1,10 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+// Package build provides the build handlers for the Vela API.
+//
+// Usage:
+//
+// import "github.com/go-vela/server/api/build"
+package build
diff --git a/api/build/executable.go b/api/build/executable.go
new file mode 100644
index 000000000..9df63467d
--- /dev/null
+++ b/api/build/executable.go
@@ -0,0 +1,94 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/claims"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/executable builds GetBuildExecutable
+//
+// Get a build executable in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number to retrieve
+// required: true
+// type: integer
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the build executable
+// type: json
+// schema:
+// "$ref": "#/definitions/Build"
+// '400':
+// description: Bad request
+// schema:
+// "$ref": "#/definitions/Error"
+// '401':
+// description: Unauthorized
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Could not retrieve build executable
+// schema:
+// "$ref": "#/definitions/Error"
+
+// GetBuildExecutable represents the API handler to capture
+// a build executable for a repo from the configured backend.
+func GetBuildExecutable(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ cl := claims.Retrieve(c)
+ ctx := c.Request.Context()
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "subject": cl.Subject,
+ }).Infof("reading build executable %s/%d", r.GetFullName(), b.GetNumber())
+
+ bExecutable, err := database.FromContext(c).PopBuildExecutable(ctx, b.GetID())
+ if err != nil {
+ retErr := fmt.Errorf("unable to pop build executable: %w", err)
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, bExecutable)
+}
diff --git a/api/build/get.go b/api/build/get.go
new file mode 100644
index 000000000..7e476441e
--- /dev/null
+++ b/api/build/get.go
@@ -0,0 +1,70 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build} builds GetBuild
+//
+// Get a build in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number to retrieve
+// required: true
+// type: integer
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the build
+// type: json
+// schema:
+// "$ref": "#/definitions/Build"
+
+// GetBuild represents the API handler to capture
+// a build for a repo from the configured backend.
+func GetBuild(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("reading build %s/%d", r.GetFullName(), b.GetNumber())
+
+ c.JSON(http.StatusOK, b)
+}
diff --git a/api/build/get_id.go b/api/build/get_id.go
new file mode 100644
index 000000000..439cd9b81
--- /dev/null
+++ b/api/build/get_id.go
@@ -0,0 +1,119 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/search/builds/{id} builds GetBuildByID
+//
+// Get a single build by its id in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: id
+// description: build id
+// required: true
+// type: number
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved build
+// schema:
+// "$ref": "#/definitions/Build"
+// '400':
+// description: Unable to retrieve the build
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to retrieve the build
+// schema:
+// "$ref": "#/definitions/Error"
+
+// GetBuildByID represents the API handler to capture a
+// build by its id from the configured backend.
+func GetBuildByID(c *gin.Context) {
+ // Variables that will hold the library types of the build and repo
+ var (
+ b *library.Build
+ r *library.Repo
+ )
+
+ // Capture user from middleware
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ // Parse build ID from path
+ id, err := strconv.ParseInt(c.Param("id"), 10, 64)
+
+ if err != nil {
+ retErr := fmt.Errorf("unable to parse build id: %w", err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": id,
+ "user": u.GetName(),
+ }).Infof("reading build %d", id)
+
+ // Get build from database
+ b, err = database.FromContext(c).GetBuild(ctx, id)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get build: %w", err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // Get repo from database using repo ID field from build
+ r, err = database.FromContext(c).GetRepo(ctx, b.GetRepoID())
+ if err != nil {
+ retErr := fmt.Errorf("unable to get repo: %w", err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // Capture user access from SCM. We do this in order to ensure user has access and is not
+ // just retrieving any build using a random id number.
+ perm, err := scm.FromContext(c).RepoAccess(u, u.GetToken(), r.GetOrg(), r.GetName())
+ if err != nil {
+ logrus.Errorf("unable to get user %s access level for repo %s", u.GetName(), r.GetFullName())
+ }
+
+ // Ensure that user has at least read access to repo to return the build
+ if perm == "none" && !u.GetAdmin() {
+ retErr := fmt.Errorf("unable to retrieve build %d: user does not have read access to repo", id)
+
+ util.HandleError(c, http.StatusUnauthorized, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, b)
+}
diff --git a/api/build/list_org.go b/api/build/list_org.go
new file mode 100644
index 000000000..3ab3bf9d3
--- /dev/null
+++ b/api/build/list_org.go
@@ -0,0 +1,223 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/api"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/repos/{org}/builds builds ListBuildsForOrg
+//
+// Get a list of builds by org in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: query
+// name: event
+// description: Filter by build event
+// type: string
+// enum:
+// - comment
+// - deployment
+// - pull_request
+// - push
+// - schedule
+// - tag
+// - in: query
+// name: branch
+// description: Filter builds by branch
+// type: string
+// - in: query
+// name: status
+// description: Filter by build status
+// type: string
+// enum:
+// - canceled
+// - error
+// - failure
+// - killed
+// - pending
+// - running
+// - success
+// - in: query
+// name: page
+// description: The page of results to retrieve
+// type: integer
+// default: 1
+// - in: query
+// name: per_page
+// description: How many results per page to return
+// type: integer
+// maximum: 100
+// default: 10
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved build list
+// schema:
+// type: array
+// items:
+// "$ref": "#/definitions/Build"
+// headers:
+// X-Total-Count:
+// description: Total number of results
+// type: integer
+// Link:
+// description: see https://tools.ietf.org/html/rfc5988
+// type: string
+// '400':
+// description: Unable to retrieve the list of builds
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to retrieve the list of builds
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ListBuildsForOrg represents the API handler to capture a
+// list of builds associated with an org from the configured backend.
+func ListBuildsForOrg(c *gin.Context) {
+ // variables that will hold the build list, build list filters and total count
+ var (
+ filters = map[string]interface{}{}
+ b []*library.Build
+ t int64
+ )
+
+ // capture middleware values
+ o := org.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "user": u.GetName(),
+ }).Infof("listing builds for org %s", o)
+
+ // capture the branch name parameter
+ branch := c.Query("branch")
+ // capture the event type parameter
+ event := c.Query("event")
+ // capture the status type parameter
+ status := c.Query("status")
+
+ // check if branch filter was provided
+ if len(branch) > 0 {
+ // add branch to filters map
+ filters["branch"] = branch
+ }
+ // check if event filter was provided
+ if len(event) > 0 {
+ // verify the event provided is a valid event type
+ if event != constants.EventComment && event != constants.EventDeploy &&
+ event != constants.EventPush && event != constants.EventPull &&
+ event != constants.EventTag && event != constants.EventSchedule {
+ retErr := fmt.Errorf("unable to process event %s: invalid event type provided", event)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // add event to filters map
+ filters["event"] = event
+ }
+ // check if status filter was provided
+ if len(status) > 0 {
+ // verify the status provided is a valid status type
+ if status != constants.StatusCanceled && status != constants.StatusError &&
+ status != constants.StatusFailure && status != constants.StatusKilled &&
+ status != constants.StatusPending && status != constants.StatusRunning &&
+ status != constants.StatusSuccess {
+ retErr := fmt.Errorf("unable to process status %s: invalid status type provided", status)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // add status to filters map
+ filters["status"] = status
+ }
+
+ // capture page query parameter if present
+ page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert page query parameter for org %s: %w", o, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // capture per_page query parameter if present
+ perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert per_page query parameter for Org %s: %w", o, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // ensure per_page isn't above or below allowed values
+ perPage = util.MaxInt(1, util.MinInt(100, perPage))
+
+ // See if the user is an org admin to bypass individual permission checks
+ perm, err := scm.FromContext(c).OrgAccess(u, o)
+ if err != nil {
+ logrus.Errorf("unable to get user %s access level for org %s", u.GetName(), o)
+ }
+ // Only show public repos to non-admins
+ if perm != "admin" {
+ filters["visibility"] = constants.VisibilityPublic
+ }
+
+ // send API call to capture the list of builds for the org (and event type if passed in)
+ b, t, err = database.FromContext(c).ListBuildsForOrg(ctx, o, filters, page, perPage)
+
+ if err != nil {
+ retErr := fmt.Errorf("unable to list builds for org %s: %w", o, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // create pagination object
+ pagination := api.Pagination{
+ Page: page,
+ PerPage: perPage,
+ Total: t,
+ }
+ // set pagination headers
+ pagination.SetHeaderLink(c)
+
+ c.JSON(http.StatusOK, b)
+}
diff --git a/api/build/list_repo.go b/api/build/list_repo.go
new file mode 100644
index 000000000..1fb701dc5
--- /dev/null
+++ b/api/build/list_repo.go
@@ -0,0 +1,261 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/api"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/repos/{org}/{repo}/builds builds ListBuildsForRepo
+//
+// Get builds from the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: query
+// name: event
+// description: Filter by build event
+// type: string
+// enum:
+// - comment
+// - deployment
+// - pull_request
+// - push
+// - schedule
+// - tag
+// - in: query
+// name: commit
+// description: Filter builds based on the commit hash
+// type: string
+// - in: query
+// name: branch
+// description: Filter builds by branch
+// type: string
+// - in: query
+// name: status
+// description: Filter by build status
+// type: string
+// enum:
+// - canceled
+// - error
+// - failure
+// - killed
+// - pending
+// - running
+// - success
+// - in: query
+// name: page
+// description: The page of results to retrieve
+// type: integer
+// default: 1
+// - in: query
+// name: per_page
+// description: How many results per page to return
+// type: integer
+// maximum: 100
+// default: 10
+// - in: query
+// name: before
+// description: filter builds created before a certain time
+// type: integer
+// default: 1
+// - in: query
+// name: after
+// description: filter builds created after a certain time
+// type: integer
+// default: 0
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the builds
+// schema:
+// type: array
+// items:
+// "$ref": "#/definitions/Build"
+// headers:
+// X-Total-Count:
+// description: Total number of results
+// type: integer
+// Link:
+// description: see https://tools.ietf.org/html/rfc5988
+// type: string
+// '400':
+// description: Unable to retrieve the list of builds
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to retrieve the list of builds
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ListBuildsForRepo represents the API handler to capture a
+// list of builds for a repo from the configured backend.
+func ListBuildsForRepo(c *gin.Context) {
+ // variables that will hold the build list, build list filters and total count
+ var (
+ filters = map[string]interface{}{}
+ b []*library.Build
+ t int64
+ )
+
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("listing builds for repo %s", r.GetFullName())
+
+ // capture the branch name parameter
+ branch := c.Query("branch")
+ // capture the event type parameter
+ event := c.Query("event")
+ // capture the status type parameter
+ status := c.Query("status")
+ // capture the commit hash parameter
+ commit := c.Query("commit")
+
+ // check if branch filter was provided
+ if len(branch) > 0 {
+ // add branch to filters map
+ filters["branch"] = branch
+ }
+ // check if event filter was provided
+ if len(event) > 0 {
+ // verify the event provided is a valid event type
+ if event != constants.EventComment && event != constants.EventDeploy &&
+ event != constants.EventPush && event != constants.EventPull &&
+ event != constants.EventTag && event != constants.EventSchedule {
+ retErr := fmt.Errorf("unable to process event %s: invalid event type provided", event)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // add event to filters map
+ filters["event"] = event
+ }
+ // check if status filter was provided
+ if len(status) > 0 {
+ // verify the status provided is a valid status type
+ if status != constants.StatusCanceled && status != constants.StatusError &&
+ status != constants.StatusFailure && status != constants.StatusKilled &&
+ status != constants.StatusPending && status != constants.StatusRunning &&
+ status != constants.StatusSuccess {
+ retErr := fmt.Errorf("unable to process status %s: invalid status type provided", status)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // add status to filters map
+ filters["status"] = status
+ }
+
+ // check if commit hash filter was provided
+ if len(commit) > 0 {
+ // add commit to filters map
+ filters["commit"] = commit
+ }
+
+ // capture page query parameter if present
+ page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert page query parameter for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // capture per_page query parameter if present
+ perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert per_page query parameter for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // ensure per_page isn't above or below allowed values
+ perPage = util.MaxInt(1, util.MinInt(100, perPage))
+
+ // capture before query parameter if present, default to now
+ before, err := strconv.ParseInt(c.DefaultQuery("before", strconv.FormatInt(time.Now().UTC().Unix(), 10)), 10, 64)
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert before query parameter for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // capture after query parameter if present, default to 0
+ after, err := strconv.ParseInt(c.DefaultQuery("after", "0"), 10, 64)
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert after query parameter for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ b, t, err = database.FromContext(c).ListBuildsForRepo(ctx, r, filters, before, after, page, perPage)
+ if err != nil {
+ retErr := fmt.Errorf("unable to list builds for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // create pagination object
+ pagination := api.Pagination{
+ Page: page,
+ PerPage: perPage,
+ Total: t,
+ }
+ // set pagination headers
+ pagination.SetHeaderLink(c)
+
+ c.JSON(http.StatusOK, b)
+}
diff --git a/api/build/plan.go b/api/build/plan.go
new file mode 100644
index 000000000..13080b40c
--- /dev/null
+++ b/api/build/plan.go
@@ -0,0 +1,63 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/go-vela/server/api/service"
+ "github.com/go-vela/server/api/step"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/types/library"
+ "github.com/go-vela/types/pipeline"
+)
+
+// PlanBuild is a helper function to plan the build for
+// execution. This creates all resources, like steps
+// and services, for the build in the configured backend.
+// TODO:
+// - return build and error.
+func PlanBuild(ctx context.Context, database database.Interface, p *pipeline.Build, b *library.Build, r *library.Repo) error {
+ // update fields in build object
+ b.SetCreated(time.Now().UTC().Unix())
+
+ // send API call to create the build
+ // TODO: return created build and error instead of just error
+ b, err := database.CreateBuild(ctx, b)
+ if err != nil {
+ // clean up the objects from the pipeline in the database
+ // TODO:
+ // - even if it was created, we need to get the new build id
+ // otherwise it will be 0, which attempts to INSERT instead
+ // of UPDATE-ing the existing build - which results in
+ // a constraint error (repo_id, number)
+ // - do we want to update the build or just delete it?
+ CleanBuild(ctx, database, b, nil, nil, err)
+
+ return fmt.Errorf("unable to create new build for %s: %w", r.GetFullName(), err)
+ }
+
+ // plan all services for the build
+ services, err := service.PlanServices(database, p, b)
+ if err != nil {
+ // clean up the objects from the pipeline in the database
+ CleanBuild(ctx, database, b, services, nil, err)
+
+ return err
+ }
+
+ // plan all steps for the build
+ steps, err := step.PlanSteps(database, p, b)
+ if err != nil {
+ // clean up the objects from the pipeline in the database
+ CleanBuild(ctx, database, b, services, steps, err)
+
+ return err
+ }
+
+ return nil
+}
diff --git a/api/build/publish.go b/api/build/publish.go
new file mode 100644
index 000000000..51f94cff2
--- /dev/null
+++ b/api/build/publish.go
@@ -0,0 +1,98 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "encoding/json"
+ "time"
+
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/queue"
+ "github.com/go-vela/types"
+ "github.com/go-vela/types/library"
+ "github.com/go-vela/types/pipeline"
+ "github.com/sirupsen/logrus"
+)
+
+// PublishToQueue is a helper function that creates
+// a build item and publishes it to the queue.
+func PublishToQueue(ctx context.Context, queue queue.Service, db database.Interface, p *pipeline.Build, b *library.Build, r *library.Repo, u *library.User) {
+ byteExecutable, err := json.Marshal(p)
+ if err != nil {
+ logrus.Errorf("Failed to marshal build executable %d for %s: %v", b.GetNumber(), r.GetFullName(), err)
+
+ // error out the build
+ CleanBuild(ctx, db, b, nil, nil, err)
+
+ return
+ }
+
+ bExecutable := new(library.BuildExecutable)
+ bExecutable.SetBuildID(b.GetID())
+ bExecutable.SetData(byteExecutable)
+
+ err = db.CreateBuildExecutable(ctx, bExecutable)
+ if err != nil {
+ logrus.Errorf("Failed to publish build executable to database %d for %s: %v", b.GetNumber(), r.GetFullName(), err)
+
+ // error out the build
+ CleanBuild(ctx, db, b, nil, nil, err)
+
+ return
+ }
+
+ item := types.ToItem(b, r, u)
+
+ logrus.Infof("Converting queue item to json for build %d for %s", b.GetNumber(), r.GetFullName())
+
+ byteItem, err := json.Marshal(item)
+ if err != nil {
+ logrus.Errorf("Failed to convert item to json for build %d for %s: %v", b.GetNumber(), r.GetFullName(), err)
+
+ // error out the build
+ CleanBuild(ctx, db, b, nil, nil, err)
+
+ return
+ }
+
+ logrus.Infof("Establishing route for build %d for %s", b.GetNumber(), r.GetFullName())
+
+ route, err := queue.Route(&p.Worker)
+ if err != nil {
+ logrus.Errorf("unable to set route for build %d for %s: %v", b.GetNumber(), r.GetFullName(), err)
+
+ // error out the build
+ CleanBuild(ctx, db, b, nil, nil, err)
+
+ return
+ }
+
+ logrus.Infof("Publishing item for build %d for %s to queue %s", b.GetNumber(), r.GetFullName(), route)
+
+ err = queue.Push(context.Background(), route, byteItem)
+ if err != nil {
+ logrus.Errorf("Retrying; Failed to publish build %d for %s: %v", b.GetNumber(), r.GetFullName(), err)
+
+ err = queue.Push(context.Background(), route, byteItem)
+ if err != nil {
+ logrus.Errorf("Failed to publish build %d for %s: %v", b.GetNumber(), r.GetFullName(), err)
+
+ // error out the build
+ CleanBuild(ctx, db, b, nil, nil, err)
+
+ return
+ }
+ }
+
+ // update fields in build object
+ b.SetEnqueued(time.Now().UTC().Unix())
+
+ // update the build in the db to reflect the time it was enqueued
+ _, err = db.UpdateBuild(ctx, b)
+ if err != nil {
+ logrus.Errorf("Failed to update build %d during publish to queue for %s: %v", b.GetNumber(), r.GetFullName(), err)
+ }
+}
diff --git a/api/build/restart.go b/api/build/restart.go
new file mode 100644
index 000000000..804ad88bd
--- /dev/null
+++ b/api/build/restart.go
@@ -0,0 +1,353 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/compiler"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/queue"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/claims"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/go-vela/types/pipeline"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build} builds RestartBuild
+//
+// Restart a build in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number to restart
+// required: true
+// type: integer
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Request processed but build was skipped
+// schema:
+// type: string
+// '201':
+// description: Successfully restarted the build
+// schema:
+// "$ref": "#/definitions/Build"
+// '400':
+// description: Unable to restart the build
+// schema:
+// "$ref": "#/definitions/Error"
+// '404':
+// description: Unable to restart the build
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to restart the build
+// schema:
+// "$ref": "#/definitions/Error"
+
+// RestartBuild represents the API handler to restart an existing build in the configured backend.
+//
+//nolint:funlen // ignore statement count
+func RestartBuild(c *gin.Context) {
+ // capture middleware values
+ m := c.MustGet("metadata").(*types.Metadata)
+ cl := claims.Retrieve(c)
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logger := logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ })
+
+ logger.Infof("restarting build %s", entry)
+
+ // send API call to capture the repo owner
+ u, err := database.FromContext(c).GetUser(r.GetUserID())
+ if err != nil {
+ retErr := fmt.Errorf("unable to get owner for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // create SQL filters for querying pending and running builds for repo
+ filters := map[string]interface{}{
+ "status": []string{constants.StatusPending, constants.StatusRunning},
+ }
+
+ // send API call to capture the number of pending or running builds for the repo
+ builds, err := database.FromContext(c).CountBuildsForRepo(ctx, r, filters)
+ if err != nil {
+ retErr := fmt.Errorf("unable to restart build: unable to get count of builds for repo %s", r.GetFullName())
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // check if the number of pending and running builds exceeds the limit for the repo
+ if builds >= r.GetBuildLimit() {
+ retErr := fmt.Errorf("unable to restart build: repo %s has exceeded the concurrent build limit of %d", r.GetFullName(), r.GetBuildLimit())
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update fields in build object
+ b.SetID(0)
+ b.SetCreated(time.Now().UTC().Unix())
+ b.SetEnqueued(0)
+ b.SetStarted(0)
+ b.SetFinished(0)
+ b.SetStatus(constants.StatusPending)
+ b.SetHost("")
+ b.SetRuntime("")
+ b.SetDistribution("")
+ b.SetSender(cl.Subject)
+
+ // update the PR event action if action was never set
+ // for backwards compatibility with pre-0.14 releases.
+ if b.GetEvent() == constants.EventPull && b.GetEventAction() == "" {
+ // technically, the action could have been opened or synchronize.
+ // will not affect behavior of the pipeline since we did not
+ // support actions for builds where this would be the case.
+ b.SetEventAction(constants.ActionOpened)
+ }
+
+ // set the parent equal to the restarted build number
+ b.SetParent(b.GetNumber())
+ // update the build numbers based off repo counter
+ inc := r.GetCounter() + 1
+ r.SetCounter(inc)
+ b.SetNumber(inc)
+
+ // populate the build link if a web address is provided
+ if len(m.Vela.WebAddress) > 0 {
+ b.SetLink(
+ fmt.Sprintf("%s/%s/%d", m.Vela.WebAddress, r.GetFullName(), b.GetNumber()),
+ )
+ }
+
+ // variable to store changeset files
+ var files []string
+ // check if the build event is not issue_comment or pull_request
+ if !strings.EqualFold(b.GetEvent(), constants.EventComment) &&
+ !strings.EqualFold(b.GetEvent(), constants.EventPull) {
+ // send API call to capture list of files changed for the commit
+ files, err = scm.FromContext(c).Changeset(u, r, b.GetCommit())
+ if err != nil {
+ retErr := fmt.Errorf("unable to restart build: failed to get changeset for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ }
+
+ // check if the build event is a pull_request
+ if strings.EqualFold(b.GetEvent(), constants.EventPull) {
+ // capture number from build
+ number, err := getPRNumberFromBuild(b)
+ if err != nil {
+ retErr := fmt.Errorf("unable to restart build: failed to get pull_request number for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // send API call to capture list of files changed for the pull request
+ files, err = scm.FromContext(c).ChangesetPR(u, r, number)
+ if err != nil {
+ retErr := fmt.Errorf("unable to restart build: failed to get changeset for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ }
+
+ // variables to store pipeline configuration
+ var (
+ // variable to store the raw pipeline configuration
+ config []byte
+ // variable to store executable pipeline
+ p *pipeline.Build
+ // variable to store pipeline configuration
+ pipeline *library.Pipeline
+ // variable to store the pipeline type for the repository
+ pipelineType = r.GetPipelineType()
+ )
+
+ // send API call to attempt to capture the pipeline
+ pipeline, err = database.FromContext(c).GetPipelineForRepo(ctx, b.GetCommit(), r)
+ if err != nil { // assume the pipeline doesn't exist in the database yet (before pipeline support was added)
+ // send API call to capture the pipeline configuration file
+ config, err = scm.FromContext(c).ConfigBackoff(u, r, b.GetCommit())
+ if err != nil {
+ retErr := fmt.Errorf("unable to get pipeline configuration for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+ } else {
+ config = pipeline.GetData()
+ }
+
+ // ensure we use the expected pipeline type when compiling
+ //
+ // The pipeline type for a repo can change at any time which can break compiling
+ // existing pipelines in the system for that repo. To account for this, we update
+ // the repo pipeline type to match what was defined for the existing pipeline
+ // before compiling. After we're done compiling, we reset the pipeline type.
+ if len(pipeline.GetType()) > 0 {
+ r.SetPipelineType(pipeline.GetType())
+ }
+
+ var compiled *library.Pipeline
+ // parse and compile the pipeline configuration file
+ p, compiled, err = compiler.FromContext(c).
+ Duplicate().
+ WithBuild(b).
+ WithFiles(files).
+ WithMetadata(m).
+ WithRepo(r).
+ WithUser(u).
+ Compile(config)
+ if err != nil {
+ retErr := fmt.Errorf("unable to compile pipeline configuration for %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ // reset the pipeline type for the repo
+ //
+ // The pipeline type for a repo can change at any time which can break compiling
+ // existing pipelines in the system for that repo. To account for this, we update
+ // the repo pipeline type to match what was defined for the existing pipeline
+ // before compiling. After we're done compiling, we reset the pipeline type.
+ r.SetPipelineType(pipelineType)
+
+ // skip the build if only the init or clone steps are found
+ skip := SkipEmptyBuild(p)
+ if skip != "" {
+ // set build to successful status
+ b.SetStatus(constants.StatusSkipped)
+
+ // send API call to set the status on the commit
+ err = scm.FromContext(c).Status(u, b, r.GetOrg(), r.GetName())
+ if err != nil {
+ logrus.Errorf("unable to set commit status for %s/%d: %v", r.GetFullName(), b.GetNumber(), err)
+ }
+
+ c.JSON(http.StatusOK, skip)
+
+ return
+ }
+
+ // check if the pipeline did not already exist in the database
+ //
+ //nolint:dupl // ignore duplicate code
+ if pipeline == nil {
+ pipeline = compiled
+ pipeline.SetRepoID(r.GetID())
+ pipeline.SetCommit(b.GetCommit())
+ pipeline.SetRef(b.GetRef())
+
+ // send API call to create the pipeline
+ pipeline, err = database.FromContext(c).CreatePipeline(ctx, pipeline)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create pipeline for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+ }
+
+ b.SetPipelineID(pipeline.GetID())
+
+ // create the objects from the pipeline in the database
+ err = PlanBuild(ctx, database.FromContext(c), p, b, r)
+ if err != nil {
+ util.HandleError(c, http.StatusInternalServerError, err)
+
+ return
+ }
+
+ // send API call to update repo for ensuring counter is incremented
+ r, err = database.FromContext(c).UpdateRepo(ctx, r)
+ if err != nil {
+ retErr := fmt.Errorf("unable to restart build: failed to update repo %s: %w", r.GetFullName(), err)
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // send API call to capture the restarted build
+ b, _ = database.FromContext(c).GetBuildForRepo(ctx, r, b.GetNumber())
+
+ c.JSON(http.StatusCreated, b)
+
+ // send API call to set the status on the commit
+ err = scm.FromContext(c).Status(u, b, r.GetOrg(), r.GetName())
+ if err != nil {
+ logger.Errorf("unable to set commit status for build %s: %v", entry, err)
+ }
+
+ // publish the build to the queue
+ go PublishToQueue(
+ ctx,
+ queue.FromGinContext(c),
+ database.FromContext(c),
+ p,
+ b,
+ r,
+ u,
+ )
+}
diff --git a/api/build/skip.go b/api/build/skip.go
new file mode 100644
index 000000000..4d5cf8af9
--- /dev/null
+++ b/api/build/skip.go
@@ -0,0 +1,41 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "github.com/go-vela/types/pipeline"
+)
+
+// SkipEmptyBuild checks if the build should be skipped due to it
+// not containing any steps besides init or clone.
+//
+//nolint:goconst // ignore init and clone constants
+func SkipEmptyBuild(p *pipeline.Build) string {
+ if len(p.Stages) == 1 {
+ if p.Stages[0].Name == "init" {
+ return "skipping build since only init stage found"
+ }
+ }
+
+ if len(p.Stages) == 2 {
+ if p.Stages[0].Name == "init" && p.Stages[1].Name == "clone" {
+ return "skipping build since only init and clone stages found"
+ }
+ }
+
+ if len(p.Steps) == 1 {
+ if p.Steps[0].Name == "init" {
+ return "skipping build since only init step found"
+ }
+ }
+
+ if len(p.Steps) == 2 {
+ if p.Steps[0].Name == "init" && p.Steps[1].Name == "clone" {
+ return "skipping build since only init and clone steps found"
+ }
+ }
+
+ return ""
+}
diff --git a/api/build_test.go b/api/build/skip_test.go
similarity index 80%
rename from api/build_test.go
rename to api/build/skip_test.go
index 43253c108..ca805a84e 100644
--- a/api/build_test.go
+++ b/api/build/skip_test.go
@@ -1,4 +1,8 @@
-package api
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
import (
"testing"
@@ -6,10 +10,11 @@ import (
"github.com/go-vela/types/pipeline"
)
-func Test_skipEmptyBuild(t *testing.T) {
+func Test_SkipEmptyBuild(t *testing.T) {
type args struct {
p *pipeline.Build
}
+
tests := []struct {
name string
args args
@@ -64,10 +69,11 @@ func Test_skipEmptyBuild(t *testing.T) {
},
}}}, ""},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if got := skipEmptyBuild(tt.args.p); got != tt.want {
- t.Errorf("skipEmptyBuild() = %v, want %v", got, tt.want)
+ if got := SkipEmptyBuild(tt.args.p); got != tt.want {
+ t.Errorf("SkipEmptyBuild() = %v, want %v", got, tt.want)
}
})
}
diff --git a/api/build/token.go b/api/build/token.go
new file mode 100644
index 000000000..da18a0322
--- /dev/null
+++ b/api/build/token.go
@@ -0,0 +1,119 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/internal/token"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/claims"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/token builds GetBuildToken
+//
+// Get a build token
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved build token
+// schema:
+// "$ref": "#/definitions/Token"
+// '400':
+// description: Bad request
+// schema:
+// "$ref": "#/definitions/Error"
+// '409':
+// description: Conflict (requested build token for build not in pending state)
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to generate build token
+// schema:
+// "$ref": "#/definitions/Error"
+
+// GetBuildToken represents the API handler to generate a build token.
+func GetBuildToken(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ cl := claims.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "user": cl.Subject,
+ }).Infof("generating build token for build %s/%d", r.GetFullName(), b.GetNumber())
+
+ // if build is not in a pending state, then a build token should not be needed - conflict
+ if !strings.EqualFold(b.GetStatus(), constants.StatusPending) {
+ retErr := fmt.Errorf("unable to mint build token: build is not in pending state")
+ util.HandleError(c, http.StatusConflict, retErr)
+
+ return
+ }
+
+ // retrieve token manager from context
+ tm := c.MustGet("token-manager").(*token.Manager)
+
+ // set expiration to repo timeout plus configurable buffer
+ exp := (time.Duration(r.GetTimeout()) * time.Minute) + tm.BuildTokenBufferDuration
+
+ // set mint token options
+ bmto := &token.MintTokenOpts{
+ Hostname: cl.Subject,
+ BuildID: b.GetID(),
+ Repo: r.GetFullName(),
+ TokenType: constants.WorkerBuildTokenType,
+ TokenDuration: exp,
+ }
+
+ // mint token
+ bt, err := tm.MintToken(bmto)
+ if err != nil {
+ retErr := fmt.Errorf("unable to generate build token: %w", err)
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, library.Token{Token: &bt})
+}
diff --git a/api/build/update.go b/api/build/update.go
new file mode 100644
index 000000000..d042fdd82
--- /dev/null
+++ b/api/build/update.go
@@ -0,0 +1,184 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/claims"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation PUT /api/v1/repos/{org}/{repo}/builds/{build} builds UpdateBuild
+//
+// Updates a build in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number to update
+// required: true
+// type: integer
+// - in: body
+// name: body
+// description: Payload containing the build to update
+// required: true
+// schema:
+// "$ref": "#/definitions/Build"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully updated the build
+// schema:
+// "$ref": "#/definitions/Build"
+// '404':
+// description: Unable to update the build
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to update the build
+// schema:
+// "$ref": "#/definitions/Error"
+
+// UpdateBuild represents the API handler to update
+// a build for a repo in the configured backend.
+func UpdateBuild(c *gin.Context) {
+ // capture middleware values
+ cl := claims.Retrieve(c)
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ ctx := c.Request.Context()
+
+ entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "user": cl.Subject,
+ }).Infof("updating build %s", entry)
+
+ // capture body from API request
+ input := new(library.Build)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for build %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ // update build fields if provided
+ if len(input.GetStatus()) > 0 {
+ // update status if set
+ b.SetStatus(input.GetStatus())
+ }
+
+ if len(input.GetError()) > 0 {
+ // update error if set
+ b.SetError(input.GetError())
+ }
+
+ if input.GetEnqueued() > 0 {
+ // update enqueued if set
+ b.SetEnqueued(input.GetEnqueued())
+ }
+
+ if input.GetStarted() > 0 {
+ // update started if set
+ b.SetStarted(input.GetStarted())
+ }
+
+ if input.GetFinished() > 0 {
+ // update finished if set
+ b.SetFinished(input.GetFinished())
+ }
+
+ if len(input.GetTitle()) > 0 {
+ // update title if set
+ b.SetTitle(input.GetTitle())
+ }
+
+ if len(input.GetMessage()) > 0 {
+ // update message if set
+ b.SetMessage(input.GetMessage())
+ }
+
+ if len(input.GetHost()) > 0 {
+ // update host if set
+ b.SetHost(input.GetHost())
+ }
+
+ if len(input.GetRuntime()) > 0 {
+ // update runtime if set
+ b.SetRuntime(input.GetRuntime())
+ }
+
+ if len(input.GetDistribution()) > 0 {
+ // update distribution if set
+ b.SetDistribution(input.GetDistribution())
+ }
+
+ // send API call to update the build
+ b, err = database.FromContext(c).UpdateBuild(ctx, b)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update build %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, b)
+
+ // check if the build is in a "final" state
+ if b.GetStatus() == constants.StatusSuccess ||
+ b.GetStatus() == constants.StatusFailure ||
+ b.GetStatus() == constants.StatusCanceled ||
+ b.GetStatus() == constants.StatusKilled ||
+ b.GetStatus() == constants.StatusError {
+ // send API call to capture the repo owner
+ u, err := database.FromContext(c).GetUser(r.GetUserID())
+ if err != nil {
+ logrus.Errorf("unable to get owner for build %s: %v", entry, err)
+ }
+
+ // send API call to set the status on the commit
+ err = scm.FromContext(c).Status(u, b, r.GetOrg(), r.GetName())
+ if err != nil {
+ logrus.Errorf("unable to set commit status for build %s: %v", entry, err)
+ }
+ }
+}
diff --git a/api/deployment.go b/api/deployment.go
deleted file mode 100644
index 33f43b6f8..000000000
--- a/api/deployment.go
+++ /dev/null
@@ -1,343 +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 api
-
-import (
- "fmt"
- "net/http"
- "strconv"
-
- "github.com/go-vela/server/router/middleware/org"
-
- "github.com/go-vela/server/database"
- "github.com/go-vela/server/router/middleware/repo"
- "github.com/go-vela/server/router/middleware/user"
- "github.com/go-vela/server/scm"
- "github.com/go-vela/server/util"
-
- "github.com/go-vela/types/library"
-
- "github.com/gin-gonic/gin"
- "github.com/sirupsen/logrus"
-)
-
-// swagger:operation POST /api/v1/deployments/{org}/{repo} deployment CreateDeployment
-//
-// Create a deployment for the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '201':
-// description: Successfully created the deployment
-// schema:
-// "$ref": "#/definitions/Deployment"
-// '400':
-// description: Unable to create the deployment
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to create the deployment
-// schema:
-// "$ref": "#/definitions/Error"
-
-// CreateDeployment represents the API handler to
-// create a deployment in the configured backend.
-func CreateDeployment(c *gin.Context) {
- // capture middleware values
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("creating new deployment for repo %s", r.GetFullName())
-
- // capture body from API request
- input := new(library.Deployment)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for new deployment for %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // update fields in deployment object
- input.SetRepoID(r.GetID())
- input.SetUser(u.GetName())
-
- if len(input.GetDescription()) == 0 {
- input.SetDescription("Deployment request from Vela")
- }
-
- if len(input.GetTask()) == 0 {
- input.SetTask("deploy:vela")
- }
-
- // send API call to create the deployment
- err = scm.FromContext(c).CreateDeployment(u, r, input)
- if err != nil {
- retErr := fmt.Errorf("unable to create new deployment for %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusCreated, input)
-}
-
-// swagger:operation GET /api/v1/deployments/{org}/{repo} deployment GetDeployments
-//
-// Get a list of deployments for the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: query
-// name: page
-// description: The page of results to retrieve
-// type: integer
-// default: 1
-// - in: query
-// name: per_page
-// description: How many results per page to return
-// type: integer
-// maximum: 100
-// default: 10
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the list of deployments
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/Deployment"
-// headers:
-// X-Total-Count:
-// description: Total number of results
-// type: integer
-// Link:
-// description: see https://tools.ietf.org/html/rfc5988
-// type: string
-// '400':
-// description: Unable to retrieve the list of deployments
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to retrieve the list of deployments
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetDeployments represents the API handler to capture
-// a list of deployments from the configured backend.
-func GetDeployments(c *gin.Context) {
- // capture middleware values
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("reading deployments for repo %s", r.GetFullName())
-
- // capture page query parameter if present
- page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
- if err != nil {
- retErr := fmt.Errorf("unable to convert page query parameter for %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // capture per_page query parameter if present
- perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to convert per_page query parameter for %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // ensure per_page isn't above or below allowed values
- //
- // nolint: gomnd // ignore magic number
- perPage = util.MaxInt(1, util.MinInt(100, perPage))
-
- // send API call to capture the total number of deployments for the repo
- t, err := scm.FromContext(c).GetDeploymentCount(u, r)
- if err != nil {
- retErr := fmt.Errorf("unable to get deployment count for %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the list of steps for the build
- d, err := scm.FromContext(c).GetDeploymentList(u, r, page, perPage)
- if err != nil {
- retErr := fmt.Errorf("unable to get deployments for %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- dWithBs := []*library.Deployment{}
- for _, deployment := range d {
- b, err := database.FromContext(c).GetDeploymentBuildList(*deployment.URL)
- if err != nil {
- retErr := fmt.Errorf("unable to get builds for deployment %d: %w", deployment.GetID(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- builds := []library.Build{}
- for _, build := range b {
- builds = append(builds, *build)
- }
-
- deployment.SetBuilds(builds)
-
- dWithBs = append(dWithBs, deployment)
- }
-
- // create pagination object
- pagination := Pagination{
- Page: page,
- PerPage: perPage,
- Total: t,
- }
- // set pagination headers
- pagination.SetHeaderLink(c)
-
- c.JSON(http.StatusOK, dWithBs)
-}
-
-// swagger:operation GET /api/v1/deployments/{org}/{repo}/{deployment} deployment GetDeployment
-//
-// Get a deployment from the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: deployment
-// description: Name of the org
-// required: true
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the deployment
-// schema:
-// "$ref": "#/definitions/Deployment"
-// '400':
-// description: Unable to retrieve the deployment
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to retrieve the deployment
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetDeployment represents the API handler to
-// capture a deployment from the configured backend.
-func GetDeployment(c *gin.Context) {
- // capture middleware values
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
- deployment := c.Param("deployment")
-
- entry := fmt.Sprintf("%s/%s", r.GetFullName(), deployment)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("reading deployment %s", entry)
-
- number, err := strconv.Atoi(deployment)
- if err != nil {
- retErr := fmt.Errorf("invalid deployment parameter provided: %s", deployment)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // send API call to capture the deployment
- d, err := scm.FromContext(c).GetDeployment(u, r, int64(number))
- if err != nil {
- retErr := fmt.Errorf("unable to get deployment %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, d)
-}
diff --git a/api/deployment/create.go b/api/deployment/create.go
new file mode 100644
index 000000000..1ad549c07
--- /dev/null
+++ b/api/deployment/create.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 deployment
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/deployments/{org}/{repo} deployments CreateDeployment
+//
+// Create a deployment for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '201':
+// description: Successfully created the deployment
+// schema:
+// "$ref": "#/definitions/Deployment"
+// '400':
+// description: Unable to create the deployment
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to create the deployment
+// schema:
+// "$ref": "#/definitions/Error"
+
+// CreateDeployment represents the API handler to
+// create a deployment in the configured backend.
+func CreateDeployment(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("creating new deployment for repo %s", r.GetFullName())
+
+ // capture body from API request
+ input := new(library.Deployment)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for new deployment for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update fields in deployment object
+ input.SetRepoID(r.GetID())
+ input.SetUser(u.GetName())
+
+ if len(input.GetDescription()) == 0 {
+ input.SetDescription("Deployment request from Vela")
+ }
+
+ if len(input.GetTask()) == 0 {
+ input.SetTask("deploy:vela")
+ }
+
+ // send API call to create the deployment
+ err = scm.FromContext(c).CreateDeployment(u, r, input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create new deployment for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusCreated, input)
+}
diff --git a/api/deployment/doc.go b/api/deployment/doc.go
new file mode 100644
index 000000000..c6118b8f3
--- /dev/null
+++ b/api/deployment/doc.go
@@ -0,0 +1,10 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+// Package deployment provides the deployment handlers for the Vela API.
+//
+// Usage:
+//
+// import "github.com/go-vela/server/api/deployment"
+package deployment
diff --git a/api/deployment/get.go b/api/deployment/get.go
new file mode 100644
index 000000000..ff6827d3a
--- /dev/null
+++ b/api/deployment/get.go
@@ -0,0 +1,100 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package deployment
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/deployments/{org}/{repo}/{deployment} deployments GetDeployment
+//
+// Get a deployment from the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: deployment
+// description: Number of the deployment
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the deployment
+// schema:
+// "$ref": "#/definitions/Deployment"
+// '400':
+// description: Unable to retrieve the deployment
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to retrieve the deployment
+// schema:
+// "$ref": "#/definitions/Error"
+
+// GetDeployment represents the API handler to
+// capture a deployment from the configured backend.
+func GetDeployment(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ deployment := util.PathParameter(c, "deployment")
+
+ entry := fmt.Sprintf("%s/%s", r.GetFullName(), deployment)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("reading deployment %s", entry)
+
+ number, err := strconv.Atoi(deployment)
+ if err != nil {
+ retErr := fmt.Errorf("invalid deployment parameter provided: %s", deployment)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // send API call to capture the deployment
+ d, err := scm.FromContext(c).GetDeployment(u, r, int64(number))
+ if err != nil {
+ retErr := fmt.Errorf("unable to get deployment %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, d)
+}
diff --git a/api/deployment/list.go b/api/deployment/list.go
new file mode 100644
index 000000000..cb2ec2010
--- /dev/null
+++ b/api/deployment/list.go
@@ -0,0 +1,171 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package deployment
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/api"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/deployments/{org}/{repo} deployments ListDeployments
+//
+// Get a list of deployments for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: query
+// name: page
+// description: The page of results to retrieve
+// type: integer
+// default: 1
+// - in: query
+// name: per_page
+// description: How many results per page to return
+// type: integer
+// maximum: 100
+// default: 10
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the list of deployments
+// schema:
+// type: array
+// items:
+// "$ref": "#/definitions/Deployment"
+// headers:
+// X-Total-Count:
+// description: Total number of results
+// type: integer
+// Link:
+// description: see https://tools.ietf.org/html/rfc5988
+// type: string
+// '400':
+// description: Unable to retrieve the list of deployments
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to retrieve the list of deployments
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ListDeployments represents the API handler to capture
+// a list of deployments from the configured backend.
+func ListDeployments(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("reading deployments for repo %s", r.GetFullName())
+
+ // capture page query parameter if present
+ page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert page query parameter for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // capture per_page query parameter if present
+ perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert per_page query parameter for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // 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 deployments for the repo
+ t, err := scm.FromContext(c).GetDeploymentCount(u, r)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get deployment count for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // send API call to capture the list of deployments for the repo
+ d, err := scm.FromContext(c).GetDeploymentList(u, r, page, perPage)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get deployments for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ dWithBs := []*library.Deployment{}
+
+ for _, deployment := range d {
+ b, _, err := database.FromContext(c).ListBuildsForDeployment(ctx, deployment, nil, 1, 3)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get builds for deployment %d: %w", deployment.GetID(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ builds := []library.Build{}
+ for _, build := range b {
+ builds = append(builds, *build)
+ }
+
+ deployment.SetBuilds(builds)
+
+ dWithBs = append(dWithBs, deployment)
+ }
+
+ // create pagination object
+ pagination := api.Pagination{
+ Page: page,
+ PerPage: perPage,
+ Total: t,
+ }
+ // set pagination headers
+ pagination.SetHeaderLink(c)
+
+ c.JSON(http.StatusOK, dWithBs)
+}
diff --git a/api/doc.go b/api/doc.go
index e1fe11d97..4f9d47c48 100644
--- a/api/doc.go
+++ b/api/doc.go
@@ -6,5 +6,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/api"
+// import "github.com/go-vela/server/api"
package api
diff --git a/api/hook.go b/api/hook.go
deleted file mode 100644
index f1b7da600..000000000
--- a/api/hook.go
+++ /dev/null
@@ -1,594 +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 api
-
-import (
- "fmt"
- "net/http"
- "strconv"
- "time"
-
- "github.com/go-vela/server/router/middleware/org"
- "github.com/go-vela/server/router/middleware/user"
-
- "github.com/go-vela/server/database"
- "github.com/go-vela/server/router/middleware/repo"
- "github.com/go-vela/server/util"
-
- "github.com/go-vela/types/library"
-
- "github.com/gin-gonic/gin"
- "github.com/sirupsen/logrus"
-)
-
-// swagger:operation POST /api/v1/hooks/{org}/{repo} webhook CreateHook
-//
-// Create a webhook for the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: body
-// name: body
-// description: Webhook payload that we expect from the user or VCS
-// required: true
-// schema:
-// "$ref": "#/definitions/Webhook"
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '201':
-// description: The webhook has been created
-// schema:
-// "$ref": "#/definitions/Webhook"
-// '400':
-// description: The webhook was unable to be created
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: The webhook was unable to be created
-// schema:
-// "$ref": "#/definitions/Error"
-
-// CreateHook represents the API handler to create
-// a webhook in the configured backend.
-func CreateHook(c *gin.Context) {
- // capture middleware values
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("creating new hook for repo %s", r.GetFullName())
-
- // capture body from API request
- input := new(library.Hook)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for new hook for repo %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // send API call to capture the last hook for the repo
- lastHook, err := database.FromContext(c).GetLastHook(r)
- if err != nil {
- retErr := fmt.Errorf("unable to get last hook for repo %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // update fields in webhook object
- input.SetRepoID(r.GetID())
- input.SetNumber(1)
-
- if input.GetCreated() == 0 {
- input.SetCreated(time.Now().UTC().Unix())
- }
-
- if lastHook != nil {
- input.SetNumber(
- lastHook.GetNumber() + 1,
- )
- }
-
- // send API call to create the webhook
- err = database.FromContext(c).CreateHook(input)
- if err != nil {
- retErr := fmt.Errorf("unable to create hook for repo %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the created webhook
- h, _ := database.FromContext(c).GetHook(input.GetNumber(), r)
-
- c.JSON(http.StatusCreated, h)
-}
-
-// swagger:operation GET /api/v1/hooks/{org}/{repo} webhook GetHooks
-//
-// Retrieve the webhooks for the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: query
-// name: page
-// description: The page of results to retrieve
-// type: integer
-// default: 1
-// - in: query
-// name: per_page
-// description: How many results per page to return
-// type: integer
-// maximum: 100
-// default: 10
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved webhooks
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/Webhook"
-// headers:
-// X-Total-Count:
-// description: Total number of results
-// type: integer
-// Link:
-// description: see https://tools.ietf.org/html/rfc5988
-// type: string
-// '400':
-// description: Unable to retrieve webhooks
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to retrieve webhooks
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetHooks represents the API handler to capture a list
-// of webhooks from the configured backend.
-func GetHooks(c *gin.Context) {
- // capture middleware values
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("reading hooks for repo %s", r.GetFullName())
-
- // capture page query parameter if present
- page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to convert page query parameter for repo %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // capture per_page query parameter if present
- perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to convert per_page query parameter for repo %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // ensure per_page isn't above or below allowed values
- //
- // nolint: gomnd // ignore magic number
- perPage = util.MaxInt(1, util.MinInt(100, perPage))
-
- // send API call to capture the total number of webhooks for the repo
- t, err := database.FromContext(c).GetRepoHookCount(r)
- if err != nil {
- retErr := fmt.Errorf("unable to get hooks count for repo %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the list of steps for the build
- h, err := database.FromContext(c).GetRepoHookList(r, page, perPage)
- if err != nil {
- retErr := fmt.Errorf("unable to get hooks for repo %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // create pagination object
- pagination := Pagination{
- Page: page,
- PerPage: perPage,
- Total: t,
- }
- // set pagination headers
- pagination.SetHeaderLink(c)
-
- c.JSON(http.StatusOK, h)
-}
-
-// swagger:operation GET /api/v1/hooks/{org}/{repo}/{hook} webhook GetHook
-//
-// Retrieve a webhook for the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: hook
-// description: Number of the hook
-// required: true
-// type: integer
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the webhook
-// schema:
-// "$ref": "#/definitions/Webhook"
-// '400':
-// description: Unable to retrieve the webhook
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to retrieve the webhook
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetHook represents the API handler to capture a
-// webhook from the configured backend.
-func GetHook(c *gin.Context) {
- // capture middleware values
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
- hook := c.Param("hook")
-
- entry := fmt.Sprintf("%s/%s", r.GetFullName(), hook)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "hook": hook,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("reading hook %s", entry)
-
- number, err := strconv.Atoi(hook)
- if err != nil {
- retErr := fmt.Errorf("invalid hook parameter provided: %s", hook)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // send API call to capture the webhook
- h, err := database.FromContext(c).GetHook(number, r)
- if err != nil {
- retErr := fmt.Errorf("unable to get hook %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, h)
-}
-
-// swagger:operation PUT /api/v1/hooks/{org}/{repo}/{hook} webhook UpdateHook
-//
-// Update a webhook for the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: hook
-// description: Number of the hook
-// required: true
-// type: integer
-// - in: body
-// name: body
-// description: Webhook payload that we expect from the user or VCS
-// required: true
-// schema:
-// "$ref": "#/definitions/Webhook"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully updated the webhook
-// schema:
-// "$ref": "#/definitions/Webhook"
-// '400':
-// description: The webhook was unable to be updated
-// schema:
-// "$ref": "#/definitions/Error"
-// '404':
-// description: The webhook was unable to be updated
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: The webhook was unable to be updated
-// schema:
-// "$ref": "#/definitions/Error"
-
-// UpdateHook represents the API handler to update
-// a webhook in the configured backend.
-func UpdateHook(c *gin.Context) {
- // capture middleware values
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
- hook := c.Param("hook")
-
- entry := fmt.Sprintf("%s/%s", r.GetFullName(), hook)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "hook": hook,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("updating hook %s", entry)
-
- // capture body from API request
- input := new(library.Hook)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for hook %s: %w", entry, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- number, err := strconv.Atoi(hook)
- if err != nil {
- retErr := fmt.Errorf("invalid hook parameter provided: %s", hook)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // send API call to capture the webhook
- h, err := database.FromContext(c).GetHook(number, r)
- if err != nil {
- retErr := fmt.Errorf("unable to get hook %s: %w", entry, err)
-
- util.HandleError(c, http.StatusNotFound, retErr)
-
- return
- }
-
- // update webhook fields if provided
- if input.GetCreated() > 0 {
- // update created if set
- h.SetCreated(input.GetCreated())
- }
-
- if len(input.GetHost()) > 0 {
- // update host if set
- h.SetHost(input.GetHost())
- }
-
- if len(input.GetEvent()) > 0 {
- // update event if set
- h.SetEvent(input.GetEvent())
- }
-
- if len(input.GetBranch()) > 0 {
- // update branch if set
- h.SetBranch(input.GetBranch())
- }
-
- if len(input.GetError()) > 0 {
- // update error if set
- h.SetError(input.GetError())
- }
-
- if len(input.GetStatus()) > 0 {
- // update status if set
- h.SetStatus(input.GetStatus())
- }
-
- if len(input.GetLink()) > 0 {
- // update link if set
- h.SetLink(input.GetLink())
- }
-
- // send API call to update the webhook
- err = database.FromContext(c).UpdateHook(h)
- if err != nil {
- retErr := fmt.Errorf("unable to update hook %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the updated user
- h, _ = database.FromContext(c).GetHook(h.GetNumber(), r)
-
- c.JSON(http.StatusOK, h)
-}
-
-// swagger:operation DELETE /api/v1/hooks/{org}/{repo}/{hook} webhook DeleteHook
-//
-// Delete a webhook for the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: hook
-// description: Number of the hook
-// required: true
-// type: integer
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully deleted the webhook
-// schema:
-// type: string
-// '400':
-// description: The webhook was unable to be deleted
-// schema:
-// "$ref": "#/definitions/Error"
-// '404':
-// description: The webhook was unable to be deleted
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: The webhook was unable to be deleted
-// schema:
-// "$ref": "#/definitions/Error"
-
-// DeleteHook represents the API handler to remove
-// a webhook from the configured backend.
-func DeleteHook(c *gin.Context) {
- // capture middleware values
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
- hook := c.Param("hook")
-
- entry := fmt.Sprintf("%s/%s", r.GetFullName(), hook)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "hook": hook,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("deleting hook %s", entry)
-
- number, err := strconv.Atoi(hook)
- if err != nil {
- retErr := fmt.Errorf("invalid hook parameter provided: %s", hook)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // send API call to capture the webhook
- h, err := database.FromContext(c).GetHook(number, r)
- if err != nil {
- retErr := fmt.Errorf("unable to get hook %s: %w", hook, err)
-
- util.HandleError(c, http.StatusNotFound, retErr)
-
- return
- }
-
- // send API call to remove the webhook
- err = database.FromContext(c).DeleteHook(h.GetID())
- if err != nil {
- retErr := fmt.Errorf("unable to delete hook %s: %w", hook, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, fmt.Sprintf("hook %s deleted", entry))
-}
diff --git a/api/hook/create.go b/api/hook/create.go
new file mode 100644
index 000000000..b33d9fdfb
--- /dev/null
+++ b/api/hook/create.go
@@ -0,0 +1,126 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/hooks/{org}/{repo} webhook CreateHook
+//
+// Create a webhook for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: body
+// name: body
+// description: Webhook payload that we expect from the user or VCS
+// required: true
+// schema:
+// "$ref": "#/definitions/Webhook"
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '201':
+// description: The webhook has been created
+// schema:
+// "$ref": "#/definitions/Webhook"
+// '400':
+// description: The webhook was unable to be created
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: The webhook was unable to be created
+// schema:
+// "$ref": "#/definitions/Error"
+
+// CreateHook represents the API handler to create
+// a webhook in the configured backend.
+func CreateHook(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("creating new hook for repo %s", r.GetFullName())
+
+ // capture body from API request
+ input := new(library.Hook)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for new hook for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // send API call to capture the last hook for the repo
+ lastHook, err := database.FromContext(c).LastHookForRepo(r)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get last hook for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // update fields in webhook object
+ input.SetRepoID(r.GetID())
+ input.SetNumber(1)
+
+ if input.GetCreated() == 0 {
+ input.SetCreated(time.Now().UTC().Unix())
+ }
+
+ if lastHook != nil {
+ input.SetNumber(
+ lastHook.GetNumber() + 1,
+ )
+ }
+
+ // send API call to create the webhook
+ h, err := database.FromContext(c).CreateHook(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create hook for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusCreated, h)
+}
diff --git a/api/hook/delete.go b/api/hook/delete.go
new file mode 100644
index 000000000..7ada1f0fb
--- /dev/null
+++ b/api/hook/delete.go
@@ -0,0 +1,115 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation DELETE /api/v1/hooks/{org}/{repo}/{hook} webhook DeleteHook
+//
+// Delete a webhook for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: hook
+// description: Number of the hook
+// required: true
+// type: integer
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully deleted the webhook
+// schema:
+// type: string
+// '400':
+// description: The webhook was unable to be deleted
+// schema:
+// "$ref": "#/definitions/Error"
+// '404':
+// description: The webhook was unable to be deleted
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: The webhook was unable to be deleted
+// schema:
+// "$ref": "#/definitions/Error"
+
+// DeleteHook represents the API handler to remove
+// a webhook from the configured backend.
+func DeleteHook(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ hook := util.PathParameter(c, "hook")
+
+ entry := fmt.Sprintf("%s/%s", r.GetFullName(), hook)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "hook": hook,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("deleting hook %s", entry)
+
+ number, err := strconv.Atoi(hook)
+ if err != nil {
+ retErr := fmt.Errorf("invalid hook parameter provided: %s", hook)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // send API call to capture the webhook
+ h, err := database.FromContext(c).GetHookForRepo(r, number)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get hook %s: %w", hook, err)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ // send API call to remove the webhook
+ err = database.FromContext(c).DeleteHook(h)
+ if err != nil {
+ retErr := fmt.Errorf("unable to delete hook %s: %w", hook, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("hook %s deleted", entry))
+}
diff --git a/api/hook/get.go b/api/hook/get.go
new file mode 100644
index 000000000..458183dcb
--- /dev/null
+++ b/api/hook/get.go
@@ -0,0 +1,101 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/hooks/{org}/{repo}/{hook} webhook GetHook
+//
+// Retrieve a webhook for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: hook
+// description: Number of the hook
+// required: true
+// type: integer
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the webhook
+// schema:
+// "$ref": "#/definitions/Webhook"
+// '400':
+// description: Unable to retrieve the webhook
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to retrieve the webhook
+// schema:
+// "$ref": "#/definitions/Error"
+
+// GetHook represents the API handler to capture a
+// webhook from the configured backend.
+func GetHook(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ hook := util.PathParameter(c, "hook")
+
+ entry := fmt.Sprintf("%s/%s", r.GetFullName(), hook)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "hook": hook,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("reading hook %s", entry)
+
+ number, err := strconv.Atoi(hook)
+ if err != nil {
+ retErr := fmt.Errorf("invalid hook parameter provided: %s", hook)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // send API call to capture the webhook
+ h, err := database.FromContext(c).GetHookForRepo(r, number)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get hook %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, h)
+}
diff --git a/api/hook/list.go b/api/hook/list.go
new file mode 100644
index 000000000..2cb6d05fe
--- /dev/null
+++ b/api/hook/list.go
@@ -0,0 +1,136 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/api"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/hooks/{org}/{repo} webhook ListHooks
+//
+// Retrieve the webhooks for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: query
+// name: page
+// description: The page of results to retrieve
+// type: integer
+// default: 1
+// - in: query
+// name: per_page
+// description: How many results per page to return
+// type: integer
+// maximum: 100
+// default: 10
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved webhooks
+// schema:
+// type: array
+// items:
+// "$ref": "#/definitions/Webhook"
+// headers:
+// X-Total-Count:
+// description: Total number of results
+// type: integer
+// Link:
+// description: see https://tools.ietf.org/html/rfc5988
+// type: string
+// '400':
+// description: Unable to retrieve webhooks
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to retrieve webhooks
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ListHooks represents the API handler to capture a list
+// of webhooks from the configured backend.
+func ListHooks(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("reading hooks for repo %s", r.GetFullName())
+
+ // capture page query parameter if present
+ page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert page query parameter for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // capture per_page query parameter if present
+ perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert per_page query parameter for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // ensure per_page isn't above or below allowed values
+ perPage = util.MaxInt(1, util.MinInt(100, perPage))
+
+ // send API call to capture the list of steps for the build
+ h, t, err := database.FromContext(c).ListHooksForRepo(r, page, perPage)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get hooks for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // create pagination object
+ pagination := api.Pagination{
+ Page: page,
+ PerPage: perPage,
+ Total: t,
+ }
+ // set pagination headers
+ pagination.SetHeaderLink(c)
+
+ c.JSON(http.StatusOK, h)
+}
diff --git a/api/hook/redeliver.go b/api/hook/redeliver.go
new file mode 100644
index 000000000..4afcb8a79
--- /dev/null
+++ b/api/hook/redeliver.go
@@ -0,0 +1,115 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/hooks/{org}/{repo}/{hook}/redeliver webhook RedeliverHook
+//
+// Redeliver a webhook from the SCM
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: hook
+// description: Number of the hook
+// required: true
+// type: integer
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully redelivered the webhook
+// schema:
+// "$ref": "#/definitions/Webhook"
+// '400':
+// description: The webhook was unable to be redelivered
+// schema:
+// "$ref": "#/definitions/Error"
+// '404':
+// description: The webhook was unable to be redelivered
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: The webhook was unable to be redelivered
+// schema:
+// "$ref": "#/definitions/Error"
+
+// RedeliverHook represents the API handler to redeliver
+// a webhook from the SCM.
+func RedeliverHook(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ hook := util.PathParameter(c, "hook")
+
+ entry := fmt.Sprintf("%s/%s", r.GetFullName(), hook)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "hook": hook,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("redelivering hook %s", entry)
+
+ number, err := strconv.Atoi(hook)
+ if err != nil {
+ retErr := fmt.Errorf("invalid hook parameter provided: %s", hook)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // send API call to capture the webhook
+ h, err := database.FromContext(c).GetHookForRepo(r, number)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get hook %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ err = scm.FromContext(c).RedeliverWebhook(c, u, r, h)
+ if err != nil {
+ retErr := fmt.Errorf("unable to redeliver hook %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("hook %s redelivered", entry))
+}
diff --git a/api/hook/update.go b/api/hook/update.go
new file mode 100644
index 000000000..2c5cdcfb7
--- /dev/null
+++ b/api/hook/update.go
@@ -0,0 +1,170 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation PUT /api/v1/hooks/{org}/{repo}/{hook} webhook UpdateHook
+//
+// Update a webhook for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: hook
+// description: Number of the hook
+// required: true
+// type: integer
+// - in: body
+// name: body
+// description: Webhook payload that we expect from the user or VCS
+// required: true
+// schema:
+// "$ref": "#/definitions/Webhook"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully updated the webhook
+// schema:
+// "$ref": "#/definitions/Webhook"
+// '400':
+// description: The webhook was unable to be updated
+// schema:
+// "$ref": "#/definitions/Error"
+// '404':
+// description: The webhook was unable to be updated
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: The webhook was unable to be updated
+// schema:
+// "$ref": "#/definitions/Error"
+
+// UpdateHook represents the API handler to update
+// a webhook in the configured backend.
+func UpdateHook(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ hook := util.PathParameter(c, "hook")
+
+ entry := fmt.Sprintf("%s/%s", r.GetFullName(), hook)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "hook": hook,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("updating hook %s", entry)
+
+ // capture body from API request
+ input := new(library.Hook)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for hook %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ number, err := strconv.Atoi(hook)
+ if err != nil {
+ retErr := fmt.Errorf("invalid hook parameter provided: %s", hook)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // send API call to capture the webhook
+ h, err := database.FromContext(c).GetHookForRepo(r, number)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get hook %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ // update webhook fields if provided
+ if input.GetCreated() > 0 {
+ // update created if set
+ h.SetCreated(input.GetCreated())
+ }
+
+ if len(input.GetHost()) > 0 {
+ // update host if set
+ h.SetHost(input.GetHost())
+ }
+
+ if len(input.GetEvent()) > 0 {
+ // update event if set
+ h.SetEvent(input.GetEvent())
+ }
+
+ if len(input.GetBranch()) > 0 {
+ // update branch if set
+ h.SetBranch(input.GetBranch())
+ }
+
+ if len(input.GetError()) > 0 {
+ // update error if set
+ h.SetError(input.GetError())
+ }
+
+ if len(input.GetStatus()) > 0 {
+ // update status if set
+ h.SetStatus(input.GetStatus())
+ }
+
+ if len(input.GetLink()) > 0 {
+ // update link if set
+ h.SetLink(input.GetLink())
+ }
+
+ // send API call to update the webhook
+ h, err = database.FromContext(c).UpdateHook(h)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update hook %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, h)
+}
diff --git a/api/log.go b/api/log.go
deleted file mode 100644
index eafc9d0db..000000000
--- a/api/log.go
+++ /dev/null
@@ -1,875 +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 api
-
-import (
- "fmt"
- "net/http"
-
- "github.com/go-vela/server/router/middleware/org"
- "github.com/go-vela/server/router/middleware/user"
-
- "github.com/go-vela/server/database"
- "github.com/go-vela/server/router/middleware/build"
- "github.com/go-vela/server/router/middleware/repo"
- "github.com/go-vela/server/router/middleware/service"
- "github.com/go-vela/server/router/middleware/step"
- "github.com/go-vela/server/util"
-
- "github.com/go-vela/types/library"
-
- "github.com/gin-gonic/gin"
- "github.com/sirupsen/logrus"
-)
-
-// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/logs builds GetBuildLogs
-//
-// Get logs for a build in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved logs for the build
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/Log"
-// '500':
-// description: Unable to retrieve logs for the build
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetBuildLogs represents the API handler to capture a
-// list of logs for a build from the configured backend.
-func GetBuildLogs(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("reading logs for build %s", entry)
-
- // send API call to capture the list of logs for the build
- l, err := database.FromContext(c).GetBuildLogs(b.GetID())
- if err != nil {
- retErr := fmt.Errorf("unable to get logs for build %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, l)
-}
-
-// nolint: lll // ignore long line length due to API path
-//
-// swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build}/services/{service}/logs services CreateServiceLogs
-//
-// Create the logs for a service
-//
-// ---
-// deprecated: true
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: path
-// name: service
-// description: ID of the service
-// required: true
-// type: integer
-// - in: body
-// name: body
-// description: Payload containing the log to create
-// required: true
-// schema:
-// "$ref": "#/definitions/Log"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '201':
-// description: Successfully created the service logs
-// schema:
-// "$ref": "#/definitions/Log"
-// '400':
-// description: Unable to create the service logs
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to create the service logs
-// schema:
-// "$ref": "#/definitions/Error"
-
-// CreateServiceLog represents the API handler to create
-// the logs for a service in the configured backend.
-//
-// nolint: dupl // ignore similar code with step
-func CreateServiceLog(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- s := service.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "service": s.GetNumber(),
- "user": u.GetName(),
- }).Infof("creating logs for service %s", entry)
-
- // capture body from API request
- input := new(library.Log)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for service %s: %w", entry, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // update fields in log object
- input.SetServiceID(s.GetID())
- input.SetBuildID(b.GetID())
- input.SetRepoID(r.GetID())
-
- // send API call to create the logs
- err = database.FromContext(c).CreateLog(input)
- if err != nil {
- retErr := fmt.Errorf("unable to create logs for service %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the created log
- l, _ := database.FromContext(c).GetServiceLog(s.GetID())
-
- c.JSON(http.StatusCreated, l)
-}
-
-// nolint: lll // ignore long line length due to API path
-//
-// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/services/{service}/logs services GetServiceLogs
-//
-// Retrieve the logs for a service
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: path
-// name: service
-// description: ID of the service
-// required: true
-// type: integer
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the service logs
-// schema:
-// "$ref": "#/definitions/Log"
-// '500':
-// description: Unable to retrieve the service logs
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetServiceLog represents the API handler to capture
-// the logs for a service from the configured backend.
-func GetServiceLog(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- s := service.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "service": s.GetNumber(),
- "user": u.GetName(),
- }).Infof("reading logs for service %s", entry)
-
- // send API call to capture the service logs
- l, err := database.FromContext(c).GetServiceLog(s.GetID())
- if err != nil {
- retErr := fmt.Errorf("unable to get logs for service %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, l)
-}
-
-// nolint: lll // ignore long line length due to API path
-//
-// swagger:operation PUT /api/v1/repos/{org}/{repo}/builds/{build}/services/{service}/logs services UpdateServiceLog
-//
-// Update the logs for a service
-//
-// ---
-// deprecated: true
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: path
-// name: service
-// description: ID of the service
-// required: true
-// type: integer
-// - in: body
-// name: body
-// description: Payload containing the log to update
-// required: true
-// schema:
-// "$ref": "#/definitions/Log"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully updated the service logs
-// schema:
-// "$ref": "#/definitions/Log"
-// '400':
-// description: Unable to updated the service logs
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to updates the service logs
-// schema:
-// "$ref": "#/definitions/Error"
-
-// UpdateServiceLog represents the API handler to update
-// the logs for a service in the configured backend.
-func UpdateServiceLog(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- s := service.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "service": s.GetNumber(),
- "user": u.GetName(),
- }).Infof("updating logs for service %s", entry)
-
- // send API call to capture the service logs
- l, err := database.FromContext(c).GetServiceLog(s.GetID())
- if err != nil {
- retErr := fmt.Errorf("unable to get logs for service %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // capture body from API request
- input := new(library.Log)
-
- err = c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for service %s: %w", entry, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // update log fields if provided
- if len(input.GetData()) > 0 {
- // update data if set
- l.SetData(input.GetData())
- }
-
- // send API call to update the log
- err = database.FromContext(c).UpdateLog(l)
- if err != nil {
- retErr := fmt.Errorf("unable to update logs for service %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the updated log
- l, _ = database.FromContext(c).GetServiceLog(s.GetID())
-
- c.JSON(http.StatusOK, l)
-}
-
-// nolint: lll // ignore long line length due to API path
-//
-// swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build}/services/{service}/logs services DeleteServiceLogs
-//
-// Delete the logs for a service
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: path
-// name: service
-// description: ID of the service
-// required: true
-// type: integer
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully deleted the service logs
-// schema:
-// type: string
-// '500':
-// description: Unable to delete the service logs
-// schema:
-// "$ref": "#/definitions/Error"
-
-// DeleteServiceLog represents the API handler to remove
-// the logs for a service from the configured backend.
-//
-// nolint: dupl // ignore similar code with step
-func DeleteServiceLog(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- s := service.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "service": s.GetNumber(),
- "user": u.GetName(),
- }).Infof("deleting logs for service %s", entry)
-
- // send API call to remove the log
- err := database.FromContext(c).DeleteLog(s.GetID())
- if err != nil {
- retErr := fmt.Errorf("unable to delete logs for service %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, fmt.Sprintf("logs deleted for service %s", entry))
-}
-
-// nolint: lll // ignore long line length due to API path
-//
-// swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step}/logs steps CreateStepLog
-//
-// Create the logs for a step
-//
-// ---
-// deprecated: true
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: path
-// name: step
-// description: Step number
-// required: true
-// type: integer
-// - in: body
-// name: body
-// description: Payload containing the log to create
-// required: true
-// schema:
-// "$ref": "#/definitions/Log"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '201':
-// description: Successfully created the logs for step
-// schema:
-// "$ref": "#/definitions/Log"
-// '400':
-// description: Unable to create the logs for a step
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to create the logs for a step
-// schema:
-// "$ref": "#/definitions/Error"
-
-// CreateStepLog represents the API handler to create
-// the logs for a step in the configured backend.
-//
-// nolint: dupl // ignore similar code with service
-func CreateStepLog(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- s := step.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "step": s.GetNumber(),
- "user": u.GetName(),
- }).Infof("creating logs for step %s", entry)
-
- // capture body from API request
- input := new(library.Log)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for step %s: %w", entry, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // update fields in log object
- input.SetStepID(s.GetID())
- input.SetBuildID(b.GetID())
- input.SetRepoID(r.GetID())
-
- // send API call to create the logs
- err = database.FromContext(c).CreateLog(input)
- if err != nil {
- retErr := fmt.Errorf("unable to create logs for step %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the created log
- l, _ := database.FromContext(c).GetStepLog(s.GetID())
-
- c.JSON(http.StatusCreated, l)
-}
-
-// nolint: lll // ignore long line length due to API path
-//
-// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step}/logs steps GetStepLog
-//
-// Retrieve the logs for a step
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: path
-// name: step
-// description: Step number
-// required: true
-// type: integer
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the logs for step
-// type: json
-// schema:
-// "$ref": "#/definitions/Log"
-// '500':
-// description: Unable to retrieve the logs for a step
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetStepLog represents the API handler to capture
-// the logs for a step from the configured backend.
-func GetStepLog(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- s := step.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "step": s.GetNumber(),
- "user": u.GetName(),
- }).Infof("reading logs for step %s", entry)
-
- // send API call to capture the step logs
- l, err := database.FromContext(c).GetStepLog(s.GetID())
- if err != nil {
- retErr := fmt.Errorf("unable to get logs for step %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, l)
-}
-
-// nolint: lll // ignore long line length due to API path
-//
-// swagger:operation PUT /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step}/logs steps UpdateStepLog
-//
-// Update the logs for a step
-//
-// ---
-// deprecated: true
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: path
-// name: step
-// description: Step number
-// required: true
-// type: integer
-// - in: body
-// name: body
-// description: Payload containing the log to update
-// required: true
-// schema:
-// "$ref": "#/definitions/Log"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully updated the logs for step
-// schema:
-// "$ref": "#/definitions/Log"
-// '400':
-// description: Unable to update the logs for a step
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to update the logs for a step
-// schema:
-// "$ref": "#/definitions/Error"
-
-// UpdateStepLog represents the API handler to update
-// the logs for a step in the configured backend.
-func UpdateStepLog(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- s := step.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "step": s.GetNumber(),
- "user": u.GetName(),
- }).Infof("updating logs for step %s", entry)
-
- // send API call to capture the step logs
- l, err := database.FromContext(c).GetStepLog(s.GetID())
- if err != nil {
- retErr := fmt.Errorf("unable to get logs for step %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // capture body from API request
- input := new(library.Log)
-
- err = c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for step %s: %v", entry, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // update log fields if provided
- if len(input.GetData()) > 0 {
- // update data if set
- l.SetData(input.GetData())
- }
-
- // send API call to update the log
- err = database.FromContext(c).UpdateLog(l)
- if err != nil {
- retErr := fmt.Errorf("unable to update logs for step %s: %v", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the updated log
- l, _ = database.FromContext(c).GetStepLog(s.GetID())
-
- c.JSON(http.StatusOK, l)
-}
-
-// nolint: lll // ignore long line length due to API path
-//
-// swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step}/logs steps DeleteStepLog
-//
-// Delete the logs for a step
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: path
-// name: step
-// description: Step number
-// required: true
-// type: integer
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully deleted the logs for the step
-// schema:
-// type: string
-// '500':
-// description: Unable to delete the logs for the step
-// schema:
-// "$ref": "#/definitions/Error"
-
-// DeleteStepLog represents the API handler to remove
-// the logs for a step from the configured backend.
-//
-// nolint: dupl // ignore similar code with service
-func DeleteStepLog(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- s := step.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "step": s.GetNumber(),
- "user": u.GetName(),
- }).Infof("deleting logs for step %s", entry)
-
- // send API call to remove the log
- err := database.FromContext(c).DeleteLog(s.GetID())
- if err != nil {
- retErr := fmt.Errorf("unable to delete logs for step %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, fmt.Sprintf("logs deleted for step %s", entry))
-}
diff --git a/api/log/create_service.go b/api/log/create_service.go
new file mode 100644
index 000000000..bf8cd4ec1
--- /dev/null
+++ b/api/log/create_service.go
@@ -0,0 +1,124 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code to step
+package log
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/service"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build}/services/{service}/logs services CreateServiceLog
+//
+// Create the logs for a service
+//
+// ---
+// deprecated: true
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: path
+// name: service
+// description: Service number
+// required: true
+// type: integer
+// - in: body
+// name: body
+// description: Payload containing the log to create
+// required: true
+// schema:
+// "$ref": "#/definitions/Log"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '201':
+// description: Successfully created the service logs
+// '400':
+// description: Unable to create the service logs
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to create the service logs
+// schema:
+// "$ref": "#/definitions/Error"
+
+// CreateServiceLog represents the API handler to create
+// the logs for a service in the configured backend.
+func CreateServiceLog(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ s := service.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "service": s.GetNumber(),
+ "user": u.GetName(),
+ }).Infof("creating logs for service %s", entry)
+
+ // capture body from API request
+ input := new(library.Log)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for service %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update fields in log object
+ input.SetServiceID(s.GetID())
+ input.SetBuildID(b.GetID())
+ input.SetRepoID(r.GetID())
+
+ // send API call to create the logs
+ err = database.FromContext(c).CreateLog(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create logs for service %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusCreated, nil)
+}
diff --git a/api/log/create_step.go b/api/log/create_step.go
new file mode 100644
index 000000000..d2abed7a9
--- /dev/null
+++ b/api/log/create_step.go
@@ -0,0 +1,124 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code to service
+package log
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/step"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step}/logs steps CreateStepLog
+//
+// Create the logs for a step
+//
+// ---
+// deprecated: true
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: path
+// name: step
+// description: Step number
+// required: true
+// type: integer
+// - in: body
+// name: body
+// description: Payload containing the log to create
+// required: true
+// schema:
+// "$ref": "#/definitions/Log"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '201':
+// description: Successfully created the logs for step
+// '400':
+// description: Unable to create the logs for a step
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to create the logs for a step
+// schema:
+// "$ref": "#/definitions/Error"
+
+// CreateStepLog represents the API handler to create
+// the logs for a step in the configured backend.
+func CreateStepLog(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ s := step.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "step": s.GetNumber(),
+ "user": u.GetName(),
+ }).Infof("creating logs for step %s", entry)
+
+ // capture body from API request
+ input := new(library.Log)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for step %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update fields in log object
+ input.SetStepID(s.GetID())
+ input.SetBuildID(b.GetID())
+ input.SetRepoID(r.GetID())
+
+ // send API call to create the logs
+ err = database.FromContext(c).CreateLog(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create logs for step %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusCreated, nil)
+}
diff --git a/api/log/delete_service.go b/api/log/delete_service.go
new file mode 100644
index 000000000..c87d87b1b
--- /dev/null
+++ b/api/log/delete_service.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.
+
+//nolint:dupl // ignore similar code with step
+package log
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/service"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build}/services/{service}/logs services DeleteServiceLog
+//
+// Delete the logs for a service
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: path
+// name: service
+// description: Service number
+// required: true
+// type: integer
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully deleted the service logs
+// schema:
+// type: string
+// '500':
+// description: Unable to delete the service logs
+// schema:
+// "$ref": "#/definitions/Error"
+
+// DeleteServiceLog represents the API handler to remove
+// the logs for a service from the configured backend.
+func DeleteServiceLog(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ s := service.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "service": s.GetNumber(),
+ "user": u.GetName(),
+ }).Infof("deleting logs for service %s", entry)
+
+ // send API call to capture the service logs
+ l, err := database.FromContext(c).GetLogForService(s)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get logs for service %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // send API call to remove the log
+ err = database.FromContext(c).DeleteLog(l)
+ if err != nil {
+ retErr := fmt.Errorf("unable to delete logs for service %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("logs deleted for service %s", entry))
+}
diff --git a/api/log/delete_step.go b/api/log/delete_step.go
new file mode 100644
index 000000000..90e7fc7ac
--- /dev/null
+++ b/api/log/delete_step.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.
+
+//nolint:dupl // ignore similar code with service
+package log
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/step"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step}/logs steps DeleteStepLog
+//
+// Delete the logs for a step
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: path
+// name: step
+// description: Step number
+// required: true
+// type: integer
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully deleted the logs for the step
+// schema:
+// type: string
+// '500':
+// description: Unable to delete the logs for the step
+// schema:
+// "$ref": "#/definitions/Error"
+
+// DeleteStepLog represents the API handler to remove
+// the logs for a step from the configured backend.
+func DeleteStepLog(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ s := step.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "step": s.GetNumber(),
+ "user": u.GetName(),
+ }).Infof("deleting logs for step %s", entry)
+
+ // send API call to capture the step logs
+ l, err := database.FromContext(c).GetLogForStep(s)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get logs for step %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // send API call to remove the log
+ err = database.FromContext(c).DeleteLog(l)
+ if err != nil {
+ retErr := fmt.Errorf("unable to delete logs for step %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("logs deleted for step %s", entry))
+}
diff --git a/api/log/doc.go b/api/log/doc.go
new file mode 100644
index 000000000..9f619b01c
--- /dev/null
+++ b/api/log/doc.go
@@ -0,0 +1,10 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+// Package log provides the log handlers for the Vela API.
+//
+// Usage:
+//
+// import "github.com/go-vela/server/api/log"
+package log
diff --git a/api/log/get_service.go b/api/log/get_service.go
new file mode 100644
index 000000000..2a15ad7e5
--- /dev/null
+++ b/api/log/get_service.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.
+
+//nolint:dupl // ignore similar code with step
+package log
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/service"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/services/{service}/logs services GetServiceLog
+//
+// Retrieve the logs for a service
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: path
+// name: service
+// description: Service number
+// required: true
+// type: integer
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the service logs
+// schema:
+// "$ref": "#/definitions/Log"
+// '500':
+// description: Unable to retrieve the service logs
+// schema:
+// "$ref": "#/definitions/Error"
+
+// GetServiceLog represents the API handler to capture
+// the logs for a service from the configured backend.
+func GetServiceLog(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ s := service.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "service": s.GetNumber(),
+ "user": u.GetName(),
+ }).Infof("reading logs for service %s", entry)
+
+ // send API call to capture the service logs
+ l, err := database.FromContext(c).GetLogForService(s)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get logs for service %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, l)
+}
diff --git a/api/log/get_step.go b/api/log/get_step.go
new file mode 100644
index 000000000..39859f1dd
--- /dev/null
+++ b/api/log/get_step.go
@@ -0,0 +1,98 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code with service
+package log
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/step"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step}/logs steps GetStepLog
+//
+// Retrieve the logs for a step
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: path
+// name: step
+// description: Step number
+// required: true
+// type: integer
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the logs for step
+// type: json
+// schema:
+// "$ref": "#/definitions/Log"
+// '500':
+// description: Unable to retrieve the logs for a step
+// schema:
+// "$ref": "#/definitions/Error"
+
+// GetStepLog represents the API handler to capture
+// the logs for a step from the configured backend.
+func GetStepLog(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ s := step.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "step": s.GetNumber(),
+ "user": u.GetName(),
+ }).Infof("reading logs for step %s", entry)
+
+ // send API call to capture the step logs
+ l, err := database.FromContext(c).GetLogForStep(s)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get logs for step %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, l)
+}
diff --git a/api/log/list_build.go b/api/log/list_build.go
new file mode 100644
index 000000000..fe93947d0
--- /dev/null
+++ b/api/log/list_build.go
@@ -0,0 +1,134 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/api"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/logs builds ListLogsForBuild
+//
+// List logs for a build in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: query
+// name: page
+// description: The page of results to retrieve
+// type: integer
+// default: 1
+// - in: query
+// name: per_page
+// description: How many results per page to return
+// type: integer
+// maximum: 100
+// default: 10
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved logs for the build
+// schema:
+// type: array
+// items:
+// "$ref": "#/definitions/Log"
+// '500':
+// description: Unable to retrieve logs for the build
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ListLogsForBuild represents the API handler to capture a
+// list of logs for a build from the configured backend.
+func ListLogsForBuild(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("listing logs for build %s", entry)
+
+ // capture page query parameter if present
+ page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
+ if err != nil {
+ //nolint:lll // ignore long line length due to error message
+ retErr := fmt.Errorf("unable to convert page query parameter for build %s: %w", entry, err)
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // capture per_page query parameter if present
+ perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert per_page query parameter for build %s: %w", entry, err)
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // ensure per_page isn't above or below allowed values
+ perPage = util.MaxInt(1, util.MinInt(100, perPage))
+
+ // send API call to capture the list of logs for the build
+ l, t, err := database.FromContext(c).ListLogsForBuild(b, page, perPage)
+ if err != nil {
+ retErr := fmt.Errorf("unable to list logs for build %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // create pagination object
+ pagination := api.Pagination{
+ Page: page,
+ PerPage: perPage,
+ Total: t,
+ }
+ // set pagination headers
+ pagination.SetHeaderLink(c)
+
+ c.JSON(http.StatusOK, l)
+}
diff --git a/api/log/update_service.go b/api/log/update_service.go
new file mode 100644
index 000000000..92d96e1f1
--- /dev/null
+++ b/api/log/update_service.go
@@ -0,0 +1,137 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code with step
+package log
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/service"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation PUT /api/v1/repos/{org}/{repo}/builds/{build}/services/{service}/logs services UpdateServiceLog
+//
+// Update the logs for a service
+//
+// ---
+// deprecated: true
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: path
+// name: service
+// description: Service number
+// required: true
+// type: integer
+// - in: body
+// name: body
+// description: Payload containing the log to update
+// required: true
+// schema:
+// "$ref": "#/definitions/Log"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully updated the service logs
+// schema:
+// "$ref": "#/definitions/Log"
+// '400':
+// description: Unable to updated the service logs
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to updates the service logs
+// schema:
+// "$ref": "#/definitions/Error"
+
+// UpdateServiceLog represents the API handler to update
+// the logs for a service in the configured backend.
+func UpdateServiceLog(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ s := service.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "service": s.GetNumber(),
+ "user": u.GetName(),
+ }).Infof("updating logs for service %s", entry)
+
+ // send API call to capture the service logs
+ l, err := database.FromContext(c).GetLogForService(s)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get logs for service %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // capture body from API request
+ input := new(library.Log)
+
+ err = c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for service %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update log fields if provided
+ if len(input.GetData()) > 0 {
+ // update data if set
+ l.SetData(input.GetData())
+ }
+
+ // send API call to update the log
+ err = database.FromContext(c).UpdateLog(l)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update logs for service %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, nil)
+}
diff --git a/api/log/update_step.go b/api/log/update_step.go
new file mode 100644
index 000000000..14a642d57
--- /dev/null
+++ b/api/log/update_step.go
@@ -0,0 +1,137 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code with service
+package log
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/step"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation PUT /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step}/logs steps UpdateStepLog
+//
+// Update the logs for a step
+//
+// ---
+// deprecated: true
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: path
+// name: step
+// description: Step number
+// required: true
+// type: integer
+// - in: body
+// name: body
+// description: Payload containing the log to update
+// required: true
+// schema:
+// "$ref": "#/definitions/Log"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully updated the logs for step
+// schema:
+// "$ref": "#/definitions/Log"
+// '400':
+// description: Unable to update the logs for a step
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to update the logs for a step
+// schema:
+// "$ref": "#/definitions/Error"
+
+// UpdateStepLog represents the API handler to update
+// the logs for a step in the configured backend.
+func UpdateStepLog(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ s := step.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "step": s.GetNumber(),
+ "user": u.GetName(),
+ }).Infof("updating logs for step %s", entry)
+
+ // send API call to capture the step logs
+ l, err := database.FromContext(c).GetLogForStep(s)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get logs for step %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // capture body from API request
+ input := new(library.Log)
+
+ err = c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for step %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update log fields if provided
+ if len(input.GetData()) > 0 {
+ // update data if set
+ l.SetData(input.GetData())
+ }
+
+ // send API call to update the log
+ err = database.FromContext(c).UpdateLog(l)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update logs for step %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, nil)
+}
diff --git a/api/metrics.go b/api/metrics.go
index 59a5f5f37..08994e65c 100644
--- a/api/metrics.go
+++ b/api/metrics.go
@@ -8,15 +8,69 @@ import (
"net/http"
"time"
- "github.com/go-vela/server/database"
-
"github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/queue"
+ "github.com/go-vela/types/constants"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/sirupsen/logrus"
)
+// MetricsQueryParameters holds query parameter information pertaining to requested metrics.
+type MetricsQueryParameters struct {
+ // UserCount represents total platform users
+ UserCount bool `form:"user_count"`
+ // RepoCount represents total platform repos
+ RepoCount bool `form:"repo_count"`
+
+ // BuildCount represents total number of builds
+ BuildCount bool `form:"build_count"`
+ // RunningBuildCount represents total number of builds with status==running
+ RunningBuildCount bool `form:"running_build_count"`
+ // PendingBuildCount represents total number of builds with status==pending
+ PendingBuildCount bool `form:"pending_build_count"`
+ // QueuedBuildCount represents total number of builds currently in the queue
+ QueuedBuildCount bool `form:"queued_build_count"`
+ // FailureBuildCount represents total number of builds with status==failure
+ FailureBuildCount bool `form:"failure_build_count"`
+ // KilledBuildCount represents total number of builds with status==killed
+ KilledBuildCount bool `form:"killed_build_count"`
+ // SuccessBuildCount represents total number of builds with status==success
+ SuccessBuildCount bool `form:"success_build_count"`
+ // ErrorBuildCount represents total number of builds with status==error
+ ErrorBuildCount bool `form:"error_build_count"`
+
+ // StepImageCount represents total number of step images
+ StepImageCount bool `form:"step_image_count"`
+ // StepStatusCount represents total number of step statuses
+ StepStatusCount bool `form:"step_status_count"`
+ // ServiceImageCount represents total number of service images
+ ServiceImageCount bool `form:"service_image_count"`
+ // ServiceStatusCount represents total number of service statuses
+ ServiceStatusCount bool `form:"service_status_count"`
+
+ // WorkerBuildLimit represents total worker build limit
+ WorkerBuildLimit bool `form:"worker_build_limit"`
+ // ActiveWorkerCount represents total number of active workers
+ ActiveWorkerCount bool `form:"active_worker_count"`
+ // InactiveWorkerCount represents total number of inactive workers
+ InactiveWorkerCount bool `form:"inactive_worker_count"`
+
+ // IdleWorkerCount represents total number of workers with a status of idle
+ // where worker RunningBuildIDs.length = 0
+ IdleWorkerCount bool `form:"idle_worker_count"`
+ // AvailableWorkerCount represents total number of workers with a status of available,
+ // where worker RunningBuildIDs.length > 0 and < worker BuildLimit
+ AvailableWorkerCount bool `form:"available_worker_count"`
+ // BusyWorkerCount represents total number of workers with a status of busy,
+ // where worker BuildLimit == worker RunningBuildIDs.length
+ BusyWorkerCount bool `form:"busy_worker_count"`
+ // ErrorWorkerCount represents total number of workers with a status of error
+ ErrorWorkerCount bool `form:"error_worker_count"`
+}
+
// predefine Prometheus metrics else they will be regenerated
// each function call which will throw error:
// "duplicate metrics collector registration attempted".
@@ -54,6 +108,111 @@ var (
// produces:
// - text/plain
// parameters:
+// - in: query
+// name: user_count
+// description: Indicates a request for user count
+// type: boolean
+// default: false
+// - in: query
+// name: repo_count
+// description: Indicates a request for repo count
+// type: boolean
+// default: false
+// - in: query
+// name: build_count
+// description: Indicates a request for build count
+// type: boolean
+// default: false
+// - in: query
+// name: running_build_count
+// description: Indicates a request for running build count
+// type: boolean
+// default: false
+// - in: query
+// name: pending_build_count
+// description: Indicates a request for pending build count
+// type: boolean
+// default: false
+// - in: query
+// name: queued_build_count
+// description: Indicates a request for queued build count
+// type: boolean
+// default: false
+// - in: query
+// name: failure_build_count
+// description: Indicates a request for failure build count
+// type: boolean
+// default: false
+// - in: query
+// name: killed_build_count
+// description: Indicates a request for killed build count
+// type: boolean
+// default: false
+// - in: query
+// name: success_build_count
+// description: Indicates a request for success build count
+// type: boolean
+// default: false
+// - in: query
+// name: error_build_count
+// description: Indicates a request for error build count
+// type: boolean
+// default: false
+// - in: query
+// name: step_image_count
+// description: Indicates a request for step image count
+// type: boolean
+// default: false
+// - in: query
+// name: step_status_count
+// description: Indicates a request for step status count
+// type: boolean
+// default: false
+// - in: query
+// name: service_image_count
+// description: Indicates a request for service image count
+// type: boolean
+// default: false
+// - in: query
+// name: service_status_count
+// description: Indicates a request for service status count
+// type: boolean
+// default: false
+// - in: query
+// name: worker_build_limit
+// description: Indicates a request for total worker build limit
+// type: boolean
+// default: false
+// - in: query
+// name: active_worker_count
+// description: Indicates a request for active worker count
+// type: boolean
+// default: false
+// - in: query
+// name: inactive_worker_count
+// description: Indicates a request for inactive worker count
+// type: boolean
+// default: false
+// - in: query
+// name: idle_worker_count
+// description: Indicates a request for idle worker count
+// type: boolean
+// default: false
+// - in: query
+// name: available_worker_count
+// description: Indicates a request for available worker count
+// type: boolean
+// default: false
+// - in: query
+// name: busy_worker_count
+// description: Indicates a request for busy worker count
+// type: boolean
+// default: false
+// - in: query
+// name: error_worker_count
+// description: Indicates a request for error worker count
+// type: boolean
+// default: false
// responses:
// '200':
// description: Successfully retrieved the Vela metrics
@@ -73,142 +232,266 @@ func CustomMetrics(c *gin.Context) {
// helper function to get the totals of resource types.
//
-// nolint: funlen // ignore function length due to comments
+//nolint:funlen,gocyclo // ignore function length and cyclomatic complexity
func recordGauges(c *gin.Context) {
- // send API call to capture the total number of users
- u, err := database.FromContext(c).GetUserCount()
- if err != nil {
- logrus.Errorf("unable to get count of all users: %v", err)
- }
+ // capture middleware values
+ ctx := c.Request.Context()
- // send API call to capture the total number of repos
- r, err := database.FromContext(c).GetRepoCount()
+ // variable to store query parameters
+ q := MetricsQueryParameters{}
+
+ // take incoming request and bind query parameters
+ err := c.ShouldBindQuery(&q)
if err != nil {
- logrus.Errorf("unable to get count of all repos: %v", err)
+ logrus.Errorf("unable to get bind query parameters: %v", err)
+ } // continue execution with parameters defaulted to false
+
+ // get each metric separately based on request query parameters
+ // user_count
+ if q.UserCount {
+ // send API call to capture the total number of users
+ u, err := database.FromContext(c).CountUsers()
+ if err != nil {
+ logrus.Errorf("unable to get count of all users: %v", err)
+ }
+ // add platform metrics
+ totals.WithLabelValues("platform", "count", "users").Set(float64(u))
}
- // send API call to capture the total number of builds
- b, err := database.FromContext(c).GetBuildCount()
- if err != nil {
- logrus.Errorf("unable to get count of all builds: %v", err)
+ // repo_count
+ if q.RepoCount {
+ // send API call to capture the total number of repos
+ r, err := database.FromContext(c).CountRepos(ctx)
+ if err != nil {
+ logrus.Errorf("unable to get count of all repos: %v", err)
+ }
+ // add platform metrics
+ totals.WithLabelValues("platform", "count", "repos").Set(float64(r))
}
- // send API call to capture the total number of running builds
- bRun, err := database.FromContext(c).GetBuildCountByStatus("running")
- if err != nil {
- logrus.Errorf("unable to get count of all running builds: %v", err)
+ // build_count
+ if q.BuildCount {
+ // send API call to capture the total number of builds
+ b, err := database.FromContext(c).CountBuilds(ctx)
+ if err != nil {
+ logrus.Errorf("unable to get count of all builds: %v", err)
+ }
+ // add platform metrics
+ totals.WithLabelValues("platform", "count", "builds").Set(float64(b))
}
- // send API call to capture the total number of pending builds
- bPen, err := database.FromContext(c).GetBuildCountByStatus("pending")
- if err != nil {
- logrus.Errorf("unable to get count of all pending builds: %v", err)
+ // running_build_count
+ if q.RunningBuildCount {
+ // send API call to capture the total number of running builds
+ bRun, err := database.FromContext(c).CountBuildsForStatus(ctx, "running", nil)
+ if err != nil {
+ logrus.Errorf("unable to get count of all running builds: %v", err)
+ }
+ // add build metrics
+ totals.WithLabelValues("build", "status", "running").Set(float64(bRun))
}
- // send API call to capture the total number of failure builds
- bFail, err := database.FromContext(c).GetBuildCountByStatus("failure")
- if err != nil {
- logrus.Errorf("unable to get count of all failure builds: %v", err)
+ // pending_build_count
+ if q.PendingBuildCount {
+ // send API call to capture the total number of pending builds
+ bPen, err := database.FromContext(c).CountBuildsForStatus(ctx, "pending", nil)
+ if err != nil {
+ logrus.Errorf("unable to get count of all pending builds: %v", err)
+ }
+ // add build metrics
+ totals.WithLabelValues("build", "status", "pending").Set(float64(bPen))
}
- // send API call to capture the total number of killed builds
- bKill, err := database.FromContext(c).GetBuildCountByStatus("killed")
- if err != nil {
- logrus.Errorf("unable to get count of all killed builds: %v", err)
+ // queued_build_count
+ if q.QueuedBuildCount {
+ // send API call to capture the total number of queued builds
+ t, err := queue.FromContext(c).Length(c)
+ if err != nil {
+ logrus.Errorf("unable to get count of all queued builds: %v", err)
+ }
+
+ totals.WithLabelValues("build", "status", "queued").Set(float64(t))
}
- // send API call to capture the total number of success builds
- bSucc, err := database.FromContext(c).GetBuildCountByStatus("success")
- if err != nil {
- logrus.Errorf("unable to get count of all success builds: %v", err)
+ // failure_build_count
+ if q.FailureBuildCount {
+ // send API call to capture the total number of failure builds
+ bFail, err := database.FromContext(c).CountBuildsForStatus(ctx, "failure", nil)
+ if err != nil {
+ logrus.Errorf("unable to get count of all failure builds: %v", err)
+ }
+ // add build metrics
+ totals.WithLabelValues("build", "status", "failed").Set(float64(bFail))
}
- // send API call to capture the total number of error builds
- bErr, err := database.FromContext(c).GetBuildCountByStatus("error")
- if err != nil {
- logrus.Errorf("unable to get count of all error builds: %v", err)
+ // killed_build_count
+ if q.KilledBuildCount {
+ // send API call to capture the total number of killed builds
+ bKill, err := database.FromContext(c).CountBuildsForStatus(ctx, "killed", nil)
+ if err != nil {
+ logrus.Errorf("unable to get count of all killed builds: %v", err)
+ }
+ // add build metrics
+ totals.WithLabelValues("build", "status", "killed").Set(float64(bKill))
}
- // send API call to capture the total number of step images
- stepImageMap, err := database.FromContext(c).GetStepImageCount()
- if err != nil {
- logrus.Errorf("unable to get count of all step images: %v", err)
+ // success_build_count
+ if q.SuccessBuildCount {
+ // send API call to capture the total number of success builds
+ bSucc, err := database.FromContext(c).CountBuildsForStatus(ctx, "success", nil)
+ if err != nil {
+ logrus.Errorf("unable to get count of all success builds: %v", err)
+ }
+ // add build metrics
+ totals.WithLabelValues("build", "status", "success").Set(float64(bSucc))
}
- // send API call to capture the total number of step statuses
- stepStatusMap, err := database.FromContext(c).GetStepStatusCount()
- if err != nil {
- logrus.Errorf("unable to get count of all step statuses: %v", err)
+ // error_build_count
+ if q.ErrorBuildCount {
+ // send API call to capture the total number of error builds
+ bErr, err := database.FromContext(c).CountBuildsForStatus(ctx, "error", nil)
+ if err != nil {
+ logrus.Errorf("unable to get count of all error builds: %v", err)
+ }
+ // add build metrics
+ totals.WithLabelValues("build", "status", "error").Set(float64(bErr))
}
- // send API call to capture the total number of service images
- serviceImageMap, err := database.FromContext(c).GetServiceImageCount()
- if err != nil {
- logrus.Errorf("unable to get count of all service images: %v", err)
+ // step_image_count
+ if q.StepImageCount {
+ // send API call to capture the total number of step images
+ stepImageMap, err := database.FromContext(c).ListStepImageCount()
+ if err != nil {
+ logrus.Errorf("unable to get count of all step images: %v", err)
+ }
+ // add step image metrics
+ for image, count := range stepImageMap {
+ stepImages.WithLabelValues(image).Set(count)
+ }
}
- // send API call to capture the total number of service statuses
- serviceStatusMap, err := database.FromContext(c).GetServiceStatusCount()
- if err != nil {
- logrus.Errorf("unable to get count of all service statuses: %v", err)
+ // step_status_count
+ if q.StepStatusCount {
+ // send API call to capture the total number of step statuses
+ stepStatusMap, err := database.FromContext(c).ListStepStatusCount()
+ if err != nil {
+ logrus.Errorf("unable to get count of all step statuses: %v", err)
+ }
+ // add step status metrics
+ for status, count := range stepStatusMap {
+ totals.WithLabelValues("steps", "status", status).Set(count)
+ }
}
- // send API call to capture the workers
- workers, err := database.FromContext(c).GetWorkerList()
- if err != nil {
- logrus.Errorf("unable to get workers: %v", err)
+ // service_image_count
+ if q.ServiceImageCount {
+ // send API call to capture the total number of service images
+ serviceImageMap, err := database.FromContext(c).ListServiceImageCount()
+ if err != nil {
+ logrus.Errorf("unable to get count of all service images: %v", err)
+ }
+ // add service image metrics
+ for image, count := range serviceImageMap {
+ serviceImages.WithLabelValues(image).Set(count)
+ }
}
- // Add platform metrics
- totals.WithLabelValues("platform", "count", "users").Set(float64(u))
- totals.WithLabelValues("platform", "count", "repos").Set(float64(r))
- totals.WithLabelValues("platform", "count", "builds").Set(float64(b))
-
- // Add build metrics
- totals.WithLabelValues("build", "status", "running").Set(float64(bRun))
- totals.WithLabelValues("build", "status", "pending").Set(float64(bPen))
- totals.WithLabelValues("build", "status", "failed").Set(float64(bFail))
- totals.WithLabelValues("build", "status", "killed").Set(float64(bKill))
- totals.WithLabelValues("build", "status", "success").Set(float64(bSucc))
- totals.WithLabelValues("build", "status", "error").Set(float64(bErr))
-
- // Add worker metrics
- var buildLimit int64
- var activeWorkers int64
- var inactiveWorkers int64
- // get the unix time from worker_active_interval ago
- before := time.Now().UTC().Add(-c.Value("worker_active_interval").(time.Duration)).Unix()
- for _, worker := range workers {
- // check if the worker checked in within the last worker_active_interval
- if worker.GetLastCheckedIn() >= before {
- buildLimit += worker.GetBuildLimit()
- activeWorkers++
- } else {
- inactiveWorkers++
+ // service_status_count
+ if q.ServiceStatusCount {
+ // send API call to capture the total number of service statuses
+ serviceStatusMap, err := database.FromContext(c).ListServiceStatusCount()
+ if err != nil {
+ logrus.Errorf("unable to get count of all service statuses: %v", err)
+ }
+ // add service status metrics
+ for status, count := range serviceStatusMap {
+ totals.WithLabelValues("services", "status", status).Set(count)
}
}
- totals.WithLabelValues("worker", "sum", "build_limit").Set(float64(buildLimit))
- totals.WithLabelValues("worker", "count", "active").Set(float64(activeWorkers))
- totals.WithLabelValues("worker", "count", "inactive").Set(float64(inactiveWorkers))
+ // add worker metrics
+ var (
+ buildLimit int64
+ activeWorkers int64
+ inactiveWorkers int64
+ idleWorkers int64
+ availableWorkers int64
+ busyWorkers int64
+ errorWorkers int64
+ )
- // Add step status metrics
- for status, count := range stepStatusMap {
- totals.WithLabelValues("steps", "status", status).Set(count)
- }
+ // get worker metrics based on request query parameters
+ // worker_build_limit, active_worker_count, inactive_worker_count, idle_worker_count, available_worker_count, busy_worker_count, error_worker_count
+ if q.WorkerBuildLimit || q.ActiveWorkerCount || q.InactiveWorkerCount || q.IdleWorkerCount || q.AvailableWorkerCount || q.BusyWorkerCount || q.ErrorWorkerCount {
+ // send API call to capture the workers
+ workers, err := database.FromContext(c).ListWorkers()
+ if err != nil {
+ logrus.Errorf("unable to get workers: %v", err)
+ }
- // Add service status metrics
- for status, count := range serviceStatusMap {
- totals.WithLabelValues("services", "status", status).Set(count)
- }
+ // get the unix time from worker_active_interval ago
+ before := time.Now().UTC().Add(-c.Value("worker_active_interval").(time.Duration)).Unix()
+
+ // active, inactive counts
+ // idle, available, busy, error counts
+ for _, worker := range workers {
+ // check if the worker checked in within the last worker_active_interval
+ if worker.GetLastCheckedIn() >= before {
+ buildLimit += worker.GetBuildLimit()
+ activeWorkers++
+ } else {
+ inactiveWorkers++
+ }
+ // check if the worker checked in within the last worker_active_interval
+ if worker.GetLastCheckedIn() >= before {
+
+ switch worker.GetStatus() {
+ case constants.WorkerStatusIdle:
+ idleWorkers++
+ case constants.WorkerStatusAvailable:
+ availableWorkers++
+ case constants.WorkerStatusBusy:
+ busyWorkers++
+ case constants.WorkerStatusError:
+ errorWorkers++
+ }
+ }
+ }
- // Add step image metrics
- for image, count := range stepImageMap {
- stepImages.WithLabelValues(image).Set(count)
- }
+ // apply metrics based on request query parameters
+ // worker_build_limit
+ if q.WorkerBuildLimit {
+ totals.WithLabelValues("worker", "sum", "build_limit").Set(float64(buildLimit))
+ }
+
+ // active_worker_count
+ if q.ActiveWorkerCount {
+ totals.WithLabelValues("worker", "count", "active").Set(float64(activeWorkers))
+ }
+
+ // inactive_worker_count
+ if q.InactiveWorkerCount {
+ totals.WithLabelValues("worker", "count", "inactive").Set(float64(inactiveWorkers))
+ }
- // Add service image metrics
- for image, count := range serviceImageMap {
- serviceImages.WithLabelValues(image).Set(count)
+ // idle_worker_count
+ if q.IdleWorkerCount {
+ totals.WithLabelValues("worker", "count", "idle").Set(float64(idleWorkers))
+ }
+
+ // available_worker_count
+ if q.AvailableWorkerCount {
+ totals.WithLabelValues("worker", "count", "available").Set(float64(availableWorkers))
+ }
+
+ // busy_worker_count
+ if q.BusyWorkerCount {
+ totals.WithLabelValues("worker", "count", "busy").Set(float64(busyWorkers))
+ }
+
+ // error_worker_count
+ if q.ErrorWorkerCount {
+ totals.WithLabelValues("worker", "count", "error").Set(float64(errorWorkers))
+ }
}
}
diff --git a/api/pagination.go b/api/pagination.go
index e7261d563..a63af8214 100644
--- a/api/pagination.go
+++ b/api/pagination.go
@@ -70,7 +70,6 @@ func (p *Pagination) SetHeaderLink(c *gin.Context) {
l = append(l, ls)
}
- // nolint: gomnd // ignore magic number
c.Header("X-Total-Count", strconv.FormatInt(p.Total, 10))
c.Header("Link", strings.Join(l, ", "))
}
@@ -121,7 +120,7 @@ func (p *Pagination) TotalPages() int {
// resolveScheme is a helper to determine the protocol scheme
// c.Request.URL.Scheme does not seem to reliably provide this.
//
-// nolint: goconst // ignore making constant for https
+//nolint:goconst // ignore making constant for https
func resolveScheme(r *http.Request) string {
switch {
case r.Header.Get("X-Forwarded-Proto") == "https":
diff --git a/api/pipeline.go b/api/pipeline.go
deleted file mode 100644
index fdae72790..000000000
--- a/api/pipeline.go
+++ /dev/null
@@ -1,567 +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 api
-
-import (
- "fmt"
- "net/http"
- "strconv"
- "strings"
-
- "github.com/gin-gonic/gin"
- "github.com/go-vela/server/compiler"
- "github.com/go-vela/server/compiler/registry/github"
- "github.com/go-vela/server/database"
- "github.com/go-vela/server/router/middleware/org"
- "github.com/go-vela/server/router/middleware/repo"
- "github.com/go-vela/server/router/middleware/user"
- "github.com/go-vela/server/scm"
- "github.com/go-vela/server/util"
- "github.com/go-vela/types"
- "github.com/go-vela/types/library"
- "github.com/go-vela/types/yaml"
- "github.com/sirupsen/logrus"
-)
-
-const (
- outputJSON = "json"
- outputYAML = "yaml"
-)
-
-// swagger:operation GET /api/v1/pipelines/{org}/{repo} pipelines GetPipeline
-//
-// Get a pipeline configuration from the source provider
-//
-// ---
-// produces:
-// - application/x-yaml
-// - application/json
-// parameters:
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: query
-// name: ref
-// description: Ref for retrieving pipeline configuration file
-// type: string
-// - in: query
-// name: output
-// description: Output string for specifying output format
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the pipeline
-// schema:
-// "$ref": "#/definitions/PipelineBuild"
-// '400':
-// description: Unable to retrieve the pipeline configuration templates
-// schema:
-// "$ref": "#/definitions/Error"
-// '404':
-// description: Unable to retrieve the pipeline configuration templates
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetPipeline represents the API handler to capture a
-// pipeline configuration for a repo from the the source provider.
-func GetPipeline(ctx *gin.Context) {
- // capture middleware values
- o := org.Retrieve(ctx)
- r := repo.Retrieve(ctx)
- u := user.Retrieve(ctx)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("reading pipeline for repo %s", r.GetFullName())
-
- pipeline, _, err := getUnprocessedPipeline(ctx)
- if err != nil {
- util.HandleError(ctx, http.StatusBadRequest, err)
- return
- }
-
- writeOutput(ctx, pipeline)
-}
-
-// swagger:operation GET /api/v1/pipelines/{org}/{repo}/templates pipelines GetTemplates
-//
-// Get a map of templates utilized by a pipeline configuration from the source provider
-//
-// ---
-// produces:
-// - application/x-yaml
-// - application/json
-// parameters:
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: query
-// name: ref
-// description: Ref for retrieving pipeline configuration file
-// type: string
-// - in: query
-// name: output
-// description: Output string for specifying output format
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the map of pipeline templates
-// schema:
-// "$ref": "#/definitions/Template"
-// '400':
-// description: Unable to retrieve the pipeline configuration templates
-// schema:
-// "$ref": "#/definitions/Error"
-// '404':
-// description: Unable to retrieve the pipeline configuration templates
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetTemplates represents the API handler to capture a
-// map of templates utilized by a pipeline configuration.
-func GetTemplates(ctx *gin.Context) {
- // capture middleware values
- o := org.Retrieve(ctx)
- r := repo.Retrieve(ctx)
- u := user.Retrieve(ctx)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("reading templates from pipeline for repo %s", r.GetFullName())
-
- pipeline, _, err := getUnprocessedPipeline(ctx)
- if err != nil {
- util.HandleError(ctx, http.StatusBadRequest, err)
- return
- }
-
- // create map of templates for response body
- templates, err := getTemplateLinks(ctx, pipeline.Templates)
- if err != nil {
- retErr := fmt.Errorf("unable to set template links for %s: %w", repoName(ctx), err)
- util.HandleError(ctx, http.StatusBadRequest, retErr)
- return
- }
-
- writeOutput(ctx, templates)
-}
-
-// swagger:operation POST /api/v1/pipelines/{org}/{repo}/expand pipelines ExpandPipeline
-//
-// Get and expand a pipeline configuration from the source provider
-//
-// ---
-// produces:
-// - application/x-yaml
-// - application/json
-// parameters:
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: query
-// name: ref
-// description: Ref for retrieving pipeline configuration file
-// type: string
-// - in: query
-// name: output
-// description: Output string for specifying output format
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved and expanded the pipeline
-// type: json
-// schema:
-// "$ref": "#/definitions/PipelineBuild"
-// '400':
-// description: Unable to expand the pipeline configuration
-// schema:
-// "$ref": "#/definitions/Error"
-// '404':
-// description: Unable to retrieve the pipeline configuration
-// schema:
-// "$ref": "#/definitions/Error"
-
-// ExpandPipeline represents the API handler to capture and
-// expand a pipeline configuration.
-func ExpandPipeline(ctx *gin.Context) {
- // capture middleware values
- o := org.Retrieve(ctx)
- r := repo.Retrieve(ctx)
- u := user.Retrieve(ctx)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("expanding templates from pipeline for repo %s", r.GetFullName())
-
- pipeline, comp, err := getUnprocessedPipeline(ctx)
- if err != nil {
- util.HandleError(ctx, http.StatusBadRequest, err)
- return
- }
-
- if err := expandPipeline(ctx, pipeline, comp, false); err != nil {
- util.HandleError(ctx, http.StatusBadRequest, err)
- return
- }
-
- writeOutput(ctx, pipeline)
-}
-
-// swagger:operation POST /api/v1/pipelines/{org}/{repo}/validate pipelines ValidatePipeline
-//
-// Get, expand and validate a pipeline configuration from the source provider
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: query
-// name: ref
-// description: Ref for retrieving pipeline configuration file
-// type: string
-// - in: query
-// name: output
-// description: Output string for specifying output format
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved, expanded and validated the pipeline
-// schema:
-// type: string
-// '400':
-// description: Unable to validate the pipeline configuration
-// schema:
-// "$ref": "#/definitions/Error"
-// '404':
-// description: Unable to retrieve the pipeline configuration
-// schema:
-// "$ref": "#/definitions/Error"
-
-// ValidatePipeline represents the API handler to capture, expand and
-// validate a pipeline configuration.
-func ValidatePipeline(ctx *gin.Context) {
- // capture middleware values
- o := org.Retrieve(ctx)
- r := repo.Retrieve(ctx)
- u := user.Retrieve(ctx)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("validating pipeline for repo %s", r.GetFullName())
-
- pipeline, comp, err := getUnprocessedPipeline(ctx)
- if err != nil {
- util.HandleError(ctx, http.StatusBadRequest, err)
- return
- }
-
- // check optional template query parameter
- if ok, _ := strconv.ParseBool(ctx.DefaultQuery("template", "true")); ok {
- if err := expandPipeline(ctx, pipeline, comp, false); err != nil {
- util.HandleError(ctx, http.StatusBadRequest, err)
- return
- }
- }
-
- // validate the yaml configuration
- if err = comp.Validate(pipeline); err != nil {
- retErr := fmt.Errorf("unable to validate pipeline configuration for %s: %w", repoName(ctx), err)
- util.HandleError(ctx, http.StatusBadRequest, retErr)
- return
- }
-
- writeOutput(ctx, pipeline)
-}
-
-// swagger:operation POST /api/v1/pipelines/{org}/{repo}/compile pipelines CompilePipeline
-//
-// Get, expand and compile a pipeline configuration from the source provider
-//
-// ---
-// produces:
-// - application/x-yaml
-// - application/json
-// parameters:
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: query
-// name: ref
-// description: Ref for retrieving pipeline configuration file
-// type: string
-// - in: query
-// name: output
-// description: Output string for specifying output format
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved and compiled the pipeline
-// schema:
-// "$ref": "#/definitions/PipelineBuild"
-// '400':
-// description: Unable to validate the pipeline configuration
-// schema:
-// "$ref": "#/definitions/Error"
-// '404':
-// description: Unable to retrieve the pipeline configuration
-// schema:
-// "$ref": "#/definitions/Error"
-
-// CompilePipeline represents the API handler to capture,
-// expand and compile a pipeline configuration.
-//
-func CompilePipeline(ctx *gin.Context) {
- // capture middleware values
- o := org.Retrieve(ctx)
- r := repo.Retrieve(ctx)
- u := user.Retrieve(ctx)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("compiling pipeline for repo %s", r.GetFullName())
-
- pipeline, comp, err := getUnprocessedPipeline(ctx)
- if err != nil {
- util.HandleError(ctx, http.StatusBadRequest, err)
- return
- }
-
- if err := expandPipeline(ctx, pipeline, comp, true); err != nil {
- util.HandleError(ctx, http.StatusBadRequest, err)
- return
- }
-
- // validate the yaml configuration
- if err = comp.Validate(pipeline); err != nil {
- retErr := fmt.Errorf("unable to validate pipeline configuration for %s: %w", repoName(ctx), err)
- util.HandleError(ctx, http.StatusBadRequest, retErr)
- return
- }
-
- writeOutput(ctx, pipeline)
-}
-
-// getUnprocessedPipeline retrieves the unprocessed pipeline from a given context.
-func getUnprocessedPipeline(ctx *gin.Context) (*yaml.Build, compiler.Engine, error) {
- // capture middleware values
- meta := ctx.MustGet("metadata").(*types.Metadata)
- repo := repo.Retrieve(ctx)
-
- // capture query parameters
- ref := ctx.DefaultQuery("ref", repo.GetBranch())
-
- // send API call to capture the repo owner
- user, err := database.FromContext(ctx).GetUser(repo.GetUserID())
- if err != nil {
- return nil, nil, fmt.Errorf("unable to get owner for %s: %w", repo.GetFullName(), err)
- }
-
- // send API call to capture the pipeline configuration file
- config, err := scm.FromContext(ctx).ConfigBackoff(user, repo, ref)
- if err != nil {
- return nil, nil, fmt.Errorf("unable to get pipeline configuration for %s: %w", repoName(ctx), err)
- }
-
- // create the compiler with extra information embedded into it
- comp := compiler.FromContext(ctx).
- WithMetadata(meta).
- WithRepo(repo).
- WithUser(user)
-
- pipeline, err := comp.Parse(config)
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- return nil, nil, fmt.Errorf("unable to parse pipeline configuration for %s: %w", repoName(ctx), err)
- }
-
- return pipeline, comp, nil
-}
-
-// getTemplateLinks helper function that retrieves source provider links
-// for a list of templates and returns a map of library templates.
-//
-// nolint: lll // ignore long line length due to variable names
-func getTemplateLinks(ctx *gin.Context, templates yaml.TemplateSlice) (map[string]*library.Template, error) {
- r := repo.Retrieve(ctx)
- u, err := database.FromContext(ctx).GetUser(r.GetUserID())
- if err != nil {
- return nil, err
- }
-
- m := make(map[string]*library.Template)
- for _, t := range templates {
- // convert to library type
- tmpl := t.ToLibrary()
-
- // create a new compiler github client for parsing,
- // no address or token needed for Parse
- cl, err := github.New("", "")
- if err != nil {
- return nil, fmt.Errorf("unable to create compiler github client: %w", err)
- }
-
- // parse template source
- src, err := cl.Parse(tmpl.GetSource())
- if err != nil {
- return nil, fmt.Errorf("unable to parse source for %s: %w", tmpl.GetSource(), err)
- }
-
- // retrieve link to template file from github
- link, err := scm.FromContext(ctx).GetHTMLURL(u, src.Org, src.Repo, src.Name, src.Ref)
- if err != nil {
- return nil, fmt.Errorf("unable to get html url for %s/%s/%s/@%s: %w", src.Org, src.Repo, src.Name, src.Ref, err)
- }
-
- // set link to template file
- tmpl.SetLink(link)
-
- m[tmpl.GetName()] = tmpl
- }
-
- return m, nil
-}
-
-// repoName takes the given context and returns a string friendly
-// representation with the format of 'repository@reference'.
-func repoName(ctx *gin.Context) string {
- repo := repo.Retrieve(ctx)
- ref := ctx.DefaultQuery("ref", repo.GetBranch())
-
- return fmt.Sprintf("%s@%s", repo.GetFullName(), ref)
-}
-
-// writeOutput returns writes output to the request based on the preferred
-// output as defined in the request's 'output' query defaulting to YAML.
-func writeOutput(ctx *gin.Context, pipeline interface{}) {
- output := ctx.DefaultQuery("output", outputYAML)
-
- // format response body based off output query parameter
- switch strings.ToLower(output) {
- case outputJSON:
- ctx.JSON(http.StatusOK, pipeline)
- case outputYAML:
- fallthrough
- default:
- ctx.YAML(http.StatusOK, pipeline)
- }
-}
-
-// expandPipeline uses a given pipeline and compiler to expand stages and steps
-// in the pipeline along with optionally substituting the environmental variables.
-//
-// nolint: lll // ignore long line length due to variable names
-func expandPipeline(ctx *gin.Context, pipeline *yaml.Build, comp compiler.Engine, substituteEnv bool) error {
- // create map of templates for easy lookup
- templates := pipeline.Templates.Map()
-
- var err error
-
- if len(pipeline.Stages) > 0 {
- // inject the templates into the stages
- pipeline.Stages, pipeline.Secrets, pipeline.Services, pipeline.Environment, err = comp.ExpandStages(pipeline, templates)
- if err != nil {
- return fmt.Errorf("unable to expand stages in pipeline configuration for %s: %w", repoName(ctx), err)
- }
-
- if substituteEnv {
- // inject the substituted environment variables into the stages
- pipeline.Stages, err = comp.SubstituteStages(pipeline.Stages)
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- return fmt.Errorf("unable to substitute stages in pipeline configuration for %s: %w", repoName(ctx), err)
- }
- }
- } else {
- // inject the templates into the steps
- pipeline.Steps, pipeline.Secrets, pipeline.Services, pipeline.Environment, err = comp.ExpandSteps(pipeline, templates)
- if err != nil {
- return fmt.Errorf("unable to expand steps in pipeline configuration for %s: %w", repoName(ctx), err)
- }
-
- if substituteEnv {
- // inject the substituted environment variables into the steps
- pipeline.Steps, err = comp.SubstituteSteps(pipeline.Steps)
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- return fmt.Errorf("unable to substitute steps in pipeline configuration for %s: %w", repoName(ctx), err)
- }
- }
- }
-
- return nil
-}
diff --git a/api/pipeline/compile.go b/api/pipeline/compile.go
new file mode 100644
index 000000000..8ac5fed93
--- /dev/null
+++ b/api/pipeline/compile.go
@@ -0,0 +1,110 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code with expand
+package pipeline
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/compiler"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/pipeline"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/pipelines/{org}/{repo}/{pipeline}/compile pipelines CompilePipeline
+//
+// Get, expand and compile a pipeline from the configured backend
+//
+// ---
+// produces:
+// - application/x-yaml
+// - application/json
+// parameters:
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: pipeline
+// description: Commit SHA for pipeline to retrieve
+// required: true
+// type: string
+// - in: query
+// name: output
+// description: Output string for specifying output format
+// type: string
+// default: yaml
+// enum:
+// - json
+// - yaml
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved and compiled the pipeline
+// schema:
+// "$ref": "#/definitions/PipelineBuild"
+// '400':
+// description: Unable to validate the pipeline configuration
+// schema:
+// "$ref": "#/definitions/Error"
+// '404':
+// description: Unable to retrieve the pipeline configuration
+// schema:
+// "$ref": "#/definitions/Error"
+
+// CompilePipeline represents the API handler to capture,
+// expand and compile a pipeline configuration.
+func CompilePipeline(c *gin.Context) {
+ // capture middleware values
+ m := c.MustGet("metadata").(*types.Metadata)
+ o := org.Retrieve(c)
+ p := pipeline.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%s", r.GetFullName(), p.GetCommit())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "pipeline": p.GetCommit(),
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("compiling pipeline %s", entry)
+
+ // ensure we use the expected pipeline type when compiling
+ r.SetPipelineType(p.GetType())
+
+ // create the compiler object
+ compiler := compiler.FromContext(c).Duplicate().WithCommit(p.GetCommit()).WithMetadata(m).WithRepo(r).WithUser(u)
+
+ // compile the pipeline
+ pipeline, _, err := compiler.CompileLite(p.GetData(), true, true)
+ if err != nil {
+ retErr := fmt.Errorf("unable to compile pipeline %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ writeOutput(c, pipeline)
+}
diff --git a/api/pipeline/create.go b/api/pipeline/create.go
new file mode 100644
index 000000000..87bfb6634
--- /dev/null
+++ b/api/pipeline/create.go
@@ -0,0 +1,112 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/pipelines/{org}/{repo} pipelines CreatePipeline
+//
+// Create a pipeline in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: body
+// name: body
+// description: Payload containing the pipeline to create
+// required: true
+// schema:
+// "$ref": "#/definitions/Pipeline"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '201':
+// description: Successfully created the pipeline
+// type: json
+// schema:
+// "$ref": "#/definitions/Pipeline"
+// '400':
+// description: Unable to create the pipeline
+// schema:
+// "$ref": "#/definitions/Error"
+// '404':
+// description: Unable to create the pipeline
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to create the pipeline
+// schema:
+// "$ref": "#/definitions/Error"
+
+// CreatePipeline represents the API handler to
+// create a pipeline in the configured backend.
+func CreatePipeline(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logger := logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ })
+
+ logger.Infof("creating new pipeline for repo %s", r.GetFullName())
+
+ // capture body from API request
+ input := new(library.Pipeline)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for new build for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update fields in pipeline object
+ input.SetRepoID(r.GetID())
+
+ // send API call to create the pipeline
+ p, err := database.FromContext(c).CreatePipeline(ctx, input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create pipeline %s/%s: %w", r.GetFullName(), input.GetCommit(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusCreated, p)
+}
diff --git a/api/pipeline/delete.go b/api/pipeline/delete.go
new file mode 100644
index 000000000..1012b3094
--- /dev/null
+++ b/api/pipeline/delete.go
@@ -0,0 +1,93 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/pipeline"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation DELETE /api/v1/pipelines/{org}/{repo}/{pipeline} pipelines DeletePipeline
+//
+// Delete a pipeline from the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: pipeline
+// description: Commit SHA for pipeline to delete
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully deleted the pipeline
+// schema:
+// type: string
+// '400':
+// description: Unable to delete the pipeline
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to delete the pipeline
+// schema:
+// "$ref": "#/definitions/Error"
+
+// DeletePipeline represents the API handler to remove
+// a pipeline for a repo from the configured backend.
+func DeletePipeline(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ p := pipeline.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ entry := fmt.Sprintf("%s/%s", r.GetFullName(), p.GetCommit())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "pipeline": p.GetCommit(),
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("deleting pipeline %s", entry)
+
+ // send API call to remove the build
+ err := database.FromContext(c).DeletePipeline(ctx, p)
+ if err != nil {
+ retErr := fmt.Errorf("unable to delete pipeline %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("pipeline %s deleted", entry))
+}
diff --git a/api/pipeline/doc.go b/api/pipeline/doc.go
new file mode 100644
index 000000000..0b2c02901
--- /dev/null
+++ b/api/pipeline/doc.go
@@ -0,0 +1,10 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+// Package pipeline provides the pipeline handlers for the Vela API.
+//
+// Usage:
+//
+// import "github.com/go-vela/server/api/pipeline"
+package pipeline
diff --git a/api/pipeline/expand.go b/api/pipeline/expand.go
new file mode 100644
index 000000000..17f782fee
--- /dev/null
+++ b/api/pipeline/expand.go
@@ -0,0 +1,111 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code with compile
+package pipeline
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/compiler"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/pipeline"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/pipelines/{org}/{repo}/{pipeline}/expand pipelines ExpandPipeline
+//
+// Get and expand a pipeline from the configured backend
+//
+// ---
+// produces:
+// - application/x-yaml
+// - application/json
+// parameters:
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: pipeline
+// description: Commit SHA for pipeline to retrieve
+// required: true
+// type: string
+// - in: query
+// name: output
+// description: Output string for specifying output format
+// type: string
+// default: yaml
+// enum:
+// - json
+// - yaml
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved and expanded the pipeline
+// type: json
+// schema:
+// "$ref": "#/definitions/PipelineBuild"
+// '400':
+// description: Unable to expand the pipeline configuration
+// schema:
+// "$ref": "#/definitions/Error"
+// '404':
+// description: Unable to retrieve the pipeline configuration
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ExpandPipeline represents the API handler to capture and
+// expand a pipeline configuration.
+func ExpandPipeline(c *gin.Context) {
+ // capture middleware values
+ m := c.MustGet("metadata").(*types.Metadata)
+ o := org.Retrieve(c)
+ p := pipeline.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%s", r.GetFullName(), p.GetCommit())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "pipeline": p.GetCommit(),
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("expanding templates for pipeline %s", entry)
+
+ // ensure we use the expected pipeline type when compiling
+ r.SetPipelineType(p.GetType())
+
+ // create the compiler object
+ compiler := compiler.FromContext(c).Duplicate().WithCommit(p.GetCommit()).WithMetadata(m).WithRepo(r).WithUser(u)
+
+ // expand the templates in the pipeline
+ pipeline, _, err := compiler.CompileLite(p.GetData(), true, false)
+ if err != nil {
+ retErr := fmt.Errorf("unable to expand pipeline %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ writeOutput(c, pipeline)
+}
diff --git a/api/pipeline/get.go b/api/pipeline/get.go
new file mode 100644
index 000000000..45beb61dc
--- /dev/null
+++ b/api/pipeline/get.go
@@ -0,0 +1,70 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/pipeline"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/pipelines/{org}/{repo}/{pipeline} pipelines GetPipeline
+//
+// Get a pipeline from the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: pipeline
+// description: Commit SHA for pipeline to retrieve
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the pipeline
+// type: json
+// schema:
+// "$ref": "#/definitions/Pipeline"
+
+// GetPipeline represents the API handler to capture
+// a pipeline for a repo from the configured backend.
+func GetPipeline(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ p := pipeline.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "pipeline": p.GetCommit(),
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("reading pipeline %s/%s", r.GetFullName(), p.GetCommit())
+
+ c.JSON(http.StatusOK, p)
+}
diff --git a/api/pipeline/list.go b/api/pipeline/list.go
new file mode 100644
index 000000000..a43f33788
--- /dev/null
+++ b/api/pipeline/list.go
@@ -0,0 +1,140 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/api"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/pipelines/{org}/{repo} pipelines ListPipelines
+//
+// List pipelines from the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: query
+// name: page
+// description: The page of results to retrieve
+// type: integer
+// default: 1
+// - in: query
+// name: per_page
+// description: How many results per page to return
+// type: integer
+// maximum: 100
+// default: 10
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the pipelines
+// schema:
+// type: array
+// items:
+// "$ref": "#/definitions/Pipeline"
+// headers:
+// X-Total-Count:
+// description: Total number of results
+// type: integer
+// Link:
+// description: see https://tools.ietf.org/html/rfc5988
+// type: string
+// '400':
+// description: Unable to retrieve the list of pipelines
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to retrieve the list of pipelines
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ListPipelines represents the API handler to capture a list
+// of pipelines for a repo from the configured backend.
+func ListPipelines(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("listing pipelines for repo %s", r.GetFullName())
+
+ // capture page query parameter if present
+ page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
+ if err != nil {
+ //nolint:lll // ignore long line length due to error message
+ retErr := fmt.Errorf("unable to convert page query parameter for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // capture per_page query parameter if present
+ perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
+ if err != nil {
+ //nolint:lll // ignore long line length due to error message
+ retErr := fmt.Errorf("unable to convert per_page query parameter for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // ensure per_page isn't above or below allowed values
+ //
+ //nolint:gomnd // ignore magic number
+ perPage = util.MaxInt(1, util.MinInt(100, perPage))
+
+ p, t, err := database.FromContext(c).ListPipelinesForRepo(ctx, r, page, perPage)
+ if err != nil {
+ retErr := fmt.Errorf("unable to list pipelines for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // create pagination object
+ pagination := api.Pagination{
+ Page: page,
+ PerPage: perPage,
+ Total: t,
+ }
+ // set pagination headers
+ pagination.SetHeaderLink(c)
+
+ c.JSON(http.StatusOK, p)
+}
diff --git a/api/pipeline/output.go b/api/pipeline/output.go
new file mode 100644
index 000000000..46fcace56
--- /dev/null
+++ b/api/pipeline/output.go
@@ -0,0 +1,35 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "net/http"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/util"
+)
+
+const (
+ outputJSON = "json"
+ outputYAML = "yaml"
+)
+
+// writeOutput is a helper function to return the provided value to the
+// request based off the output query parameter provided. If no output
+// query parameter is provided, then YAML is used by default.
+func writeOutput(c *gin.Context, value interface{}) {
+ output := util.QueryParameter(c, "output", outputYAML)
+
+ // format response body based off output query parameter
+ switch strings.ToLower(output) {
+ case outputJSON:
+ c.JSON(http.StatusOK, value)
+ case outputYAML:
+ fallthrough
+ default:
+ c.YAML(http.StatusOK, value)
+ }
+}
diff --git a/api/pipeline/template.go b/api/pipeline/template.go
new file mode 100644
index 000000000..54233b691
--- /dev/null
+++ b/api/pipeline/template.go
@@ -0,0 +1,160 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/compiler"
+ "github.com/go-vela/server/compiler/registry/github"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/pipeline"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types"
+ "github.com/go-vela/types/library"
+ "github.com/go-vela/types/yaml"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/pipelines/{org}/{repo}/{pipeline}/templates pipelines GetTemplates
+//
+// Get a map of templates utilized by a pipeline from the configured backend
+//
+// ---
+// produces:
+// - application/x-yaml
+// - application/json
+// parameters:
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: pipeline
+// description: Commit SHA for pipeline to retrieve
+// required: true
+// type: string
+// - in: query
+// name: output
+// description: Output string for specifying output format
+// type: string
+// default: yaml
+// enum:
+// - json
+// - yaml
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the map of pipeline templates
+// schema:
+// "$ref": "#/definitions/Template"
+// '400':
+// description: Unable to retrieve the pipeline configuration templates
+// schema:
+// "$ref": "#/definitions/Error"
+// '404':
+// description: Unable to retrieve the pipeline configuration templates
+// schema:
+// "$ref": "#/definitions/Error"
+
+// GetTemplates represents the API handler to capture a
+// map of templates utilized by a pipeline configuration.
+func GetTemplates(c *gin.Context) {
+ // capture middleware values
+ m := c.MustGet("metadata").(*types.Metadata)
+ o := org.Retrieve(c)
+ p := pipeline.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%s", r.GetFullName(), p.GetCommit())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "pipeline": p.GetCommit(),
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("reading templates from pipeline %s", entry)
+
+ // create the compiler object
+ compiler := compiler.FromContext(c).Duplicate().WithCommit(p.GetCommit()).WithMetadata(m).WithRepo(r).WithUser(u)
+
+ // parse the pipeline configuration
+ pipeline, _, err := compiler.Parse(p.GetData(), p.GetType(), new(yaml.Template))
+ if err != nil {
+ util.HandleError(c, http.StatusBadRequest, fmt.Errorf("unable to parse pipeline %s: %w", entry, err))
+
+ return
+ }
+
+ // send API call to capture the repo owner
+ user, err := database.FromContext(c).GetUser(r.GetUserID())
+ if err != nil {
+ util.HandleError(c, http.StatusBadRequest, fmt.Errorf("unable to get owner for %s: %w", r.GetFullName(), err))
+
+ return
+ }
+
+ baseErr := fmt.Sprintf("unable to set template links for %s", entry)
+
+ templates := make(map[string]*library.Template)
+ for name, template := range pipeline.Templates.Map() {
+ templates[name] = template.ToLibrary()
+
+ // create a compiler registry client for parsing (no address or token needed for Parse)
+ registry, err := github.New("", "")
+ if err != nil {
+ util.HandleError(c, http.StatusBadRequest, fmt.Errorf("%s: unable to create compiler github client: %w", baseErr, err))
+
+ return
+ }
+
+ // capture source path to template
+ source := template.Source
+
+ // if type is file, compose a source string so the template can be found
+ if strings.EqualFold(template.Type, "file") {
+ source = fmt.Sprintf("%s%s/%s/%s@%s", registry.URL, o, r.GetName(), source, p.GetCommit())
+ }
+
+ // parse the source for the template using the compiler registry client
+ src, err := registry.Parse(source)
+ if err != nil {
+ util.HandleError(c, http.StatusBadRequest, fmt.Errorf("%s: unable to parse source for %s: %w", baseErr, template.Source, err))
+
+ return
+ }
+
+ // retrieve link to template file from github
+ link, err := scm.FromContext(c).GetHTMLURL(user, src.Org, src.Repo, src.Name, src.Ref)
+ if err != nil {
+ util.HandleError(c, http.StatusBadRequest, fmt.Errorf("%s: unable to get html url for %s/%s/%s/@%s: %w", baseErr, src.Org, src.Repo, src.Name, src.Ref, err))
+
+ return
+ }
+
+ // set link to file for template
+ templates[name].SetLink(link)
+ }
+
+ writeOutput(c, templates)
+}
diff --git a/api/pipeline/update.go b/api/pipeline/update.go
new file mode 100644
index 000000000..8c349f5bb
--- /dev/null
+++ b/api/pipeline/update.go
@@ -0,0 +1,184 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/pipeline"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation PUT /api/v1/pipelines/{org}/{repo}/{pipeline} pipelines UpdatePipeline
+//
+// Update a pipeline in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: pipeline
+// description: Commit SHA for pipeline to update
+// required: true
+// type: string
+// - in: body
+// name: body
+// description: Payload containing the pipeline to update
+// required: true
+// schema:
+// "$ref": "#/definitions/Pipeline"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully updated the pipeline
+// schema:
+// "$ref": "#/definitions/Pipeline"
+// '404':
+// description: Unable to update the pipeline
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to update the pipeline
+// schema:
+// "$ref": "#/definitions/Error"
+
+// UpdatePipeline represents the API handler to update
+// a pipeline for a repo in the configured backend.
+func UpdatePipeline(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ p := pipeline.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ entry := fmt.Sprintf("%s/%s", r.GetFullName(), p.GetCommit())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "pipeline": p.GetCommit(),
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("updating pipeline %s", entry)
+
+ // capture body from API request
+ input := new(library.Pipeline)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for pipeline %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ // check if the Flavor field in the pipeline was provided
+ if len(input.GetFlavor()) > 0 {
+ // update the Flavor field
+ p.SetFlavor(input.GetFlavor())
+ }
+
+ // check if the Platform field in the pipeline was provided
+ if len(input.GetPlatform()) > 0 {
+ // update the Platform field
+ p.SetPlatform(input.GetPlatform())
+ }
+
+ // check if the Ref field in the pipeline was provided
+ if len(input.GetRef()) > 0 {
+ // update the Ref field
+ p.SetRef(input.GetRef())
+ }
+
+ // check if the Type field in the pipeline was provided
+ if len(input.GetType()) > 0 {
+ // update the Type field
+ p.SetType(input.GetType())
+ }
+
+ // check if the Version field in the pipeline was provided
+ if len(input.GetVersion()) > 0 {
+ // update the Version field
+ p.SetVersion(input.GetVersion())
+ }
+
+ // check if the ExternalSecrets field in the pipeline was provided
+ if input.ExternalSecrets != nil {
+ // update the ExternalSecrets field
+ p.SetExternalSecrets(input.GetExternalSecrets())
+ }
+
+ // check if the InternalSecrets field in the pipeline was provided
+ if input.InternalSecrets != nil {
+ // update the InternalSecrets field
+ p.SetInternalSecrets(input.GetInternalSecrets())
+ }
+
+ // check if the Services field in the pipeline was provided
+ if input.Services != nil {
+ // update the Services field
+ p.SetServices(input.GetServices())
+ }
+
+ // check if the Stages field in the pipeline was provided
+ if input.Stages != nil {
+ // update the Stages field
+ p.SetStages(input.GetStages())
+ }
+
+ // check if the Steps field in the pipeline was provided
+ if input.Steps != nil {
+ // update the Steps field
+ p.SetSteps(input.GetSteps())
+ }
+
+ // check if the Templates field in the pipeline was provided
+ if input.Templates != nil {
+ // update the Templates field
+ p.SetTemplates(input.GetTemplates())
+ }
+
+ // check if the Data field in the pipeline was provided
+ if len(input.GetData()) > 0 {
+ // update the data field
+ p.SetData(input.GetData())
+ }
+
+ // send API call to update the pipeline
+ p, err = database.FromContext(c).UpdatePipeline(ctx, p)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update pipeline %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, p)
+}
diff --git a/api/pipeline/validate.go b/api/pipeline/validate.go
new file mode 100644
index 000000000..45aae1ff8
--- /dev/null
+++ b/api/pipeline/validate.go
@@ -0,0 +1,118 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/compiler"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/pipeline"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/pipelines/{org}/{repo}/{pipeline}/validate pipelines ValidatePipeline
+//
+// Get, expand and validate a pipeline from the configured backend
+//
+// ---
+// produces:
+// - application/x-yaml
+// - application/json
+// parameters:
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: pipeline
+// description: Commit SHA for pipeline to retrieve
+// required: true
+// type: string
+// - in: query
+// name: output
+// description: Output string for specifying output format
+// type: string
+// default: yaml
+// enum:
+// - json
+// - yaml
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved, expanded and validated the pipeline
+// schema:
+// type: string
+// '400':
+// description: Unable to validate the pipeline configuration
+// schema:
+// "$ref": "#/definitions/Error"
+// '404':
+// description: Unable to retrieve the pipeline configuration
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ValidatePipeline represents the API handler to capture,
+// expand and validate a pipeline configuration.
+func ValidatePipeline(c *gin.Context) {
+ // capture middleware values
+ m := c.MustGet("metadata").(*types.Metadata)
+ o := org.Retrieve(c)
+ p := pipeline.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%s", r.GetFullName(), p.GetCommit())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "pipeline": p.GetCommit(),
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("validating pipeline %s", entry)
+
+ // ensure we use the expected pipeline type when compiling
+ r.SetPipelineType(p.GetType())
+
+ // create the compiler object
+ compiler := compiler.FromContext(c).Duplicate().WithCommit(p.GetCommit()).WithMetadata(m).WithRepo(r).WithUser(u)
+
+ // capture optional template query parameter
+ template, err := strconv.ParseBool(c.DefaultQuery("template", "true"))
+ if err != nil {
+ util.HandleError(c, http.StatusBadRequest, fmt.Errorf("unable to parse template query parameter for %s: %w", entry, err))
+
+ return
+ }
+
+ // validate the pipeline
+ pipeline, _, err := compiler.CompileLite(p.GetData(), template, false)
+ if err != nil {
+ retErr := fmt.Errorf("unable to validate pipeline %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ writeOutput(c, pipeline)
+}
diff --git a/api/repo.go b/api/repo.go
deleted file mode 100644
index 6fd5476ad..000000000
--- a/api/repo.go
+++ /dev/null
@@ -1,1075 +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 api
-
-import (
- "encoding/base64"
- "fmt"
- "net/http"
- "strconv"
- "strings"
-
- "github.com/go-vela/server/router/middleware/org"
-
- "github.com/go-vela/server/database"
- "github.com/go-vela/server/router/middleware/repo"
- "github.com/go-vela/server/router/middleware/user"
- "github.com/go-vela/server/scm"
- "github.com/go-vela/server/util"
-
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/library"
-
- "github.com/gin-gonic/gin"
- "github.com/google/uuid"
- "github.com/sirupsen/logrus"
-)
-
-// swagger:operation POST /api/v1/repos repos CreateRepo
-//
-// Create a repo in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: body
-// name: body
-// description: Payload containing the repo to create
-// required: true
-// schema:
-// "$ref": "#/definitions/Repo"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '201':
-// description: Successfully created the repo
-// schema:
-// "$ref": "#/definitions/Repo"
-// '400':
-// description: Unable to create the repo
-// schema:
-// "$ref": "#/definitions/Error"
-// '403':
-// description: Unable to create the repo
-// schema:
-// "$ref": "#/definitions/Error"
-// '409':
-// description: Unable to create the repo
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to create the repo
-// schema:
-// "$ref": "#/definitions/Error"
-// '503':
-// description: Unable to create the repo
-// schema:
-// "$ref": "#/definitions/Error"
-
-// CreateRepo represents the API handler to
-// create a repo in the configured backend.
-//
-// nolint: funlen,gocyclo // ignore function length and cyclomatic complexity
-func CreateRepo(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
- allowlist := c.Value("allowlist").([]string)
- defaultBuildLimit := c.Value("defaultBuildLimit").(int64)
- defaultTimeout := c.Value("defaultTimeout").(int64)
- maxBuildLimit := c.Value("maxBuildLimit").(int64)
-
- // capture body from API request
- input := new(library.Repo)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for new repo: %w", err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": input.GetOrg(),
- "repo": input.GetName(),
- "user": u.GetName(),
- }).Infof("creating new repo %s", input.GetFullName())
-
- // get repo information from the source
- r, err := scm.FromContext(c).GetRepo(u, input)
- if err != nil {
- retErr := fmt.Errorf("unable to retrieve repo info for %s from source: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // update fields in repo object
- r.SetUserID(u.GetID())
-
- // set the active field based off the input provided
- if input.Active == nil {
- // default active field to true
- r.SetActive(true)
- } else {
- r.SetActive(input.GetActive())
- }
-
- // set the build limit field based off the input provided
- if input.GetBuildLimit() == 0 {
- // default build limit to value configured by server
- r.SetBuildLimit(defaultBuildLimit)
- } else if input.GetBuildLimit() > maxBuildLimit {
- // set build limit to value configured by server to prevent limit from exceeding max
- r.SetBuildLimit(maxBuildLimit)
- } else {
- r.SetBuildLimit(input.GetBuildLimit())
- }
-
- // set the timeout field based off the input provided
- if input.GetTimeout() == 0 && defaultTimeout == 0 {
- // default build timeout to 30m
- r.SetTimeout(constants.BuildTimeoutDefault)
- } else if input.GetTimeout() == 0 {
- r.SetTimeout(defaultTimeout)
- } else {
- r.SetTimeout(input.GetTimeout())
- }
-
- // set the visibility field based off the input provided
- if len(input.GetVisibility()) == 0 {
- // default visibility field to public
- r.SetVisibility(constants.VisibilityPublic)
- } else {
- r.SetVisibility(input.GetVisibility())
- }
-
- // set default events if no events are passed in
- if !input.GetAllowPull() && !input.GetAllowPush() &&
- !input.GetAllowDeploy() && !input.GetAllowTag() &&
- !input.GetAllowComment() {
- // default events to push and pull_request
- r.SetAllowPull(true)
- r.SetAllowPush(true)
- } else {
- r.SetAllowComment(input.GetAllowComment())
- r.SetAllowDeploy(input.GetAllowDeploy())
- r.SetAllowPull(input.GetAllowPull())
- r.SetAllowPush(input.GetAllowPush())
- r.SetAllowTag(input.GetAllowTag())
- }
-
- if len(input.GetPipelineType()) == 0 {
- r.SetPipelineType(constants.PipelineTypeYAML)
- } else {
- // ensure the pipeline type matches one of the expected values
- if input.GetPipelineType() != constants.PipelineTypeYAML &&
- input.GetPipelineType() != constants.PipelineTypeGo &&
- input.GetPipelineType() != constants.PipelineTypeStarlark {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to create new repo %s: invalid pipeline_type provided %s", r.GetFullName(), input.GetPipelineType())
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
- r.SetPipelineType(input.GetPipelineType())
- }
-
- // create unique id for the repo
- uid, err := uuid.NewRandom()
- if err != nil {
- retErr := fmt.Errorf("unable to create UID for repo %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusServiceUnavailable, retErr)
-
- return
- }
-
- r.SetHash(
- base64.StdEncoding.EncodeToString(
- []byte(strings.TrimSpace(uid.String())),
- ),
- )
-
- // ensure repo is allowed to be activated
- if !checkAllowlist(r, allowlist) {
- retErr := fmt.Errorf("unable to activate repo: %s is not on allowlist", r.GetFullName())
-
- util.HandleError(c, http.StatusForbidden, retErr)
-
- return
- }
-
- // send API call to capture the repo from the database
- dbRepo, err := database.FromContext(c).GetRepo(r.GetOrg(), r.GetName())
- if err == nil && dbRepo.GetActive() {
- retErr := fmt.Errorf("unable to activate repo: %s is already active", r.GetFullName())
-
- util.HandleError(c, http.StatusConflict, retErr)
-
- return
- }
-
- // check if the repo already has a hash created
- if len(dbRepo.GetHash()) > 0 {
- // overwrite the new repo hash with the existing repo hash
- r.SetHash(dbRepo.GetHash())
- }
-
- // send API call to create the webhook
- if c.Value("webhookvalidation").(bool) {
- _, err = scm.FromContext(c).Enable(u, r.GetOrg(), r.GetName(), r.GetHash())
- if err != nil {
- retErr := fmt.Errorf("unable to create webhook for %s: %w", r.GetFullName(), err)
-
- switch err.Error() {
- case "repo already enabled":
- util.HandleError(c, http.StatusConflict, retErr)
- return
- case "repo not found":
- util.HandleError(c, http.StatusNotFound, retErr)
- return
- }
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
- }
-
- // if the repo exists but is inactive
- if len(dbRepo.GetOrg()) > 0 && !dbRepo.GetActive() {
- // update the repo owner
- dbRepo.SetUserID(u.GetID())
- // update the default branch
- dbRepo.SetBranch(r.GetBranch())
- // activate the repo
- dbRepo.SetActive(true)
-
- // send API call to update the repo
- err = database.FromContext(c).UpdateRepo(dbRepo)
- if err != nil {
- retErr := fmt.Errorf("unable to set repo %s to active: %w", dbRepo.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the updated repo
- r, _ = database.FromContext(c).GetRepo(dbRepo.GetOrg(), dbRepo.GetName())
- } else {
- // send API call to create the repo
- err = database.FromContext(c).CreateRepo(r)
- if err != nil {
- retErr := fmt.Errorf("unable to create new repo %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the created repo
- r, _ = database.FromContext(c).GetRepo(r.GetOrg(), r.GetName())
- }
-
- c.JSON(http.StatusCreated, r)
-}
-
-// swagger:operation GET /api/v1/repos repos GetRepos
-//
-// Get all repos in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// security:
-// - ApiKeyAuth: []
-// parameters:
-// - in: query
-// name: page
-// description: The page of results to retrieve
-// type: integer
-// default: 1
-// - in: query
-// name: per_page
-// description: How many results per page to return
-// type: integer
-// maximum: 100
-// default: 10
-// responses:
-// '200':
-// description: Successfully retrieved the repo
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/Repo"
-// headers:
-// X-Total-Count:
-// description: Total number of results
-// type: integer
-// Link:
-// description: see https://tools.ietf.org/html/rfc5988
-// type: string
-// '400':
-// description: Unable to retrieve the repo
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to retrieve the repo
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetRepos represents the API handler to capture a list
-// of repos for a user from the configured backend.
-func GetRepos(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Infof("reading repos for user %s", u.GetName())
-
- // capture page query parameter if present
- page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
- if err != nil {
- retErr := fmt.Errorf("unable to convert page query parameter for user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // capture per_page query parameter if present
- perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to convert per_page query parameter for user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // ensure per_page isn't above or below allowed values
- //
- // nolint: gomnd // ignore magic number
- perPage = util.MaxInt(1, util.MinInt(100, perPage))
-
- // send API call to capture the total number of repos for the user
- t, err := database.FromContext(c).GetUserRepoCount(u)
- if err != nil {
- retErr := fmt.Errorf("unable to get repo count for user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the list of repos for the user
- r, err := database.FromContext(c).GetUserRepoList(u, page, perPage)
- if err != nil {
- retErr := fmt.Errorf("unable to get repos for user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // create pagination object
- pagination := Pagination{
- Page: page,
- PerPage: perPage,
- Total: t,
- }
- // set pagination headers
- pagination.SetHeaderLink(c)
-
- c.JSON(http.StatusOK, r)
-}
-
-// swagger:operation GET /api/v1/{org} repos GetOrgRepos
-//
-// Get all repos for the provided org in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// security:
-// - ApiKeyAuth: []
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: query
-// name: active
-// description: Filter active repos
-// type: boolean
-// default: true
-// - in: query
-// name: page
-// description: The page of results to retrieve
-// type: integer
-// default: 1
-// - in: query
-// name: per_page
-// description: How many results per page to return
-// type: integer
-// maximum: 100
-// default: 10
-// responses:
-// '200':
-// description: Successfully retrieved the repo
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/Repo"
-// headers:
-// X-Total-Count:
-// description: Total number of results
-// type: integer
-// Link:
-// description: see https://tools.ietf.org/html/rfc5988
-// type: string
-// '400':
-// description: Unable to retrieve the org
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to retrieve the org
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetOrgRepos represents the API handler to capture a list
-// of repos for an org from the configured backend.
-func GetOrgRepos(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
- org := c.Param("org")
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": org,
- "user": u.GetName(),
- }).Infof("reading repos for org %s", org)
-
- // capture page query parameter if present
- page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
- if err != nil {
- retErr := fmt.Errorf("unable to convert page query parameter for user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // capture per_page query parameter if present
- perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to convert per_page query parameter for user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // ensure per_page isn't above or below allowed values
- //
- // nolint: gomnd // ignore magic number
- perPage = util.MaxInt(1, util.MinInt(100, perPage))
-
- // See if the user is an org admin to bypass individual permission checks
- perm, err := scm.FromContext(c).OrgAccess(u, org)
- if err != nil {
- logrus.Errorf("unable to get user %s access level for org %s", u.GetName(), org)
- }
-
- filters := map[string]string{}
- // Only show public repos to non-admins
- if perm != "admin" {
- filters["visibility"] = "public"
- }
-
- filters["active"] = c.DefaultQuery("active", "true")
-
- // send API call to capture the total number of repos for the org
- t, err := database.FromContext(c).GetOrgRepoCount(org, filters)
- if err != nil {
- retErr := fmt.Errorf("unable to get repo count for org %s: %w", org, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the list of repos for the org
- r, err := database.FromContext(c).GetOrgRepoList(org, filters, page, perPage)
- if err != nil {
- retErr := fmt.Errorf("unable to get repos for org %s: %w", org, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // create pagination object
- pagination := Pagination{
- Page: page,
- PerPage: perPage,
- Total: t,
- }
- // set pagination headers
- pagination.SetHeaderLink(c)
-
- c.JSON(http.StatusOK, r)
-}
-
-// swagger:operation GET /api/v1/repos/{org}/{repo} repos GetRepo
-//
-// Get a repo in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the repo
-// schema:
-// "$ref": "#/definitions/Repo"
-
-// GetRepo represents the API handler to
-// capture a repo from the configured backend.
-func GetRepo(c *gin.Context) {
- // capture middleware values
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("reading repo %s", r.GetFullName())
-
- c.JSON(http.StatusOK, r)
-}
-
-// swagger:operation PUT /api/v1/repos/{org}/{repo} repos UpdateRepo
-//
-// Update a repo in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: body
-// name: body
-// description: Payload containing the repo to update
-// required: true
-// schema:
-// "$ref": "#/definitions/Repo"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully updated the repo
-// schema:
-// "$ref": "#/definitions/Repo"
-// '400':
-// description: Unable to update the repo
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to update the repo
-// schema:
-// "$ref": "#/definitions/Error"
-// '503':
-// description: Unable to update the repo
-// schema:
-// "$ref": "#/definitions/Error"
-
-// UpdateRepo represents the API handler to update
-// a repo in the configured backend.
-//
-// nolint: funlen // ignore function length due to comments and conditionals
-func UpdateRepo(c *gin.Context) {
- // capture middleware values
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
- maxBuildLimit := c.Value("maxBuildLimit").(int64)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("updating repo %s", r.GetFullName())
-
- // capture body from API request
- input := new(library.Repo)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for repo %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // update repo fields if provided
- if len(input.GetBranch()) > 0 {
- // update branch if set
- r.SetBranch(input.GetBranch())
- }
-
- // update build limit if set
- if input.GetBuildLimit() > 0 {
- // allow build limit between 1 - value configured by server
- r.SetBuildLimit(
- int64(
- util.MaxInt(
- constants.BuildLimitMin,
- util.MinInt(
- int(input.GetBuildLimit()),
- int(maxBuildLimit),
- ), // clamp max
- ), // clamp min
- ),
- )
- }
-
- if input.GetTimeout() > 0 {
- // update build timeout if set
- r.SetTimeout(
- int64(
- util.MaxInt(
- constants.BuildTimeoutMin,
- util.MinInt(
- int(input.GetTimeout()),
- constants.BuildTimeoutMax,
- ), // clamp max
- ), // clamp min
- ),
- )
- }
-
- if input.GetCounter() > 0 {
- if input.GetCounter() <= r.GetCounter() {
- retErr := fmt.Errorf("unable to set counter for repo %s: must be greater than current %d",
- r.GetFullName(), r.GetCounter())
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- r.SetCounter(input.GetCounter())
- }
-
- if len(input.GetVisibility()) > 0 {
- // update visibility if set
- r.SetVisibility(input.GetVisibility())
- }
-
- if input.Private != nil {
- // update private if set
- r.SetPrivate(input.GetPrivate())
- }
-
- if input.Active != nil {
- // update active if set
- r.SetActive(input.GetActive())
- }
-
- if input.AllowPull != nil {
- // update allow_pull if set
- r.SetAllowPull(input.GetAllowPull())
- }
-
- if input.AllowPush != nil {
- // update allow_push if set
- r.SetAllowPush(input.GetAllowPush())
- }
-
- if input.AllowDeploy != nil {
- // update allow_deploy if set
- r.SetAllowDeploy(input.GetAllowDeploy())
- }
-
- if input.AllowTag != nil {
- // update allow_tag if set
- r.SetAllowTag(input.GetAllowTag())
- }
-
- if input.AllowComment != nil {
- // update allow_comment if set
- r.SetAllowComment(input.GetAllowComment())
- }
-
- // set default events if no events are enabled
- if !r.GetAllowPull() && !r.GetAllowPush() &&
- !r.GetAllowDeploy() && !r.GetAllowTag() &&
- !r.GetAllowComment() {
- r.SetAllowPull(true)
- r.SetAllowPush(true)
- }
-
- if len(input.GetPipelineType()) != 0 {
- // ensure the pipeline type matches one of the expected values
- if input.GetPipelineType() != constants.PipelineTypeYAML &&
- input.GetPipelineType() != constants.PipelineTypeGo &&
- input.GetPipelineType() != constants.PipelineTypeStarlark {
- retErr := fmt.Errorf("pipeline_type of %s is invalid", input.GetPipelineType())
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
- r.SetPipelineType(input.GetPipelineType())
- }
-
- // set hash for repo if no hash is already set
- if len(r.GetHash()) == 0 {
- // create unique id for the repo
- uid, err := uuid.NewRandom()
- if err != nil {
- retErr := fmt.Errorf("unable to create UID for repo %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusServiceUnavailable, retErr)
-
- return
- }
-
- r.SetHash(
- base64.StdEncoding.EncodeToString(
- []byte(strings.TrimSpace(uid.String())),
- ),
- )
- }
-
- // send API call to update the repo
- err = database.FromContext(c).UpdateRepo(r)
- if err != nil {
- retErr := fmt.Errorf("unable to update repo %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the updated repo
- r, _ = database.FromContext(c).GetRepo(r.GetOrg(), r.GetName())
-
- c.JSON(http.StatusOK, r)
-}
-
-// swagger:operation DELETE /api/v1/repos/{org}/{repo} repos DeleteRepo
-//
-// Delete a repo in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully deleted the repo
-// schema:
-// type: string
-// '500':
-// description: Unable to deleted the repo
-// schema:
-// "$ref": "#/definitions/Error"
-// '510':
-// description: Unable to deleted the repo
-// schema:
-// "$ref": "#/definitions/Error"
-
-// DeleteRepo represents the API handler to remove
-// a repo from the configured backend.
-func DeleteRepo(c *gin.Context) {
- // capture middleware values
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("deleting repo %s", r.GetFullName())
-
- // send API call to remove the webhook
- err := scm.FromContext(c).Disable(u, r.GetOrg(), r.GetName())
- if err != nil {
- retErr := fmt.Errorf("unable to delete webhook for %s: %w", r.GetFullName(), err)
-
- if err.Error() == "Repo not found" {
- util.HandleError(c, http.StatusNotExtended, retErr)
-
- return
- }
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // Mark the the repo as inactive
- r.SetActive(false)
-
- err = database.FromContext(c).UpdateRepo(r)
- if err != nil {
- retErr := fmt.Errorf("unable to set repo %s to inactive: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // Comment out actual delete until delete mechanism is fleshed out
- // err = database.FromContext(c).DeleteRepo(r.ID)
- // if err != nil {
- // retErr := fmt.Errorf("Error while deleting repo %s: %v", r.FullName, err)
- // util.HandleError(c, http.StatusInternalServerError, retErr)
- // return
- // }
-
- c.JSON(http.StatusOK, fmt.Sprintf("repo %s deleted", r.GetFullName()))
-}
-
-// swagger:operation PATCH /api/v1/repos/{org}/{repo}/repair repos RepairRepo
-//
-// Remove and recreate the webhook for a repo
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully repaired the repo
-// schema:
-// type: string
-// '500':
-// description: Unable to repair the repo
-// schema:
-// "$ref": "#/definitions/Error"
-
-// RepairRepo represents the API handler to remove
-// and then create a webhook for a repo.
-func RepairRepo(c *gin.Context) {
- // capture middleware values
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("repairing repo %s", r.GetFullName())
-
- // send API call to remove the webhook
- err := scm.FromContext(c).Disable(u, r.GetOrg(), r.GetName())
- if err != nil {
- retErr := fmt.Errorf("unable to delete webhook for %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to create the webhook
- _, err = scm.FromContext(c).Enable(u, r.GetOrg(), r.GetName(), r.GetHash())
- if err != nil {
- retErr := fmt.Errorf("unable to create webhook for %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // if the repo was previously inactive, mark it as active
- if !r.GetActive() {
- r.SetActive(true)
-
- // send API call to update the repo
- err = database.FromContext(c).UpdateRepo(r)
- if err != nil {
- retErr := fmt.Errorf("unable to set repo %s to active: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
- }
-
- c.JSON(http.StatusOK, fmt.Sprintf("repo %s repaired", r.GetFullName()))
-}
-
-// swagger:operation PATCH /api/v1/repos/{org}/{repo}/chown repos ChownRepo
-//
-// Change the owner of the webhook for a repo
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully changed the owner for the repo
-// schema:
-// type: string
-// '500':
-// description: Unable to change the owner for the repo
-// schema:
-// "$ref": "#/definitions/Error"
-
-// ChownRepo represents the API handler to change
-// the owner of a repo in the configured backend.
-func ChownRepo(c *gin.Context) {
- // capture middleware values
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("changing owner of repo %s to %s", r.GetFullName(), u.GetName())
-
- // update repo owner
- r.SetUserID(u.GetID())
-
- // send API call to updated the repo
- err := database.FromContext(c).UpdateRepo(r)
- if err != nil {
- retErr := fmt.Errorf("unable to change owner of repo %s: %w", r.GetFullName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, fmt.Sprintf("repo %s changed owner", r.GetFullName()))
-}
-
-// checkAllowlist is a helper function to ensure only repos in the
-// allowlist are allowed to enable repos. If the allowlist is
-// empty then any repo can be enabled.
-func checkAllowlist(r *library.Repo, allowlist []string) bool {
- // if the allowlist is not set or empty allow any repo to be enabled
- if len(allowlist) == 0 {
- return true
- }
-
- for _, repo := range allowlist {
- // allow all repos in org
- if strings.Contains(repo, "/*") {
- if strings.HasPrefix(repo, r.GetOrg()) {
- return true
- }
- }
-
- // allow specific repo within org
- if repo == r.GetFullName() {
- return true
- }
- }
-
- return false
-}
diff --git a/api/repo/chown.go b/api/repo/chown.go
new file mode 100644
index 000000000..93a4ec5fa
--- /dev/null
+++ b/api/repo/chown.go
@@ -0,0 +1,82 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation PATCH /api/v1/repos/{org}/{repo}/chown repos ChownRepo
+//
+// Change the owner of the webhook for a repo
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully changed the owner for the repo
+// schema:
+// type: string
+// '500':
+// description: Unable to change the owner for the repo
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ChownRepo represents the API handler to change
+// the owner of a repo in the configured backend.
+func ChownRepo(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("changing owner of repo %s to %s", r.GetFullName(), u.GetName())
+
+ // update repo owner
+ r.SetUserID(u.GetID())
+
+ // send API call to update the repo
+ _, err := database.FromContext(c).UpdateRepo(ctx, r)
+ if err != nil {
+ retErr := fmt.Errorf("unable to change owner of repo %s to %s: %w", r.GetFullName(), u.GetName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("repo %s changed owner to %s", r.GetFullName(), u.GetName()))
+}
diff --git a/api/repo/create.go b/api/repo/create.go
new file mode 100644
index 000000000..d76aae325
--- /dev/null
+++ b/api/repo/create.go
@@ -0,0 +1,323 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "encoding/base64"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/google/uuid"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/repos repos CreateRepo
+//
+// Create a repo in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: body
+// name: body
+// description: Payload containing the repo to create
+// required: true
+// schema:
+// "$ref": "#/definitions/Repo"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '201':
+// description: Successfully created the repo
+// schema:
+// "$ref": "#/definitions/Repo"
+// '400':
+// description: Unable to create the repo
+// schema:
+// "$ref": "#/definitions/Error"
+// '403':
+// description: Unable to create the repo
+// schema:
+// "$ref": "#/definitions/Error"
+// '409':
+// description: Unable to create the repo
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to create the repo
+// schema:
+// "$ref": "#/definitions/Error"
+// '503':
+// description: Unable to create the repo
+// schema:
+// "$ref": "#/definitions/Error"
+
+// CreateRepo represents the API handler to
+// create a repo in the configured backend.
+//
+//nolint:funlen,gocyclo // ignore function length and cyclomatic complexity
+func CreateRepo(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+ allowlist := c.Value("allowlist").([]string)
+ defaultBuildLimit := c.Value("defaultBuildLimit").(int64)
+ defaultTimeout := c.Value("defaultTimeout").(int64)
+ maxBuildLimit := c.Value("maxBuildLimit").(int64)
+ defaultRepoEvents := c.Value("defaultRepoEvents").([]string)
+ ctx := c.Request.Context()
+
+ // capture body from API request
+ input := new(library.Repo)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for new repo: %w", err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": input.GetOrg(),
+ "repo": input.GetName(),
+ "user": u.GetName(),
+ }).Infof("creating new repo %s", input.GetFullName())
+
+ // get repo information from the source
+ r, err := scm.FromContext(c).GetRepo(u, input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to retrieve repo info for %s from source: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update fields in repo object
+ r.SetUserID(u.GetID())
+
+ // set the active field based off the input provided
+ if input.Active == nil {
+ // default active field to true
+ r.SetActive(true)
+ } else {
+ r.SetActive(input.GetActive())
+ }
+
+ // set the build limit field based off the input provided
+ if input.GetBuildLimit() == 0 {
+ // default build limit to value configured by server
+ r.SetBuildLimit(defaultBuildLimit)
+ } else if input.GetBuildLimit() > maxBuildLimit {
+ // set build limit to value configured by server to prevent limit from exceeding max
+ r.SetBuildLimit(maxBuildLimit)
+ } else {
+ r.SetBuildLimit(input.GetBuildLimit())
+ }
+
+ // set the timeout field based off the input provided
+ if input.GetTimeout() == 0 && defaultTimeout == 0 {
+ // default build timeout to 30m
+ r.SetTimeout(constants.BuildTimeoutDefault)
+ } else if input.GetTimeout() == 0 {
+ r.SetTimeout(defaultTimeout)
+ } else {
+ r.SetTimeout(input.GetTimeout())
+ }
+
+ // set the visibility field based off the input provided
+ if len(input.GetVisibility()) > 0 {
+ // default visibility field to the input visibility
+ r.SetVisibility(input.GetVisibility())
+ }
+
+ // fields restricted to platform admins
+ if u.GetAdmin() {
+ // trusted default is false
+ if input.GetTrusted() != r.GetTrusted() {
+ r.SetTrusted(input.GetTrusted())
+ }
+ }
+
+ // set default events if no events are passed in
+ if !input.GetAllowPull() && !input.GetAllowPush() &&
+ !input.GetAllowDeploy() && !input.GetAllowTag() &&
+ !input.GetAllowComment() {
+ for _, event := range defaultRepoEvents {
+ switch event {
+ case constants.EventPull:
+ r.SetAllowPull(true)
+ case constants.EventPush:
+ r.SetAllowPush(true)
+ case constants.EventDeploy:
+ r.SetAllowDeploy(true)
+ case constants.EventTag:
+ r.SetAllowTag(true)
+ case constants.EventComment:
+ r.SetAllowComment(true)
+ }
+ }
+ } else {
+ r.SetAllowComment(input.GetAllowComment())
+ r.SetAllowDeploy(input.GetAllowDeploy())
+ r.SetAllowPull(input.GetAllowPull())
+ r.SetAllowPush(input.GetAllowPush())
+ r.SetAllowTag(input.GetAllowTag())
+ }
+
+ if len(input.GetPipelineType()) == 0 {
+ r.SetPipelineType(constants.PipelineTypeYAML)
+ } else {
+ // ensure the pipeline type matches one of the expected values
+ if input.GetPipelineType() != constants.PipelineTypeYAML &&
+ input.GetPipelineType() != constants.PipelineTypeGo &&
+ input.GetPipelineType() != constants.PipelineTypeStarlark {
+ retErr := fmt.Errorf("unable to create new repo %s: invalid pipeline_type provided %s", r.GetFullName(), input.GetPipelineType())
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+ r.SetPipelineType(input.GetPipelineType())
+ }
+
+ // create unique id for the repo
+ uid, err := uuid.NewRandom()
+ if err != nil {
+ retErr := fmt.Errorf("unable to create UID for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusServiceUnavailable, retErr)
+
+ return
+ }
+
+ r.SetHash(
+ base64.StdEncoding.EncodeToString(
+ []byte(strings.TrimSpace(uid.String())),
+ ),
+ )
+
+ // ensure repo is allowed to be activated
+ if !util.CheckAllowlist(r, allowlist) {
+ retErr := fmt.Errorf("unable to activate repo: %s is not on allowlist", r.GetFullName())
+
+ util.HandleError(c, http.StatusForbidden, retErr)
+
+ return
+ }
+
+ // send API call to capture the repo from the database
+ dbRepo, err := database.FromContext(c).GetRepoForOrg(ctx, r.GetOrg(), r.GetName())
+ if err == nil && dbRepo.GetActive() {
+ retErr := fmt.Errorf("unable to activate repo: %s is already active", r.GetFullName())
+
+ util.HandleError(c, http.StatusConflict, retErr)
+
+ return
+ }
+
+ // check if the repo already has a hash created
+ if len(dbRepo.GetHash()) > 0 {
+ // overwrite the new repo hash with the existing repo hash
+ r.SetHash(dbRepo.GetHash())
+ }
+
+ h := new(library.Hook)
+
+ // err being nil means we have a record of this repo (dbRepo)
+ if err == nil {
+ h, _ = database.FromContext(c).LastHookForRepo(dbRepo)
+
+ // make sure our record of the repo allowed events matches what we send to SCM
+ // what the dbRepo has should override default events on enable
+ r.SetAllowComment(dbRepo.GetAllowComment())
+ r.SetAllowDeploy(dbRepo.GetAllowDeploy())
+ r.SetAllowPull(dbRepo.GetAllowPull())
+ r.SetAllowPush(dbRepo.GetAllowPush())
+ r.SetAllowTag(dbRepo.GetAllowTag())
+ }
+
+ // check if we should create the webhook
+ if c.Value("webhookvalidation").(bool) {
+ // send API call to create the webhook
+ h, _, err = scm.FromContext(c).Enable(u, r, h)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create webhook for %s: %w", r.GetFullName(), err)
+
+ switch err.Error() {
+ case "repo already enabled":
+ util.HandleError(c, http.StatusConflict, retErr)
+ return
+ case "repo not found":
+ util.HandleError(c, http.StatusNotFound, retErr)
+ return
+ }
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ }
+
+ // if the repo exists but is inactive
+ if len(dbRepo.GetOrg()) > 0 && !dbRepo.GetActive() {
+ // update the repo owner
+ dbRepo.SetUserID(u.GetID())
+ // update the default branch
+ dbRepo.SetBranch(r.GetBranch())
+ // activate the repo
+ dbRepo.SetActive(true)
+
+ // send API call to update the repo
+ r, err = database.FromContext(c).UpdateRepo(ctx, dbRepo)
+ if err != nil {
+ retErr := fmt.Errorf("unable to set repo %s to active: %w", dbRepo.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ } else {
+ // send API call to create the repo
+ r, err = database.FromContext(c).CreateRepo(ctx, r)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create new repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ }
+
+ // create init hook in the DB after repo has been added in order to capture its ID
+ if c.Value("webhookvalidation").(bool) {
+ // update initialization hook
+ h.SetRepoID(r.GetID())
+ // create first hook for repo in the database
+ _, err = database.FromContext(c).CreateHook(h)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create initialization webhook for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ }
+
+ c.JSON(http.StatusCreated, r)
+}
diff --git a/api/repo/delete.go b/api/repo/delete.go
new file mode 100644
index 000000000..4ddaf11f7
--- /dev/null
+++ b/api/repo/delete.go
@@ -0,0 +1,110 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation DELETE /api/v1/repos/{org}/{repo} repos DeleteRepo
+//
+// Delete a repo in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully deleted the repo
+// schema:
+// type: string
+// '500':
+// description: Unable to deleted the repo
+// schema:
+// "$ref": "#/definitions/Error"
+// '510':
+// description: Unable to deleted the repo
+// schema:
+// "$ref": "#/definitions/Error"
+
+// DeleteRepo represents the API handler to remove
+// a repo from the configured backend.
+func DeleteRepo(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("deleting repo %s", r.GetFullName())
+
+ // send API call to remove the webhook
+ err := scm.FromContext(c).Disable(u, r.GetOrg(), r.GetName())
+ if err != nil {
+ retErr := fmt.Errorf("unable to delete webhook for %s: %w", r.GetFullName(), err)
+
+ if err.Error() == "Repo not found" {
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // Mark the repo as inactive
+ r.SetActive(false)
+
+ _, err = database.FromContext(c).UpdateRepo(ctx, r)
+ if err != nil {
+ retErr := fmt.Errorf("unable to set repo %s to inactive: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // Comment out actual delete until delete mechanism is fleshed out
+ // err = database.FromContext(c).DeleteRepo(r.ID)
+ // if err != nil {
+ // retErr := fmt.Errorf("Error while deleting repo %s: %w", r.FullName, err)
+ // util.HandleError(c, http.StatusInternalServerError, retErr)
+ // return
+ // }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("repo %s set to inactive", r.GetFullName()))
+}
diff --git a/api/repo/doc.go b/api/repo/doc.go
new file mode 100644
index 000000000..82eb5304e
--- /dev/null
+++ b/api/repo/doc.go
@@ -0,0 +1,10 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+// Package repo provides the repo handlers for the Vela API.
+//
+// Usage:
+//
+// import "github.com/go-vela/server/api/repo"
+package repo
diff --git a/api/repo/get.go b/api/repo/get.go
new file mode 100644
index 000000000..65cbede1b
--- /dev/null
+++ b/api/repo/get.go
@@ -0,0 +1,61 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/repos/{org}/{repo} repos GetRepo
+//
+// Get a repo in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the repo
+// schema:
+// "$ref": "#/definitions/Repo"
+
+// GetRepo represents the API handler to
+// capture a repo from the configured backend.
+func GetRepo(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("reading repo %s", r.GetFullName())
+
+ c.JSON(http.StatusOK, r)
+}
diff --git a/api/repo/list.go b/api/repo/list.go
new file mode 100644
index 000000000..77c127b3c
--- /dev/null
+++ b/api/repo/list.go
@@ -0,0 +1,131 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/api"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/repos repos ListRepos
+//
+// Get all repos in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// security:
+// - ApiKeyAuth: []
+// parameters:
+// - in: query
+// name: page
+// description: The page of results to retrieve
+// type: integer
+// default: 1
+// - in: query
+// name: per_page
+// description: How many results per page to return
+// type: integer
+// maximum: 100
+// default: 10
+// responses:
+// '200':
+// description: Successfully retrieved the repo
+// schema:
+// type: array
+// items:
+// "$ref": "#/definitions/Repo"
+// headers:
+// X-Total-Count:
+// description: Total number of results
+// type: integer
+// Link:
+// description: see https://tools.ietf.org/html/rfc5988
+// type: string
+// '400':
+// description: Unable to retrieve the repo
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to retrieve the repo
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ListRepos represents the API handler to capture a list
+// of repos for a user from the configured backend.
+func ListRepos(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ }).Infof("listing repos for user %s", u.GetName())
+
+ // capture page query parameter if present
+ page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert page query parameter for user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // capture per_page query parameter if present
+ perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert per_page query parameter for user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // ensure per_page isn't above or below allowed values
+ perPage = util.MaxInt(1, util.MinInt(100, perPage))
+
+ // capture the sort_by query parameter if present
+ sortBy := util.QueryParameter(c, "sort_by", "name")
+
+ // capture the query parameters if present:
+ //
+ // * active
+ filters := map[string]interface{}{
+ "active": util.QueryParameter(c, "active", "true"),
+ }
+
+ // send API call to capture the list of repos for the user
+ r, t, err := database.FromContext(c).ListReposForUser(ctx, u, sortBy, filters, page, perPage)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get repos for user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // create pagination object
+ pagination := api.Pagination{
+ Page: page,
+ PerPage: perPage,
+ Total: t,
+ }
+ // set pagination headers
+ pagination.SetHeaderLink(c)
+
+ c.JSON(http.StatusOK, r)
+}
diff --git a/api/repo/list_org.go b/api/repo/list_org.go
new file mode 100644
index 000000000..727b91b5d
--- /dev/null
+++ b/api/repo/list_org.go
@@ -0,0 +1,164 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/api"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/repos/{org} repos ListReposForOrg
+//
+// Get all repos for the provided org in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// security:
+// - ApiKeyAuth: []
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: query
+// name: active
+// description: Filter active repos
+// type: boolean
+// default: true
+// - in: query
+// name: page
+// description: The page of results to retrieve
+// type: integer
+// default: 1
+// - in: query
+// name: per_page
+// description: How many results per page to return
+// type: integer
+// maximum: 100
+// default: 10
+// - in: query
+// name: sort_by
+// description: How to sort the results
+// type: string
+// enum:
+// - name
+// - latest
+// default: name
+// responses:
+// '200':
+// description: Successfully retrieved the repo
+// schema:
+// type: array
+// items:
+// "$ref": "#/definitions/Repo"
+// headers:
+// X-Total-Count:
+// description: Total number of results
+// type: integer
+// Link:
+// description: see https://tools.ietf.org/html/rfc5988
+// type: string
+// '400':
+// description: Unable to retrieve the org
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to retrieve the org
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ListReposForOrg represents the API handler to capture a list
+// of repos for an org from the configured backend.
+func ListReposForOrg(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "user": u.GetName(),
+ }).Infof("listing repos for org %s", o)
+
+ // capture page query parameter if present
+ page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert page query parameter for user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // capture per_page query parameter if present
+ perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert per_page query parameter for user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // ensure per_page isn't above or below allowed values
+ perPage = util.MaxInt(1, util.MinInt(100, perPage))
+
+ // capture the sort_by query parameter if present
+ sortBy := util.QueryParameter(c, "sort_by", "name")
+
+ // capture the query parameters if present:
+ //
+ // * active
+ filters := map[string]interface{}{
+ "active": util.QueryParameter(c, "active", "true"),
+ }
+
+ // See if the user is an org admin to bypass individual permission checks
+ perm, err := scm.FromContext(c).OrgAccess(u, o)
+ if err != nil {
+ logrus.Errorf("unable to get user %s access level for org %s", u.GetName(), o)
+ }
+ // Only show public repos to non-admins
+ if perm != "admin" {
+ filters["visibility"] = constants.VisibilityPublic
+ }
+
+ // send API call to capture the list of repos for the org
+ r, t, err := database.FromContext(c).ListReposForOrg(ctx, o, sortBy, filters, page, perPage)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get repos for org %s: %w", o, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // create pagination object
+ pagination := api.Pagination{
+ Page: page,
+ PerPage: perPage,
+ Total: t,
+ }
+ // set pagination headers
+ pagination.SetHeaderLink(c)
+
+ c.JSON(http.StatusOK, r)
+}
diff --git a/api/repo/repair.go b/api/repo/repair.go
new file mode 100644
index 000000000..337f52d91
--- /dev/null
+++ b/api/repo/repair.go
@@ -0,0 +1,137 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation PATCH /api/v1/repos/{org}/{repo}/repair repos RepairRepo
+//
+// Remove and recreate the webhook for a repo
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully repaired the repo
+// schema:
+// type: string
+// '500':
+// description: Unable to repair the repo
+// schema:
+// "$ref": "#/definitions/Error"
+
+// RepairRepo represents the API handler to remove
+// and then create a webhook for a repo.
+func RepairRepo(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("repairing repo %s", r.GetFullName())
+
+ // check if we should create the webhook
+ if c.Value("webhookvalidation").(bool) {
+ // send API call to remove the webhook
+ err := scm.FromContext(c).Disable(u, r.GetOrg(), r.GetName())
+ if err != nil {
+ retErr := fmt.Errorf("unable to delete webhook for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ hook, err := database.FromContext(c).LastHookForRepo(r)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get last hook for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // send API call to create the webhook
+ hook, _, err = scm.FromContext(c).Enable(u, r, hook)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create webhook for %s: %w", r.GetFullName(), err)
+
+ switch err.Error() {
+ case "repo already enabled":
+ util.HandleError(c, http.StatusConflict, retErr)
+ return
+ case "repo not found":
+ util.HandleError(c, http.StatusNotFound, retErr)
+ return
+ }
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ hook.SetRepoID(r.GetID())
+
+ _, err = database.FromContext(c).CreateHook(hook)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create initialization webhook for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ }
+
+ // if the repo was previously inactive, mark it as active
+ if !r.GetActive() {
+ r.SetActive(true)
+
+ // send API call to update the repo
+ _, err := database.FromContext(c).UpdateRepo(ctx, r)
+ if err != nil {
+ retErr := fmt.Errorf("unable to set repo %s to active: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("repo %s repaired", r.GetFullName()))
+}
diff --git a/api/repo/update.go b/api/repo/update.go
new file mode 100644
index 000000000..a7684c393
--- /dev/null
+++ b/api/repo/update.go
@@ -0,0 +1,309 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "encoding/base64"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/google/uuid"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation PUT /api/v1/repos/{org}/{repo} repos UpdateRepo
+//
+// Update a repo in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: body
+// name: body
+// description: Payload containing the repo to update
+// required: true
+// schema:
+// "$ref": "#/definitions/Repo"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully updated the repo
+// schema:
+// "$ref": "#/definitions/Repo"
+// '400':
+// description: Unable to update the repo
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to update the repo
+// schema:
+// "$ref": "#/definitions/Error"
+// '503':
+// description: Unable to update the repo
+// schema:
+// "$ref": "#/definitions/Error"
+
+// UpdateRepo represents the API handler to update
+// a repo in the configured backend.
+//
+//nolint:funlen,gocyclo // ignore function length
+func UpdateRepo(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ maxBuildLimit := c.Value("maxBuildLimit").(int64)
+ ctx := c.Request.Context()
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("updating repo %s", r.GetFullName())
+
+ // capture body from API request
+ input := new(library.Repo)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ eventsChanged := false
+
+ // update repo fields if provided
+ if len(input.GetBranch()) > 0 {
+ // update branch if set
+ r.SetBranch(input.GetBranch())
+ }
+
+ // update build limit if set
+ if input.GetBuildLimit() > 0 {
+ // allow build limit between 1 - value configured by server
+ r.SetBuildLimit(
+ int64(
+ util.MaxInt(
+ constants.BuildLimitMin,
+ util.MinInt(
+ int(input.GetBuildLimit()),
+ int(maxBuildLimit),
+ ), // clamp max
+ ), // clamp min
+ ),
+ )
+ }
+
+ if input.GetTimeout() > 0 {
+ // update build timeout if set
+ r.SetTimeout(
+ int64(
+ util.MaxInt(
+ constants.BuildTimeoutMin,
+ util.MinInt(
+ int(input.GetTimeout()),
+ constants.BuildTimeoutMax,
+ ), // clamp max
+ ), // clamp min
+ ),
+ )
+ }
+
+ if input.GetCounter() > 0 {
+ if input.GetCounter() <= r.GetCounter() {
+ retErr := fmt.Errorf("unable to set counter for repo %s: must be greater than current %d",
+ r.GetFullName(), r.GetCounter())
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ r.SetCounter(input.GetCounter())
+ }
+
+ if len(input.GetVisibility()) > 0 {
+ // update visibility if set
+ r.SetVisibility(input.GetVisibility())
+ }
+
+ if input.Private != nil {
+ // update private if set
+ r.SetPrivate(input.GetPrivate())
+ }
+
+ if input.Active != nil {
+ // update active if set
+ r.SetActive(input.GetActive())
+ }
+
+ if input.AllowPull != nil {
+ // update allow_pull if set
+ r.SetAllowPull(input.GetAllowPull())
+
+ eventsChanged = true
+ }
+
+ if input.AllowPush != nil {
+ // update allow_push if set
+ r.SetAllowPush(input.GetAllowPush())
+
+ eventsChanged = true
+ }
+
+ if input.AllowDeploy != nil {
+ // update allow_deploy if set
+ r.SetAllowDeploy(input.GetAllowDeploy())
+
+ eventsChanged = true
+ }
+
+ if input.AllowTag != nil {
+ // update allow_tag if set
+ r.SetAllowTag(input.GetAllowTag())
+
+ eventsChanged = true
+ }
+
+ if input.AllowComment != nil {
+ // update allow_comment if set
+ r.SetAllowComment(input.GetAllowComment())
+
+ eventsChanged = true
+ }
+
+ // set default events if no events are enabled
+ if !r.GetAllowPull() && !r.GetAllowPush() &&
+ !r.GetAllowDeploy() && !r.GetAllowTag() &&
+ !r.GetAllowComment() {
+ r.SetAllowPull(true)
+ r.SetAllowPush(true)
+ }
+
+ if len(input.GetPipelineType()) != 0 {
+ // ensure the pipeline type matches one of the expected values
+ if input.GetPipelineType() != constants.PipelineTypeYAML &&
+ input.GetPipelineType() != constants.PipelineTypeGo &&
+ input.GetPipelineType() != constants.PipelineTypeStarlark {
+ retErr := fmt.Errorf("pipeline_type of %s is invalid", input.GetPipelineType())
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ r.SetPipelineType(input.GetPipelineType())
+ }
+
+ // set hash for repo if no hash is already set
+ if len(r.GetHash()) == 0 {
+ // create unique id for the repo
+ uid, err := uuid.NewRandom()
+ if err != nil {
+ retErr := fmt.Errorf("unable to create UID for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusServiceUnavailable, retErr)
+
+ return
+ }
+
+ r.SetHash(
+ base64.StdEncoding.EncodeToString(
+ []byte(strings.TrimSpace(uid.String())),
+ ),
+ )
+ }
+
+ // fields restricted to platform admins
+ if u.GetAdmin() {
+ // trusted
+ if input.GetTrusted() != r.GetTrusted() {
+ r.SetTrusted(input.GetTrusted())
+ }
+ }
+
+ // if webhook validation is not set or events didn't change, skip webhook update
+ if c.Value("webhookvalidation").(bool) && eventsChanged {
+ // grab last hook from repo to fetch the webhook ID
+ lastHook, err := database.FromContext(c).LastHookForRepo(r)
+ if err != nil {
+ retErr := fmt.Errorf("unable to retrieve last hook for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ // if user is platform admin, fetch the repo owner token to make changes to webhook
+ if u.GetAdmin() {
+ // capture admin name for logging
+ admn := u.GetName()
+
+ u, err = database.FromContext(c).GetUser(r.GetUserID())
+ if err != nil {
+ retErr := fmt.Errorf("unable to get repo owner of %s for platform admin webhook update: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // log admin override update repo hook
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("platform admin %s updating repo webhook events for repo %s", admn, r.GetFullName())
+ }
+ // update webhook with new events
+ err = scm.FromContext(c).Update(u, r, lastHook.GetWebhookID())
+ if err != nil {
+ retErr := fmt.Errorf("unable to update repo webhook for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ }
+
+ // send API call to update the repo
+ r, err = database.FromContext(c).UpdateRepo(ctx, r)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, r)
+}
diff --git a/api/schedule/create.go b/api/schedule/create.go
new file mode 100644
index 000000000..667eb3ca9
--- /dev/null
+++ b/api/schedule/create.go
@@ -0,0 +1,241 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/adhocore/gronx"
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/schedules/{org}/{repo} schedules CreateSchedule
+//
+// Create a schedule in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: body
+// name: body
+// description: Payload containing the schedule to create
+// required: true
+// schema:
+// "$ref": "#/definitions/Schedule"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '201':
+// description: Successfully created the schedule
+// schema:
+// "$ref": "#/definitions/Schedule"
+// '400':
+// description: Unable to create the schedule
+// schema:
+// "$ref": "#/definitions/Error"
+// '403':
+// description: Unable to create the schedule
+// schema:
+// "$ref": "#/definitions/Error"
+// '409':
+// description: Unable to create the schedule
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to create the schedule
+// schema:
+// "$ref": "#/definitions/Error"
+// '503':
+// description: Unable to create the schedule
+// schema:
+// "$ref": "#/definitions/Error"
+
+// CreateSchedule represents the API handler to
+// create a schedule in the configured backend.
+func CreateSchedule(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+ r := repo.Retrieve(c)
+ ctx := c.Request.Context()
+ allowlist := c.Value("allowlistschedule").([]string)
+ minimumFrequency := c.Value("scheduleminimumfrequency").(time.Duration)
+
+ // capture body from API request
+ input := new(library.Schedule)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for new schedule: %w", err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // ensure the entry is valid
+ err = validateEntry(minimumFrequency, input.GetEntry())
+ if err != nil {
+ retErr := fmt.Errorf("schedule of %s with entry %s is invalid: %w", input.GetName(), input.GetEntry(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // ensure schedule name is defined
+ if input.GetName() == "" {
+ util.HandleError(c, http.StatusBadRequest, fmt.Errorf("schedule name must be set"))
+ }
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("creating new schedule %s", input.GetName())
+
+ // ensure repo is allowed to create new schedules
+ if !util.CheckAllowlist(r, allowlist) {
+ retErr := fmt.Errorf("unable to create schedule %s: %s is not on allowlist", input.GetName(), r.GetFullName())
+
+ util.HandleError(c, http.StatusForbidden, retErr)
+
+ return
+ }
+
+ s := new(library.Schedule)
+
+ // update fields in schedule object
+ s.SetCreatedBy(u.GetName())
+ s.SetRepoID(r.GetID())
+ s.SetName(input.GetName())
+ s.SetEntry(input.GetEntry())
+ s.SetCreatedAt(time.Now().UTC().Unix())
+ s.SetUpdatedAt(time.Now().UTC().Unix())
+ s.SetUpdatedBy(u.GetName())
+
+ if input.GetBranch() == "" {
+ s.SetBranch(r.GetBranch())
+ } else {
+ s.SetBranch(input.GetBranch())
+ }
+
+ // set the active field based off the input provided
+ if input.Active == nil {
+ // default active field to true
+ s.SetActive(true)
+ } else {
+ s.SetActive(input.GetActive())
+ }
+
+ // send API call to capture the schedule from the database
+ dbSchedule, err := database.FromContext(c).GetScheduleForRepo(ctx, r, input.GetName())
+ if err == nil && dbSchedule.GetActive() {
+ retErr := fmt.Errorf("unable to create schedule: %s is already active", input.GetName())
+
+ util.HandleError(c, http.StatusConflict, retErr)
+
+ return
+ }
+
+ if !r.GetActive() {
+ retErr := fmt.Errorf("unable to create schedule: %s repo %s is disabled", input.GetName(), r.GetFullName())
+
+ util.HandleError(c, http.StatusConflict, retErr)
+
+ return
+ }
+
+ // if the schedule exists but is inactive
+ if dbSchedule.GetID() != 0 && !dbSchedule.GetActive() && input.GetActive() {
+ // update the user who created the schedule
+ dbSchedule.SetUpdatedBy(u.GetName())
+ // activate the schedule
+ dbSchedule.SetActive(true)
+
+ // send API call to update the schedule
+ s, err = database.FromContext(c).UpdateSchedule(ctx, dbSchedule, true)
+ if err != nil {
+ retErr := fmt.Errorf("unable to set schedule %s to active: %w", dbSchedule.GetName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ } else {
+ // send API call to create the schedule
+ s, err = database.FromContext(c).CreateSchedule(ctx, s)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create new schedule %s: %w", r.GetName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ }
+
+ c.JSON(http.StatusCreated, s)
+}
+
+// validateEntry validates the entry for a minimum frequency.
+func validateEntry(minimum time.Duration, entry string) error {
+ gron := gronx.New()
+
+ // check if expr is even valid
+ valid := gron.IsValid(entry)
+ if !valid {
+ return fmt.Errorf("invalid entry of %s", entry)
+ }
+
+ // iterate 5 times through ticks in an effort to catch scalene entries
+ tickForward := 5
+
+ // start with now
+ t := time.Now().UTC()
+
+ for i := 0; i < tickForward; i++ {
+ // check the previous occurrence of the entry
+ prevTime, err := gronx.PrevTickBefore(entry, t, true)
+ if err != nil {
+ return err
+ }
+
+ // check the next occurrence of the entry
+ nextTime, err := gronx.NextTickAfter(entry, t, false)
+ if err != nil {
+ return err
+ }
+
+ // ensure the time between previous and next schedule exceeds the minimum duration
+ if nextTime.Sub(prevTime) < minimum {
+ return fmt.Errorf("entry needs to occur less frequently than every %s", minimum)
+ }
+
+ t = nextTime
+ }
+
+ return nil
+}
diff --git a/api/schedule/create_test.go b/api/schedule/create_test.go
new file mode 100644
index 000000000..a2956f6ca
--- /dev/null
+++ b/api/schedule/create_test.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 schedule
+
+import (
+ "testing"
+ "time"
+)
+
+func Test_validateEntry(t *testing.T) {
+ type args struct {
+ minimum time.Duration
+ entry string
+ }
+ tests := []struct {
+ name string
+ args args
+ wantErr bool
+ }{
+ {
+ name: "exceeds minimum frequency",
+ args: args{
+ minimum: 30 * time.Minute,
+ entry: "* * * * *",
+ },
+ wantErr: true,
+ },
+ {
+ name: "exceeds minimum frequency with tag",
+ args: args{
+ minimum: 30 * time.Minute,
+ entry: "@15minutes",
+ },
+ wantErr: true,
+ },
+ {
+ name: "exceeds minimum frequency with scalene entry pattern",
+ args: args{
+ minimum: 30 * time.Minute,
+ entry: "1,2,45 * * * *",
+ },
+ wantErr: true,
+ },
+ {
+ name: "meets minimum frequency",
+ args: args{
+ minimum: 30 * time.Second,
+ entry: "* * * * *",
+ },
+ wantErr: false,
+ },
+ {
+ name: "meets minimum frequency with tag",
+ args: args{
+ minimum: 30 * time.Second,
+ entry: "@hourly",
+ },
+ wantErr: false,
+ },
+ {
+ name: "meets minimum frequency with comma entry pattern",
+ args: args{
+ minimum: 15 * time.Minute,
+ entry: "0,15,30,45 * * * *",
+ },
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if err := validateEntry(tt.args.minimum, tt.args.entry); (err != nil) != tt.wantErr {
+ t.Errorf("validateEntry() error = %v, wantErr %v", err, tt.wantErr)
+ }
+ })
+ }
+}
diff --git a/api/schedule/delete.go b/api/schedule/delete.go
new file mode 100644
index 000000000..9c6f1dda8
--- /dev/null
+++ b/api/schedule/delete.go
@@ -0,0 +1,89 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/schedule"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation DELETE /api/v1/repos/{org}/{repo}/{schedule} schedules DeleteSchedule
+//
+// Delete a schedule in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: schedule
+// description: Name of the schedule
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully deleted the schedule
+// schema:
+// type: string
+// '500':
+// description: Unable to delete the schedule
+// schema:
+// "$ref": "#/definitions/Error"
+// '510':
+// description: Unable to delete the schedule
+// schema:
+// "$ref": "#/definitions/Error"
+
+// DeleteSchedule represents the API handler to remove
+// a schedule from the configured backend.
+func DeleteSchedule(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ s := schedule.Retrieve(c)
+ ctx := c.Request.Context()
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("deleting schedule %s", s.GetName())
+
+ err := database.FromContext(c).DeleteSchedule(ctx, s)
+ if err != nil {
+ retErr := fmt.Errorf("unable to delete schedule %s: %w", s.GetName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("schedule %s deleted", s.GetName()))
+}
diff --git a/api/schedule/get.go b/api/schedule/get.go
new file mode 100644
index 000000000..727de5b84
--- /dev/null
+++ b/api/schedule/get.go
@@ -0,0 +1,69 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/schedule"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/schedules/{org}/{repo}/{schedule} schedules GetSchedule
+//
+// Get a schedule in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: schedule
+// description: Name of the schedule
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the schedule
+// schema:
+// "$ref": "#/definitions/Schedule"
+
+// GetSchedule represents the API handler to
+// capture a schedule from the configured backend.
+func GetSchedule(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ s := schedule.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ "schedule": s.GetName(),
+ }).Infof("reading schedule %s", s.GetName())
+
+ c.JSON(http.StatusOK, s)
+}
diff --git a/api/schedule/list.go b/api/schedule/list.go
new file mode 100644
index 000000000..a3d6d6e14
--- /dev/null
+++ b/api/schedule/list.go
@@ -0,0 +1,132 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/api"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/schedules/{org}/{repo} schedules ListSchedules
+//
+// Get all schedules in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// security:
+// - ApiKeyAuth: []
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: query
+// name: page
+// description: The page of results to retrieve
+// type: integer
+// default: 1
+// - in: query
+// name: per_page
+// description: How many results per page to return
+// type: integer
+// maximum: 100
+// default: 10
+// responses:
+// '200':
+// description: Successfully retrieved the schedules
+// schema:
+// type: array
+// items:
+// "$ref": "#/definitions/Schedule"
+// headers:
+// X-Total-Count:
+// description: Total number of results
+// type: integer
+// Link:
+// description: see https://tools.ietf.org/html/rfc5988
+// type: string
+// '400':
+// description: Unable to retrieve the schedules
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to retrieve the schedules
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ListSchedules represents the API handler to capture a list
+// of schedules for a repo from the configured backend.
+func ListSchedules(c *gin.Context) {
+ // capture middleware values
+ r := repo.Retrieve(c)
+ ctx := c.Request.Context()
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "repo": r.GetName(),
+ "org": r.GetOrg(),
+ }).Infof("listing schedules for repo %s", r.GetFullName())
+
+ // capture page query parameter if present
+ page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert page query parameter for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // capture per_page query parameter if present
+ perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert per_page query parameter for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // ensure per_page isn't above or below allowed values
+ perPage = util.MaxInt(1, util.MinInt(100, perPage))
+
+ // send API call to capture the list of schedules for the repo
+ s, t, err := database.FromContext(c).ListSchedulesForRepo(ctx, r, page, perPage)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get schedules for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // create pagination object
+ pagination := api.Pagination{
+ Page: page,
+ PerPage: perPage,
+ Total: t,
+ }
+ // set pagination headers
+ pagination.SetHeaderLink(c)
+
+ c.JSON(http.StatusOK, s)
+}
diff --git a/api/schedule/update.go b/api/schedule/update.go
new file mode 100644
index 000000000..6c2a8b9b6
--- /dev/null
+++ b/api/schedule/update.go
@@ -0,0 +1,145 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/schedule"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation PUT /api/v1/schedules/{org}/{repo}/{schedule} schedules UpdateSchedule
+//
+// Update a schedule for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: schedule
+// description: Name of the schedule
+// required: true
+// type: string
+// - in: body
+// name: body
+// description: Payload containing the schedule to update
+// required: true
+// schema:
+// "$ref": "#/definitions/Schedule"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully updated the schedule
+// schema:
+// "$ref": "#/definitions/Schedule"
+// '400':
+// description: Unable to update the schedule
+// schema:
+// "$ref": "#/definitions/Error"
+// '404':
+// description: Unable to update the schedule
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to update the schedule
+// schema:
+// "$ref": "#/definitions/Error"
+
+// UpdateSchedule represents the API handler to update
+// a schedule in the configured backend.
+func UpdateSchedule(c *gin.Context) {
+ // capture middleware values
+ r := repo.Retrieve(c)
+ s := schedule.Retrieve(c)
+ ctx := c.Request.Context()
+ u := user.Retrieve(c)
+ scheduleName := util.PathParameter(c, "schedule")
+ minimumFrequency := c.Value("scheduleminimumfrequency").(time.Duration)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "schedule": scheduleName,
+ "repo": r.GetName(),
+ "org": r.GetOrg(),
+ }).Infof("updating schedule %s", scheduleName)
+
+ // capture body from API request
+ input := new(library.Schedule)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for schedule %s: %w", scheduleName, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update schedule fields if provided
+ if input.Active != nil {
+ // update active if set to true
+ s.SetActive(input.GetActive())
+ }
+
+ if input.GetName() != "" {
+ // update name if defined
+ s.SetName(input.GetName())
+ }
+
+ if input.GetEntry() != "" {
+ err = validateEntry(minimumFrequency, input.GetEntry())
+ if err != nil {
+ retErr := fmt.Errorf("schedule entry of %s is invalid: %w", input.GetEntry(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update entry if defined
+ s.SetEntry(input.GetEntry())
+ }
+
+ // set the updated by field using claims
+ s.SetUpdatedBy(u.GetName())
+ if input.GetBranch() != "" {
+ s.SetBranch(input.GetBranch())
+ }
+
+ // update the schedule within the database
+ s, err = database.FromContext(c).UpdateSchedule(ctx, s, true)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update scheduled %s: %w", scheduleName, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, s)
+}
diff --git a/api/scm/doc.go b/api/scm/doc.go
new file mode 100644
index 000000000..5d66cafed
--- /dev/null
+++ b/api/scm/doc.go
@@ -0,0 +1,10 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+// Package scm provides the scm handlers for the Vela API.
+//
+// Usage:
+//
+// import "github.com/go-vela/server/api/scm"
+package scm
diff --git a/api/scm/sync.go b/api/scm/sync.go
new file mode 100644
index 000000000..dc264f264
--- /dev/null
+++ b/api/scm/sync.go
@@ -0,0 +1,138 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package scm
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/scm/repos/{org}/{repo}/sync scm SyncRepo
+//
+// Sync up scm service and database in the context of a specific repo
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully synchronized repo
+// schema:
+// type: string
+// '500':
+// description: Unable to synchronize repo
+// schema:
+// "$ref": "#/definitions/Error"
+
+// SyncRepo represents the API handler to
+// synchronize a single repository between
+// SCM service and the database should a discrepancy
+// exist. Primarily used for deleted repos or to align
+// subscribed events with allowed events.
+func SyncRepo(c *gin.Context) {
+ // capture middleware values
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logger := logrus.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ })
+
+ logger.Infof("syncing repo %s", r.GetFullName())
+
+ // retrieve repo from source code manager service
+ _, err := scm.FromContext(c).GetRepo(u, r)
+
+ // if there is an error retrieving repo, we know it is deleted: set to inactive
+ if err != nil {
+ // set repo to inactive - do not delete
+ r.SetActive(false)
+
+ // update repo in database
+ _, err := database.FromContext(c).UpdateRepo(ctx, r)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update repo for org %s: %w", o, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // exit with success as hook sync will be unnecessary
+ c.JSON(http.StatusOK, fmt.Sprintf("repo %s synced", r.GetFullName()))
+
+ return
+ }
+
+ // verify the user is an admin of the repo
+ // we cannot use our normal permissions check due to the possibility the repo was deleted
+ perm, err := scm.FromContext(c).RepoAccess(u, u.GetToken(), o, r.GetName())
+ if err != nil {
+ logger.Errorf("unable to get user %s access level for org %s", u.GetName(), o)
+ }
+
+ if !strings.EqualFold(perm, "admin") {
+ retErr := fmt.Errorf("user %s does not have 'admin' permissions for the repo %s", u.GetName(), r.GetFullName())
+
+ util.HandleError(c, http.StatusUnauthorized, retErr)
+
+ return
+ }
+
+ // if we have webhook validation, update the repo hook in the SCM
+ if c.Value("webhookvalidation").(bool) {
+ // grab last hook from repo to fetch the webhook ID
+ lastHook, err := database.FromContext(c).LastHookForRepo(r)
+ if err != nil {
+ retErr := fmt.Errorf("unable to retrieve last hook for repo %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // update webhook
+ err = scm.FromContext(c).Update(u, r, lastHook.GetWebhookID())
+ if err != nil {
+ retErr := fmt.Errorf("unable to update repo webhook for %s: %w", r.GetFullName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("repo %s synced", r.GetFullName()))
+}
diff --git a/api/scm.go b/api/scm/sync_org.go
similarity index 52%
rename from api/scm.go
rename to api/scm/sync_org.go
index 69ed13e2c..944a33adc 100644
--- a/api/scm.go
+++ b/api/scm/sync_org.go
@@ -1,8 +1,8 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
-package api
+package scm
import (
"fmt"
@@ -11,7 +11,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/go-vela/server/database"
"github.com/go-vela/server/router/middleware/org"
- "github.com/go-vela/server/router/middleware/repo"
"github.com/go-vela/server/router/middleware/user"
"github.com/go-vela/server/scm"
"github.com/go-vela/server/util"
@@ -19,7 +18,7 @@ import (
"github.com/sirupsen/logrus"
)
-// swagger:operation GET /api/v1/scm/orgs/{org}/sync scm SyncRepos
+// swagger:operation GET /api/v1/scm/orgs/{org}/sync scm SyncReposForOrg
//
// Sync up repos from scm service and database in a specified org
//
@@ -44,14 +43,16 @@ import (
// schema:
// "$ref": "#/definitions/Error"
-// SyncRepos represents the API handler to
+// SyncReposForOrg represents the API handler to
// synchronize organization repositories between
// SCM Service and the database should a discrepancy
-// exist. Common after deleting SCM repos.
-func SyncRepos(c *gin.Context) {
+// exist. Primarily used for deleted repos or to align
+// subscribed events with allowed events.
+func SyncReposForOrg(c *gin.Context) {
// capture middleware values
o := org.Retrieve(c)
u := user.Retrieve(c)
+ ctx := c.Request.Context()
// update engine logger with API metadata
//
@@ -63,20 +64,23 @@ func SyncRepos(c *gin.Context) {
logger.Infof("syncing repos for org %s", o)
- // See if the user is an org admin to bypass individual permission checks
+ // see if the user is an org admin
perm, err := scm.FromContext(c).OrgAccess(u, o)
if err != nil {
logger.Errorf("unable to get user %s access level for org %s", u.GetName(), o)
}
- filters := map[string]string{}
- // Only show public repos to non-admins
+ // only allow org-wide syncing if user is admin of org
if perm != "admin" {
- filters["visibility"] = "public"
+ retErr := fmt.Errorf("unable to sync repos in org %s: must be an org admin", o)
+
+ util.HandleError(c, http.StatusUnauthorized, retErr)
+
+ return
}
// send API call to capture the total number of repos for the org
- t, err := database.FromContext(c).GetOrgRepoCount(o, filters)
+ t, err := database.FromContext(c).CountReposForOrg(ctx, o, map[string]interface{}{})
if err != nil {
retErr := fmt.Errorf("unable to get repo count for org %s: %w", o, err)
@@ -88,9 +92,8 @@ func SyncRepos(c *gin.Context) {
repos := []*library.Repo{}
page := 0
// capture all repos belonging to a certain org in database
- // nolint: gomnd // ignore magic number
for orgRepos := int64(0); orgRepos < t; orgRepos += 100 {
- r, err := database.FromContext(c).GetOrgRepoList(o, filters, page, 100)
+ r, _, err := database.FromContext(c).ListReposForOrg(ctx, o, "name", map[string]interface{}{}, page, 100)
if err != nil {
retErr := fmt.Errorf("unable to get repo count for org %s: %w", o, err)
@@ -98,7 +101,9 @@ func SyncRepos(c *gin.Context) {
return
}
+
repos = append(repos, r...)
+
page++
}
@@ -109,7 +114,7 @@ func SyncRepos(c *gin.Context) {
if err != nil {
repo.SetActive(false)
- err := database.FromContext(c).UpdateRepo(repo)
+ _, err := database.FromContext(c).UpdateRepo(ctx, repo)
if err != nil {
retErr := fmt.Errorf("unable to update repo for org %s: %w", o, err)
@@ -118,80 +123,30 @@ func SyncRepos(c *gin.Context) {
return
}
}
- }
- c.JSON(http.StatusOK, fmt.Sprintf("org %s repos synced", o))
-}
-
-// swagger:operation GET /api/v1/scm/repos/{org}/{repo}/sync scm SyncRepo
-//
-// Sync up scm service and database in the context of a specific repo
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully synchronized repo
-// schema:
-// type: string
-// '500':
-// description: Unable to synchronize repo
-// schema:
-// "$ref": "#/definitions/Error"
-
-// SyncRepo represents the API handler to
-// synchronize a single repository between
-// SCM service and the database should a discrepancy
-// exist. Common after deleting SCM repos.
-func SyncRepo(c *gin.Context) {
- // capture middleware values
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logger := logrus.WithFields(logrus.Fields{
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- })
-
- logger.Infof("syncing repo %s", r.GetFullName())
+ // if we have webhook validation, update the repo hook in the SCM
+ if c.Value("webhookvalidation").(bool) {
+ // grab last hook from repo to fetch the webhook ID
+ lastHook, err := database.FromContext(c).LastHookForRepo(repo)
+ if err != nil {
+ retErr := fmt.Errorf("unable to retrieve last hook for repo %s: %w", repo.GetFullName(), err)
- // retrieve repo from source code manager service
- _, err := scm.FromContext(c).GetRepo(u, r)
+ util.HandleError(c, http.StatusInternalServerError, retErr)
- // if there is an error retrieving repo, we know it is deleted: sync time
- if err != nil {
- // set repo to inactive - do not delete
- r.SetActive(false)
+ return
+ }
- // update repo in database
- err := database.FromContext(c).UpdateRepo(r)
- if err != nil {
- retErr := fmt.Errorf("unable to update repo for org %s: %w", o, err)
+ // update webhook
+ err = scm.FromContext(c).Update(u, repo, lastHook.GetWebhookID())
+ if err != nil {
+ retErr := fmt.Errorf("unable to update repo webhook for %s: %w", repo.GetFullName(), err)
- util.HandleError(c, http.StatusInternalServerError, retErr)
+ util.HandleError(c, http.StatusInternalServerError, retErr)
- return
+ return
+ }
}
}
- c.JSON(http.StatusOK, fmt.Sprintf("repo %s synced", r.GetFullName()))
+ c.JSON(http.StatusOK, fmt.Sprintf("org %s repos synced", o))
}
diff --git a/api/secret.go b/api/secret.go
deleted file mode 100644
index c870a1a7b..000000000
--- a/api/secret.go
+++ /dev/null
@@ -1,762 +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 api
-
-import (
- "fmt"
- "net/http"
- "strconv"
- "strings"
- "time"
-
- "github.com/go-vela/server/router/middleware/user"
- "github.com/go-vela/server/scm"
- "github.com/go-vela/server/secret"
- "github.com/go-vela/server/util"
-
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/library"
-
- "github.com/gin-gonic/gin"
- "github.com/sirupsen/logrus"
-)
-
-// nolint: lll // ignore long line length due to description
-//
-// swagger:operation POST /api/v1/secrets/{engine}/{type}/{org}/{name} secrets CreateSecret
-//
-// Create a secret
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: engine
-// description: Secret engine to create a secret in, eg. "native"
-// required: true
-// type: string
-// - in: path
-// name: type
-// description: Secret type to create
-// enum:
-// - org
-// - repo
-// - shared
-// required: true
-// type: string
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: name
-// description: Name of the repo if a repo secret, team name if a shared secret, or '*' if an org secret
-// required: true
-// type: string
-// - in: body
-// name: body
-// description: Payload containing the secret to create
-// required: true
-// schema:
-// "$ref": "#/definitions/Secret"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully created the secret
-// schema:
-// "$ref": "#/definitions/Secret"
-// '400':
-// description: Unable to create the secret
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to create the secret
-// schema:
-// "$ref": "#/definitions/Error"
-
-// CreateSecret represents the API handler to
-// create a secret in the configured backend.
-func CreateSecret(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
- e := c.Param("engine")
- t := c.Param("type")
- o := c.Param("org")
- n := c.Param("name")
-
- entry := fmt.Sprintf("%s/%s/%s", t, o, n)
-
- // create log fields from API metadata
- fields := logrus.Fields{
- "engine": e,
- "org": o,
- "repo": n,
- "type": t,
- "user": u.GetName(),
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(t, constants.SecretShared) {
- // update log fields from API metadata
- fields = logrus.Fields{
- "engine": e,
- "org": o,
- "team": n,
- "type": t,
- "user": u.GetName(),
- }
- }
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(fields).Infof("creating new secret %s for %s service", entry, e)
-
- // capture body from API request
- input := new(library.Secret)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for secret %s for %s service: %w", entry, e, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // update fields in secret object
- input.SetOrg(o)
- input.SetRepo(n)
- input.SetType(t)
- input.SetCreatedAt(time.Now().UTC().Unix())
- input.SetCreatedBy(u.GetName())
- input.SetUpdatedAt(time.Now().UTC().Unix())
- input.SetUpdatedBy(u.GetName())
-
- if len(input.GetImages()) > 0 {
- input.SetImages(unique(input.GetImages()))
- }
-
- if len(input.GetEvents()) > 0 {
- input.SetEvents(unique(input.GetEvents()))
- }
-
- if len(input.GetEvents()) == 0 {
- // set default events to enable for the secret
- input.SetEvents([]string{constants.EventPush, constants.EventTag, constants.EventDeploy})
- }
-
- if input.AllowCommand == nil {
- input.SetAllowCommand(true)
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(t, constants.SecretShared) {
- // update the team instead of repo
- input.SetTeam(n)
- input.Repo = nil
- }
-
- // send API call to create the secret
- err = secret.FromContext(c, e).Create(t, o, n, input)
- if err != nil {
- retErr := fmt.Errorf("unable to create secret %s for %s service: %w", entry, e, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- s, _ := secret.FromContext(c, e).Get(t, o, n, input.GetName())
-
- c.JSON(http.StatusOK, s.Sanitize())
-}
-
-// nolint: lll // ignore long line length due to description
-//
-// swagger:operation GET /api/v1/secrets/{engine}/{type}/{org}/{name} secrets GetSecrets
-//
-// Retrieve a list of secrets from the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: engine
-// description: Secret engine to create a secret in, eg. "native"
-// required: true
-// type: string
-// - in: path
-// name: type
-// description: Secret type to create
-// enum:
-// - org
-// - repo
-// - shared
-// required: true
-// type: string
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: name
-// description: Name of the repo if a repo secret, team name if a shared secret, or '*' if an org secret
-// required: true
-// type: string
-// - in: query
-// name: page
-// description: The page of results to retrieve
-// type: integer
-// default: 1
-// - in: query
-// name: per_page
-// description: How many results per page to return
-// type: integer
-// maximum: 100
-// default: 10
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the list of secrets
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/Secret"
-// headers:
-// X-Total-Count:
-// description: Total number of results
-// type: integer
-// Link:
-// description: see https://tools.ietf.org/html/rfc5988
-// type: string
-// '400':
-// description: Unable to retrieve the list of secrets
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to retrieve the list of secrets
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetSecrets represents the API handler to capture
-// a list of secrets from the configured backend.
-//
-// nolint: funlen // ignore function length due to comments
-func GetSecrets(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
- e := c.Param("engine")
- t := c.Param("type")
- o := c.Param("org")
- n := c.Param("name")
-
- var teams []string
- // get list of user's teams if type is shared secret and team is '*'
- if t == constants.SecretShared && n == "*" {
- var err error
- teams, err = scm.FromContext(c).ListUsersTeamsForOrg(u, o)
- if err != nil {
- retErr := fmt.Errorf("unable to get users %s teams for org %s: %v", u.GetName(), o, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
- }
-
- entry := fmt.Sprintf("%s/%s/%s", t, o, n)
-
- // create log fields from API metadata
- fields := logrus.Fields{
- "engine": e,
- "org": o,
- "repo": n,
- "type": t,
- "user": u.GetName(),
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(t, constants.SecretShared) {
- // update log fields from API metadata
- fields = logrus.Fields{
- "engine": e,
- "org": o,
- "team": n,
- "type": t,
- "user": u.GetName(),
- }
- }
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(fields).Infof("reading secrets %s from %s service", entry, e)
-
- // capture page query parameter if present
- page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to convert page query parameter for %s from %s service: %w", entry, e, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // capture per_page query parameter if present
- perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to convert per_page query parameter for %s from %s service: %w", entry, e, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // send API call to capture the total number of secrets
- total, err := secret.FromContext(c, e).Count(t, o, n, teams)
- if err != nil {
- retErr := fmt.Errorf("unable to get secret count for %s from %s service: %w", entry, e, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // ensure per_page isn't above or below allowed values
- //
- // nolint: gomnd // ignore magic number
- perPage = util.MaxInt(1, util.MinInt(100, perPage))
-
- // send API call to capture the list of secrets
- s, err := secret.FromContext(c, e).List(t, o, n, page, perPage, teams)
- if err != nil {
- retErr := fmt.Errorf("unable to get secrets for %s from %s service: %w", entry, e, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // create pagination object
- pagination := Pagination{
- Page: page,
- PerPage: perPage,
- Total: total,
- }
- // set pagination headers
- pagination.SetHeaderLink(c)
-
- // variable we want to return
- secrets := []*library.Secret{}
- // iterate through all secrets
- for _, secret := range s {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := secret
-
- // sanitize secret to ensure no value is provided
- secrets = append(secrets, tmp.Sanitize())
- }
-
- c.JSON(http.StatusOK, secrets)
-}
-
-// nolint: lll // ignore long line length due to description
-//
-// swagger:operation GET /api/v1/secrets/{engine}/{type}/{org}/{name}/{secret} secrets GetSecret
-//
-// Retrieve a secret from the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: engine
-// description: Secret engine to create a secret in, eg. "native"
-// required: true
-// type: string
-// - in: path
-// name: type
-// description: Secret type to create
-// enum:
-// - org
-// - repo
-// - shared
-// required: true
-// type: string
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: name
-// description: Name of the repo if a repo secret, team name if a shared secret, or '*' if an org secret
-// required: true
-// type: string
-// - in: path
-// name: secret
-// description: Name of the secret
-// required: true
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the secret
-// schema:
-// "$ref": "#/definitions/Secret"
-// '500':
-// description: Unable to retrieve the secret
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetSecret gets a secret from the provided secrets service.
-func GetSecret(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
- e := c.Param("engine")
- t := c.Param("type")
- o := c.Param("org")
- n := c.Param("name")
- s := strings.TrimPrefix(c.Param("secret"), "/")
-
- entry := fmt.Sprintf("%s/%s/%s/%s", t, o, n, s)
-
- // create log fields from API metadata
- fields := logrus.Fields{
- "engine": e,
- "org": o,
- "repo": n,
- "secret": s,
- "type": t,
- "user": u.GetName(),
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(t, constants.SecretShared) {
- // update log fields from API metadata
- fields = logrus.Fields{
- "engine": e,
- "org": o,
- "secret": s,
- "team": n,
- "type": t,
- "user": u.GetName(),
- }
- }
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(fields).Infof("reading secret %s from %s service", entry, e)
-
- // send API call to capture the secret
- secret, err := secret.FromContext(c, e).Get(t, o, n, s)
- if err != nil {
- retErr := fmt.Errorf("unable to get secret %s from %s service: %w", entry, e, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // only allow workers to access the full secret with the value
- if u.GetAdmin() && u.GetName() == "vela-worker" {
- c.JSON(http.StatusOK, secret)
-
- return
- }
-
- c.JSON(http.StatusOK, secret.Sanitize())
-}
-
-// nolint: lll // ignore long line length due to description
-//
-// swagger:operation PUT /api/v1/secrets/{engine}/{type}/{org}/{name}/{secret} secrets UpdateSecrets
-//
-// Update a secret on the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: engine
-// description: Secret engine to update the secret in, eg. "native"
-// required: true
-// type: string
-// - in: path
-// name: type
-// description: Secret type to update
-// enum:
-// - org
-// - repo
-// - shared
-// required: true
-// type: string
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: name
-// description: Name of the repo if a repo secret, team name if a shared secret, or '*' if an org secret
-// required: true
-// type: string
-// - in: path
-// name: secret
-// description: Name of the secret
-// required: true
-// type: string
-// - in: body
-// name: body
-// description: Payload containing the secret to create
-// required: true
-// schema:
-// "$ref": "#/definitions/Secret"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully updated the secret
-// schema:
-// "$ref": "#/definitions/Secret"
-// '400':
-// description: Unable to update the secret
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to update the secret
-// schema:
-// "$ref": "#/definitions/Error"
-
-// UpdateSecret updates a secret for the provided secrets service.
-func UpdateSecret(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
- e := c.Param("engine")
- t := c.Param("type")
- o := c.Param("org")
- n := c.Param("name")
- s := strings.TrimPrefix(c.Param("secret"), "/")
-
- entry := fmt.Sprintf("%s/%s/%s/%s", t, o, n, s)
-
- // create log fields from API metadata
- fields := logrus.Fields{
- "engine": e,
- "org": o,
- "repo": n,
- "secret": s,
- "type": t,
- "user": u.GetName(),
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(t, constants.SecretShared) {
- // update log fields from API metadata
- fields = logrus.Fields{
- "engine": e,
- "org": o,
- "secret": s,
- "team": n,
- "type": t,
- "user": u.GetName(),
- }
- }
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(fields).Infof("updating secret %s for %s service", entry, e)
-
- // capture body from API request
- input := new(library.Secret)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for secret %s for %s service: %v", entry, e, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // update secret fields if provided
- input.SetName(s)
- input.SetOrg(o)
- input.SetRepo(n)
- input.SetType(t)
- input.SetUpdatedAt(time.Now().UTC().Unix())
- input.SetUpdatedBy(u.GetName())
-
- if input.Images != nil {
- // update images if set
- input.SetImages(unique(input.GetImages()))
- }
-
- if len(input.GetEvents()) > 0 {
- input.SetEvents(unique(input.GetEvents()))
- }
-
- if input.AllowCommand != nil {
- // update allow_command if set
- input.SetAllowCommand(input.GetAllowCommand())
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(t, constants.SecretShared) {
- // update the team instead of repo
- input.SetTeam(n)
- input.Repo = nil
- }
-
- // send API call to update the secret
- err = secret.FromContext(c, e).Update(t, o, n, input)
- if err != nil {
- retErr := fmt.Errorf("unable to update secret %s for %s service: %w", entry, e, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the updated secret
- secret, _ := secret.FromContext(c, e).Get(t, o, n, input.GetName())
-
- c.JSON(http.StatusOK, secret.Sanitize())
-}
-
-// nolint: lll // ignore long line length due to description
-//
-// swagger:operation DELETE /api/v1/secrets/{engine}/{type}/{org}/{name}/{secret} secrets DeleteSecret
-//
-// Delete a secret from the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: engine
-// description: Secret engine to delete the secret from, eg. "native"
-// required: true
-// type: string
-// - in: path
-// name: type
-// description: Secret type to delete
-// enum:
-// - org
-// - repo
-// - shared
-// required: true
-// type: string
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: name
-// description: Name of the repo if a repo secret, team name if a shared secret, or '*' if an org secret
-// required: true
-// type: string
-// - in: path
-// name: secret
-// description: Name of the secret
-// required: true
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully deleted the secret
-// schema:
-// type: string
-// '500':
-// description: Unable to delete the secret
-// schema:
-// "$ref": "#/definitions/Error"
-
-// DeleteSecret deletes a secret from the provided secrets service.
-func DeleteSecret(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
- e := c.Param("engine")
- t := c.Param("type")
- o := c.Param("org")
- n := c.Param("name")
- s := strings.TrimPrefix(c.Param("secret"), "/")
-
- entry := fmt.Sprintf("%s/%s/%s/%s", t, o, n, s)
-
- // create log fields from API metadata
- fields := logrus.Fields{
- "engine": e,
- "org": o,
- "repo": n,
- "secret": s,
- "type": t,
- "user": u.GetName(),
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(t, constants.SecretShared) {
- // update log fields from API metadata
- fields = logrus.Fields{
- "engine": e,
- "org": o,
- "secret": s,
- "team": n,
- "type": t,
- "user": u.GetName(),
- }
- }
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(fields).Infof("deleting secret %s from %s service", entry, e)
-
- // send API call to remove the secret
- err := secret.FromContext(c, e).Delete(t, o, n, s)
- if err != nil {
- retErr := fmt.Errorf("unable to delete secret %s from %s service: %w", entry, e, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, fmt.Sprintf("secret %s deleted from %s service", entry, e))
-}
-
-// unique is a helper function that takes a slice and
-// validates that there are no duplicate entries.
-func unique(stringSlice []string) []string {
- keys := make(map[string]bool)
- list := []string{}
-
- for _, entry := range stringSlice {
- if _, value := keys[entry]; !value {
- keys[entry] = true
-
- list = append(list, entry)
- }
- }
-
- return list
-}
diff --git a/api/secret/create.go b/api/secret/create.go
new file mode 100644
index 000000000..82a1cac5a
--- /dev/null
+++ b/api/secret/create.go
@@ -0,0 +1,242 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/secret"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+//
+// swagger:operation POST /api/v1/secrets/{engine}/{type}/{org}/{name} secrets CreateSecret
+//
+// Create a secret
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: engine
+// description: Secret engine to create a secret in, eg. "native"
+// required: true
+// type: string
+// - in: path
+// name: type
+// description: Secret type to create
+// enum:
+// - org
+// - repo
+// - shared
+// required: true
+// type: string
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: name
+// description: Name of the repo if a repo secret, team name if a shared secret, or '*' if an org secret
+// required: true
+// type: string
+// - in: body
+// name: body
+// description: Payload containing the secret to create
+// required: true
+// schema:
+// "$ref": "#/definitions/Secret"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully created the secret
+// schema:
+// "$ref": "#/definitions/Secret"
+// '400':
+// description: Unable to create the secret
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to create the secret
+// schema:
+// "$ref": "#/definitions/Error"
+
+// CreateSecret represents the API handler to
+// create a secret in the configured backend.
+//
+//nolint:funlen // suppress long function error
+func CreateSecret(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+ e := util.PathParameter(c, "engine")
+ t := util.PathParameter(c, "type")
+ o := util.PathParameter(c, "org")
+ n := util.PathParameter(c, "name")
+
+ entry := fmt.Sprintf("%s/%s/%s", t, o, n)
+
+ // create log fields from API metadata
+ fields := logrus.Fields{
+ "engine": e,
+ "org": o,
+ "repo": n,
+ "type": t,
+ "user": u.GetName(),
+ }
+
+ // check if secret is a shared secret
+ if strings.EqualFold(t, constants.SecretShared) {
+ // update log fields from API metadata
+ fields = logrus.Fields{
+ "engine": e,
+ "org": o,
+ "team": n,
+ "type": t,
+ "user": u.GetName(),
+ }
+ }
+
+ if strings.EqualFold(t, constants.SecretOrg) {
+ // retrieve org name from SCM
+ //
+ // SCM can be case insensitive, causing access retrieval to work
+ // but Org/Repo != org/repo in Vela. So this check ensures that
+ // what a user inputs matches the casing we expect in Vela since
+ // the SCM will have the source of truth for casing.
+ org, err := scm.FromContext(c).GetOrgName(u, o)
+ if err != nil {
+ retErr := fmt.Errorf("unable to retrieve organization %s", o)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ // check if casing is accurate
+ if org != o {
+ retErr := fmt.Errorf("unable to retrieve organization %s. Did you mean %s?", o, org)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+ }
+
+ if strings.EqualFold(t, constants.SecretRepo) {
+ // retrieve org and repo name from SCM
+ //
+ // same story as org secret. SCM has accurate casing.
+ scmOrg, scmRepo, err := scm.FromContext(c).GetOrgAndRepoName(u, o, n)
+ if err != nil {
+ retErr := fmt.Errorf("unable to retrieve repository %s/%s", o, n)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ // check if casing is accurate for org entry
+ if scmOrg != o {
+ retErr := fmt.Errorf("unable to retrieve org %s. Did you mean %s?", o, scmOrg)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ // check if casing is accurate for repo entry
+ if scmRepo != n {
+ retErr := fmt.Errorf("unable to retrieve repository %s. Did you mean %s?", n, scmRepo)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+ }
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(fields).Infof("creating new secret %s for %s service", entry, e)
+
+ // capture body from API request
+ input := new(library.Secret)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for secret %s for %s service: %w", entry, e, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // reject secrets with solely whitespace characters as its value
+ trimmed := strings.TrimSpace(input.GetValue())
+ if len(trimmed) == 0 {
+ retErr := fmt.Errorf("secret value must contain non-whitespace characters")
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update fields in secret object
+ input.SetOrg(o)
+ input.SetRepo(n)
+ input.SetType(t)
+ input.SetCreatedAt(time.Now().UTC().Unix())
+ input.SetCreatedBy(u.GetName())
+ input.SetUpdatedAt(time.Now().UTC().Unix())
+ input.SetUpdatedBy(u.GetName())
+
+ if len(input.GetImages()) > 0 {
+ input.SetImages(util.Unique(input.GetImages()))
+ }
+
+ if len(input.GetEvents()) > 0 {
+ input.SetEvents(util.Unique(input.GetEvents()))
+ }
+
+ if len(input.GetEvents()) == 0 {
+ // set default events to enable for the secret
+ input.SetEvents([]string{constants.EventPush, constants.EventTag, constants.EventDeploy})
+ }
+
+ if input.AllowCommand == nil {
+ input.SetAllowCommand(true)
+ }
+
+ // check if secret is a shared secret
+ if strings.EqualFold(t, constants.SecretShared) {
+ // update the team instead of repo
+ input.SetTeam(n)
+ input.Repo = nil
+ }
+
+ // send API call to create the secret
+ s, err := secret.FromContext(c, e).Create(t, o, n, input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create secret %s for %s service: %w", entry, e, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, s.Sanitize())
+}
diff --git a/api/secret/delete.go b/api/secret/delete.go
new file mode 100644
index 000000000..f8134ba5f
--- /dev/null
+++ b/api/secret/delete.go
@@ -0,0 +1,121 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/secret"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/sirupsen/logrus"
+)
+
+//
+// swagger:operation DELETE /api/v1/secrets/{engine}/{type}/{org}/{name}/{secret} secrets DeleteSecret
+//
+// Delete a secret from the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: engine
+// description: Secret engine to delete the secret from, eg. "native"
+// required: true
+// type: string
+// - in: path
+// name: type
+// description: Secret type to delete
+// enum:
+// - org
+// - repo
+// - shared
+// required: true
+// type: string
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: name
+// description: Name of the repo if a repo secret, team name if a shared secret, or '*' if an org secret
+// required: true
+// type: string
+// - in: path
+// name: secret
+// description: Name of the secret
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully deleted the secret
+// schema:
+// type: string
+// '500':
+// description: Unable to delete the secret
+// schema:
+// "$ref": "#/definitions/Error"
+
+// DeleteSecret deletes a secret from the provided secrets service.
+func DeleteSecret(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+ e := util.PathParameter(c, "engine")
+ t := util.PathParameter(c, "type")
+ o := util.PathParameter(c, "org")
+ n := util.PathParameter(c, "name")
+ s := strings.TrimPrefix(util.PathParameter(c, "secret"), "/")
+
+ entry := fmt.Sprintf("%s/%s/%s/%s", t, o, n, s)
+
+ // create log fields from API metadata
+ fields := logrus.Fields{
+ "engine": e,
+ "org": o,
+ "repo": n,
+ "secret": s,
+ "type": t,
+ "user": u.GetName(),
+ }
+
+ // check if secret is a shared secret
+ if strings.EqualFold(t, constants.SecretShared) {
+ // update log fields from API metadata
+ fields = logrus.Fields{
+ "engine": e,
+ "org": o,
+ "secret": s,
+ "team": n,
+ "type": t,
+ "user": u.GetName(),
+ }
+ }
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(fields).Infof("deleting secret %s from %s service", entry, e)
+
+ // send API call to remove the secret
+ err := secret.FromContext(c, e).Delete(t, o, n, s)
+ if err != nil {
+ retErr := fmt.Errorf("unable to delete secret %s from %s service: %w", entry, e, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("secret %s deleted from %s service", entry, e))
+}
diff --git a/api/secret/doc.go b/api/secret/doc.go
new file mode 100644
index 000000000..db89d7b55
--- /dev/null
+++ b/api/secret/doc.go
@@ -0,0 +1,10 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+// Package secret provides the secret handlers for the Vela API.
+//
+// Usage:
+//
+// import "github.com/go-vela/server/api/secret"
+package secret
diff --git a/api/secret/get.go b/api/secret/get.go
new file mode 100644
index 000000000..0f24b95c0
--- /dev/null
+++ b/api/secret/get.go
@@ -0,0 +1,130 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/router/middleware/claims"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/secret"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/sirupsen/logrus"
+)
+
+//
+// swagger:operation GET /api/v1/secrets/{engine}/{type}/{org}/{name}/{secret} secrets GetSecret
+//
+// Retrieve a secret from the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: engine
+// description: Secret engine to create a secret in, eg. "native"
+// required: true
+// type: string
+// - in: path
+// name: type
+// description: Secret type to create
+// enum:
+// - org
+// - repo
+// - shared
+// required: true
+// type: string
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: name
+// description: Name of the repo if a repo secret, team name if a shared secret, or '*' if an org secret
+// required: true
+// type: string
+// - in: path
+// name: secret
+// description: Name of the secret
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the secret
+// schema:
+// "$ref": "#/definitions/Secret"
+// '500':
+// description: Unable to retrieve the secret
+// schema:
+// "$ref": "#/definitions/Error"
+
+// GetSecret gets a secret from the provided secrets service.
+func GetSecret(c *gin.Context) {
+ // capture middleware values
+ cl := claims.Retrieve(c)
+ u := user.Retrieve(c)
+ e := util.PathParameter(c, "engine")
+ t := util.PathParameter(c, "type")
+ o := util.PathParameter(c, "org")
+ n := util.PathParameter(c, "name")
+ s := strings.TrimPrefix(util.PathParameter(c, "secret"), "/")
+
+ entry := fmt.Sprintf("%s/%s/%s/%s", t, o, n, s)
+
+ // create log fields from API metadata
+ fields := logrus.Fields{
+ "engine": e,
+ "org": o,
+ "repo": n,
+ "secret": s,
+ "type": t,
+ "user": u.GetName(),
+ }
+
+ // check if secret is a shared secret
+ if strings.EqualFold(t, constants.SecretShared) {
+ // update log fields from API metadata
+ fields = logrus.Fields{
+ "engine": e,
+ "org": o,
+ "secret": s,
+ "team": n,
+ "type": t,
+ "user": u.GetName(),
+ }
+ }
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(fields).Infof("reading secret %s from %s service", entry, e)
+
+ // send API call to capture the secret
+ secret, err := secret.FromContext(c, e).Get(t, o, n, s)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get secret %s from %s service: %w", entry, e, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // only allow workers to access the full secret with the value
+ if strings.EqualFold(cl.TokenType, constants.WorkerBuildTokenType) {
+ c.JSON(http.StatusOK, secret)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, secret.Sanitize())
+}
diff --git a/api/secret/list.go b/api/secret/list.go
new file mode 100644
index 000000000..e603c473f
--- /dev/null
+++ b/api/secret/list.go
@@ -0,0 +1,210 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/api"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/secret"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+//
+// swagger:operation GET /api/v1/secrets/{engine}/{type}/{org}/{name} secrets ListSecrets
+//
+// Retrieve a list of secrets from the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: engine
+// description: Secret engine to create a secret in, eg. "native"
+// required: true
+// type: string
+// - in: path
+// name: type
+// description: Secret type to create
+// enum:
+// - org
+// - repo
+// - shared
+// required: true
+// type: string
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: name
+// description: Name of the repo if a repo secret, team name if a shared secret, or '*' if an org secret
+// required: true
+// type: string
+// - in: query
+// name: page
+// description: The page of results to retrieve
+// type: integer
+// default: 1
+// - in: query
+// name: per_page
+// description: How many results per page to return
+// type: integer
+// maximum: 100
+// default: 10
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the list of secrets
+// schema:
+// type: array
+// items:
+// "$ref": "#/definitions/Secret"
+// headers:
+// X-Total-Count:
+// description: Total number of results
+// type: integer
+// Link:
+// description: see https://tools.ietf.org/html/rfc5988
+// type: string
+// '400':
+// description: Unable to retrieve the list of secrets
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to retrieve the list of secrets
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ListSecrets represents the API handler to capture
+// a list of secrets from the configured backend.
+func ListSecrets(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+ e := util.PathParameter(c, "engine")
+ t := util.PathParameter(c, "type")
+ o := util.PathParameter(c, "org")
+ n := util.PathParameter(c, "name")
+
+ var teams []string
+ // get list of user's teams if type is shared secret and team is '*'
+ if t == constants.SecretShared && n == "*" {
+ var err error
+
+ teams, err = scm.FromContext(c).ListUsersTeamsForOrg(u, o)
+ if err != nil {
+ retErr := fmt.Errorf("unable to list users %s teams for org %s: %w", u.GetName(), o, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+ }
+
+ entry := fmt.Sprintf("%s/%s/%s", t, o, n)
+
+ // create log fields from API metadata
+ fields := logrus.Fields{
+ "engine": e,
+ "org": o,
+ "repo": n,
+ "type": t,
+ "user": u.GetName(),
+ }
+
+ // check if secret is a shared secret
+ if strings.EqualFold(t, constants.SecretShared) {
+ // update log fields from API metadata
+ fields = logrus.Fields{
+ "engine": e,
+ "org": o,
+ "team": n,
+ "type": t,
+ "user": u.GetName(),
+ }
+ }
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(fields).Infof("listing secrets %s from %s service", entry, e)
+
+ // capture page query parameter if present
+ page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert page query parameter for %s from %s service: %w", entry, e, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // capture per_page query parameter if present
+ perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert per_page query parameter for %s from %s service: %w", entry, e, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // send API call to capture the total number of secrets
+ total, err := secret.FromContext(c, e).Count(t, o, n, teams)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get secret count for %s from %s service: %w", entry, e, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // ensure per_page isn't above or below allowed values
+ perPage = util.MaxInt(1, util.MinInt(100, perPage))
+
+ // send API call to capture the list of secrets
+ s, err := secret.FromContext(c, e).List(t, o, n, page, perPage, teams)
+ if err != nil {
+ retErr := fmt.Errorf("unable to list secrets for %s from %s service: %w", entry, e, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // create pagination object
+ pagination := api.Pagination{
+ Page: page,
+ PerPage: perPage,
+ Total: total,
+ }
+ // set pagination headers
+ pagination.SetHeaderLink(c)
+
+ // variable we want to return
+ secrets := []*library.Secret{}
+ // iterate through all secrets
+ for _, secret := range s {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := secret
+
+ // sanitize secret to ensure no value is provided
+ secrets = append(secrets, tmp.Sanitize())
+ }
+
+ c.JSON(http.StatusOK, secrets)
+}
diff --git a/api/secret/update.go b/api/secret/update.go
new file mode 100644
index 000000000..1feaae4c3
--- /dev/null
+++ b/api/secret/update.go
@@ -0,0 +1,174 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/secret"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+//
+// swagger:operation PUT /api/v1/secrets/{engine}/{type}/{org}/{name}/{secret} secrets UpdateSecret
+//
+// Update a secret on the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: engine
+// description: Secret engine to update the secret in, eg. "native"
+// required: true
+// type: string
+// - in: path
+// name: type
+// description: Secret type to update
+// enum:
+// - org
+// - repo
+// - shared
+// required: true
+// type: string
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: name
+// description: Name of the repo if a repo secret, team name if a shared secret, or '*' if an org secret
+// required: true
+// type: string
+// - in: path
+// name: secret
+// description: Name of the secret
+// required: true
+// type: string
+// - in: body
+// name: body
+// description: Payload containing the secret to create
+// required: true
+// schema:
+// "$ref": "#/definitions/Secret"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully updated the secret
+// schema:
+// "$ref": "#/definitions/Secret"
+// '400':
+// description: Unable to update the secret
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to update the secret
+// schema:
+// "$ref": "#/definitions/Error"
+
+// UpdateSecret updates a secret for the provided secrets service.
+func UpdateSecret(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+ e := util.PathParameter(c, "engine")
+ t := util.PathParameter(c, "type")
+ o := util.PathParameter(c, "org")
+ n := util.PathParameter(c, "name")
+ s := strings.TrimPrefix(util.PathParameter(c, "secret"), "/")
+
+ entry := fmt.Sprintf("%s/%s/%s/%s", t, o, n, s)
+
+ // create log fields from API metadata
+ fields := logrus.Fields{
+ "engine": e,
+ "org": o,
+ "repo": n,
+ "secret": s,
+ "type": t,
+ "user": u.GetName(),
+ }
+
+ // check if secret is a shared secret
+ if strings.EqualFold(t, constants.SecretShared) {
+ // update log fields from API metadata
+ fields = logrus.Fields{
+ "engine": e,
+ "org": o,
+ "secret": s,
+ "team": n,
+ "type": t,
+ "user": u.GetName(),
+ }
+ }
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(fields).Infof("updating secret %s for %s service", entry, e)
+
+ // capture body from API request
+ input := new(library.Secret)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for secret %s for %s service: %w", entry, e, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update secret fields if provided
+ input.SetName(s)
+ input.SetOrg(o)
+ input.SetRepo(n)
+ input.SetType(t)
+ input.SetUpdatedAt(time.Now().UTC().Unix())
+ input.SetUpdatedBy(u.GetName())
+
+ if input.Images != nil {
+ // update images if set
+ input.SetImages(util.Unique(input.GetImages()))
+ }
+
+ if len(input.GetEvents()) > 0 {
+ input.SetEvents(util.Unique(input.GetEvents()))
+ }
+
+ if input.AllowCommand != nil {
+ // update allow_command if set
+ input.SetAllowCommand(input.GetAllowCommand())
+ }
+
+ // check if secret is a shared secret
+ if strings.EqualFold(t, constants.SecretShared) {
+ // update the team instead of repo
+ input.SetTeam(n)
+ input.Repo = nil
+ }
+
+ // send API call to update the secret
+ secret, err := secret.FromContext(c, e).Update(t, o, n, input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update secret %s for %s service: %w", entry, e, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, secret.Sanitize())
+}
diff --git a/api/service.go b/api/service.go
deleted file mode 100644
index 29ece1604..000000000
--- a/api/service.go
+++ /dev/null
@@ -1,610 +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 api
-
-import (
- "fmt"
- "net/http"
- "strconv"
- "time"
-
- "github.com/go-vela/server/router/middleware/org"
- "github.com/go-vela/server/router/middleware/user"
-
- "github.com/go-vela/server/database"
- "github.com/go-vela/server/router/middleware/build"
- "github.com/go-vela/server/router/middleware/repo"
- "github.com/go-vela/server/router/middleware/service"
- "github.com/go-vela/server/util"
-
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/library"
- "github.com/go-vela/types/pipeline"
-
- "github.com/gin-gonic/gin"
- "github.com/sirupsen/logrus"
-)
-
-// swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build}/services services CreateService
-//
-// Create a service for a build in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: body
-// name: body
-// description: Payload containing the service to create
-// required: true
-// schema:
-// "$ref": "#/definitions/Service"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '201':
-// description: Successfully created the service
-// schema:
-// "$ref": "#/definitions/Service"
-// '400':
-// description: Unable to create the service
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to create the service
-// schema:
-// "$ref": "#/definitions/Error"
-
-// CreateService represents the API handler to create
-// a service for a build in the configured backend.
-//
-// nolint: dupl // ignore similar code with step
-func CreateService(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("creating new service for build %s", entry)
-
- // capture body from API request
- input := new(library.Service)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for new service for build %s: %w", entry, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // update fields in service object
- input.SetRepoID(r.GetID())
- input.SetBuildID(b.GetID())
-
- if len(input.GetStatus()) == 0 {
- input.SetStatus(constants.StatusPending)
- }
-
- if input.GetCreated() == 0 {
- input.SetCreated(time.Now().UTC().Unix())
- }
-
- // send API call to create the service
- err = database.FromContext(c).CreateService(input)
- if err != nil {
- retErr := fmt.Errorf("unable to create service for build %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the created service
- s, _ := database.FromContext(c).GetService(input.GetNumber(), b)
-
- c.JSON(http.StatusCreated, s)
-}
-
-// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/services services GetServices
-//
-// Get a list of all services for a build in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: query
-// name: page
-// description: The page of results to retrieve
-// type: integer
-// default: 1
-// - in: query
-// name: per_page
-// description: How many results per page to return
-// type: integer
-// maximum: 100
-// default: 10
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the list of services
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/Service"
-// headers:
-// X-Total-Count:
-// description: Total number of results
-// type: integer
-// Link:
-// description: see https://tools.ietf.org/html/rfc5988
-// type: string
-// '400':
-// description: Unable to retrieve the list of services
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to retrieve the list of services
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetServices represents the API handler to capture a list
-// of services for a build from the configured backend.
-func GetServices(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("reading services for build %s", entry)
-
- // capture page query parameter if present
- page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
- if err != nil {
- retErr := fmt.Errorf("unable to convert page query parameter for build %s: %w", entry, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // capture per_page query parameter if present
- perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
- if err != nil {
- retErr := fmt.Errorf("unable to convert per_page query parameter for build %s: %w", entry, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // ensure per_page isn't above or below allowed values
- //
- // nolint: gomnd // ignore magic number
- perPage = util.MaxInt(1, util.MinInt(100, perPage))
-
- // send API call to capture the total number of services for the build
- t, err := database.FromContext(c).GetBuildServiceCount(b)
- if err != nil {
- retErr := fmt.Errorf("unable to get services count for build %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the list of services for the build
- s, err := database.FromContext(c).GetBuildServiceList(b, page, perPage)
- if err != nil {
- retErr := fmt.Errorf("unable to get services for build %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // create pagination object
- pagination := Pagination{
- Page: page,
- PerPage: perPage,
- Total: t,
- }
- // set pagination headers
- pagination.SetHeaderLink(c)
-
- c.JSON(http.StatusOK, s)
-}
-
-// nolint: lll // ignore long line length due to API path
-//
-// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/services/{service} services GetService
-//
-// Get a service for a build in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: path
-// name: service
-// description: Name of the service
-// required: true
-// type: integer
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the service
-// schema:
-// "$ref": "#/definitions/Service"
-// '400':
-// description: Unable to retrieve the service
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to retrieve the service
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetService represents the API handler to capture a
-// service for a build from the configured backend.
-func GetService(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- s := service.Retrieve(c)
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "service": s.GetNumber(),
- "user": u.GetName(),
- }).Infof("reading service %s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
-
- c.JSON(http.StatusOK, s)
-}
-
-// nolint: lll // ignore long line length due to API path
-//
-// swagger:operation PUT /api/v1/repos/{org}/{repo}/builds/{build}/services/{service} services UpdateService
-//
-// Update a service for a build in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: path
-// name: service
-// description: Service number
-// required: true
-// type: integer
-// - in: body
-// name: body
-// description: Payload containing the service to update
-// required: true
-// schema:
-// "$ref": "#/definitions/Service"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully updated the service
-// schema:
-// "$ref": "#/definitions/Service"
-// '400':
-// description: Unable to update the service
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to update the service
-// schema:
-// "$ref": "#/definitions/Error"
-
-// UpdateService represents the API handler to update
-// a service for a build in the configured backend.
-func UpdateService(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- s := service.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "service": s.GetNumber(),
- "user": u.GetName(),
- }).Infof("updating service %s", entry)
-
- // capture body from API request
- input := new(library.Service)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for service %s: %w", entry, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // update service fields if provided
- if len(input.GetStatus()) > 0 {
- // update status if set
- s.SetStatus(input.GetStatus())
- }
-
- if len(input.GetError()) > 0 {
- // update error if set
- s.SetError(input.GetError())
- }
-
- if input.GetExitCode() > 0 {
- // update exit_code if set
- s.SetExitCode(input.GetExitCode())
- }
-
- if input.GetStarted() > 0 {
- // update started if set
- s.SetStarted(input.GetStarted())
- }
-
- if input.GetFinished() > 0 {
- // update finished if set
- s.SetFinished(input.GetFinished())
- }
-
- // send API call to update the service
- err = database.FromContext(c).UpdateService(s)
- if err != nil {
- retErr := fmt.Errorf("unable to update service %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the updated service
- s, _ = database.FromContext(c).GetService(s.GetNumber(), b)
-
- c.JSON(http.StatusOK, s)
-}
-
-// nolint: lll // ignore long line length due to API path
-//
-// swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build}/services/{service} services DeleteService
-//
-// Delete a service for a build in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: path
-// name: service
-// description: Service Number
-// required: true
-// type: integer
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully deleted the service
-// schema:
-// type: string
-// '500':
-// description: Unable to delete the service
-// schema:
-// "$ref": "#/definitions/Error"
-
-// DeleteService represents the API handler to remove
-// a service for a build from the configured backend.
-//
-// nolint: dupl // ignore similar code with step
-func DeleteService(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- s := service.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "service": s.GetNumber(),
- "user": u.GetName(),
- }).Infof("deleting service %s", entry)
-
- // send API call to remove the service
- err := database.FromContext(c).DeleteService(s.GetID())
- if err != nil {
- retErr := fmt.Errorf("unable to delete service %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, fmt.Sprintf("service %s deleted", entry))
-}
-
-// planServices is a helper function to plan all services
-// in the build for execution. This creates the services
-// for the build in the configured backend.
-//
-// nolint: lll // ignore long line length due to variable names
-func planServices(database database.Service, p *pipeline.Build, b *library.Build) ([]*library.Service, error) {
- // variable to store planned services
- services := []*library.Service{}
-
- // iterate through all pipeline services
- for _, service := range p.Services {
- // create the service object
- s := new(library.Service)
- s.SetBuildID(b.GetID())
- s.SetRepoID(b.GetRepoID())
- s.SetName(service.Name)
- s.SetImage(service.Image)
- s.SetNumber(service.Number)
- s.SetStatus(constants.StatusPending)
- s.SetCreated(time.Now().UTC().Unix())
-
- // send API call to create the service
- err := database.CreateService(s)
- if err != nil {
- return services, fmt.Errorf("unable to create service %s: %w", s.GetName(), err)
- }
-
- // send API call to capture the created service
- s, err = database.GetService(s.GetNumber(), b)
- if err != nil {
- return services, fmt.Errorf("unable to get service %s: %w", s.GetName(), err)
- }
-
- // populate environment variables from service library
- //
- // https://pkg.go.dev/github.com/go-vela/types/library#Service.Environment
- err = service.MergeEnv(s.Environment())
- if err != nil {
- return services, err
- }
-
- // create the log object
- l := new(library.Log)
- l.SetServiceID(s.GetID())
- l.SetBuildID(b.GetID())
- l.SetRepoID(b.GetRepoID())
- l.SetData([]byte{})
-
- // send API call to create the service logs
- err = database.CreateLog(l)
- if err != nil {
- return services, fmt.Errorf("unable to create service logs for service %s: %w", s.GetName(), err)
- }
- }
-
- return services, nil
-}
diff --git a/api/service/create.go b/api/service/create.go
new file mode 100644
index 000000000..2dcccbd26
--- /dev/null
+++ b/api/service/create.go
@@ -0,0 +1,125 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package service
+
+import (
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build}/services services CreateService
+//
+// Create a service for a build in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: body
+// name: body
+// description: Payload containing the service to create
+// required: true
+// schema:
+// "$ref": "#/definitions/Service"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '201':
+// description: Successfully created the service
+// schema:
+// "$ref": "#/definitions/Service"
+// '400':
+// description: Unable to create the service
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to create the service
+// schema:
+// "$ref": "#/definitions/Error"
+
+// CreateService represents the API handler to create
+// a service for a build in the configured backend.
+func CreateService(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("creating new service for build %s", entry)
+
+ // capture body from API request
+ input := new(library.Service)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for new service for build %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update fields in service object
+ input.SetRepoID(r.GetID())
+ input.SetBuildID(b.GetID())
+
+ if len(input.GetStatus()) == 0 {
+ input.SetStatus(constants.StatusPending)
+ }
+
+ if input.GetCreated() == 0 {
+ input.SetCreated(time.Now().UTC().Unix())
+ }
+
+ // send API call to create the service
+ s, err := database.FromContext(c).CreateService(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create service for build %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusCreated, s)
+}
diff --git a/api/service/delete.go b/api/service/delete.go
new file mode 100644
index 000000000..d85aa0fe8
--- /dev/null
+++ b/api/service/delete.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 service
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/service"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+//
+// swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build}/services/{service} services DeleteService
+//
+// Delete a service for a build in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: path
+// name: service
+// description: Service Number
+// required: true
+// type: integer
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully deleted the service
+// schema:
+// type: string
+// '500':
+// description: Unable to delete the service
+// schema:
+// "$ref": "#/definitions/Error"
+
+// DeleteService represents the API handler to remove
+// a service for a build from the configured backend.
+func DeleteService(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ s := service.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "service": s.GetNumber(),
+ "user": u.GetName(),
+ }).Infof("deleting service %s", entry)
+
+ // send API call to remove the service
+ err := database.FromContext(c).DeleteService(s)
+ if err != nil {
+ retErr := fmt.Errorf("unable to delete service %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("service %s deleted", entry))
+}
diff --git a/api/service/doc.go b/api/service/doc.go
new file mode 100644
index 000000000..53dc07284
--- /dev/null
+++ b/api/service/doc.go
@@ -0,0 +1,10 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+// Package service provides the service handlers for the Vela API.
+//
+// Usage:
+//
+// import "github.com/go-vela/server/api/service"
+package service
diff --git a/api/service/get.go b/api/service/get.go
new file mode 100644
index 000000000..b2165a69f
--- /dev/null
+++ b/api/service/get.go
@@ -0,0 +1,86 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package service
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/service"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/sirupsen/logrus"
+)
+
+//
+// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/services/{service} services GetService
+//
+// Get a service for a build in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: path
+// name: service
+// description: Name of the service
+// required: true
+// type: integer
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the service
+// schema:
+// "$ref": "#/definitions/Service"
+// '400':
+// description: Unable to retrieve the service
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to retrieve the service
+// schema:
+// "$ref": "#/definitions/Error"
+
+// GetService represents the API handler to capture a
+// service for a build from the configured backend.
+func GetService(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ s := service.Retrieve(c)
+ u := user.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "service": s.GetNumber(),
+ "user": u.GetName(),
+ }).Infof("reading service %s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
+
+ c.JSON(http.StatusOK, s)
+}
diff --git a/api/service/list.go b/api/service/list.go
new file mode 100644
index 000000000..22a7b8be7
--- /dev/null
+++ b/api/service/list.go
@@ -0,0 +1,146 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package service
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/api"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/services services ListServices
+//
+// Get a list of all services for a build in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: query
+// name: page
+// description: The page of results to retrieve
+// type: integer
+// default: 1
+// - in: query
+// name: per_page
+// description: How many results per page to return
+// type: integer
+// maximum: 100
+// default: 10
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the list of services
+// schema:
+// type: array
+// items:
+// "$ref": "#/definitions/Service"
+// headers:
+// X-Total-Count:
+// description: Total number of results
+// type: integer
+// Link:
+// description: see https://tools.ietf.org/html/rfc5988
+// type: string
+// '400':
+// description: Unable to retrieve the list of services
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to retrieve the list of services
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ListServices represents the API handler to capture a list
+// of services for a build from the configured backend.
+func ListServices(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("reading services for build %s", entry)
+
+ // capture page query parameter if present
+ page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert page query parameter for build %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // capture per_page query parameter if present
+ perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert per_page query parameter for build %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // ensure per_page isn't above or below allowed values
+ perPage = util.MaxInt(1, util.MinInt(100, perPage))
+
+ // send API call to capture the list of services for the build
+ s, t, err := database.FromContext(c).ListServicesForBuild(b, map[string]interface{}{}, page, perPage)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get services for build %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // create pagination object
+ pagination := api.Pagination{
+ Page: page,
+ PerPage: perPage,
+ Total: t,
+ }
+ // set pagination headers
+ pagination.SetHeaderLink(c)
+
+ c.JSON(http.StatusOK, s)
+}
diff --git a/api/service/plan.go b/api/service/plan.go
new file mode 100644
index 000000000..a0200767e
--- /dev/null
+++ b/api/service/plan.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 service
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/go-vela/types/pipeline"
+)
+
+// PlanServices is a helper function to plan all services
+// in the build for execution. This creates the services
+// for the build in the configured backend.
+func PlanServices(database database.Interface, p *pipeline.Build, b *library.Build) ([]*library.Service, error) {
+ // variable to store planned services
+ services := []*library.Service{}
+
+ // iterate through all pipeline services
+ for _, service := range p.Services {
+ // create the service object
+ s := new(library.Service)
+ s.SetBuildID(b.GetID())
+ s.SetRepoID(b.GetRepoID())
+ s.SetName(service.Name)
+ s.SetImage(service.Image)
+ s.SetNumber(service.Number)
+ s.SetStatus(constants.StatusPending)
+ s.SetCreated(time.Now().UTC().Unix())
+
+ // send API call to create the service
+ s, err := database.CreateService(s)
+ if err != nil {
+ return services, fmt.Errorf("unable to create service %s: %w", s.GetName(), err)
+ }
+
+ // populate environment variables from service library
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/library#Service.Environment
+ err = service.MergeEnv(s.Environment())
+ if err != nil {
+ return services, err
+ }
+
+ // create the log object
+ l := new(library.Log)
+ l.SetServiceID(s.GetID())
+ l.SetBuildID(b.GetID())
+ l.SetRepoID(b.GetRepoID())
+ l.SetData([]byte{})
+
+ // send API call to create the service logs
+ err = database.CreateLog(l)
+ if err != nil {
+ return services, fmt.Errorf("unable to create service logs for service %s: %w", s.GetName(), err)
+ }
+ }
+
+ return services, nil
+}
diff --git a/api/service/update.go b/api/service/update.go
new file mode 100644
index 000000000..7c1a5859f
--- /dev/null
+++ b/api/service/update.go
@@ -0,0 +1,146 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package service
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/service"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+//
+// swagger:operation PUT /api/v1/repos/{org}/{repo}/builds/{build}/services/{service} services UpdateService
+//
+// Update a service for a build in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: path
+// name: service
+// description: Service number
+// required: true
+// type: integer
+// - in: body
+// name: body
+// description: Payload containing the service to update
+// required: true
+// schema:
+// "$ref": "#/definitions/Service"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully updated the service
+// schema:
+// "$ref": "#/definitions/Service"
+// '400':
+// description: Unable to update the service
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to update the service
+// schema:
+// "$ref": "#/definitions/Error"
+
+// UpdateService represents the API handler to update
+// a service for a build in the configured backend.
+func UpdateService(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ s := service.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "service": s.GetNumber(),
+ "user": u.GetName(),
+ }).Infof("updating service %s", entry)
+
+ // capture body from API request
+ input := new(library.Service)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for service %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update service fields if provided
+ if len(input.GetStatus()) > 0 {
+ // update status if set
+ s.SetStatus(input.GetStatus())
+ }
+
+ if len(input.GetError()) > 0 {
+ // update error if set
+ s.SetError(input.GetError())
+ }
+
+ if input.GetExitCode() > 0 {
+ // update exit_code if set
+ s.SetExitCode(input.GetExitCode())
+ }
+
+ if input.GetStarted() > 0 {
+ // update started if set
+ s.SetStarted(input.GetStarted())
+ }
+
+ if input.GetFinished() > 0 {
+ // update finished if set
+ s.SetFinished(input.GetFinished())
+ }
+
+ // send API call to update the service
+ s, err = database.FromContext(c).UpdateService(s)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update service %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, s)
+}
diff --git a/api/step.go b/api/step.go
deleted file mode 100644
index 32c111af5..000000000
--- a/api/step.go
+++ /dev/null
@@ -1,662 +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 api
-
-import (
- "fmt"
- "net/http"
- "strconv"
- "time"
-
- "github.com/gin-gonic/gin"
- "github.com/go-vela/server/database"
- "github.com/go-vela/server/router/middleware/build"
- "github.com/go-vela/server/router/middleware/org"
- "github.com/go-vela/server/router/middleware/repo"
- "github.com/go-vela/server/router/middleware/step"
- "github.com/go-vela/server/router/middleware/user"
- "github.com/go-vela/server/util"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/library"
- "github.com/go-vela/types/pipeline"
- "github.com/sirupsen/logrus"
-)
-
-// swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build}/steps steps CreateStep
-//
-// Create a step for a build
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: body
-// name: body
-// description: Payload containing the step to create
-// required: true
-// schema:
-// "$ref": "#/definitions/Step"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '201':
-// description: Successfully created the step
-// schema:
-// "$ref": "#/definitions/Step"
-// '400':
-// description: Unable to create the step
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to create the step
-// schema:
-// "$ref": "#/definitions/Error"
-
-// CreateStep represents the API handler to create
-// a step for a build in the configured backend.
-//
-// nolint: dupl // ignore similar code with service
-func CreateStep(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("creating new step for build %s", entry)
-
- // capture body from API request
- input := new(library.Step)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for new step for build %s: %w", entry, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // update fields in step object
- input.SetRepoID(r.GetID())
- input.SetBuildID(b.GetID())
-
- if len(input.GetStatus()) == 0 {
- input.SetStatus(constants.StatusPending)
- }
-
- if input.GetCreated() == 0 {
- input.SetCreated(time.Now().UTC().Unix())
- }
-
- // send API call to create the step
- err = database.FromContext(c).CreateStep(input)
- if err != nil {
- retErr := fmt.Errorf("unable to create step for build %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the created step
- s, _ := database.FromContext(c).GetStep(input.GetNumber(), b)
-
- c.JSON(http.StatusCreated, s)
-}
-
-// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/steps steps GetSteps
-//
-// Retrieve a list of steps for a build
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: query
-// name: page
-// description: The page of results to retrieve
-// type: integer
-// default: 1
-// - in: query
-// name: per_page
-// description: How many results per page to return
-// type: integer
-// maximum: 100
-// default: 10
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the list of steps
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/Step"
-// headers:
-// X-Total-Count:
-// description: Total number of results
-// type: integer
-// Link:
-// description: see https://tools.ietf.org/html/rfc5988
-// type: string
-// '400':
-// description: Unable to retrieve the list of steps
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to retrieve the list of steps
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetSteps represents the API handler to capture a list
-// of steps for a build from the configured backend.
-func GetSteps(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "user": u.GetName(),
- }).Infof("reading steps for build %s", entry)
-
- // capture page query parameter if present
- page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
- if err != nil {
- retErr := fmt.Errorf("unable to convert page query parameter for build %s: %w", entry, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // capture per_page query parameter if present
- perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
- if err != nil {
- retErr := fmt.Errorf("unable to convert per_page query parameter for build %s: %w", entry, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // ensure per_page isn't above or below allowed values
- //
- // nolint: gomnd // ignore magic number
- 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)
- if err != nil {
- retErr := fmt.Errorf("unable to get steps for build %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // create pagination object
- pagination := Pagination{
- Page: page,
- PerPage: perPage,
- Total: t,
- }
- // set pagination headers
- pagination.SetHeaderLink(c)
-
- c.JSON(http.StatusOK, s)
-}
-
-// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step} steps GetStep
-//
-// Retrieve a step for a build
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: path
-// name: step
-// description: Build number
-// required: true
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the step
-// schema:
-// "$ref": "#/definitions/Step"
-
-// GetStep represents the API handler to capture a
-// step for a build from the configured backend.
-func GetStep(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- s := step.Retrieve(c)
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "step": s.GetNumber(),
- "user": u.GetName(),
- }).Infof("reading step %s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
-
- c.JSON(http.StatusOK, s)
-}
-
-// swagger:operation PUT /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step} steps UpdateStep
-//
-// Update a step for a build
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: path
-// name: step
-// description: Step number
-// required: true
-// type: integer
-// - in: body
-// name: body
-// description: Payload containing the step to update
-// required: true
-// schema:
-// "$ref": "#/definitions/Step"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully updated the step
-// schema:
-// "$ref": "#/definitions/Step"
-// '400':
-// description: Unable to update the step
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to update the step
-// schema:
-// "$ref": "#/definitions/Error"
-
-// UpdateStep represents the API handler to update
-// a step for a build in the configured backend.
-func UpdateStep(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- s := step.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "step": s.GetNumber(),
- "user": u.GetName(),
- }).Infof("updating step %s", entry)
-
- // capture body from API request
- input := new(library.Step)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for step %s: %v", entry, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // update step fields if provided
- if len(input.GetStatus()) > 0 {
- // update status if set
- s.SetStatus(input.GetStatus())
- }
-
- if len(input.GetError()) > 0 {
- // update error if set
- s.SetError(input.GetError())
- }
-
- if input.GetExitCode() > 0 {
- // update exit_code if set
- s.SetExitCode(input.GetExitCode())
- }
-
- if input.GetStarted() > 0 {
- // update started if set
- s.SetStarted(input.GetStarted())
- }
-
- if input.GetFinished() > 0 {
- // update finished if set
- s.SetFinished(input.GetFinished())
- }
-
- if len(input.GetHost()) > 0 {
- // update host if set
- s.SetHost(input.GetHost())
- }
-
- if len(input.GetRuntime()) > 0 {
- // update runtime if set
- s.SetRuntime(input.GetRuntime())
- }
-
- if len(input.GetDistribution()) > 0 {
- // update distribution if set
- s.SetDistribution(input.GetDistribution())
- }
-
- // send API call to update the step
- err = database.FromContext(c).UpdateStep(s)
- if err != nil {
- retErr := fmt.Errorf("unable to update step %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the updated step
- s, _ = database.FromContext(c).GetStep(s.GetNumber(), b)
-
- c.JSON(http.StatusOK, s)
-}
-
-// swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step} steps DeleteStep
-//
-// Delete a step for a build
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: path
-// name: step
-// description: Step number
-// required: true
-// type: integer
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully deleted the step
-// schema:
-// type: string
-// '500':
-// description: Successfully deleted the step
-// schema:
-// "$ref": "#/definitions/Error"
-
-// DeleteStep represents the API handler to remove
-// a step for a build from the configured backend.
-//
-// nolint: dupl // ignore similar code with service
-func DeleteStep(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- s := step.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "step": s.GetNumber(),
- "user": u.GetName(),
- }).Infof("deleting step %s", entry)
-
- // send API call to remove the step
- err := database.FromContext(c).DeleteStep(s.GetID())
- if err != nil {
- retErr := fmt.Errorf("unable to delete step %s: %w", entry, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, fmt.Sprintf("step %s deleted", entry))
-}
-
-// planSteps is a helper function to plan all steps
-// in the build for execution. This creates the steps
-// for the build in the configured backend.
-//
-// nolint: funlen,lll // ignore function length and long line length
-func planSteps(database database.Service, p *pipeline.Build, b *library.Build) ([]*library.Step, error) {
- // variable to store planned steps
- steps := []*library.Step{}
-
- // iterate through all pipeline stages
- for _, stage := range p.Stages {
- // iterate through all steps for each pipeline stage
- for _, step := range stage.Steps {
- // create the step object
- s := new(library.Step)
- s.SetBuildID(b.GetID())
- s.SetRepoID(b.GetRepoID())
- s.SetNumber(step.Number)
- s.SetName(step.Name)
- s.SetImage(step.Image)
- s.SetStage(stage.Name)
- s.SetStatus(constants.StatusPending)
- s.SetCreated(time.Now().UTC().Unix())
-
- // send API call to create the step
- err := database.CreateStep(s)
- if err != nil {
- return steps, fmt.Errorf("unable to create step %s: %w", s.GetName(), err)
- }
-
- // send API call to capture the created step
- s, err = database.GetStep(s.GetNumber(), b)
- if err != nil {
- return steps, fmt.Errorf("unable to get step %s: %w", s.GetName(), err)
- }
-
- // populate environment variables from step library
- //
- // https://pkg.go.dev/github.com/go-vela/types/library#step.Environment
- err = step.MergeEnv(s.Environment())
- if err != nil {
- return steps, err
- }
-
- // create the log object
- l := new(library.Log)
- l.SetStepID(s.GetID())
- l.SetBuildID(b.GetID())
- l.SetRepoID(b.GetRepoID())
- l.SetData([]byte{})
-
- // send API call to create the step logs
- err = database.CreateLog(l)
- if err != nil {
- return nil, fmt.Errorf("unable to create logs for step %s: %w", s.GetName(), err)
- }
-
- steps = append(steps, s)
- }
- }
-
- // iterate through all pipeline steps
- for _, step := range p.Steps {
- // create the step object
- s := new(library.Step)
- s.SetBuildID(b.GetID())
- s.SetRepoID(b.GetRepoID())
- s.SetNumber(step.Number)
- s.SetName(step.Name)
- s.SetImage(step.Image)
- s.SetStatus(constants.StatusPending)
- s.SetCreated(time.Now().UTC().Unix())
-
- // send API call to create the step
- err := database.CreateStep(s)
- if err != nil {
- return steps, fmt.Errorf("unable to create step %s: %w", s.GetName(), err)
- }
-
- // send API call to capture the created step
- s, err = database.GetStep(s.GetNumber(), b)
- if err != nil {
- return steps, fmt.Errorf("unable to get step %s: %w", s.GetName(), err)
- }
-
- // populate environment variables from step library
- //
- // https://pkg.go.dev/github.com/go-vela/types/library#step.Environment
- err = step.MergeEnv(s.Environment())
- if err != nil {
- return steps, err
- }
-
- // create the log object
- l := new(library.Log)
- l.SetStepID(s.GetID())
- l.SetBuildID(b.GetID())
- l.SetRepoID(b.GetRepoID())
- l.SetData([]byte{})
-
- // send API call to create the step logs
- err = database.CreateLog(l)
- if err != nil {
- return steps, fmt.Errorf("unable to create logs for step %s: %w", s.GetName(), err)
- }
-
- steps = append(steps, s)
- }
-
- return steps, nil
-}
diff --git a/api/step/create.go b/api/step/create.go
new file mode 100644
index 000000000..58e71a99e
--- /dev/null
+++ b/api/step/create.go
@@ -0,0 +1,125 @@
+// 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"
+ "net/http"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build}/steps steps CreateStep
+//
+// Create a step for a build
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: body
+// name: body
+// description: Payload containing the step to create
+// required: true
+// schema:
+// "$ref": "#/definitions/Step"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '201':
+// description: Successfully created the step
+// schema:
+// "$ref": "#/definitions/Step"
+// '400':
+// description: Unable to create the step
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to create the step
+// schema:
+// "$ref": "#/definitions/Error"
+
+// CreateStep represents the API handler to create
+// a step for a build in the configured backend.
+func CreateStep(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("creating new step for build %s", entry)
+
+ // capture body from API request
+ input := new(library.Step)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for new step for build %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update fields in step object
+ input.SetRepoID(r.GetID())
+ input.SetBuildID(b.GetID())
+
+ if len(input.GetStatus()) == 0 {
+ input.SetStatus(constants.StatusPending)
+ }
+
+ if input.GetCreated() == 0 {
+ input.SetCreated(time.Now().UTC().Unix())
+ }
+
+ // send API call to create the step
+ s, err := database.FromContext(c).CreateStep(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create step for build %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusCreated, s)
+}
diff --git a/api/step/delete.go b/api/step/delete.go
new file mode 100644
index 000000000..1a43c30b3
--- /dev/null
+++ b/api/step/delete.go
@@ -0,0 +1,96 @@
+// 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"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/step"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step} steps DeleteStep
+//
+// Delete a step for a build
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: path
+// name: step
+// description: Step number
+// required: true
+// type: integer
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully deleted the step
+// schema:
+// type: string
+// '500':
+// description: Successfully deleted the step
+// schema:
+// "$ref": "#/definitions/Error"
+
+// DeleteStep represents the API handler to remove
+// a step for a build from the configured backend.
+func DeleteStep(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ s := step.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "step": s.GetNumber(),
+ "user": u.GetName(),
+ }).Infof("deleting step %s", entry)
+
+ // send API call to remove the step
+ err := database.FromContext(c).DeleteStep(s)
+ if err != nil {
+ retErr := fmt.Errorf("unable to delete step %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("step %s deleted", entry))
+}
diff --git a/api/step/doc.go b/api/step/doc.go
new file mode 100644
index 000000000..fad7dce79
--- /dev/null
+++ b/api/step/doc.go
@@ -0,0 +1,10 @@
+// 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 provides the step handlers for the Vela API.
+//
+// Usage:
+//
+// import "github.com/go-vela/server/api/step"
+package step
diff --git a/api/step/get.go b/api/step/get.go
new file mode 100644
index 000000000..cf55c1bb5
--- /dev/null
+++ b/api/step/get.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 (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/step"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step} steps GetStep
+//
+// Retrieve a step for a build
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: path
+// name: step
+// description: Step number
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the step
+// schema:
+// "$ref": "#/definitions/Step"
+
+// GetStep represents the API handler to capture a
+// step for a build from the configured backend.
+func GetStep(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ s := step.Retrieve(c)
+ u := user.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "step": s.GetNumber(),
+ "user": u.GetName(),
+ }).Infof("reading step %s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
+
+ c.JSON(http.StatusOK, s)
+}
diff --git a/api/step/list.go b/api/step/list.go
new file mode 100644
index 000000000..5d76fb33c
--- /dev/null
+++ b/api/step/list.go
@@ -0,0 +1,146 @@
+// 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"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/api"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/repos/{org}/{repo}/builds/{build}/steps steps ListSteps
+//
+// Retrieve a list of steps for a build
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: query
+// name: page
+// description: The page of results to retrieve
+// type: integer
+// default: 1
+// - in: query
+// name: per_page
+// description: How many results per page to return
+// type: integer
+// maximum: 100
+// default: 10
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the list of steps
+// schema:
+// type: array
+// items:
+// "$ref": "#/definitions/Step"
+// headers:
+// X-Total-Count:
+// description: Total number of results
+// type: integer
+// Link:
+// description: see https://tools.ietf.org/html/rfc5988
+// type: string
+// '400':
+// description: Unable to retrieve the list of steps
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to retrieve the list of steps
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ListSteps represents the API handler to capture a list
+// of steps for a build from the configured backend.
+func ListSteps(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Infof("listing steps for build %s", entry)
+
+ // capture page query parameter if present
+ page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert page query parameter for build %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // capture per_page query parameter if present
+ perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert per_page query parameter for build %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // ensure per_page isn't above or below allowed values
+ perPage = util.MaxInt(1, util.MinInt(100, perPage))
+
+ // send API call to capture the list of steps for the build
+ s, t, err := database.FromContext(c).ListStepsForBuild(b, map[string]interface{}{}, page, perPage)
+ if err != nil {
+ retErr := fmt.Errorf("unable to list steps for build %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // create pagination object
+ pagination := api.Pagination{
+ Page: page,
+ PerPage: perPage,
+ Total: t,
+ }
+ // set pagination headers
+ pagination.SetHeaderLink(c)
+
+ c.JSON(http.StatusOK, s)
+}
diff --git a/api/step/plan.go b/api/step/plan.go
new file mode 100644
index 000000000..daf795284
--- /dev/null
+++ b/api/step/plan.go
@@ -0,0 +1,91 @@
+// 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"
+ "time"
+
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/go-vela/types/pipeline"
+)
+
+// PlanSteps is a helper function to plan all steps
+// in the build for execution. This creates the steps
+// for the build in the configured backend.
+func PlanSteps(database database.Interface, p *pipeline.Build, b *library.Build) ([]*library.Step, error) {
+ // variable to store planned steps
+ steps := []*library.Step{}
+
+ // iterate through all pipeline stages
+ for _, stage := range p.Stages {
+ // iterate through all steps for each pipeline stage
+ for _, step := range stage.Steps {
+ // create the step object
+ s, err := planStep(database, b, step, stage.Name)
+ if err != nil {
+ return steps, err
+ }
+
+ steps = append(steps, s)
+ }
+ }
+
+ // iterate through all pipeline steps
+ for _, step := range p.Steps {
+ s, err := planStep(database, b, step, "")
+ if err != nil {
+ return steps, err
+ }
+
+ steps = append(steps, s)
+ }
+
+ return steps, nil
+}
+
+func planStep(database database.Interface, b *library.Build, c *pipeline.Container, stage string) (*library.Step, error) {
+ // create the step object
+ s := new(library.Step)
+ s.SetBuildID(b.GetID())
+ s.SetRepoID(b.GetRepoID())
+ s.SetNumber(c.Number)
+ s.SetName(c.Name)
+ s.SetImage(c.Image)
+ s.SetStage(stage)
+ s.SetStatus(constants.StatusPending)
+ s.SetCreated(time.Now().UTC().Unix())
+
+ // send API call to create the step
+ s, err := database.CreateStep(s)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create step %s: %w", s.GetName(), err)
+ }
+
+ // populate environment variables from step library
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/library#step.Environment
+ err = c.MergeEnv(s.Environment())
+ if err != nil {
+ return nil, err
+ }
+
+ // create the log object
+ l := new(library.Log)
+ l.SetStepID(s.GetID())
+ l.SetBuildID(b.GetID())
+ l.SetRepoID(b.GetRepoID())
+ l.SetData([]byte{})
+
+ // send API call to create the step logs
+ err = database.CreateLog(l)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create logs for step %s: %w", s.GetName(), err)
+ }
+
+ return s, nil
+}
diff --git a/api/step/update.go b/api/step/update.go
new file mode 100644
index 000000000..d0563d98d
--- /dev/null
+++ b/api/step/update.go
@@ -0,0 +1,160 @@
+// 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"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/step"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation PUT /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step} steps UpdateStep
+//
+// Update a step for a build
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: org
+// description: Name of the org
+// required: true
+// type: string
+// - in: path
+// name: repo
+// description: Name of the repo
+// required: true
+// type: string
+// - in: path
+// name: build
+// description: Build number
+// required: true
+// type: integer
+// - in: path
+// name: step
+// description: Step number
+// required: true
+// type: integer
+// - in: body
+// name: body
+// description: Payload containing the step to update
+// required: true
+// schema:
+// "$ref": "#/definitions/Step"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully updated the step
+// schema:
+// "$ref": "#/definitions/Step"
+// '400':
+// description: Unable to update the step
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to update the step
+// schema:
+// "$ref": "#/definitions/Error"
+
+// UpdateStep represents the API handler to update
+// a step for a build in the configured backend.
+func UpdateStep(c *gin.Context) {
+ // capture middleware values
+ b := build.Retrieve(c)
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ s := step.Retrieve(c)
+ u := user.Retrieve(c)
+
+ entry := fmt.Sprintf("%s/%d/%d", r.GetFullName(), b.GetNumber(), s.GetNumber())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "org": o,
+ "repo": r.GetName(),
+ "step": s.GetNumber(),
+ "user": u.GetName(),
+ }).Infof("updating step %s", entry)
+
+ // capture body from API request
+ input := new(library.Step)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for step %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update step fields if provided
+ if len(input.GetStatus()) > 0 {
+ // update status if set
+ s.SetStatus(input.GetStatus())
+ }
+
+ if len(input.GetError()) > 0 {
+ // update error if set
+ s.SetError(input.GetError())
+ }
+
+ if input.GetExitCode() > 0 {
+ // update exit_code if set
+ s.SetExitCode(input.GetExitCode())
+ }
+
+ if input.GetStarted() > 0 {
+ // update started if set
+ s.SetStarted(input.GetStarted())
+ }
+
+ if input.GetFinished() > 0 {
+ // update finished if set
+ s.SetFinished(input.GetFinished())
+ }
+
+ if len(input.GetHost()) > 0 {
+ // update host if set
+ s.SetHost(input.GetHost())
+ }
+
+ if len(input.GetRuntime()) > 0 {
+ // update runtime if set
+ s.SetRuntime(input.GetRuntime())
+ }
+
+ if len(input.GetDistribution()) > 0 {
+ // update distribution if set
+ s.SetDistribution(input.GetDistribution())
+ }
+
+ // send API call to update the step
+ s, err = database.FromContext(c).UpdateStep(s)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update step %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, s)
+}
diff --git a/api/stream.go b/api/stream.go
deleted file mode 100644
index 22cf1e5b7..000000000
--- a/api/stream.go
+++ /dev/null
@@ -1,338 +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 api
-
-import (
- "bufio"
- "bytes"
- "fmt"
- "net/http"
- "time"
-
- "github.com/go-vela/server/router/middleware/org"
- "github.com/go-vela/server/router/middleware/user"
-
- "github.com/go-vela/server/database"
- "github.com/go-vela/server/router/middleware/build"
- "github.com/go-vela/server/router/middleware/repo"
- "github.com/go-vela/server/router/middleware/service"
- "github.com/go-vela/server/router/middleware/step"
- "github.com/go-vela/server/util"
-
- "github.com/gin-gonic/gin"
- "github.com/sirupsen/logrus"
-)
-
-const logUpdateInterval = 1 * time.Second
-
-// nolint:lll // due to api endpoint parameters
-// swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build}/service/{service}/stream stream PostServiceStream
-//
-// Stream the logs for a service
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: path
-// name: service
-// description: Service number
-// required: true
-// type: integer
-// - in: body
-// name: body
-// description: Payload containing logs
-// required: true
-// schema:
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '204':
-// description: Successfully received logs
-// '400':
-// description: Unable to stream the logs
-// schema:
-// "$ref": "#/definitions/Error"
-// '404':
-// description: Unable to stream the logs
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to stream the logs
-// schema:
-// "$ref": "#/definitions/Error"
-
-// PostServiceStream represents the API handler that
-// streams service logs to the database.
-// nolint: dupl // separate service/step functions for consistency with API
-func PostServiceStream(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- s := service.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logger := logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "service": s.GetNumber(),
- "user": u.GetName(),
- })
-
- logger.Infof("streaming logs for service %s/%d", entry, s.GetNumber())
-
- // create new buffer for uploading logs
- logs := new(bytes.Buffer)
- // create new channel for processing logs
- done := make(chan bool)
- // defer closing channel to stop processing logs
- defer close(done)
-
- // send API call to capture the service logs
- _log, err := database.FromContext(c).GetServiceLog(s.GetID())
- if err != nil {
- retErr := fmt.Errorf("unable to get logs for service %s/%d: %w", entry, s.GetNumber(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- go func() {
- logger.Debugf("polling request body buffer for service %s/%d", entry, s.GetNumber())
-
- // spawn "infinite" loop that will upload logs
- // from the buffer until the channel is closed
- for {
- // sleep before attempting to upload logs
- time.Sleep(logUpdateInterval)
-
- // create a non-blocking select to check if the channel is closed
- select {
- // after repo timeout of idle (no response) end the stream
- //
- // this is a safety mechanism
- case <-time.After(time.Duration(r.GetTimeout()) * time.Minute):
- logger.Tracef("repo timeout of %d exceeded", r.GetTimeout())
-
- return
- // channel is closed
- case <-done:
- logger.Trace("channel closed for polling container logs")
-
- // return out of the go routine
- return
- // channel is not closed
- default:
- // get the current size of log data
- currBytesSize := len(_log.GetData())
-
- // update the existing log with the new bytes if there is new data to add
- if len(logs.Bytes()) > currBytesSize {
- // https://pkg.go.dev/github.com/go-vela/types/library?tab=doc#Log.SetData
- _log.SetData(logs.Bytes())
-
- // update the log in the database
- err = database.FromContext(c).UpdateLog(_log)
- if err != nil {
- retErr := fmt.Errorf("unable to update logs for service %s/%d: %w", entry, s.GetNumber(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
- }
- }
- }
- }()
-
- logger.Debugf("scanning request body for service %s/%d", entry, s.GetNumber())
-
- scanner := bufio.NewScanner(c.Request.Body)
- for scanner.Scan() {
- // write all the logs from the scanner
- logs.Write(append(scanner.Bytes(), []byte("\n")...))
- }
-
- c.JSON(http.StatusNoContent, nil)
-}
-
-// nolint:lll // due to api endpoint parameters
-// swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build}/steps/{step}/stream stream PostStepStream
-//
-// Stream the logs for a step
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: org
-// description: Name of the org
-// required: true
-// type: string
-// - in: path
-// name: repo
-// description: Name of the repo
-// required: true
-// type: string
-// - in: path
-// name: build
-// description: Build number
-// required: true
-// type: integer
-// - in: path
-// name: step
-// description: Step number
-// required: true
-// type: integer
-// - in: body
-// name: body
-// description: Payload containing logs
-// required: true
-// schema:
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '204':
-// description: Successfully received logs
-// '400':
-// description: Unable to stream the logs
-// schema:
-// "$ref": "#/definitions/Error"
-// '404':
-// description: Unable to stream the logs
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to stream the logs
-// schema:
-// "$ref": "#/definitions/Error"
-
-// PostStepStream represents the API handler that
-// streams service logs to the database.
-// nolint: dupl // separate service/step functions for consistency with API
-func PostStepStream(c *gin.Context) {
- // capture middleware values
- b := build.Retrieve(c)
- o := org.Retrieve(c)
- r := repo.Retrieve(c)
- s := step.Retrieve(c)
- u := user.Retrieve(c)
-
- entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logger := logrus.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "org": o,
- "repo": r.GetName(),
- "step": s.GetNumber(),
- "user": u.GetName(),
- })
-
- logger.Infof("streaming logs for step %s/%d", entry, s.GetNumber())
-
- // create new buffer for uploading logs
- logs := new(bytes.Buffer)
- // create new channel for processing logs
- done := make(chan bool)
- // defer closing channel to stop processing logs
- defer close(done)
-
- // send API call to capture the step logs
- _log, err := database.FromContext(c).GetStepLog(s.GetID())
- if err != nil {
- retErr := fmt.Errorf("unable to get logs for step %s/%d: %w", entry, s.GetNumber(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- go func() {
- logger.Debugf("polling request body buffer for step %s/%d", entry, s.GetNumber())
-
- // spawn "infinite" loop that will upload logs
- // from the buffer until the channel is closed
- for {
- // sleep before attempting to upload logs
- time.Sleep(logUpdateInterval)
-
- // create a non-blocking select to check if the channel is closed
- select {
- // after repo timeout of idle (no response) end the stream
- //
- // this is a safety mechanism
- case <-time.After(time.Duration(r.GetTimeout()) * time.Minute):
- logger.Tracef("repo timeout of %d exceeded", r.GetTimeout())
-
- return
- // channel is closed
- case <-done:
- logger.Trace("channel closed for polling container logs")
-
- // return out of the go routine
- return
- // channel is not closed
- default:
- // get the current size of log data
- currBytesSize := len(_log.GetData())
-
- // update the existing log with the new bytes if there is new data to add
- if len(logs.Bytes()) > currBytesSize {
- // https://pkg.go.dev/github.com/go-vela/types/library?tab=doc#Log.SetData
- _log.SetData(logs.Bytes())
-
- // update the log in the database
- err = database.FromContext(c).UpdateLog(_log)
- if err != nil {
- retErr := fmt.Errorf("unable to update logs for step %s/%d: %w", entry, s.GetNumber(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
- }
- }
- }
- }()
-
- logger.Debugf("scanning request body for step %s/%d", entry, s.GetNumber())
-
- scanner := bufio.NewScanner(c.Request.Body)
- for scanner.Scan() {
- // write all the logs from the scanner
- logs.Write(append(scanner.Bytes(), []byte("\n")...))
- }
-
- c.JSON(http.StatusNoContent, nil)
-}
diff --git a/api/user.go b/api/user.go
deleted file mode 100644
index d9aaba8a0..000000000
--- a/api/user.go
+++ /dev/null
@@ -1,792 +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 api
-
-import (
- "encoding/base64"
- "fmt"
- "net/http"
- "strconv"
-
- "github.com/go-vela/server/database"
- "github.com/go-vela/server/router/middleware/token"
- "github.com/go-vela/server/router/middleware/user"
- "github.com/go-vela/server/scm"
- "github.com/go-vela/server/util"
-
- "github.com/go-vela/types/library"
-
- "github.com/gin-gonic/gin"
- "github.com/google/uuid"
- "github.com/sirupsen/logrus"
-)
-
-// swagger:operation POST /api/v1/users users CreateUser
-//
-// Create a user for the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: body
-// name: body
-// description: Payload containing the user to create
-// required: true
-// schema:
-// "$ref": "#/definitions/User"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '201':
-// description: Successfully created the user
-// schema:
-// "$ref": "#/definitions/User"
-// '400':
-// description: Unable to create the user
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to create the user
-// schema:
-// "$ref": "#/definitions/Error"
-
-// CreateUser represents the API handler to create
-// a user in the configured backend.
-func CreateUser(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
-
- // capture body from API request
- input := new(library.User)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for new user: %w", err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Infof("creating new user %s", input.GetName())
-
- // send API call to create the user
- err = database.FromContext(c).CreateUser(input)
- if err != nil {
- retErr := fmt.Errorf("unable to create user: %w", err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the created user
- user, _ := database.FromContext(c).GetUserName(input.GetName())
-
- c.JSON(http.StatusCreated, user)
-}
-
-// swagger:operation GET /api/v1/users users GetUsers
-//
-// Retrieve a list of users for the configured backend
-//
-// ---
-// produces:
-// - application/json
-// security:
-// - ApiKeyAuth: []
-// parameters:
-// - in: query
-// name: page
-// description: The page of results to retrieve
-// type: integer
-// default: 1
-// - in: query
-// name: per_page
-// description: How many results per page to return
-// type: integer
-// maximum: 100
-// default: 10
-// responses:
-// '200':
-// description: Successfully retrieved the list of users
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/User"
-// headers:
-// X-Total-Count:
-// description: Total number of results
-// type: integer
-// Link:
-// description: see https://tools.ietf.org/html/rfc5988
-// type: string
-// '400':
-// description: Unable to retrieve the list of users
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to retrieve the list of users
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetUsers represents the API handler to capture a list
-// of users from the configured backend.
-func GetUsers(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Info("reading lite users")
-
- // capture page query parameter if present
- page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
- if err != nil {
- retErr := fmt.Errorf("unable to convert page query parameter for users: %w", err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // capture per_page query parameter if present
- perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
- if err != nil {
- retErr := fmt.Errorf("unable to convert per_page query parameter for users: %w", err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // ensure per_page isn't above or below allowed values
- //
- // nolint: gomnd // ignore magic number
- perPage = util.MaxInt(1, util.MinInt(100, perPage))
-
- // send API call to capture the total number of users
- t, err := database.FromContext(c).GetUserCount()
- if err != nil {
- retErr := fmt.Errorf("unable to get users count: %w", err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the list of users
- users, err := database.FromContext(c).GetUserLiteList(page, perPage)
- if err != nil {
- retErr := fmt.Errorf("unable to get users: %w", err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // create pagination object
- pagination := Pagination{
- Page: page,
- PerPage: perPage,
- Total: t,
- }
- // set pagination headers
- pagination.SetHeaderLink(c)
-
- c.JSON(http.StatusOK, users)
-}
-
-// swagger:operation GET /api/v1/user users GetCurrentUser
-//
-// Retrieve the current authenticated user from the configured backend
-//
-// ---
-// produces:
-// - application/json
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the current user
-// schema:
-// "$ref": "#/definitions/User"
-
-// GetCurrentUser represents the API handler to capture the
-// currently authenticated user from the configured backend.
-func GetCurrentUser(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Infof("reading current user %s", u.GetName())
-
- c.JSON(http.StatusOK, u)
-}
-
-// swagger:operation PUT /api/v1/user users UpdateCurrentUser
-//
-// Update the current authenticated user in the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: body
-// name: body
-// description: Payload containing the user to update
-// required: true
-// schema:
-// "$ref": "#/definitions/User"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully updated the current user
-// schema:
-// "$ref": "#/definitions/User"
-// '400':
-// description: Unable to update the current user
-// schema:
-// "$ref": "#/definitions/Error"
-// '404':
-// description: Unable to update the current user
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to update the current user
-// schema:
-// "$ref": "#/definitions/Error"
-
-// UpdateCurrentUser represents the API handler to capture and
-// update the currently authenticated user from the configured backend.
-func UpdateCurrentUser(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Infof("updating current user %s", u.GetName())
-
- // capture body from API request
- input := new(library.User)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // update user fields if provided
- if input.Favorites != nil {
- // update favorites if set
- u.SetFavorites(input.GetFavorites())
- }
-
- // send API call to update the user
- err = database.FromContext(c).UpdateUser(u)
- if err != nil {
- retErr := fmt.Errorf("unable to update user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the updated user
- u, err = database.FromContext(c).GetUserName(u.GetName())
- if err != nil {
- retErr := fmt.Errorf("unable to get updated user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusNotFound, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, u)
-}
-
-// swagger:operation GET /api/v1/users/{user} users GetUser
-//
-// Retrieve a user for the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: user
-// description: Name of the user
-// required: true
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the user
-// schema:
-// "$ref": "#/definitions/User"
-// '404':
-// description: Unable to retrieve the user
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetUser represents the API handler to capture a
-// user from the configured backend.
-func GetUser(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
- user := c.Param("user")
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Infof("reading user %s", user)
-
- // send API call to capture the user
- u, err := database.FromContext(c).GetUserName(user)
- if err != nil {
- retErr := fmt.Errorf("unable to get user %s: %w", user, err)
-
- util.HandleError(c, http.StatusNotFound, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, u)
-}
-
-// swagger:operation GET /api/v1/user/source/repos users GetUserSourceRepos
-//
-// Retrieve a list of repos for the current authenticated user
-//
-// ---
-// produces:
-// - application/json
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved a list of repos for the current user
-// schema:
-// "$ref": "#/definitions/Repo"
-// '404':
-// description: Unable to retrieve a list of repos for the current user
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetUserSourceRepos represents the API handler to capture
-// the list of repos for a user from the configured backend.
-func GetUserSourceRepos(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Infof("reading available SCM repos for user %s", u.GetName())
-
- // variables to capture requested data
- dbRepos := []*library.Repo{}
- output := make(map[string][]library.Repo)
-
- // send API call to capture the list of repos for the user
- srcRepos, err := scm.FromContext(c).ListUserRepos(u)
- if err != nil {
- retErr := fmt.Errorf("unable to get SCM repos for user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusNotFound, retErr)
-
- return
- }
-
- // create a map
- // TODO: clean this up
- for _, srepo := range srcRepos {
- // local variables to avoid bad memory address de-referencing
- // initialize active to false
- org := srepo.Org
- name := srepo.Name
- active := false
-
- // library struct to omit optional fields
- repo := library.Repo{
- Org: org,
- Name: name,
- Active: &active,
- }
- output[srepo.GetOrg()] = append(output[srepo.GetOrg()], repo)
- }
-
- for org := range output {
- // capture source repos from the database backend, grouped by org
- page := 1
- filters := map[string]string{}
- for page > 0 {
- // send API call to capture the list of repos for the org
- // nolint: gomnd // ignore magic number
- dbReposPart, err := database.FromContext(c).GetOrgRepoList(org, filters, page, 100)
- if err != nil {
- retErr := fmt.Errorf("unable to get repos for org %s: %w", org, err)
-
- util.HandleError(c, http.StatusNotFound, retErr)
-
- return
- }
-
- // add repos to list of database org repos
- dbRepos = append(dbRepos, dbReposPart...)
-
- // assume no more pages exist if under 100 results are returned
- //
- // nolint: gomnd // ignore magic number
- if len(dbReposPart) < 100 {
- page = 0
- } else {
- page++
- }
- }
-
- // apply org repos active status to output map
- for _, dbRepo := range dbRepos {
- if orgRepos, ok := output[dbRepo.GetOrg()]; ok {
- for i := range orgRepos {
- if orgRepos[i].GetName() == dbRepo.GetName() {
- active := dbRepo.GetActive()
- (&orgRepos[i]).Active = &active
- }
- }
- }
- }
- }
-
- c.JSON(http.StatusOK, output)
-}
-
-// swagger:operation PUT /api/v1/users/{user} users UpdateUser
-//
-// Update a user for the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: user
-// description: Name of the user
-// required: true
-// type: string
-// - in: body
-// name: body
-// description: Payload containing the user to update
-// required: true
-// schema:
-// "$ref": "#/definitions/User"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully updated the user
-// schema:
-// "$ref": "#/definitions/User"
-// '400':
-// description: Unable to update the user
-// schema:
-// "$ref": "#/definitions/Error"
-// '404':
-// description: Unable to update the user
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to update the user
-// schema:
-// "$ref": "#/definitions/Error"
-
-// UpdateUser represents the API handler to update
-// a user in the configured backend.
-func UpdateUser(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
- user := c.Param("user")
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Infof("updating user %s", user)
-
- // capture body from API request
- input := new(library.User)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for user %s: %w", user, err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // send API call to capture the user
- u, err = database.FromContext(c).GetUserName(user)
- if err != nil {
- retErr := fmt.Errorf("unable to get user %s: %w", user, err)
-
- util.HandleError(c, http.StatusNotFound, retErr)
-
- return
- }
-
- // update user fields if provided
- if input.GetActive() {
- // update active if set to true
- u.SetActive(input.GetActive())
- }
-
- if input.GetAdmin() {
- // update admin if set to true
- u.SetAdmin(input.GetAdmin())
- }
-
- if input.Favorites != nil {
- // update favorites if set
- u.SetFavorites(input.GetFavorites())
- }
-
- // send API call to update the user
- err = database.FromContext(c).UpdateUser(u)
- if err != nil {
- retErr := fmt.Errorf("unable to update user %s: %w", user, err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the updated user
- u, _ = database.FromContext(c).GetUserName(user)
-
- c.JSON(http.StatusOK, u)
-}
-
-// swagger:operation DELETE /api/v1/users/{user} users DeleteUser
-//
-// Delete a user for the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: user
-// description: Name of the user
-// required: true
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully deleted of user
-// schema:
-// type: string
-// '404':
-// description: Unable to delete user
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to delete user
-// schema:
-// "$ref": "#/definitions/Error"
-
-// DeleteUser represents the API handler to remove
-// a user from the configured backend.
-func DeleteUser(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
- user := c.Param("user")
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Infof("deleting user %s", user)
-
- // send API call to capture the user
- u, err := database.FromContext(c).GetUserName(user)
- if err != nil {
- retErr := fmt.Errorf("unable to get user %s: %w", user, err)
-
- util.HandleError(c, http.StatusNotFound, retErr)
-
- return
- }
-
- // send API call to remove the user
- err = database.FromContext(c).DeleteUser(u.GetID())
- if err != nil {
- retErr := fmt.Errorf("unable to delete user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, fmt.Sprintf("user %s deleted", u.GetName()))
-}
-
-// swagger:operation POST /api/v1/user/token users CreateToken
-//
-// Create a token for the current authenticated user
-//
-// ---
-// produces:
-// - application/json
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully created a token for the current user
-// schema:
-// "$ref": "#/definitions/Login"
-// '503':
-// description: Unable to create a token for the current user
-// schema:
-// "$ref": "#/definitions/Error"
-
-// CreateToken represents the API handler to create
-// a user token in the configured backend.
-func CreateToken(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Infof("composing token for user %s", u.GetName())
-
- // compose JWT token for user
- rt, at, err := token.Compose(c, u)
- if err != nil {
- retErr := fmt.Errorf("unable to compose token for user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusServiceUnavailable, retErr)
-
- return
- }
-
- u.SetRefreshToken(rt)
-
- // send API call to update the user
- err = database.FromContext(c).UpdateUser(u)
- if err != nil {
- retErr := fmt.Errorf("unable to update user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusServiceUnavailable, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, library.Login{Token: &at})
-}
-
-// swagger:operation DELETE /api/v1/user/token users DeleteToken
-//
-// Delete a token for the current authenticated user
-//
-// ---
-// produces:
-// - application/json
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully delete a token for the current user
-// schema:
-// type: string
-// '500':
-// description: Unable to delete a token for the current user
-// schema:
-// "$ref": "#/definitions/Error"
-
-// DeleteToken represents the API handler to revoke
-// and recreate a user token in the configured backend.
-func DeleteToken(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Infof("revoking token for user %s", u.GetName())
-
- // create unique id for the user
- uid, err := uuid.NewRandom()
- if err != nil {
- retErr := fmt.Errorf("unable to create UID for user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusServiceUnavailable, retErr)
-
- return
- }
-
- u.SetHash(
- base64.StdEncoding.EncodeToString(
- []byte(uid.String()),
- ),
- )
-
- // compose JWT token for user
- rt, at, err := token.Compose(c, u)
- if err != nil {
- retErr := fmt.Errorf("unable to compose token for user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusServiceUnavailable, retErr)
-
- return
- }
-
- u.SetRefreshToken(rt)
-
- // send API call to update the user
- err = database.FromContext(c).UpdateUser(u)
- if err != nil {
- retErr := fmt.Errorf("unable to update user %s: %w", u.GetName(), err)
-
- util.HandleError(c, http.StatusServiceUnavailable, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, library.Login{Token: &at})
-}
diff --git a/api/user/create.go b/api/user/create.go
new file mode 100644
index 000000000..d6348810e
--- /dev/null
+++ b/api/user/create.go
@@ -0,0 +1,88 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/users users CreateUser
+//
+// Create a user for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: body
+// name: body
+// description: Payload containing the user to create
+// required: true
+// schema:
+// "$ref": "#/definitions/User"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '201':
+// description: Successfully created the user
+// schema:
+// "$ref": "#/definitions/User"
+// '400':
+// description: Unable to create the user
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to create the user
+// schema:
+// "$ref": "#/definitions/Error"
+
+// CreateUser represents the API handler to create
+// a user in the configured backend.
+func CreateUser(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+
+ // capture body from API request
+ input := new(library.User)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for new user: %w", err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ }).Infof("creating new user %s", input.GetName())
+
+ // send API call to create the user
+ err = database.FromContext(c).CreateUser(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create user: %w", err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // send API call to capture the created user
+ user, _ := database.FromContext(c).GetUserForName(input.GetName())
+
+ c.JSON(http.StatusCreated, user)
+}
diff --git a/api/user/create_token.go b/api/user/create_token.go
new file mode 100644
index 000000000..dee7044cb
--- /dev/null
+++ b/api/user/create_token.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.
+
+//nolint:dupl // ignore similar code with delete token
+package user
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/internal/token"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/user/token users CreateToken
+//
+// Create a token for the current authenticated user
+//
+// ---
+// produces:
+// - application/json
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully created a token for the current user
+// schema:
+// "$ref": "#/definitions/Token"
+// '503':
+// description: Unable to create a token for the current user
+// schema:
+// "$ref": "#/definitions/Error"
+
+// CreateToken represents the API handler to create
+// a user token in the configured backend.
+func CreateToken(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ }).Infof("composing token for user %s", u.GetName())
+
+ tm := c.MustGet("token-manager").(*token.Manager)
+
+ // compose JWT token for user
+ rt, at, err := tm.Compose(c, u)
+ if err != nil {
+ retErr := fmt.Errorf("unable to compose token for user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusServiceUnavailable, retErr)
+
+ return
+ }
+
+ u.SetRefreshToken(rt)
+
+ // send API call to update the user
+ err = database.FromContext(c).UpdateUser(u)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusServiceUnavailable, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, library.Token{Token: &at})
+}
diff --git a/api/user/delete.go b/api/user/delete.go
new file mode 100644
index 000000000..9e0651e7e
--- /dev/null
+++ b/api/user/delete.go
@@ -0,0 +1,82 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation DELETE /api/v1/users/{user} users DeleteUser
+//
+// Delete a user for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: user
+// description: Name of the user
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully deleted of user
+// schema:
+// type: string
+// '404':
+// description: Unable to delete user
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to delete user
+// schema:
+// "$ref": "#/definitions/Error"
+
+// DeleteUser represents the API handler to remove
+// a user from the configured backend.
+func DeleteUser(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+ user := util.PathParameter(c, "user")
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ }).Infof("deleting user %s", user)
+
+ // send API call to capture the user
+ u, err := database.FromContext(c).GetUserForName(user)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get user %s: %w", user, err)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ // send API call to remove the user
+ err = database.FromContext(c).DeleteUser(u)
+ if err != nil {
+ retErr := fmt.Errorf("unable to delete user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("user %s deleted", u.GetName()))
+}
diff --git a/api/user/delete_token.go b/api/user/delete_token.go
new file mode 100644
index 000000000..22fa9724d
--- /dev/null
+++ b/api/user/delete_token.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.
+
+//nolint:dupl // ignore similar code with create token
+package user
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/internal/token"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation DELETE /api/v1/user/token users DeleteToken
+//
+// Delete a token for the current authenticated user
+//
+// ---
+// produces:
+// - application/json
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully delete a token for the current user
+// schema:
+// type: string
+// '500':
+// description: Unable to delete a token for the current user
+// schema:
+// "$ref": "#/definitions/Error"
+
+// DeleteToken represents the API handler to revoke
+// and recreate a user token in the configured backend.
+func DeleteToken(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ }).Infof("revoking token for user %s", u.GetName())
+
+ tm := c.MustGet("token-manager").(*token.Manager)
+
+ // compose JWT token for user
+ rt, at, err := tm.Compose(c, u)
+ if err != nil {
+ retErr := fmt.Errorf("unable to compose token for user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusServiceUnavailable, retErr)
+
+ return
+ }
+
+ u.SetRefreshToken(rt)
+
+ // send API call to update the user
+ err = database.FromContext(c).UpdateUser(u)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusServiceUnavailable, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, library.Token{Token: &at})
+}
diff --git a/api/user/doc.go b/api/user/doc.go
new file mode 100644
index 000000000..7f1ce6bc5
--- /dev/null
+++ b/api/user/doc.go
@@ -0,0 +1,10 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+// Package user provides the user handlers for the Vela API.
+//
+// Usage:
+//
+// import "github.com/go-vela/server/api/user"
+package user
diff --git a/api/user/get.go b/api/user/get.go
new file mode 100644
index 000000000..9f0b723d3
--- /dev/null
+++ b/api/user/get.go
@@ -0,0 +1,68 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/users/{user} users GetUser
+//
+// Retrieve a user for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: user
+// description: Name of the user
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the user
+// schema:
+// "$ref": "#/definitions/User"
+// '404':
+// description: Unable to retrieve the user
+// schema:
+// "$ref": "#/definitions/Error"
+
+// GetUser represents the API handler to capture a
+// user from the configured backend.
+func GetUser(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+ user := util.PathParameter(c, "user")
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ }).Infof("reading user %s", user)
+
+ // send API call to capture the user
+ u, err := database.FromContext(c).GetUserForName(user)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get user %s: %w", user, err)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, u)
+}
diff --git a/api/user/get_current.go b/api/user/get_current.go
new file mode 100644
index 000000000..b4b1ef4c3
--- /dev/null
+++ b/api/user/get_current.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 user
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/user users GetCurrentUser
+//
+// Retrieve the current authenticated user from the configured backend
+//
+// ---
+// produces:
+// - application/json
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the current user
+// schema:
+// "$ref": "#/definitions/User"
+
+// GetCurrentUser represents the API handler to capture the
+// currently authenticated user from the configured backend.
+func GetCurrentUser(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ }).Infof("reading current user %s", u.GetName())
+
+ c.JSON(http.StatusOK, u)
+}
diff --git a/api/user/get_source.go b/api/user/get_source.go
new file mode 100644
index 000000000..9892f319e
--- /dev/null
+++ b/api/user/get_source.go
@@ -0,0 +1,126 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/user/source/repos users GetSourceRepos
+//
+// Retrieve a list of repos for the current authenticated user
+//
+// ---
+// produces:
+// - application/json
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved a list of repos for the current user
+// schema:
+// "$ref": "#/definitions/Repo"
+// '404':
+// description: Unable to retrieve a list of repos for the current user
+// schema:
+// "$ref": "#/definitions/Error"
+
+// GetSourceRepos represents the API handler to capture
+// the list of repos for a user from the configured backend.
+func GetSourceRepos(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ }).Infof("reading available SCM repos for user %s", u.GetName())
+
+ // variables to capture requested data
+ dbRepos := []*library.Repo{}
+ output := make(map[string][]library.Repo)
+
+ // send API call to capture the list of repos for the user
+ srcRepos, err := scm.FromContext(c).ListUserRepos(u)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get SCM repos for user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ // create a map
+ // TODO: clean this up
+ for _, srepo := range srcRepos {
+ // local variables to avoid bad memory address de-referencing
+ // initialize active to false
+ org := srepo.Org
+ name := srepo.Name
+ active := false
+
+ // library struct to omit optional fields
+ repo := library.Repo{
+ Org: org,
+ Name: name,
+ Active: &active,
+ }
+ output[srepo.GetOrg()] = append(output[srepo.GetOrg()], repo)
+ }
+
+ for org := range output {
+ // capture source repos from the database backend, grouped by org
+ page := 1
+ filters := map[string]interface{}{}
+
+ for page > 0 {
+ // send API call to capture the list of repos for the org
+ dbReposPart, _, err := database.FromContext(c).ListReposForOrg(ctx, org, "name", filters, page, 100)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get repos for org %s: %w", org, err)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ // add repos to list of database org repos
+ dbRepos = append(dbRepos, dbReposPart...)
+
+ // assume no more pages exist if under 100 results are returned
+ if len(dbReposPart) < 100 {
+ page = 0
+ } else {
+ page++
+ }
+ }
+
+ // apply org repos active status to output map
+ for _, dbRepo := range dbRepos {
+ if orgRepos, ok := output[dbRepo.GetOrg()]; ok {
+ for i := range orgRepos {
+ if orgRepos[i].GetName() == dbRepo.GetName() {
+ active := dbRepo.GetActive()
+ (&orgRepos[i]).Active = &active
+ }
+ }
+ }
+ }
+ }
+
+ c.JSON(http.StatusOK, output)
+}
diff --git a/api/user/list.go b/api/user/list.go
new file mode 100644
index 000000000..1f879bfef
--- /dev/null
+++ b/api/user/list.go
@@ -0,0 +1,120 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/api"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/users users ListUsers
+//
+// Retrieve a list of users for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// security:
+// - ApiKeyAuth: []
+// parameters:
+// - in: query
+// name: page
+// description: The page of results to retrieve
+// type: integer
+// default: 1
+// - in: query
+// name: per_page
+// description: How many results per page to return
+// type: integer
+// maximum: 100
+// default: 10
+// responses:
+// '200':
+// description: Successfully retrieved the list of users
+// schema:
+// type: array
+// items:
+// "$ref": "#/definitions/User"
+// headers:
+// X-Total-Count:
+// description: Total number of results
+// type: integer
+// Link:
+// description: see https://tools.ietf.org/html/rfc5988
+// type: string
+// '400':
+// description: Unable to retrieve the list of users
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to retrieve the list of users
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ListUsers represents the API handler to capture a list
+// of users from the configured backend.
+func ListUsers(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ }).Info("reading lite users")
+
+ // capture page query parameter if present
+ page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert page query parameter for users: %w", err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // capture per_page query parameter if present
+ perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10"))
+ if err != nil {
+ retErr := fmt.Errorf("unable to convert per_page query parameter for users: %w", err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // ensure per_page isn't above or below allowed values
+ perPage = util.MaxInt(1, util.MinInt(100, perPage))
+
+ // send API call to capture the list of users
+ users, t, err := database.FromContext(c).ListLiteUsers(page, perPage)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get users: %w", err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // create pagination object
+ pagination := api.Pagination{
+ Page: page,
+ PerPage: perPage,
+ Total: t,
+ }
+ // set pagination headers
+ pagination.SetHeaderLink(c)
+
+ c.JSON(http.StatusOK, users)
+}
diff --git a/api/user/update.go b/api/user/update.go
new file mode 100644
index 000000000..d2fb631ab
--- /dev/null
+++ b/api/user/update.go
@@ -0,0 +1,124 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation PUT /api/v1/users/{user} users UpdateUser
+//
+// Update a user for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: user
+// description: Name of the user
+// required: true
+// type: string
+// - in: body
+// name: body
+// description: Payload containing the user to update
+// required: true
+// schema:
+// "$ref": "#/definitions/User"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully updated the user
+// schema:
+// "$ref": "#/definitions/User"
+// '400':
+// description: Unable to update the user
+// schema:
+// "$ref": "#/definitions/Error"
+// '404':
+// description: Unable to update the user
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to update the user
+// schema:
+// "$ref": "#/definitions/Error"
+
+// UpdateUser represents the API handler to update
+// a user in the configured backend.
+func UpdateUser(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+ user := util.PathParameter(c, "user")
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ }).Infof("updating user %s", user)
+
+ // capture body from API request
+ input := new(library.User)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for user %s: %w", user, err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // send API call to capture the user
+ u, err = database.FromContext(c).GetUserForName(user)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get user %s: %w", user, err)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ // update user fields if provided
+ if input.GetActive() {
+ // update active if set to true
+ u.SetActive(input.GetActive())
+ }
+
+ if input.GetAdmin() {
+ // update admin if set to true
+ u.SetAdmin(input.GetAdmin())
+ }
+
+ if input.Favorites != nil {
+ // update favorites if set
+ u.SetFavorites(input.GetFavorites())
+ }
+
+ // send API call to update the user
+ err = database.FromContext(c).UpdateUser(u)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update user %s: %w", user, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // send API call to capture the updated user
+ u, _ = database.FromContext(c).GetUserForName(user)
+
+ c.JSON(http.StatusOK, u)
+}
diff --git a/api/user/update_current.go b/api/user/update_current.go
new file mode 100644
index 000000000..ed861ae61
--- /dev/null
+++ b/api/user/update_current.go
@@ -0,0 +1,105 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation PUT /api/v1/user users UpdateCurrentUser
+//
+// Update the current authenticated user in the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: body
+// name: body
+// description: Payload containing the user to update
+// required: true
+// schema:
+// "$ref": "#/definitions/User"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully updated the current user
+// schema:
+// "$ref": "#/definitions/User"
+// '400':
+// description: Unable to update the current user
+// schema:
+// "$ref": "#/definitions/Error"
+// '404':
+// description: Unable to update the current user
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to update the current user
+// schema:
+// "$ref": "#/definitions/Error"
+
+// UpdateCurrentUser represents the API handler to capture and
+// update the currently authenticated user from the configured backend.
+func UpdateCurrentUser(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ }).Infof("updating current user %s", u.GetName())
+
+ // capture body from API request
+ input := new(library.User)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update user fields if provided
+ if input.Favorites != nil {
+ // update favorites if set
+ u.SetFavorites(input.GetFavorites())
+ }
+
+ // send API call to update the user
+ err = database.FromContext(c).UpdateUser(u)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // send API call to capture the updated user
+ u, err = database.FromContext(c).GetUserForName(u.GetName())
+ if err != nil {
+ retErr := fmt.Errorf("unable to get updated user %s: %w", u.GetName(), err)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, u)
+}
diff --git a/api/webhook.go b/api/webhook.go
deleted file mode 100644
index d5641e677..000000000
--- a/api/webhook.go
+++ /dev/null
@@ -1,679 +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 api
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net/http"
- "strings"
- "time"
-
- "github.com/go-vela/server/compiler"
- "github.com/go-vela/server/database"
- "github.com/go-vela/server/queue"
- "github.com/go-vela/server/scm"
- "github.com/go-vela/server/util"
-
- "github.com/go-vela/types"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/library"
- "github.com/go-vela/types/pipeline"
-
- "github.com/gin-gonic/gin"
- "github.com/sirupsen/logrus"
-)
-
-var baseErr = "unable to process webhook"
-
-// swagger:operation POST /webhook base PostWebhook
-//
-// Deliver a webhook to the vela api
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: body
-// name: body
-// description: Webhook payload that we expect from the user or VCS
-// required: true
-// schema:
-// "$ref": "#/definitions/Webhook"
-// responses:
-// '200':
-// description: Successfully received the webhook
-// schema:
-// "$ref": "#/definitions/Build"
-// '400':
-// description: Malformed webhook payload
-// schema:
-// "$ref": "#/definitions/Error"
-// '404':
-// description: Unable to receive the webhook
-// schema:
-// "$ref": "#/definitions/Error"
-// '401':
-// description: Unauthenticated
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to receive the webhook
-// schema:
-// "$ref": "#/definitions/Error"
-
-// PostWebhook represents the API handler to capture
-// a webhook from a source control provider and
-// publish it to the configure queue.
-//
-// nolint: funlen,gocyclo // ignore function length and cyclomatic complexity
-func PostWebhook(c *gin.Context) {
- logrus.Info("webhook received")
-
- // capture middleware values
- m := c.MustGet("metadata").(*types.Metadata)
-
- // duplicate request so we can perform operations on the request body
- //
- // https://golang.org/pkg/net/http/#Request.Clone
- dupRequest := c.Request.Clone(context.TODO())
-
- // -------------------- Start of TODO: --------------------
- //
- // Remove the below code once http.Request.Clone()
- // actually performs a deep clone.
- //
- // This code is required due to a known bug:
- //
- // * https://github.com/golang/go/issues/36095
-
- // create buffer for reading request body
- var buf bytes.Buffer
-
- // read the request body for duplication
- _, err := buf.ReadFrom(c.Request.Body)
- if err != nil {
- retErr := fmt.Errorf("unable to read webhook body: %v", err)
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // add the request body to the original request
- c.Request.Body = ioutil.NopCloser(&buf)
-
- // add the request body to the duplicate request
- dupRequest.Body = ioutil.NopCloser(bytes.NewReader(buf.Bytes()))
- //
- // -------------------- End of TODO: --------------------
-
- // process the webhook from the source control provider
- // comment, number, h, r, b
- webhook, err := scm.FromContext(c).ProcessWebhook(c.Request)
- if err != nil {
- retErr := fmt.Errorf("unable to parse webhook: %v", err)
- util.HandleError(c, http.StatusBadRequest, retErr)
- return
- }
-
- // check if the hook should be skipped
- if skip, skipReason := webhook.ShouldSkip(); skip {
- c.JSON(http.StatusOK, fmt.Sprintf("skipping build: %s", skipReason))
-
- return
- }
-
- h, r, b := webhook.Hook, webhook.Repo, webhook.Build
-
- defer func() {
- // send API call to update the webhook
- err = database.FromContext(c).UpdateHook(h)
- if err != nil {
- logrus.Errorf("unable to update webhook %s/%s: %v", r.GetFullName(), h.GetSourceID(), err)
- }
- }()
-
- // check if build was parsed from webhook
- if b == nil && h.GetEvent() != constants.EventRepositoryRename {
- // typically, this should only happen on a webhook
- // "ping" which gets sent when the webhook is created
- c.JSON(http.StatusOK, "no build to process")
-
- return
- }
-
- // check if repo was parsed from webhook
- if r == nil {
- retErr := fmt.Errorf("%s: failed to parse repo from webhook", baseErr)
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
-
- if h.GetEvent() == constants.EventRepositoryRename {
- err = renameRepository(h, r, c)
- if err != nil {
- util.HandleError(c, http.StatusBadRequest, err)
- h.SetStatus(constants.StatusFailure)
- h.SetError(err.Error())
- return
- }
- return
- }
-
- // send API call to capture parsed repo from webhook
- r, err = database.FromContext(c).GetRepo(r.GetOrg(), r.GetName())
-
- if err != nil {
- retErr := fmt.Errorf("%s: failed to get repo %s: %v", baseErr, r.GetFullName(), err)
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
-
- // set the RepoID fields
- b.SetRepoID(r.GetID())
- h.SetRepoID(r.GetID())
-
- // send API call to capture the last hook for the repo
- lastHook, err := database.FromContext(c).GetLastHook(r)
- if err != nil {
- retErr := fmt.Errorf("unable to get last hook for repo %s: %v", r.GetFullName(), err)
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
-
- // set the Number field
- if lastHook != nil {
- h.SetNumber(
- lastHook.GetNumber() + 1,
- )
- }
-
- // send API call to create the webhook
- err = database.FromContext(c).CreateHook(h)
- if err != nil {
- retErr := fmt.Errorf("unable to create webhook %s/%d: %v", r.GetFullName(), h.GetNumber(), err)
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
-
- // send API call to capture the created webhook
- h, _ = database.FromContext(c).GetHook(h.GetNumber(), r)
-
- // verify the webhook from the source control provider
- if c.Value("webhookvalidation").(bool) {
- err = scm.FromContext(c).VerifyWebhook(dupRequest, r)
- if err != nil {
- retErr := fmt.Errorf("unable to verify webhook: %v", err)
- util.HandleError(c, http.StatusUnauthorized, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
- }
-
- // check if the repo is active
- if !r.GetActive() {
- retErr := fmt.Errorf("%s: %s is not an active repo", baseErr, r.GetFullName())
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
-
- // verify the build has a valid event and the repo allows that event type
- if (b.GetEvent() == constants.EventPush && !r.GetAllowPush()) ||
- (b.GetEvent() == constants.EventPull && !r.GetAllowPull()) ||
- (b.GetEvent() == constants.EventComment && !r.GetAllowComment()) ||
- (b.GetEvent() == constants.EventTag && !r.GetAllowTag()) ||
- (b.GetEvent() == constants.EventDeploy && !r.GetAllowDeploy()) {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("%s: %s does not have %s events enabled", baseErr, r.GetFullName(), b.GetEvent())
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
-
- // check if the repo has a valid owner
- if r.GetUserID() == 0 {
- retErr := fmt.Errorf("%s: %s has no valid owner", baseErr, r.GetFullName())
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
-
- // send API call to capture repo owner
- u, err := database.FromContext(c).GetUser(r.GetUserID())
- if err != nil {
- retErr := fmt.Errorf("%s: failed to get owner for %s: %v", baseErr, r.GetFullName(), err)
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
-
- // create SQL filters for querying pending and running builds for repo
- filters := map[string]interface{}{
- "status": []string{constants.StatusPending, constants.StatusRunning},
- }
-
- // send API call to capture the number of pending or running builds for the repo
- builds, err := database.FromContext(c).GetRepoBuildCount(r, filters)
- if err != nil {
- retErr := fmt.Errorf("%s: unable to get count of builds for repo %s", baseErr, r.GetFullName())
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
-
- // check if the number of pending and running builds exceeds the limit for the repo
- if builds >= r.GetBuildLimit() {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("%s: repo %s has exceeded the concurrent build limit of %d", baseErr, r.GetFullName(), r.GetBuildLimit())
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
-
- // update fields in build object
- b.SetNumber(r.GetCounter())
- b.SetParent(b.GetNumber())
- b.SetStatus(constants.StatusPending)
-
- // if this is a comment on a pull_request event
- if strings.EqualFold(b.GetEvent(), constants.EventComment) && webhook.PRNumber > 0 {
- commit, branch, baseref, headref, err := scm.FromContext(c).GetPullRequest(u, r, webhook.PRNumber)
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("%s: failed to get pull request info for %s: %v", baseErr, r.GetFullName(), err)
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
-
- b.SetCommit(commit)
- b.SetBranch(strings.Replace(branch, "refs/heads/", "", -1))
- b.SetBaseRef(baseref)
- b.SetHeadRef(headref)
- }
-
- // variable to store changeset files
- var files []string
- // check if the build event is not issue_comment
- if !strings.EqualFold(b.GetEvent(), constants.EventComment) {
- // check if the build event is not pull_request
- if !strings.EqualFold(b.GetEvent(), constants.EventPull) {
- // send API call to capture list of files changed for the commit
- files, err = scm.FromContext(c).Changeset(u, r, b.GetCommit())
- if err != nil {
- retErr := fmt.Errorf("%s: failed to get changeset for %s: %v", baseErr, r.GetFullName(), err)
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
- }
- }
-
- // check if the build event is a pull_request
- if strings.EqualFold(b.GetEvent(), constants.EventPull) && webhook.PRNumber > 0 {
- // send API call to capture list of files changed for the pull request
- files, err = scm.FromContext(c).ChangesetPR(u, r, webhook.PRNumber)
- if err != nil {
- retErr := fmt.Errorf("%s: failed to get changeset for %s: %v", baseErr, r.GetFullName(), err)
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
- }
-
- // send API call to capture the pipeline configuration file
- config, err := scm.FromContext(c).ConfigBackoff(u, r, b.GetCommit())
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("%s: failed to get pipeline configuration for %s: %v", baseErr, r.GetFullName(), err)
- util.HandleError(c, http.StatusNotFound, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
-
- // variable to store pipeline
- var p *pipeline.Build
- // number of times to retry
- retryLimit := 3
-
- // iterate through with a retryLimit
- for i := 0; i < retryLimit; i++ {
- // check if we're on the first iteration of the loop
- if i > 0 {
- // incrementally sleep in between retries
- time.Sleep(time.Duration(i) * time.Second)
- }
-
- // send API call to capture repo for the counter
- r, err = database.FromContext(c).GetRepo(r.GetOrg(), r.GetName())
- if err != nil {
- retErr := fmt.Errorf("%s: failed to get repo %s: %v", baseErr, r.GetFullName(), err)
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
-
- // set the parent equal to the current repo counter
- b.SetParent(r.GetCounter())
-
- // check if the parent is set to 0
- if b.GetParent() == 0 {
- // parent should be "1" if it's the first build ran
- b.SetParent(1)
- }
-
- // update the build numbers based off repo counter
- inc := r.GetCounter() + 1
-
- r.SetCounter(inc)
- b.SetNumber(inc)
-
- // populate the build link if a web address is provided
- if len(m.Vela.WebAddress) > 0 {
- b.SetLink(
- fmt.Sprintf("%s/%s/%d", m.Vela.WebAddress, r.GetFullName(), b.GetNumber()),
- )
- }
-
- // parse and compile the pipeline configuration file
- p, err = compiler.FromContext(c).
- Duplicate().
- WithBuild(b).
- WithComment(webhook.Comment).
- WithFiles(files).
- WithMetadata(m).
- WithRepo(r).
- WithUser(u).
- Compile(config)
- if err != nil {
- // format the error message with extra information
- err = fmt.Errorf("unable to compile pipeline configuration for %s: %v", r.GetFullName(), err)
-
- // log the error for traceability
- logrus.Error(err.Error())
-
- retErr := fmt.Errorf("%s: %v", baseErr, err)
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
-
- // skip the build if only the init or clone steps are found
- skip := skipEmptyBuild(p)
- if skip != "" {
- // set build to successful status
- b.SetStatus(constants.StatusSkipped)
-
- // send API call to set the status on the commit
- err = scm.FromContext(c).Status(u, b, r.GetOrg(), r.GetName())
- if err != nil {
- logrus.Errorf("unable to set commit status for %s/%d: %v", r.GetFullName(), b.GetNumber(), err)
- }
-
- c.JSON(http.StatusOK, skip)
- return
- }
-
- // create the objects from the pipeline in the database
- err = planBuild(database.FromContext(c), p, b, r)
- if err != nil {
- // log the error for traceability
- logrus.Error(err.Error())
-
- // check if the retry limit has been exceeded
- if i < retryLimit {
- // reset fields set by cleanBuild for retry
- b.SetError("")
- b.SetStatus(constants.StatusPending)
- b.SetFinished(0)
-
- // continue to the next iteration of the loop
- continue
- }
-
- retErr := fmt.Errorf("%s: %v", baseErr, err)
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
-
- // break the loop because everything was successful
- break
- }
-
- // send API call to update repo for ensuring counter is incremented
- err = database.FromContext(c).UpdateRepo(r)
- if err != nil {
- retErr := fmt.Errorf("%s: failed to update repo %s: %v", baseErr, r.GetFullName(), err)
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
-
- return
- }
-
- // send API call to capture the triggered build
- b, err = database.FromContext(c).GetBuild(b.GetNumber(), r)
- if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("%s: failed to get new build %s/%d: %v", baseErr, r.GetFullName(), b.GetNumber(), err)
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
- }
-
- // set the BuildID field
- h.SetBuildID(b.GetID())
-
- c.JSON(http.StatusOK, b)
-
- // send API call to set the status on the commit
- err = scm.FromContext(c).Status(u, b, r.GetOrg(), r.GetName())
- if err != nil {
- logrus.Errorf("unable to set commit status for %s/%d: %v", r.GetFullName(), b.GetNumber(), err)
- }
-
- // publish the build to the queue
- go publishToQueue(
- queue.FromGinContext(c),
- database.FromContext(c),
- p,
- b,
- r,
- u,
- )
-}
-
-// publishToQueue is a helper function that creates
-// a build item and publishes it to the queue.
-//
-// nolint: lll // ignore long line length due to variables
-func publishToQueue(queue queue.Service, db database.Service, p *pipeline.Build, b *library.Build, r *library.Repo, u *library.User) {
- item := types.ToItem(p, b, r, u)
-
- logrus.Infof("Converting queue item to json for build %d for %s", b.GetNumber(), r.GetFullName())
-
- byteItem, err := json.Marshal(item)
- if err != nil {
- logrus.Errorf("Failed to convert item to json for build %d for %s: %v", b.GetNumber(), r.GetFullName(), err)
-
- // error out the build
- cleanBuild(db, b, nil, nil)
-
- return
- }
-
- logrus.Infof("Establishing route for build %d for %s", b.GetNumber(), r.GetFullName())
-
- route, err := queue.Route(&p.Worker)
- if err != nil {
- logrus.Errorf("unable to set route for build %d for %s: %v", b.GetNumber(), r.GetFullName(), err)
-
- // error out the build
- cleanBuild(db, b, nil, nil)
-
- return
- }
-
- logrus.Infof("Publishing item for build %d for %s to queue %s", b.GetNumber(), r.GetFullName(), route)
-
- err = queue.Push(context.Background(), route, byteItem)
- if err != nil {
- logrus.Errorf("Retrying; Failed to publish build %d for %s: %v", b.GetNumber(), r.GetFullName(), err)
-
- err = queue.Push(context.Background(), route, byteItem)
- if err != nil {
- logrus.Errorf("Failed to publish build %d for %s: %v", b.GetNumber(), r.GetFullName(), err)
-
- // error out the build
- cleanBuild(db, b, nil, nil)
-
- return
- }
- }
-
- // update fields in build object
- b.SetEnqueued(time.Now().UTC().Unix())
-
- // update the build in the db to reflect the time it was enqueued
- err = db.UpdateBuild(b)
- if err != nil {
- logrus.Errorf("Failed to update build %d during publish to queue for %s: %v", b.GetNumber(), r.GetFullName(), err)
- }
-}
-
-// renameRepository is a helper function that takes the old name of the repo,
-// queries the database for the repo that matches that name and org, and updates
-// that repo to its new name in order to preserve it. It also updates the secrets
-// associated with that repo.
-func renameRepository(h *library.Hook, r *library.Repo, c *gin.Context) error {
- // get the old name of the repo
- previousName := r.GetPreviousName()
- // get the repo from the database that matches the old name
- dbR, err := database.FromContext(c).GetRepo(r.GetOrg(), previousName)
- if err != nil {
- // nolint: lll // ignore long line for error formatting
- retErr := fmt.Errorf("%s: failed to get repo %s/%s from database", baseErr, r.GetOrg(), previousName)
- util.HandleError(c, http.StatusBadRequest, retErr)
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
- return retErr
- }
-
- // update the repo name information
- dbR.SetName(r.GetName())
- dbR.SetFullName(r.GetFullName())
- dbR.SetClone(r.GetClone())
- dbR.SetLink(r.GetLink())
- dbR.SetPreviousName(previousName)
-
- // update the repo in the database
- err = database.FromContext(c).UpdateRepo(dbR)
- if err != nil {
- // nolint: lll // ignore long line for error formatting
- retErr := fmt.Errorf("%s: failed to update repo %s/%s in database", baseErr, r.GetOrg(), previousName)
- util.HandleError(c, http.StatusBadRequest, retErr)
- h.SetStatus(constants.StatusFailure)
- h.SetError(retErr.Error())
- return retErr
- }
-
- // get total number of secrets associated with repository
- // nolint: lll // ignore long line due to extensive function arguments
- t, err := database.FromContext(c).GetTypeSecretCount(constants.SecretRepo, r.GetOrg(), previousName, []string{})
- if err != nil {
- return fmt.Errorf("unable to get secret count for repo %s/%s: %w", r.GetOrg(), previousName, err)
- }
- secrets := []*library.Secret{}
-
- page := 1
- // capture all secrets belonging to certain repo in database
- // nolint: gomnd // ignore magic number
- for repoSecrets := int64(0); repoSecrets < t; repoSecrets += 100 {
- // nolint: lll // ignore long line due to extensive function arguments
- s, err := database.FromContext(c).GetTypeSecretList(constants.SecretRepo, r.GetOrg(), previousName, page, 100, []string{})
- if err != nil {
- return fmt.Errorf("unable to get secret list for repo %s/%s: %w", r.GetOrg(), previousName, err)
- }
- secrets = append(secrets, s...)
- page++
- }
-
- // update secrets to point to the new repository name
- for _, secret := range secrets {
- secret.SetRepo(r.GetName())
- err = database.FromContext(c).UpdateSecret(secret)
- if err != nil {
- return fmt.Errorf("unable to update secret for repo %s/%s: %w", r.GetOrg(), previousName, err)
- }
- }
-
- c.JSON(http.StatusOK, r)
- return nil
-}
diff --git a/api/webhook/doc.go b/api/webhook/doc.go
new file mode 100644
index 000000000..0acc05476
--- /dev/null
+++ b/api/webhook/doc.go
@@ -0,0 +1,10 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+// Package webhook provides the webhook handlers for the Vela API.
+//
+// Usage:
+//
+// import "github.com/go-vela/server/api/webhook"
+package webhook
diff --git a/api/webhook/post.go b/api/webhook/post.go
new file mode 100644
index 000000000..ce11a614f
--- /dev/null
+++ b/api/webhook/post.go
@@ -0,0 +1,909 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package webhook
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "io"
+ "net/http"
+ "reflect"
+ "strings"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/api/build"
+ "github.com/go-vela/server/compiler"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/queue"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/go-vela/types/pipeline"
+ "github.com/sirupsen/logrus"
+)
+
+var baseErr = "unable to process webhook"
+
+// swagger:operation POST /webhook base PostWebhook
+//
+// Deliver a webhook to the vela api
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: body
+// name: body
+// description: Webhook payload that we expect from the user or VCS
+// required: true
+// schema:
+// "$ref": "#/definitions/Webhook"
+// responses:
+// '200':
+// description: Successfully received the webhook
+// schema:
+// "$ref": "#/definitions/Build"
+// '400':
+// description: Malformed webhook payload
+// schema:
+// "$ref": "#/definitions/Error"
+// '404':
+// description: Unable to receive the webhook
+// schema:
+// "$ref": "#/definitions/Error"
+// '401':
+// description: Unauthenticated
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to receive the webhook
+// schema:
+// "$ref": "#/definitions/Error"
+
+// PostWebhook represents the API handler to capture
+// a webhook from a source control provider and
+// publish it to the configure queue.
+//
+//nolint:funlen,gocyclo // ignore function length and cyclomatic complexity
+func PostWebhook(c *gin.Context) {
+ logrus.Info("webhook received")
+
+ // capture middleware values
+ m := c.MustGet("metadata").(*types.Metadata)
+ ctx := c.Request.Context()
+
+ // duplicate request so we can perform operations on the request body
+ //
+ // https://golang.org/pkg/net/http/#Request.Clone
+ dupRequest := c.Request.Clone(ctx)
+
+ // -------------------- Start of TODO: --------------------
+ //
+ // Remove the below code once http.Request.Clone()
+ // actually performs a deep clone.
+ //
+ // This code is required due to a known bug:
+ //
+ // * https://github.com/golang/go/issues/36095
+
+ // create buffer for reading request body
+ var buf bytes.Buffer
+
+ // read the request body for duplication
+ _, err := buf.ReadFrom(c.Request.Body)
+ if err != nil {
+ retErr := fmt.Errorf("unable to read webhook body: %w", err)
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // add the request body to the original request
+ c.Request.Body = io.NopCloser(&buf)
+
+ // add the request body to the duplicate request
+ dupRequest.Body = io.NopCloser(bytes.NewReader(buf.Bytes()))
+ //
+ // -------------------- End of TODO: --------------------
+
+ // process the webhook from the source control provider
+ // comment, number, h, r, b
+ webhook, err := scm.FromContext(c).ProcessWebhook(c.Request)
+ if err != nil {
+ retErr := fmt.Errorf("unable to parse webhook: %w", err)
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // check if the hook should be skipped
+ if skip, skipReason := webhook.ShouldSkip(); skip {
+ c.JSON(http.StatusOK, fmt.Sprintf("skipping build: %s", skipReason))
+
+ return
+ }
+
+ h, r, b := webhook.Hook, webhook.Repo, webhook.Build
+
+ logrus.Debugf("hook generated from SCM: %v", h)
+ logrus.Debugf("repo generated from SCM: %v", r)
+
+ // if event is repository event, handle separately and return
+ if strings.EqualFold(h.GetEvent(), constants.EventRepository) {
+ r, err = handleRepositoryEvent(ctx, c, m, h, r)
+ if err != nil {
+ util.HandleError(c, http.StatusInternalServerError, err)
+ return
+ }
+
+ // if there were actual changes to the repo, return the repo object
+ if r.GetID() != 0 {
+ c.JSON(http.StatusOK, r)
+ return
+ }
+
+ c.JSON(http.StatusOK, "handled repository event, no build to process")
+
+ return
+ }
+
+ // check if build was parsed from webhook.
+ if b == nil {
+ // typically, this should only happen on a webhook
+ // "ping" which gets sent when the webhook is created
+ c.JSON(http.StatusOK, "no build to process")
+
+ return
+ }
+
+ logrus.Debugf(`build author: %s,
+ build branch: %s,
+ build commit: %s,
+ build ref: %s`,
+ b.GetAuthor(), b.GetBranch(), b.GetCommit(), b.GetRef())
+
+ // check if repo was parsed from webhook
+ if r == nil {
+ retErr := fmt.Errorf("%s: failed to parse repo from webhook", baseErr)
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ defer func() {
+ // send API call to update the webhook
+ _, err = database.FromContext(c).UpdateHook(h)
+ if err != nil {
+ logrus.Errorf("unable to update webhook %s/%d: %v", r.GetFullName(), h.GetNumber(), err)
+ }
+ }()
+
+ // send API call to capture parsed repo from webhook
+ repo, err := database.FromContext(c).GetRepoForOrg(ctx, r.GetOrg(), r.GetName())
+ if err != nil {
+ retErr := fmt.Errorf("%s: failed to get repo %s: %w", baseErr, r.GetFullName(), err)
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ // set the RepoID fields
+ b.SetRepoID(repo.GetID())
+ h.SetRepoID(repo.GetID())
+
+ // send API call to capture the last hook for the repo
+ lastHook, err := database.FromContext(c).LastHookForRepo(repo)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get last hook for repo %s: %w", repo.GetFullName(), err)
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ // set the Number field
+ if lastHook != nil {
+ h.SetNumber(
+ lastHook.GetNumber() + 1,
+ )
+ }
+
+ // send API call to create the webhook
+ h, err = database.FromContext(c).CreateHook(h)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create webhook %s/%d: %w", repo.GetFullName(), h.GetNumber(), err)
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ // verify the webhook from the source control provider
+ if c.Value("webhookvalidation").(bool) {
+ err = scm.FromContext(c).VerifyWebhook(dupRequest, repo)
+ if err != nil {
+ retErr := fmt.Errorf("unable to verify webhook: %w", err)
+ util.HandleError(c, http.StatusUnauthorized, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+ }
+
+ // check if the repo is active
+ if !repo.GetActive() {
+ retErr := fmt.Errorf("%s: %s is not an active repo", baseErr, repo.GetFullName())
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ // verify the build has a valid event and the repo allows that event type
+ if (b.GetEvent() == constants.EventPush && !repo.GetAllowPush()) ||
+ (b.GetEvent() == constants.EventPull && !repo.GetAllowPull()) ||
+ (b.GetEvent() == constants.EventComment && !repo.GetAllowComment()) ||
+ (b.GetEvent() == constants.EventTag && !repo.GetAllowTag()) ||
+ (b.GetEvent() == constants.EventDeploy && !repo.GetAllowDeploy()) {
+ retErr := fmt.Errorf("%s: %s does not have %s events enabled", baseErr, repo.GetFullName(), b.GetEvent())
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ // check if the repo has a valid owner
+ if repo.GetUserID() == 0 {
+ retErr := fmt.Errorf("%s: %s has no valid owner", baseErr, repo.GetFullName())
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ // send API call to capture repo owner
+ logrus.Debugf("capturing owner of repository %s", repo.GetFullName())
+
+ u, err := database.FromContext(c).GetUser(repo.GetUserID())
+ if err != nil {
+ retErr := fmt.Errorf("%s: failed to get owner for %s: %w", baseErr, repo.GetFullName(), err)
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ // confirm current repo owner has at least write access to repo (needed for status update later)
+ _, err = scm.FromContext(c).RepoAccess(u, u.GetToken(), r.GetOrg(), r.GetName())
+ if err != nil {
+ retErr := fmt.Errorf("unable to publish build to queue: repository owner %s no longer has write access to repository %s", u.GetName(), r.GetFullName())
+ util.HandleError(c, http.StatusUnauthorized, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ // create SQL filters for querying pending and running builds for repo
+ filters := map[string]interface{}{
+ "status": []string{constants.StatusPending, constants.StatusRunning},
+ }
+
+ // send API call to capture the number of pending or running builds for the repo
+ builds, err := database.FromContext(c).CountBuildsForRepo(ctx, repo, filters)
+ if err != nil {
+ retErr := fmt.Errorf("%s: unable to get count of builds for repo %s", baseErr, repo.GetFullName())
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ logrus.Debugf("currently %d builds running on repo %s", builds, repo.GetFullName())
+
+ // check if the number of pending and running builds exceeds the limit for the repo
+ if builds >= repo.GetBuildLimit() {
+ retErr := fmt.Errorf("%s: repo %s has exceeded the concurrent build limit of %d", baseErr, repo.GetFullName(), repo.GetBuildLimit())
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ // update fields in build object
+ logrus.Debugf("updating build number to %d", repo.GetCounter())
+ b.SetNumber(repo.GetCounter())
+
+ logrus.Debugf("updating parent number to %d", b.GetNumber())
+ b.SetParent(b.GetNumber())
+
+ logrus.Debug("updating status to pending")
+ b.SetStatus(constants.StatusPending)
+
+ // if this is a comment on a pull_request event
+ if strings.EqualFold(b.GetEvent(), constants.EventComment) && webhook.PRNumber > 0 {
+ commit, branch, baseref, headref, err := scm.FromContext(c).GetPullRequest(u, repo, webhook.PRNumber)
+ if err != nil {
+ retErr := fmt.Errorf("%s: failed to get pull request info for %s: %w", baseErr, repo.GetFullName(), err)
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ b.SetCommit(commit)
+ b.SetBranch(strings.Replace(branch, "refs/heads/", "", -1))
+ b.SetBaseRef(baseref)
+ b.SetHeadRef(headref)
+ }
+
+ // variable to store changeset files
+ var files []string
+ // check if the build event is not issue_comment or pull_request
+ if !strings.EqualFold(b.GetEvent(), constants.EventComment) &&
+ !strings.EqualFold(b.GetEvent(), constants.EventPull) {
+ // send API call to capture list of files changed for the commit
+ files, err = scm.FromContext(c).Changeset(u, repo, b.GetCommit())
+ if err != nil {
+ retErr := fmt.Errorf("%s: failed to get changeset for %s: %w", baseErr, repo.GetFullName(), err)
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+ }
+
+ // check if the build event is a pull_request
+ if strings.EqualFold(b.GetEvent(), constants.EventPull) && webhook.PRNumber > 0 {
+ // send API call to capture list of files changed for the pull request
+ files, err = scm.FromContext(c).ChangesetPR(u, repo, webhook.PRNumber)
+ if err != nil {
+ retErr := fmt.Errorf("%s: failed to get changeset for %s: %w", baseErr, repo.GetFullName(), err)
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+ }
+
+ var (
+ // variable to store the raw pipeline configuration
+ config []byte
+ // variable to store executable pipeline
+ p *pipeline.Build
+ // variable to store pipeline configuration
+ pipeline *library.Pipeline
+ // variable to control number of times to retry processing pipeline
+ retryLimit = 3
+ // variable to store the pipeline type for the repository
+ pipelineType = repo.GetPipelineType()
+ )
+
+ // implement a loop to process asynchronous operations with a retry limit
+ //
+ // Some operations taken during the webhook workflow can lead to race conditions
+ // failing to successfully process the request. This logic ensures we attempt our
+ // best efforts to handle these cases gracefully.
+ for i := 0; i < retryLimit; i++ {
+ logrus.Debugf("compilation loop - attempt %d", i+1)
+ // check if we're on the first iteration of the loop
+ if i > 0 {
+ // incrementally sleep in between retries
+ time.Sleep(time.Duration(i) * time.Second)
+ }
+
+ // send API call to attempt to capture the pipeline
+ pipeline, err = database.FromContext(c).GetPipelineForRepo(ctx, b.GetCommit(), repo)
+ if err != nil { // assume the pipeline doesn't exist in the database yet
+ // send API call to capture the pipeline configuration file
+ config, err = scm.FromContext(c).ConfigBackoff(u, repo, b.GetCommit())
+ if err != nil {
+ retErr := fmt.Errorf("%s: unable to get pipeline configuration for %s: %w", baseErr, repo.GetFullName(), err)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+ } else {
+ config = pipeline.GetData()
+ }
+
+ // send API call to capture repo for the counter (grabbing repo again to ensure counter is correct)
+ repo, err = database.FromContext(c).GetRepoForOrg(ctx, repo.GetOrg(), repo.GetName())
+ if err != nil {
+ retErr := fmt.Errorf("%s: unable to get repo %s: %w", baseErr, r.GetFullName(), err)
+
+ // check if the retry limit has been exceeded
+ if i < retryLimit-1 {
+ logrus.WithError(retErr).Warningf("retrying #%d", i+1)
+
+ // continue to the next iteration of the loop
+ continue
+ }
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ // update repo fields with any changes from SCM process
+ repo.SetTopics(r.GetTopics())
+ repo.SetBranch(r.GetBranch())
+
+ // set the parent equal to the current repo counter
+ b.SetParent(repo.GetCounter())
+
+ // check if the parent is set to 0
+ if b.GetParent() == 0 {
+ // parent should be "1" if it's the first build ran
+ b.SetParent(1)
+ }
+
+ // update the build numbers based off repo counter
+ inc := repo.GetCounter() + 1
+ repo.SetCounter(inc)
+ b.SetNumber(inc)
+
+ // populate the build link if a web address is provided
+ if len(m.Vela.WebAddress) > 0 {
+ b.SetLink(
+ fmt.Sprintf("%s/%s/%d", m.Vela.WebAddress, repo.GetFullName(), b.GetNumber()),
+ )
+ }
+
+ // ensure we use the expected pipeline type when compiling
+ //
+ // The pipeline type for a repo can change at any time which can break compiling
+ // existing pipelines in the system for that repo. To account for this, we update
+ // the repo pipeline type to match what was defined for the existing pipeline
+ // before compiling. After we're done compiling, we reset the pipeline type.
+ if len(pipeline.GetType()) > 0 {
+ repo.SetPipelineType(pipeline.GetType())
+ }
+
+ var compiled *library.Pipeline
+ // parse and compile the pipeline configuration file
+ p, compiled, err = compiler.FromContext(c).
+ Duplicate().
+ WithBuild(b).
+ WithComment(webhook.Comment).
+ WithCommit(b.GetCommit()).
+ WithFiles(files).
+ WithMetadata(m).
+ WithRepo(repo).
+ WithUser(u).
+ Compile(config)
+ if err != nil {
+ // format the error message with extra information
+ err = fmt.Errorf("unable to compile pipeline configuration for %s: %w", repo.GetFullName(), err)
+
+ // log the error for traceability
+ logrus.Error(err.Error())
+
+ retErr := fmt.Errorf("%s: %w", baseErr, err)
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ // reset the pipeline type for the repo
+ //
+ // The pipeline type for a repo can change at any time which can break compiling
+ // existing pipelines in the system for that repo. To account for this, we update
+ // the repo pipeline type to match what was defined for the existing pipeline
+ // before compiling. After we're done compiling, we reset the pipeline type.
+ repo.SetPipelineType(pipelineType)
+
+ // skip the build if only the init or clone steps are found
+ skip := build.SkipEmptyBuild(p)
+ if skip != "" {
+ // set build to successful status
+ b.SetStatus(constants.StatusSkipped)
+
+ // send API call to set the status on the commit
+ err = scm.FromContext(c).Status(u, b, repo.GetOrg(), repo.GetName())
+ if err != nil {
+ logrus.Errorf("unable to set commit status for %s/%d: %v", repo.GetFullName(), b.GetNumber(), err)
+ }
+
+ c.JSON(http.StatusOK, skip)
+
+ return
+ }
+
+ // check if the pipeline did not already exist in the database
+ if pipeline == nil {
+ pipeline = compiled
+ pipeline.SetRepoID(repo.GetID())
+ pipeline.SetCommit(b.GetCommit())
+ pipeline.SetRef(b.GetRef())
+
+ // send API call to create the pipeline
+ pipeline, err = database.FromContext(c).CreatePipeline(ctx, pipeline)
+ if err != nil {
+ retErr := fmt.Errorf("%s: failed to create pipeline for %s: %w", baseErr, repo.GetFullName(), err)
+
+ // check if the retry limit has been exceeded
+ if i < retryLimit-1 {
+ logrus.WithError(retErr).Warningf("retrying #%d", i+1)
+
+ // continue to the next iteration of the loop
+ continue
+ }
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+ }
+
+ b.SetPipelineID(pipeline.GetID())
+
+ // create the objects from the pipeline in the database
+ // TODO:
+ // - if a build gets created and something else fails midway,
+ // the next loop will attempt to create the same build,
+ // using the same Number and thus create a constraint
+ // conflict; consider deleting the partially created
+ // build object in the database
+ err = build.PlanBuild(ctx, database.FromContext(c), p, b, repo)
+ if err != nil {
+ retErr := fmt.Errorf("%s: %w", baseErr, err)
+
+ // check if the retry limit has been exceeded
+ if i < retryLimit-1 {
+ logrus.WithError(retErr).Warningf("retrying #%d", i+1)
+
+ // reset fields set by cleanBuild for retry
+ b.SetError("")
+ b.SetStatus(constants.StatusPending)
+ b.SetFinished(0)
+
+ // continue to the next iteration of the loop
+ continue
+ }
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ // break the loop because everything was successful
+ break
+ } // end of retry loop
+
+ // send API call to update repo for ensuring counter is incremented
+ repo, err = database.FromContext(c).UpdateRepo(ctx, repo)
+ if err != nil {
+ retErr := fmt.Errorf("%s: failed to update repo %s: %w", baseErr, repo.GetFullName(), err)
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ // return error if pipeline didn't get populated
+ if p == nil {
+ retErr := fmt.Errorf("%s: failed to set pipeline for %s: %w", baseErr, repo.GetFullName(), err)
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ // return error if build didn't get populated
+ if b == nil {
+ retErr := fmt.Errorf("%s: failed to set build for %s: %w", baseErr, repo.GetFullName(), err)
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ // send API call to capture the triggered build
+ b, err = database.FromContext(c).GetBuildForRepo(ctx, repo, b.GetNumber())
+ if err != nil {
+ retErr := fmt.Errorf("%s: failed to get new build %s/%d: %w", baseErr, repo.GetFullName(), b.GetNumber(), err)
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return
+ }
+
+ // set the BuildID field
+ h.SetBuildID(b.GetID())
+
+ c.JSON(http.StatusOK, b)
+
+ // send API call to set the status on the commit
+ err = scm.FromContext(c).Status(u, b, repo.GetOrg(), repo.GetName())
+ if err != nil {
+ logrus.Errorf("unable to set commit status for %s/%d: %v", repo.GetFullName(), b.GetNumber(), err)
+ }
+
+ // publish the build to the queue
+ go build.PublishToQueue(
+ ctx,
+ queue.FromGinContext(c),
+ database.FromContext(c),
+ p,
+ b,
+ repo,
+ u,
+ )
+}
+
+func handleRepositoryEvent(ctx context.Context, c *gin.Context, m *types.Metadata, h *library.Hook, r *library.Repo) (*library.Repo, error) {
+ logrus.Debugf("webhook is repository event, making necessary updates to repo %s", r.GetFullName())
+
+ defer func() {
+ // send API call to update the webhook
+ _, err := database.FromContext(c).CreateHook(h)
+ if err != nil {
+ logrus.Errorf("unable to create webhook %s/%d: %v", r.GetFullName(), h.GetNumber(), err)
+ }
+ }()
+
+ switch h.GetEventAction() {
+ // if action is rename, go through rename routine
+ case constants.ActionRenamed, constants.ActionTransferred:
+ r, err := renameRepository(ctx, h, r, c, m)
+ if err != nil {
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(err.Error())
+
+ return nil, err
+ }
+
+ return r, nil
+ // if action is archived, unarchived, or edited, perform edits to relevant repo fields
+ case "archived", "unarchived", constants.ActionEdited:
+ logrus.Debugf("repository action %s for %s", h.GetEventAction(), r.GetFullName())
+ // send call to get repository from database
+ dbRepo, err := database.FromContext(c).GetRepoForOrg(ctx, r.GetOrg(), r.GetName())
+ if err != nil {
+ retErr := fmt.Errorf("%s: failed to get repo %s: %w", baseErr, r.GetFullName(), err)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return nil, retErr
+ }
+
+ // send API call to capture the last hook for the repo
+ lastHook, err := database.FromContext(c).LastHookForRepo(dbRepo)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get last hook for repo %s: %w", r.GetFullName(), err)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return nil, retErr
+ }
+
+ // set the Number field
+ if lastHook != nil {
+ h.SetNumber(
+ lastHook.GetNumber() + 1,
+ )
+ }
+
+ h.SetRepoID(dbRepo.GetID())
+
+ // the only edits to a repo that impact Vela are to these three fields
+ if !strings.EqualFold(dbRepo.GetBranch(), r.GetBranch()) {
+ dbRepo.SetBranch(r.GetBranch())
+ }
+
+ if dbRepo.GetActive() != r.GetActive() {
+ dbRepo.SetActive(r.GetActive())
+ }
+
+ if !reflect.DeepEqual(dbRepo.GetTopics(), r.GetTopics()) {
+ dbRepo.SetTopics(r.GetTopics())
+ }
+
+ // update repo object in the database after applying edits
+ dbRepo, err = database.FromContext(c).UpdateRepo(ctx, dbRepo)
+ if err != nil {
+ retErr := fmt.Errorf("%s: failed to update repo %s: %w", baseErr, r.GetFullName(), err)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return nil, err
+ }
+
+ return dbRepo, nil
+ // all other repo event actions are skippable
+ default:
+ return r, nil
+ }
+}
+
+// renameRepository is a helper function that takes the old name of the repo,
+// queries the database for the repo that matches that name and org, and updates
+// that repo to its new name in order to preserve it. It also updates the secrets
+// associated with that repo as well as build links for the UI.
+func renameRepository(ctx context.Context, h *library.Hook, r *library.Repo, c *gin.Context, m *types.Metadata) (*library.Repo, error) {
+ logrus.Infof("renaming repository from %s to %s", r.GetPreviousName(), r.GetName())
+
+ // get the old name of the repo
+ prevOrg, prevRepo := util.SplitFullName(r.GetPreviousName())
+
+ // get the repo from the database that matches the old name
+ dbR, err := database.FromContext(c).GetRepoForOrg(ctx, prevOrg, prevRepo)
+ if err != nil {
+ retErr := fmt.Errorf("%s: failed to get repo %s/%s from database", baseErr, prevOrg, prevRepo)
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return nil, retErr
+ }
+
+ // update hook object which will be added to DB upon reaching deferred function in PostWebhook
+ h.SetRepoID(r.GetID())
+
+ // send API call to capture the last hook for the repo
+ lastHook, err := database.FromContext(c).LastHookForRepo(dbR)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get last hook for repo %s: %w", r.GetFullName(), err)
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return nil, retErr
+ }
+
+ // set the Number field
+ if lastHook != nil {
+ h.SetNumber(
+ lastHook.GetNumber() + 1,
+ )
+ }
+
+ // get total number of secrets associated with repository
+ t, err := database.FromContext(c).CountSecretsForRepo(dbR, map[string]interface{}{})
+ if err != nil {
+ return nil, fmt.Errorf("unable to get secret count for repo %s/%s: %w", prevOrg, prevRepo, err)
+ }
+
+ secrets := []*library.Secret{}
+ page := 1
+ // capture all secrets belonging to certain repo in database
+ for repoSecrets := int64(0); repoSecrets < t; repoSecrets += 100 {
+ s, _, err := database.FromContext(c).ListSecretsForRepo(dbR, map[string]interface{}{}, page, 100)
+ if err != nil {
+ return nil, fmt.Errorf("unable to get secret list for repo %s/%s: %w", prevOrg, prevRepo, err)
+ }
+
+ secrets = append(secrets, s...)
+
+ page++
+ }
+
+ // update secrets to point to the new repository name
+ for _, secret := range secrets {
+ secret.SetOrg(r.GetOrg())
+ secret.SetRepo(r.GetName())
+
+ _, err = database.FromContext(c).UpdateSecret(secret)
+ if err != nil {
+ return nil, fmt.Errorf("unable to update secret for repo %s/%s: %w", prevOrg, prevRepo, err)
+ }
+ }
+
+ // get total number of builds associated with repository
+ t, err = database.FromContext(c).CountBuildsForRepo(ctx, dbR, nil)
+ if err != nil {
+ return nil, fmt.Errorf("unable to get build count for repo %s: %w", dbR.GetFullName(), err)
+ }
+
+ builds := []*library.Build{}
+ page = 1
+ // capture all builds belonging to repo in database
+ for build := int64(0); build < t; build += 100 {
+ b, _, err := database.FromContext(c).ListBuildsForRepo(ctx, dbR, nil, time.Now().Unix(), 0, page, 100)
+ if err != nil {
+ return nil, fmt.Errorf("unable to get build list for repo %s: %w", dbR.GetFullName(), err)
+ }
+
+ builds = append(builds, b...)
+
+ page++
+ }
+
+ // update build link to route to proper repo name
+ for _, build := range builds {
+ build.SetLink(
+ fmt.Sprintf("%s/%s/%d", m.Vela.WebAddress, r.GetFullName(), build.GetNumber()),
+ )
+
+ _, err = database.FromContext(c).UpdateBuild(ctx, build)
+ if err != nil {
+ return nil, fmt.Errorf("unable to update build for repo %s: %w", dbR.GetFullName(), err)
+ }
+ }
+
+ // update the repo name information
+ dbR.SetName(r.GetName())
+ dbR.SetOrg(r.GetOrg())
+ dbR.SetFullName(r.GetFullName())
+ dbR.SetClone(r.GetClone())
+ dbR.SetLink(r.GetLink())
+ dbR.SetPreviousName(r.GetPreviousName())
+
+ // update the repo in the database
+ dbR, err = database.FromContext(c).UpdateRepo(ctx, dbR)
+ if err != nil {
+ retErr := fmt.Errorf("%s: failed to update repo %s/%s in database", baseErr, prevOrg, prevRepo)
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ h.SetStatus(constants.StatusFailure)
+ h.SetError(retErr.Error())
+
+ return nil, retErr
+ }
+
+ return dbR, nil
+}
diff --git a/api/worker.go b/api/worker.go
deleted file mode 100644
index 6f5b65636..000000000
--- a/api/worker.go
+++ /dev/null
@@ -1,342 +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 api
-
-import (
- "fmt"
- "net/http"
-
- "github.com/go-vela/server/router/middleware/user"
-
- "github.com/go-vela/server/database"
- "github.com/go-vela/server/router/middleware/worker"
- "github.com/go-vela/server/util"
-
- "github.com/go-vela/types/library"
-
- "github.com/gin-gonic/gin"
- "github.com/sirupsen/logrus"
-)
-
-// swagger:operation POST /api/v1/workers workers CreateWorker
-//
-// Create a worker for the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: body
-// name: body
-// description: Payload containing the worker to create
-// required: true
-// schema:
-// "$ref": "#/definitions/Worker"
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '201':
-// description: Successfully created the worker
-// schema:
-// type: string
-// '400':
-// description: Unable to create the worker
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to create the worker
-// schema:
-// "$ref": "#/definitions/Error"
-
-// CreateWorker represents the API handler to
-// create a worker in the configured backend.
-func CreateWorker(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
-
- // capture body from API request
- input := new(library.Worker)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for new worker: %w", err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "user": u.GetName(),
- "worker": input.GetHostname(),
- }).Infof("creating new worker %s", input.GetHostname())
-
- err = database.FromContext(c).CreateWorker(input)
- if err != nil {
- retErr := fmt.Errorf("unable to create worker: %w", err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusCreated, fmt.Sprintf("worker %s created", input.GetHostname()))
-}
-
-// swagger:operation GET /api/v1/workers workers GetWorkers
-//
-// Retrieve a list of workers for the configured backend
-//
-// ---
-// produces:
-// - application/json
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the list of workers
-// schema:
-// type: array
-// items:
-// "$ref": "#/definitions/Worker"
-// '500':
-// description: Unable to retrieve the list of workers
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetWorkers represents the API handler to capture a
-// list of workers from the configured backend.
-func GetWorkers(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Info("reading workers")
-
- w, err := database.FromContext(c).GetWorkerList()
- if err != nil {
- retErr := fmt.Errorf("unable to get workers: %w", err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, w)
-}
-
-// swagger:operation GET /api/v1/workers/{worker} workers GetWorker
-//
-// Retrieve a worker for the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: worker
-// description: Hostname of the worker
-// required: true
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully retrieved the worker
-// schema:
-// "$ref": "#/definitions/Worker"
-// '404':
-// description: Unable to retrieve the worker
-// schema:
-// "$ref": "#/definitions/Error"
-
-// GetWorker represents the API handler to capture a
-// worker from the configured backend.
-func GetWorker(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
- w := worker.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "user": u.GetName(),
- "worker": w.GetHostname(),
- }).Infof("reading worker %s", w.GetHostname())
-
- w, err := database.FromContext(c).GetWorker(w.GetHostname())
- if err != nil {
- retErr := fmt.Errorf("unable to get workers: %w", err)
-
- util.HandleError(c, http.StatusNotFound, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, w)
-}
-
-// swagger:operation PUT /api/v1/workers/{worker} workers UpdateWorker
-//
-// Update a worker for the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: body
-// name: body
-// description: Payload containing the worker to update
-// required: true
-// schema:
-// "$ref": "#/definitions/Worker"
-// - in: path
-// name: worker
-// description: Name of the worker
-// required: true
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully updated the worker
-// schema:
-// "$ref": "#/definitions/Worker"
-// '400':
-// description: Unable to update the worker
-// schema:
-// "$ref": "#/definitions/Error"
-// '404':
-// description: Unable to update the worker
-// schema:
-// "$ref": "#/definitions/Error"
-// '500':
-// description: Unable to update the worker
-// schema:
-// "$ref": "#/definitions/Error"
-
-// UpdateWorker represents the API handler to
-// create a worker in the configured backend.
-func UpdateWorker(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
- w := worker.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "user": u.GetName(),
- "worker": w.GetHostname(),
- }).Infof("updating worker %s", w.GetHostname())
-
- // capture body from API request
- input := new(library.Worker)
-
- err := c.Bind(input)
- if err != nil {
- retErr := fmt.Errorf("unable to decode JSON for worker %s: %w", w.GetHostname(), err)
-
- util.HandleError(c, http.StatusBadRequest, retErr)
-
- return
- }
-
- if len(input.GetAddress()) > 0 {
- // update admin if set
- w.SetAddress(input.GetAddress())
- }
-
- if len(input.GetRoutes()) > 0 {
- // update routes if set
- w.SetRoutes(input.GetRoutes())
- }
-
- if input.GetActive() {
- // update active if set
- w.SetActive(input.GetActive())
- }
-
- if input.GetLastCheckedIn() > 0 {
- // update LastCheckedIn if set
- w.SetLastCheckedIn(input.GetLastCheckedIn())
- }
-
- // send API call to update the worker
- err = database.FromContext(c).UpdateWorker(w)
- if err != nil {
- retErr := fmt.Errorf("unable to update worker %s: %w", w.GetHostname(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- // send API call to capture the updated worker
- w, _ = database.FromContext(c).GetWorker(w.GetHostname())
-
- c.JSON(http.StatusOK, w)
-}
-
-// swagger:operation DELETE /api/v1/workers/{worker} workers DeleteWorker
-//
-// Delete a worker for the configured backend
-//
-// ---
-// produces:
-// - application/json
-// parameters:
-// - in: path
-// name: worker
-// description: Name of the worker
-// required: true
-// type: string
-// security:
-// - ApiKeyAuth: []
-// responses:
-// '200':
-// description: Successfully deleted of worker
-// schema:
-// type: string
-// '500':
-// description: Unable to delete worker
-// schema:
-// "$ref": "#/definitions/Error"
-
-// DeleteWorker represents the API handler to remove
-// a worker from the configured backend.
-func DeleteWorker(c *gin.Context) {
- // capture middleware values
- u := user.Retrieve(c)
- w := worker.Retrieve(c)
-
- // update engine logger with API metadata
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
- logrus.WithFields(logrus.Fields{
- "user": u.GetName(),
- "worker": w.GetHostname(),
- }).Infof("deleting worker %s", w.GetHostname())
-
- // send API call to remove the step
- err := database.FromContext(c).DeleteWorker(w.GetID())
- if err != nil {
- retErr := fmt.Errorf("unable to delete worker %s: %w", w.GetHostname(), err)
-
- util.HandleError(c, http.StatusInternalServerError, retErr)
-
- return
- }
-
- c.JSON(http.StatusOK, fmt.Sprintf("worker %s deleted", w.GetHostname()))
-}
diff --git a/api/worker/create.go b/api/worker/create.go
new file mode 100644
index 000000000..b7d1bf286
--- /dev/null
+++ b/api/worker/create.go
@@ -0,0 +1,141 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/internal/token"
+ "github.com/go-vela/server/router/middleware/claims"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/workers workers CreateWorker
+//
+// Create a worker for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: body
+// name: body
+// description: Payload containing the worker to create
+// required: true
+// schema:
+// "$ref": "#/definitions/Worker"
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '201':
+// description: Successfully created the worker and retrieved auth token
+// schema:
+// "$ref": "#definitions/Token"
+// '400':
+// description: Unable to create the worker
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to create the worker
+// schema:
+// "$ref": "#/definitions/Error"
+
+// CreateWorker represents the API handler to
+// create a worker in the configured backend.
+func CreateWorker(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+ cl := claims.Retrieve(c)
+
+ // capture body from API request
+ input := new(library.Worker)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for new worker: %w", err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // verify input host name matches worker hostname
+ if !strings.EqualFold(cl.TokenType, constants.ServerWorkerTokenType) && !strings.EqualFold(cl.Subject, input.GetHostname()) {
+ retErr := fmt.Errorf("unable to add worker; claims subject %s does not match worker hostname %s", cl.Subject, input.GetHostname())
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ input.SetLastCheckedIn(time.Now().Unix())
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ "worker": input.GetHostname(),
+ }).Infof("creating new worker %s", input.GetHostname())
+
+ err = database.FromContext(c).CreateWorker(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to create worker: %w", err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ switch cl.TokenType {
+ // if symmetric token configured, send back symmetric token
+ case constants.ServerWorkerTokenType:
+ if secret, ok := c.Value("secret").(string); ok {
+ tkn := new(library.Token)
+ tkn.SetToken(secret)
+ c.JSON(http.StatusCreated, tkn)
+
+ return
+ }
+
+ retErr := fmt.Errorf("symmetric token provided but not configured in server")
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ // if worker register token, send back auth token
+ default:
+ tm := c.MustGet("token-manager").(*token.Manager)
+
+ wmto := &token.MintTokenOpts{
+ TokenType: constants.WorkerAuthTokenType,
+ TokenDuration: tm.WorkerAuthTokenDuration,
+ Hostname: cl.Subject,
+ }
+
+ tkn := new(library.Token)
+
+ wt, err := tm.MintToken(wmto)
+ if err != nil {
+ retErr := fmt.Errorf("unable to generate auth token for worker %s: %w", input.GetHostname(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ tkn.SetToken(wt)
+
+ c.JSON(http.StatusCreated, tkn)
+ }
+}
diff --git a/api/worker/delete.go b/api/worker/delete.go
new file mode 100644
index 000000000..fa0ad5e65
--- /dev/null
+++ b/api/worker/delete.go
@@ -0,0 +1,70 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/router/middleware/worker"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation DELETE /api/v1/workers/{worker} workers DeleteWorker
+//
+// Delete a worker for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: worker
+// description: Name of the worker
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully deleted of worker
+// schema:
+// type: string
+// '500':
+// description: Unable to delete worker
+// schema:
+// "$ref": "#/definitions/Error"
+
+// DeleteWorker represents the API handler to remove
+// a worker from the configured backend.
+func DeleteWorker(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+ w := worker.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ "worker": w.GetHostname(),
+ }).Infof("deleting worker %s", w.GetHostname())
+
+ // send API call to remove the step
+ err := database.FromContext(c).DeleteWorker(w)
+ if err != nil {
+ retErr := fmt.Errorf("unable to delete worker %s: %w", w.GetHostname(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("worker %s deleted", w.GetHostname()))
+}
diff --git a/api/worker/doc.go b/api/worker/doc.go
new file mode 100644
index 000000000..86da09ed7
--- /dev/null
+++ b/api/worker/doc.go
@@ -0,0 +1,10 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+// Package worker provides the worker handlers for the Vela API.
+//
+// Usage:
+//
+// import "github.com/go-vela/server/api/worker"
+package worker
diff --git a/api/worker/get.go b/api/worker/get.go
new file mode 100644
index 000000000..88b323532
--- /dev/null
+++ b/api/worker/get.go
@@ -0,0 +1,69 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/router/middleware/worker"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/workers/{worker} workers GetWorker
+//
+// Retrieve a worker for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: worker
+// description: Hostname of the worker
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the worker
+// schema:
+// "$ref": "#/definitions/Worker"
+// '404':
+// description: Unable to retrieve the worker
+// schema:
+// "$ref": "#/definitions/Error"
+
+// GetWorker represents the API handler to capture a
+// worker from the configured backend.
+func GetWorker(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+ w := worker.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ "worker": w.GetHostname(),
+ }).Infof("reading worker %s", w.GetHostname())
+
+ w, err := database.FromContext(c).GetWorkerForHostname(w.GetHostname())
+ if err != nil {
+ retErr := fmt.Errorf("unable to get workers: %w", err)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, w)
+}
diff --git a/api/worker/list.go b/api/worker/list.go
new file mode 100644
index 000000000..2587ba07f
--- /dev/null
+++ b/api/worker/list.go
@@ -0,0 +1,62 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation GET /api/v1/workers workers ListWorkers
+//
+// Retrieve a list of workers for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully retrieved the list of workers
+// schema:
+// type: array
+// items:
+// "$ref": "#/definitions/Worker"
+// '500':
+// description: Unable to retrieve the list of workers
+// schema:
+// "$ref": "#/definitions/Error"
+
+// ListWorkers represents the API handler to capture a
+// list of workers from the configured backend.
+func ListWorkers(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ }).Info("reading workers")
+
+ w, err := database.FromContext(c).ListWorkers()
+ if err != nil {
+ retErr := fmt.Errorf("unable to get workers: %w", err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ c.JSON(http.StatusOK, w)
+}
diff --git a/api/worker/refresh.go b/api/worker/refresh.go
new file mode 100644
index 000000000..cd4aa7ef3
--- /dev/null
+++ b/api/worker/refresh.go
@@ -0,0 +1,138 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/internal/token"
+ "github.com/go-vela/server/router/middleware/claims"
+ "github.com/go-vela/server/router/middleware/worker"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation POST /api/v1/workers/{worker}/refresh workers RefreshWorkerAuth
+//
+// Refresh authorization token for worker
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: path
+// name: worker
+// description: Name of the worker
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully refreshed auth
+// schema:
+// "$ref": "#/definitions/Token"
+// '400':
+// description: Unable to refresh worker auth
+// schema:
+// "$ref": "#/definitions/Error"
+// '404':
+// description: Unable to refresh worker auth
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to refresh worker auth
+// schema:
+// "$ref": "#/definitions/Error"
+
+// Refresh represents the API handler to
+// refresh the auth token for a worker.
+func Refresh(c *gin.Context) {
+ // capture middleware values
+ w := worker.Retrieve(c)
+ cl := claims.Retrieve(c)
+
+ // if we are not using a symmetric token, and the subject does not match the input, request should be denied
+ if !strings.EqualFold(cl.TokenType, constants.ServerWorkerTokenType) && !strings.EqualFold(cl.Subject, w.GetHostname()) {
+ retErr := fmt.Errorf("unable to refresh worker auth: claims subject %s does not match worker hostname %s", cl.Subject, w.GetHostname())
+
+ logrus.WithFields(logrus.Fields{
+ "subject": cl.Subject,
+ "worker": w.GetHostname(),
+ }).Warnf("attempted refresh of worker %s using token from worker %s", w.GetHostname(), cl.Subject)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // set last checked in time
+ w.SetLastCheckedIn(time.Now().Unix())
+
+ // send API call to update the worker
+ err := database.FromContext(c).UpdateWorker(w)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update worker %s: %w", w.GetHostname(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "worker": w.GetHostname(),
+ }).Infof("refreshing worker %s authentication", w.GetHostname())
+
+ switch cl.TokenType {
+ // if symmetric token configured, send back symmetric token
+ case constants.ServerWorkerTokenType:
+ if secret, ok := c.Value("secret").(string); ok {
+ tkn := new(library.Token)
+ tkn.SetToken(secret)
+ c.JSON(http.StatusOK, tkn)
+
+ return
+ }
+
+ retErr := fmt.Errorf("symmetric token provided but not configured in server")
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ // if worker auth / register token, send back auth token
+ case constants.WorkerAuthTokenType, constants.WorkerRegisterTokenType:
+ tm := c.MustGet("token-manager").(*token.Manager)
+
+ wmto := &token.MintTokenOpts{
+ TokenType: constants.WorkerAuthTokenType,
+ TokenDuration: tm.WorkerAuthTokenDuration,
+ Hostname: cl.Subject,
+ }
+
+ tkn := new(library.Token)
+
+ wt, err := tm.MintToken(wmto)
+ if err != nil {
+ retErr := fmt.Errorf("unable to generate auth token for worker %s: %w", w.GetHostname(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ tkn.SetToken(wt)
+
+ c.JSON(http.StatusOK, tkn)
+ }
+}
diff --git a/api/worker/update.go b/api/worker/update.go
new file mode 100644
index 000000000..b3a8d5130
--- /dev/null
+++ b/api/worker/update.go
@@ -0,0 +1,140 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/router/middleware/worker"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// swagger:operation PUT /api/v1/workers/{worker} workers UpdateWorker
+//
+// Update a worker for the configured backend
+//
+// ---
+// produces:
+// - application/json
+// parameters:
+// - in: body
+// name: body
+// description: Payload containing the worker to update
+// required: true
+// schema:
+// "$ref": "#/definitions/Worker"
+// - in: path
+// name: worker
+// description: Name of the worker
+// required: true
+// type: string
+// security:
+// - ApiKeyAuth: []
+// responses:
+// '200':
+// description: Successfully updated the worker
+// schema:
+// "$ref": "#/definitions/Worker"
+// '400':
+// description: Unable to update the worker
+// schema:
+// "$ref": "#/definitions/Error"
+// '404':
+// description: Unable to update the worker
+// schema:
+// "$ref": "#/definitions/Error"
+// '500':
+// description: Unable to update the worker
+// schema:
+// "$ref": "#/definitions/Error"
+
+// UpdateWorker represents the API handler to
+// update a worker in the configured backend.
+func UpdateWorker(c *gin.Context) {
+ // capture middleware values
+ u := user.Retrieve(c)
+ w := worker.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ "worker": w.GetHostname(),
+ }).Infof("updating worker %s", w.GetHostname())
+
+ // capture body from API request
+ input := new(library.Worker)
+
+ err := c.Bind(input)
+ if err != nil {
+ retErr := fmt.Errorf("unable to decode JSON for worker %s: %w", w.GetHostname(), err)
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ if len(input.GetAddress()) > 0 {
+ // update address if set
+ w.SetAddress(input.GetAddress())
+ }
+
+ if len(input.GetRoutes()) > 0 {
+ // update routes if set
+ w.SetRoutes(input.GetRoutes())
+ }
+
+ if input.Active != nil {
+ // update active if set
+ w.SetActive(input.GetActive())
+ }
+
+ if input.RunningBuildIDs != nil {
+ // update runningBuildIDs if set
+ w.SetRunningBuildIDs(input.GetRunningBuildIDs())
+ }
+
+ if len(input.GetStatus()) > 0 {
+ // update status if set
+ w.SetStatus(input.GetStatus())
+ }
+
+ if input.GetLastStatusUpdateAt() > 0 {
+ // update lastStatusUpdateAt if set
+ w.SetLastStatusUpdateAt(input.GetLastStatusUpdateAt())
+ }
+
+ if input.GetLastBuildStartedAt() > 0 {
+ // update lastBuildStartedAt if set
+ w.SetLastBuildStartedAt(input.GetLastBuildStartedAt())
+ }
+
+ if input.GetLastBuildFinishedAt() > 0 {
+ // update lastBuildFinishedAt if set
+ w.SetLastBuildFinishedAt(input.GetLastBuildFinishedAt())
+ }
+
+ // send API call to update the worker
+ err = database.FromContext(c).UpdateWorker(w)
+ if err != nil {
+ retErr := fmt.Errorf("unable to update worker %s: %w", w.GetHostname(), err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // send API call to capture the updated worker
+ w, _ = database.FromContext(c).GetWorkerForHostname(w.GetHostname())
+
+ c.JSON(http.StatusOK, w)
+}
diff --git a/cmd/vela-server/database.go b/cmd/vela-server/database.go
deleted file mode 100644
index cee4f9a83..000000000
--- a/cmd/vela-server/database.go
+++ /dev/null
@@ -1,35 +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 main
-
-import (
- "github.com/go-vela/server/database"
-
- "github.com/sirupsen/logrus"
-
- "github.com/urfave/cli/v2"
-)
-
-// helper function to setup the database from the CLI arguments.
-func setupDatabase(c *cli.Context) (database.Service, error) {
- logrus.Debug("Creating database client from CLI configuration")
-
- // database configuration
- _setup := &database.Setup{
- Driver: c.String("database.driver"),
- Address: c.String("database.addr"),
- CompressionLevel: c.Int("database.compression.level"),
- ConnectionLife: c.Duration("database.connection.life"),
- ConnectionIdle: c.Int("database.connection.idle"),
- ConnectionOpen: c.Int("database.connection.open"),
- EncryptionKey: c.String("database.encryption.key"),
- SkipCreation: c.Bool("database.skip_creation"),
- }
-
- // setup the database
- //
- // https://pkg.go.dev/github.com/go-vela/server/database?tab=doc#New
- return database.New(_setup)
-}
diff --git a/cmd/vela-server/main.go b/cmd/vela-server/main.go
index 31f0b4286..788f6c26b 100644
--- a/cmd/vela-server/main.go
+++ b/cmd/vela-server/main.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
@@ -23,7 +23,7 @@ import (
_ "github.com/joho/godotenv/autoload"
)
-// nolint: funlen // ignore function length due to flags
+//nolint:funlen // ignore line length
func main() {
// capture application version information
v := version.New()
@@ -41,7 +41,6 @@ func main() {
app.Name = "vela-server"
app.Action = server
app.Version = v.Semantic()
-
app.Flags = []cli.Flag{
&cli.StringFlag{
EnvVars: []string{"VELA_LOG_LEVEL", "LOG_LEVEL"},
@@ -76,6 +75,17 @@ func main() {
Name: "vela-secret",
Usage: "secret used for server <-> agent communication",
},
+ &cli.StringFlag{
+ EnvVars: []string{"VELA_SERVER_PRIVATE_KEY"},
+ Name: "vela-server-private-key",
+ Usage: "private key used for signing tokens",
+ },
+ &cli.StringFlag{
+ EnvVars: []string{"VELA_CLONE_IMAGE"},
+ Name: "clone-image",
+ Usage: "the clone image to use for the injected clone step",
+ Value: "target/vela-git:v0.8.0@sha256:02de004ae9dbf184c70039cb9ce431c31d6e7580eb9e6ec64a97ebf108aa65cb",
+ },
&cli.StringSliceFlag{
EnvVars: []string{"VELA_REPO_ALLOWLIST"},
Name: "vela-repo-allowlist",
@@ -85,9 +95,8 @@ func main() {
&cli.BoolFlag{
EnvVars: []string{"VELA_DISABLE_WEBHOOK_VALIDATION"},
Name: "vela-disable-webhook-validation",
- // nolint: lll // ignore long line length due to description
- Usage: "determines whether or not webhook validation is disabled. useful for local development.",
- Value: false,
+ Usage: "determines whether or not webhook validation is disabled. useful for local development.",
+ Value: false,
},
&cli.BoolFlag{
EnvVars: []string{"VELA_ENABLE_SECURE_COOKIE"},
@@ -113,24 +122,44 @@ func main() {
Usage: "override default build timeout (minutes)",
Value: constants.BuildTimeoutDefault,
},
-
- // Security Flags
-
+ &cli.StringSliceFlag{
+ EnvVars: []string{"VELA_DEFAULT_REPO_EVENTS"},
+ Name: "default-repo-events",
+ Usage: "override default events for newly activated repositories",
+ Value: cli.NewStringSlice(constants.EventPush),
+ },
+ // Token Manager Flags
&cli.DurationFlag{
- EnvVars: []string{"VELA_ACCESS_TOKEN_DURATION", "ACCESS_TOKEN_DURATION"},
- Name: "access-token-duration",
- Usage: "sets the duration of the access token",
+ EnvVars: []string{"VELA_USER_ACCESS_TOKEN_DURATION", "USER_ACCESS_TOKEN_DURATION"},
+ Name: "user-access-token-duration",
+ Usage: "sets the duration of the user access token",
Value: 15 * time.Minute,
},
&cli.DurationFlag{
- EnvVars: []string{"VELA_REFRESH_TOKEN_DURATION", "REFRESH_TOKEN_DURATION"},
- Name: "refresh-token-duration",
- Usage: "sets the duration of the refresh token",
+ EnvVars: []string{"VELA_USER_REFRESH_TOKEN_DURATION", "USER_REFRESH_TOKEN_DURATION"},
+ Name: "user-refresh-token-duration",
+ Usage: "sets the duration of the user refresh token",
Value: 8 * time.Hour,
},
-
+ &cli.DurationFlag{
+ EnvVars: []string{"VELA_BUILD_TOKEN_BUFFER_DURATION", "BUILD_TOKEN_BUFFER_DURATION"},
+ Name: "build-token-buffer-duration",
+ Usage: "sets the duration of the buffer for build token expiration based on repo build timeout",
+ Value: 5 * time.Minute,
+ },
+ &cli.DurationFlag{
+ EnvVars: []string{"VELA_WORKER_AUTH_TOKEN_DURATION", "WORKER_AUTH_TOKEN_DURATION"},
+ Name: "worker-auth-token-duration",
+ Usage: "sets the duration of the worker auth token",
+ Value: 20 * time.Minute,
+ },
+ &cli.DurationFlag{
+ EnvVars: []string{"VELA_WORKER_REGISTER_TOKEN_DURATION", "WORKER_REGISTER_TOKEN_DURATION"},
+ Name: "worker-register-token-duration",
+ Usage: "sets the duration of the worker register token",
+ Value: 1 * time.Minute,
+ },
// Compiler Flags
-
&cli.BoolFlag{
EnvVars: []string{"VELA_COMPILER_GITHUB", "COMPILER_GITHUB"},
Name: "github-driver",
@@ -146,7 +175,6 @@ func main() {
Name: "github-token",
Usage: "github token, used by compiler, for pulling registry templates",
},
-
&cli.StringFlag{
EnvVars: []string{"VELA_MODIFICATION_ADDR", "MODIFICATION_ADDR"},
Name: "modification-addr",
@@ -155,53 +183,68 @@ func main() {
&cli.StringFlag{
EnvVars: []string{"VELA_MODIFICATION_SECRET", "MODIFICATION_SECRET"},
Name: "modification-secret",
- // nolint: lll // ignore long line length due to description
- Usage: "modification secret, used by compiler, secret to allow connectivity between compiler and modification endpoint",
+ Usage: "modification secret, used by compiler, secret to allow connectivity between compiler and modification endpoint",
},
&cli.DurationFlag{
EnvVars: []string{"VELA_MODIFICATION_TIMEOUT", "MODIFICATION_TIMEOUT"},
Name: "modification-timeout",
- // nolint: lll // ignore long line length due to description
- Usage: "modification timeout, used by compiler, duration that the modification http request will timeout after",
- Value: 8 * time.Second,
+ Usage: "modification timeout, used by compiler, duration that the modification http request will timeout after",
+ Value: 8 * time.Second,
},
&cli.IntFlag{
EnvVars: []string{"VELA_MODIFICATION_RETRIES", "MODIFICATION_RETRIES"},
Name: "modification-retries",
- // nolint: lll // ignore long line length due to description
- Usage: "modification retries, used by compiler, number of http requires that the modification http request will fail after",
- Value: 5,
+ Usage: "modification retries, used by compiler, number of http requires that the modification http request will fail after",
+ Value: 5,
+ },
+ &cli.IntFlag{
+ EnvVars: []string{"VELA_MAX_TEMPLATE_DEPTH", "MAX_TEMPLATE_DEPTH"},
+ Name: "max-template-depth",
+ Usage: "max template depth, used by compiler, maximum number of templates that can be called in a template chain",
+ Value: 3,
},
-
&cli.DurationFlag{
EnvVars: []string{"VELA_WORKER_ACTIVE_INTERVAL", "WORKER_ACTIVE_INTERVAL"},
Name: "worker-active-interval",
Usage: "interval at which workers will show as active within the /metrics endpoint",
Value: 5 * time.Minute,
},
+ // schedule flags
+ &cli.DurationFlag{
+ EnvVars: []string{"VELA_SCHEDULE_MINIMUM_FREQUENCY", "SCHEDULE_MINIMUM_FREQUENCY"},
+ Name: "schedule-minimum-frequency",
+ Usage: "minimum time allowed between each build triggered for a schedule",
+ Value: 1 * time.Hour,
+ },
+ &cli.DurationFlag{
+ EnvVars: []string{"VELA_SCHEDULE_INTERVAL", "SCHEDULE_INTERVAL"},
+ Name: "schedule-interval",
+ Usage: "interval at which schedules will be processed by the server to trigger builds",
+ Value: 5 * time.Minute,
+ },
+ &cli.StringSliceFlag{
+ EnvVars: []string{"VELA_SCHEDULE_ALLOWLIST"},
+ Name: "vela-schedule-allowlist",
+ Usage: "limit which repos can be utilize the schedule feature within the system",
+ Value: &cli.StringSlice{},
+ },
}
-
- // Database Flags
-
+ // Add Database Flags
app.Flags = append(app.Flags, database.Flags...)
- // Queue Flags
-
+ // Add Queue Flags
app.Flags = append(app.Flags, queue.Flags...)
- // Secret Flags
-
+ // Add Secret Flags
app.Flags = append(app.Flags, secret.Flags...)
- // Source Flags
-
+ // Add Source Flags
app.Flags = append(app.Flags, scm.Flags...)
// set logrus to log in JSON format
logrus.SetFormatter(&logrus.JSONFormatter{})
- err = app.Run(os.Args)
- if err != nil {
+ if err = app.Run(os.Args); err != nil {
logrus.Fatal(err)
}
}
diff --git a/cmd/vela-server/metadata.go b/cmd/vela-server/metadata.go
index 93f821d0c..4ce86ea31 100644
--- a/cmd/vela-server/metadata.go
+++ b/cmd/vela-server/metadata.go
@@ -98,7 +98,7 @@ func metadataSource(c *cli.Context) (*types.Source, error) {
// helper function to capture the Vela metadata from the CLI arguments.
//
-// nolint: unparam // ignore unparam for now
+//nolint:unparam // ignore unparam for now
func metadataVela(c *cli.Context) (*types.Vela, error) {
logrus.Trace("Creating Vela metadata from CLI configuration")
diff --git a/cmd/vela-server/queue.go b/cmd/vela-server/queue.go
index 677137e18..edd703e69 100644
--- a/cmd/vela-server/queue.go
+++ b/cmd/vela-server/queue.go
@@ -18,11 +18,12 @@ func setupQueue(c *cli.Context) (queue.Service, error) {
// queue configuration
_setup := &queue.Setup{
- Driver: c.String("queue.driver"),
- Address: c.String("queue.addr"),
- Cluster: c.Bool("queue.cluster"),
- Routes: c.StringSlice("queue.routes"),
- Timeout: c.Duration("queue.pop.timeout"),
+ Driver: c.String("queue.driver"),
+ Address: c.String("queue.addr"),
+ Cluster: c.Bool("queue.cluster"),
+ Routes: c.StringSlice("queue.routes"),
+ Timeout: c.Duration("queue.pop.timeout"),
+ PrivateKey: c.String("queue.private-key"),
}
// setup the queue
diff --git a/cmd/vela-server/schedule.go b/cmd/vela-server/schedule.go
new file mode 100644
index 000000000..b65723177
--- /dev/null
+++ b/cmd/vela-server/schedule.go
@@ -0,0 +1,400 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package main
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/adhocore/gronx"
+ "github.com/go-vela/server/api/build"
+ "github.com/go-vela/server/compiler"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/queue"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/types"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/go-vela/types/pipeline"
+ "github.com/sirupsen/logrus"
+
+ "k8s.io/apimachinery/pkg/util/wait"
+)
+
+const (
+ scheduleErr = "unable to trigger build for schedule"
+
+ scheduleWait = "waiting to trigger build for schedule"
+)
+
+func processSchedules(ctx context.Context, start time.Time, compiler compiler.Engine, database database.Interface, metadata *types.Metadata, queue queue.Service, scm scm.Service) error {
+ logrus.Infof("processing active schedules to create builds")
+
+ // send API call to capture the list of active schedules
+ schedules, err := database.ListActiveSchedules(ctx)
+ if err != nil {
+ return err
+ }
+
+ // iterate through the list of active schedules
+ for _, s := range schedules {
+ // sleep for 1s - 2s before processing the active schedule
+ //
+ // This should prevent multiple servers from processing a schedule at the same time by
+ // leveraging a base duration along with a standard deviation of randomness a.k.a.
+ // "jitter". To create the jitter, we use a base duration of 1s with a scale factor of 1.0.
+ time.Sleep(wait.Jitter(time.Second, 1.0))
+
+ // send API call to capture the schedule
+ //
+ // This is needed to ensure we are not dealing with a stale schedule since we fetch
+ // all schedules once and iterate through that list which can take a significant
+ // amount of time to get to the end of the list.
+ schedule, err := database.GetSchedule(ctx, s.GetID())
+ if err != nil {
+ logrus.WithError(err).Warnf("%s %s", scheduleErr, schedule.GetName())
+
+ continue
+ }
+
+ // ignore triggering a build if the schedule is no longer active
+ if !schedule.GetActive() {
+ logrus.Tracef("skipping to trigger build for inactive schedule %s", schedule.GetName())
+
+ continue
+ }
+
+ // capture the last time a build was triggered for the schedule in UTC
+ scheduled := time.Unix(schedule.GetScheduledAt(), 0).UTC()
+
+ // capture the previous occurrence of the entry rounded to the nearest whole interval
+ //
+ // i.e. if it's 4:02 on five minute intervals, this will be 4:00
+ prevTime, err := gronx.PrevTick(schedule.GetEntry(), true)
+ if err != nil {
+ logrus.WithError(err).Warnf("%s %s", scheduleErr, schedule.GetName())
+
+ continue
+ }
+
+ // capture the next occurrence of the entry after the last schedule rounded to the nearest whole interval
+ //
+ // i.e. if it's 4:02 on five minute intervals, this will be 4:05
+ nextTime, err := gronx.NextTickAfter(schedule.GetEntry(), scheduled, true)
+ if err != nil {
+ logrus.WithError(err).Warnf("%s %s", scheduleErr, schedule.GetName())
+
+ continue
+ }
+
+ // check if we should wait to trigger a build for the schedule
+ //
+ // The current time must be after the next occurrence of the schedule.
+ if !time.Now().After(nextTime) {
+ logrus.Tracef("%s %s: current time not past next occurrence", scheduleWait, schedule.GetName())
+
+ continue
+ }
+
+ // check if we should wait to trigger a build for the schedule
+ //
+ // The previous occurrence of the schedule must be after the starting time of processing schedules.
+ if !prevTime.After(start) {
+ logrus.Tracef("%s %s: previous occurence not after starting point", scheduleWait, schedule.GetName())
+
+ continue
+ }
+
+ // update the scheduled_at field with the current timestamp
+ //
+ // This should help prevent multiple servers from processing a schedule at the same time
+ // by updating the schedule with a new timestamp to reflect the current state.
+ schedule.SetScheduledAt(time.Now().UTC().Unix())
+
+ // send API call to update schedule for ensuring scheduled_at field is set
+ _, err = database.UpdateSchedule(ctx, schedule, false)
+ if err != nil {
+ logrus.WithError(err).Warnf("%s %s", scheduleErr, schedule.GetName())
+
+ continue
+ }
+
+ // process the schedule and trigger a new build
+ err = processSchedule(ctx, schedule, compiler, database, metadata, queue, scm)
+ if err != nil {
+ logrus.WithError(err).Warnf("%s %s", scheduleErr, schedule.GetName())
+
+ continue
+ }
+ }
+
+ return nil
+}
+
+//nolint:funlen // ignore function length and number of statements
+func processSchedule(ctx context.Context, s *library.Schedule, compiler compiler.Engine, database database.Interface, metadata *types.Metadata, queue queue.Service, scm scm.Service) error {
+ // send API call to capture the repo for the schedule
+ r, err := database.GetRepo(ctx, s.GetRepoID())
+ if err != nil {
+ return fmt.Errorf("unable to fetch repo: %w", err)
+ }
+
+ logrus.Tracef("processing schedule %s/%s", r.GetFullName(), s.GetName())
+
+ // check if the repo is active
+ if !r.GetActive() {
+ return fmt.Errorf("repo %s is not active", r.GetFullName())
+ }
+
+ // check if the repo has a valid owner
+ if r.GetUserID() == 0 {
+ return fmt.Errorf("repo %s does not have a valid owner", r.GetFullName())
+ }
+
+ // send API call to capture the owner for the repo
+ u, err := database.GetUser(r.GetUserID())
+ if err != nil {
+ return fmt.Errorf("unable to get owner for repo %s: %w", r.GetFullName(), err)
+ }
+
+ // send API call to confirm repo owner has at least write access to repo
+ _, err = scm.RepoAccess(u, u.GetToken(), r.GetOrg(), r.GetName())
+ if err != nil {
+ return fmt.Errorf("%s does not have at least write access for repo %s", u.GetName(), r.GetFullName())
+ }
+
+ // create SQL filters for querying pending and running builds for repo
+ filters := map[string]interface{}{
+ "status": []string{constants.StatusPending, constants.StatusRunning},
+ }
+
+ // send API call to capture the number of pending or running builds for the repo
+ builds, err := database.CountBuildsForRepo(context.TODO(), r, filters)
+ if err != nil {
+ return fmt.Errorf("unable to get count of builds for repo %s: %w", r.GetFullName(), err)
+ }
+
+ // check if the number of pending and running builds exceeds the limit for the repo
+ if builds >= r.GetBuildLimit() {
+ return fmt.Errorf("repo %s has excceded the concurrent build limit of %d", r.GetFullName(), r.GetBuildLimit())
+ }
+
+ // send API call to capture the commit sha for the branch
+ _, commit, err := scm.GetBranch(u, r, s.GetBranch())
+ if err != nil {
+ return fmt.Errorf("failed to get commit for repo %s on %s branch: %w", r.GetFullName(), r.GetBranch(), err)
+ }
+
+ url := strings.TrimSuffix(r.GetClone(), ".git")
+
+ b := new(library.Build)
+ b.SetAuthor(s.GetCreatedBy())
+ b.SetBranch(s.GetBranch())
+ b.SetClone(r.GetClone())
+ b.SetCommit(commit)
+ b.SetDeploy(s.GetName())
+ b.SetEvent(constants.EventSchedule)
+ b.SetMessage(fmt.Sprintf("triggered for %s schedule with %s entry", s.GetName(), s.GetEntry()))
+ b.SetRef(fmt.Sprintf("refs/heads/%s", b.GetBranch()))
+ b.SetRepoID(r.GetID())
+ b.SetSender(s.GetUpdatedBy())
+ b.SetSource(fmt.Sprintf("%s/tree/%s", url, b.GetBranch()))
+ b.SetStatus(constants.StatusPending)
+ b.SetTitle(fmt.Sprintf("%s received from %s", constants.EventSchedule, url))
+
+ // populate the build link if a web address is provided
+ if len(metadata.Vela.WebAddress) > 0 {
+ b.SetLink(fmt.Sprintf("%s/%s/%d", metadata.Vela.WebAddress, r.GetFullName(), b.GetNumber()))
+ }
+
+ var (
+ // variable to store the raw pipeline configuration
+ config []byte
+ // variable to store executable pipeline
+ p *pipeline.Build
+ // variable to store pipeline configuration
+ pipeline *library.Pipeline
+ // variable to control number of times to retry processing pipeline
+ retryLimit = 5
+ // variable to store the pipeline type for the repository
+ pipelineType = r.GetPipelineType()
+ )
+
+ // implement a loop to process asynchronous operations with a retry limit
+ //
+ // Some operations taken during this workflow can lead to race conditions failing to successfully process
+ // the request. This logic ensures we attempt our best efforts to handle these cases gracefully.
+ for i := 0; i < retryLimit; i++ {
+ logrus.Debugf("compilation loop - attempt %d", i+1)
+ // check if we're on the first iteration of the loop
+ if i > 0 {
+ // incrementally sleep in between retries
+ time.Sleep(time.Duration(i) * time.Second)
+ }
+
+ // send API call to attempt to capture the pipeline
+ pipeline, err = database.GetPipelineForRepo(context.TODO(), b.GetCommit(), r)
+ if err != nil { // assume the pipeline doesn't exist in the database yet
+ // send API call to capture the pipeline configuration file
+ config, err = scm.ConfigBackoff(u, r, b.GetCommit())
+ if err != nil {
+ return fmt.Errorf("unable to get pipeline config for %s/%s: %w", r.GetFullName(), b.GetCommit(), err)
+ }
+ } else {
+ config = pipeline.GetData()
+ }
+
+ // send API call to capture repo for the counter (grabbing repo again to ensure counter is correct)
+ r, err = database.GetRepoForOrg(ctx, r.GetOrg(), r.GetName())
+ if err != nil {
+ err = fmt.Errorf("unable to get repo %s: %w", r.GetFullName(), err)
+
+ // check if the retry limit has been exceeded
+ if i < retryLimit-1 {
+ logrus.WithError(err).Warningf("retrying #%d", i+1)
+
+ // continue to the next iteration of the loop
+ continue
+ }
+
+ return err
+ }
+
+ // set the build numbers based off repo counter
+ b.SetNumber(r.GetCounter() + 1)
+ // set the parent equal to the current repo counter
+ b.SetParent(r.GetCounter())
+ // check if the parent is set to 0
+ if b.GetParent() == 0 {
+ // parent should be "1" if it's the first build ran
+ b.SetParent(1)
+ }
+ r.SetCounter(r.GetCounter() + 1)
+
+ // set the build link if a web address is provided
+ if len(metadata.Vela.WebAddress) > 0 {
+ b.SetLink(fmt.Sprintf("%s/%s/%d", metadata.Vela.WebAddress, r.GetFullName(), b.GetNumber()))
+ }
+
+ // ensure we use the expected pipeline type when compiling
+ //
+ // The pipeline type for a repo can change at any time which can break compiling
+ // existing pipelines in the system for that repo. To account for this, we update
+ // the repo pipeline type to match what was defined for the existing pipeline
+ // before compiling. After we're done compiling, we reset the pipeline type.
+ if len(pipeline.GetType()) > 0 {
+ r.SetPipelineType(pipeline.GetType())
+ }
+
+ var compiled *library.Pipeline
+ // parse and compile the pipeline configuration file
+ p, compiled, err = compiler.
+ Duplicate().
+ WithBuild(b).
+ WithCommit(b.GetCommit()).
+ WithMetadata(metadata).
+ WithRepo(r).
+ WithUser(u).
+ Compile(config)
+ if err != nil {
+ return fmt.Errorf("unable to compile pipeline config for %s/%s: %w", r.GetFullName(), b.GetCommit(), err)
+ }
+
+ // reset the pipeline type for the repo
+ //
+ // The pipeline type for a repo can change at any time which can break compiling
+ // existing pipelines in the system for that repo. To account for this, we update
+ // the repo pipeline type to match what was defined for the existing pipeline
+ // before compiling. After we're done compiling, we reset the pipeline type.
+ r.SetPipelineType(pipelineType)
+
+ // skip the build if only the init or clone steps are found
+ skip := build.SkipEmptyBuild(p)
+ if skip != "" {
+ return nil
+ }
+
+ // check if the pipeline did not already exist in the database
+ if pipeline == nil {
+ pipeline = compiled
+ pipeline.SetRepoID(r.GetID())
+ pipeline.SetCommit(b.GetCommit())
+ pipeline.SetRef(b.GetRef())
+
+ // send API call to create the pipeline
+ pipeline, err = database.CreatePipeline(context.TODO(), pipeline)
+ if err != nil {
+ err = fmt.Errorf("failed to create pipeline for %s: %w", r.GetFullName(), err)
+
+ // check if the retry limit has been exceeded
+ if i < retryLimit-1 {
+ logrus.WithError(err).Warningf("retrying #%d", i+1)
+
+ // continue to the next iteration of the loop
+ continue
+ }
+
+ return err
+ }
+ }
+
+ b.SetPipelineID(pipeline.GetID())
+
+ // create the objects from the pipeline in the database
+ // TODO:
+ // - if a build gets created and something else fails midway,
+ // the next loop will attempt to create the same build,
+ // using the same Number and thus create a constraint
+ // conflict; consider deleting the partially created
+ // build object in the database
+ err = build.PlanBuild(context.TODO(), database, p, b, r)
+ if err != nil {
+ // check if the retry limit has been exceeded
+ if i < retryLimit-1 {
+ logrus.WithError(err).Warningf("retrying #%d", i+1)
+
+ // reset fields set by cleanBuild for retry
+ b.SetError("")
+ b.SetStatus(constants.StatusPending)
+ b.SetFinished(0)
+
+ // continue to the next iteration of the loop
+ continue
+ }
+
+ return err
+ }
+
+ // break the loop because everything was successful
+ break
+ } // end of retry loop
+
+ // send API call to update repo for ensuring counter is incremented
+ r, err = database.UpdateRepo(ctx, r)
+ if err != nil {
+ return fmt.Errorf("unable to update repo %s: %w", r.GetFullName(), err)
+ }
+
+ // send API call to capture the triggered build
+ b, err = database.GetBuildForRepo(context.TODO(), r, b.GetNumber())
+ if err != nil {
+ return fmt.Errorf("unable to get new build %s/%d: %w", r.GetFullName(), b.GetNumber(), err)
+ }
+
+ // publish the build to the queue
+ go build.PublishToQueue(
+ context.TODO(),
+ queue,
+ database,
+ p,
+ b,
+ r,
+ u,
+ )
+
+ return nil
+}
diff --git a/cmd/vela-server/secret.go b/cmd/vela-server/secret.go
index ee780dce0..56e6c0572 100644
--- a/cmd/vela-server/secret.go
+++ b/cmd/vela-server/secret.go
@@ -15,7 +15,7 @@ import (
)
// helper function to setup the secrets engines from the CLI arguments.
-func setupSecrets(c *cli.Context, d database.Service) (map[string]secret.Service, error) {
+func setupSecrets(c *cli.Context, d database.Interface) (map[string]secret.Service, error) {
logrus.Debug("Creating secret clients from CLI configuration")
secrets := make(map[string]secret.Service)
diff --git a/cmd/vela-server/server.go b/cmd/vela-server/server.go
index 9de6747c6..cb9f0edd1 100644
--- a/cmd/vela-server/server.go
+++ b/cmd/vela-server/server.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
@@ -9,19 +9,22 @@ import (
"fmt"
"net/http"
"net/url"
+ "os"
+ "os/signal"
+ "syscall"
"time"
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
"github.com/go-vela/server/router"
"github.com/go-vela/server/router/middleware"
-
- "github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
-
"github.com/urfave/cli/v2"
- "gopkg.in/tomb.v2"
+ "golang.org/x/sync/errgroup"
+
+ "k8s.io/apimachinery/pkg/util/wait"
)
-// nolint: funlen // ignore function length
func server(c *cli.Context) error {
// validate all input
err := validate(c)
@@ -59,7 +62,7 @@ func server(c *cli.Context) error {
return err
}
- database, err := setupDatabase(c)
+ database, err := database.FromCLIContext(c)
if err != nil {
return err
}
@@ -87,13 +90,15 @@ func server(c *cli.Context) error {
router := router.Load(
middleware.Compiler(compiler),
middleware.Database(database),
- middleware.Logger(logrus.StandardLogger(), time.RFC3339, true),
+ middleware.Logger(logrus.StandardLogger(), time.RFC3339),
middleware.Metadata(metadata),
+ middleware.TokenManager(setupTokenManager(c)),
middleware.Queue(queue),
middleware.RequestVersion,
middleware.Secret(c.String("vela-secret")),
middleware.Secrets(secrets),
middleware.Scm(scm),
+ middleware.QueueSigningPrivateKey(c.String("queue.private-key")),
middleware.Allowlist(c.StringSlice("vela-repo-allowlist")),
middleware.DefaultBuildLimit(c.Int64("default-build-limit")),
middleware.DefaultTimeout(c.Int64("default-build-timeout")),
@@ -101,6 +106,9 @@ func server(c *cli.Context) error {
middleware.WebhookValidation(!c.Bool("vela-disable-webhook-validation")),
middleware.SecureCookie(c.Bool("vela-enable-secure-cookie")),
middleware.Worker(c.Duration("worker-active-interval")),
+ middleware.DefaultRepoEvents(c.StringSlice("default-repo-events")),
+ middleware.AllowlistSchedule(c.StringSlice("vela-schedule-allowlist")),
+ middleware.ScheduleFrequency(c.Duration("schedule-minimum-frequency")),
)
addr, err := url.Parse(c.String("server-addr"))
@@ -108,43 +116,99 @@ func server(c *cli.Context) error {
return err
}
- var tomb tomb.Tomb
- // start http server
- tomb.Go(func() error {
- port := addr.Port()
-
- // check if a port is part of the address
- if len(port) == 0 {
- port = c.String("server-port")
- }
+ port := addr.Port()
+ // check if a port is part of the address
+ if len(port) == 0 {
+ port = c.String("server-port")
+ }
- // gin expects the address to be ":" ie ":8080"
- srv := &http.Server{Addr: fmt.Sprintf(":%s", port), Handler: router}
+ // gin expects the address to be ":" ie ":8080"
+ srv := &http.Server{
+ Addr: fmt.Sprintf(":%s", port),
+ Handler: router,
+ ReadHeaderTimeout: 60 * time.Second,
+ }
- logrus.Infof("running server on %s", addr.Host)
- go func() {
- logrus.Info("Starting HTTP server...")
- err := srv.ListenAndServe()
+ // create the context for controlling the worker subprocesses
+ ctx, done := context.WithCancel(context.Background())
+ // create the errgroup for managing worker subprocesses
+ //
+ // https://pkg.go.dev/golang.org/x/sync/errgroup?tab=doc#Group
+ g, gctx := errgroup.WithContext(ctx)
+
+ // spawn goroutine to check for signals to gracefully shutdown
+ g.Go(func() error {
+ signalChannel := make(chan os.Signal, 1)
+ signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM)
+
+ select {
+ case sig := <-signalChannel:
+ logrus.Infof("received signal: %s", sig)
+ err := srv.Shutdown(ctx)
if err != nil {
- tomb.Kill(err)
+ logrus.Error(err)
}
- }()
-
- // nolint: gosimple // ignore this for now
- for {
- select {
- case <-tomb.Dying():
- logrus.Info("Stopping HTTP server...")
- return srv.Shutdown(context.Background())
+ done()
+ case <-gctx.Done():
+ logrus.Info("closing signal goroutine")
+ err := srv.Shutdown(ctx)
+ if err != nil {
+ logrus.Error(err)
}
+ return gctx.Err()
}
+
+ return nil
})
- // Wait for stuff and watch for errors
- err = tomb.Wait()
- if err != nil {
+ // spawn goroutine for starting the server
+ g.Go(func() error {
+ logrus.Infof("starting server on %s", addr.Host)
+ err = srv.ListenAndServe()
+ if err != nil {
+ // log a message indicating the failure of the server
+ logrus.Errorf("failing server: %v", err)
+ }
+
return err
- }
+ })
+
+ // spawn goroutine for starting the scheduler
+ g.Go(func() error {
+ logrus.Info("starting scheduler")
+ for {
+ // track the starting time for when the server begins processing schedules
+ //
+ // This will be used to control which schedules will have a build triggered based
+ // off the configured entry and last time a build was triggered for the schedule.
+ start := time.Now().UTC()
+
+ // capture the interval of time to wait before processing schedules
+ //
+ // We need to sleep for some amount of time before we attempt to process schedules
+ // setup in the database. Since the schedule interval is configurable, we use that
+ // as the base duration to determine how long to sleep for.
+ interval := c.Duration("schedule-interval")
+
+ // This should prevent multiple servers from processing schedules at the same time by
+ // leveraging a base duration along with a standard deviation of randomness a.k.a.
+ // "jitter". To create the jitter, we use the configured schedule interval duration
+ // along with a scale factor of 0.5.
+ jitter := wait.Jitter(interval, 0.5)
+
+ logrus.Infof("sleeping for %v before scheduling builds", jitter)
+ // sleep for a duration of time before processing schedules
+ time.Sleep(jitter)
+
+ err = processSchedules(ctx, start, compiler, database, metadata, queue, scm)
+ if err != nil {
+ logrus.WithError(err).Warn("unable to process schedules")
+ } else {
+ logrus.Trace("successfully processed schedules")
+ }
+ }
+ })
- return tomb.Err()
+ // wait for errors from server subprocesses
+ return g.Wait()
}
diff --git a/cmd/vela-server/token.go b/cmd/vela-server/token.go
new file mode 100644
index 000000000..298ef283a
--- /dev/null
+++ b/cmd/vela-server/token.go
@@ -0,0 +1,32 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package main
+
+import (
+ "github.com/golang-jwt/jwt/v5"
+
+ "github.com/sirupsen/logrus"
+
+ "github.com/urfave/cli/v2"
+
+ "github.com/go-vela/server/internal/token"
+)
+
+// helper function to setup the tokenmanager from the CLI arguments.
+func setupTokenManager(c *cli.Context) *token.Manager {
+ logrus.Debug("Creating token manager from CLI configuration")
+
+ tm := &token.Manager{
+ PrivateKey: c.String("vela-server-private-key"),
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: c.Duration("user-access-token-duration"),
+ UserRefreshTokenDuration: c.Duration("user-refresh-token-duration"),
+ BuildTokenBufferDuration: c.Duration("build-token-buffer-duration"),
+ WorkerAuthTokenDuration: c.Duration("worker-auth-token-duration"),
+ WorkerRegisterTokenDuration: c.Duration("worker-register-token-duration"),
+ }
+
+ return tm
+}
diff --git a/cmd/vela-server/validate.go b/cmd/vela-server/validate.go
index 4098b6a42..cac3b0424 100644
--- a/cmd/vela-server/validate.go
+++ b/cmd/vela-server/validate.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
@@ -8,6 +8,8 @@ import (
"fmt"
"strings"
+ "github.com/go-vela/types/constants"
+
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
@@ -31,8 +33,6 @@ func validate(c *cli.Context) error {
}
// helper function to validate the core CLI configuration.
-//
-// nolint:lll // ignoring line length check to avoid breaking up error messages
func validateCore(c *cli.Context) error {
logrus.Trace("Validating core CLI configuration")
@@ -48,8 +48,12 @@ func validateCore(c *cli.Context) error {
return fmt.Errorf("server-addr (VELA_ADDR or VELA_HOST) flag must not have trailing slash")
}
- if len(c.String("vela-secret")) == 0 {
- return fmt.Errorf("vela-secret (VELA_SECRET) flag is not properly configured")
+ if len(c.String("clone-image")) == 0 {
+ return fmt.Errorf("clone-image (VELA_CLONE_IMAGE) flag is not properly configured")
+ }
+
+ if len(c.String("vela-server-private-key")) == 0 {
+ return fmt.Errorf("vela-server-private-key (VELA_SERVER_PRIVATE_KEY) flag is not properly configured")
}
if len(c.String("webui-addr")) == 0 {
@@ -68,8 +72,12 @@ func validateCore(c *cli.Context) error {
}
}
- if c.Duration("refresh-token-duration").Seconds() <= c.Duration("access-token-duration").Seconds() {
- return fmt.Errorf("refresh-token-duration (VELA_REFRESH_TOKEN_DURATION) must be larger than the access-token-duration (VELA_ACCESS_TOKEN_DURATION)")
+ if c.Duration("user-refresh-token-duration").Seconds() <= c.Duration("user-access-token-duration").Seconds() {
+ return fmt.Errorf("user-refresh-token-duration (VELA_USER_REFRESH_TOKEN_DURATION) must be larger than the user-access-token-duration (VELA_USER_ACCESS_TOKEN_DURATION)")
+ }
+
+ if c.Duration("build-token-buffer-duration").Seconds() < 0 {
+ return fmt.Errorf("build-token-buffer-duration (VELA_BUILD_TOKEN_BUFFER_DURATION) must not be a negative time value")
}
if c.Int64("default-build-limit") == 0 {
@@ -80,12 +88,22 @@ func validateCore(c *cli.Context) error {
return fmt.Errorf("max-build-limit (VELA_MAX_BUILD_LIMIT) flag must be greater than 0")
}
+ for _, event := range c.StringSlice("default-repo-events") {
+ switch event {
+ case constants.EventPull:
+ case constants.EventPush:
+ case constants.EventDeploy:
+ case constants.EventTag:
+ case constants.EventComment:
+ default:
+ return fmt.Errorf("default-repo-events (VELA_DEFAULT_REPO_EVENTS) has the unsupported value of %s", event)
+ }
+ }
+
return nil
}
// helper function to validate the compiler CLI configuration.
-//
-// nolint:lll // ignoring line length check to avoid breaking up error messages
func validateCompiler(c *cli.Context) error {
logrus.Trace("Validating compiler CLI configuration")
@@ -99,5 +117,9 @@ func validateCompiler(c *cli.Context) error {
}
}
+ if c.Int("max-template-depth") < 1 {
+ return fmt.Errorf("max-template-depth (VELA_MAX_TEMPLATE_DEPTH) or (MAX_TEMPLATE_DEPTH) flag must be greater than 0")
+ }
+
return nil
}
diff --git a/compiler/context.go b/compiler/context.go
index 41196c22a..2a5622628 100644
--- a/compiler/context.go
+++ b/compiler/context.go
@@ -54,7 +54,7 @@ func FromGinContext(c *gin.Context) Engine {
func WithContext(c context.Context, e Engine) context.Context {
// set the compiler Engine in the context.Context
//
- // nolint: revive,staticcheck // ignore using string with context value
+ //nolint:revive,staticcheck // ignore using string with context value
return context.WithValue(c, key, e)
}
diff --git a/compiler/context_test.go b/compiler/context_test.go
index 97dd383d2..e5b340de6 100644
--- a/compiler/context_test.go
+++ b/compiler/context_test.go
@@ -22,7 +22,7 @@ func TestCompiler_FromContext(t *testing.T) {
want Engine
}{
{
- // nolint: staticcheck // ignore using string with context value
+ //nolint:staticcheck, revive // ignore using string with context value
context: context.WithValue(context.Background(), key, _engine),
want: _engine,
},
@@ -31,7 +31,7 @@ func TestCompiler_FromContext(t *testing.T) {
want: nil,
},
{
- // nolint: staticcheck // ignore using string with context value
+ //nolint:staticcheck, revive // ignore using string with context value
context: context.WithValue(context.Background(), key, "foo"),
want: nil,
},
@@ -92,7 +92,7 @@ func TestCompiler_WithContext(t *testing.T) {
// setup types
var _engine Engine
- // nolint: staticcheck // ignore using string with context value
+ //nolint:staticcheck, revive // ignore using string with context value
want := context.WithValue(context.Background(), key, _engine)
// run test
diff --git a/compiler/doc.go b/compiler/doc.go
index d86fb589d..0e6e12e57 100644
--- a/compiler/doc.go
+++ b/compiler/doc.go
@@ -8,5 +8,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/compiler"
+// import "github.com/go-vela/server/compiler"
package compiler
diff --git a/compiler/engine.go b/compiler/engine.go
index 86c0c7b37..0296c99d4 100644
--- a/compiler/engine.go
+++ b/compiler/engine.go
@@ -20,7 +20,12 @@ type Engine interface {
// Compile defines a function that produces an executable
// representation of a pipeline from an object. This calls
// Parse internally to convert the object to a yaml configuration.
- Compile(interface{}) (*pipeline.Build, error)
+ Compile(interface{}) (*pipeline.Build, *library.Pipeline, error)
+
+ // CompileLite defines a function that produces an light executable
+ // representation of a pipeline from an object. This calls
+ // Parse internally to convert the object to a yaml configuration.
+ CompileLite(interface{}, bool, bool) (*yaml.Build, *library.Pipeline, error)
// Duplicate defines a function that
// creates a clone of the Engine.
@@ -28,7 +33,7 @@ type Engine interface {
// Parse defines a function that converts
// an object to a yaml configuration.
- Parse(interface{}) (*yaml.Build, error)
+ Parse(interface{}, string, *yaml.Template) (*yaml.Build, []byte, error)
// ParseRaw defines a function that converts
// an object to a string.
@@ -66,12 +71,10 @@ type Engine interface {
// ExpandStages defines a function that injects the template
// for each templated step in every stage in a yaml configuration.
- // nolint: lll // ignore long line length due to return args
- ExpandStages(*yaml.Build, map[string]*yaml.Template) (yaml.StageSlice, yaml.SecretSlice, yaml.ServiceSlice, raw.StringSliceMap, error)
+ ExpandStages(*yaml.Build, map[string]*yaml.Template, *pipeline.RuleData) (*yaml.Build, error)
// ExpandSteps defines a function that injects the template
- // for each templated step in a yaml configuration.
- // nolint: lll // ignore long line length due to return args
- ExpandSteps(*yaml.Build, map[string]*yaml.Template) (yaml.StepSlice, yaml.SecretSlice, yaml.ServiceSlice, raw.StringSliceMap, error)
+ // for each templated step in a yaml configuration with the provided template depth.
+ ExpandSteps(*yaml.Build, map[string]*yaml.Template, *pipeline.RuleData, int) (*yaml.Build, error)
// Init Compiler Interface Functions
@@ -118,12 +121,18 @@ type Engine interface {
// WithComment defines a function that sets
// the comment in the Engine.
WithComment(string) Engine
+ // WithCommit defines a function that sets
+ // the commit in the Engine.
+ WithCommit(string) Engine
// WithFiles defines a function that sets
// the changeset files in the Engine.
WithFiles([]string) Engine
// WithLocal defines a function that sets
// the compiler local field in the Engine.
WithLocal(bool) Engine
+ // WithLocalTemplates defines a function that sets
+ // the compiler local templates field in the Engine.
+ WithLocalTemplates([]string) Engine
// WithMetadata defines a function that sets
// the compiler Metadata type in the Engine.
WithMetadata(*types.Metadata) Engine
diff --git a/compiler/native/clone.go b/compiler/native/clone.go
index f62f2da6f..ffc14393a 100644
--- a/compiler/native/clone.go
+++ b/compiler/native/clone.go
@@ -10,8 +10,6 @@ import (
)
const (
- // default image for clone process.
- cloneImage = "target/vela-git:v0.4.0"
// default name for clone stage.
cloneStageName = "clone"
// default name for clone step.
@@ -34,7 +32,7 @@ func (c *client) CloneStage(p *yaml.Build) (*yaml.Build, error) {
Steps: yaml.StepSlice{
&yaml.Step{
Detach: false,
- Image: cloneImage,
+ Image: c.CloneImage,
Name: cloneStepName,
Privileged: false,
Pull: constants.PullNotPresent,
@@ -67,7 +65,7 @@ func (c *client) CloneStep(p *yaml.Build) (*yaml.Build, error) {
// create new clone step
clone := &yaml.Step{
Detach: false,
- Image: cloneImage,
+ Image: c.CloneImage,
Name: cloneStepName,
Privileged: false,
Pull: constants.PullNotPresent,
diff --git a/compiler/native/clone_test.go b/compiler/native/clone_test.go
index fd0f137d2..f481ee4c3 100644
--- a/compiler/native/clone_test.go
+++ b/compiler/native/clone_test.go
@@ -13,9 +13,12 @@ import (
"github.com/urfave/cli/v2"
)
+const defaultCloneImage = "target/vela-git:latest"
+
func TestNative_CloneStage(t *testing.T) {
// setup types
set := flag.NewFlagSet("test", 0)
+ set.String("clone-image", defaultCloneImage, "doc")
c := cli.NewContext(nil, set, nil)
str := "foo"
@@ -53,7 +56,7 @@ func TestNative_CloneStage(t *testing.T) {
Name: "clone",
Steps: yaml.StepSlice{
&yaml.Step{
- Image: "target/vela-git:v0.4.0",
+ Image: defaultCloneImage,
Name: "clone",
Pull: "not_present",
},
@@ -113,6 +116,7 @@ func TestNative_CloneStage(t *testing.T) {
func TestNative_CloneStep(t *testing.T) {
// setup types
set := flag.NewFlagSet("test", 0)
+ set.String("clone-image", defaultCloneImage, "doc")
c := cli.NewContext(nil, set, nil)
str := "foo"
@@ -142,7 +146,7 @@ func TestNative_CloneStep(t *testing.T) {
Version: "v1",
Steps: yaml.StepSlice{
&yaml.Step{
- Image: "target/vela-git:v0.4.0",
+ Image: defaultCloneImage,
Name: "clone",
Pull: "not_present",
},
diff --git a/compiler/native/compile.go b/compiler/native/compile.go
index 1ab2ccd8d..d106fa480 100644
--- a/compiler/native/compile.go
+++ b/compiler/native/compile.go
@@ -9,11 +9,13 @@ import (
"context"
"encoding/json"
"fmt"
- "io/ioutil"
+ "io"
"net/http"
"strings"
"time"
+ "github.com/go-vela/types/constants"
+
yml "github.com/buildkite/yaml"
"github.com/go-vela/types/library"
@@ -39,154 +41,265 @@ type ModifyResponse struct {
}
// Compile produces an executable pipeline from a yaml configuration.
-//
-// nolint: gocyclo,funlen // ignore function length due to comments
-func (c *client) Compile(v interface{}) (*pipeline.Build, error) {
- p, err := c.Parse(v)
+func (c *client) Compile(v interface{}) (*pipeline.Build, *library.Pipeline, error) {
+ p, data, err := c.Parse(v, c.repo.GetPipelineType(), new(yaml.Template))
if err != nil {
- return nil, err
+ return nil, nil, err
}
+ // create the library pipeline object from the yaml configuration
+ _pipeline := p.ToPipelineLibrary()
+ _pipeline.SetData(data)
+ _pipeline.SetType(c.repo.GetPipelineType())
+
// validate the yaml configuration
err = c.Validate(p)
if err != nil {
- return nil, err
+ return nil, _pipeline, err
}
// create map of templates for easy lookup
- tmpls := mapFromTemplates(p.Templates)
+ templates := mapFromTemplates(p.Templates)
+
+ event := c.build.GetEvent()
+ action := c.build.GetEventAction()
+
+ // if the build has an event action, concatenate event and event action for matching
+ if !strings.EqualFold(action, "") {
+ event = event + ":" + action
+ }
// create the ruledata to purge steps
r := &pipeline.RuleData{
Branch: c.build.GetBranch(),
Comment: c.comment,
- Event: c.build.GetEvent(),
+ Event: event,
Path: c.files,
Repo: c.repo.GetFullName(),
Tag: strings.TrimPrefix(c.build.GetRef(), "refs/tags/"),
Target: c.build.GetDeploy(),
}
- if len(p.Stages) > 0 {
- // check if the pipeline disabled the clone
- if p.Metadata.Clone == nil || *p.Metadata.Clone {
- // inject the clone stage
- p, err = c.CloneStage(p)
- if err != nil {
- return nil, err
- }
- }
-
- // inject the init stage
- p, err = c.InitStage(p)
+ switch {
+ case p.Metadata.RenderInline:
+ newPipeline, err := c.compileInline(p, c.TemplateDepth)
if err != nil {
- return nil, err
+ return nil, _pipeline, err
}
-
- // inject the templates into the stages
- p.Stages, p.Secrets, p.Services, p.Environment, err = c.ExpandStages(p, tmpls)
+ // validate the yaml configuration
+ err = c.Validate(newPipeline)
if err != nil {
- return nil, err
+ return nil, _pipeline, err
}
- if c.ModificationService.Endpoint != "" {
- // send config to external endpoint for modification
- p, err = c.modifyConfig(p, c.build, c.repo)
- if err != nil {
- return nil, err
- }
+ if len(newPipeline.Stages) > 0 {
+ return c.compileStages(newPipeline, _pipeline, map[string]*yaml.Template{}, r)
}
+ return c.compileSteps(newPipeline, _pipeline, map[string]*yaml.Template{}, r)
+ case len(p.Stages) > 0:
+ return c.compileStages(p, _pipeline, templates, r)
+ default:
+ return c.compileSteps(p, _pipeline, templates, r)
+ }
+}
+
+// CompileLite produces a partial of an executable pipeline from a yaml configuration.
+func (c *client) CompileLite(v interface{}, template, substitute bool) (*yaml.Build, *library.Pipeline, error) {
+ p, data, err := c.Parse(v, c.repo.GetPipelineType(), new(yaml.Template))
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // create the library pipeline object from the yaml configuration
+ _pipeline := p.ToPipelineLibrary()
+ _pipeline.SetData(data)
+ _pipeline.SetType(c.repo.GetPipelineType())
+
+ if p.Metadata.RenderInline {
+ newPipeline, err := c.compileInline(p, c.TemplateDepth)
+ if err != nil {
+ return nil, _pipeline, err
+ }
// validate the yaml configuration
- err = c.Validate(p)
+ err = c.Validate(newPipeline)
if err != nil {
- return nil, err
+ return nil, _pipeline, err
}
- // Create some default global environment inject vars
- // these are used below to overwrite to an empty
- // map if they should not be injected into a container
- envGlobalServices, envGlobalSecrets, envGlobalSteps := p.Environment, p.Environment, p.Environment
+ p = newPipeline
+ }
+
+ if template {
+ // create map of templates for easy lookup
+ templates := mapFromTemplates(p.Templates)
- if !p.Metadata.HasEnvironment("services") {
- envGlobalServices = make(raw.StringSliceMap)
- }
+ switch {
+ case len(p.Stages) > 0:
+ // inject the templates into the steps
+ p, err = c.ExpandStages(p, templates, nil)
+ if err != nil {
+ return nil, _pipeline, err
+ }
- if !p.Metadata.HasEnvironment("secrets") {
- envGlobalSecrets = make(raw.StringSliceMap)
- }
+ if substitute {
+ // inject the substituted environment variables into the steps
+ p.Stages, err = c.SubstituteStages(p.Stages)
+ if err != nil {
+ return nil, _pipeline, err
+ }
+ }
+ case len(p.Steps) > 0:
+ // inject the templates into the steps
+ p, err = c.ExpandSteps(p, templates, nil, c.TemplateDepth)
+ if err != nil {
+ return nil, _pipeline, err
+ }
- if !p.Metadata.HasEnvironment("steps") {
- envGlobalSteps = make(raw.StringSliceMap)
+ if substitute {
+ // inject the substituted environment variables into the steps
+ p.Steps, err = c.SubstituteSteps(p.Steps)
+ if err != nil {
+ return nil, _pipeline, err
+ }
+ }
}
+ }
- // inject the environment variables into the services
- p.Services, err = c.EnvironmentServices(p.Services, envGlobalServices)
+ // validate the yaml configuration
+ err = c.Validate(p)
+ if err != nil {
+ return nil, _pipeline, err
+ }
+
+ return p, _pipeline, nil
+}
+
+// compileInline parses and expands out inline pipelines.
+func (c *client) compileInline(p *yaml.Build, depth int) (*yaml.Build, error) {
+ newPipeline := *p
+ newPipeline.Templates = yaml.TemplateSlice{}
+
+ // return if max template depth has been reached
+ if depth == 0 {
+ retErr := fmt.Errorf("max template depth of %d exceeded", c.TemplateDepth)
+
+ return nil, retErr
+ }
+
+ for _, template := range p.Templates {
+ bytes, err := c.getTemplate(template, template.Name)
if err != nil {
return nil, err
}
- // inject the environment variables into the secrets
- p.Secrets, err = c.EnvironmentSecrets(p.Secrets, envGlobalSecrets)
- if err != nil {
- return nil, err
+ format := template.Format
+
+ // set the default format to golang if the user did not define anything
+ if template.Format == "" {
+ format = constants.PipelineTypeGo
}
- // inject the environment variables into the stages
- p.Stages, err = c.EnvironmentStages(p.Stages, envGlobalSteps)
+ parsed, _, err := c.Parse(bytes, format, template)
if err != nil {
return nil, err
}
- // inject the substituted environment variables into the stages
- p.Stages, err = c.SubstituteStages(p.Stages)
- if err != nil {
- return nil, err
+ // if template parsed contains a template reference, recurse with decremented depth
+ if len(parsed.Templates) > 0 && parsed.Metadata.RenderInline {
+ parsed, err = c.compileInline(parsed, depth-1)
+ if err != nil {
+ return nil, err
+ }
}
- // inject the scripts into the stages
- p.Stages, err = c.ScriptStages(p.Stages)
- if err != nil {
- return nil, err
+ switch {
+ case len(parsed.Environment) > 0:
+ for key, value := range parsed.Environment {
+ newPipeline.Environment[key] = value
+ }
+
+ fallthrough
+ case len(parsed.Stages) > 0:
+ // ensure all templated steps inside stages have template prefix
+ for stgIndex, newStage := range parsed.Stages {
+ parsed.Stages[stgIndex].Name = fmt.Sprintf("%s_%s", template.Name, newStage.Name)
+
+ for index, newStep := range newStage.Steps {
+ parsed.Stages[stgIndex].Steps[index].Name = fmt.Sprintf("%s_%s", template.Name, newStep.Name)
+ }
+ }
+
+ newPipeline.Stages = append(newPipeline.Stages, parsed.Stages...)
+
+ fallthrough
+ case len(parsed.Steps) > 0:
+ // ensure all templated steps have template prefix
+ for index, newStep := range parsed.Steps {
+ parsed.Steps[index].Name = fmt.Sprintf("%s_%s", template.Name, newStep.Name)
+ }
+
+ newPipeline.Steps = append(newPipeline.Steps, parsed.Steps...)
+
+ fallthrough
+ case len(parsed.Services) > 0:
+ newPipeline.Services = append(newPipeline.Services, parsed.Services...)
+ fallthrough
+ case len(parsed.Secrets) > 0:
+ newPipeline.Secrets = append(newPipeline.Secrets, parsed.Secrets...)
+ default:
+ //nolint:lll // ignore long line length due to error message
+ return nil, fmt.Errorf("empty template %s provided: template must contain secrets, services, stages or steps", template.Name)
}
- // return executable representation
- return c.TransformStages(r, p)
+ if len(newPipeline.Stages) > 0 && len(newPipeline.Steps) > 0 {
+ //nolint:lll // ignore long line length due to error message
+ return nil, fmt.Errorf("invalid template %s provided: templates cannot mix stages and steps", template.Name)
+ }
}
+ return &newPipeline, nil
+}
+
+// compileSteps executes the workflow for converting a YAML pipeline into an executable struct.
+//
+//nolint:dupl,lll // linter thinks the steps and stages workflows are identical
+func (c *client) compileSteps(p *yaml.Build, _pipeline *library.Pipeline, tmpls map[string]*yaml.Template, r *pipeline.RuleData) (*pipeline.Build, *library.Pipeline, error) {
+ var err error
+
// check if the pipeline disabled the clone
if p.Metadata.Clone == nil || *p.Metadata.Clone {
// inject the clone step
p, err = c.CloneStep(p)
if err != nil {
- return nil, err
+ return nil, _pipeline, err
}
}
// inject the init step
p, err = c.InitStep(p)
if err != nil {
- return nil, err
+ return nil, _pipeline, err
}
// inject the templates into the steps
- p.Steps, p.Secrets, p.Services, p.Environment, err = c.ExpandSteps(p, tmpls)
+ p, err = c.ExpandSteps(p, tmpls, r, c.TemplateDepth)
if err != nil {
- return nil, err
+ return nil, _pipeline, err
}
if c.ModificationService.Endpoint != "" {
// send config to external endpoint for modification
p, err = c.modifyConfig(p, c.build, c.repo)
if err != nil {
- return nil, err
+ return nil, _pipeline, err
}
}
// validate the yaml configuration
err = c.Validate(p)
if err != nil {
- return nil, err
+ return nil, _pipeline, err
}
// Create some default global environment inject vars
@@ -209,49 +322,149 @@ func (c *client) Compile(v interface{}) (*pipeline.Build, error) {
// inject the environment variables into the services
p.Services, err = c.EnvironmentServices(p.Services, envGlobalServices)
if err != nil {
- return nil, err
+ return nil, _pipeline, err
}
// inject the environment variables into the secrets
p.Secrets, err = c.EnvironmentSecrets(p.Secrets, envGlobalSecrets)
if err != nil {
- return nil, err
+ return nil, _pipeline, err
}
// inject the environment variables into the steps
p.Steps, err = c.EnvironmentSteps(p.Steps, envGlobalSteps)
if err != nil {
- return nil, err
+ return nil, _pipeline, err
}
// inject the substituted environment variables into the steps
p.Steps, err = c.SubstituteSteps(p.Steps)
if err != nil {
- return nil, err
+ return nil, _pipeline, err
}
// inject the scripts into the steps
p.Steps, err = c.ScriptSteps(p.Steps)
if err != nil {
- return nil, err
+ return nil, _pipeline, err
+ }
+
+ // create executable representation
+ build, err := c.TransformSteps(r, p)
+ if err != nil {
+ return nil, _pipeline, err
+ }
+
+ return build, _pipeline, nil
+}
+
+// compileStages executes the workflow for converting a YAML pipeline into an executable struct.
+//
+//nolint:dupl,lll // linter thinks the steps and stages workflows are identical
+func (c *client) compileStages(p *yaml.Build, _pipeline *library.Pipeline, tmpls map[string]*yaml.Template, r *pipeline.RuleData) (*pipeline.Build, *library.Pipeline, error) {
+ var err error
+
+ // check if the pipeline disabled the clone
+ if p.Metadata.Clone == nil || *p.Metadata.Clone {
+ // inject the clone stage
+ p, err = c.CloneStage(p)
+ if err != nil {
+ return nil, _pipeline, err
+ }
+ }
+
+ // inject the init stage
+ p, err = c.InitStage(p)
+ if err != nil {
+ return nil, _pipeline, err
+ }
+
+ // inject the templates into the stages
+ p, err = c.ExpandStages(p, tmpls, r)
+ if err != nil {
+ return nil, _pipeline, err
+ }
+
+ if c.ModificationService.Endpoint != "" {
+ // send config to external endpoint for modification
+ p, err = c.modifyConfig(p, c.build, c.repo)
+ if err != nil {
+ return nil, _pipeline, err
+ }
+ }
+
+ // validate the yaml configuration
+ err = c.Validate(p)
+ if err != nil {
+ return nil, _pipeline, err
+ }
+
+ // Create some default global environment inject vars
+ // these are used below to overwrite to an empty
+ // map if they should not be injected into a container
+ envGlobalServices, envGlobalSecrets, envGlobalSteps := p.Environment, p.Environment, p.Environment
+
+ if !p.Metadata.HasEnvironment("services") {
+ envGlobalServices = make(raw.StringSliceMap)
+ }
+
+ if !p.Metadata.HasEnvironment("secrets") {
+ envGlobalSecrets = make(raw.StringSliceMap)
+ }
+
+ if !p.Metadata.HasEnvironment("steps") {
+ envGlobalSteps = make(raw.StringSliceMap)
+ }
+
+ // inject the environment variables into the services
+ p.Services, err = c.EnvironmentServices(p.Services, envGlobalServices)
+ if err != nil {
+ return nil, _pipeline, err
+ }
+
+ // inject the environment variables into the secrets
+ p.Secrets, err = c.EnvironmentSecrets(p.Secrets, envGlobalSecrets)
+ if err != nil {
+ return nil, _pipeline, err
+ }
+
+ // inject the environment variables into the stages
+ p.Stages, err = c.EnvironmentStages(p.Stages, envGlobalSteps)
+ if err != nil {
+ return nil, _pipeline, err
+ }
+
+ // inject the substituted environment variables into the stages
+ p.Stages, err = c.SubstituteStages(p.Stages)
+ if err != nil {
+ return nil, _pipeline, err
+ }
+
+ // inject the scripts into the stages
+ p.Stages, err = c.ScriptStages(p.Stages)
+ if err != nil {
+ return nil, _pipeline, err
}
- // return executable representation
- return c.TransformSteps(r, p)
+ // create executable representation
+ build, err := c.TransformStages(r, p)
+ if err != nil {
+ return nil, _pipeline, err
+ }
+
+ return build, _pipeline, nil
}
// errorHandler ensures the error contains the number of request attempts.
func errorHandler(resp *http.Response, err error, attempts int) (*http.Response, error) {
if err != nil {
- // nolint:lll // detailed error message
- err = fmt.Errorf("giving up connecting to modification endpoint after %d attempts due to: %v", attempts, err)
+ err = fmt.Errorf("giving up connecting to modification endpoint after %d attempts due to: %w", attempts, err)
}
return resp, err
}
// modifyConfig sends the configuration to external http endpoint for modification.
-// nolint:lll // parameter struct references push line limit
func (c *client) modifyConfig(build *yaml.Build, libraryBuild *library.Build, repo *library.Repo) (*yaml.Build, error) {
// create request to send to endpoint
data, err := yml.Marshal(build)
@@ -284,17 +497,16 @@ func (c *client) modifyConfig(build *yaml.Build, libraryBuild *library.Build, re
Backoff: retryablehttp.DefaultBackoff,
}
+ // ensure the overall request(s) do not take over the defined timeout
+ ctx, cancel := context.WithTimeout(context.Background(), c.ModificationService.Timeout)
+ defer cancel()
+
// create POST request
- req, err := retryablehttp.NewRequest("POST", c.ModificationService.Endpoint, bytes.NewBuffer(b))
+ req, err := retryablehttp.NewRequestWithContext(ctx, "POST", c.ModificationService.Endpoint, bytes.NewBuffer(b))
if err != nil {
return nil, err
}
- // ensure the overall request(s) do not take over the defined timeout
- ctx, cancel := context.WithTimeout(req.Request.Context(), c.ModificationService.Timeout)
- defer cancel()
- req.WithContext(ctx)
-
// add content-type and auth headers
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.ModificationService.Secret))
@@ -311,7 +523,7 @@ func (c *client) modifyConfig(build *yaml.Build, libraryBuild *library.Build, re
return nil, fmt.Errorf("modification endpoint returned status code %v", resp.StatusCode)
}
- body, err := ioutil.ReadAll(resp.Body)
+ body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read payload: %w", err)
}
diff --git a/compiler/native/compile_test.go b/compiler/native/compile_test.go
index f7a925c97..3a37703d6 100644
--- a/compiler/native/compile_test.go
+++ b/compiler/native/compile_test.go
@@ -7,10 +7,16 @@ package native
import (
"flag"
"fmt"
- "io/ioutil"
"net/http"
"net/http/httptest"
- "reflect"
+ "os"
+ "path/filepath"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/raw"
+
+ "github.com/google/go-github/v53/github"
+
"testing"
"time"
@@ -31,6 +37,8 @@ import (
func TestNative_Compile_StagesPipeline(t *testing.T) {
// setup types
set := flag.NewFlagSet("test", 0)
+ set.String("clone-image", defaultCloneImage, "doc")
+ set.Int("max-template-depth", 5, "doc")
c := cli.NewContext(nil, set, nil)
m := &types.Metadata{
@@ -129,7 +137,7 @@ func TestNative_Compile_StagesPipeline(t *testing.T) {
ID: "__0_clone_clone",
Directory: "/vela/src/foo//",
Environment: cloneEnv,
- Image: "target/vela-git:v0.4.0",
+ Image: defaultCloneImage,
Name: "clone",
Number: 2,
Pull: "not_present",
@@ -236,7 +244,7 @@ func TestNative_Compile_StagesPipeline(t *testing.T) {
}
// run test
- yaml, err := ioutil.ReadFile("testdata/stages_pipeline.yml")
+ yaml, err := os.ReadFile("testdata/stages_pipeline.yml")
if err != nil {
t.Errorf("Reading yaml file return err: %v", err)
}
@@ -248,7 +256,7 @@ func TestNative_Compile_StagesPipeline(t *testing.T) {
compiler.WithMetadata(m)
- got, err := compiler.Compile(yaml)
+ got, _, err := compiler.Compile(yaml)
if err != nil {
t.Errorf("Compile returned err: %v", err)
}
@@ -291,7 +299,7 @@ func TestNative_Compile_StagesPipeline_Modification(t *testing.T) {
number := 1
// run test
- yaml, err := ioutil.ReadFile("testdata/stages_pipeline.yml")
+ yaml, err := os.ReadFile("testdata/stages_pipeline.yml")
if err != nil {
t.Errorf("Reading yaml file return err: %v", err)
}
@@ -301,6 +309,7 @@ func TestNative_Compile_StagesPipeline_Modification(t *testing.T) {
libraryBuild *library.Build
repo *library.Repo
}
+
tests := []struct {
name string
args args
@@ -317,6 +326,7 @@ func TestNative_Compile_StagesPipeline_Modification(t *testing.T) {
endpoint: fmt.Sprintf("%s/%s", s.URL, "config/bad"),
}, true},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
compiler := client{
@@ -327,7 +337,7 @@ func TestNative_Compile_StagesPipeline_Modification(t *testing.T) {
repo: &library.Repo{Name: &author},
build: &library.Build{Author: &name, Number: &number},
}
- _, err := compiler.Compile(yaml)
+ _, _, err := compiler.Compile(yaml)
if (err != nil) != tt.wantErr {
t.Errorf("Compile() error = %v, wantErr %v", err, tt.wantErr)
return
@@ -357,7 +367,7 @@ func TestNative_Compile_StepsPipeline_Modification(t *testing.T) {
number := 1
// run test
- yaml, err := ioutil.ReadFile("testdata/steps_pipeline.yml")
+ yaml, err := os.ReadFile("testdata/steps_pipeline.yml")
if err != nil {
t.Errorf("Reading yaml file return err: %v", err)
}
@@ -367,6 +377,7 @@ func TestNative_Compile_StepsPipeline_Modification(t *testing.T) {
libraryBuild *library.Build
repo *library.Repo
}
+
tests := []struct {
name string
args args
@@ -383,6 +394,7 @@ func TestNative_Compile_StepsPipeline_Modification(t *testing.T) {
endpoint: fmt.Sprintf("%s/%s", s.URL, "config/bad"),
}, true},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
compiler := client{
@@ -393,7 +405,7 @@ func TestNative_Compile_StepsPipeline_Modification(t *testing.T) {
repo: tt.args.repo,
build: tt.args.libraryBuild,
}
- _, err := compiler.Compile(yaml)
+ _, _, err := compiler.Compile(yaml)
if (err != nil) != tt.wantErr {
t.Errorf("Compile() error = %v, wantErr %v", err, tt.wantErr)
return
@@ -405,6 +417,8 @@ func TestNative_Compile_StepsPipeline_Modification(t *testing.T) {
func TestNative_Compile_StepsPipeline(t *testing.T) {
// setup types
set := flag.NewFlagSet("test", 0)
+ set.String("clone-image", defaultCloneImage, "doc")
+ set.Int("max-template-depth", 5, "doc")
c := cli.NewContext(nil, set, nil)
m := &types.Metadata{
@@ -485,7 +499,7 @@ func TestNative_Compile_StepsPipeline(t *testing.T) {
ID: "step___0_clone",
Directory: "/vela/src/foo//",
Environment: cloneEnv,
- Image: "target/vela-git:v0.4.0",
+ Image: defaultCloneImage,
Name: "clone",
Number: 2,
Pull: "not_present",
@@ -562,7 +576,7 @@ func TestNative_Compile_StepsPipeline(t *testing.T) {
}
// run test
- yaml, err := ioutil.ReadFile("testdata/steps_pipeline.yml")
+ yaml, err := os.ReadFile("testdata/steps_pipeline.yml")
if err != nil {
t.Errorf("Reading yaml file return err: %v", err)
}
@@ -574,7 +588,7 @@ func TestNative_Compile_StepsPipeline(t *testing.T) {
compiler.WithMetadata(m)
- got, err := compiler.Compile(yaml)
+ got, _, err := compiler.Compile(yaml)
if err != nil {
t.Errorf("Compile returned err: %v", err)
}
@@ -592,10 +606,12 @@ func TestNative_Compile_StagesPipelineTemplate(t *testing.T) {
_, engine := gin.CreateTestContext(resp)
// setup mock server
- engine.GET("/api/v3/repos/:org/:name/contents/:path", func(c *gin.Context) {
- c.Header("Content-Type", "application/json")
- c.Status(http.StatusOK)
- c.File("testdata/template.json")
+ engine.GET("/api/v3/repos/:org/:repo/contents/:path", func(c *gin.Context) {
+ body, err := convertFileToGithubResponse(c.Param("path"))
+ if err != nil {
+ t.Error(err)
+ }
+ c.JSON(http.StatusOK, body)
})
s := httptest.NewServer(engine)
@@ -606,6 +622,8 @@ func TestNative_Compile_StagesPipelineTemplate(t *testing.T) {
set.Bool("github-driver", true, "doc")
set.String("github-url", s.URL, "doc")
set.String("github-token", "", "doc")
+ set.String("clone-image", defaultCloneImage, "doc")
+ set.Int("max-template-depth", 5, "doc")
c := cli.NewContext(nil, set, nil)
m := &types.Metadata{
@@ -702,7 +720,7 @@ func TestNative_Compile_StagesPipelineTemplate(t *testing.T) {
ID: "__0_clone_clone",
Directory: "/vela/src/foo//",
Environment: setupEnv,
- Image: "target/vela-git:v0.4.0",
+ Image: defaultCloneImage,
Name: "clone",
Number: 2,
Pull: "not_present",
@@ -813,7 +831,7 @@ func TestNative_Compile_StagesPipelineTemplate(t *testing.T) {
}
// run test
- yaml, err := ioutil.ReadFile("testdata/stages_pipeline_template.yml")
+ yaml, err := os.ReadFile("testdata/stages_pipeline_template.yml")
if err != nil {
t.Errorf("Reading yaml file return err: %v", err)
}
@@ -825,7 +843,7 @@ func TestNative_Compile_StagesPipelineTemplate(t *testing.T) {
compiler.WithMetadata(m)
- got, err := compiler.Compile(yaml)
+ got, _, err := compiler.Compile(yaml)
if err != nil {
t.Errorf("Compile returned err: %v", err)
}
@@ -855,10 +873,12 @@ func TestNative_Compile_StepsPipelineTemplate(t *testing.T) {
_, engine := gin.CreateTestContext(resp)
// setup mock server
- engine.GET("/api/v3/repos/foo/bar/contents/:path", func(c *gin.Context) {
- c.Header("Content-Type", "application/json")
- c.Status(http.StatusOK)
- c.File("testdata/template.json")
+ engine.GET("/api/v3/repos/:org/:repo/contents/:path", func(c *gin.Context) {
+ body, err := convertFileToGithubResponse(c.Param("path"))
+ if err != nil {
+ t.Error(err)
+ }
+ c.JSON(http.StatusOK, body)
})
s := httptest.NewServer(engine)
@@ -869,6 +889,8 @@ func TestNative_Compile_StepsPipelineTemplate(t *testing.T) {
set.Bool("github-driver", true, "doc")
set.String("github-url", s.URL, "doc")
set.String("github-token", "", "doc")
+ set.String("clone-image", defaultCloneImage, "doc")
+ set.Int("max-template-depth", 5, "doc")
c := cli.NewContext(nil, set, nil)
m := &types.Metadata{
@@ -955,7 +977,7 @@ func TestNative_Compile_StepsPipelineTemplate(t *testing.T) {
ID: "step___0_clone",
Directory: "/vela/src/foo//",
Environment: setupEnv,
- Image: "target/vela-git:v0.4.0",
+ Image: defaultCloneImage,
Name: "clone",
Number: 2,
Pull: "not_present",
@@ -1050,7 +1072,7 @@ func TestNative_Compile_StepsPipelineTemplate(t *testing.T) {
}
// run test
- yaml, err := ioutil.ReadFile("testdata/steps_pipeline_template.yml")
+ yaml, err := os.ReadFile("testdata/steps_pipeline_template.yml")
if err != nil {
t.Errorf("Reading yaml file return err: %v", err)
}
@@ -1062,7 +1084,7 @@ func TestNative_Compile_StepsPipelineTemplate(t *testing.T) {
compiler.WithMetadata(m)
- got, err := compiler.Compile(yaml)
+ got, _, err := compiler.Compile(yaml)
if err != nil {
t.Errorf("Compile returned err: %v", err)
}
@@ -1072,7 +1094,8 @@ func TestNative_Compile_StepsPipelineTemplate(t *testing.T) {
}
}
-func TestNative_Compile_InvalidType(t *testing.T) {
+// Test evaluation of `vela "tempalate_name"` function.
+func TestNative_Compile_StepsPipelineTemplate_VelaFunction_TemplateName(t *testing.T) {
// setup context
gin.SetMode(gin.TestMode)
@@ -1080,10 +1103,12 @@ func TestNative_Compile_InvalidType(t *testing.T) {
_, engine := gin.CreateTestContext(resp)
// setup mock server
- engine.GET("/api/v3/repos/foo/bar/contents/:path", func(c *gin.Context) {
- c.Header("Content-Type", "application/json")
- c.Status(http.StatusOK)
- c.File("testdata/template.json")
+ engine.GET("/api/v3/repos/:org/:repo/contents/:path", func(c *gin.Context) {
+ body, err := convertFileToGithubResponse(c.Param("path"))
+ if err != nil {
+ t.Error(err)
+ }
+ c.JSON(http.StatusOK, body)
})
s := httptest.NewServer(engine)
@@ -1094,6 +1119,8 @@ func TestNative_Compile_InvalidType(t *testing.T) {
set.Bool("github-driver", true, "doc")
set.String("github-url", s.URL, "doc")
set.String("github-token", "", "doc")
+ set.String("clone-image", defaultCloneImage, "doc")
+ set.Int("max-template-depth", 5, "doc")
c := cli.NewContext(nil, set, nil)
m := &types.Metadata{
@@ -1116,14 +1143,12 @@ func TestNative_Compile_InvalidType(t *testing.T) {
},
}
- gradleEnv := environment(nil, m, nil, nil)
- gradleEnv["GRADLE_OPTS"] = "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false"
- gradleEnv["GRADLE_USER_HOME"] = ".gradle"
+ setupEnv := environment(nil, m, nil, nil)
- dockerEnv := environment(nil, m, nil, nil)
- dockerEnv["PARAMETER_REGISTRY"] = "index.docker.io"
- dockerEnv["PARAMETER_REPO"] = "github/octocat"
- dockerEnv["PARAMETER_TAGS"] = "latest,dev"
+ helloEnv := environment(nil, m, nil, nil)
+ helloEnv["HOME"] = "/root"
+ helloEnv["SHELL"] = "/bin/sh"
+ helloEnv["VELA_BUILD_SCRIPT"] = generateScriptPosix([]string{"echo sample"})
want := &pipeline.Build{
Version: "1",
@@ -1137,7 +1162,7 @@ func TestNative_Compile_InvalidType(t *testing.T) {
&pipeline.Container{
ID: "step___0_init",
Directory: "/vela/src/foo//",
- Environment: environment(nil, m, nil, nil),
+ Environment: setupEnv,
Image: "#init",
Name: "init",
Number: 1,
@@ -1146,52 +1171,148 @@ func TestNative_Compile_InvalidType(t *testing.T) {
&pipeline.Container{
ID: "step___0_clone",
Directory: "/vela/src/foo//",
- Environment: environment(nil, m, nil, nil),
- Image: "target/vela-git:v0.4.0",
+ Environment: setupEnv,
+ Image: defaultCloneImage,
Name: "clone",
Number: 2,
Pull: "not_present",
},
&pipeline.Container{
- ID: "step___0_docker",
+ ID: "step___0_sample_hello",
Directory: "/vela/src/foo//",
- Image: "plugins/docker:18.09",
- Environment: dockerEnv,
- Name: "docker",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: helloEnv,
+ Image: "sample",
+ Name: "sample_hello",
Number: 3,
- Pull: "always",
- Secrets: pipeline.StepSecretSlice{
- &pipeline.StepSecret{
- Source: "docker_username",
- Target: "registry_username",
- },
- &pipeline.StepSecret{
- Source: "docker_password",
- Target: "registry_password",
- },
- },
+ Pull: "not_present",
},
},
- Secrets: pipeline.SecretSlice{
- &pipeline.Secret{
- Name: "docker_username",
- Key: "org/repo/docker/username",
- Engine: "native",
- Type: "repo",
- Origin: &pipeline.Container{},
+ }
+
+ // run test
+ yaml, err := os.ReadFile("testdata/template_name.yml")
+ if err != nil {
+ t.Errorf("Reading yaml file return err: %v", err)
+ }
+
+ compiler, err := New(c)
+ if err != nil {
+ t.Errorf("Creating compiler returned err: %v", err)
+ }
+
+ compiler.WithMetadata(m)
+
+ got, _, err := compiler.Compile(yaml)
+ if err != nil {
+ t.Errorf("Compile returned err: %v", err)
+ }
+
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("Compile() mismatch (-want +got):\n%s", diff)
+ }
+}
+
+// Test evaluation of `vela "tempalate_name"` function on a inline template.
+func TestNative_Compile_StepsPipelineTemplate_VelaFunction_TemplateName_Inline(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ _, engine := gin.CreateTestContext(resp)
+
+ // setup mock server
+ engine.GET("/api/v3/repos/:org/:repo/contents/:path", func(c *gin.Context) {
+ body, err := convertFileToGithubResponse(c.Param("path"))
+ if err != nil {
+ t.Error(err)
+ }
+ c.JSON(http.StatusOK, body)
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup types
+ set := flag.NewFlagSet("test", 0)
+ set.Bool("github-driver", true, "doc")
+ set.String("github-url", s.URL, "doc")
+ set.String("github-token", "", "doc")
+ set.String("clone-image", defaultCloneImage, "doc")
+ set.Int("max-template-depth", 5, "doc")
+ c := cli.NewContext(nil, set, nil)
+
+ m := &types.Metadata{
+ Database: &types.Database{
+ Driver: "foo",
+ Host: "foo",
+ },
+ Queue: &types.Queue{
+ Channel: "foo",
+ Driver: "foo",
+ Host: "foo",
+ },
+ Source: &types.Source{
+ Driver: "foo",
+ Host: "foo",
+ },
+ Vela: &types.Vela{
+ Address: "foo",
+ WebAddress: "foo",
+ },
+ }
+
+ setupEnv := environment(nil, m, nil, nil)
+
+ helloEnv := environment(nil, m, nil, nil)
+ helloEnv["HOME"] = "/root"
+ helloEnv["SHELL"] = "/bin/sh"
+ helloEnv["VELA_BUILD_SCRIPT"] = generateScriptPosix([]string{"echo inline_templatename"})
+
+ want := &pipeline.Build{
+ Version: "1",
+ ID: "__0",
+ Metadata: pipeline.Metadata{
+ Clone: true,
+ Template: false,
+ Environment: []string{"steps", "services", "secrets"},
+ },
+ Steps: pipeline.ContainerSlice{
+ &pipeline.Container{
+ ID: "step___0_init",
+ Directory: "/vela/src/foo//",
+ Environment: setupEnv,
+ Image: "#init",
+ Name: "init",
+ Number: 1,
+ Pull: "not_present",
},
- &pipeline.Secret{
- Name: "docker_password",
- Key: "org/repo/docker/password",
- Engine: "vault",
- Type: "repo",
- Origin: &pipeline.Container{},
+ &pipeline.Container{
+ ID: "step___0_clone",
+ Directory: "/vela/src/foo//",
+ Environment: setupEnv,
+ Image: defaultCloneImage,
+ Name: "clone",
+ Number: 2,
+ Pull: "not_present",
+ },
+ &pipeline.Container{
+ ID: "step___0_inline_templatename_hello",
+ Directory: "/vela/src/foo//",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: helloEnv,
+ Image: "inline_templatename",
+ Name: "inline_templatename_hello",
+ Number: 3,
+ Pull: "not_present",
},
},
}
// run test
- yaml, err := ioutil.ReadFile("testdata/invalid_type.yml")
+ yaml, err := os.ReadFile("testdata/template_name_inline.yml")
if err != nil {
t.Errorf("Reading yaml file return err: %v", err)
}
@@ -1203,13 +1324,88 @@ func TestNative_Compile_InvalidType(t *testing.T) {
compiler.WithMetadata(m)
- got, err := compiler.Compile(yaml)
+ got, _, err := compiler.Compile(yaml)
if err != nil {
t.Errorf("Compile returned err: %v", err)
}
- if !reflect.DeepEqual(got, want) {
- t.Errorf("Compile is %v, want %v", got, want)
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("Compile() mismatch (-want +got):\n%s", diff)
+ }
+}
+
+func TestNative_Compile_InvalidType(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ _, engine := gin.CreateTestContext(resp)
+
+ // setup mock server
+ engine.GET("/api/v3/repos/:org/:repo/contents/:path", func(c *gin.Context) {
+ body, err := convertFileToGithubResponse(c.Param("path"))
+ if err != nil {
+ t.Error(err)
+ }
+ c.JSON(http.StatusOK, body)
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup types
+ set := flag.NewFlagSet("test", 0)
+ set.Bool("github-driver", true, "doc")
+ set.String("github-url", s.URL, "doc")
+ set.String("github-token", "", "doc")
+ set.Int("max-template-depth", 5, "doc")
+ c := cli.NewContext(nil, set, nil)
+
+ m := &types.Metadata{
+ Database: &types.Database{
+ Driver: "foo",
+ Host: "foo",
+ },
+ Queue: &types.Queue{
+ Channel: "foo",
+ Driver: "foo",
+ Host: "foo",
+ },
+ Source: &types.Source{
+ Driver: "foo",
+ Host: "foo",
+ },
+ Vela: &types.Vela{
+ Address: "foo",
+ WebAddress: "foo",
+ },
+ }
+
+ gradleEnv := environment(nil, m, nil, nil)
+ gradleEnv["GRADLE_OPTS"] = "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false"
+ gradleEnv["GRADLE_USER_HOME"] = ".gradle"
+
+ dockerEnv := environment(nil, m, nil, nil)
+ dockerEnv["PARAMETER_REGISTRY"] = "index.docker.io"
+ dockerEnv["PARAMETER_REPO"] = "github/octocat"
+ dockerEnv["PARAMETER_TAGS"] = "latest,dev"
+
+ // run test
+ invalidYaml, err := os.ReadFile("testdata/invalid_type.yml")
+ if err != nil {
+ t.Errorf("Reading yaml file return err: %v", err)
+ }
+
+ compiler, err := New(c)
+ if err != nil {
+ t.Errorf("Creating compiler returned err: %v", err)
+ }
+
+ compiler.WithMetadata(m)
+
+ _, _, err = compiler.Compile(invalidYaml)
+ if err == nil {
+ t.Error("Compile should have returned an err")
}
}
@@ -1218,6 +1414,8 @@ func TestNative_Compile_Clone(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("github-driver", true, "doc")
set.String("github-token", "", "doc")
+ set.String("clone-image", defaultCloneImage, "doc")
+ set.Int("max-template-depth", 5, "doc")
c := cli.NewContext(nil, set, nil)
m := &types.Metadata{
@@ -1298,7 +1496,7 @@ func TestNative_Compile_Clone(t *testing.T) {
ID: "step___0_clone",
Directory: "/vela/src/foo//",
Environment: environment(nil, m, nil, nil),
- Image: "target/vela-git:v0.4.0",
+ Image: defaultCloneImage,
Name: "clone",
Number: 2,
Pull: "not_present",
@@ -1337,7 +1535,7 @@ func TestNative_Compile_Clone(t *testing.T) {
ID: "step___0_clone",
Directory: "/vela/src/foo//",
Environment: cloneEnv,
- Image: "target/vela-git:v0.4.0",
+ Image: "target/vela-git:v0.5.1",
Name: "clone",
Number: 2,
Pull: "always",
@@ -1357,6 +1555,7 @@ func TestNative_Compile_Clone(t *testing.T) {
type args struct {
file string
}
+
tests := []struct {
name string
args args
@@ -1373,10 +1572,11 @@ func TestNative_Compile_Clone(t *testing.T) {
file: "testdata/clone_replace.yml",
}, wantReplace, false},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// run test
- yaml, err := ioutil.ReadFile(tt.args.file)
+ yaml, err := os.ReadFile(tt.args.file)
if err != nil {
t.Errorf("Reading yaml file return err: %v", err)
}
@@ -1388,7 +1588,7 @@ func TestNative_Compile_Clone(t *testing.T) {
compiler.WithMetadata(m)
- got, err := compiler.Compile(yaml)
+ got, _, err := compiler.Compile(yaml)
if err != nil {
t.Errorf("Compile returned err: %v", err)
}
@@ -1405,6 +1605,8 @@ func TestNative_Compile_Pipeline_Type(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("github-driver", true, "doc")
set.String("github-token", "", "doc")
+ set.String("clone-image", defaultCloneImage, "doc")
+ set.Int("max-template-depth", 5, "doc")
c := cli.NewContext(nil, set, nil)
m := &types.Metadata{
@@ -1435,8 +1637,9 @@ func TestNative_Compile_Pipeline_Type(t *testing.T) {
Version: "1",
ID: "__0",
Metadata: pipeline.Metadata{
- Clone: true,
- Template: false,
+ Clone: true,
+ Template: false,
+ Environment: []string{"steps", "services", "secrets"},
},
Steps: pipeline.ContainerSlice{
&pipeline.Container{
@@ -1452,7 +1655,7 @@ func TestNative_Compile_Pipeline_Type(t *testing.T) {
ID: "step___0_clone",
Directory: "/vela/src/foo//",
Environment: defaultEnv,
- Image: "target/vela-git:v0.4.0",
+ Image: defaultCloneImage,
Name: "clone",
Number: 2,
Pull: "not_present",
@@ -1479,8 +1682,9 @@ func TestNative_Compile_Pipeline_Type(t *testing.T) {
Version: "1",
ID: "__0",
Metadata: pipeline.Metadata{
- Clone: true,
- Template: false,
+ Clone: true,
+ Template: false,
+ Environment: []string{"steps", "services", "secrets"},
},
Steps: pipeline.ContainerSlice{
&pipeline.Container{
@@ -1496,7 +1700,7 @@ func TestNative_Compile_Pipeline_Type(t *testing.T) {
ID: "step___0_clone",
Directory: "/vela/src/foo//",
Environment: defaultGoEnv,
- Image: "target/vela-git:v0.4.0",
+ Image: defaultCloneImage,
Name: "clone",
Number: 2,
Pull: "not_present",
@@ -1523,8 +1727,9 @@ func TestNative_Compile_Pipeline_Type(t *testing.T) {
Version: "1",
ID: "__0",
Metadata: pipeline.Metadata{
- Clone: true,
- Template: false,
+ Clone: true,
+ Template: false,
+ Environment: []string{"steps", "services", "secrets"},
},
Steps: pipeline.ContainerSlice{
&pipeline.Container{
@@ -1540,7 +1745,7 @@ func TestNative_Compile_Pipeline_Type(t *testing.T) {
ID: "step___0_clone",
Directory: "/vela/src/foo//",
Environment: defaultStarlarkEnv,
- Image: "target/vela-git:v0.4.0",
+ Image: defaultCloneImage,
Name: "clone",
Number: 2,
Pull: "not_present",
@@ -1561,6 +1766,7 @@ func TestNative_Compile_Pipeline_Type(t *testing.T) {
file string
pipelineType string
}
+
tests := []struct {
name string
args args
@@ -1571,10 +1777,11 @@ func TestNative_Compile_Pipeline_Type(t *testing.T) {
{"golang", args{file: "testdata/pipeline_type_go.yml", pipelineType: "go"}, wantGo, false},
{"starlark", args{file: "testdata/pipeline_type.star", pipelineType: "starlark"}, wantStarlark, false},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// run test
- yaml, err := ioutil.ReadFile(tt.args.file)
+ yaml, err := os.ReadFile(tt.args.file)
if err != nil {
t.Errorf("Reading yaml file return err: %v", err)
}
@@ -1587,7 +1794,7 @@ func TestNative_Compile_Pipeline_Type(t *testing.T) {
compiler.WithMetadata(m)
compiler.WithRepo(&library.Repo{PipelineType: &tt.args.pipelineType})
- got, err := compiler.Compile(yaml)
+ got, _, err := compiler.Compile(yaml)
if err != nil {
t.Errorf("Compile returned err: %v", err)
}
@@ -1608,7 +1815,7 @@ func TestNative_Compile_NoStepsorStages(t *testing.T) {
number := 1
// run test
- yaml, err := ioutil.ReadFile("testdata/metadata.yml")
+ yaml, err := os.ReadFile("testdata/metadata.yml")
if err != nil {
t.Errorf("Reading yaml file return err: %v", err)
}
@@ -1617,10 +1824,11 @@ func TestNative_Compile_NoStepsorStages(t *testing.T) {
if err != nil {
t.Errorf("Creating compiler returned err: %v", err)
}
+
compiler.repo = &library.Repo{Name: &author}
compiler.build = &library.Build{Author: &name, Number: &number}
- got, err := compiler.Compile(yaml)
+ got, _, err := compiler.Compile(yaml)
if err == nil {
t.Errorf("Compile should have returned err")
}
@@ -1639,7 +1847,7 @@ func TestNative_Compile_StepsandStages(t *testing.T) {
number := 1
// run test
- yaml, err := ioutil.ReadFile("testdata/steps_and_stages.yml")
+ yaml, err := os.ReadFile("testdata/steps_and_stages.yml")
if err != nil {
t.Errorf("Reading yaml file return err: %v", err)
}
@@ -1648,10 +1856,11 @@ func TestNative_Compile_StepsandStages(t *testing.T) {
if err != nil {
t.Errorf("Creating compiler returned err: %v", err)
}
+
compiler.repo = &library.Repo{Name: &author}
compiler.build = &library.Build{Author: &name, Number: &number}
- got, err := compiler.Compile(yaml)
+ got, _, err := compiler.Compile(yaml)
if err == nil {
t.Errorf("Compile should have returned err")
}
@@ -1683,10 +1892,12 @@ func Test_client_modifyConfig(t *testing.T) {
_, engine := gin.CreateTestContext(resp)
// setup mock server
- engine.GET("/api/v3/repos/foo/bar/contents/:path", func(c *gin.Context) {
- c.Header("Content-Type", "application/json")
- c.Status(http.StatusOK)
- c.File("testdata/template.json")
+ engine.GET("/api/v3/repos/:org/:repo/contents/:path", func(c *gin.Context) {
+ body, err := convertFileToGithubResponse(c.Param("path"))
+ if err != nil {
+ t.Error(err)
+ }
+ c.JSON(http.StatusOK, body)
})
m := &types.Metadata{
@@ -1724,7 +1935,7 @@ func Test_client_modifyConfig(t *testing.T) {
},
&yaml.Step{
Environment: environment(nil, m, nil, nil),
- Image: "target/vela-git:v0.3.0",
+ Image: defaultCloneImage,
Name: "clone",
Pull: "not_present",
},
@@ -1757,7 +1968,7 @@ func Test_client_modifyConfig(t *testing.T) {
},
&yaml.Step{
Environment: environment(nil, m, nil, nil),
- Image: "target/vela-git:v0.3.0",
+ Image: defaultCloneImage,
Name: "clone",
Pull: "not_present",
},
@@ -1851,6 +2062,7 @@ func Test_client_modifyConfig(t *testing.T) {
libraryBuild *library.Build
repo *library.Repo
}
+
tests := []struct {
name string
args args
@@ -1894,6 +2106,7 @@ func Test_client_modifyConfig(t *testing.T) {
endpoint: fmt.Sprintf("%s/%s", s.URL, "config/empty"),
}, nil, true},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
compiler := client{
@@ -1915,3 +2128,1308 @@ func Test_client_modifyConfig(t *testing.T) {
})
}
}
+
+func convertFileToGithubResponse(file string) (github.RepositoryContent, error) {
+ body, err := os.ReadFile(filepath.Join("testdata", file))
+ if err != nil {
+ return github.RepositoryContent{}, err
+ }
+
+ content := github.RepositoryContent{
+ Encoding: github.String(""),
+ Content: github.String(string(body)),
+ }
+
+ return content, nil
+}
+
+func generateTestEnv(command string, m *types.Metadata, pipelineType string) map[string]string {
+ output := environment(nil, m, nil, nil)
+ output["VELA_BUILD_SCRIPT"] = generateScriptPosix([]string{command})
+ output["HOME"] = "/root"
+ output["SHELL"] = "/bin/sh"
+ output["VELA_REPO_PIPELINE_TYPE"] = pipelineType
+
+ return output
+}
+
+func Test_Compile_Inline(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ _, engine := gin.CreateTestContext(resp)
+
+ // setup mock server
+ engine.GET("/api/v3/repos/:org/:repo/contents/:path", func(c *gin.Context) {
+ body, err := convertFileToGithubResponse(c.Param("path"))
+ if err != nil {
+ t.Error(err)
+ }
+ c.JSON(http.StatusOK, body)
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup types
+ set := flag.NewFlagSet("test", 0)
+ set.Bool("github-driver", true, "doc")
+ set.String("github-url", s.URL, "doc")
+ set.String("github-token", "", "doc")
+ set.String("clone-image", defaultCloneImage, "doc")
+ set.Int("max-template-depth", 5, "doc")
+ c := cli.NewContext(nil, set, nil)
+
+ m := &types.Metadata{
+ Database: &types.Database{
+ Driver: "foo",
+ Host: "foo",
+ },
+ Queue: &types.Queue{
+ Channel: "foo",
+ Driver: "foo",
+ Host: "foo",
+ },
+ Source: &types.Source{
+ Driver: "foo",
+ Host: "foo",
+ },
+ Vela: &types.Vela{
+ Address: "foo",
+ WebAddress: "foo",
+ },
+ }
+
+ initEnv := environment(nil, m, nil, nil)
+ testEnv := environment(nil, m, nil, nil)
+ testEnv["FOO"] = "Hello, foo!"
+ testEnv["HELLO"] = "Hello, Vela!"
+ stepEnv := environment(nil, m, nil, nil)
+ stepEnv["FOO"] = "Hello, foo!"
+ stepEnv["HELLO"] = "Hello, Vela!"
+ stepEnv["PARAMETER_FIRST"] = "foo"
+ golangEnv := environment(nil, m, nil, nil)
+ golangEnv["VELA_REPO_PIPELINE_TYPE"] = "go"
+
+ type args struct {
+ file string
+ pipelineType string
+ }
+
+ tests := []struct {
+ name string
+ args args
+ want *pipeline.Build
+ wantErr bool
+ }{
+ {
+ name: "root stages",
+ args: args{
+ file: "testdata/inline_with_stages.yml",
+ },
+ want: &pipeline.Build{
+ Version: "1",
+ ID: "__0",
+ Metadata: pipeline.Metadata{
+ Clone: true,
+ Environment: []string{"steps", "services", "secrets"},
+ },
+ Stages: []*pipeline.Stage{
+ {
+ Name: "init",
+ Environment: initEnv,
+ Steps: pipeline.ContainerSlice{
+ &pipeline.Container{
+ ID: "__0_init_init",
+ Directory: "/vela/src/foo//",
+ Environment: initEnv,
+ Image: "#init",
+ Name: "init",
+ Number: 1,
+ Pull: "not_present",
+ },
+ },
+ },
+ {
+ Name: "clone",
+ Environment: initEnv,
+ Steps: pipeline.ContainerSlice{
+ &pipeline.Container{
+ ID: "__0_clone_clone",
+ Directory: "/vela/src/foo//",
+ Environment: initEnv,
+ Image: defaultCloneImage,
+ Name: "clone",
+ Number: 2,
+ Pull: "not_present",
+ },
+ },
+ },
+ {
+ Name: "test",
+ Needs: []string{"clone"},
+ Environment: initEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_test_test",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo from inline", m, ""),
+ Image: "alpine",
+ Name: "test",
+ Pull: "not_present",
+ Number: 3,
+ },
+ },
+ },
+ {
+ Name: "golang_foo",
+ Needs: []string{"clone"},
+ Environment: initEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_golang_foo_golang_foo",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo hello from foo", m, ""),
+ Image: "golang:latest",
+ Name: "golang_foo",
+ Pull: "not_present",
+ Number: 4,
+ },
+ },
+ },
+ {
+ Name: "golang_bar",
+ Needs: []string{"clone"},
+ Environment: initEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_golang_bar_golang_bar",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo hello from bar", m, ""),
+ Image: "golang:latest",
+ Name: "golang_bar",
+ Pull: "not_present",
+ Number: 5,
+ },
+ },
+ },
+ {
+ Name: "golang_star",
+ Needs: []string{"clone"},
+ Environment: initEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_golang_star_golang_star",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo hello from star", m, ""),
+ Image: "golang:latest",
+ Name: "golang_star",
+ Pull: "not_present",
+ Number: 6,
+ },
+ },
+ },
+ {
+ Name: "starlark_foo",
+ Needs: []string{"clone"},
+ Environment: initEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_starlark_foo_starlark_build_foo",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo hello from foo", m, ""),
+ Image: "alpine",
+ Name: "starlark_build_foo",
+ Pull: "not_present",
+ Number: 7,
+ },
+ },
+ },
+ {
+ Name: "starlark_bar",
+ Needs: []string{"clone"},
+ Environment: initEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_starlark_bar_starlark_build_bar",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo hello from bar", m, ""),
+ Image: "alpine",
+ Name: "starlark_build_bar",
+ Pull: "not_present",
+ Number: 8,
+ },
+ },
+ },
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "nested templates",
+ args: args{
+ file: "testdata/inline_nested_template.yml",
+ },
+ want: &pipeline.Build{
+ Version: "1",
+ ID: "__0",
+ Metadata: pipeline.Metadata{
+ Clone: true,
+ Environment: []string{"steps", "services", "secrets"},
+ },
+ Stages: []*pipeline.Stage{
+ {
+ Name: "init",
+ Environment: initEnv,
+ Steps: pipeline.ContainerSlice{
+ &pipeline.Container{
+ ID: "__0_init_init",
+ Directory: "/vela/src/foo//",
+ Environment: initEnv,
+ Image: "#init",
+ Name: "init",
+ Number: 1,
+ Pull: "not_present",
+ },
+ },
+ },
+ {
+ Name: "clone",
+ Environment: initEnv,
+ Steps: pipeline.ContainerSlice{
+ &pipeline.Container{
+ ID: "__0_clone_clone",
+ Directory: "/vela/src/foo//",
+ Environment: initEnv,
+ Image: defaultCloneImage,
+ Name: "clone",
+ Number: 2,
+ Pull: "not_present",
+ },
+ },
+ },
+ {
+ Name: "test",
+ Needs: []string{"clone"},
+ Environment: initEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_test_test",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo from inline", m, ""),
+ Image: "alpine",
+ Name: "test",
+ Pull: "not_present",
+ Number: 3,
+ },
+ },
+ },
+ {
+ Name: "nested_test",
+ Needs: []string{"clone"},
+ Environment: initEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_nested_test_nested_test",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo from inline", m, ""),
+ Image: "alpine",
+ Name: "nested_test",
+ Pull: "not_present",
+ Number: 4,
+ },
+ },
+ },
+ {
+ Name: "nested_golang_foo",
+ Needs: []string{"clone"},
+ Environment: initEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_nested_golang_foo_nested_golang_foo",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo hello from foo", m, ""),
+ Image: "golang:latest",
+ Name: "nested_golang_foo",
+ Pull: "not_present",
+ Number: 5,
+ },
+ },
+ },
+ {
+ Name: "nested_golang_bar",
+ Needs: []string{"clone"},
+ Environment: initEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_nested_golang_bar_nested_golang_bar",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo hello from bar", m, ""),
+ Image: "golang:latest",
+ Name: "nested_golang_bar",
+ Pull: "not_present",
+ Number: 6,
+ },
+ },
+ },
+ {
+ Name: "nested_golang_star",
+ Needs: []string{"clone"},
+ Environment: initEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_nested_golang_star_nested_golang_star",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo hello from star", m, ""),
+ Image: "golang:latest",
+ Name: "nested_golang_star",
+ Pull: "not_present",
+ Number: 7,
+ },
+ },
+ },
+ {
+ Name: "nested_starlark_foo",
+ Needs: []string{"clone"},
+ Environment: initEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_nested_starlark_foo_nested_starlark_build_foo",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo hello from foo", m, ""),
+ Image: "alpine",
+ Name: "nested_starlark_build_foo",
+ Pull: "not_present",
+ Number: 8,
+ },
+ },
+ },
+ {
+ Name: "nested_starlark_bar",
+ Needs: []string{"clone"},
+ Environment: initEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_nested_starlark_bar_nested_starlark_build_bar",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo hello from bar", m, ""),
+ Image: "alpine",
+ Name: "nested_starlark_build_bar",
+ Pull: "not_present",
+ Number: 9,
+ },
+ },
+ },
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "root steps",
+ args: args{
+ file: "testdata/inline_with_steps.yml",
+ },
+ want: &pipeline.Build{
+ Version: "1",
+ ID: "__0",
+ Metadata: pipeline.Metadata{
+ Clone: true,
+ Environment: []string{"steps", "services", "secrets"},
+ },
+ Steps: []*pipeline.Container{
+ {
+ ID: "step___0_init",
+ Directory: "/vela/src/foo//",
+ Environment: initEnv,
+ Name: "init",
+ Image: "#init",
+ Number: 1,
+ Pull: "not_present",
+ },
+ {
+ ID: "step___0_clone",
+ Directory: "/vela/src/foo//",
+ Environment: initEnv,
+ Name: "clone",
+ Image: defaultCloneImage,
+ Number: 2,
+ Pull: "not_present",
+ },
+ {
+ ID: "step___0_test",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Directory: "/vela/src/foo//",
+ Environment: generateTestEnv("echo from inline", m, ""),
+ Name: "test",
+ Image: "alpine",
+ Number: 3,
+ Pull: "not_present",
+ },
+ {
+ ID: "step___0_golang_foo",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Directory: "/vela/src/foo//",
+ Environment: generateTestEnv("echo hello from foo", m, ""),
+ Name: "golang_foo",
+ Image: "alpine",
+ Number: 4,
+ Pull: "not_present",
+ },
+ {
+ ID: "step___0_golang_bar",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Directory: "/vela/src/foo//",
+ Environment: generateTestEnv("echo hello from bar", m, ""),
+ Name: "golang_bar",
+ Image: "alpine",
+ Number: 5,
+ Pull: "not_present",
+ },
+ {
+ ID: "step___0_golang_star",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Directory: "/vela/src/foo//",
+ Environment: generateTestEnv("echo hello from star", m, ""),
+ Name: "golang_star",
+ Image: "alpine",
+ Number: 6,
+ Pull: "not_present",
+ },
+ {
+ ID: "step___0_starlark_build_foo",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Directory: "/vela/src/foo//",
+ Environment: generateTestEnv("echo hello from foo", m, ""),
+ Name: "starlark_build_foo",
+ Image: "alpine",
+ Number: 7,
+ Pull: "not_present",
+ },
+ {
+ ID: "step___0_starlark_build_bar",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Directory: "/vela/src/foo//",
+ Environment: generateTestEnv("echo hello from bar", m, ""),
+ Name: "starlark_build_bar",
+ Image: "alpine",
+ Number: 8,
+ Pull: "not_present",
+ },
+ },
+ },
+ },
+ {
+ name: "stages and steps",
+ args: args{
+ file: "testdata/inline_with_stages_and_steps.yml",
+ },
+ want: nil,
+ wantErr: true,
+ },
+ {
+ name: "circular template call",
+ args: args{
+ file: "testdata/inline_circular_template.yml",
+ },
+ want: nil,
+ wantErr: true,
+ },
+ {
+ name: "secrets",
+ args: args{
+ file: "testdata/inline_with_secrets.yml",
+ },
+ want: &pipeline.Build{
+ Version: "1",
+ ID: "__0",
+ Metadata: pipeline.Metadata{
+ Clone: true,
+ Environment: []string{"steps", "services", "secrets"},
+ },
+ Steps: []*pipeline.Container{
+ {
+ ID: "step___0_init",
+ Directory: "/vela/src/foo//",
+ Environment: initEnv,
+ Name: "init",
+ Image: "#init",
+ Number: 1,
+ Pull: "not_present",
+ },
+ {
+ ID: "step___0_clone",
+ Directory: "/vela/src/foo//",
+ Environment: initEnv,
+ Name: "clone",
+ Image: defaultCloneImage,
+ Number: 2,
+ Pull: "not_present",
+ },
+ {
+ ID: "step___0_test",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Directory: "/vela/src/foo//",
+ Environment: generateTestEnv("echo from inline", m, ""),
+ Name: "test",
+ Image: "alpine",
+ Number: 3,
+ Pull: "not_present",
+ },
+ },
+ Secrets: pipeline.SecretSlice{
+ &pipeline.Secret{
+ Name: "foo_username",
+ Key: "org/repo/foo/username",
+ Engine: "native",
+ Type: "repo",
+ Origin: &pipeline.Container{},
+ },
+ &pipeline.Secret{
+ Name: "docker_username",
+ Key: "org/repo/docker/username",
+ Engine: "native",
+ Type: "repo",
+ Origin: &pipeline.Container{},
+ },
+ &pipeline.Secret{
+ Name: "docker_password",
+ Key: "org/repo/docker/password",
+ Engine: "vault",
+ Type: "repo",
+ Origin: &pipeline.Container{},
+ },
+ &pipeline.Secret{
+ Name: "docker_username",
+ Key: "org/docker/username",
+ Engine: "native",
+ Type: "org",
+ Origin: &pipeline.Container{},
+ },
+ &pipeline.Secret{
+ Name: "docker_password",
+ Key: "org/docker/password",
+ Engine: "vault",
+ Type: "org",
+ Origin: &pipeline.Container{},
+ },
+ &pipeline.Secret{
+ Name: "docker_username",
+ Key: "org/team/docker/username",
+ Engine: "native",
+ Type: "shared",
+ Origin: &pipeline.Container{},
+ },
+ &pipeline.Secret{
+ Name: "docker_password",
+ Key: "org/team/docker/password",
+ Engine: "vault",
+ Type: "shared",
+ Origin: &pipeline.Container{},
+ },
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "services",
+ args: args{
+ file: "testdata/inline_with_services.yml",
+ },
+ want: &pipeline.Build{
+ Version: "1",
+ ID: "__0",
+ Metadata: pipeline.Metadata{
+ Clone: true,
+ Environment: []string{"steps", "services", "secrets"},
+ },
+ Steps: []*pipeline.Container{
+ {
+ ID: "step___0_init",
+ Directory: "/vela/src/foo//",
+ Environment: initEnv,
+ Name: "init",
+ Image: "#init",
+ Number: 1,
+ Pull: "not_present",
+ },
+ {
+ ID: "step___0_clone",
+ Directory: "/vela/src/foo//",
+ Environment: initEnv,
+ Name: "clone",
+ Image: defaultCloneImage,
+ Number: 2,
+ Pull: "not_present",
+ },
+ {
+ ID: "step___0_test",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Directory: "/vela/src/foo//",
+ Environment: generateTestEnv("echo from inline", m, ""),
+ Name: "test",
+ Image: "alpine",
+ Number: 3,
+ Pull: "not_present",
+ },
+ },
+ Services: []*pipeline.Container{
+ {
+ ID: "service___0_postgres",
+ Detach: true,
+ Environment: initEnv,
+ Image: "postgres:latest",
+ Name: "postgres",
+ Number: 1,
+ Pull: "not_present",
+ },
+ {
+ ID: "service___0_cache",
+ Detach: true,
+ Environment: initEnv,
+ Image: "redis",
+ Name: "cache",
+ Number: 2,
+ Pull: "not_present",
+ },
+ {
+ ID: "service___0_database",
+ Detach: true,
+ Environment: initEnv,
+ Image: "mongo",
+ Name: "database",
+ Number: 3,
+ Pull: "not_present",
+ },
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "environment",
+ args: args{
+ file: "testdata/inline_with_environment.yml",
+ },
+ want: &pipeline.Build{
+ Version: "1",
+ ID: "__0",
+ Metadata: pipeline.Metadata{
+ Clone: true,
+ Environment: []string{"steps", "services", "secrets"},
+ },
+ Steps: []*pipeline.Container{
+ {
+ ID: "step___0_init",
+ Directory: "/vela/src/foo//",
+ Environment: testEnv,
+ Name: "init",
+ Image: "#init",
+ Number: 1,
+ Pull: "not_present",
+ },
+ {
+ ID: "step___0_clone",
+ Directory: "/vela/src/foo//",
+ Environment: testEnv,
+ Name: "clone",
+ Image: defaultCloneImage,
+ Number: 2,
+ Pull: "not_present",
+ },
+ {
+ ID: "step___0_test",
+ Directory: "/vela/src/foo//",
+ Environment: stepEnv,
+ Name: "test",
+ Image: "alpine",
+ Number: 3,
+ Pull: "not_present",
+ },
+ },
+ },
+ },
+ {
+ name: "golang base",
+ args: args{
+ file: "testdata/inline_with_golang.yml",
+ pipelineType: constants.PipelineTypeGo,
+ },
+ want: &pipeline.Build{
+ Version: "1",
+ ID: "__0",
+ Metadata: pipeline.Metadata{
+ Clone: true,
+ Environment: []string{"steps", "services", "secrets"},
+ },
+ Stages: []*pipeline.Stage{
+ {
+ Name: "init",
+ Environment: golangEnv,
+ Steps: pipeline.ContainerSlice{
+ &pipeline.Container{
+ ID: "__0_init_init",
+ Directory: "/vela/src/foo//",
+ Environment: golangEnv,
+ Image: "#init",
+ Name: "init",
+ Number: 1,
+ Pull: "not_present",
+ },
+ },
+ },
+ {
+ Name: "clone",
+ Environment: golangEnv,
+ Steps: pipeline.ContainerSlice{
+ &pipeline.Container{
+ ID: "__0_clone_clone",
+ Directory: "/vela/src/foo//",
+ Environment: golangEnv,
+ Image: defaultCloneImage,
+ Name: "clone",
+ Number: 2,
+ Pull: "not_present",
+ },
+ },
+ },
+ {
+ Name: "foo",
+ Needs: []string{"clone"},
+ Environment: golangEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_foo_foo",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo from inline foo", m, constants.PipelineTypeGo),
+ Image: "alpine",
+ Name: "foo",
+ Pull: "not_present",
+ Number: 3,
+ },
+ },
+ },
+ {
+ Name: "bar",
+ Needs: []string{"clone"},
+ Environment: golangEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_bar_bar",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo from inline bar", m, constants.PipelineTypeGo),
+ Image: "alpine",
+ Name: "bar",
+ Pull: "not_present",
+ Number: 4,
+ },
+ },
+ },
+ {
+ Name: "star",
+ Needs: []string{"clone"},
+ Environment: golangEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_star_star",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo from inline star", m, constants.PipelineTypeGo),
+ Image: "alpine",
+ Name: "star",
+ Pull: "not_present",
+ Number: 5,
+ },
+ },
+ },
+ {
+ Name: "golang_foo",
+ Needs: []string{"clone"},
+ Environment: golangEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_golang_foo_golang_foo",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo hello from foo", m, constants.PipelineTypeGo),
+ Image: "golang:latest",
+ Name: "golang_foo",
+ Pull: "not_present",
+ Number: 6,
+ },
+ },
+ },
+ {
+ Name: "golang_bar",
+ Needs: []string{"clone"},
+ Environment: golangEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_golang_bar_golang_bar",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo hello from bar", m, constants.PipelineTypeGo),
+ Image: "golang:latest",
+ Name: "golang_bar",
+ Pull: "not_present",
+ Number: 7,
+ },
+ },
+ },
+ {
+ Name: "golang_star",
+ Needs: []string{"clone"},
+ Environment: golangEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_golang_star_golang_star",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo hello from star", m, constants.PipelineTypeGo),
+ Image: "golang:latest",
+ Name: "golang_star",
+ Pull: "not_present",
+ Number: 8,
+ },
+ },
+ },
+ {
+ Name: "starlark_foo",
+ Needs: []string{"clone"},
+ Environment: golangEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_starlark_foo_starlark_build_foo",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo hello from foo", m, constants.PipelineTypeGo),
+ Image: "alpine",
+ Name: "starlark_build_foo",
+ Pull: "not_present",
+ Number: 9,
+ },
+ },
+ },
+ {
+ Name: "starlark_bar",
+ Needs: []string{"clone"},
+ Environment: golangEnv,
+ Steps: []*pipeline.Container{
+ {
+ ID: "__0_starlark_bar_starlark_build_bar",
+ Commands: []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"},
+ Directory: "/vela/src/foo//",
+ Entrypoint: []string{"/bin/sh", "-c"},
+ Environment: generateTestEnv("echo hello from bar", m, constants.PipelineTypeGo),
+ Image: "alpine",
+ Name: "starlark_build_bar",
+ Pull: "not_present",
+ Number: 10,
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ yaml, err := os.ReadFile(tt.args.file)
+ if err != nil {
+ t.Errorf("Reading yaml file return err: %v", err)
+ }
+ compiler, err := New(c)
+ if err != nil {
+ t.Errorf("Creating compiler returned err: %v", err)
+ }
+
+ compiler.WithMetadata(m)
+
+ if tt.args.pipelineType != "" {
+ compiler.WithRepo(&library.Repo{PipelineType: &tt.args.pipelineType})
+ }
+
+ got, _, err := compiler.Compile(yaml)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("Compile() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+
+ // WARNING: hack to compare stages
+ //
+ // Channel values can only be compared for equality.
+ // Two channel values are considered equal if they
+ // originated from the same make call meaning they
+ // refer to the same channel value in memory.
+ if got != nil {
+ for i, stage := range got.Stages {
+ tmp := tt.want.Stages
+
+ tmp[i].Done = stage.Done
+ }
+ }
+
+ if diff := cmp.Diff(tt.want, got); diff != "" {
+ t.Errorf("Compile() mismatch (-want +got):\n%s", diff)
+ }
+ })
+ }
+}
+
+func Test_CompileLite(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ _, engine := gin.CreateTestContext(resp)
+
+ // setup mock server
+ engine.GET("/api/v3/repos/:org/:repo/contents/:path", func(c *gin.Context) {
+ body, err := convertFileToGithubResponse(c.Param("path"))
+ if err != nil {
+ t.Error(err)
+ }
+ c.JSON(http.StatusOK, body)
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup types
+ set := flag.NewFlagSet("test", 0)
+ set.Bool("github-driver", true, "doc")
+ set.String("github-url", s.URL, "doc")
+ set.String("github-token", "", "doc")
+ set.Int("max-template-depth", 5, "doc")
+ c := cli.NewContext(nil, set, nil)
+
+ m := &types.Metadata{
+ Database: &types.Database{
+ Driver: "foo",
+ Host: "foo",
+ },
+ Queue: &types.Queue{
+ Channel: "foo",
+ Driver: "foo",
+ Host: "foo",
+ },
+ Source: &types.Source{
+ Driver: "foo",
+ Host: "foo",
+ },
+ Vela: &types.Vela{
+ Address: "foo",
+ WebAddress: "foo",
+ },
+ }
+
+ type args struct {
+ file string
+ pipelineType string
+ template bool
+ substitute bool
+ }
+
+ tests := []struct {
+ name string
+ args args
+ want *yaml.Build
+ wantErr bool
+ }{
+ {
+ name: "render_inline with stages",
+ args: args{
+ file: "testdata/inline_with_stages.yml",
+ pipelineType: "",
+ template: true,
+ substitute: true,
+ },
+ want: &yaml.Build{
+ Version: "1",
+ Metadata: yaml.Metadata{
+ RenderInline: true,
+ Environment: []string{"steps", "services", "secrets"},
+ },
+ Templates: []*yaml.Template{},
+ Stages: []*yaml.Stage{
+ {
+ Name: "test",
+ Needs: []string{"clone"},
+ Steps: []*yaml.Step{
+ {
+ Commands: raw.StringSlice{"echo from inline"},
+ Image: "alpine",
+ Name: "test",
+ Pull: "not_present",
+ },
+ },
+ },
+ {
+ Name: "golang_foo",
+ Needs: []string{"clone"},
+ Steps: []*yaml.Step{
+ {
+ Commands: raw.StringSlice{"echo hello from foo"},
+ Image: "golang:latest",
+ Name: "golang_foo",
+ Pull: "not_present",
+ },
+ },
+ },
+ {
+ Name: "golang_bar",
+ Needs: []string{"clone"},
+ Steps: []*yaml.Step{
+ {
+ Commands: raw.StringSlice{"echo hello from bar"},
+ Image: "golang:latest",
+ Name: "golang_bar",
+ Pull: "not_present",
+ },
+ },
+ },
+ {
+ Name: "golang_star",
+ Needs: []string{"clone"},
+ Steps: []*yaml.Step{
+ {
+ Commands: raw.StringSlice{"echo hello from star"},
+ Image: "golang:latest",
+ Name: "golang_star",
+ Pull: "not_present",
+ },
+ },
+ },
+ {
+ Name: "starlark_foo",
+ Needs: []string{"clone"},
+ Steps: []*yaml.Step{
+ {
+ Commands: raw.StringSlice{"echo hello from foo"},
+ Image: "alpine",
+ Name: "starlark_build_foo",
+ Pull: "not_present",
+ },
+ },
+ },
+ {
+ Name: "starlark_bar",
+ Needs: []string{"clone"},
+ Steps: []*yaml.Step{
+ {
+ Commands: raw.StringSlice{"echo hello from bar"},
+ Image: "alpine",
+ Name: "starlark_build_bar",
+ Pull: "not_present",
+ },
+ },
+ },
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "render_inline with steps",
+ args: args{
+ file: "testdata/inline_with_steps.yml",
+ pipelineType: "",
+ template: true,
+ substitute: true,
+ },
+ want: &yaml.Build{
+ Version: "1",
+ Metadata: yaml.Metadata{
+ RenderInline: true,
+ Environment: []string{"steps", "services", "secrets"},
+ },
+ Steps: yaml.StepSlice{
+ {
+ Commands: raw.StringSlice{"echo from inline"},
+ Image: "alpine",
+ Name: "test",
+ Pull: "not_present",
+ },
+ {
+ Commands: raw.StringSlice{"echo hello from foo"},
+ Image: "alpine",
+ Name: "golang_foo",
+ Pull: "not_present",
+ },
+ {
+ Commands: raw.StringSlice{"echo hello from bar"},
+ Image: "alpine",
+ Name: "golang_bar",
+ Pull: "not_present",
+ },
+ {
+ Commands: raw.StringSlice{"echo hello from star"},
+ Image: "alpine",
+ Name: "golang_star",
+ Pull: "not_present",
+ },
+ {
+ Commands: raw.StringSlice{"echo hello from foo"},
+ Image: "alpine",
+ Name: "starlark_build_foo",
+ Pull: "not_present",
+ },
+ {
+ Commands: raw.StringSlice{"echo hello from bar"},
+ Image: "alpine",
+ Name: "starlark_build_bar",
+ Pull: "not_present",
+ },
+ },
+ Templates: yaml.TemplateSlice{},
+ },
+ wantErr: false,
+ },
+ {
+ name: "golang",
+ args: args{
+ file: "testdata/golang_inline_stages.yml",
+ pipelineType: "golang",
+ template: false,
+ substitute: false,
+ },
+ want: &yaml.Build{
+ Version: "1",
+ Metadata: yaml.Metadata{
+ Environment: []string{"steps", "services", "secrets"},
+ },
+ Stages: []*yaml.Stage{
+ {
+ Name: "foo",
+ Needs: []string{"clone"},
+ Steps: []*yaml.Step{
+ {
+ Commands: raw.StringSlice{"echo hello from foo"},
+ Image: "alpine",
+ Name: "foo",
+ Pull: "not_present",
+ },
+ },
+ },
+ {
+ Name: "bar",
+ Needs: []string{"clone"},
+ Steps: []*yaml.Step{
+ {
+ Commands: raw.StringSlice{"echo hello from bar"},
+ Image: "alpine",
+ Name: "bar",
+ Pull: "not_present",
+ },
+ },
+ },
+ {
+ Name: "star",
+ Needs: []string{"clone"},
+ Steps: []*yaml.Step{
+ {
+ Commands: raw.StringSlice{"echo hello from star"},
+ Image: "alpine",
+ Name: "star",
+ Pull: "not_present",
+ },
+ },
+ },
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "step with template",
+ args: args{
+ file: "testdata/step_inline_template.yml",
+ pipelineType: "",
+ template: false,
+ substitute: false,
+ },
+ want: nil,
+ wantErr: true,
+ },
+ {
+ name: "stage with template",
+ args: args{
+ file: "testdata/stage_inline_template.yml",
+ pipelineType: "",
+ template: false,
+ substitute: false,
+ },
+ want: nil,
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ compiler, err := New(c)
+ if err != nil {
+ t.Errorf("Creating compiler returned err: %v", err)
+ }
+
+ compiler.WithMetadata(m)
+ if tt.args.pipelineType != "" {
+ compiler.WithRepo(&library.Repo{PipelineType: &tt.args.pipelineType})
+ }
+
+ yaml, err := os.ReadFile(tt.args.file)
+ if err != nil {
+ t.Errorf("Reading yaml file return err: %v", err)
+ }
+
+ got, _, err := compiler.CompileLite(yaml, tt.args.template, tt.args.substitute)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("CompileLite() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if diff := cmp.Diff(tt.want, got); diff != "" {
+ t.Errorf("CompileLite() mismatch (-want +got):\n%s", diff)
+ }
+ })
+ }
+}
diff --git a/compiler/native/doc.go b/compiler/native/doc.go
index 3de2c7a3a..81877d3da 100644
--- a/compiler/native/doc.go
+++ b/compiler/native/doc.go
@@ -8,5 +8,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/compiler/native"
+// import "github.com/go-vela/server/compiler/native"
package native
diff --git a/compiler/native/environment.go b/compiler/native/environment.go
index bfe26262a..7536731a6 100644
--- a/compiler/native/environment.go
+++ b/compiler/native/environment.go
@@ -18,7 +18,6 @@ import (
// EnvironmentStages injects environment variables
// for each stage in a yaml configuration.
-// nolint:lll // ignore function line length
func (c *client) EnvironmentStages(s yaml.StageSlice, globalEnv raw.StringSliceMap) (yaml.StageSlice, error) {
// iterate through all stages
for _, stage := range s {
@@ -33,7 +32,6 @@ func (c *client) EnvironmentStages(s yaml.StageSlice, globalEnv raw.StringSliceM
// EnvironmentStage injects environment variables
// for each stage in a yaml configuration.
-// nolint:lll // ignore function line length
func (c *client) EnvironmentStage(s *yaml.Stage, globalEnv raw.StringSliceMap) (*yaml.Stage, error) {
// make empty map of environment variables
env := make(map[string]string)
@@ -74,7 +72,6 @@ func (c *client) EnvironmentStage(s *yaml.Stage, globalEnv raw.StringSliceMap) (
// EnvironmentSteps injects environment variables
// for each step in a stage for the yaml configuration.
-// nolint:lll // ignore function line length
func (c *client) EnvironmentSteps(s yaml.StepSlice, stageEnv raw.StringSliceMap) (yaml.StepSlice, error) {
// iterate through all steps
for _, step := range s {
@@ -101,7 +98,6 @@ func (c *client) EnvironmentStep(s *yaml.Step, stageEnv raw.StringSliceMap) (*ya
// capture all environment variables from the local environment
for _, e := range os.Environ() {
// split the environment variable on = into a key value pair
- // nolint: gomnd // ignore magic number
parts := strings.SplitN(e, "=", 2)
env[parts[0]] = parts[1]
@@ -150,7 +146,6 @@ func (c *client) EnvironmentStep(s *yaml.Step, stageEnv raw.StringSliceMap) (*ya
// EnvironmentServices injects environment variables
// for each service in a yaml configuration.
-// nolint:lll // ignore function line length
func (c *client) EnvironmentServices(s yaml.ServiceSlice, globalEnv raw.StringSliceMap) (yaml.ServiceSlice, error) {
// iterate through all services
for _, service := range s {
@@ -186,7 +181,6 @@ func (c *client) EnvironmentServices(s yaml.ServiceSlice, globalEnv raw.StringSl
// EnvironmentSecrets injects environment variables
// for each secret plugin in a yaml configuration.
-// nolint:lll // ignore function line length
func (c *client) EnvironmentSecrets(s yaml.SecretSlice, globalEnv raw.StringSliceMap) (yaml.SecretSlice, error) {
// iterate through all secrets
for _, secret := range s {
@@ -205,7 +199,6 @@ func (c *client) EnvironmentSecrets(s yaml.SecretSlice, globalEnv raw.StringSlic
// capture all environment variables from the local environment
for _, e := range os.Environ() {
// split the environment variable on = into a key value pair
- // nolint: gomnd // ignore magic number
parts := strings.SplitN(e, "=", 2)
env[parts[0]] = parts[1]
@@ -264,7 +257,6 @@ func (c *client) EnvironmentBuild() map[string]string {
// capture all environment variables from the local environment
for _, e := range os.Environ() {
// split the environment variable on = into a key value pair
- // nolint: gomnd // ignore magic number
parts := strings.SplitN(e, "=", 2)
env[parts[0]] = parts[1]
@@ -292,8 +284,6 @@ func appendMap(originalMap, otherMap map[string]string) map[string]string {
}
// helper function that creates the standard set of environment variables for a pipeline.
-//
-// nolint: lll // ignore line length due to number of parameters provided
func environment(b *library.Build, m *types.Metadata, r *library.Repo, u *library.User) map[string]string {
// set default workspace
workspace := constants.WorkspaceDefault
@@ -316,7 +306,7 @@ func environment(b *library.Build, m *types.Metadata, r *library.Repo, u *librar
env["VELA_RUNTIME"] = notImplemented
env["VELA_SOURCE"] = notImplemented
env["VELA_VERSION"] = notImplemented
- env["CI"] = "vela"
+ env["CI"] = "true"
// populate environment variables from metadata
if m != nil {
diff --git a/compiler/native/environment_test.go b/compiler/native/environment_test.go
index 8ad6c7ccd..cba5e9242 100644
--- a/compiler/native/environment_test.go
+++ b/compiler/native/environment_test.go
@@ -126,7 +126,7 @@ func TestNative_EnvironmentSteps(t *testing.T) {
"BUILD_STATUS": "",
"BUILD_TITLE": "",
"BUILD_WORKSPACE": "/vela/src",
- "CI": "vela",
+ "CI": "true",
"REPOSITORY_ACTIVE": "false",
"REPOSITORY_ALLOW_COMMENT": "false",
"REPOSITORY_ALLOW_DEPLOY": "false",
@@ -156,6 +156,7 @@ func TestNative_EnvironmentSteps(t *testing.T) {
"VELA_BUILD_DISTRIBUTION": "",
"VELA_BUILD_ENQUEUED": "0",
"VELA_BUILD_EVENT": "",
+ "VELA_BUILD_EVENT_ACTION": "",
"VELA_BUILD_HOST": "",
"VELA_BUILD_LINK": "",
"VELA_BUILD_MESSAGE": "",
@@ -184,6 +185,7 @@ func TestNative_EnvironmentSteps(t *testing.T) {
"VELA_REPO_ALLOW_PUSH": "false",
"VELA_REPO_ALLOW_TAG": "false",
"VELA_REPO_BRANCH": "",
+ "VELA_REPO_TOPICS": "",
"VELA_REPO_BUILD_LIMIT": "0",
"VELA_REPO_CLONE": "",
"VELA_REPO_FULL_NAME": "",
@@ -219,8 +221,8 @@ func TestNative_EnvironmentSteps(t *testing.T) {
t.Errorf("EnvironmentSteps returned err: %v", err)
}
- if !reflect.DeepEqual(got, want) {
- t.Errorf("EnvironmentSteps is %v, want %v", got, want)
+ if diff := cmp.Diff(got, want); diff != "" {
+ t.Errorf("EnvironmentSteps mismatch (-want +got):\n%s", diff)
}
}
@@ -273,7 +275,7 @@ func TestNative_EnvironmentServices(t *testing.T) {
"BUILD_STATUS": "",
"BUILD_TITLE": "",
"BUILD_WORKSPACE": "/vela/src",
- "CI": "vela",
+ "CI": "true",
"REPOSITORY_ACTIVE": "false",
"REPOSITORY_ALLOW_COMMENT": "false",
"REPOSITORY_ALLOW_DEPLOY": "false",
@@ -303,6 +305,7 @@ func TestNative_EnvironmentServices(t *testing.T) {
"VELA_BUILD_DISTRIBUTION": "",
"VELA_BUILD_ENQUEUED": "0",
"VELA_BUILD_EVENT": "",
+ "VELA_BUILD_EVENT_ACTION": "",
"VELA_BUILD_HOST": "",
"VELA_BUILD_LINK": "",
"VELA_BUILD_MESSAGE": "",
@@ -331,6 +334,7 @@ func TestNative_EnvironmentServices(t *testing.T) {
"VELA_REPO_ALLOW_PUSH": "false",
"VELA_REPO_ALLOW_TAG": "false",
"VELA_REPO_BRANCH": "",
+ "VELA_REPO_TOPICS": "",
"VELA_REPO_BUILD_LIMIT": "0",
"VELA_REPO_CLONE": "",
"VELA_REPO_FULL_NAME": "",
@@ -366,8 +370,8 @@ func TestNative_EnvironmentServices(t *testing.T) {
t.Errorf("EnvironmentServices returned err: %v", err)
}
- if !reflect.DeepEqual(got, want) {
- t.Errorf("EnvironmentServices is %v, want %v", got, want)
+ if diff := cmp.Diff(got, want); diff != "" {
+ t.Errorf("EnvironmentServices mismatch (-want +got):\n%s", diff)
}
}
@@ -431,7 +435,7 @@ func TestNative_EnvironmentSecrets(t *testing.T) {
"BUILD_STATUS": "",
"BUILD_TITLE": "",
"BUILD_WORKSPACE": "/vela/src",
- "CI": "vela",
+ "CI": "true",
"PARAMETER_FOO": "bar",
"REPOSITORY_ACTIVE": "false",
"REPOSITORY_ALLOW_COMMENT": "false",
@@ -462,6 +466,7 @@ func TestNative_EnvironmentSecrets(t *testing.T) {
"VELA_BUILD_DISTRIBUTION": "",
"VELA_BUILD_ENQUEUED": "0",
"VELA_BUILD_EVENT": "",
+ "VELA_BUILD_EVENT_ACTION": "",
"VELA_BUILD_HOST": "",
"VELA_BUILD_LINK": "",
"VELA_BUILD_MESSAGE": "",
@@ -490,6 +495,7 @@ func TestNative_EnvironmentSecrets(t *testing.T) {
"VELA_REPO_ALLOW_PUSH": "false",
"VELA_REPO_ALLOW_TAG": "false",
"VELA_REPO_BRANCH": "",
+ "VELA_REPO_TOPICS": "",
"VELA_REPO_BUILD_LIMIT": "0",
"VELA_REPO_CLONE": "",
"VELA_REPO_FULL_NAME": "",
@@ -526,8 +532,8 @@ func TestNative_EnvironmentSecrets(t *testing.T) {
t.Errorf("EnvironmentSecrets returned err: %v", err)
}
- if !reflect.DeepEqual(got, want) {
- t.Errorf("EnvironmentSecrets is %v, want %v", got, want)
+ if diff := cmp.Diff(got, want); diff != "" {
+ t.Errorf("EnvironmentSecrets mismatch (-want +got):\n%s", diff)
}
}
@@ -538,6 +544,7 @@ func TestNative_environment(t *testing.T) {
num64 := int64(num)
str := "foo"
workspace := "/vela/src/foo/foo/foo"
+ topics := []string{"cloud", "security"}
// push
push := "push"
// tag
@@ -545,6 +552,7 @@ func TestNative_environment(t *testing.T) {
tagref := "refs/tags/1"
// pull_request
pull := "pull_request"
+ pullact := "opened"
pullref := "refs/pull/1/head"
// deployment
deploy := "deployment"
@@ -563,36 +571,36 @@ func TestNative_environment(t *testing.T) {
w: workspace,
b: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &push, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &str, BaseRef: &str},
m: &types.Metadata{Database: &types.Database{Driver: str, Host: str}, Queue: &types.Queue{Channel: str, Driver: str, Host: str}, Source: &types.Source{Driver: str, Host: str}, Vela: &types.Vela{Address: str, WebAddress: str}},
- r: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL},
+ r: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL},
u: &library.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL},
- want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "push", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "foo", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "vela", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "push", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "foo", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"},
+ want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "push", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "foo", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "push", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "foo", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"},
},
// tag
{
w: workspace,
b: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &tag, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &tagref, BaseRef: &str},
m: &types.Metadata{Database: &types.Database{Driver: str, Host: str}, Queue: &types.Queue{Channel: str, Driver: str, Host: str}, Source: &types.Source{Driver: str, Host: str}, Vela: &types.Vela{Address: str, WebAddress: str}},
- r: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL},
+ r: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL},
u: &library.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL},
- want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "tag", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/tags/1", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TAG": "1", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "vela", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "tag", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/tags/1", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TAG": "1", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"},
+ want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "tag", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/tags/1", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TAG": "1", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "tag", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/tags/1", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TAG": "1", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"},
},
// pull_request
{
w: workspace,
- b: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &pull, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str},
+ b: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &pull, EventAction: &pullact, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str},
m: &types.Metadata{Database: &types.Database{Driver: str, Host: str}, Queue: &types.Queue{Channel: str, Driver: str, Host: str}, Source: &types.Source{Driver: str, Host: str}, Vela: &types.Vela{Address: str, WebAddress: str}},
- r: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL},
+ r: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL},
u: &library.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL},
- want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "pull_request", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_PULL_REQUEST_NUMBER": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "vela", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "pull_request", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_PULL_REQUEST": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_PULL_REQUEST": "1", "VELA_PULL_REQUEST_SOURCE": "", "VELA_PULL_REQUEST_TARGET": "foo", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"},
+ want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "pull_request", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_PULL_REQUEST_NUMBER": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "pull_request", "VELA_BUILD_EVENT_ACTION": "opened", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_PULL_REQUEST": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_PULL_REQUEST": "1", "VELA_PULL_REQUEST_SOURCE": "", "VELA_PULL_REQUEST_TARGET": "foo", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"},
},
// deployment
{
w: workspace,
b: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &deploy, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &target, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str},
m: &types.Metadata{Database: &types.Database{Driver: str, Host: str}, Queue: &types.Queue{Channel: str, Driver: str, Host: str}, Source: &types.Source{Driver: str, Host: str}, Vela: &types.Vela{Address: str, WebAddress: str}},
- r: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL},
+ r: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL},
u: &library.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL},
- want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "deployment", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TARGET": "production", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "vela", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "deployment", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TARGET": "production", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DEPLOYMENT": "production", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"},
+ want: map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "deployment", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TARGET": "production", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "deployment", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TARGET": "production", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DEPLOYMENT": "production", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_BRANCH": "foo", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"},
},
}
@@ -600,8 +608,8 @@ func TestNative_environment(t *testing.T) {
for _, test := range tests {
got := environment(test.b, test.m, test.r, test.u)
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("environment is %v, want %v", got, test.want)
+ if diff := cmp.Diff(got, test.want); diff != "" {
+ t.Errorf("environment mismatch (-want +got):\n%s", diff)
}
}
}
@@ -611,6 +619,7 @@ func Test_mergeMap(t *testing.T) {
combinedMap map[string]string
loopMap map[string]string
}
+
tests := []struct {
name string
args args
@@ -632,6 +641,7 @@ func Test_mergeMap(t *testing.T) {
"VELA_TEST": "foo",
}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := appendMap(tt.args.combinedMap, tt.args.loopMap); !reflect.DeepEqual(got, tt.want) {
@@ -647,6 +657,7 @@ func Test_client_EnvironmentBuild(t *testing.T) {
num := 1
num64 := int64(num)
str := "foo"
+ topics := []string{"cloud", "security"}
//workspace := "/vela/src/foo/foo/foo"
// push
push := "push"
@@ -655,16 +666,19 @@ func Test_client_EnvironmentBuild(t *testing.T) {
tagref := "refs/tags/1"
// pull_request
pull := "pull_request"
+ pullact := "opened"
pullref := "refs/pull/1/head"
// deployment
deploy := "deployment"
target := "production"
+
type fields struct {
build *library.Build
metadata *types.Metadata
repo *library.Repo
user *library.User
}
+
tests := []struct {
name string
fields fields
@@ -673,29 +687,29 @@ func Test_client_EnvironmentBuild(t *testing.T) {
{"push", fields{
build: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &push, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &str, BaseRef: &str},
metadata: &types.Metadata{Database: &types.Database{Driver: str, Host: str}, Queue: &types.Queue{Channel: str, Driver: str, Host: str}, Source: &types.Source{Driver: str, Host: str}, Vela: &types.Vela{Address: str, WebAddress: str}},
- repo: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL},
+ repo: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL},
user: &library.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL},
- }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "push", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "foo", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "vela", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "push", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "foo", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}},
+ }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "push", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "foo", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "push", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "foo", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"}},
{"tag", fields{
build: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &tag, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &tagref, BaseRef: &str},
metadata: &types.Metadata{Database: &types.Database{Driver: str, Host: str}, Queue: &types.Queue{Channel: str, Driver: str, Host: str}, Source: &types.Source{Driver: str, Host: str}, Vela: &types.Vela{Address: str, WebAddress: str}},
- repo: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL},
+ repo: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL},
user: &library.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL},
- }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "tag", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/tags/1", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TAG": "1", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "vela", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "tag", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/tags/1", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TAG": "1", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"},
+ }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "tag", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/tags/1", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TAG": "1", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "tag", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/tags/1", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TAG": "1", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"},
},
{"pull_request", fields{
- build: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &pull, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str},
+ build: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &pull, EventAction: &pullact, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &str, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str},
metadata: &types.Metadata{Database: &types.Database{Driver: str, Host: str}, Queue: &types.Queue{Channel: str, Driver: str, Host: str}, Source: &types.Source{Driver: str, Host: str}, Vela: &types.Vela{Address: str, WebAddress: str}},
- repo: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL},
+ repo: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL},
user: &library.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL},
- }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "pull_request", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_PULL_REQUEST_NUMBER": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "vela", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "pull_request", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_PULL_REQUEST": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_PULL_REQUEST": "1", "VELA_PULL_REQUEST_SOURCE": "", "VELA_PULL_REQUEST_TARGET": "foo", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"},
+ }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "pull_request", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_PULL_REQUEST_NUMBER": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "pull_request", "VELA_BUILD_EVENT_ACTION": "opened", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_PULL_REQUEST": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_PULL_REQUEST": "1", "VELA_PULL_REQUEST_SOURCE": "", "VELA_PULL_REQUEST_TARGET": "foo", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"},
},
{"deployment", fields{
build: &library.Build{ID: &num64, RepoID: &num64, Number: &num, Parent: &num, Event: &deploy, Status: &str, Error: &str, Enqueued: &num64, Created: &num64, Started: &num64, Finished: &num64, Deploy: &target, Clone: &str, Source: &str, Title: &str, Message: &str, Commit: &str, Sender: &str, Author: &str, Branch: &str, Ref: &pullref, BaseRef: &str},
metadata: &types.Metadata{Database: &types.Database{Driver: str, Host: str}, Queue: &types.Queue{Channel: str, Driver: str, Host: str}, Source: &types.Source{Driver: str, Host: str}, Vela: &types.Vela{Address: str, WebAddress: str}},
- repo: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL},
+ repo: &library.Repo{ID: &num64, UserID: &num64, Org: &str, Name: &str, FullName: &str, Link: &str, Clone: &str, Branch: &str, Topics: &topics, BuildLimit: &num64, Timeout: &num64, Visibility: &str, Private: &booL, Trusted: &booL, Active: &booL, AllowPull: &booL, AllowPush: &booL, AllowDeploy: &booL, AllowTag: &booL, AllowComment: &booL},
user: &library.User{ID: &num64, Name: &str, Token: &str, Active: &booL, Admin: &booL},
- }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "deployment", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TARGET": "production", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "vela", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "deployment", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TARGET": "production", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DEPLOYMENT": "production", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"},
+ }, map[string]string{"BUILD_AUTHOR": "foo", "BUILD_AUTHOR_EMAIL": "", "BUILD_BASE_REF": "foo", "BUILD_BRANCH": "foo", "BUILD_CHANNEL": "foo", "BUILD_CLONE": "foo", "BUILD_COMMIT": "foo", "BUILD_CREATED": "1", "BUILD_ENQUEUED": "1", "BUILD_EVENT": "deployment", "BUILD_HOST": "", "BUILD_LINK": "", "BUILD_MESSAGE": "foo", "BUILD_NUMBER": "1", "BUILD_PARENT": "1", "BUILD_REF": "refs/pull/1/head", "BUILD_SENDER": "foo", "BUILD_SOURCE": "foo", "BUILD_STARTED": "1", "BUILD_STATUS": "foo", "BUILD_TARGET": "production", "BUILD_TITLE": "foo", "BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "CI": "true", "REPOSITORY_ACTIVE": "false", "REPOSITORY_ALLOW_COMMENT": "false", "REPOSITORY_ALLOW_DEPLOY": "false", "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "false", "REPOSITORY_ALLOW_TAG": "false", "REPOSITORY_BRANCH": "foo", "REPOSITORY_CLONE": "foo", "REPOSITORY_FULL_NAME": "foo", "REPOSITORY_LINK": "foo", "REPOSITORY_NAME": "foo", "REPOSITORY_ORG": "foo", "REPOSITORY_PRIVATE": "false", "REPOSITORY_TIMEOUT": "1", "REPOSITORY_TRUSTED": "false", "REPOSITORY_VISIBILITY": "foo", "VELA": "true", "VELA_ADDR": "foo", "VELA_BUILD_AUTHOR": "foo", "VELA_BUILD_AUTHOR_EMAIL": "", "VELA_BUILD_BASE_REF": "foo", "VELA_BUILD_BRANCH": "foo", "VELA_BUILD_CHANNEL": "foo", "VELA_BUILD_CLONE": "foo", "VELA_BUILD_COMMIT": "foo", "VELA_BUILD_CREATED": "1", "VELA_BUILD_DISTRIBUTION": "", "VELA_BUILD_ENQUEUED": "1", "VELA_BUILD_EVENT": "deployment", "VELA_BUILD_EVENT_ACTION": "", "VELA_BUILD_HOST": "", "VELA_BUILD_LINK": "", "VELA_BUILD_MESSAGE": "foo", "VELA_BUILD_NUMBER": "1", "VELA_BUILD_PARENT": "1", "VELA_BUILD_REF": "refs/pull/1/head", "VELA_BUILD_RUNTIME": "", "VELA_BUILD_SENDER": "foo", "VELA_BUILD_SOURCE": "foo", "VELA_BUILD_STARTED": "1", "VELA_BUILD_STATUS": "foo", "VELA_BUILD_TARGET": "production", "VELA_BUILD_TITLE": "foo", "VELA_BUILD_WORKSPACE": "/vela/src/foo/foo/foo", "VELA_CHANNEL": "foo", "VELA_DATABASE": "foo", "VELA_DEPLOYMENT": "production", "VELA_DISTRIBUTION": "TODO", "VELA_HOST": "foo", "VELA_NETRC_MACHINE": "foo", "VELA_NETRC_PASSWORD": "foo", "VELA_NETRC_USERNAME": "x-oauth-basic", "VELA_QUEUE": "foo", "VELA_REPO_ACTIVE": "false", "VELA_REPO_ALLOW_COMMENT": "false", "VELA_REPO_ALLOW_DEPLOY": "false", "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "false", "VELA_REPO_ALLOW_TAG": "false", "VELA_REPO_BRANCH": "foo", "VELA_REPO_BUILD_LIMIT": "1", "VELA_REPO_CLONE": "foo", "VELA_REPO_FULL_NAME": "foo", "VELA_REPO_LINK": "foo", "VELA_REPO_NAME": "foo", "VELA_REPO_ORG": "foo", "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_PRIVATE": "false", "VELA_REPO_TIMEOUT": "1", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_TRUSTED": "false", "VELA_REPO_VISIBILITY": "foo", "VELA_RUNTIME": "TODO", "VELA_SOURCE": "foo", "VELA_USER_ACTIVE": "false", "VELA_USER_ADMIN": "false", "VELA_USER_FAVORITES": "[]", "VELA_USER_NAME": "foo", "VELA_VERSION": "TODO", "VELA_WORKSPACE": "/vela/src/foo/foo/foo"},
},
}
for _, tt := range tests {
@@ -706,8 +720,9 @@ func Test_client_EnvironmentBuild(t *testing.T) {
repo: tt.fields.repo,
user: tt.fields.user,
}
- if got := c.EnvironmentBuild(); !reflect.DeepEqual(got, tt.want) {
- t.Errorf("EnvironmentBuild() = %v, want %v", got, tt.want)
+ got := c.EnvironmentBuild()
+ if diff := cmp.Diff(got, tt.want); diff != "" {
+ t.Errorf("EnvironmentBuild mismatch (-want +got):\n%s", diff)
}
})
}
diff --git a/compiler/native/expand.go b/compiler/native/expand.go
index 0b68c5f02..d593bc26e 100644
--- a/compiler/native/expand.go
+++ b/compiler/native/expand.go
@@ -8,6 +8,10 @@ import (
"fmt"
"strings"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/pipeline"
+
+ "github.com/go-vela/server/compiler/registry"
"github.com/go-vela/server/compiler/template/native"
"github.com/go-vela/server/compiler/template/starlark"
"github.com/spf13/afero"
@@ -19,44 +23,53 @@ import (
// ExpandStages injects the template for each
// templated step in every stage in a yaml configuration.
-//
-// nolint: lll // ignore long line length due to variable names
-func (c *client) ExpandStages(s *yaml.Build, tmpls map[string]*yaml.Template) (yaml.StageSlice, yaml.SecretSlice, yaml.ServiceSlice, raw.StringSliceMap, error) {
+func (c *client) ExpandStages(s *yaml.Build, tmpls map[string]*yaml.Template, r *pipeline.RuleData) (*yaml.Build, error) {
+ if len(tmpls) == 0 {
+ return s, nil
+ }
+
// iterate through all stages
for _, stage := range s.Stages {
// inject the templates into the steps for the stage
- steps, secrets, services, environment, err := c.ExpandSteps(&yaml.Build{Steps: stage.Steps, Secrets: s.Secrets, Services: s.Services, Environment: s.Environment}, tmpls)
+ p, err := c.ExpandSteps(&yaml.Build{Steps: stage.Steps, Secrets: s.Secrets, Services: s.Services, Environment: s.Environment}, tmpls, r, c.TemplateDepth)
if err != nil {
- return nil, nil, nil, nil, err
+ return nil, err
}
- stage.Steps = steps
- s.Secrets = secrets
- s.Services = services
- s.Environment = environment
+ stage.Steps = p.Steps
+ s.Secrets = p.Secrets
+ s.Services = p.Services
+ s.Environment = p.Environment
}
- return s.Stages, s.Secrets, s.Services, s.Environment, nil
+ return s, nil
}
// ExpandSteps injects the template for each
// templated step in a yaml configuration.
-//
-// nolint: lll,funlen,gocyclo // ignore long line length due to variable names
-func (c *client) ExpandSteps(s *yaml.Build, tmpls map[string]*yaml.Template) (yaml.StepSlice, yaml.SecretSlice, yaml.ServiceSlice, raw.StringSliceMap, error) {
+func (c *client) ExpandSteps(s *yaml.Build, tmpls map[string]*yaml.Template, r *pipeline.RuleData, depth int) (*yaml.Build, error) {
+ if len(tmpls) == 0 {
+ return s, nil
+ }
+
+ // return if max template depth has been reached
+ if depth == 0 {
+ retErr := fmt.Errorf("max template depth of %d exceeded", c.TemplateDepth)
+
+ return s, retErr
+ }
+
steps := yaml.StepSlice{}
secrets := s.Secrets
services := s.Services
environment := s.Environment
+
if len(environment) == 0 {
environment = make(raw.StringSliceMap)
}
// iterate through each step
for _, step := range s.Steps {
- // nolint: ineffassign,staticcheck // ignore ineffectual assignment
- bytes := []byte{}
-
// skip if no template is provided for the step
if len(step.Template.Name) == 0 {
// add existing step if no template
@@ -67,7 +80,23 @@ func (c *client) ExpandSteps(s *yaml.Build, tmpls map[string]*yaml.Template) (ya
// lookup step template name
tmpl, ok := tmpls[step.Template.Name]
if !ok {
- return yaml.StepSlice{}, yaml.SecretSlice{}, yaml.ServiceSlice{}, raw.StringSliceMap{}, fmt.Errorf("missing template source for template %s in pipeline for step %s", step.Template.Name, step.Name)
+ return s, fmt.Errorf("missing template source for template %s in pipeline for step %s", step.Template.Name, step.Name)
+ }
+
+ // if ruledata is nil (CompileLite), continue with expansion
+ if r != nil {
+ // form a one-step pipeline to prep for purge check
+ check := &yaml.StepSlice{step}
+ pipeline := &pipeline.Build{
+ Steps: *check.ToPipeline(),
+ }
+
+ pipeline = pipeline.Purge(r)
+
+ // if step purged, do not proceed with expansion
+ if len(pipeline.Steps) == 0 {
+ continue
+ }
}
// Create some default global environment inject vars
@@ -82,83 +111,34 @@ func (c *client) ExpandSteps(s *yaml.Build, tmpls map[string]*yaml.Template) (ya
// inject environment information for template
step, err := c.EnvironmentStep(step, envGlobalSteps)
if err != nil {
- return yaml.StepSlice{}, yaml.SecretSlice{}, yaml.ServiceSlice{}, raw.StringSliceMap{}, err
+ return s, err
}
- switch {
- case c.local:
- a := &afero.Afero{
- Fs: afero.NewOsFs(),
- }
-
- bytes, err = a.ReadFile(tmpl.Source)
- if err != nil {
- return yaml.StepSlice{}, yaml.SecretSlice{}, yaml.ServiceSlice{}, raw.StringSliceMap{}, err
- }
-
- case strings.EqualFold(tmpl.Type, "github"):
- // parse source from template
- src, err := c.Github.Parse(tmpl.Source)
- if err != nil {
- return yaml.StepSlice{}, yaml.SecretSlice{}, yaml.ServiceSlice{}, raw.StringSliceMap{}, fmt.Errorf("invalid template source provided for %s: %v", step.Template.Name, err)
- }
-
- // pull from github without auth when the host isn't provided or is set to github.com
- if !c.UsePrivateGithub && (len(src.Host) == 0 || strings.Contains(src.Host, "github.com")) {
- logrus.WithFields(logrus.Fields{
- "org": src.Org,
- "repo": src.Repo,
- "path": src.Name,
- "host": src.Host,
- }).Tracef("Using GitHub client to pull template")
- bytes, err = c.Github.Template(nil, src)
- if err != nil {
- return yaml.StepSlice{}, yaml.SecretSlice{}, yaml.ServiceSlice{}, raw.StringSliceMap{}, err
- }
- } else {
- logrus.WithFields(logrus.Fields{
- "org": src.Org,
- "repo": src.Repo,
- "path": src.Name,
- "host": src.Host,
- }).Tracef("Using authenticated GitHub client to pull template")
- // use private (authenticated) github instance to pull from
- bytes, err = c.PrivateGithub.Template(c.user, src)
- if err != nil {
- return yaml.StepSlice{}, yaml.SecretSlice{}, yaml.ServiceSlice{}, raw.StringSliceMap{}, err
- }
- }
-
- default:
- logrus.Errorf("Unsupported template type: %v", tmpl.Type)
- continue
+ bytes, err := c.getTemplate(tmpl, step.Template.Name)
+ if err != nil {
+ return s, err
}
- var tmplSteps yaml.StepSlice
- var tmplSecrets yaml.SecretSlice
- var tmplServices yaml.ServiceSlice
- var tmplEnvironment raw.StringSliceMap
+ tmplBuild, err := c.mergeTemplate(bytes, tmpl, step)
+ if err != nil {
+ return s, err
+ }
- // TODO: provide friendlier error messages with file type mismatches
- switch tmpl.Format {
- case "go", "golang", "":
- // render template for steps
- tmplSteps, tmplSecrets, tmplServices, tmplEnvironment, err = native.RenderStep(string(bytes), step)
- if err != nil {
- return yaml.StepSlice{}, yaml.SecretSlice{}, yaml.ServiceSlice{}, raw.StringSliceMap{}, err
+ // if template references other templates, expand again
+ if len(tmplBuild.Templates) != 0 {
+ // if the tmplBuild has render_inline but the parent build does not, abort
+ if tmplBuild.Metadata.RenderInline && !s.Metadata.RenderInline {
+ return s, fmt.Errorf("cannot use render_inline inside a called template (%s)", step.Template.Name)
}
- case "starlark":
- // render template for steps
- tmplSteps, tmplSecrets, tmplServices, tmplEnvironment, err = starlark.RenderStep(string(bytes), step)
+
+ tmplBuild, err = c.ExpandSteps(tmplBuild, mapFromTemplates(tmplBuild.Templates), r, depth-1)
if err != nil {
- return yaml.StepSlice{}, yaml.SecretSlice{}, yaml.ServiceSlice{}, raw.StringSliceMap{}, err
+ return s, err
}
- default:
- return yaml.StepSlice{}, yaml.SecretSlice{}, yaml.ServiceSlice{}, raw.StringSliceMap{}, fmt.Errorf("format of %s is unsupported", tmpl.Format)
}
// loop over secrets within template
- for _, secret := range tmplSecrets {
+ for _, secret := range tmplBuild.Secrets {
found := false
// loop over secrets within base configuration
for _, sec := range secrets {
@@ -175,8 +155,9 @@ func (c *client) ExpandSteps(s *yaml.Build, tmpls map[string]*yaml.Template) (ya
}
// loop over services within template
- for _, service := range tmplServices {
+ for _, service := range tmplBuild.Services {
found := false
+
for _, serv := range services {
if serv.Name == service.Name {
found = true
@@ -190,8 +171,9 @@ func (c *client) ExpandSteps(s *yaml.Build, tmpls map[string]*yaml.Template) (ya
}
// loop over environment within template
- for key, value := range tmplEnvironment {
+ for key, value := range tmplBuild.Environment {
found := false
+
for env := range environment {
if key == env {
found = true
@@ -205,10 +187,137 @@ func (c *client) ExpandSteps(s *yaml.Build, tmpls map[string]*yaml.Template) (ya
}
// add templated steps
- steps = append(steps, tmplSteps...)
+ steps = append(steps, tmplBuild.Steps...)
}
- return steps, secrets, services, environment, nil
+ s.Steps = steps
+ s.Secrets = secrets
+ s.Services = services
+ s.Environment = environment
+
+ return s, nil
+}
+
+func (c *client) getTemplate(tmpl *yaml.Template, name string) ([]byte, error) {
+ var (
+ bytes []byte
+ err error
+ )
+
+ switch {
+ case c.local:
+ // iterate over locally provided templates
+ for _, t := range c.localTemplates {
+ parts := strings.Split(t, ":")
+ if len(parts) != 2 {
+ return nil, fmt.Errorf("local templates must be provided in the form :, got %s", t)
+ }
+
+ if strings.EqualFold(tmpl.Name, parts[0]) {
+ a := &afero.Afero{
+ Fs: afero.NewOsFs(),
+ }
+
+ bytes, err = a.ReadFile(parts[1])
+ if err != nil {
+ return bytes, err
+ }
+
+ return bytes, nil
+ }
+ }
+
+ // no template found in provided templates, exit with error
+ return nil, fmt.Errorf("unable to find template %s: not supplied in list %s", tmpl.Name, c.localTemplates)
+
+ case strings.EqualFold(tmpl.Type, "github"):
+ // parse source from template
+ src, err := c.Github.Parse(tmpl.Source)
+ if err != nil {
+ return bytes, fmt.Errorf("invalid template source provided for %s: %w", name, err)
+ }
+
+ // pull from github without auth when the host isn't provided or is set to github.com
+ if !c.UsePrivateGithub && (len(src.Host) == 0 || strings.Contains(src.Host, "github.com")) {
+ logrus.WithFields(logrus.Fields{
+ "org": src.Org,
+ "repo": src.Repo,
+ "path": src.Name,
+ "host": src.Host,
+ }).Tracef("Using GitHub client to pull template")
+
+ bytes, err = c.Github.Template(nil, src)
+ if err != nil {
+ return bytes, err
+ }
+ } else {
+ logrus.WithFields(logrus.Fields{
+ "org": src.Org,
+ "repo": src.Repo,
+ "path": src.Name,
+ "host": src.Host,
+ }).Tracef("Using authenticated GitHub client to pull template")
+
+ // use private (authenticated) github instance to pull from
+ bytes, err = c.PrivateGithub.Template(c.user, src)
+ if err != nil {
+ return bytes, err
+ }
+ }
+
+ case strings.EqualFold(tmpl.Type, "file"):
+ src := ®istry.Source{
+ Org: c.repo.GetOrg(),
+ Repo: c.repo.GetName(),
+ Name: tmpl.Source,
+ Ref: c.commit,
+ }
+
+ if !c.UsePrivateGithub {
+ logrus.WithFields(logrus.Fields{
+ "org": src.Org,
+ "repo": src.Repo,
+ "path": src.Name,
+ }).Tracef("Using GitHub client to pull template")
+
+ bytes, err = c.Github.Template(nil, src)
+ if err != nil {
+ return bytes, err
+ }
+ } else {
+ logrus.WithFields(logrus.Fields{
+ "org": src.Org,
+ "repo": src.Repo,
+ "path": src.Name,
+ }).Tracef("Using authenticated GitHub client to pull template")
+
+ // use private (authenticated) github instance to pull from
+ bytes, err = c.PrivateGithub.Template(c.user, src)
+ if err != nil {
+ return bytes, err
+ }
+ }
+
+ default:
+ return bytes, fmt.Errorf("unsupported template type: %v", tmpl.Type)
+ }
+
+ return bytes, nil
+}
+
+//nolint:lll // ignore long line length due to input arguments
+func (c *client) mergeTemplate(bytes []byte, tmpl *yaml.Template, step *yaml.Step) (*yaml.Build, error) {
+ switch tmpl.Format {
+ case constants.PipelineTypeGo, "golang", "":
+ //nolint:lll // ignore long line length due to return
+ return native.Render(string(bytes), step.Name, step.Template.Name, step.Environment, step.Template.Variables)
+ case constants.PipelineTypeStarlark:
+ //nolint:lll // ignore long line length due to return
+ return starlark.Render(string(bytes), step.Name, step.Template.Name, step.Environment, step.Template.Variables)
+ default:
+ //nolint:lll // ignore long line length due to return
+ return &yaml.Build{}, fmt.Errorf("format of %s is unsupported", tmpl.Format)
+ }
}
// helper function that creates a map of templates from a yaml configuration.
diff --git a/compiler/native/expand_test.go b/compiler/native/expand_test.go
index 5dae3f99a..d2a8a92b6 100644
--- a/compiler/native/expand_test.go
+++ b/compiler/native/expand_test.go
@@ -11,6 +11,8 @@ import (
"reflect"
"testing"
+ "github.com/go-vela/types/library"
+ "github.com/go-vela/types/pipeline"
"github.com/go-vela/types/raw"
"github.com/go-vela/types/yaml"
"github.com/google/go-cmp/cmp"
@@ -27,10 +29,12 @@ func TestNative_ExpandStages(t *testing.T) {
_, engine := gin.CreateTestContext(resp)
// setup mock server
- engine.GET("/api/v3/repos/foo/bar/contents/:path", func(c *gin.Context) {
- c.Header("Content-Type", "application/json")
- c.Status(http.StatusOK)
- c.File("testdata/template.json")
+ engine.GET("/api/v3/repos/:org/:repo/contents/:path", func(c *gin.Context) {
+ body, err := convertFileToGithubResponse(c.Param("path"))
+ if err != nil {
+ t.Error(err)
+ }
+ c.JSON(http.StatusOK, body)
})
s := httptest.NewServer(engine)
@@ -41,12 +45,13 @@ func TestNative_ExpandStages(t *testing.T) {
set.Bool("github-driver", true, "doc")
set.String("github-url", s.URL, "doc")
set.String("github-token", "", "doc")
+ set.Int("max-template-depth", 5, "doc")
c := cli.NewContext(nil, set, nil)
tmpls := map[string]*yaml.Template{
"gradle": {
Name: "gradle",
- Source: "github.example.com/foo/bar/template.yml",
+ Source: "github.example.com/foo/bar/long_template.yml",
Type: "github",
},
}
@@ -144,23 +149,24 @@ func TestNative_ExpandStages(t *testing.T) {
t.Errorf("Creating new compiler returned err: %v", err)
}
- stages, secrets, services, environment, err := compiler.ExpandStages(&yaml.Build{Stages: stages, Services: yaml.ServiceSlice{}, Environment: raw.StringSliceMap{}}, tmpls)
+ build, err := compiler.ExpandStages(&yaml.Build{Stages: stages, Services: yaml.ServiceSlice{}, Environment: raw.StringSliceMap{}}, tmpls, new(pipeline.RuleData))
if err != nil {
t.Errorf("ExpandStages returned err: %v", err)
}
- if diff := cmp.Diff(stages, wantStages); diff != "" {
+ if diff := cmp.Diff(build.Stages, wantStages); diff != "" {
t.Errorf("ExpandStages() mismatch (-want +got):\n%s", diff)
}
- if diff := cmp.Diff(secrets, wantSecrets); diff != "" {
+ if diff := cmp.Diff(build.Secrets, wantSecrets); diff != "" {
t.Errorf("ExpandStages() mismatch (-want +got):\n%s", diff)
}
- if diff := cmp.Diff(services, wantServices); diff != "" {
+ if diff := cmp.Diff(build.Services, wantServices); diff != "" {
t.Errorf("ExpandStages() mismatch (-want +got):\n%s", diff)
}
- if diff := cmp.Diff(environment, wantEnvironment); diff != "" {
+
+ if diff := cmp.Diff(build.Environment, wantEnvironment); diff != "" {
t.Errorf("ExpandStages() mismatch (-want +got):\n%s", diff)
}
}
@@ -173,10 +179,12 @@ func TestNative_ExpandSteps(t *testing.T) {
_, engine := gin.CreateTestContext(resp)
// setup mock server
- engine.GET("/api/v3/repos/foo/bar/contents/:path", func(c *gin.Context) {
- c.Header("Content-Type", "application/json")
- c.Status(http.StatusOK)
- c.File("testdata/template.json")
+ engine.GET("/api/v3/repos/:org/:repo/contents/:path", func(c *gin.Context) {
+ body, err := convertFileToGithubResponse(c.Param("path"))
+ if err != nil {
+ t.Error(err)
+ }
+ c.JSON(http.StatusOK, body)
})
s := httptest.NewServer(engine)
@@ -187,13 +195,38 @@ func TestNative_ExpandSteps(t *testing.T) {
set.Bool("github-driver", true, "doc")
set.String("github-url", s.URL, "doc")
set.String("github-token", "", "doc")
+ set.Int("max-template-depth", 5, "doc")
c := cli.NewContext(nil, set, nil)
- tmpls := map[string]*yaml.Template{
- "gradle": {
- Name: "gradle",
- Source: "github.example.com/foo/bar/template.yml",
- Type: "github",
+ testRepo := new(library.Repo)
+
+ testRepo.SetID(1)
+ testRepo.SetOrg("foo")
+ testRepo.SetName("bar")
+
+ tests := []struct {
+ name string
+ tmpls map[string]*yaml.Template
+ }{
+ {
+ name: "GitHub",
+ tmpls: map[string]*yaml.Template{
+ "gradle": {
+ Name: "gradle",
+ Source: "github.example.com/foo/bar/long_template.yml",
+ Type: "github",
+ },
+ },
+ },
+ {
+ name: "File",
+ tmpls: map[string]*yaml.Template{
+ "gradle": {
+ Name: "gradle",
+ Source: "long_template.yml",
+ Type: "file",
+ },
+ },
},
}
@@ -286,25 +319,31 @@ func TestNative_ExpandSteps(t *testing.T) {
t.Errorf("Creating new compiler returned err: %v", err)
}
- steps, secrets, services, environment, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Services: yaml.ServiceSlice{}, Environment: globalEnvironment}, tmpls)
- if err != nil {
- t.Errorf("ExpandSteps returned err: %v", err)
- }
+ compiler.WithCommit("123abc456def").WithRepo(testRepo)
- if diff := cmp.Diff(steps, wantSteps); diff != "" {
- t.Errorf("ExpandSteps() mismatch (-want +got):\n%s", diff)
- }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ build, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Services: yaml.ServiceSlice{}, Environment: globalEnvironment}, test.tmpls, new(pipeline.RuleData), compiler.TemplateDepth)
+ if err != nil {
+ t.Errorf("ExpandSteps_Type%s returned err: %v", test.name, err)
+ }
- if diff := cmp.Diff(secrets, wantSecrets); diff != "" {
- t.Errorf("ExpandSteps() mismatch (-want +got):\n%s", diff)
- }
+ if diff := cmp.Diff(build.Steps, wantSteps); diff != "" {
+ t.Errorf("ExpandSteps()_Type%s mismatch (-want +got):\n%s", test.name, diff)
+ }
- if diff := cmp.Diff(services, wantServices); diff != "" {
- t.Errorf("ExpandSteps() mismatch (-want +got):\n%s", diff)
- }
+ if diff := cmp.Diff(build.Secrets, wantSecrets); diff != "" {
+ t.Errorf("ExpandSteps()_Type%s mismatch (-want +got):\n%s", test.name, diff)
+ }
- if diff := cmp.Diff(environment, wantEnvironment); diff != "" {
- t.Errorf("ExpandSteps() mismatch (-want +got):\n%s", diff)
+ if diff := cmp.Diff(build.Services, wantServices); diff != "" {
+ t.Errorf("ExpandSteps()_Type%s mismatch (-want +got):\n%s", test.name, diff)
+ }
+
+ if diff := cmp.Diff(build.Environment, wantEnvironment); diff != "" {
+ t.Errorf("ExpandSteps()_Type%s mismatch (-want +got):\n%s", test.name, diff)
+ }
+ })
}
}
@@ -316,15 +355,12 @@ func TestNative_ExpandStepsMulti(t *testing.T) {
_, engine := gin.CreateTestContext(resp)
// setup mock server
- engine.GET("/api/v3/repos/foo/bar/contents/:path", func(c *gin.Context) {
- c.Header("Content-Type", "application/json")
- c.Status(http.StatusOK)
- c.File("testdata/template-gradle.json")
- })
- engine.GET("/api/v3/repos/bar/foo/contents/:path", func(c *gin.Context) {
- c.Header("Content-Type", "application/json")
- c.Status(http.StatusOK)
- c.File("testdata/template-maven.json")
+ engine.GET("/api/v3/repos/:org/:repo/contents/:path", func(c *gin.Context) {
+ body, err := convertFileToGithubResponse(c.Param("path"))
+ if err != nil {
+ t.Error(err)
+ }
+ c.JSON(http.StatusOK, body)
})
s := httptest.NewServer(engine)
@@ -335,6 +371,7 @@ func TestNative_ExpandStepsMulti(t *testing.T) {
set.Bool("github-driver", true, "doc")
set.String("github-url", s.URL, "doc")
set.String("github-token", "", "doc")
+ set.Int("max-template-depth", 5, "doc")
c := cli.NewContext(nil, set, nil)
tmpls := map[string]*yaml.Template{
@@ -348,6 +385,11 @@ func TestNative_ExpandStepsMulti(t *testing.T) {
Source: "github.example.com/bar/foo/maven.yml",
Type: "github",
},
+ "npm": {
+ Name: "npm",
+ Source: "github.example.com/foo/bar/gradle.yml",
+ Type: "github",
+ },
}
steps := yaml.StepSlice{
@@ -372,6 +414,27 @@ func TestNative_ExpandStepsMulti(t *testing.T) {
"pull_policy": "pull: true",
},
},
+ Ruleset: yaml.Ruleset{
+ If: yaml.Rules{
+ Branch: []string{"main"},
+ },
+ },
+ },
+ &yaml.Step{
+ Name: "sample",
+ Template: yaml.StepTemplate{
+ Name: "npm",
+ Variables: map[string]interface{}{
+ "image": "openjdk:latest",
+ "environment": "{ GRADLE_USER_HOME: .gradle, GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false }",
+ "pull_policy": "pull: true",
+ },
+ },
+ Ruleset: yaml.Ruleset{
+ If: yaml.Rules{
+ Branch: []string{"dev"},
+ },
+ },
},
}
@@ -476,7 +539,7 @@ func TestNative_ExpandStepsMulti(t *testing.T) {
"auth_method": "token",
"username": "octocat",
"items": []interface{}{
- map[interface{}]interface{}{string("path"): string("docker"), string("source"): string("secret/docker")},
+ map[interface{}]interface{}{"path": "docker", "source": "secret/docker"},
},
},
},
@@ -497,7 +560,7 @@ func TestNative_ExpandStepsMulti(t *testing.T) {
"auth_method": "token",
"username": "octocat",
"items": []interface{}{
- map[interface{}]interface{}{string("path"): string("docker"), string("source"): string("secret/docker")},
+ map[interface{}]interface{}{"path": "docker", "source": "secret/docker"},
},
},
},
@@ -520,24 +583,27 @@ func TestNative_ExpandStepsMulti(t *testing.T) {
t.Errorf("Creating new compiler returned err: %v", err)
}
- steps, secrets, services, environment, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Services: yaml.ServiceSlice{}, Environment: raw.StringSliceMap{}}, tmpls)
+ ruledata := new(pipeline.RuleData)
+ ruledata.Branch = "main"
+
+ build, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Services: yaml.ServiceSlice{}, Environment: raw.StringSliceMap{}}, tmpls, ruledata, compiler.TemplateDepth)
if err != nil {
t.Errorf("ExpandSteps returned err: %v", err)
}
- if diff := cmp.Diff(steps, wantSteps); diff != "" {
+ if diff := cmp.Diff(build.Steps, wantSteps); diff != "" {
t.Errorf("ExpandSteps() mismatch (-want +got):\n%s", diff)
}
- if diff := cmp.Diff(secrets, wantSecrets); diff != "" {
+ if diff := cmp.Diff(build.Secrets, wantSecrets); diff != "" {
t.Errorf("ExpandSteps() mismatch (-want +got):\n%s", diff)
}
- if diff := cmp.Diff(services, wantServices); diff != "" {
+ if diff := cmp.Diff(build.Services, wantServices); diff != "" {
t.Errorf("ExpandSteps() mismatch (-want +got):\n%s", diff)
}
- if diff := cmp.Diff(environment, wantEnvironment); diff != "" {
+ if diff := cmp.Diff(build.Environment, wantEnvironment); diff != "" {
t.Errorf("ExpandSteps() mismatch (-want +got):\n%s", diff)
}
}
@@ -550,10 +616,12 @@ func TestNative_ExpandStepsStarlark(t *testing.T) {
_, engine := gin.CreateTestContext(resp)
// setup mock server
- engine.GET("/api/v3/repos/foo/bar/contents/:path", func(c *gin.Context) {
- c.Header("Content-Type", "application/json")
- c.Status(http.StatusOK)
- c.File("testdata/template-starlark.json")
+ engine.GET("/api/v3/repos/:org/:repo/contents/:path", func(c *gin.Context) {
+ body, err := convertFileToGithubResponse(c.Param("path"))
+ if err != nil {
+ t.Error(err)
+ }
+ c.JSON(http.StatusOK, body)
})
s := httptest.NewServer(engine)
@@ -564,6 +632,7 @@ func TestNative_ExpandStepsStarlark(t *testing.T) {
set.Bool("github-driver", true, "doc")
set.String("github-url", s.URL, "doc")
set.String("github-token", "", "doc")
+ set.Int("max-template-depth", 5, "doc")
c := cli.NewContext(nil, set, nil)
tmpls := map[string]*yaml.Template{
@@ -607,28 +676,366 @@ func TestNative_ExpandStepsStarlark(t *testing.T) {
t.Errorf("Creating new compiler returned err: %v", err)
}
- steps, secrets, services, environment, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Secrets: yaml.SecretSlice{}, Services: yaml.ServiceSlice{}, Environment: raw.StringSliceMap{}}, tmpls)
+ build, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Secrets: yaml.SecretSlice{}, Services: yaml.ServiceSlice{}, Environment: raw.StringSliceMap{}}, tmpls, new(pipeline.RuleData), compiler.TemplateDepth)
if err != nil {
t.Errorf("ExpandSteps returned err: %v", err)
}
- if diff := cmp.Diff(steps, wantSteps); diff != "" {
+ if diff := cmp.Diff(build.Steps, wantSteps); diff != "" {
t.Errorf("ExpandSteps() mismatch (-want +got):\n%s", diff)
}
- if diff := cmp.Diff(secrets, wantSecrets); diff != "" {
+ if diff := cmp.Diff(build.Secrets, wantSecrets); diff != "" {
t.Errorf("ExpandSteps() mismatch (-want +got):\n%s", diff)
}
- if diff := cmp.Diff(services, wantServices); diff != "" {
+ if diff := cmp.Diff(build.Services, wantServices); diff != "" {
t.Errorf("ExpandSteps() mismatch (-want +got):\n%s", diff)
}
- if diff := cmp.Diff(environment, wantEnvironment); diff != "" {
+ if diff := cmp.Diff(build.Environment, wantEnvironment); diff != "" {
t.Errorf("ExpandSteps() mismatch (-want +got):\n%s", diff)
}
}
+func TestNative_ExpandSteps_TemplateCallTemplate(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ _, engine := gin.CreateTestContext(resp)
+
+ // setup mock server
+ engine.GET("/api/v3/repos/:org/:repo/contents/:path", func(c *gin.Context) {
+ body, err := convertFileToGithubResponse(c.Param("path"))
+ if err != nil {
+ t.Error(err)
+ }
+ c.JSON(http.StatusOK, body)
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup types
+ set := flag.NewFlagSet("test", 0)
+ set.Bool("github-driver", true, "doc")
+ set.String("github-url", s.URL, "doc")
+ set.String("github-token", "", "doc")
+ set.Int("max-template-depth", 5, "doc")
+ c := cli.NewContext(nil, set, nil)
+
+ testBuild := new(library.Build)
+
+ testBuild.SetID(1)
+ testBuild.SetCommit("123abc456def")
+
+ testRepo := new(library.Repo)
+
+ testRepo.SetID(1)
+ testRepo.SetOrg("foo")
+ testRepo.SetName("bar")
+
+ tests := []struct {
+ name string
+ tmpls map[string]*yaml.Template
+ }{
+ {
+ name: "Test 1",
+ tmpls: map[string]*yaml.Template{
+ "chain": {
+ Name: "chain",
+ Source: "github.example.com/faz/baz/template_calls_template.yml",
+ Type: "github",
+ },
+ },
+ },
+ }
+
+ steps := yaml.StepSlice{
+ &yaml.Step{
+ Name: "sample",
+ Template: yaml.StepTemplate{
+ Name: "chain",
+ },
+ },
+ }
+
+ globalEnvironment := raw.StringSliceMap{
+ "foo": "test1",
+ "bar": "test2",
+ }
+
+ wantSteps := yaml.StepSlice{
+ &yaml.Step{
+ Commands: []string{"./gradlew downloadDependencies"},
+ Environment: raw.StringSliceMap{
+ "GRADLE_OPTS": "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false",
+ "GRADLE_USER_HOME": ".gradle",
+ },
+ Image: "openjdk:latest",
+ Name: "sample_call template_install",
+ Pull: "always",
+ },
+ &yaml.Step{
+ Commands: []string{"./gradlew check"},
+ Environment: raw.StringSliceMap{
+ "GRADLE_OPTS": "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false",
+ "GRADLE_USER_HOME": ".gradle",
+ },
+ Image: "openjdk:latest",
+ Name: "sample_call template_test",
+ Pull: "always",
+ },
+ &yaml.Step{
+ Commands: []string{"./gradlew build"},
+ Environment: raw.StringSliceMap{
+ "GRADLE_OPTS": "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false",
+ "GRADLE_USER_HOME": ".gradle",
+ },
+ Image: "openjdk:latest",
+ Name: "sample_call template_build",
+ Pull: "always",
+ },
+ }
+
+ wantSecrets := yaml.SecretSlice{
+ &yaml.Secret{
+ Name: "docker_username",
+ Key: "org/repo/foo/bar",
+ Engine: "native",
+ Type: "repo",
+ Origin: yaml.Origin{},
+ },
+ &yaml.Secret{
+ Name: "foo_password",
+ Key: "org/repo/foo/password",
+ Engine: "vault",
+ Type: "repo",
+ Origin: yaml.Origin{},
+ },
+ }
+
+ wantServices := yaml.ServiceSlice{
+ &yaml.Service{
+ Image: "postgres:12",
+ Name: "postgres",
+ Pull: "not_present",
+ },
+ }
+
+ wantEnvironment := raw.StringSliceMap{
+ "foo": "test1",
+ "bar": "test2",
+ "star": "test3",
+ }
+
+ // run test
+ compiler, err := New(c)
+ if err != nil {
+ t.Errorf("Creating new compiler returned err: %v", err)
+ }
+
+ compiler.WithBuild(testBuild).WithRepo(testRepo)
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ build, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Services: yaml.ServiceSlice{}, Environment: globalEnvironment}, test.tmpls, new(pipeline.RuleData), compiler.TemplateDepth)
+ if err != nil {
+ t.Errorf("ExpandSteps_Type%s returned err: %v", test.name, err)
+ }
+
+ if diff := cmp.Diff(build.Steps, wantSteps); diff != "" {
+ t.Errorf("ExpandSteps()_Type%s mismatch (-want +got):\n%s", test.name, diff)
+ }
+
+ if diff := cmp.Diff(build.Secrets, wantSecrets); diff != "" {
+ t.Errorf("ExpandSteps()_Type%s mismatch (-want +got):\n%s", test.name, diff)
+ }
+
+ if diff := cmp.Diff(build.Services, wantServices); diff != "" {
+ t.Errorf("ExpandSteps()_Type%s mismatch (-want +got):\n%s", test.name, diff)
+ }
+
+ if diff := cmp.Diff(build.Environment, wantEnvironment); diff != "" {
+ t.Errorf("ExpandSteps()_Type%s mismatch (-want +got):\n%s", test.name, diff)
+ }
+ })
+ }
+}
+
+func TestNative_ExpandSteps_TemplateCallTemplate_CircularFail(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ _, engine := gin.CreateTestContext(resp)
+
+ // setup mock server
+ engine.GET("/api/v3/repos/:org/:repo/contents/:path", func(c *gin.Context) {
+ body, err := convertFileToGithubResponse(c.Param("path"))
+ if err != nil {
+ t.Error(err)
+ }
+ c.JSON(http.StatusOK, body)
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup types
+ set := flag.NewFlagSet("test", 0)
+ set.Bool("github-driver", true, "doc")
+ set.String("github-url", s.URL, "doc")
+ set.String("github-token", "", "doc")
+ set.Int("max-template-depth", 5, "doc")
+ c := cli.NewContext(nil, set, nil)
+
+ testBuild := new(library.Build)
+
+ testBuild.SetID(1)
+ testBuild.SetCommit("123abc456def")
+
+ testRepo := new(library.Repo)
+
+ testRepo.SetID(1)
+ testRepo.SetOrg("foo")
+ testRepo.SetName("bar")
+
+ tests := []struct {
+ name string
+ tmpls map[string]*yaml.Template
+ }{
+ {
+ name: "Test 1",
+ tmpls: map[string]*yaml.Template{
+ "circle": {
+ Name: "circle",
+ Source: "github.example.com/bad/design/template_calls_itself.yml",
+ Type: "github",
+ },
+ },
+ },
+ }
+
+ steps := yaml.StepSlice{
+ &yaml.Step{
+ Name: "sample",
+ Template: yaml.StepTemplate{
+ Name: "circle",
+ },
+ },
+ }
+
+ globalEnvironment := raw.StringSliceMap{
+ "foo": "test1",
+ "bar": "test2",
+ }
+
+ // run test
+ compiler, err := New(c)
+ if err != nil {
+ t.Errorf("Creating new compiler returned err: %v", err)
+ }
+
+ compiler.WithBuild(testBuild).WithRepo(testRepo)
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ _, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Services: yaml.ServiceSlice{}, Environment: globalEnvironment}, test.tmpls, new(pipeline.RuleData), compiler.TemplateDepth)
+ if err == nil {
+ t.Errorf("ExpandSteps_Type%s should have returned an error", test.name)
+ }
+ })
+ }
+}
+
+func TestNative_ExpandSteps_CallTemplateWithRenderInline(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ _, engine := gin.CreateTestContext(resp)
+
+ // setup mock server
+ engine.GET("/api/v3/repos/:org/:repo/contents/:path", func(c *gin.Context) {
+ body, err := convertFileToGithubResponse(c.Param("path"))
+ if err != nil {
+ t.Error(err)
+ }
+ c.JSON(http.StatusOK, body)
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup types
+ set := flag.NewFlagSet("test", 0)
+ set.Bool("github-driver", true, "doc")
+ set.String("github-url", s.URL, "doc")
+ set.String("github-token", "", "doc")
+ set.Int("max-template-depth", 5, "doc")
+ c := cli.NewContext(nil, set, nil)
+
+ testBuild := new(library.Build)
+
+ testBuild.SetID(1)
+ testBuild.SetCommit("123abc456def")
+
+ testRepo := new(library.Repo)
+
+ testRepo.SetID(1)
+ testRepo.SetOrg("foo")
+ testRepo.SetName("bar")
+
+ tests := []struct {
+ name string
+ tmpls map[string]*yaml.Template
+ }{
+ {
+ name: "Test 1",
+ tmpls: map[string]*yaml.Template{
+ "render_inline": {
+ Name: "render_inline",
+ Source: "github.example.com/github/octocat/nested.yml",
+ Type: "github",
+ },
+ },
+ },
+ }
+
+ steps := yaml.StepSlice{
+ &yaml.Step{
+ Name: "sample",
+ Template: yaml.StepTemplate{
+ Name: "render_inline",
+ },
+ },
+ }
+
+ globalEnvironment := raw.StringSliceMap{
+ "foo": "test1",
+ "bar": "test2",
+ }
+
+ // run test
+ compiler, err := New(c)
+ if err != nil {
+ t.Errorf("Creating new compiler returned err: %v", err)
+ }
+
+ compiler.WithBuild(testBuild).WithRepo(testRepo)
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ _, err := compiler.ExpandSteps(&yaml.Build{Steps: steps, Services: yaml.ServiceSlice{}, Environment: globalEnvironment}, test.tmpls, new(pipeline.RuleData), compiler.TemplateDepth)
+ if err == nil {
+ t.Errorf("ExpandSteps_Type%s should have returned an error", test.name)
+ }
+ })
+ }
+}
+
func TestNative_mapFromTemplates(t *testing.T) {
// setup types
str := "foo"
diff --git a/compiler/native/native.go b/compiler/native/native.go
index 5a231dd68..c394af933 100644
--- a/compiler/native/native.go
+++ b/compiler/native/native.go
@@ -31,21 +31,26 @@ type client struct {
PrivateGithub registry.Service
UsePrivateGithub bool
ModificationService ModificationConfig
-
- build *library.Build
- comment string
- files []string
- local bool
- metadata *types.Metadata
- repo *library.Repo
- user *library.User
+ CloneImage string
+ TemplateDepth int
+
+ build *library.Build
+ comment string
+ commit string
+ files []string
+ local bool
+ localTemplates []string
+ metadata *types.Metadata
+ repo *library.Repo
+ user *library.User
}
// New returns a Pipeline implementation that integrates with the supported registries.
//
-// nolint: revive // ignore returning unexported client
+//nolint:revive // ignore returning unexported client
func New(ctx *cli.Context) (*client, error) {
logrus.Debug("Creating registry clients from CLI configuration")
+
c := new(client)
if ctx.String("modification-addr") != "" {
@@ -65,6 +70,11 @@ func New(ctx *cli.Context) (*client, error) {
c.Github = github
+ // set the clone image to use for the injected clone step
+ c.CloneImage = ctx.String("clone-image")
+
+ c.TemplateDepth = ctx.Int("max-template-depth")
+
if ctx.Bool("github-driver") {
logrus.Tracef("setting up Private GitHub Client for %s", ctx.String("github-url"))
// setup private github service
@@ -103,6 +113,8 @@ func (c *client) Duplicate() compiler.Engine {
cc.PrivateGithub = c.PrivateGithub
cc.UsePrivateGithub = c.UsePrivateGithub
cc.ModificationService = c.ModificationService
+ cc.CloneImage = c.CloneImage
+ cc.TemplateDepth = c.TemplateDepth
return cc
}
@@ -125,6 +137,15 @@ func (c *client) WithComment(cmt string) compiler.Engine {
return c
}
+// WithCommit sets the comment in the Engine.
+func (c *client) WithCommit(cmt string) compiler.Engine {
+ if cmt != "" {
+ c.commit = cmt
+ }
+
+ return c
+}
+
// WithFiles sets the changeset files in the Engine.
func (c *client) WithFiles(f []string) compiler.Engine {
if f != nil {
@@ -141,6 +162,13 @@ func (c *client) WithLocal(local bool) compiler.Engine {
return c
}
+// WithLocalTemplates sets the compiler local templates in the Engine.
+func (c *client) WithLocalTemplates(templates []string) compiler.Engine {
+ c.localTemplates = templates
+
+ return c
+}
+
// WithMetadata sets the compiler metadata type in the Engine.
func (c *client) WithMetadata(m *types.Metadata) compiler.Engine {
if m != nil {
diff --git a/compiler/native/native_test.go b/compiler/native/native_test.go
index 62c6fb7ed..de1f60631 100644
--- a/compiler/native/native_test.go
+++ b/compiler/native/native_test.go
@@ -202,6 +202,26 @@ func TestNative_WithLocal(t *testing.T) {
}
}
+func TestNative_WithLocalTemplates(t *testing.T) {
+ // setup types
+ set := flag.NewFlagSet("test", 0)
+ c := cli.NewContext(nil, set, nil)
+
+ localTemplates := []string{"example:tmpl.yml", "exmpl:template.yml"}
+ want, _ := New(c)
+ want.localTemplates = []string{"example:tmpl.yml", "exmpl:template.yml"}
+
+ // run test
+ got, err := New(c)
+ if err != nil {
+ t.Errorf("Unable to create new compiler: %v", err)
+ }
+
+ if !reflect.DeepEqual(got.WithLocalTemplates(localTemplates), want) {
+ t.Errorf("WithLocalTemplates is %v, want %v", got, want)
+ }
+}
+
func TestNative_WithMetadata(t *testing.T) {
// setup types
set := flag.NewFlagSet("test", 0)
diff --git a/compiler/native/parse.go b/compiler/native/parse.go
index 71b449e59..b3ff020f4 100644
--- a/compiler/native/parse.go
+++ b/compiler/native/parse.go
@@ -7,7 +7,6 @@ package native
import (
"fmt"
"io"
- "io/ioutil"
"os"
"github.com/go-vela/server/compiler/template/native"
@@ -43,29 +42,40 @@ func (c *client) ParseRaw(v interface{}) (string, error) {
}
// Parse converts an object to a yaml configuration.
-func (c *client) Parse(v interface{}) (*types.Build, error) {
- var p *types.Build
-
- switch c.repo.GetPipelineType() {
- case constants.PipelineTypeGo:
+func (c *client) Parse(v interface{}, pipelineType string, template *types.Template) (*types.Build, []byte, error) {
+ var (
+ p *types.Build
+ raw []byte
+ )
+
+ switch pipelineType {
+ case constants.PipelineTypeGo, "golang":
// expand the base configuration
parsedRaw, err := c.ParseRaw(v)
if err != nil {
- return nil, err
+ return nil, nil, err
}
- p, err = native.RenderBuild(parsedRaw, c.EnvironmentBuild())
+
+ // capture the raw pipeline configuration
+ raw = []byte(parsedRaw)
+
+ p, err = native.RenderBuild(template.Name, parsedRaw, c.EnvironmentBuild(), template.Variables)
if err != nil {
- return nil, err
+ return nil, raw, err
}
case constants.PipelineTypeStarlark:
// expand the base configuration
parsedRaw, err := c.ParseRaw(v)
if err != nil {
- return nil, err
+ return nil, nil, err
}
- p, err = starlark.RenderBuild(parsedRaw, c.EnvironmentBuild())
+
+ // capture the raw pipeline configuration
+ raw = []byte(parsedRaw)
+
+ p, err = starlark.RenderBuild(template.Name, parsedRaw, c.EnvironmentBuild(), template.Variables)
if err != nil {
- return nil, err
+ return nil, raw, err
}
case constants.PipelineTypeYAML, "":
switch v := v.(type) {
@@ -86,31 +96,30 @@ func (c *client) Parse(v interface{}) (*types.Build, error) {
// parse string as yaml configuration
return ParseString(v)
default:
- return nil, fmt.Errorf("unable to parse yaml: unrecognized type %T", v)
+ return nil, nil, fmt.Errorf("unable to parse yaml: unrecognized type %T", v)
}
default:
- // nolint:lll // detailed error message
- return nil, fmt.Errorf("unable to parse config: unrecognized pipeline_type of %s", c.repo.GetPipelineType())
+ return nil, nil, fmt.Errorf("unable to parse config: unrecognized pipeline_type of %s", c.repo.GetPipelineType())
}
- return p, nil
+ return p, raw, nil
}
// ParseBytes converts a byte slice to a yaml configuration.
-func ParseBytes(b []byte) (*types.Build, error) {
+func ParseBytes(data []byte) (*types.Build, []byte, error) {
config := new(types.Build)
// unmarshal the bytes into the yaml configuration
- err := yaml.Unmarshal(b, config)
+ err := yaml.Unmarshal(data, config)
if err != nil {
- return nil, fmt.Errorf("unable to unmarshal yaml: %v", err)
+ return nil, data, fmt.Errorf("unable to unmarshal yaml: %w", err)
}
- return config, nil
+ return config, data, nil
}
// ParseFile converts an os.File into a yaml configuration.
-func ParseFile(f *os.File) (*types.Build, error) {
+func ParseFile(f *os.File) (*types.Build, []byte, error) {
return ParseReader(f)
}
@@ -120,11 +129,11 @@ func ParseFileRaw(f *os.File) (string, error) {
}
// ParsePath converts a file path into a yaml configuration.
-func ParsePath(p string) (*types.Build, error) {
+func ParsePath(p string) (*types.Build, []byte, error) {
// open the file for reading
f, err := os.Open(p)
if err != nil {
- return nil, fmt.Errorf("unable to open yaml file %s: %v", p, err)
+ return nil, nil, fmt.Errorf("unable to open yaml file %s: %w", p, err)
}
defer f.Close()
@@ -137,7 +146,7 @@ func ParsePathRaw(p string) (string, error) {
// open the file for reading
f, err := os.Open(p)
if err != nil {
- return "", fmt.Errorf("unable to open yaml file %s: %v", p, err)
+ return "", fmt.Errorf("unable to open yaml file %s: %w", p, err)
}
defer f.Close()
@@ -146,28 +155,28 @@ func ParsePathRaw(p string) (string, error) {
}
// ParseReader converts an io.Reader into a yaml configuration.
-func ParseReader(r io.Reader) (*types.Build, error) {
+func ParseReader(r io.Reader) (*types.Build, []byte, error) {
// read all the bytes from the reader
- b, err := ioutil.ReadAll(r)
+ data, err := io.ReadAll(r)
if err != nil {
- return nil, fmt.Errorf("unable to read bytes for yaml: %v", err)
+ return nil, nil, fmt.Errorf("unable to read bytes for yaml: %w", err)
}
- return ParseBytes(b)
+ return ParseBytes(data)
}
// ParseReaderRaw converts an io.Reader into a yaml configuration.
func ParseReaderRaw(r io.Reader) (string, error) {
// read all the bytes from the reader
- b, err := ioutil.ReadAll(r)
+ b, err := io.ReadAll(r)
if err != nil {
- return "", fmt.Errorf("unable to read bytes for yaml: %v", err)
+ return "", fmt.Errorf("unable to read bytes for yaml: %w", err)
}
return string(b), nil
}
// ParseString converts a string into a yaml configuration.
-func ParseString(s string) (*types.Build, error) {
+func ParseString(s string) (*types.Build, []byte, error) {
return ParseBytes([]byte(s))
}
diff --git a/compiler/native/parse_test.go b/compiler/native/parse_test.go
index d1dcb3c39..0bb5a0195 100644
--- a/compiler/native/parse_test.go
+++ b/compiler/native/parse_test.go
@@ -8,7 +8,6 @@ import (
"bytes"
"errors"
"flag"
- "io/ioutil"
"os"
"reflect"
"testing"
@@ -36,12 +35,12 @@ func TestNative_Parse_Metadata_Bytes(t *testing.T) {
}
// run test
- b, err := ioutil.ReadFile("testdata/metadata.yml")
+ b, err := os.ReadFile("testdata/metadata.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
- got, err := client.Parse(b)
+ got, _, err := client.Parse(b, "", new(yaml.Template))
if err != nil {
t.Errorf("Parse returned err: %v", err)
}
@@ -71,7 +70,7 @@ func TestNative_Parse_Metadata_File(t *testing.T) {
defer f.Close()
- got, err := client.Parse(f)
+ got, _, err := client.Parse(f, "", new(yaml.Template))
if err != nil {
t.Errorf("Parse returned err: %v", err)
}
@@ -86,7 +85,7 @@ func TestNative_Parse_Metadata_Invalid(t *testing.T) {
client, _ := New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil))
// run test
- got, err := client.Parse(nil)
+ got, _, err := client.Parse(nil, "", new(yaml.Template))
if err == nil {
t.Error("Parse should have returned err")
@@ -110,7 +109,7 @@ func TestNative_Parse_Metadata_Path(t *testing.T) {
}
// run test
- got, err := client.Parse("testdata/metadata.yml")
+ got, _, err := client.Parse("testdata/metadata.yml", "", new(yaml.Template))
if err != nil {
t.Errorf("Parse returned err: %v", err)
}
@@ -133,12 +132,12 @@ func TestNative_Parse_Metadata_Reader(t *testing.T) {
}
// run test
- b, err := ioutil.ReadFile("testdata/metadata.yml")
+ b, err := os.ReadFile("testdata/metadata.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
- got, err := client.Parse(bytes.NewReader(b))
+ got, _, err := client.Parse(bytes.NewReader(b), "", new(yaml.Template))
if err != nil {
t.Errorf("Parse returned err: %v", err)
}
@@ -161,12 +160,12 @@ func TestNative_Parse_Metadata_String(t *testing.T) {
}
// run test
- b, err := ioutil.ReadFile("testdata/metadata.yml")
+ b, err := os.ReadFile("testdata/metadata.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
- got, err := client.Parse(string(b))
+ got, _, err := client.Parse(string(b), "", new(yaml.Template))
if err != nil {
t.Errorf("Parse returned err: %v", err)
}
@@ -180,6 +179,9 @@ func TestNative_Parse_Parameters(t *testing.T) {
// setup types
client, _ := New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil))
want := &yaml.Build{
+ Metadata: yaml.Metadata{
+ Environment: []string{"steps", "services", "secrets"},
+ },
Steps: yaml.StepSlice{
&yaml.Step{
Image: "plugins/docker:18.09",
@@ -205,12 +207,12 @@ func TestNative_Parse_Parameters(t *testing.T) {
}
// run test
- b, err := ioutil.ReadFile("testdata/parameters.yml")
+ b, err := os.ReadFile("testdata/parameters.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
- got, err := client.Parse(b)
+ got, _, err := client.Parse(b, "", new(yaml.Template))
if err != nil {
t.Errorf("Parse returned err: %v", err)
}
@@ -331,12 +333,12 @@ func TestNative_Parse_StagesPipeline(t *testing.T) {
}
// run test
- b, err := ioutil.ReadFile("testdata/stages_pipeline.yml")
+ b, err := os.ReadFile("testdata/stages_pipeline.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
- got, err := client.Parse(b)
+ got, _, err := client.Parse(b, "", new(yaml.Template))
if err != nil {
t.Errorf("Parse returned err: %v", err)
}
@@ -428,12 +430,12 @@ func TestNative_Parse_StepsPipeline(t *testing.T) {
}
// run test
- b, err := ioutil.ReadFile("testdata/steps_pipeline.yml")
+ b, err := os.ReadFile("testdata/steps_pipeline.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
- got, err := client.Parse(b)
+ got, _, err := client.Parse(b, "", new(yaml.Template))
if err != nil {
t.Errorf("Parse returned err: %v", err)
}
@@ -447,6 +449,9 @@ func TestNative_Parse_Secrets(t *testing.T) {
// setup types
client, _ := New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil))
want := &yaml.Build{
+ Metadata: yaml.Metadata{
+ Environment: []string{"steps", "services", "secrets"},
+ },
Secrets: yaml.SecretSlice{
&yaml.Secret{
Name: "docker_username",
@@ -488,12 +493,12 @@ func TestNative_Parse_Secrets(t *testing.T) {
}
// run test
- b, err := ioutil.ReadFile("testdata/secrets.yml")
+ b, err := os.ReadFile("testdata/secrets.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
- got, err := client.Parse(b)
+ got, _, err := client.Parse(b, "", new(yaml.Template))
if err != nil {
t.Errorf("Parse returned err: %v", err)
@@ -508,6 +513,9 @@ func TestNative_Parse_Stages(t *testing.T) {
// setup types
client, _ := New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil))
want := &yaml.Build{
+ Metadata: yaml.Metadata{
+ Environment: []string{"steps", "services", "secrets"},
+ },
Stages: yaml.StageSlice{
&yaml.Stage{
Name: "install",
@@ -561,12 +569,12 @@ func TestNative_Parse_Stages(t *testing.T) {
}
// run test
- b, err := ioutil.ReadFile("testdata/stages.yml")
+ b, err := os.ReadFile("testdata/stages.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
- got, err := client.Parse(b)
+ got, _, err := client.Parse(b, "", new(yaml.Template))
if err != nil {
t.Errorf("Parse returned err: %v", err)
@@ -581,6 +589,9 @@ func TestNative_Parse_Steps(t *testing.T) {
// setup types
client, _ := New(cli.NewContext(nil, flag.NewFlagSet("test", 0), nil))
want := &yaml.Build{
+ Metadata: yaml.Metadata{
+ Environment: []string{"steps", "services", "secrets"},
+ },
Steps: yaml.StepSlice{
&yaml.Step{
Commands: []string{"./gradlew downloadDependencies"},
@@ -616,12 +627,12 @@ func TestNative_Parse_Steps(t *testing.T) {
}
// run test
- b, err := ioutil.ReadFile("testdata/steps.yml")
+ b, err := os.ReadFile("testdata/steps.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
- got, err := client.Parse(b)
+ got, _, err := client.Parse(b, "", new(yaml.Template))
if err != nil {
t.Errorf("Parse returned err: %v", err)
@@ -644,12 +655,12 @@ func TestNative_ParseBytes_Metadata(t *testing.T) {
}
// run test
- b, err := ioutil.ReadFile("testdata/metadata.yml")
+ b, err := os.ReadFile("testdata/metadata.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
- got, err := ParseBytes(b)
+ got, _, err := ParseBytes(b)
if err != nil {
t.Errorf("ParseBytes returned err: %v", err)
@@ -662,12 +673,12 @@ func TestNative_ParseBytes_Metadata(t *testing.T) {
func TestNative_ParseBytes_Invalid(t *testing.T) {
// run test
- b, err := ioutil.ReadFile("testdata/invalid.yml")
+ b, err := os.ReadFile("testdata/invalid.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
- got, err := ParseBytes(b)
+ got, _, err := ParseBytes(b)
if err == nil {
t.Error("ParseBytes should have returned err")
@@ -697,7 +708,7 @@ func TestNative_ParseFile_Metadata(t *testing.T) {
defer f.Close()
- got, err := ParseFile(f)
+ got, _, err := ParseFile(f)
if err != nil {
t.Errorf("ParseFile returned err: %v", err)
@@ -717,7 +728,7 @@ func TestNative_ParseFile_Invalid(t *testing.T) {
f.Close()
- got, err := ParseFile(f)
+ got, _, err := ParseFile(f)
if err == nil {
t.Error("ParseFile should have returned err")
@@ -740,7 +751,7 @@ func TestNative_ParsePath_Metadata(t *testing.T) {
}
// run test
- got, err := ParsePath("testdata/metadata.yml")
+ got, _, err := ParsePath("testdata/metadata.yml")
if err != nil {
t.Errorf("ParsePath returned err: %v", err)
@@ -753,7 +764,7 @@ func TestNative_ParsePath_Metadata(t *testing.T) {
func TestNative_ParsePath_Invalid(t *testing.T) {
// run test
- got, err := ParsePath("testdata/foobar.yml")
+ got, _, err := ParsePath("testdata/foobar.yml")
if err == nil {
t.Error("ParsePath should have returned err")
@@ -776,12 +787,12 @@ func TestNative_ParseReader_Metadata(t *testing.T) {
}
// run test
- b, err := ioutil.ReadFile("testdata/metadata.yml")
+ b, err := os.ReadFile("testdata/metadata.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
- got, err := ParseReader(bytes.NewReader(b))
+ got, _, err := ParseReader(bytes.NewReader(b))
if err != nil {
t.Errorf("ParseReader returned err: %v", err)
@@ -794,7 +805,7 @@ func TestNative_ParseReader_Metadata(t *testing.T) {
func TestNative_ParseReader_Invalid(t *testing.T) {
// run test
- got, err := ParseReader(FailReader{})
+ got, _, err := ParseReader(FailReader{})
if err == nil {
t.Error("ParseFile should have returned err")
@@ -817,12 +828,12 @@ func TestNative_ParseString_Metadata(t *testing.T) {
}
// run test
- b, err := ioutil.ReadFile("testdata/metadata.yml")
+ b, err := os.ReadFile("testdata/metadata.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
- got, err := ParseString(string(b))
+ got, _, err := ParseString(string(b))
if err != nil {
t.Errorf("ParseString returned err: %v", err)
@@ -846,7 +857,7 @@ func Test_client_Parse(t *testing.T) {
Metadata: yaml.Metadata{
Template: false,
Clone: nil,
- Environment: nil,
+ Environment: []string{"steps", "services", "secrets"},
},
Steps: yaml.StepSlice{
{
@@ -859,10 +870,12 @@ func Test_client_Parse(t *testing.T) {
},
},
}
+
type args struct {
pipelineType string
file string
}
+
tests := []struct {
name string
args args
@@ -873,12 +886,13 @@ func Test_client_Parse(t *testing.T) {
{"starlark", args{pipelineType: constants.PipelineTypeStarlark, file: "testdata/pipeline_type.star"}, want, false},
{"go", args{pipelineType: constants.PipelineTypeGo, file: "testdata/pipeline_type_go.yml"}, want, false},
{"empty", args{pipelineType: "", file: "testdata/pipeline_type_default.yml"}, want, false},
- {"nil", args{pipelineType: "nil", file: "testdata/pipeline_type_default.yml"}, want, false},
+ {"nil", args{pipelineType: "nil", file: "testdata/pipeline_type_default.yml"}, nil, true},
{"invalid", args{pipelineType: "foo", file: "testdata/pipeline_type_default.yml"}, nil, true},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- content, err := ioutil.ReadFile(tt.args.file)
+ content, err := os.ReadFile(tt.args.file)
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
@@ -892,7 +906,7 @@ func Test_client_Parse(t *testing.T) {
}
}
- got, err := c.Parse(content)
+ got, _, err := c.Parse(content, tt.args.pipelineType, new(yaml.Template))
if (err != nil) != tt.wantErr {
t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr)
return
@@ -905,13 +919,15 @@ func Test_client_Parse(t *testing.T) {
}
func Test_client_ParseRaw(t *testing.T) {
- expected, err := ioutil.ReadFile("testdata/metadata.yml")
+ expected, err := os.ReadFile("testdata/metadata.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
+
type args struct {
kind string
}
+
tests := []struct {
name string
args args
@@ -925,13 +941,14 @@ func Test_client_ParseRaw(t *testing.T) {
{"path", args{kind: "path"}, string(expected), false},
{"unexpected", args{kind: "foo"}, "", true},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var content interface{}
var err error
switch tt.args.kind {
case "byte":
- content, err = ioutil.ReadFile("testdata/metadata.yml")
+ content, err = os.ReadFile("testdata/metadata.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
@@ -941,7 +958,7 @@ func Test_client_ParseRaw(t *testing.T) {
t.Errorf("Reading file returned err: %v", err)
}
case "ioreader":
- b, err := ioutil.ReadFile("testdata/metadata.yml")
+ b, err := os.ReadFile("testdata/metadata.yml")
if err != nil {
t.Errorf("ParseReader returned err: %v", err)
}
diff --git a/compiler/native/script.go b/compiler/native/script.go
index 83ff32f84..8256ee5a1 100644
--- a/compiler/native/script.go
+++ b/compiler/native/script.go
@@ -39,7 +39,7 @@ func (c *client) ScriptSteps(s yaml.StepSlice) (yaml.StepSlice, error) {
}
// set the default home
- // nolint: goconst // ignore making this a constant for now
+ //nolint:goconst // ignore making this a constant for now
home := "/root"
// override the home value if user is defined
// TODO:
@@ -60,7 +60,7 @@ func (c *client) ScriptSteps(s yaml.StepSlice) (yaml.StepSlice, error) {
// set the environment variables for the step
step.Environment["VELA_BUILD_SCRIPT"] = script
step.Environment["HOME"] = home
- // nolint: goconst // ignore making this a constant for now
+ //nolint:goconst // ignore making this a constant for now
step.Environment["SHELL"] = "/bin/sh"
}
diff --git a/compiler/native/script_test.go b/compiler/native/script_test.go
index a7f9517a2..bde8adcda 100644
--- a/compiler/native/script_test.go
+++ b/compiler/native/script_test.go
@@ -120,6 +120,7 @@ func TestNative_ScriptSteps(t *testing.T) {
type args struct {
s yaml.StepSlice
}
+
tests := []struct {
name string
args args
@@ -234,6 +235,7 @@ func TestNative_ScriptSteps(t *testing.T) {
},
}, false},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
compiler, err := New(c)
diff --git a/compiler/native/substitute.go b/compiler/native/substitute.go
index c0a4be6de..fcfd2b263 100644
--- a/compiler/native/substitute.go
+++ b/compiler/native/substitute.go
@@ -16,7 +16,7 @@ import (
)
// SubstituteStages replaces every declared environment
-// variable with it's corresponding value for each step
+// variable with its corresponding value for each step
// in every stage in a yaml configuration.
func (c *client) SubstituteStages(s types.StageSlice) (types.StageSlice, error) {
// iterate through all stages
@@ -34,7 +34,7 @@ func (c *client) SubstituteStages(s types.StageSlice) (types.StageSlice, error)
}
// SubstituteSteps replaces every declared environment
-// variable with it's corresponding value for each step
+// variable with its corresponding value for each step
// in a yaml configuration.
func (c *client) SubstituteSteps(s types.StepSlice) (types.StepSlice, error) {
// iterate through all steps
@@ -42,7 +42,7 @@ func (c *client) SubstituteSteps(s types.StepSlice) (types.StepSlice, error) {
// marshal step configuration
body, err := yaml.Marshal(step)
if err != nil {
- return nil, fmt.Errorf("unable to marshal configuration: %v", err)
+ return nil, fmt.Errorf("unable to marshal configuration: %w", err)
}
// create substitute function
@@ -67,13 +67,13 @@ func (c *client) SubstituteSteps(s types.StepSlice) (types.StepSlice, error) {
// substitute the environment variables
subStep, err := envsubst.Eval(string(body), subFunc)
if err != nil {
- return nil, fmt.Errorf("unable to substitute environment variables: %v", err)
+ return nil, fmt.Errorf("unable to substitute environment variables: %w", err)
}
// unmarshal step configuration
err = yaml.Unmarshal([]byte(subStep), step)
if err != nil {
- return nil, fmt.Errorf("unable to unmarshal configuration: %v", err)
+ return nil, fmt.Errorf("unable to unmarshal configuration: %w", err)
}
}
diff --git a/compiler/native/substitute_test.go b/compiler/native/substitute_test.go
index feff0cf4f..da27378ac 100644
--- a/compiler/native/substitute_test.go
+++ b/compiler/native/substitute_test.go
@@ -6,62 +6,175 @@ package native
import (
"flag"
- "reflect"
"testing"
- "github.com/go-vela/types/yaml"
-
"github.com/urfave/cli/v2"
+
+ "github.com/go-vela/types/yaml"
+ "github.com/google/go-cmp/cmp"
)
-func TestNative_SubstituteStages(t *testing.T) {
+func Test_client_SubstituteStages(t *testing.T) {
+ type args struct {
+ stages yaml.StageSlice
+ }
+
// setup types
set := flag.NewFlagSet("test", 0)
c := cli.NewContext(nil, set, nil)
- s := yaml.StageSlice{
+ tests := []struct {
+ name string
+ args args
+ want yaml.StageSlice
+ wantErr bool
+ }{
{
- Name: "simple",
- Steps: yaml.StepSlice{
- {
- Commands: []string{"echo ${FOO}", "echo $${BAR}"},
- Environment: map[string]string{"FOO": "baz", "BAR": "baz"},
- Image: "alpine:latest",
- Name: "simple",
- Pull: "always",
+ name: "normal",
+ args: args{
+ stages: yaml.StageSlice{
+ {
+ Name: "simple",
+ Steps: yaml.StepSlice{
+ {
+ Commands: []string{"echo ${FOO}", "echo $${BAR}"},
+ Environment: map[string]string{"FOO": "baz", "BAR": "baz"},
+ Image: "alpine:latest",
+ Name: "simple",
+ Pull: "always",
+ },
+ },
+ },
+ {
+ Name: "advanced",
+ Steps: yaml.StepSlice{
+ {
+ Commands: []string{"echo ${COMPLEX}"},
+ Environment: map[string]string{"COMPLEX": "{\"hello\":\n \"world\"}"},
+ Image: "alpine:latest",
+ Name: "advanced",
+ Pull: "always",
+ },
+ },
+ },
+ {
+ Name: "not_found",
+ Steps: yaml.StepSlice{
+ {
+ Commands: []string{"echo $NOT_FOUND", "echo ${NOT_FOUND}", "echo $${NOT_FOUND}"},
+ Environment: map[string]string{"FOO": "baz", "BAR": "baz"},
+ Image: "alpine:latest",
+ Name: "not_found",
+ Pull: "always",
+ },
+ },
+ },
},
},
- },
- {
- Name: "advanced",
- Steps: yaml.StepSlice{
+ want: yaml.StageSlice{
{
- Commands: []string{"echo ${COMPLEX}"},
- Environment: map[string]string{"COMPLEX": "{\"hello\":\n \"world\"}"},
- Image: "alpine:latest",
- Name: "advanced",
- Pull: "always",
+ Name: "simple",
+ Steps: yaml.StepSlice{
+ {
+ Commands: []string{"echo baz", "echo ${BAR}"},
+ Environment: map[string]string{"FOO": "baz", "BAR": "baz"},
+ Image: "alpine:latest",
+ Name: "simple",
+ Pull: "always",
+ },
+ },
},
- },
- },
- {
- Name: "not_found",
- Steps: yaml.StepSlice{
{
- Commands: []string{"echo $NOT_FOUND", "echo ${NOT_FOUND}", "echo $${NOT_FOUND}"},
- Environment: map[string]string{"FOO": "baz", "BAR": "baz"},
- Image: "alpine:latest",
- Name: "not_found",
- Pull: "always",
+ Name: "advanced",
+ Steps: yaml.StepSlice{
+ {
+ Commands: []string{"echo \"{\\\"hello\\\":\\n \\\"world\\\"}\""},
+ Environment: map[string]string{"COMPLEX": "{\"hello\":\n \"world\"}"},
+ Image: "alpine:latest",
+ Name: "advanced",
+ Pull: "always",
+ },
+ },
+ },
+ {
+ Name: "not_found",
+ Steps: yaml.StepSlice{
+ {
+ Commands: []string{"echo $NOT_FOUND", "echo ${NOT_FOUND}", "echo ${NOT_FOUND}"},
+ Environment: map[string]string{"FOO": "baz", "BAR": "baz"},
+ Image: "alpine:latest",
+ Name: "not_found",
+ Pull: "always",
+ },
+ },
},
},
+ wantErr: false,
},
}
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ compiler, err := New(c)
+ if err != nil {
+ t.Errorf("Creating compiler returned err: %v", err)
+ }
+
+ got, err := compiler.SubstituteStages(tt.args.stages)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("SubstituteStages() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+
+ if diff := cmp.Diff(tt.want, got); diff != "" {
+ t.Errorf("SubstituteStages() mismatch (-want +got):\n%s", diff)
+ }
+ })
+ }
+}
- want := yaml.StageSlice{
+func Test_client_SubstituteSteps(t *testing.T) {
+ type args struct {
+ steps yaml.StepSlice
+ }
+
+ // setup types
+ set := flag.NewFlagSet("test", 0)
+ c := cli.NewContext(nil, set, nil)
+
+ tests := []struct {
+ name string
+ args args
+ want yaml.StepSlice
+ wantErr bool
+ }{
{
- Name: "simple",
- Steps: yaml.StepSlice{
+ name: "steps",
+ args: args{
+ steps: yaml.StepSlice{
+ {
+ Commands: []string{"echo ${FOO}", "echo $${BAR}"},
+ Environment: map[string]string{"FOO": "baz", "BAR": "baz"},
+ Image: "alpine:latest",
+ Name: "simple",
+ Pull: "always",
+ },
+ {
+ Commands: []string{"echo ${COMPLEX}"},
+ Environment: map[string]string{"COMPLEX": "{\"hello\":\n \"world\"}"},
+ Image: "alpine:latest",
+ Name: "advanced",
+ Pull: "always",
+ },
+ {
+ Commands: []string{"echo $NOT_FOUND", "echo ${NOT_FOUND}", "echo $${NOT_FOUND}"},
+ Environment: map[string]string{"FOO": "baz", "BAR": "baz"},
+ Image: "alpine:latest",
+ Name: "not_found",
+ Pull: "always",
+ },
+ },
+ },
+ want: yaml.StepSlice{
{
Commands: []string{"echo baz", "echo ${BAR}"},
Environment: map[string]string{"FOO": "baz", "BAR": "baz"},
@@ -69,11 +182,6 @@ func TestNative_SubstituteStages(t *testing.T) {
Name: "simple",
Pull: "always",
},
- },
- },
- {
- Name: "advanced",
- Steps: yaml.StepSlice{
{
Commands: []string{"echo \"{\\\"hello\\\":\\n \\\"world\\\"}\""},
Environment: map[string]string{"COMPLEX": "{\"hello\":\n \"world\"}"},
@@ -81,11 +189,6 @@ func TestNative_SubstituteStages(t *testing.T) {
Name: "advanced",
Pull: "always",
},
- },
- },
- {
- Name: "not_found",
- Steps: yaml.StepSlice{
{
Commands: []string{"echo $NOT_FOUND", "echo ${NOT_FOUND}", "echo ${NOT_FOUND}"},
Environment: map[string]string{"FOO": "baz", "BAR": "baz"},
@@ -94,91 +197,61 @@ func TestNative_SubstituteStages(t *testing.T) {
Pull: "always",
},
},
+ wantErr: false,
},
- }
-
- // run test
- compiler, err := New(c)
- if err != nil {
- t.Errorf("Creating compiler returned err: %v", err)
- }
-
- got, err := compiler.SubstituteStages(s)
-
- if err != nil {
- t.Errorf("SubstituteStages returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, want) {
- t.Errorf("SubstituteStages is %v, want %v", got, want)
- }
-}
-
-func TestNative_SubstituteSteps(t *testing.T) {
- // setup types
- set := flag.NewFlagSet("test", 0)
- c := cli.NewContext(nil, set, nil)
-
- p := yaml.StepSlice{
{
- Commands: []string{"echo ${FOO}", "echo $${BAR}"},
- Environment: map[string]string{"FOO": "baz", "BAR": "baz"},
- Image: "alpine:latest",
- Name: "simple",
- Pull: "always",
- },
- {
- Commands: []string{"echo ${COMPLEX}"},
- Environment: map[string]string{"COMPLEX": "{\"hello\":\n \"world\"}"},
- Image: "alpine:latest",
- Name: "advanced",
- Pull: "always",
- },
- {
- Commands: []string{"echo $NOT_FOUND", "echo ${NOT_FOUND}", "echo $${NOT_FOUND}"},
- Environment: map[string]string{"FOO": "baz", "BAR": "baz"},
- Image: "alpine:latest",
- Name: "not_found",
- Pull: "always",
- },
- }
-
- want := yaml.StepSlice{
- {
- Commands: []string{"echo baz", "echo ${BAR}"},
- Environment: map[string]string{"FOO": "baz", "BAR": "baz"},
- Image: "alpine:latest",
- Name: "simple",
- Pull: "always",
- },
- {
- Commands: []string{"echo \"{\\\"hello\\\":\\n \\\"world\\\"}\""},
- Environment: map[string]string{"COMPLEX": "{\"hello\":\n \"world\"}"},
- Image: "alpine:latest",
- Name: "advanced",
- Pull: "always",
- },
- {
- Commands: []string{"echo $NOT_FOUND", "echo ${NOT_FOUND}", "echo ${NOT_FOUND}"},
- Environment: map[string]string{"FOO": "baz", "BAR": "baz"},
- Image: "alpine:latest",
- Name: "not_found",
- Pull: "always",
+ name: "template",
+ args: args{
+ steps: yaml.StepSlice{
+ {
+ Name: "sample",
+ Template: yaml.StepTemplate{
+ Name: "go",
+ Variables: map[string]interface{}{
+ "build_author": "${BUILD_AUTHOR}",
+ "unknown": "${DEPLOYMENT_PARAMETER_API_IMAGE}",
+ },
+ },
+ Environment: map[string]string{
+ "BUILD_AUTHOR": "testauthor",
+ },
+ },
+ },
+ },
+ want: yaml.StepSlice{
+ {
+ Name: "sample",
+ Template: yaml.StepTemplate{
+ Name: "go",
+ Variables: map[string]interface{}{
+ "build_author": "testauthor",
+ "unknown": "${DEPLOYMENT_PARAMETER_API_IMAGE}",
+ },
+ },
+ Environment: map[string]string{
+ "BUILD_AUTHOR": "testauthor",
+ },
+ },
+ },
+ wantErr: false,
},
}
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ compiler, err := New(c)
+ if err != nil {
+ t.Errorf("Creating compiler returned err: %v", err)
+ }
- // run test
- compiler, err := New(c)
- if err != nil {
- t.Errorf("Creating compiler returned err: %v", err)
- }
-
- got, err := compiler.SubstituteSteps(p)
- if err != nil {
- t.Errorf("SubstituteSteps returned err: %v", err)
- }
+ got, err := compiler.SubstituteSteps(tt.args.steps)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("SubstituteSteps() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
- if !reflect.DeepEqual(got, want) {
- t.Errorf("SubstituteSteps is %v, want %v", got, want)
+ if diff := cmp.Diff(tt.want, got); diff != "" {
+ t.Errorf("SubstituteSteps() mismatch (-want +got):\n%s", diff)
+ }
+ })
}
}
diff --git a/compiler/native/testdata/circular.yml b/compiler/native/testdata/circular.yml
new file mode 100644
index 000000000..bd8327df3
--- /dev/null
+++ b/compiler/native/testdata/circular.yml
@@ -0,0 +1,16 @@
+metadata:
+ render_inline: true
+ template: true
+
+templates:
+ - name: bad
+ source: github.example.com/github/octocat/inline_circular_template.yml
+ type: github
+
+stages:
+ test:
+ steps:
+ - name: test
+ image: alpine
+ commands:
+ - echo from inline
\ No newline at end of file
diff --git a/compiler/native/testdata/clone_replace.yml b/compiler/native/testdata/clone_replace.yml
index 69ab9a369..e258910b6 100644
--- a/compiler/native/testdata/clone_replace.yml
+++ b/compiler/native/testdata/clone_replace.yml
@@ -5,7 +5,7 @@ metadata:
steps:
- name: clone
- image: target/vela-git:v0.4.0
+ image: target/vela-git:v0.5.1
parameters:
depth: 5
pull: always
diff --git a/compiler/native/testdata/environment.yml b/compiler/native/testdata/environment.yml
new file mode 100644
index 000000000..fe6e584e0
--- /dev/null
+++ b/compiler/native/testdata/environment.yml
@@ -0,0 +1,3 @@
+---
+environment:
+ FOO: "Hello, foo!"
\ No newline at end of file
diff --git a/compiler/native/testdata/golang_inline_stages.yml b/compiler/native/testdata/golang_inline_stages.yml
new file mode 100644
index 000000000..59b390d47
--- /dev/null
+++ b/compiler/native/testdata/golang_inline_stages.yml
@@ -0,0 +1,13 @@
+version: "1"
+
+{{$stageList := list "foo" "bar" "star" -}}
+
+stages:
+ {{range $stage := $stageList -}}
+ {{ $stage }}:
+ steps:
+ - name: {{ $stage }}
+ image: {{ default "alpine" $.image }}
+ commands:
+ - echo hello from {{ $stage }}
+ {{ end }}
\ No newline at end of file
diff --git a/compiler/native/testdata/golang_inline_steps.yml b/compiler/native/testdata/golang_inline_steps.yml
new file mode 100644
index 000000000..1a69da41b
--- /dev/null
+++ b/compiler/native/testdata/golang_inline_steps.yml
@@ -0,0 +1,11 @@
+version: "1"
+
+{{$stepList := list "foo" "bar" "star" -}}
+
+steps:
+ {{range $step := $stepList -}}
+ - name: {{ $step }}
+ image: alpine
+ commands:
+ - echo hello from {{ $step }}
+ {{ end }}
\ No newline at end of file
diff --git a/compiler/native/testdata/gradle.yml b/compiler/native/testdata/gradle.yml
new file mode 100644
index 000000000..ed1e8e0d1
--- /dev/null
+++ b/compiler/native/testdata/gradle.yml
@@ -0,0 +1,70 @@
+metadata:
+ template: true
+
+steps:
+ - name: install
+ commands:
+ - ./gradlew downloadDependencies
+ environment: {{ .environment }}
+ image: {{ .image }}
+ {{ .pull_policy }}
+
+ - name: test
+ commands:
+ - ./gradlew check
+ environment: {{ .environment }}
+ image: {{ .image }}
+ {{ .pull_policy }}
+
+ - name: build
+ commands:
+ - ./gradlew build
+ environment: {{ .environment }}
+ image: {{ .image }}
+ {{ .pull_policy }}
+
+secrets:
+ - name: docker_username
+ key: org/repo/foo/bar
+ engine: native
+ type: repo
+
+ - name: foo_password
+ key: org/repo/foo/password
+ engine: vault
+ type: repo
+
+ - name: vault_token
+
+ - origin:
+ name: private vault
+ image: target/secret-vault:latest
+ pull: always
+ secrets: [ vault_token ]
+ parameters:
+ addr: vault.example.com
+ auth_method: token
+ username: octocat
+ items:
+ - source: secret/docker
+ path: docker
+
+ {{ if .secret }}
+
+- name: bar_password
+ key: org/repo/bar/password
+ engine: vault
+ type: repo
+
+ {{ end }}
+
+services:
+ - name: postgres
+ image: postgres:12
+
+ {{ if .service }}
+
+ - name: redis
+ key: redis:6
+
+ {{ end }}
diff --git a/compiler/native/testdata/inline_circular_template.yml b/compiler/native/testdata/inline_circular_template.yml
new file mode 100644
index 000000000..4b0469c58
--- /dev/null
+++ b/compiler/native/testdata/inline_circular_template.yml
@@ -0,0 +1,19 @@
+version: "1"
+
+metadata:
+ render_inline: true
+
+templates:
+ - name: nested
+ source: github.example.com/github/octocat/circular.yml
+ type: github
+ vars:
+ image: golang:latest
+
+stages:
+ test:
+ steps:
+ - name: test
+ image: alpine
+ commands:
+ - echo from inline
\ No newline at end of file
diff --git a/compiler/native/testdata/inline_nested_template.yml b/compiler/native/testdata/inline_nested_template.yml
new file mode 100644
index 000000000..0faaeb0c8
--- /dev/null
+++ b/compiler/native/testdata/inline_nested_template.yml
@@ -0,0 +1,19 @@
+version: "1"
+
+metadata:
+ render_inline: true
+
+templates:
+ - name: nested
+ source: github.example.com/github/octocat/nested.yml
+ type: github
+ vars:
+ image: golang:latest
+
+stages:
+ test:
+ steps:
+ - name: test
+ image: alpine
+ commands:
+ - echo from inline
\ No newline at end of file
diff --git a/compiler/native/testdata/inline_with_environment.yml b/compiler/native/testdata/inline_with_environment.yml
new file mode 100644
index 000000000..ffdb844fc
--- /dev/null
+++ b/compiler/native/testdata/inline_with_environment.yml
@@ -0,0 +1,19 @@
+version: "1"
+
+environment:
+ HELLO: "Hello, Vela!"
+
+metadata:
+ render_inline: true
+
+templates:
+ - name: golang
+ source: github.example.com/github/octocat/environment.yml
+ format: golang
+ type: github
+
+steps:
+ - name: test
+ image: alpine
+ parameters:
+ first: "foo"
\ No newline at end of file
diff --git a/compiler/native/testdata/inline_with_golang.yml b/compiler/native/testdata/inline_with_golang.yml
new file mode 100644
index 000000000..12767cfab
--- /dev/null
+++ b/compiler/native/testdata/inline_with_golang.yml
@@ -0,0 +1,28 @@
+version: "1"
+
+metadata:
+ render_inline: true
+
+templates:
+ - name: golang
+ source: github.example.com/github/octocat/golang_inline_stages.yml
+ format: golang
+ type: github
+ vars:
+ image: golang:latest
+ - name: starlark
+ source: github.example.com/github/octocat/starlark_inline_stages.star
+ format: starlark
+ type: github
+
+{{$stageList := list "foo" "bar" "star" -}}
+
+stages:
+ {{range $stage := $stageList -}}
+ {{ $stage }}:
+ steps:
+ - name: {{ $stage }}
+ image: alpine
+ commands:
+ - echo from inline {{ $stage }}
+ {{ end }}
\ No newline at end of file
diff --git a/compiler/native/testdata/inline_with_secrets.yml b/compiler/native/testdata/inline_with_secrets.yml
new file mode 100644
index 000000000..42ec50464
--- /dev/null
+++ b/compiler/native/testdata/inline_with_secrets.yml
@@ -0,0 +1,22 @@
+version: "1"
+
+secrets:
+ - name: foo_username
+ key: org/repo/foo/username
+ engine: native
+ type: repo
+
+metadata:
+ render_inline: true
+
+templates:
+ - name: golang
+ source: github.example.com/github/octocat/secrets.yml
+ format: golang
+ type: github
+
+steps:
+ - name: test
+ image: alpine
+ commands:
+ - echo from inline
\ No newline at end of file
diff --git a/compiler/native/testdata/inline_with_services.yml b/compiler/native/testdata/inline_with_services.yml
new file mode 100644
index 000000000..5ecc71c2b
--- /dev/null
+++ b/compiler/native/testdata/inline_with_services.yml
@@ -0,0 +1,20 @@
+version: "1"
+
+services:
+ - name: postgres
+ image: postgres:latest
+
+metadata:
+ render_inline: true
+
+templates:
+ - name: golang
+ source: github.example.com/github/octocat/services.yml
+ format: golang
+ type: github
+
+steps:
+ - name: test
+ image: alpine
+ commands:
+ - echo from inline
\ No newline at end of file
diff --git a/compiler/native/testdata/inline_with_stages.yml b/compiler/native/testdata/inline_with_stages.yml
new file mode 100644
index 000000000..89d79b5e3
--- /dev/null
+++ b/compiler/native/testdata/inline_with_stages.yml
@@ -0,0 +1,24 @@
+version: "1"
+
+metadata:
+ render_inline: true
+
+templates:
+ - name: golang
+ source: github.example.com/github/octocat/golang_inline_stages.yml
+ format: golang
+ type: github
+ vars:
+ image: golang:latest
+ - name: starlark
+ source: github.example.com/github/octocat/starlark_inline_stages.star
+ format: starlark
+ type: github
+
+stages:
+ test:
+ steps:
+ - name: test
+ image: alpine
+ commands:
+ - echo from inline
\ No newline at end of file
diff --git a/compiler/native/testdata/inline_with_stages_and_steps.yml b/compiler/native/testdata/inline_with_stages_and_steps.yml
new file mode 100644
index 000000000..8a70e12a7
--- /dev/null
+++ b/compiler/native/testdata/inline_with_stages_and_steps.yml
@@ -0,0 +1,22 @@
+version: "1"
+
+metadata:
+ render_inline: true
+
+templates:
+ - name: golang
+ source: github.example.com/github/octocat/golang_inline_stages.yml
+ format: golang
+ type: github
+ - name: starlark
+ source: github.example.com/github/octocat/starlark_inline_steps.star
+ format: starlark
+ type: github
+
+stages:
+ test:
+ steps:
+ - name: test
+ image: alpine
+ commands:
+ - echo from inline
\ No newline at end of file
diff --git a/compiler/native/testdata/inline_with_steps.yml b/compiler/native/testdata/inline_with_steps.yml
new file mode 100644
index 000000000..28348cf8d
--- /dev/null
+++ b/compiler/native/testdata/inline_with_steps.yml
@@ -0,0 +1,20 @@
+version: "1"
+
+metadata:
+ render_inline: true
+
+templates:
+ - name: golang
+ source: github.example.com/github/octocat/golang_inline_steps.yml
+ format: golang
+ type: github
+ - name: starlark
+ source: github.example.com/github/octocat/starlark_inline_steps.star
+ format: starlark
+ type: github
+
+steps:
+ - name: test
+ image: alpine
+ commands:
+ - echo from inline
\ No newline at end of file
diff --git a/compiler/native/testdata/long_template.yml b/compiler/native/testdata/long_template.yml
new file mode 100644
index 000000000..c9f92ee44
--- /dev/null
+++ b/compiler/native/testdata/long_template.yml
@@ -0,0 +1,59 @@
+environment:
+ star: "test3"
+ bar: "test4"
+
+metadata:
+ template: true
+
+steps:
+ - name: install
+ commands:
+ - ./gradlew downloadDependencies
+ environment: {{ .environment }}
+ image: {{ .image }}
+ {{ .pull_policy }}
+
+ - name: test
+ commands:
+ - ./gradlew check
+ environment: {{ .environment }}
+ image: {{ .image }}
+ {{ .pull_policy }}
+
+ - name: build
+ commands:
+ - ./gradlew build
+ environment: {{ .environment }}
+ image: {{ .image }}
+ {{ .pull_policy }}
+
+secrets:
+ - name: docker_username
+ key: org/repo/foo/bar
+ engine: native
+ type: repo
+
+ - name: foo_password
+ key: org/repo/foo/password
+ engine: vault
+ type: repo
+
+ {{ if .secret }}
+
+ - name: bar_password
+ key: org/repo/bar/password
+ engine: vault
+ type: repo
+
+ {{ end }}
+
+services:
+ - name: postgres
+ image: postgres:12
+
+ {{ if .service }}
+
+ - name: redis
+ key: redis:6
+
+ {{ end }}
diff --git a/compiler/native/testdata/maven.yml b/compiler/native/testdata/maven.yml
new file mode 100644
index 000000000..fd6ff73b1
--- /dev/null
+++ b/compiler/native/testdata/maven.yml
@@ -0,0 +1,70 @@
+metadata:
+ template: true
+
+steps:
+ - name: install
+ commands:
+ - mvn downloadDependencies
+ environment: {{ .environment }}
+ image: {{ .image }}
+ {{ .pull_policy }}
+
+ - name: test
+ commands:
+ - mvn check
+ environment: {{ .environment }}
+ image: {{ .image }}
+ {{ .pull_policy }}
+
+ - name: build
+ commands:
+ - mvn build
+ environment: {{ .environment }}
+ image: {{ .image }}
+ {{ .pull_policy }}
+
+secrets:
+ - name: docker_username
+ key: org/repo/foo/bar
+ engine: native
+ type: repo
+
+ - name: foo_password
+ key: org/repo/foo/password
+ engine: vault
+ type: repo
+
+ - name: vault_token
+
+ - origin:
+ name: private vault
+ image: target/secret-vault:latest
+ pull: always
+ secrets: [ vault_token ]
+ parameters:
+ addr: vault.example.com
+ auth_method: token
+ username: octocat
+ items:
+ - source: secret/docker
+ path: docker
+
+ {{ if .secret }}
+
+- name: bar_password
+ key: org/repo/bar/password
+ engine: vault
+ type: repo
+
+ {{ end }}
+
+services:
+ - name: postgres
+ image: postgres:12
+
+ {{ if .service }}
+
+ - name: redis
+ key: redis:6
+
+ {{ end }}
diff --git a/compiler/native/testdata/nested.yml b/compiler/native/testdata/nested.yml
new file mode 100644
index 000000000..2d44729d7
--- /dev/null
+++ b/compiler/native/testdata/nested.yml
@@ -0,0 +1,23 @@
+metadata:
+ render_inline: true
+ template: true
+
+templates:
+ - name: golang
+ source: github.example.com/github/octocat/golang_inline_stages.yml
+ format: golang
+ type: github
+ vars:
+ image: golang:latest
+ - name: starlark
+ source: github.example.com/github/octocat/starlark_inline_stages.star
+ format: starlark
+ type: github
+
+stages:
+ test:
+ steps:
+ - name: test
+ image: alpine
+ commands:
+ - echo from inline
\ No newline at end of file
diff --git a/compiler/native/testdata/services.yml b/compiler/native/testdata/services.yml
new file mode 100644
index 000000000..c4d0288a4
--- /dev/null
+++ b/compiler/native/testdata/services.yml
@@ -0,0 +1,6 @@
+---
+services:
+ - name: cache
+ image: redis
+ - name: database
+ image: mongo
\ No newline at end of file
diff --git a/compiler/native/testdata/stage_inline_template.yml b/compiler/native/testdata/stage_inline_template.yml
new file mode 100644
index 000000000..7d25f4328
--- /dev/null
+++ b/compiler/native/testdata/stage_inline_template.yml
@@ -0,0 +1,21 @@
+version: "1"
+
+metadata:
+ render_inline: true
+
+templates:
+ - name: golang
+ source: github.example.com/github/octocat/golang_inline_stages.yml
+ format: golang
+ type: github
+ vars:
+ image: golang:latest
+
+stages:
+ test:
+ steps:
+ - name: golang
+ template:
+ name: golang
+ vars:
+ image: golang:latest
\ No newline at end of file
diff --git a/compiler/native/testdata/stages_pipeline_template.yml b/compiler/native/testdata/stages_pipeline_template.yml
index 0354ec737..25189ed73 100644
--- a/compiler/native/testdata/stages_pipeline_template.yml
+++ b/compiler/native/testdata/stages_pipeline_template.yml
@@ -6,7 +6,7 @@ metadata:
templates:
- name: gradle
- source: github.example.com/github/octocat/template.yml
+ source: github.example.com/github/octocat/long_template.yml
type: github
stages:
diff --git a/compiler/native/testdata/starlark_inline_stages.star b/compiler/native/testdata/starlark_inline_stages.star
new file mode 100644
index 000000000..55ffee07f
--- /dev/null
+++ b/compiler/native/testdata/starlark_inline_stages.star
@@ -0,0 +1,25 @@
+def main(ctx):
+ stageNames = ["foo", "bar"]
+
+ stages = {}
+
+ for name in stageNames:
+ stages[name] = stage(name)
+
+ return {
+ 'version': '1',
+ 'stages': stages
+ }
+
+def stage(word):
+ return {
+ "steps": [
+ {
+ "name": "build_%s" % word,
+ "image": "alpine",
+ 'commands': [
+ "echo hello from %s" % word
+ ]
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/compiler/native/testdata/starlark_inline_steps.star b/compiler/native/testdata/starlark_inline_steps.star
new file mode 100644
index 000000000..51a2e6603
--- /dev/null
+++ b/compiler/native/testdata/starlark_inline_steps.star
@@ -0,0 +1,20 @@
+def main(ctx):
+ stepNames = ["foo", "bar"]
+
+ steps = []
+
+ for name in stepNames:
+ steps.append(
+ {
+ "name": "build_%s" % name,
+ "image": "alpine",
+ 'commands': [
+ "echo hello from %s" % name
+ ]
+ }
+ )
+
+ return {
+ 'version': '1',
+ 'steps': steps
+ }
\ No newline at end of file
diff --git a/compiler/native/testdata/step_inline_template.yml b/compiler/native/testdata/step_inline_template.yml
new file mode 100644
index 000000000..6b29e2aa3
--- /dev/null
+++ b/compiler/native/testdata/step_inline_template.yml
@@ -0,0 +1,19 @@
+version: "1"
+
+metadata:
+ render_inline: true
+
+templates:
+ - name: golang
+ source: github.example.com/github/octocat/golang_inline_stages.yml
+ format: golang
+ type: github
+ vars:
+ image: golang:latest
+
+steps:
+ - name: golang
+ template:
+ name: golang
+ vars:
+ image: golang:latest
\ No newline at end of file
diff --git a/compiler/native/testdata/steps_pipeline_template.yml b/compiler/native/testdata/steps_pipeline_template.yml
index 9e8402e8e..7d8e8db28 100644
--- a/compiler/native/testdata/steps_pipeline_template.yml
+++ b/compiler/native/testdata/steps_pipeline_template.yml
@@ -5,7 +5,7 @@ metadata:
templates:
- name: gradle
- source: github.example.com/foo/bar/template.yml
+ source: github.example.com/foo/bar/long_template.yml
type: github
steps:
diff --git a/compiler/native/testdata/template-gradle.json b/compiler/native/testdata/template-gradle.json
deleted file mode 100644
index 8ad5f9091..000000000
--- a/compiler/native/testdata/template-gradle.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "type": "file",
- "encoding": "base64",
- "size": 5362,
- "name": "gradle.yml",
- "path": "gradle.yml",
- "content": "bWV0YWRhdGE6CiAgdGVtcGxhdGU6IHRydWUKCnN0ZXBzOgogIC0gbmFtZTogaW5zdGFsbAogICAgY29tbWFuZHM6CiAgICAgIC0gLi9ncmFkbGV3IGRvd25sb2FkRGVwZW5kZW5jaWVzCiAgICBlbnZpcm9ubWVudDoge3sgLmVudmlyb25tZW50IH19CiAgICBpbWFnZToge3sgLmltYWdlIH19CiAgICB7eyAucHVsbF9wb2xpY3kgfX0KCiAgLSBuYW1lOiB0ZXN0CiAgICBjb21tYW5kczoKICAgICAgLSAuL2dyYWRsZXcgY2hlY2sKICAgIGVudmlyb25tZW50OiB7eyAuZW52aXJvbm1lbnQgfX0KICAgIGltYWdlOiB7eyAuaW1hZ2UgfX0KICAgIHt7IC5wdWxsX3BvbGljeSB9fQoKICAtIG5hbWU6IGJ1aWxkCiAgICBjb21tYW5kczoKICAgICAgLSAuL2dyYWRsZXcgYnVpbGQKICAgIGVudmlyb25tZW50OiB7eyAuZW52aXJvbm1lbnQgfX0KICAgIGltYWdlOiB7eyAuaW1hZ2UgfX0KICAgIHt7IC5wdWxsX3BvbGljeSB9fQoKc2VjcmV0czoKICAtIG5hbWU6IGRvY2tlcl91c2VybmFtZQogICAga2V5OiBvcmcvcmVwby9mb28vYmFyCiAgICBlbmdpbmU6IG5hdGl2ZQogICAgdHlwZTogcmVwbwoKICAtIG5hbWU6IGZvb19wYXNzd29yZAogICAga2V5OiBvcmcvcmVwby9mb28vcGFzc3dvcmQKICAgIGVuZ2luZTogdmF1bHQKICAgIHR5cGU6IHJlcG8KCiAgLSBuYW1lOiB2YXVsdF90b2tlbgoKICAtIG9yaWdpbjoKICAgICAgbmFtZTogcHJpdmF0ZSB2YXVsdAogICAgICBpbWFnZTogdGFyZ2V0L3NlY3JldC12YXVsdDpsYXRlc3QKICAgICAgcHVsbDogYWx3YXlzCiAgICAgIHNlY3JldHM6IFsgdmF1bHRfdG9rZW4gXQogICAgICBwYXJhbWV0ZXJzOgogICAgICAgIGFkZHI6IHZhdWx0LmV4YW1wbGUuY29tCiAgICAgICAgYXV0aF9tZXRob2Q6IHRva2VuCiAgICAgICAgdXNlcm5hbWU6IG9jdG9jYXQKICAgICAgICBpdGVtczoKICAgICAgICAgIC0gc291cmNlOiBzZWNyZXQvZG9ja2VyCiAgICAgICAgICAgIHBhdGg6IGRvY2tlcgoKe3sgaWYgLnNlY3JldCB9fQoKICAtIG5hbWU6IGJhcl9wYXNzd29yZAogICAga2V5OiBvcmcvcmVwby9iYXIvcGFzc3dvcmQKICAgIGVuZ2luZTogdmF1bHQKICAgIHR5cGU6IHJlcG8KCnt7IGVuZCB9fQoKc2VydmljZXM6CiAgIC0gbmFtZTogcG9zdGdyZXMKICAgICBpbWFnZTogcG9zdGdyZXM6MTIKCiB7eyBpZiAuc2VydmljZSB9fQoKICAgLSBuYW1lOiByZWRpcwogICAgIGtleTogcmVkaXM6NgoKIHt7IGVuZCB9fQo=",
- "sha": "3d21ec53a331a6f037a91c368710b99387d012c1",
- "url": "https://api.github.com/repos/octokit/octokit.rb/contents/gradle.yml",
- "git_url": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1",
- "html_url": "https://github.com/octokit/octokit.rb/blob/master/gradle.yml",
- "download_url": "https://raw.githubusercontent.com/octokit/octokit.rb/master/gradle.yml",
- "_links": {
- "git": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1",
- "self": "https://api.github.com/repos/octokit/octokit.rb/contents/gradle.yml",
- "html": "https://github.com/octokit/octokit.rb/blob/master/gradle.yml"
- }
-}
diff --git a/compiler/native/testdata/template-maven.json b/compiler/native/testdata/template-maven.json
deleted file mode 100644
index afc763414..000000000
--- a/compiler/native/testdata/template-maven.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "type": "file",
- "encoding": "base64",
- "size": 5362,
- "name": "maven.yml",
- "path": "maven.yml",
- "content": "bWV0YWRhdGE6CiAgdGVtcGxhdGU6IHRydWUKCnN0ZXBzOgogIC0gbmFtZTogaW5zdGFsbAogICAgY29tbWFuZHM6CiAgICAgIC0gbXZuIGRvd25sb2FkRGVwZW5kZW5jaWVzCiAgICBlbnZpcm9ubWVudDoge3sgLmVudmlyb25tZW50IH19CiAgICBpbWFnZToge3sgLmltYWdlIH19CiAgICB7eyAucHVsbF9wb2xpY3kgfX0KCiAgLSBuYW1lOiB0ZXN0CiAgICBjb21tYW5kczoKICAgICAgLSBtdm4gY2hlY2sKICAgIGVudmlyb25tZW50OiB7eyAuZW52aXJvbm1lbnQgfX0KICAgIGltYWdlOiB7eyAuaW1hZ2UgfX0KICAgIHt7IC5wdWxsX3BvbGljeSB9fQoKICAtIG5hbWU6IGJ1aWxkCiAgICBjb21tYW5kczoKICAgICAgLSBtdm4gYnVpbGQKICAgIGVudmlyb25tZW50OiB7eyAuZW52aXJvbm1lbnQgfX0KICAgIGltYWdlOiB7eyAuaW1hZ2UgfX0KICAgIHt7IC5wdWxsX3BvbGljeSB9fQoKc2VjcmV0czoKICAtIG5hbWU6IGRvY2tlcl91c2VybmFtZQogICAga2V5OiBvcmcvcmVwby9mb28vYmFyCiAgICBlbmdpbmU6IG5hdGl2ZQogICAgdHlwZTogcmVwbwoKICAtIG5hbWU6IGZvb19wYXNzd29yZAogICAga2V5OiBvcmcvcmVwby9mb28vcGFzc3dvcmQKICAgIGVuZ2luZTogdmF1bHQKICAgIHR5cGU6IHJlcG8KCiAgLSBuYW1lOiB2YXVsdF90b2tlbgoKICAtIG9yaWdpbjoKICAgICAgbmFtZTogcHJpdmF0ZSB2YXVsdAogICAgICBpbWFnZTogdGFyZ2V0L3NlY3JldC12YXVsdDpsYXRlc3QKICAgICAgcHVsbDogYWx3YXlzCiAgICAgIHNlY3JldHM6IFsgdmF1bHRfdG9rZW4gXQogICAgICBwYXJhbWV0ZXJzOgogICAgICAgIGFkZHI6IHZhdWx0LmV4YW1wbGUuY29tCiAgICAgICAgYXV0aF9tZXRob2Q6IHRva2VuCiAgICAgICAgdXNlcm5hbWU6IG9jdG9jYXQKICAgICAgICBpdGVtczoKICAgICAgICAgIC0gc291cmNlOiBzZWNyZXQvZG9ja2VyCiAgICAgICAgICAgIHBhdGg6IGRvY2tlcgoKe3sgaWYgLnNlY3JldCB9fQoKICAtIG5hbWU6IGJhcl9wYXNzd29yZAogICAga2V5OiBvcmcvcmVwby9iYXIvcGFzc3dvcmQKICAgIGVuZ2luZTogdmF1bHQKICAgIHR5cGU6IHJlcG8KCnt7IGVuZCB9fQoKc2VydmljZXM6CiAgIC0gbmFtZTogcG9zdGdyZXMKICAgICBpbWFnZTogcG9zdGdyZXM6MTIKCiB7eyBpZiAuc2VydmljZSB9fQoKICAgLSBuYW1lOiByZWRpcwogICAgIGtleTogcmVkaXM6NgoKIHt7IGVuZCB9fQo=",
- "sha": "3d21ec53a331a6f037a91c368710b99387d012c1",
- "url": "https://api.github.com/repos/octokit/octokit.rb/contents/maven.yml",
- "git_url": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1",
- "html_url": "https://github.com/octokit/octokit.rb/blob/master/maven.yml",
- "download_url": "https://raw.githubusercontent.com/octokit/octokit.rb/master/maven.yml",
- "_links": {
- "git": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1",
- "self": "https://api.github.com/repos/octokit/octokit.rb/contents/maven.yml",
- "html": "https://github.com/octokit/octokit.rb/blob/master/maven.yml"
- }
-}
diff --git a/compiler/native/testdata/template-starlark.json b/compiler/native/testdata/template-starlark.json
deleted file mode 100644
index 5a29da9df..000000000
--- a/compiler/native/testdata/template-starlark.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "type": "file",
- "encoding": "base64",
- "size": 5362,
- "name": "template.star",
- "path": "template.star",
- "content": "ZGVmIG1haW4oY3R4KToKICByZXR1cm4gewogICAgJ3ZlcnNpb24nOiAnMScsCiAgICAnZW52aXJvbm1lbnQnOiB7CiAgICAgICdzdGFyJzogJ3Rlc3QzJywKICAgICAgJ2Jhcic6ICd0ZXN0NCcsCiAgICB9LAogICAgJ3N0ZXBzJzogWwogICAgICB7CiAgICAgICAgJ25hbWUnOiAnYnVpbGQnLAogICAgICAgICdpbWFnZSc6ICdnb2xhbmc6bGF0ZXN0JywKICAgICAgICAnY29tbWFuZHMnOiBbCiAgICAgICAgICAnZ28gYnVpbGQnLAogICAgICAgICAgJ2dvIHRlc3QnLAogICAgICAgIF0KICAgICAgfSwKICAgIF0sCn0K\n",
- "sha": "3d21ec53a331a6f037a91c368710b99387d012c1",
- "url": "https://api.github.com/repos/octokit/octokit.rb/contents/template.star",
- "git_url": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1",
- "html_url": "https://github.com/octokit/octokit.rb/blob/master/template.star",
- "download_url": "https://raw.githubusercontent.com/octokit/octokit.rb/master/template.star",
- "_links": {
- "git": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1",
- "self": "https://api.github.com/repos/octokit/octokit.rb/contents/template.star",
- "html": "https://github.com/octokit/octokit.rb/blob/master/template.star"
- }
-}
diff --git a/compiler/native/testdata/template.json b/compiler/native/testdata/template.json
deleted file mode 100644
index f65b38008..000000000
--- a/compiler/native/testdata/template.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "type": "file",
- "encoding": "base64",
- "size": 5362,
- "name": "template.yml",
- "path": "template.yml",
- "content": "ZW52aXJvbm1lbnQ6CiAgc3RhcjogInRlc3QzIgogIGJhcjogInRlc3Q0IgoKbWV0YWRhdGE6CiAgdGVtcGxhdGU6IHRydWUKCnN0ZXBzOgogIC0gbmFtZTogaW5zdGFsbAogICAgY29tbWFuZHM6CiAgICAgIC0gLi9ncmFkbGV3IGRvd25sb2FkRGVwZW5kZW5jaWVzCiAgICBlbnZpcm9ubWVudDoge3sgLmVudmlyb25tZW50IH19CiAgICBpbWFnZToge3sgLmltYWdlIH19CiAgICB7eyAucHVsbF9wb2xpY3kgfX0KCiAgLSBuYW1lOiB0ZXN0CiAgICBjb21tYW5kczoKICAgICAgLSAuL2dyYWRsZXcgY2hlY2sKICAgIGVudmlyb25tZW50OiB7eyAuZW52aXJvbm1lbnQgfX0KICAgIGltYWdlOiB7eyAuaW1hZ2UgfX0KICAgIHt7IC5wdWxsX3BvbGljeSB9fQoKICAtIG5hbWU6IGJ1aWxkCiAgICBjb21tYW5kczoKICAgICAgLSAuL2dyYWRsZXcgYnVpbGQKICAgIGVudmlyb25tZW50OiB7eyAuZW52aXJvbm1lbnQgfX0KICAgIGltYWdlOiB7eyAuaW1hZ2UgfX0KICAgIHt7IC5wdWxsX3BvbGljeSB9fQoKc2VjcmV0czoKICAtIG5hbWU6IGRvY2tlcl91c2VybmFtZQogICAga2V5OiBvcmcvcmVwby9mb28vYmFyCiAgICBlbmdpbmU6IG5hdGl2ZQogICAgdHlwZTogcmVwbwoKICAtIG5hbWU6IGZvb19wYXNzd29yZAogICAga2V5OiBvcmcvcmVwby9mb28vcGFzc3dvcmQKICAgIGVuZ2luZTogdmF1bHQKICAgIHR5cGU6IHJlcG8KCnt7IGlmIC5zZWNyZXQgfX0KCiAgLSBuYW1lOiBiYXJfcGFzc3dvcmQKICAgIGtleTogb3JnL3JlcG8vYmFyL3Bhc3N3b3JkCiAgICBlbmdpbmU6IHZhdWx0CiAgICB0eXBlOiByZXBvCgp7eyBlbmQgfX0KCnNlcnZpY2VzOgogICAtIG5hbWU6IHBvc3RncmVzCiAgICAgaW1hZ2U6IHBvc3RncmVzOjEyCgoge3sgaWYgLnNlcnZpY2UgfX0KCiAgIC0gbmFtZTogcmVkaXMKICAgICBrZXk6IHJlZGlzOjYKCiB7eyBlbmQgfX0K",
- "sha": "3d21ec53a331a6f037a91c368710b99387d012c1",
- "url": "https://api.github.com/repos/octokit/octokit.rb/contents/template.yml",
- "git_url": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1",
- "html_url": "https://github.com/octokit/octokit.rb/blob/master/template.yml",
- "download_url": "https://raw.githubusercontent.com/octokit/octokit.rb/master/template.yml",
- "_links": {
- "git": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1",
- "self": "https://api.github.com/repos/octokit/octokit.rb/contents/template.yml",
- "html": "https://github.com/octokit/octokit.rb/blob/master/template.yml"
- }
-}
diff --git a/compiler/native/testdata/template.star b/compiler/native/testdata/template.star
index 079d70943..9c2815791 100644
--- a/compiler/native/testdata/template.star
+++ b/compiler/native/testdata/template.star
@@ -1,6 +1,10 @@
def main(ctx):
return {
'version': '1',
+ 'environment': {
+ 'star': 'test3',
+ 'bar': 'test4',
+ },
'steps': [
{
'name': 'build',
@@ -11,4 +15,4 @@ def main(ctx):
]
},
],
-}
\ No newline at end of file
+}
diff --git a/compiler/native/testdata/template_calls_itself.yml b/compiler/native/testdata/template_calls_itself.yml
new file mode 100644
index 000000000..9b467a675
--- /dev/null
+++ b/compiler/native/testdata/template_calls_itself.yml
@@ -0,0 +1,11 @@
+version: "1"
+
+templates:
+ - name: test
+ source: github.example.com/bad/design/template_calls_itself.yml
+ type: github
+
+steps:
+ - name: call template
+ template:
+ name: test
diff --git a/compiler/native/testdata/template_calls_template.yml b/compiler/native/testdata/template_calls_template.yml
new file mode 100644
index 000000000..b6197169d
--- /dev/null
+++ b/compiler/native/testdata/template_calls_template.yml
@@ -0,0 +1,15 @@
+version: "1"
+
+templates:
+ - name: test
+ source: github.example.com/foo/bar/long_template.yml
+ type: github
+
+steps:
+ - name: call template
+ template:
+ name: test
+ vars:
+ image: openjdk:latest
+ environment: "{ GRADLE_USER_HOME: .gradle, GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=1 -Dorg.gradle.parallel=false }"
+ pull_policy: "pull: true"
diff --git a/compiler/native/testdata/template_name.yml b/compiler/native/testdata/template_name.yml
new file mode 100644
index 000000000..652cdb2fd
--- /dev/null
+++ b/compiler/native/testdata/template_name.yml
@@ -0,0 +1,16 @@
+---
+version: "1"
+
+metadata:
+ template: false
+
+templates:
+ - name: inline_templatename
+ source: github.example.com/github/octocat/template_name_template.yml
+ type: github
+
+steps:
+ - name: sample
+ template:
+ name: inline_templatename
+
diff --git a/compiler/native/testdata/template_name_inline.yml b/compiler/native/testdata/template_name_inline.yml
new file mode 100644
index 000000000..f35801376
--- /dev/null
+++ b/compiler/native/testdata/template_name_inline.yml
@@ -0,0 +1,11 @@
+---
+version: "1"
+
+metadata:
+ template: false
+ render_inline: true
+
+templates:
+ - name: inline_templatename
+ source: github.example.com/github/octocat/template_name_template.yml
+ type: github
diff --git a/compiler/native/testdata/template_name_template.yml b/compiler/native/testdata/template_name_template.yml
new file mode 100644
index 000000000..3ff2c0262
--- /dev/null
+++ b/compiler/native/testdata/template_name_template.yml
@@ -0,0 +1,8 @@
+metadata:
+ template: true
+
+steps:
+ - name: hello
+ image: {{ vela "template_name" }}
+ commands:
+ - echo {{ vela "template_name" }}
diff --git a/compiler/native/transform.go b/compiler/native/transform.go
index 85be86d0b..4742ae700 100644
--- a/compiler/native/transform.go
+++ b/compiler/native/transform.go
@@ -33,7 +33,7 @@ const (
// default ID for secrets in a pipeline.
// format: `secret____`
//
- // nolint: gosec // ignore gosec keying off of secret as no credentials are hardcoded
+ //nolint:gosec // ignore gosec keying off of secret as no credentials are hardcoded
secretID = "secret_%s_%s_%d_%s"
)
diff --git a/compiler/native/validate.go b/compiler/native/validate.go
index 7d588a89f..dec7c7af9 100644
--- a/compiler/native/validate.go
+++ b/compiler/native/validate.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2020 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
@@ -7,45 +7,64 @@ package native
import (
"fmt"
+ "github.com/hashicorp/go-multierror"
+
"github.com/go-vela/types/yaml"
)
-// Validate verifies the the yaml configuration is valid.
+// Validate verifies the yaml configuration is valid.
func (c *client) Validate(p *yaml.Build) error {
+ var result error
// check a version is provided
if len(p.Version) == 0 {
- return fmt.Errorf("no version provided")
+ result = multierror.Append(result, fmt.Errorf("no \"version:\" YAML property provided"))
}
// check that stages or steps are provided
- if len(p.Stages) == 0 && len(p.Steps) == 0 {
- return fmt.Errorf("no stages or steps provided")
+ if len(p.Stages) == 0 && len(p.Steps) == 0 && (!p.Metadata.RenderInline && len(p.Templates) == 0) {
+ result = multierror.Append(result, fmt.Errorf("no stages, steps or templates provided"))
}
// check that stages and steps aren't provided
if len(p.Stages) > 0 && len(p.Steps) > 0 {
- return fmt.Errorf("stages and steps provided")
+ result = multierror.Append(result, fmt.Errorf("stages and steps provided"))
+ }
+
+ if p.Metadata.RenderInline {
+ for _, step := range p.Steps {
+ if step.Template.Name != "" {
+ result = multierror.Append(result, fmt.Errorf("step %s: cannot combine render_inline and a step that references a template", step.Name))
+ }
+ }
+
+ for _, stage := range p.Stages {
+ for _, step := range stage.Steps {
+ if step.Template.Name != "" {
+ result = multierror.Append(result, fmt.Errorf("step %s.%s: cannot combine render_inline and a step that references a template", stage.Name, step.Name))
+ }
+ }
+ }
}
// validate the services block provided
err := validateServices(p.Services)
if err != nil {
- return err
+ result = multierror.Append(result, err)
}
// validate the stages block provided
err = validateStages(p.Stages)
if err != nil {
- return err
+ result = multierror.Append(result, err)
}
// validate the steps block provided
err = validateSteps(p.Steps)
if err != nil {
- return err
+ result = multierror.Append(result, err)
}
- return nil
+ return result
}
// validateServices is a helper function that verifies the
@@ -84,7 +103,6 @@ func validateStages(s yaml.StageSlice) error {
return fmt.Errorf("no name provided for step for stage %s", stage.Name)
}
- // nolint: lll // ignore simplification here
if len(step.Image) == 0 && len(step.Template.Name) == 0 {
return fmt.Errorf("no image or template provided for step %s for stage %s", step.Name, stage.Name)
}
@@ -93,7 +111,6 @@ func validateStages(s yaml.StageSlice) error {
continue
}
- // nolint: lll // ignore simplification here
if len(step.Commands) == 0 && len(step.Environment) == 0 &&
len(step.Parameters) == 0 && len(step.Secrets) == 0 &&
len(step.Template.Name) == 0 && !step.Detach {
@@ -121,7 +138,6 @@ func validateSteps(s yaml.StepSlice) error {
continue
}
- // nolint: lll // ignore simplification here
if len(step.Commands) == 0 && len(step.Environment) == 0 &&
len(step.Parameters) == 0 && len(step.Secrets) == 0 &&
len(step.Template.Name) == 0 && !step.Detach {
diff --git a/compiler/native/validate_test.go b/compiler/native/validate_test.go
index 6380a2df2..772494aa7 100644
--- a/compiler/native/validate_test.go
+++ b/compiler/native/validate_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2020 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
diff --git a/compiler/registry/doc.go b/compiler/registry/doc.go
index 70cb52e2b..2064281e4 100644
--- a/compiler/registry/doc.go
+++ b/compiler/registry/doc.go
@@ -7,5 +7,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/compiler/registry"
+// import "github.com/go-vela/server/compiler/registry"
package registry
diff --git a/compiler/registry/github/doc.go b/compiler/registry/github/doc.go
index 87e9427a1..1bab3812e 100644
--- a/compiler/registry/github/doc.go
+++ b/compiler/registry/github/doc.go
@@ -8,5 +8,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/compiler/registry/github"
+// import "github.com/go-vela/server/compiler/registry/github"
package github
diff --git a/compiler/registry/github/github.go b/compiler/registry/github/github.go
index 22815b75a..7fb6f22b7 100644
--- a/compiler/registry/github/github.go
+++ b/compiler/registry/github/github.go
@@ -9,7 +9,7 @@ import (
"net/url"
"strings"
- "github.com/google/go-github/v42/github"
+ "github.com/google/go-github/v53/github"
"golang.org/x/oauth2"
)
@@ -27,7 +27,7 @@ type client struct {
// New returns a Registry implementation that integrates
// with GitHub or a GitHub Enterprise instance.
//
-// nolint: revive // ignore returning unexported client
+//nolint:revive // ignore returning unexported client
func New(address, token string) (*client, error) {
// create the client object
c := &client{
@@ -84,5 +84,6 @@ func (c *client) newClientToken(token string) *github.Client {
// ensure the proper URL is set
github.BaseURL, _ = url.Parse(c.API)
+
return github
}
diff --git a/compiler/registry/github/github_test.go b/compiler/registry/github/github_test.go
index 11d40700f..530b8f644 100644
--- a/compiler/registry/github/github_test.go
+++ b/compiler/registry/github/github_test.go
@@ -12,7 +12,7 @@ import (
"reflect"
"testing"
- "github.com/google/go-github/v42/github"
+ "github.com/google/go-github/v53/github"
"golang.org/x/oauth2"
)
@@ -125,6 +125,7 @@ func TestGithub_NewURL(t *testing.T) {
if got.URL != test.want.URL {
t.Errorf("New URL is %v, want %v", got.URL, test.want.URL)
}
+
if got.API != test.want.API {
t.Errorf("New API is %v, want %v", got.API, test.want.API)
}
diff --git a/compiler/registry/github/parse.go b/compiler/registry/github/parse.go
index 7e7a824c2..9c40cf1c8 100644
--- a/compiler/registry/github/parse.go
+++ b/compiler/registry/github/parse.go
@@ -34,13 +34,10 @@ func (c *client) Parse(path string) (*registry.Source, error) {
// this will handle multiple cases for the path:
// * //
// * ////
- // nolint: gomnd // ignore magic number
parts := strings.SplitN(u.Path, "/", 3)
// ensure org, repo and filename parts exist
- // nolint: gomnd // ignore magic number
if len(parts) < 3 {
- // nolint: lll // ignore long line length due to error message
return ®istry.Source{}, fmt.Errorf("invalid template source %s, must contain org/repo/path_to_template", path)
}
diff --git a/compiler/registry/github/template.go b/compiler/registry/github/template.go
index a712ef4c5..956a7608d 100644
--- a/compiler/registry/github/template.go
+++ b/compiler/registry/github/template.go
@@ -13,7 +13,7 @@ import (
"github.com/go-vela/types/library"
- "github.com/google/go-github/v42/github"
+ "github.com/google/go-github/v53/github"
)
// Template captures the templated pipeline configuration from the GitHub repo.
@@ -38,16 +38,18 @@ func (c *client) Template(u *library.User, s *registry.Source) ([]byte, error) {
// send API call to capture the templated pipeline configuration
//
- // nolint: lll // ignore long line length due to variable names
+
data, _, resp, err := cli.Repositories.GetContents(context.Background(), s.Org, s.Repo, s.Name, opts)
if err != nil {
if resp != nil && resp.StatusCode != http.StatusNotFound {
// return different error message depending on if a branch was provided
if len(s.Ref) == 0 {
- errString := "unexpected error fetching template %s/%s/%s: %v"
+ errString := "unexpected error fetching template %s/%s/%s: %w"
return nil, fmt.Errorf(errString, s.Org, s.Repo, s.Name, err)
}
- errString := "unexpected error fetching template %s/%s/%s@%s: %v"
+
+ errString := "unexpected error fetching template %s/%s/%s@%s: %w"
+
return nil, fmt.Errorf(errString, s.Org, s.Repo, s.Name, s.Ref, err)
}
@@ -55,6 +57,7 @@ func (c *client) Template(u *library.User, s *registry.Source) ([]byte, error) {
if len(s.Ref) == 0 {
return nil, fmt.Errorf("no Vela template found at %s/%s/%s", s.Org, s.Repo, s.Name)
}
+
return nil, fmt.Errorf("no Vela template found at %s/%s/%s@%s", s.Org, s.Repo, s.Name, s.Ref)
}
@@ -72,5 +75,6 @@ func (c *client) Template(u *library.User, s *registry.Source) ([]byte, error) {
if len(s.Ref) == 0 {
return nil, fmt.Errorf("no Vela template found at %s/%s/%s", s.Org, s.Repo, s.Name)
}
+
return nil, fmt.Errorf("no Vela template found at %s/%s/%s@%s", s.Org, s.Repo, s.Name, s.Ref)
}
diff --git a/compiler/registry/github/template_test.go b/compiler/registry/github/template_test.go
index bffa1d47c..af59a6d61 100644
--- a/compiler/registry/github/template_test.go
+++ b/compiler/registry/github/template_test.go
@@ -5,9 +5,9 @@
package github
import (
- "io/ioutil"
"net/http"
"net/http/httptest"
+ "os"
"reflect"
"testing"
@@ -21,6 +21,7 @@ import (
func TestGithub_Template(t *testing.T) {
// setup context
gin.SetMode(gin.TestMode)
+
resp := httptest.NewRecorder()
_, engine := gin.CreateTestContext(resp)
@@ -30,7 +31,9 @@ func TestGithub_Template(t *testing.T) {
c.Status(http.StatusOK)
c.File("testdata/template.json")
})
+
s := httptest.NewServer(engine)
+
defer s.Close()
// setup types
@@ -46,7 +49,7 @@ func TestGithub_Template(t *testing.T) {
Name: "template.yml",
}
- want, err := ioutil.ReadFile("testdata/template.yml")
+ want, err := os.ReadFile("testdata/template.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
@@ -75,6 +78,7 @@ func TestGithub_Template(t *testing.T) {
func TestGithub_TemplateSourceRef(t *testing.T) {
// setup context
gin.SetMode(gin.TestMode)
+
resp := httptest.NewRecorder()
_, engine := gin.CreateTestContext(resp)
@@ -88,7 +92,9 @@ func TestGithub_TemplateSourceRef(t *testing.T) {
c.Status(http.StatusOK)
c.File("testdata/template.json")
})
+
s := httptest.NewServer(engine)
+
defer s.Close()
// setup types
@@ -105,7 +111,7 @@ func TestGithub_TemplateSourceRef(t *testing.T) {
Ref: "main",
}
- want, err := ioutil.ReadFile("testdata/template.yml")
+ want, err := os.ReadFile("testdata/template.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
@@ -138,6 +144,7 @@ func TestGithub_TemplateSourceRef(t *testing.T) {
func TestGithub_TemplateEmptySourceRef(t *testing.T) {
// setup context
gin.SetMode(gin.TestMode)
+
resp := httptest.NewRecorder()
_, engine := gin.CreateTestContext(resp)
@@ -151,7 +158,9 @@ func TestGithub_TemplateEmptySourceRef(t *testing.T) {
c.Status(http.StatusOK)
c.File("testdata/template.json")
})
+
s := httptest.NewServer(engine)
+
defer s.Close()
// setup types
@@ -167,7 +176,7 @@ func TestGithub_TemplateEmptySourceRef(t *testing.T) {
Name: "template.yml",
}
- want, err := ioutil.ReadFile("testdata/template.yml")
+ want, err := os.ReadFile("testdata/template.yml")
if err != nil {
t.Errorf("Reading file returned err: %v", err)
}
@@ -200,6 +209,7 @@ func TestGithub_TemplateEmptySourceRef(t *testing.T) {
func TestGithub_Template_BadRequest(t *testing.T) {
// setup context
gin.SetMode(gin.TestMode)
+
resp := httptest.NewRecorder()
_, engine := gin.CreateTestContext(resp)
@@ -207,7 +217,9 @@ func TestGithub_Template_BadRequest(t *testing.T) {
engine.GET("/api/v3/repos/foo/bar/contents/:path", func(c *gin.Context) {
c.Status(http.StatusBadRequest)
})
+
s := httptest.NewServer(engine)
+
defer s.Close()
// setup types
@@ -247,6 +259,7 @@ func TestGithub_Template_BadRequest(t *testing.T) {
func TestGithub_Template_NotFound(t *testing.T) {
// setup context
gin.SetMode(gin.TestMode)
+
resp := httptest.NewRecorder()
_, engine := gin.CreateTestContext(resp)
@@ -254,7 +267,9 @@ func TestGithub_Template_NotFound(t *testing.T) {
engine.GET("/api/v3/repos/foo/bar/contents/:path", func(c *gin.Context) {
c.Status(http.StatusNotFound)
})
+
s := httptest.NewServer(engine)
+
defer s.Close()
// setup types
diff --git a/compiler/template/doc.go b/compiler/template/doc.go
index 66c9d028b..f9e37fb29 100644
--- a/compiler/template/doc.go
+++ b/compiler/template/doc.go
@@ -8,5 +8,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/template"
+// import "github.com/go-vela/server/template"
package template
diff --git a/compiler/template/native/convert.go b/compiler/template/native/convert.go
index ed83f9ff3..68385c6cb 100644
--- a/compiler/template/native/convert.go
+++ b/compiler/template/native/convert.go
@@ -1,3 +1,7 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
package native
import (
@@ -13,9 +17,23 @@ import (
// within the template.
func convertPlatformVars(slice raw.StringSliceMap, name string) raw.StringSliceMap {
envs := make(map[string]string)
+
+ // iterate through the list of key/value pairs provided
for key, value := range slice {
+ // lowercase the key
key = strings.ToLower(key)
+
+ // check if the key has a 'deployment_parameter_*' prefix
+ if strings.HasPrefix(key, "deployment_parameter_") {
+ // add the key/value pair with the 'deployment_parameter_` prefix
+ //
+ // this is used to ensure we prevent conflicts with `vela_*` prefixed variables
+ envs[key] = value
+ }
+
+ // check if the key has a 'vela_*' prefix
if strings.HasPrefix(key, "vela_") {
+ // add the key/value pair without the 'vela_` prefix
envs[strings.TrimPrefix(key, "vela_")] = value
}
}
@@ -29,7 +47,7 @@ func convertPlatformVars(slice raw.StringSliceMap, name string) raw.StringSliceM
// always return a string, even on marshal error (empty string).
//
// This code is under copyright (full attribution in NOTICE) and is from:
-// nolint: lll // ignore long line length due to url
+
// https://github.com/helm/helm/blob/a499b4b179307c267bdf3ec49b880e3dbd2a5591/pkg/engine/funcs.go#L83
//
// This is designed to be called from a template.
@@ -39,6 +57,7 @@ func toYAML(v interface{}) string {
// Swallow errors inside of a template.
return ""
}
+
return strings.TrimSuffix(string(data), "\n")
}
@@ -48,14 +67,21 @@ type funcHandler struct {
// returnPlatformVar returns the value of the platform
// variable if it exists within the environment map.
-func (h funcHandler) returnPlatformVar(input string) string {
- input = strings.ToLower(input)
- input = strings.TrimPrefix(input, "vela_")
- // check if key exists within map
- if _, ok := h.envs[input]; ok {
- // return value if exists
- return h.envs[input]
+func (h funcHandler) returnPlatformVar(key string) string {
+ // lowercase the key
+ key = strings.ToLower(key)
+
+ // iterate through the list of possible prefixes to look for
+ for _, prefix := range []string{"deployment_parameter_", "vela_"} {
+ // trim the prefix from the input key
+ trimmed := strings.TrimPrefix(key, prefix)
+ // check if the key exists within map
+ if _, ok := h.envs[trimmed]; ok {
+ // return the non-prefixed value if exists
+ return h.envs[trimmed]
+ }
}
+
// return empty string if not exists
return ""
}
diff --git a/compiler/template/native/convert_test.go b/compiler/template/native/convert_test.go
index 525e6a027..db0b42a8f 100644
--- a/compiler/template/native/convert_test.go
+++ b/compiler/template/native/convert_test.go
@@ -1,3 +1,7 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
package native
import (
@@ -14,6 +18,14 @@ func Test_convertPlatformVars(t *testing.T) {
templateName string
want raw.StringSliceMap
}{
+ {
+ name: "with all deployment parameter prefixed vars",
+ slice: raw.StringSliceMap{
+ "DEPLOYMENT_PARAMETER_IMAGE": "alpine:3.14",
+ },
+ templateName: "foo",
+ want: raw.StringSliceMap{"deployment_parameter_image": "alpine:3.14", "template_name": "foo"},
+ },
{
name: "with all vela prefixed vars",
slice: raw.StringSliceMap{
@@ -26,17 +38,21 @@ func Test_convertPlatformVars(t *testing.T) {
want: raw.StringSliceMap{"build_author": "octocat", "repo_full_name": "go-vela/hello-world", "user_admin": "true", "workspace": "/vela/src/github.com/go-vela/hello-world", "template_name": "foo"},
},
{
- name: "with combination of vela and user vars",
+ name: "with combination of deployment parameter, vela, and user vars",
slice: raw.StringSliceMap{
- "VELA_BUILD_AUTHOR": "octocat",
- "VELA_REPO_FULL_NAME": "go-vela/hello-world",
- "FOO_VAR1": "test1",
- "BAR_VAR1": "test2",
+ "DEPLOYMENT_PARAMETER_IMAGE": "alpine:3.14",
+ "VELA_BUILD_AUTHOR": "octocat",
+ "VELA_REPO_FULL_NAME": "go-vela/hello-world",
+ "VELA_USER_ADMIN": "true",
+ "VELA_WORKSPACE": "/vela/src/github.com/go-vela/hello-world",
+ "FOO_VAR1": "test1",
+ "BAR_VAR1": "test2",
},
templateName: "foo",
- want: raw.StringSliceMap{"build_author": "octocat", "repo_full_name": "go-vela/hello-world", "template_name": "foo"},
+ want: raw.StringSliceMap{"deployment_parameter_image": "alpine:3.14", "build_author": "octocat", "repo_full_name": "go-vela/hello-world", "user_admin": "true", "workspace": "/vela/src/github.com/go-vela/hello-world", "template_name": "foo"},
},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := convertPlatformVars(tt.slice, tt.templateName); !reflect.DeepEqual(got, tt.want) {
@@ -50,15 +66,57 @@ func Test_funcHandler_returnPlatformVar(t *testing.T) {
type fields struct {
envs raw.StringSliceMap
}
+
type args struct {
input string
}
+
tests := []struct {
name string
fields fields
args args
want string
}{
+ {
+ name: "existing deployment parameter without prefix (lowercase)",
+ fields: fields{
+ envs: raw.StringSliceMap{
+ "image": "alpine",
+ },
+ },
+ args: args{input: "image"},
+ want: "alpine",
+ },
+ {
+ name: "existing deployment parameter without prefix (uppercase)",
+ fields: fields{
+ envs: raw.StringSliceMap{
+ "image": "alpine",
+ },
+ },
+ args: args{input: "IMAGE"},
+ want: "alpine",
+ },
+ {
+ name: "existing deployment parameter with prefix (lowercase)",
+ fields: fields{
+ envs: raw.StringSliceMap{
+ "image": "alpine",
+ },
+ },
+ args: args{input: "deployment_parameter_image"},
+ want: "alpine",
+ },
+ {
+ name: "existing deployment parameter with prefix (uppercase)",
+ fields: fields{
+ envs: raw.StringSliceMap{
+ "image": "alpine",
+ },
+ },
+ args: args{input: "DEPLOYMENT_PARAMETER_IMAGE"},
+ want: "alpine",
+ },
{
name: "existing platform var without prefix (lowercase)",
fields: fields{
@@ -110,6 +168,7 @@ func Test_funcHandler_returnPlatformVar(t *testing.T) {
want: "",
},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := funcHandler{
diff --git a/compiler/template/native/doc.go b/compiler/template/native/doc.go
index 55af6bfcb..34c41a275 100644
--- a/compiler/template/native/doc.go
+++ b/compiler/template/native/doc.go
@@ -8,5 +8,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/compiler/template/native"
+// import "github.com/go-vela/server/compiler/template/native"
package native
diff --git a/compiler/template/native/render.go b/compiler/template/native/render.go
index b71cf7afc..869e4588a 100644
--- a/compiler/template/native/render.go
+++ b/compiler/template/native/render.go
@@ -1,3 +1,7 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
package native
import (
@@ -14,13 +18,12 @@ import (
"github.com/buildkite/yaml"
)
-// RenderStep combines the template with the step in the yaml pipeline.
-// nolint: lll // ignore long line length due to return args
-func RenderStep(tmpl string, s *types.Step) (types.StepSlice, types.SecretSlice, types.ServiceSlice, raw.StringSliceMap, error) {
+// Render combines the template with the step in the yaml pipeline.
+func Render(tmpl string, name string, tName string, environment raw.StringSliceMap, variables map[string]interface{}) (*types.Build, error) {
buffer := new(bytes.Buffer)
config := new(types.Build)
- velaFuncs := funcHandler{envs: convertPlatformVars(s.Environment, s.Name)}
+ velaFuncs := funcHandler{envs: convertPlatformVars(environment, name)}
templateFuncMap := map[string]interface{}{
"vela": velaFuncs.returnPlatformVar,
"toYaml": toYAML,
@@ -36,40 +39,37 @@ func RenderStep(tmpl string, s *types.Step) (types.StepSlice, types.SecretSlice,
// parse the template with Masterminds/sprig functions
//
// https://pkg.go.dev/github.com/Masterminds/sprig?tab=doc#TxtFuncMap
- t, err := template.New(s.Name).Funcs(sf).Funcs(templateFuncMap).Parse(tmpl)
+ t, err := template.New(name).Funcs(sf).Funcs(templateFuncMap).Parse(tmpl)
if err != nil {
- // nolint: lll // ignore long line length due to return arguments
- return types.StepSlice{}, types.SecretSlice{}, types.ServiceSlice{}, raw.StringSliceMap{}, fmt.Errorf("unable to parse template %s: %v", s.Template.Name, err)
+ return nil, fmt.Errorf("unable to parse template %s: %w", tName, err)
}
// apply the variables to the parsed template
- err = t.Execute(buffer, s.Template.Variables)
+ err = t.Execute(buffer, variables)
if err != nil {
- // nolint: lll // ignore long line length due to return arguments
- return types.StepSlice{}, types.SecretSlice{}, types.ServiceSlice{}, raw.StringSliceMap{}, fmt.Errorf("unable to execute template %s: %v", s.Template.Name, err)
+ return nil, fmt.Errorf("unable to execute template %s: %w", tName, err)
}
// unmarshal the template to the pipeline
err = yaml.Unmarshal(buffer.Bytes(), config)
if err != nil {
- // nolint: lll // ignore long line length due to return args
- return types.StepSlice{}, types.SecretSlice{}, types.ServiceSlice{}, raw.StringSliceMap{}, fmt.Errorf("unable to unmarshal yaml: %v", err)
+ return nil, fmt.Errorf("unable to unmarshal yaml: %w", err)
}
// ensure all templated steps have template prefix
for index, newStep := range config.Steps {
- config.Steps[index].Name = fmt.Sprintf("%s_%s", s.Name, newStep.Name)
+ config.Steps[index].Name = fmt.Sprintf("%s_%s", name, newStep.Name)
}
- return config.Steps, config.Secrets, config.Services, config.Environment, nil
+ return &types.Build{Metadata: config.Metadata, Steps: config.Steps, Secrets: config.Secrets, Services: config.Services, Environment: config.Environment, Templates: config.Templates}, nil
}
// RenderBuild renders the templated build.
-func RenderBuild(b string, envs map[string]string) (*types.Build, error) {
+func RenderBuild(tmpl string, b string, envs map[string]string, variables map[string]interface{}) (*types.Build, error) {
buffer := new(bytes.Buffer)
config := new(types.Build)
- velaFuncs := funcHandler{envs: convertPlatformVars(envs, "")}
+ velaFuncs := funcHandler{envs: convertPlatformVars(envs, tmpl)}
templateFuncMap := map[string]interface{}{
"vela": velaFuncs.returnPlatformVar,
"toYaml": toYAML,
@@ -85,13 +85,13 @@ func RenderBuild(b string, envs map[string]string) (*types.Build, error) {
// parse the template with Masterminds/sprig functions
//
// https://pkg.go.dev/github.com/Masterminds/sprig?tab=doc#TxtFuncMap
- t, err := template.New("build").Funcs(sf).Funcs(templateFuncMap).Parse(b)
+ t, err := template.New(tmpl).Funcs(sf).Funcs(templateFuncMap).Parse(b)
if err != nil {
return nil, err
}
// execute the template
- err = t.Execute(buffer, "")
+ err = t.Execute(buffer, variables)
if err != nil {
return nil, fmt.Errorf("unable to execute template: %w", err)
}
diff --git a/compiler/template/native/render_test.go b/compiler/template/native/render_test.go
index 04dd9fef4..1380f6703 100644
--- a/compiler/template/native/render_test.go
+++ b/compiler/template/native/render_test.go
@@ -5,7 +5,7 @@
package native
import (
- "io/ioutil"
+ "os"
"testing"
goyaml "github.com/buildkite/yaml"
@@ -15,11 +15,12 @@ import (
"github.com/go-vela/types/yaml"
)
-func TestNative_RenderStep(t *testing.T) {
+func TestNative_Render(t *testing.T) {
type args struct {
velaFile string
templateFile string
}
+
tests := []struct {
name string
args args
@@ -39,9 +40,10 @@ func TestNative_RenderStep(t *testing.T) {
{"disallowed env func", args{velaFile: "testdata/step/basic/step.yml", templateFile: "testdata/step/disallowed/tmpl_env.yml"}, "", true},
{"disallowed expandenv func", args{velaFile: "testdata/step/basic/step.yml", templateFile: "testdata/step/disallowed/tmpl_expandenv.yml"}, "", true},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- sFile, err := ioutil.ReadFile(tt.args.velaFile)
+ sFile, err := os.ReadFile(tt.args.velaFile)
if err != nil {
t.Error(err)
}
@@ -54,19 +56,19 @@ func TestNative_RenderStep(t *testing.T) {
"VELA_REPO_FULL_NAME": "octocat/hello-world",
}
- tmpl, err := ioutil.ReadFile(tt.args.templateFile)
+ tmpl, err := os.ReadFile(tt.args.templateFile)
if err != nil {
t.Error(err)
}
- steps, secrets, services, environment, err := RenderStep(string(tmpl), b.Steps[0])
+ tmplBuild, err := Render(string(tmpl), b.Steps[0].Name, b.Steps[0].Template.Name, b.Steps[0].Environment, b.Steps[0].Template.Variables)
if (err != nil) != tt.wantErr {
- t.Errorf("RenderStep() error = %v, wantErr %v", err, tt.wantErr)
+ t.Errorf("Render() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantErr != true {
- wFile, err := ioutil.ReadFile(tt.wantFile)
+ wFile, err := os.ReadFile(tt.wantFile)
if err != nil {
t.Error(err)
}
@@ -80,17 +82,17 @@ func TestNative_RenderStep(t *testing.T) {
wantServices := w.Services
wantEnvironment := w.Environment
- if diff := cmp.Diff(wantSteps, steps); diff != "" {
- t.Errorf("RenderStep() mismatch (-want +got):\n%s", diff)
+ if diff := cmp.Diff(wantSteps, tmplBuild.Steps); diff != "" {
+ t.Errorf("Render() mismatch (-want +got):\n%s", diff)
}
- if diff := cmp.Diff(wantSecrets, secrets); diff != "" {
- t.Errorf("RenderStep() mismatch (-want +got):\n%s", diff)
+ if diff := cmp.Diff(wantSecrets, tmplBuild.Secrets); diff != "" {
+ t.Errorf("Render() mismatch (-want +got):\n%s", diff)
}
- if diff := cmp.Diff(wantServices, services); diff != "" {
- t.Errorf("RenderStep() mismatch (-want +got):\n%s", diff)
+ if diff := cmp.Diff(wantServices, tmplBuild.Services); diff != "" {
+ t.Errorf("Render() mismatch (-want +got):\n%s", diff)
}
- if diff := cmp.Diff(wantEnvironment, environment); diff != "" {
- t.Errorf("RenderStep() mismatch (-want +got):\n%s", diff)
+ if diff := cmp.Diff(wantEnvironment, tmplBuild.Environment); diff != "" {
+ t.Errorf("Render() mismatch (-want +got):\n%s", diff)
}
}
})
@@ -101,6 +103,7 @@ func TestNative_RenderBuild(t *testing.T) {
type args struct {
velaFile string
}
+
tests := []struct {
name string
args args
@@ -111,24 +114,25 @@ func TestNative_RenderBuild(t *testing.T) {
{"stages", args{velaFile: "testdata/build/basic_stages/build.yml"}, "testdata/build/basic_stages/want.yml", false},
{"conditional match", args{velaFile: "testdata/build/conditional/build.yml"}, "testdata/build/conditional/want.yml", false},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- sFile, err := ioutil.ReadFile(tt.args.velaFile)
+ sFile, err := os.ReadFile(tt.args.velaFile)
if err != nil {
t.Error(err)
}
- got, err := RenderBuild(string(sFile), map[string]string{
+ got, err := RenderBuild("build", string(sFile), map[string]string{
"VELA_REPO_FULL_NAME": "octocat/hello-world",
"VELA_BUILD_BRANCH": "master",
- })
+ }, map[string]interface{}{})
if (err != nil) != tt.wantErr {
t.Errorf("RenderBuild() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantErr != true {
- wFile, err := ioutil.ReadFile(tt.wantFile)
+ wFile, err := os.ReadFile(tt.wantFile)
if err != nil {
t.Error(err)
}
diff --git a/compiler/template/starlark/convert.go b/compiler/template/starlark/convert.go
index 9112bf7d5..474086316 100644
--- a/compiler/template/starlark/convert.go
+++ b/compiler/template/starlark/convert.go
@@ -47,6 +47,7 @@ func convertTemplateVars(m map[string]interface{}) (*starlark.Dict, error) {
// https://pkg.go.dev/go.starlark.net/starlark#StringDict
func convertPlatformVars(slice raw.StringSliceMap, name string) (*starlark.Dict, error) {
build := starlark.NewDict(0)
+ deployment := starlark.NewDict(0)
repo := starlark.NewDict(0)
user := starlark.NewDict(0)
system := starlark.NewDict(0)
@@ -56,14 +57,22 @@ func convertPlatformVars(slice raw.StringSliceMap, name string) (*starlark.Dict,
if err != nil {
return nil, err
}
+
+ err = dict.SetKey(starlark.String("deployment"), deployment)
+ if err != nil {
+ return nil, err
+ }
+
err = dict.SetKey(starlark.String("repo"), repo)
if err != nil {
return nil, err
}
+
err = dict.SetKey(starlark.String("user"), user)
if err != nil {
return nil, err
}
+
err = dict.SetKey(starlark.String("system"), system)
if err != nil {
return nil, err
@@ -74,31 +83,47 @@ func convertPlatformVars(slice raw.StringSliceMap, name string) (*starlark.Dict,
return nil, err
}
+ // iterate through the list of key/value pairs provided
for key, value := range slice {
+ // lowercase the key
key = strings.ToLower(key)
- if strings.HasPrefix(key, "vela_") {
- key = strings.TrimPrefix(key, "vela_")
-
- switch {
- case strings.HasPrefix(key, "build_"):
- err := build.SetKey(starlark.String(strings.TrimPrefix(key, "build_")), starlark.String(value))
- if err != nil {
- return nil, err
- }
- case strings.HasPrefix(key, "repo_"):
- err := repo.SetKey(starlark.String(strings.TrimPrefix(key, "repo_")), starlark.String(value))
- if err != nil {
- return nil, err
- }
- case strings.HasPrefix(key, "user_"):
- err := user.SetKey(starlark.String(strings.TrimPrefix(key, "user_")), starlark.String(value))
- if err != nil {
- return nil, err
- }
- default:
- err := system.SetKey(starlark.String(key), starlark.String(value))
- if err != nil {
- return nil, err
+
+ // iterate through the list of possible prefixes to look for
+ for _, prefix := range []string{"deployment_parameter_", "vela_"} {
+ // check if the key has the prefix
+ if strings.HasPrefix(key, prefix) {
+ // trim the prefix from the input key
+ key = strings.TrimPrefix(key, prefix)
+
+ // check if the prefix is from 'vela_*'
+ if strings.EqualFold(prefix, "vela_") {
+ switch {
+ case strings.HasPrefix(key, "build_"):
+ err := build.SetKey(starlark.String(strings.TrimPrefix(key, "build_")), starlark.String(value))
+ if err != nil {
+ return nil, err
+ }
+ case strings.HasPrefix(key, "repo_"):
+ err := repo.SetKey(starlark.String(strings.TrimPrefix(key, "repo_")), starlark.String(value))
+ if err != nil {
+ return nil, err
+ }
+ case strings.HasPrefix(key, "user_"):
+ err := user.SetKey(starlark.String(strings.TrimPrefix(key, "user_")), starlark.String(value))
+ if err != nil {
+ return nil, err
+ }
+ default:
+ err := system.SetKey(starlark.String(key), starlark.String(value))
+ if err != nil {
+ return nil, err
+ }
+ }
+ } else { // prefix is from 'deployment_parameter_*'
+ err := deployment.SetKey(starlark.String(key), starlark.String(value))
+ if err != nil {
+ return nil, err
+ }
}
}
}
diff --git a/compiler/template/starlark/convert_test.go b/compiler/template/starlark/convert_test.go
index 77cd577ce..b6eb03bf8 100644
--- a/compiler/template/starlark/convert_test.go
+++ b/compiler/template/starlark/convert_test.go
@@ -20,24 +20,28 @@ func TestStarlark_Render_convertTemplateVars(t *testing.T) {
tags = append(tags, starlark.String("1.15"))
commands := starlark.NewDict(16)
+
err := commands.SetKey(starlark.String("test"), starlark.String("go test ./..."))
if err != nil {
t.Error(err)
}
strWant := starlark.NewDict(0)
+
err = strWant.SetKey(starlark.String("pull"), starlark.String("always"))
if err != nil {
t.Error(err)
}
arrayWant := starlark.NewDict(0)
+
err = arrayWant.SetKey(starlark.String("tags"), tags)
if err != nil {
t.Error(err)
}
mapWant := starlark.NewDict(0)
+
err = mapWant.SetKey(starlark.String("commands"), commands)
if err != nil {
t.Error(err)
@@ -78,27 +82,33 @@ func TestStarlark_Render_convertTemplateVars(t *testing.T) {
}
}
-func TestStarlark_Render_velaEnvironmentData(t *testing.T) {
+func TestStarlark_Render_convertPlatformVars(t *testing.T) {
// setup types
- build := starlark.NewDict(1)
+ build := starlark.NewDict(0)
err := build.SetKey(starlark.String("author"), starlark.String("octocat"))
if err != nil {
t.Error(err)
}
- repo := starlark.NewDict(1)
+ deployment := starlark.NewDict(0)
+ err = deployment.SetKey(starlark.String("image"), starlark.String("alpine:3.14"))
+ if err != nil {
+ t.Error(err)
+ }
+
+ repo := starlark.NewDict(0)
err = repo.SetKey(starlark.String("full_name"), starlark.String("go-vela/hello-world"))
if err != nil {
t.Error(err)
}
- user := starlark.NewDict(1)
+ user := starlark.NewDict(0)
err = user.SetKey(starlark.String("admin"), starlark.String("true"))
if err != nil {
t.Error(err)
}
- system := starlark.NewDict(2)
+ system := starlark.NewDict(0)
err = system.SetKey(starlark.String("template_name"), starlark.String("foo"))
if err != nil {
t.Error(err)
@@ -108,20 +118,76 @@ func TestStarlark_Render_velaEnvironmentData(t *testing.T) {
t.Error(err)
}
- withAllPre := starlark.NewDict(0)
- err = withAllPre.SetKey(starlark.String("build"), build)
+ // setup full dictionary
+ withAll := starlark.NewDict(0)
+ err = withAll.SetKey(starlark.String("build"), build)
+ if err != nil {
+ t.Error(err)
+ }
+ err = withAll.SetKey(starlark.String("deployment"), deployment)
+ if err != nil {
+ t.Error(err)
+ }
+ err = withAll.SetKey(starlark.String("repo"), repo)
+ if err != nil {
+ t.Error(err)
+ }
+ err = withAll.SetKey(starlark.String("user"), user)
+ if err != nil {
+ t.Error(err)
+ }
+ err = withAll.SetKey(starlark.String("system"), system)
+ if err != nil {
+ t.Error(err)
+ }
+
+ // setup vela dictionary
+ withAllVela := starlark.NewDict(0)
+ err = withAllVela.SetKey(starlark.String("build"), build)
+ if err != nil {
+ t.Error(err)
+ }
+ err = withAllVela.SetKey(starlark.String("deployment"), starlark.NewDict(0))
+ if err != nil {
+ t.Error(err)
+ }
+ err = withAllVela.SetKey(starlark.String("repo"), repo)
+ if err != nil {
+ t.Error(err)
+ }
+ err = withAllVela.SetKey(starlark.String("user"), user)
+ if err != nil {
+ t.Error(err)
+ }
+ err = withAllVela.SetKey(starlark.String("system"), system)
+ if err != nil {
+ t.Error(err)
+ }
+
+ // setup deployment dictionary
+ withAllDeployment := starlark.NewDict(0)
+ err = withAllDeployment.SetKey(starlark.String("build"), starlark.NewDict(0))
if err != nil {
t.Error(err)
}
- err = withAllPre.SetKey(starlark.String("repo"), repo)
+ err = withAllDeployment.SetKey(starlark.String("deployment"), deployment)
if err != nil {
t.Error(err)
}
- err = withAllPre.SetKey(starlark.String("user"), user)
+ err = withAllDeployment.SetKey(starlark.String("repo"), starlark.NewDict(0))
if err != nil {
t.Error(err)
}
- err = withAllPre.SetKey(starlark.String("system"), system)
+ err = withAllDeployment.SetKey(starlark.String("user"), starlark.NewDict(0))
+ if err != nil {
+ t.Error(err)
+ }
+ system = starlark.NewDict(0)
+ err = system.SetKey(starlark.String("template_name"), starlark.String("foo"))
+ if err != nil {
+ t.Error(err)
+ }
+ err = withAllDeployment.SetKey(starlark.String("system"), system)
if err != nil {
t.Error(err)
}
@@ -133,6 +199,14 @@ func TestStarlark_Render_velaEnvironmentData(t *testing.T) {
want *starlark.Dict
wantErr bool
}{
+ {
+ name: "with all deployment parameter prefixed vars",
+ slice: raw.StringSliceMap{
+ "DEPLOYMENT_PARAMETER_IMAGE": "alpine:3.14",
+ },
+ templateName: "foo",
+ want: withAllDeployment,
+ },
{
name: "with all vela prefixed var",
slice: raw.StringSliceMap{
@@ -142,9 +216,24 @@ func TestStarlark_Render_velaEnvironmentData(t *testing.T) {
"VELA_WORKSPACE": "/vela/src/github.com/go-vela/hello-world",
},
templateName: "foo",
- want: withAllPre,
+ want: withAllVela,
+ },
+ {
+ name: "with combination of deployment parameter, vela, and user vars",
+ slice: raw.StringSliceMap{
+ "DEPLOYMENT_PARAMETER_IMAGE": "alpine:3.14",
+ "VELA_BUILD_AUTHOR": "octocat",
+ "VELA_REPO_FULL_NAME": "go-vela/hello-world",
+ "VELA_USER_ADMIN": "true",
+ "VELA_WORKSPACE": "/vela/src/github.com/go-vela/hello-world",
+ "FOO_VAR1": "test1",
+ "BAR_VAR1": "test2",
+ },
+ templateName: "foo",
+ want: withAll,
},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := convertPlatformVars(tt.slice, tt.templateName)
diff --git a/compiler/template/starlark/render.go b/compiler/template/starlark/render.go
index 1d4cf1296..96b2f1b3d 100644
--- a/compiler/template/starlark/render.go
+++ b/compiler/template/starlark/render.go
@@ -8,8 +8,8 @@ import (
"bytes"
"errors"
"fmt"
-
"github.com/go-vela/types/raw"
+ "go.starlark.net/starlarkstruct"
yaml "github.com/buildkite/yaml"
types "github.com/go-vela/types/yaml"
@@ -30,58 +30,60 @@ var (
ErrInvalidPipelineReturn = errors.New("invalid pipeline return in template")
)
-// RenderStep combines the template with the step in the yaml pipeline.
-//
-// nolint: funlen,lll // ignore function length due to comments
-func RenderStep(tmpl string, s *types.Step) (types.StepSlice, types.SecretSlice, types.ServiceSlice, raw.StringSliceMap, error) {
+// Render combines the template with the step in the yaml pipeline.
+func Render(tmpl string, name string, tName string, environment raw.StringSliceMap, variables map[string]interface{}) (*types.Build, error) {
config := new(types.Build)
- thread := &starlark.Thread{Name: s.Name}
+ thread := &starlark.Thread{Name: name}
// arbitrarily limiting the steps of the thread to 5000 to help prevent infinite loops
// may need to further investigate spawning a separate POSIX process if user input is problematic
// see https://github.com/google/starlark-go/issues/160#issuecomment-466794230 for further details
- //
- // nolint: gomnd // ignore magic number
- thread.SetMaxExecutionSteps(5000)
- globals, err := starlark.ExecFile(thread, s.Template.Name, tmpl, nil)
+ thread.SetMaxExecutionSteps(GetStarlarkExecutionStepLimit())
+
+ predeclared := starlark.StringDict{"struct": starlark.NewBuiltin("struct", starlarkstruct.Make)}
+
+ globals, err := starlark.ExecFile(thread, tName, tmpl, predeclared)
+
if err != nil {
- return nil, nil, nil, nil, err
+ return nil, err
}
// check the provided template has a main function
mainVal, ok := globals["main"]
if !ok {
- return nil, nil, nil, nil, fmt.Errorf("%s: %s", ErrMissingMainFunc, s.Template.Name)
+ return nil, fmt.Errorf("%w: %s", ErrMissingMainFunc, tName)
}
// check the provided main is a function
main, ok := mainVal.(starlark.Callable)
if !ok {
- return nil, nil, nil, nil, fmt.Errorf("%s: %s", ErrInvalidMainFunc, s.Template.Name)
+ return nil, fmt.Errorf("%w: %s", ErrInvalidMainFunc, tName)
}
// load the user provided vars into a starlark type
- userVars, err := convertTemplateVars(s.Template.Variables)
+ userVars, err := convertTemplateVars(variables)
if err != nil {
- return nil, nil, nil, nil, err
+ return nil, err
}
// load the platform provided vars into a starlark type
- velaVars, err := convertPlatformVars(s.Environment, s.Name)
+ velaVars, err := convertPlatformVars(environment, name)
if err != nil {
- return nil, nil, nil, nil, err
+ return nil, err
}
// add the user and platform vars to a context to be used
// within the template caller i.e. ctx["vela"] or ctx["vars"]
context := starlark.NewDict(0)
+
err = context.SetKey(starlark.String("vela"), velaVars)
if err != nil {
- return nil, nil, nil, nil, err
+ return nil, err
}
+
err = context.SetKey(starlark.String("vars"), userVars)
if err != nil {
- return nil, nil, nil, nil, err
+ return nil, err
}
args := starlark.Tuple([]starlark.Value{context})
@@ -89,7 +91,7 @@ func RenderStep(tmpl string, s *types.Step) (types.StepSlice, types.SecretSlice,
// execute Starlark program from Go.
mainVal, err = starlark.Call(thread, main, args, nil)
if err != nil {
- return nil, nil, nil, nil, err
+ return nil, err
}
buf := new(bytes.Buffer)
@@ -99,50 +101,63 @@ func RenderStep(tmpl string, s *types.Step) (types.StepSlice, types.SecretSlice,
case *starlark.List:
for i := 0; i < v.Len(); i++ {
item := v.Index(i)
+
buf.WriteString("---\n")
+
err = writeJSON(buf, item)
if err != nil {
- return nil, nil, nil, nil, err
+ return nil, err
}
+
buf.WriteString("\n")
}
case *starlark.Dict:
buf.WriteString("---\n")
+
err = writeJSON(buf, v)
if err != nil {
- return nil, nil, nil, nil, err
+ return nil, err
}
default:
- return nil, nil, nil, nil, fmt.Errorf("%s: %s", ErrInvalidPipelineReturn, mainVal.Type())
+ return nil, fmt.Errorf("%w: %s", ErrInvalidPipelineReturn, mainVal.Type())
}
// unmarshal the template to the pipeline
err = yaml.Unmarshal(buf.Bytes(), config)
if err != nil {
- // nolint: lll // ignore long line length due to return args
- return types.StepSlice{}, types.SecretSlice{}, types.ServiceSlice{}, raw.StringSliceMap{}, fmt.Errorf("unable to unmarshal yaml: %v", err)
+ return nil, fmt.Errorf("unable to unmarshal yaml: %w", err)
}
// ensure all templated steps have template prefix
for index, newStep := range config.Steps {
- config.Steps[index].Name = fmt.Sprintf("%s_%s", s.Name, newStep.Name)
+ config.Steps[index].Name = fmt.Sprintf("%s_%s", name, newStep.Name)
}
- return config.Steps, config.Secrets, config.Services, config.Environment, nil
+ return &types.Build{Steps: config.Steps, Secrets: config.Secrets, Services: config.Services, Environment: config.Environment}, nil
+}
+
+// GetStarlarkExecutionStepLimit may eventually look up config or calculate it
+func GetStarlarkExecutionStepLimit() uint64 {
+ // arbitrarily limiting the steps of the thread to help prevent infinite loops
+ // may need to further investigate spawning a separate POSIX process if user input is problematic
+ // see https://github.com/google/starlark-go/issues/160#issuecomment-466794230 for further details
+ // This value was previously 5000 and that inhibited a four-dimensional build matrix from working.
+ return 7500
}
// RenderBuild renders the templated build.
-func RenderBuild(b string, envs map[string]string) (*types.Build, error) {
+//
+//nolint:lll // ignore function length due to input args
+func RenderBuild(tmpl string, b string, envs map[string]string, variables map[string]interface{}) (*types.Build, error) {
config := new(types.Build)
thread := &starlark.Thread{Name: "templated-base"}
- // arbitrarily limiting the steps of the thread to 5000 to help prevent infinite loops
- // may need to further investigate spawning a separate POSIX process if user input is problematic
- // see https://github.com/google/starlark-go/issues/160#issuecomment-466794230 for further details
- //
- // nolint: gomnd // ignore magic number
- thread.SetMaxExecutionSteps(5000)
- globals, err := starlark.ExecFile(thread, "templated-base", b, nil)
+
+ thread.SetMaxExecutionSteps(GetStarlarkExecutionStepLimit())
+
+ predeclared := starlark.StringDict{"struct": starlark.NewBuiltin("struct", starlarkstruct.Make)}
+
+ globals, err := starlark.ExecFile(thread, "templated-base", b, predeclared)
if err != nil {
return nil, err
}
@@ -150,17 +165,23 @@ func RenderBuild(b string, envs map[string]string) (*types.Build, error) {
// check the provided template has a main function
mainVal, ok := globals["main"]
if !ok {
- return nil, fmt.Errorf("%s: %s", ErrMissingMainFunc, "templated-base")
+ return nil, fmt.Errorf("%w: %s", ErrMissingMainFunc, "templated-base")
}
// check the provided main is a function
main, ok := mainVal.(starlark.Callable)
if !ok {
- return nil, fmt.Errorf("%s: %s", ErrInvalidMainFunc, "templated-base")
+ return nil, fmt.Errorf("%w: %s", ErrInvalidMainFunc, "templated-base")
+ }
+
+ // load the user provided vars into a starlark type
+ userVars, err := convertTemplateVars(variables)
+ if err != nil {
+ return nil, err
}
// load the platform provided vars into a starlark type
- velaVars, err := convertPlatformVars(envs, "")
+ velaVars, err := convertPlatformVars(envs, tmpl)
if err != nil {
return nil, err
}
@@ -168,11 +189,17 @@ func RenderBuild(b string, envs map[string]string) (*types.Build, error) {
// add the user and platform vars to a context to be used
// within the template caller i.e. ctx["vela"] or ctx["vars"]
context := starlark.NewDict(0)
+
err = context.SetKey(starlark.String("vela"), velaVars)
if err != nil {
return nil, err
}
+ err = context.SetKey(starlark.String("vars"), userVars)
+ if err != nil {
+ return nil, err
+ }
+
args := starlark.Tuple([]starlark.Value{context})
// execute Starlark program from Go.
@@ -188,27 +215,31 @@ func RenderBuild(b string, envs map[string]string) (*types.Build, error) {
case *starlark.List:
for i := 0; i < v.Len(); i++ {
item := v.Index(i)
+
buf.WriteString("---\n")
+
err = writeJSON(buf, item)
if err != nil {
return nil, err
}
+
buf.WriteString("\n")
}
case *starlark.Dict:
buf.WriteString("---\n")
+
err = writeJSON(buf, v)
if err != nil {
return nil, err
}
default:
- return nil, fmt.Errorf("%s: %s", ErrInvalidPipelineReturn, mainVal.Type())
+ return nil, fmt.Errorf("%w: %s", ErrInvalidPipelineReturn, mainVal.Type())
}
// unmarshal the template to the pipeline
err = yaml.Unmarshal(buf.Bytes(), config)
if err != nil {
- return nil, fmt.Errorf("unable to unmarshal yaml: %v", err)
+ return nil, fmt.Errorf("unable to unmarshal yaml: %w", err)
}
return config, nil
diff --git a/compiler/template/starlark/render_test.go b/compiler/template/starlark/render_test.go
index aa14caae9..c602bac38 100644
--- a/compiler/template/starlark/render_test.go
+++ b/compiler/template/starlark/render_test.go
@@ -5,7 +5,7 @@
package starlark
import (
- "io/ioutil"
+ "os"
"testing"
goyaml "github.com/buildkite/yaml"
@@ -14,11 +14,12 @@ import (
"github.com/google/go-cmp/cmp"
)
-func TestStarlark_RenderStep(t *testing.T) {
+func TestStarlark_Render(t *testing.T) {
type args struct {
velaFile string
starlarkFile string
}
+
tests := []struct {
name string
args args
@@ -31,9 +32,10 @@ func TestStarlark_RenderStep(t *testing.T) {
{"platform vars", args{velaFile: "testdata/step/with_vars_plat/step.yml", starlarkFile: "testdata/step/with_vars_plat/template.star"}, "testdata/step/with_vars_plat/want.yml", false},
{"cancel due to complexity", args{velaFile: "testdata/step/cancel/step.yml", starlarkFile: "testdata/step/cancel/template.star"}, "", true},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- sFile, err := ioutil.ReadFile(tt.args.velaFile)
+ sFile, err := os.ReadFile(tt.args.velaFile)
if err != nil {
t.Error(err)
}
@@ -46,19 +48,19 @@ func TestStarlark_RenderStep(t *testing.T) {
"VELA_REPO_FULL_NAME": "octocat/hello-world",
}
- tmpl, err := ioutil.ReadFile(tt.args.starlarkFile)
+ tmpl, err := os.ReadFile(tt.args.starlarkFile)
if err != nil {
t.Error(err)
}
- steps, secrets, services, environment, err := RenderStep(string(tmpl), b.Steps[0])
+ tmplBuild, err := Render(string(tmpl), b.Steps[0].Name, b.Steps[0].Template.Name, b.Steps[0].Environment, b.Steps[0].Template.Variables)
if (err != nil) != tt.wantErr {
- t.Errorf("RenderStep() error = %v, wantErr %v", err, tt.wantErr)
+ t.Errorf("Render() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantErr != true {
- wFile, err := ioutil.ReadFile(tt.wantFile)
+ wFile, err := os.ReadFile(tt.wantFile)
if err != nil {
t.Error(err)
}
@@ -72,17 +74,17 @@ func TestStarlark_RenderStep(t *testing.T) {
wantServices := w.Services
wantEnvironment := w.Environment
- if diff := cmp.Diff(wantSteps, steps); diff != "" {
- t.Errorf("RenderStep() mismatch (-want +got):\n%s", diff)
+ if diff := cmp.Diff(wantSteps, tmplBuild.Steps); diff != "" {
+ t.Errorf("Render() mismatch (-want +got):\n%s", diff)
}
- if diff := cmp.Diff(wantSecrets, secrets); diff != "" {
- t.Errorf("RenderStep() mismatch (-want +got):\n%s", diff)
+ if diff := cmp.Diff(wantSecrets, tmplBuild.Secrets); diff != "" {
+ t.Errorf("Render() mismatch (-want +got):\n%s", diff)
}
- if diff := cmp.Diff(wantServices, services); diff != "" {
- t.Errorf("RenderStep() mismatch (-want +got):\n%s", diff)
+ if diff := cmp.Diff(wantServices, tmplBuild.Services); diff != "" {
+ t.Errorf("Render() mismatch (-want +got):\n%s", diff)
}
- if diff := cmp.Diff(wantEnvironment, environment); diff != "" {
- t.Errorf("RenderStep() mismatch (-want +got):\n%s", diff)
+ if diff := cmp.Diff(wantEnvironment, tmplBuild.Environment); diff != "" {
+ t.Errorf("Render() mismatch (-want +got):\n%s", diff)
}
}
})
@@ -90,9 +92,12 @@ func TestStarlark_RenderStep(t *testing.T) {
}
func TestNative_RenderBuild(t *testing.T) {
+ noWantFile := "none"
+
type args struct {
velaFile string
}
+
tests := []struct {
name string
args args
@@ -102,25 +107,29 @@ func TestNative_RenderBuild(t *testing.T) {
{"steps", args{velaFile: "testdata/build/basic/build.star"}, "testdata/build/basic/want.yml", false},
{"stages", args{velaFile: "testdata/build/basic_stages/build.star"}, "testdata/build/basic_stages/want.yml", false},
{"conditional match", args{velaFile: "testdata/build/conditional/build.star"}, "testdata/build/conditional/want.yml", false},
+ {"steps, with structs", args{velaFile: "testdata/build/with_struct/build.star"}, "testdata/build/with_struct/want.yml", false},
+ {"large build to stress execution steps", args{velaFile: "testdata/build/large/build.star"}, noWantFile, false},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- sFile, err := ioutil.ReadFile(tt.args.velaFile)
+ sFile, err := os.ReadFile(tt.args.velaFile)
if err != nil {
t.Error(err)
}
- got, err := RenderBuild(string(sFile), map[string]string{
+ got, err := RenderBuild("build", string(sFile), map[string]string{
"VELA_REPO_FULL_NAME": "octocat/hello-world",
"VELA_BUILD_BRANCH": "master",
- })
+ "VELA_REPO_ORG": "octocat",
+ }, map[string]interface{}{})
if (err != nil) != tt.wantErr {
t.Errorf("RenderBuild() error = %v, wantErr %v", err, tt.wantErr)
return
}
- if tt.wantErr != true {
- wFile, err := ioutil.ReadFile(tt.wantFile)
+ if tt.wantErr != true && tt.wantFile != noWantFile {
+ wFile, err := os.ReadFile(tt.wantFile)
if err != nil {
t.Error(err)
}
diff --git a/compiler/template/starlark/starlark.go b/compiler/template/starlark/starlark.go
index fd987f6dc..0299ef259 100644
--- a/compiler/template/starlark/starlark.go
+++ b/compiler/template/starlark/starlark.go
@@ -33,7 +33,7 @@ var (
//
// https://github.com/wonderix/shalm/blob/899b8f7787883d40619eefcc39bd12f42a09b5e7/pkg/shalm/convert.go#L14-L85
//
-// nolint: gocyclo,funlen,lll // ignore above line length and function length due to comments
+//nolint:gocyclo // ignore complexity
func toStarlark(value interface{}) (starlark.Value, error) {
logrus.Tracef("converting %v to starlark type", value)
@@ -78,6 +78,7 @@ func toStarlark(value interface{}) (starlark.Value, error) {
if err != nil {
return nil, err
}
+
a = append(a, val)
}
@@ -90,11 +91,11 @@ func toStarlark(value interface{}) (starlark.Value, error) {
return val, nil
case reflect.Map:
- // nolint: gomnd // ignore magic number
d := starlark.NewDict(16)
for _, key := range v.MapKeys() {
strct := v.MapIndex(key)
+
keyValue, err := toStarlark(key.Interface())
if err != nil {
return nil, err
@@ -137,7 +138,7 @@ func toStarlark(value interface{}) (starlark.Value, error) {
}
}
- return nil, fmt.Errorf("%s: %v", ErrUnableToConvertStarlark, value)
+ return nil, fmt.Errorf("%w: %v", ErrUnableToConvertStarlark, value)
}
// writeJSON takes an starlark input and return the valid JSON
@@ -151,7 +152,7 @@ func toStarlark(value interface{}) (starlark.Value, error) {
// if/when we try to return values it breaks the recursion. Panics were swapped to error
// returns from implementation.
//
-// nolint: gocyclo,funlen // ignore cyclomatic complexity and function length
+//nolint:gocyclo // ignore cyclomatic complexity
func writeJSON(out *bytes.Buffer, v starlark.Value) error {
logrus.Tracef("converting %v to JSON", v)
@@ -268,7 +269,7 @@ func writeJSON(out *bytes.Buffer, v starlark.Value) error {
logrus.Error(err)
}
default:
- return fmt.Errorf("%s: %v", ErrUnableToConvertJSON, v)
+ return fmt.Errorf("%w: %v", ErrUnableToConvertJSON, v)
}
return nil
@@ -286,5 +287,6 @@ func goQuoteIsSafe(s string) bool {
return false
}
}
+
return true
}
diff --git a/compiler/template/starlark/starlark_test.go b/compiler/template/starlark/starlark_test.go
index 378da6a2f..e3aa65626 100644
--- a/compiler/template/starlark/starlark_test.go
+++ b/compiler/template/starlark/starlark_test.go
@@ -1,3 +1,7 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
package starlark
import (
@@ -9,16 +13,20 @@ import (
func TestStarlark_toStarlark(t *testing.T) {
dict := starlark.NewDict(16)
+
err := dict.SetKey(starlark.String("foo"), starlark.String("bar"))
if err != nil {
t.Error(err)
}
+
a := make([]starlark.Value, 0)
a = append(a, starlark.Value(starlark.String("foo")))
a = append(a, starlark.Value(starlark.String("bar")))
+
type args struct {
value interface{}
}
+
tests := []struct {
name string
args args
@@ -42,6 +50,7 @@ func TestStarlark_toStarlark(t *testing.T) {
{"nil", args{value: nil}, starlark.None, false},
{"map", args{value: map[string]string{"foo": "bar"}}, dict, false},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := toStarlark(tt.args.value)
diff --git a/compiler/template/starlark/testdata/build/large/build.star b/compiler/template/starlark/testdata/build/large/build.star
new file mode 100644
index 000000000..5ffbc482a
--- /dev/null
+++ b/compiler/template/starlark/testdata/build/large/build.star
@@ -0,0 +1,202 @@
+######
+## Setup the build matrix with the base versions a human will maintain.
+######
+
+DISTRO_WITH_VERSIONS = {
+ # n.b. these reduce to DockerHub tags
+ # https://hub.docker.com/_/python/tags?name=alpine
+ # https://endoflife.date/alpine
+ 'alpine': [
+ '3.17', # EOL 22 Nov 2024
+ '3.18' # EOL 09 May 2025
+ ],
+ # https://hub.docker.com/_/python/tags?name=slim
+ # https://endoflife.date/debian
+ 'debian': [
+ 'slim-bullseye', # EOL 30 Jun 2026
+ 'slim-bookworm' # EOL 10 Jun 2028
+ ]
+}
+PYTHON_VERSIONS = [
+ '3.8',
+ '3.9',
+ '3.10',
+ '3.11'
+]
+POETRY_VERSIONS = [
+ '1.6.1'
+]
+
+KANIKO_IMAGE = 'target/vela-kaniko:latest'
+HADOLINT_IMAGE = 'hadolint/hadolint:v2.12.0-alpine'
+
+
+## The base Docker container build step's config for push builds
+def base():
+ return {
+ 'image': KANIKO_IMAGE,
+ 'ruleset': {
+ 'event': 'push',
+ 'branch': 'main'
+ },
+ 'pull': 'not_present',
+ 'secrets': [
+ {
+ 'source': 'artifactory_password',
+ 'target': 'docker_password'
+ }
+ ]
+ }
+
+
+## The base Docker container plugin params for push builds
+##
+## These are parameters passed to Kaniko.
+def base_params():
+ return {
+ 'username': 'ibuildallthings',
+ 'registry': 'docker.example.com',
+ 'repo': 'docker.example.com/app/multibuild'
+ }
+
+
+## The step config for pull request builds
+def pull_request():
+ pr = base()
+ pr['ruleset']['event'] = 'pull_request'
+ pr['ruleset'].pop('branch')
+ return pr
+
+
+## The Kaniko params for pull request builds
+def pull_request_params():
+ prp = base_params()
+ prp['dry_run'] = True
+ return prp
+
+
+## Define a linting stage that uses Hadolint inside of a Make task
+##
+## This keeps our Dockerfiles tidy and compliant with conventions
+def stage_linting():
+ return {
+ 'linting': {
+ 'steps': [{
+ 'name': 'check-docker',
+ 'image': HADOLINT_IMAGE,
+ 'pull': 'not_present',
+ 'commands': [
+ 'time apk add --no-cache make',
+ 'time make check-docker'
+ ]
+ }]
+ }
+ }
+
+
+## Build stages comprised of a step for push and pull_request builds
+def stage_build_tuple(distro, distro_version, python_version, poetry_version):
+ pr = build_template("build", distro, distro_version, python_version, poetry_version, pull_request(), pull_request_params())
+ base_step = build_template("publish", distro, distro_version, python_version, poetry_version, base(), base_params())
+ combined = base_step | pr
+ return combined
+
+
+## Build a single stage for a build tuple, with its base step config and plugin parameters
+def build_template(step_name, distro, distro_version, python_version, poetry_version, step_def_base, step_def_params):
+ return {
+ ('python_%s_%s_%s %s' % (python_version, distro, distro_version, step_def_base['ruleset']['event'])): {
+ 'steps': [step_def_base | {
+ 'name': ('%s python-%s %s %s' % (step_name, python_version, distro, distro_version)),
+ 'parameters': step_def_params | {
+ 'dockerfile': ('python-%s.Dockerfile' % distro),
+ 'build_args': [
+ 'PYTHON_VERSION=%s' % python_version,
+ '%s_VERSION=%s' % (distro.upper(), distro_version),
+ 'POETRY_VERSION=%s' % poetry_version
+ ],
+ 'tags': [
+ '%s-%s-%s-%s' % (python_version, distro, distro_version, poetry_version),
+ '%s-%s-%s' % (python_version, distro, distro_version),
+ '%s-%s' % (python_version, distro)
+ ]
+ }
+ }]
+ }
+ }
+
+
+## Define a stage that uses the Slack template
+def stage_slack_notify(needs):
+ return {
+ 'slack': {
+ 'needs': needs,
+ 'steps': [{
+ 'name': 'slack',
+ 'template': {
+ 'name': 'slack'
+ }
+ }]
+ }
+ }
+
+
+## Builds the build matrix in the form of list of tuples from the constants defined at the top of the file
+def build_matrix():
+ BUILD_MATRIX = []
+ for poetry_version in POETRY_VERSIONS:
+ for python_version in PYTHON_VERSIONS:
+ for distro in DISTRO_WITH_VERSIONS:
+ for distro_version in DISTRO_WITH_VERSIONS[distro]:
+ BUILD_MATRIX.append((distro,
+ distro_version,
+ python_version,
+ poetry_version))
+ return BUILD_MATRIX
+
+
+## Construct a secret
+def secret(name, key, secret_type, engine='native'):
+ return {'name': name, 'key': key, 'engine': engine, 'type': secret_type}
+
+
+## Construct a template
+def template(name, source, version=None, template_type='github'):
+ real_source = '%s@%s' % (source, version) if version else source
+ return {
+ 'name': name,
+ 'source': real_source,
+ 'type': template_type
+ }
+
+## The main method, the real deal.
+##
+## Vela actually calls this function, its return is what Vela uses.
+def main(ctx):
+ # Retrieve the org dynamically since we're using some org secrets
+ vela_repo_org = ctx['vela']['repo']['org'] if 'vela' in ctx else "UNKNOWN-ORG"
+
+ # Build the stages from the build matrix
+ build_stages = {}
+ for (distro, distro_version, python_version, poetry_version) in build_matrix():
+ build_stages = build_stages | (stage_build_tuple(distro, distro_version, python_version, poetry_version))
+
+ # assemble the stage list with the bookends of linting and notifications in place
+ stages = stage_linting() | build_stages | stage_slack_notify(build_stages.keys())
+
+ # Build the final output
+ final = {
+ 'version': '1',
+ 'templates': [
+ template(name='slack',
+ source='git.example.com/vela/vela-templates/slack/slack.yml')
+ ],
+ 'stages': stages,
+ 'secrets': [
+ secret('artifactory_password','platform/vela-secrets/artifactory_password_for_ibuildallthings', 'shared'),
+ secret('slack_webhook', vela_repo_org + '/slack_webhook', 'org')
+ ]
+ }
+
+ return final
+
diff --git a/compiler/template/starlark/testdata/build/with_struct/build.star b/compiler/template/starlark/testdata/build/with_struct/build.star
new file mode 100644
index 000000000..d344b64cb
--- /dev/null
+++ b/compiler/template/starlark/testdata/build/with_struct/build.star
@@ -0,0 +1,25 @@
+def main(ctx):
+ step_list = [
+ struct(name="foo"),
+ struct(name="bar"),
+ struct(name="star")
+ ]
+
+ steps = []
+
+ for step in step_list:
+ steps.append(build_step(step))
+
+ return {
+ 'version': '1',
+ 'steps': steps
+ }
+
+def build_step(step):
+ return {
+ "name": "build_%s" % step.name,
+ "image": "alpine:latest",
+ 'commands': [
+ "echo %s" % step.name
+ ]
+ }
diff --git a/compiler/template/starlark/testdata/build/with_struct/want.yml b/compiler/template/starlark/testdata/build/with_struct/want.yml
new file mode 100644
index 000000000..6297ea2b9
--- /dev/null
+++ b/compiler/template/starlark/testdata/build/with_struct/want.yml
@@ -0,0 +1,16 @@
+version: 1
+steps:
+ - name: build_foo
+ image: alpine:latest
+ commands:
+ - echo foo
+
+ - name: build_bar
+ image: alpine:latest
+ commands:
+ - echo bar
+
+ - name: build_star
+ image: alpine:latest
+ commands:
+ - echo star
diff --git a/compiler/template/template.go b/compiler/template/template.go
index 36dec9a6a..75cc8eda9 100644
--- a/compiler/template/template.go
+++ b/compiler/template/template.go
@@ -12,7 +12,7 @@ type Engine interface {
// RenderBuild defines a function that combines
// the template with the build.
RenderBuild(template string, step *yaml.Step) (yaml.StepSlice, error)
- // RenderStep defines a function that combines
+ // Render defines a function that combines
// the template with the step.
- RenderStep(template string, step *yaml.Step) (yaml.StepSlice, error)
+ Render(template string, step *yaml.Step) (yaml.StepSlice, error)
}
diff --git a/database/build/build.go b/database/build/build.go
new file mode 100644
index 000000000..ce750e5ee
--- /dev/null
+++ b/database/build/build.go
@@ -0,0 +1,83 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "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 BuildInterface interface.
+ config struct {
+ // specifies to skip creating tables and indexes for the Build engine
+ SkipCreation bool
+ }
+
+ // engine represents the build functionality that implements the BuildInterface interface.
+ engine struct {
+ // engine configuration settings used in build functions
+ config *config
+
+ ctx context.Context
+
+ // gorm.io/gorm database client used in build functions
+ //
+ // https://pkg.go.dev/gorm.io/gorm#DB
+ client *gorm.DB
+
+ // sirupsen/logrus logger used in build functions
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus#Entry
+ logger *logrus.Entry
+ }
+)
+
+// New creates and returns a Vela service for integrating with builds in the database.
+//
+//nolint:revive // ignore returning unexported engine
+func New(opts ...EngineOpt) (*engine, error) {
+ // create new Build 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 build database objects
+ if e.config.SkipCreation {
+ e.logger.Warning("skipping creation of builds table and indexes in the database")
+
+ return e, nil
+ }
+
+ // create the builds table
+ err := e.CreateBuildTable(e.ctx, e.client.Config.Dialector.Name())
+ if err != nil {
+ return nil, fmt.Errorf("unable to create %s table: %w", constants.TableBuild, err)
+ }
+
+ // create the indexes for the builds table
+ err = e.CreateBuildIndexes(e.ctx)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create indexes for %s table: %w", constants.TableBuild, err)
+ }
+
+ return e, nil
+}
diff --git a/database/build/build_test.go b/database/build/build_test.go
new file mode 100644
index 000000000..0e6eb9fd9
--- /dev/null
+++ b/database/build/build_test.go
@@ -0,0 +1,289 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "database/sql/driver"
+ "reflect"
+ "testing"
+ "time"
+
+ "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 TestBuild_New(t *testing.T) {
+ // setup types
+ logger := logrus.NewEntry(logrus.StandardLogger())
+
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+ defer _sql.Close()
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateCreatedIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateSourceIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateStatusIndex).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
+ 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},
+ ctx: context.TODO(),
+ logger: logger,
+ },
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ client: _sqlite,
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ client: _sqlite,
+ config: &config{SkipCreation: false},
+ ctx: context.TODO(),
+ logger: logger,
+ },
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := New(
+ WithContext(context.TODO()),
+ WithClient(test.client),
+ WithLogger(test.logger),
+ WithSkipCreation(test.skipCreation),
+ )
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("New for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("New for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("New for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
+
+// testPostgres is a helper function to create a Postgres engine for testing.
+func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) {
+ // create the new mock sql database
+ //
+ // https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#New
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateCreatedIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateSourceIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateStatusIndex).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(
+ WithContext(context.TODO()),
+ WithClient(_postgres),
+ WithLogger(logrus.NewEntry(logrus.StandardLogger())),
+ WithSkipCreation(false),
+ )
+ if err != nil {
+ t.Errorf("unable to create new postgres build 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(
+ WithContext(context.TODO()),
+ WithClient(_sqlite),
+ WithLogger(logrus.NewEntry(logrus.StandardLogger())),
+ WithSkipCreation(false),
+ )
+ if err != nil {
+ t.Errorf("unable to create new sqlite build 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),
+ }
+}
+
+// testDeployment is a test helper function to create a library
+// Repo type with all fields set to their zero values.
+func testDeployment() *library.Deployment {
+ return &library.Deployment{
+ ID: new(int64),
+ RepoID: new(int64),
+ URL: new(string),
+ User: new(string),
+ Commit: new(string),
+ Ref: new(string),
+ Task: new(string),
+ Target: new(string),
+ Description: new(string),
+ }
+}
+
+// testRepo is a test helper function to create a library
+// Repo type with all fields set to their zero values.
+func testRepo() *library.Repo {
+ return &library.Repo{
+ ID: new(int64),
+ UserID: new(int64),
+ BuildLimit: new(int64),
+ Timeout: new(int64),
+ Counter: new(int),
+ PipelineType: new(string),
+ Hash: new(string),
+ Org: new(string),
+ Name: new(string),
+ FullName: new(string),
+ Link: new(string),
+ Clone: new(string),
+ Branch: new(string),
+ Visibility: new(string),
+ PreviousName: new(string),
+ Private: new(bool),
+ Trusted: new(bool),
+ Active: new(bool),
+ AllowPull: new(bool),
+ AllowPush: new(bool),
+ AllowDeploy: new(bool),
+ AllowTag: new(bool),
+ AllowComment: new(bool),
+ }
+}
+
+// This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values
+// that are otherwise not easily compared. These typically would be values generated
+// before adding or updating them in the database.
+//
+// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime
+type AnyArgument struct{}
+
+// Match satisfies sqlmock.Argument interface.
+func (a AnyArgument) Match(v driver.Value) bool {
+ return true
+}
+
+// NowTimestamp is used to test whether timestamps get updated correctly to the current time with lenience.
+type NowTimestamp struct{}
+
+// Match satisfies sqlmock.Argument interface.
+func (t NowTimestamp) Match(v driver.Value) bool {
+ ts, ok := v.(int64)
+ if !ok {
+ return false
+ }
+ now := time.Now().Unix()
+
+ return now-ts < 10
+}
diff --git a/database/build/clean.go b/database/build/clean.go
new file mode 100644
index 000000000..700998687
--- /dev/null
+++ b/database/build/clean.go
@@ -0,0 +1,36 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "time"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CleanBuilds updates builds to an error with a provided message with a created timestamp prior to a defined moment.
+func (e *engine) CleanBuilds(ctx context.Context, msg string, before int64) (int64, error) {
+ logrus.Tracef("cleaning pending or running builds in the database created prior to %d", before)
+
+ b := new(library.Build)
+ b.SetStatus(constants.StatusError)
+ b.SetError(msg)
+ b.SetFinished(time.Now().UTC().Unix())
+
+ build := database.BuildFromLibrary(b)
+
+ // send query to the database
+ result := e.client.
+ Table(constants.TableBuild).
+ Where("created < ?", before).
+ Where("status = 'running' OR status = 'pending'").
+ Updates(build)
+
+ return result.RowsAffected, result.Error
+}
diff --git a/database/build/clean_test.go b/database/build/clean_test.go
new file mode 100644
index 000000000..279268ff4
--- /dev/null
+++ b/database/build/clean_test.go
@@ -0,0 +1,120 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestBuild_Engine_CleanBuilds(t *testing.T) {
+ // setup types
+ _buildOne := testBuild()
+ _buildOne.SetID(1)
+ _buildOne.SetRepoID(1)
+ _buildOne.SetNumber(1)
+ _buildOne.SetCreated(1)
+ _buildOne.SetStatus("pending")
+
+ _buildTwo := testBuild()
+ _buildTwo.SetID(2)
+ _buildTwo.SetRepoID(1)
+ _buildTwo.SetNumber(2)
+ _buildTwo.SetCreated(2)
+ _buildTwo.SetStatus("running")
+
+ // setup types
+ _buildThree := testBuild()
+ _buildThree.SetID(3)
+ _buildThree.SetRepoID(1)
+ _buildThree.SetNumber(3)
+ _buildThree.SetCreated(1)
+ _buildThree.SetStatus("success")
+
+ _buildFour := testBuild()
+ _buildFour.SetID(4)
+ _buildFour.SetRepoID(1)
+ _buildFour.SetNumber(4)
+ _buildFour.SetCreated(5)
+ _buildFour.SetStatus("running")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the name query
+ _mock.ExpectExec(`UPDATE "builds" SET "status"=$1,"error"=$2,"finished"=$3,"deploy_payload"=$4 WHERE created < $5 AND (status = 'running' OR status = 'pending')`).
+ WithArgs("error", "msg", NowTimestamp{}, AnyArgument{}, 3).
+ WillReturnResult(sqlmock.NewResult(1, 2))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateBuild(context.TODO(), _buildOne)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateBuild(context.TODO(), _buildTwo)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateBuild(context.TODO(), _buildThree)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateBuild(context.TODO(), _buildFour)
+ if err != nil {
+ t.Errorf("unable to create test build 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.CleanBuilds(context.TODO(), "msg", 3)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CleanBuilds for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CleanBuilds for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CleanBuilds for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/build/count.go b/database/build/count.go
new file mode 100644
index 000000000..57c84030c
--- /dev/null
+++ b/database/build/count.go
@@ -0,0 +1,27 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+)
+
+// CountBuilds gets the count of all builds from the database.
+func (e *engine) CountBuilds(ctx context.Context) (int64, error) {
+ e.logger.Tracef("getting count of all builds from the database")
+
+ // variable to store query results
+ var b int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableBuild).
+ Count(&b).
+ Error
+
+ return b, err
+}
diff --git a/database/build/count_deployment.go b/database/build/count_deployment.go
new file mode 100644
index 000000000..21e58c6c8
--- /dev/null
+++ b/database/build/count_deployment.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 build
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CountBuildsForDeployment gets the count of builds by deployment URL from the database.
+func (e *engine) CountBuildsForDeployment(ctx context.Context, d *library.Deployment, filters map[string]interface{}) (int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "deployment": d.GetURL(),
+ }).Tracef("getting count of builds for deployment %s from the database", d.GetURL())
+
+ // variable to store query results
+ var b int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableBuild).
+ Where("source = ?", d.GetURL()).
+ Where(filters).
+ Order("number DESC").
+ Count(&b).
+ Error
+
+ return b, err
+}
diff --git a/database/build/count_deployment_test.go b/database/build/count_deployment_test.go
new file mode 100644
index 000000000..4b067896d
--- /dev/null
+++ b/database/build/count_deployment_test.go
@@ -0,0 +1,103 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestBuild_Engine_CountBuildsForDeployment(t *testing.T) {
+ // setup types
+ _buildOne := testBuild()
+ _buildOne.SetID(1)
+ _buildOne.SetRepoID(1)
+ _buildOne.SetNumber(1)
+ _buildOne.SetDeployPayload(nil)
+ _buildOne.SetSource("https://github.com/github/octocat/deployments/1")
+
+ _buildTwo := testBuild()
+ _buildTwo.SetID(2)
+ _buildTwo.SetRepoID(1)
+ _buildTwo.SetNumber(2)
+ _buildTwo.SetDeployPayload(nil)
+ _buildTwo.SetSource("https://github.com/github/octocat/deployments/1")
+
+ _deployment := testDeployment()
+ _deployment.SetID(1)
+ _deployment.SetRepoID(1)
+ _deployment.SetURL("https://github.com/github/octocat/deployments/1")
+
+ _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 "builds" WHERE source = $1`).WithArgs("https://github.com/github/octocat/deployments/1").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateBuild(context.TODO(), _buildOne)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateBuild(context.TODO(), _buildTwo)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want int64
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: 2,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: 2,
+ },
+ }
+
+ filters := map[string]interface{}{}
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.CountBuildsForDeployment(context.TODO(), _deployment, filters)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountBuildsForDeployment for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountBuildsForDeployment for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountBuildsForDeployment for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/build/count_org.go b/database/build/count_org.go
new file mode 100644
index 000000000..90e01c5e8
--- /dev/null
+++ b/database/build/count_org.go
@@ -0,0 +1,33 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/sirupsen/logrus"
+)
+
+// CountBuildsForOrg gets the count of builds by org name from the database.
+func (e *engine) CountBuildsForOrg(ctx context.Context, org string, filters map[string]interface{}) (int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": org,
+ }).Tracef("getting count of builds for org %s from the database", org)
+
+ // variable to store query results
+ var b int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableBuild).
+ Joins("JOIN repos ON builds.repo_id = repos.id").
+ Where("repos.org = ?", org).
+ Where(filters).
+ Count(&b).
+ Error
+
+ return b, err
+}
diff --git a/database/build/count_org_test.go b/database/build/count_org_test.go
new file mode 100644
index 000000000..77b60652b
--- /dev/null
+++ b/database/build/count_org_test.go
@@ -0,0 +1,160 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+)
+
+func TestBuild_Engine_CountBuildsForOrg(t *testing.T) {
+ // setup types
+ _buildOne := testBuild()
+ _buildOne.SetID(1)
+ _buildOne.SetRepoID(1)
+ _buildOne.SetNumber(1)
+ _buildOne.SetDeployPayload(nil)
+ _buildOne.SetEvent("push")
+
+ _buildTwo := testBuild()
+ _buildTwo.SetID(2)
+ _buildTwo.SetRepoID(2)
+ _buildTwo.SetNumber(2)
+ _buildTwo.SetDeployPayload(nil)
+ _buildTwo.SetEvent("push")
+
+ _repoOne := testRepo()
+ _repoOne.SetID(1)
+ _repoOne.SetUserID(1)
+ _repoOne.SetHash("baz")
+ _repoOne.SetOrg("foo")
+ _repoOne.SetName("bar")
+ _repoOne.SetFullName("foo/bar")
+ _repoOne.SetVisibility("public")
+ _repoOne.SetPipelineType("yaml")
+ _repoOne.SetTopics([]string{})
+
+ _repoTwo := testRepo()
+ _repoTwo.SetID(2)
+ _repoTwo.SetUserID(1)
+ _repoTwo.SetHash("bar")
+ _repoTwo.SetOrg("foo")
+ _repoTwo.SetName("baz")
+ _repoTwo.SetFullName("foo/baz")
+ _repoTwo.SetVisibility("public")
+ _repoTwo.SetPipelineType("yaml")
+ _repoTwo.SetTopics([]string{})
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result without filters in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+ // ensure the mock expects the query without filters
+ _mock.ExpectQuery(`SELECT count(*) FROM "builds" JOIN repos ON builds.repo_id = repos.id WHERE repos.org = $1`).WithArgs("foo").WillReturnRows(_rows)
+
+ // create expected result with event filter in mock
+ _rows = sqlmock.NewRows([]string{"count"}).AddRow(2)
+ // ensure the mock expects the query with event filter
+ _mock.ExpectQuery(`SELECT count(*) FROM "builds" JOIN repos ON builds.repo_id = repos.id WHERE repos.org = $1 AND "event" = $2`).WithArgs("foo", "push").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateBuild(context.TODO(), _buildOne)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateBuild(context.TODO(), _buildTwo)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ err = _sqlite.client.AutoMigrate(&database.Repo{})
+ if err != nil {
+ t.Errorf("unable to create repo table for sqlite: %v", err)
+ }
+
+ err = _sqlite.client.Table(constants.TableRepo).Create(database.RepoFromLibrary(_repoOne)).Error
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ err = _sqlite.client.Table(constants.TableRepo).Create(database.RepoFromLibrary(_repoTwo)).Error
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ filters map[string]interface{}
+ want int64
+ }{
+ {
+ failure: false,
+ name: "postgres without filters",
+ database: _postgres,
+ filters: map[string]interface{}{},
+ want: 2,
+ },
+ {
+ failure: false,
+ name: "postgres with event filter",
+ database: _postgres,
+ filters: map[string]interface{}{
+ "event": "push",
+ },
+ want: 2,
+ },
+ {
+ failure: false,
+ name: "sqlite3 without filters",
+ database: _sqlite,
+ filters: map[string]interface{}{},
+ want: 2,
+ },
+ {
+ failure: false,
+ name: "sqlite3 with event filter",
+ database: _sqlite,
+ filters: map[string]interface{}{
+ "event": "push",
+ },
+ want: 2,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.CountBuildsForOrg(context.TODO(), "foo", test.filters)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountBuildsForOrg for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountBuildsForOrg for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountBuildsForOrg for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/build/count_repo.go b/database/build/count_repo.go
new file mode 100644
index 000000000..e7fcee5b8
--- /dev/null
+++ b/database/build/count_repo.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 build
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CountBuildsForRepo gets the count of builds by repo ID from the database.
+func (e *engine) CountBuildsForRepo(ctx context.Context, r *library.Repo, filters map[string]interface{}) (int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ }).Tracef("getting count of builds for repo %s from the database", r.GetFullName())
+
+ // variable to store query results
+ var b int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableBuild).
+ Where("repo_id = ?", r.GetID()).
+ Where(filters).
+ Count(&b).
+ Error
+
+ return b, err
+}
diff --git a/database/build/count_repo_test.go b/database/build/count_repo_test.go
new file mode 100644
index 000000000..c7f406b32
--- /dev/null
+++ b/database/build/count_repo_test.go
@@ -0,0 +1,105 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestBuild_Engine_CountBuildsForRepo(t *testing.T) {
+ // setup types
+ _buildOne := testBuild()
+ _buildOne.SetID(1)
+ _buildOne.SetRepoID(1)
+ _buildOne.SetNumber(1)
+ _buildOne.SetDeployPayload(nil)
+
+ _buildTwo := testBuild()
+ _buildTwo.SetID(2)
+ _buildTwo.SetRepoID(1)
+ _buildTwo.SetNumber(2)
+ _buildTwo.SetDeployPayload(nil)
+
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetHash("baz")
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+ _repo.SetVisibility("public")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT count(*) FROM "builds" WHERE repo_id = $1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateBuild(context.TODO(), _buildOne)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateBuild(context.TODO(), _buildTwo)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want int64
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: 2,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: 2,
+ },
+ }
+
+ filters := map[string]interface{}{}
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.CountBuildsForRepo(context.TODO(), _repo, filters)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountBuildsForRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountBuildsForRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountBuildsForRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/build/count_status.go b/database/build/count_status.go
new file mode 100644
index 000000000..98d4acf5b
--- /dev/null
+++ b/database/build/count_status.go
@@ -0,0 +1,29 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+)
+
+// CountBuildsForStatus gets the count of builds by status from the database.
+func (e *engine) CountBuildsForStatus(ctx context.Context, status string, filters map[string]interface{}) (int64, error) {
+ e.logger.Tracef("getting count of builds for status %s from the database", status)
+
+ // variable to store query results
+ var b int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableBuild).
+ Where("status = ?", status).
+ Where(filters).
+ Count(&b).
+ Error
+
+ return b, err
+}
diff --git a/database/build/count_status_test.go b/database/build/count_status_test.go
new file mode 100644
index 000000000..c35d694e0
--- /dev/null
+++ b/database/build/count_status_test.go
@@ -0,0 +1,98 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestBuild_Engine_CountBuildsForStatus(t *testing.T) {
+ // setup types
+ _buildOne := testBuild()
+ _buildOne.SetID(1)
+ _buildOne.SetRepoID(1)
+ _buildOne.SetNumber(1)
+ _buildOne.SetDeployPayload(nil)
+ _buildOne.SetStatus("running")
+
+ _buildTwo := testBuild()
+ _buildTwo.SetID(2)
+ _buildTwo.SetRepoID(1)
+ _buildTwo.SetNumber(2)
+ _buildTwo.SetDeployPayload(nil)
+ _buildTwo.SetStatus("running")
+
+ _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 "builds" WHERE status = $1`).WithArgs("running").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateBuild(context.TODO(), _buildOne)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateBuild(context.TODO(), _buildTwo)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want int64
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: 2,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: 2,
+ },
+ }
+
+ filters := map[string]interface{}{}
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.CountBuildsForStatus(context.TODO(), "running", filters)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountBuildsForStatus for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountBuildsForStatus for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountBuildsForStatus for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/build/count_test.go b/database/build/count_test.go
new file mode 100644
index 000000000..c29d3f04f
--- /dev/null
+++ b/database/build/count_test.go
@@ -0,0 +1,94 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestBuild_Engine_CountBuilds(t *testing.T) {
+ // setup types
+ _buildOne := testBuild()
+ _buildOne.SetID(1)
+ _buildOne.SetRepoID(1)
+ _buildOne.SetNumber(1)
+ _buildOne.SetDeployPayload(nil)
+
+ _buildTwo := testBuild()
+ _buildTwo.SetID(2)
+ _buildTwo.SetRepoID(1)
+ _buildTwo.SetNumber(2)
+ _buildTwo.SetDeployPayload(nil)
+
+ _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 "builds"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateBuild(context.TODO(), _buildOne)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateBuild(context.TODO(), _buildTwo)
+ if err != nil {
+ t.Errorf("unable to create test build 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.CountBuilds(context.TODO())
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountBuilds for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountBuilds for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountBuilds for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/build/create.go b/database/build/create.go
new file mode 100644
index 000000000..ff5d7a91d
--- /dev/null
+++ b/database/build/create.go
@@ -0,0 +1,43 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code with update.go
+package build
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CreateBuild creates a new build in the database.
+func (e *engine) CreateBuild(ctx context.Context, b *library.Build) (*library.Build, error) {
+ e.logger.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ }).Tracef("creating build %d in the database", b.GetNumber())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#BuildFromLibrary
+ build := database.BuildFromLibrary(b)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Build.Validate
+ err := build.Validate()
+ if err != nil {
+ return nil, err
+ }
+
+ // crop build if any columns are too large
+ build = build.Crop()
+
+ // send query to the database
+ result := e.client.Table(constants.TableBuild).Create(build)
+
+ return build.ToLibrary(), result.Error
+}
diff --git a/database/build/create_test.go b/database/build/create_test.go
new file mode 100644
index 000000000..c425c5db7
--- /dev/null
+++ b/database/build/create_test.go
@@ -0,0 +1,79 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestBuild_Engine_CreateBuild(t *testing.T) {
+ // setup types
+ _build := testBuild()
+ _build.SetID(1)
+ _build.SetRepoID(1)
+ _build.SetNumber(1)
+ _build.SetDeployPayload(nil)
+
+ _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 "builds"
+("repo_id","pipeline_id","number","parent","event","event_action","status","error","enqueued","created","started","finished","deploy","deploy_payload","clone","source","title","message","commit","sender","author","email","link","branch","ref","base_ref","head_ref","host","runtime","distribution","id")
+VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31) RETURNING "id"`).
+ WithArgs(1, nil, 1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, AnyArgument{}, nil, nil, nil, nil, nil, nil, 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) {
+ got, err := test.database.CreateBuild(context.TODO(), _build)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateBuild for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateBuild for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, _build) {
+ t.Errorf("CreateBuild for %s returned %s, want %s", test.name, got, _build)
+ }
+ })
+ }
+}
diff --git a/database/build/delete.go b/database/build/delete.go
new file mode 100644
index 000000000..a7048c7e7
--- /dev/null
+++ b/database/build/delete.go
@@ -0,0 +1,32 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// DeleteBuild deletes an existing build from the database.
+func (e *engine) DeleteBuild(ctx context.Context, b *library.Build) error {
+ e.logger.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ }).Tracef("deleting build %d from the database", b.GetNumber())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#BuildFromLibrary
+ build := database.BuildFromLibrary(b)
+
+ // send query to the database
+ return e.client.
+ Table(constants.TableBuild).
+ Delete(build).
+ Error
+}
diff --git a/database/build/delete_test.go b/database/build/delete_test.go
new file mode 100644
index 000000000..93d1b5459
--- /dev/null
+++ b/database/build/delete_test.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 build
+
+import (
+ "context"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestBuild_Engine_DeleteBuild(t *testing.T) {
+ // setup types
+ _build := testBuild()
+ _build.SetID(1)
+ _build.SetRepoID(1)
+ _build.SetNumber(1)
+ _build.SetDeployPayload(nil)
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`DELETE FROM "builds" WHERE "builds"."id" = $1`).
+ WithArgs(1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateBuild(context.TODO(), _build)
+ if err != nil {
+ t.Errorf("unable to create test build 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.DeleteBuild(context.TODO(), _build)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("DeleteBuild for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("DeleteBuild for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/build/get.go b/database/build/get.go
new file mode 100644
index 000000000..8c57d7fe6
--- /dev/null
+++ b/database/build/get.go
@@ -0,0 +1,33 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// GetBuild gets a build by ID from the database.
+func (e *engine) GetBuild(ctx context.Context, id int64) (*library.Build, error) {
+ e.logger.Tracef("getting build %d from the database", id)
+
+ // variable to store query results
+ b := new(database.Build)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableBuild).
+ Where("id = ?", id).
+ Take(b).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ return b.ToLibrary(), nil
+}
diff --git a/database/build/get_repo.go b/database/build/get_repo.go
new file mode 100644
index 000000000..7b37bc2a7
--- /dev/null
+++ b/database/build/get_repo.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 build
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// GetBuildForRepo gets a build by repo ID and number from the database.
+func (e *engine) GetBuildForRepo(ctx context.Context, r *library.Repo, number int) (*library.Build, error) {
+ e.logger.WithFields(logrus.Fields{
+ "build": number,
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ }).Tracef("getting build %s/%d from the database", r.GetFullName(), number)
+
+ // variable to store query results
+ b := new(database.Build)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableBuild).
+ Where("repo_id = ?", r.GetID()).
+ Where("number = ?", number).
+ Take(b).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ return b.ToLibrary(), nil
+}
diff --git a/database/build/get_repo_test.go b/database/build/get_repo_test.go
new file mode 100644
index 000000000..8a36b55e9
--- /dev/null
+++ b/database/build/get_repo_test.go
@@ -0,0 +1,95 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestBuild_Engine_GetBuildForRepo(t *testing.T) {
+ // setup types
+ _build := testBuild()
+ _build.SetID(1)
+ _build.SetRepoID(1)
+ _build.SetNumber(1)
+ _build.SetDeployPayload(nil)
+
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetHash("baz")
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+ _repo.SetVisibility("public")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"}).
+ AddRow(1, 1, nil, 1, 0, "", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "builds" WHERE repo_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.CreateBuild(context.TODO(), _build)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Build
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _build,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _build,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetBuildForRepo(context.TODO(), _repo, 1)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetBuildForRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetBuildForRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetBuildForRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/build/get_test.go b/database/build/get_test.go
new file mode 100644
index 000000000..9646224b7
--- /dev/null
+++ b/database/build/get_test.go
@@ -0,0 +1,86 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestBuild_Engine_GetBuild(t *testing.T) {
+ // setup types
+ _build := testBuild()
+ _build.SetID(1)
+ _build.SetRepoID(1)
+ _build.SetNumber(1)
+ _build.SetDeployPayload(nil)
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"}).
+ AddRow(1, 1, nil, 1, 0, "", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "builds" WHERE id = $1 LIMIT 1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateBuild(context.TODO(), _build)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Build
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _build,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _build,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetBuild(context.TODO(), 1)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetBuild for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetBuild for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetBuild for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/build/index.go b/database/build/index.go
new file mode 100644
index 000000000..51dec6eba
--- /dev/null
+++ b/database/build/index.go
@@ -0,0 +1,71 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import "context"
+
+const (
+ // CreateCreatedIndex represents a query to create an
+ // index on the builds table for the created column.
+ CreateCreatedIndex = `
+CREATE INDEX
+IF NOT EXISTS
+builds_created
+ON builds (created);
+`
+
+ // CreateRepoIDIndex represents a query to create an
+ // index on the builds table for the repo_id column.
+ CreateRepoIDIndex = `
+CREATE INDEX
+IF NOT EXISTS
+builds_repo_id
+ON builds (repo_id);
+`
+
+ // CreateSourceIndex represents a query to create an
+ // index on the builds table for the source column.
+ CreateSourceIndex = `
+CREATE INDEX
+IF NOT EXISTS
+builds_source
+ON builds (source);
+`
+
+ // CreateStatusIndex represents a query to create an
+ // index on the builds table for the status column.
+ CreateStatusIndex = `
+CREATE INDEX
+IF NOT EXISTS
+builds_status
+ON builds (status);
+`
+)
+
+// CreateBuildIndexes creates the indexes for the builds table in the database.
+func (e *engine) CreateBuildIndexes(ctx context.Context) error {
+ e.logger.Tracef("creating indexes for builds table in the database")
+
+ // create the created column index for the builds table
+ err := e.client.Exec(CreateCreatedIndex).Error
+ if err != nil {
+ return err
+ }
+
+ // create the repo_id column index for the builds table
+ err = e.client.Exec(CreateRepoIDIndex).Error
+ if err != nil {
+ return err
+ }
+
+ // create the source column index for the builds table
+ err = e.client.Exec(CreateSourceIndex).Error
+ if err != nil {
+ return err
+ }
+
+ // create the status column index for the builds table
+ return e.client.Exec(CreateStatusIndex).Error
+}
diff --git a/database/build/index_test.go b/database/build/index_test.go
new file mode 100644
index 000000000..35dd25008
--- /dev/null
+++ b/database/build/index_test.go
@@ -0,0 +1,63 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestBuild_Engine_CreateBuildIndexes(t *testing.T) {
+ // setup types
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ _mock.ExpectExec(CreateCreatedIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateSourceIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateStatusIndex).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.CreateBuildIndexes(context.TODO())
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateBuildIndexes for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateBuildIndexes for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/build/interface.go b/database/build/interface.go
new file mode 100644
index 000000000..d2b8d9f41
--- /dev/null
+++ b/database/build/interface.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 build
+
+import (
+ "context"
+
+ "github.com/go-vela/types/library"
+)
+
+// BuildInterface represents the Vela interface for build
+// functions with the supported Database backends.
+//
+//nolint:revive // ignore name stutter
+type BuildInterface interface {
+ // Build Data Definition Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_definition_language
+
+ // CreateBuildIndexes defines a function that creates the indexes for the builds table.
+ CreateBuildIndexes(context.Context) error
+ // CreateBuildTable defines a function that creates the builds table.
+ CreateBuildTable(context.Context, string) error
+
+ // Build Data Manipulation Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_manipulation_language
+
+ // CleanBuilds defines a function that sets pending or running builds to error created before a given time.
+ CleanBuilds(context.Context, string, int64) (int64, error)
+ // CountBuilds defines a function that gets the count of all builds.
+ CountBuilds(context.Context) (int64, error)
+ // CountBuildsForDeployment defines a function that gets the count of builds by deployment url.
+ CountBuildsForDeployment(context.Context, *library.Deployment, map[string]interface{}) (int64, error)
+ // CountBuildsForOrg defines a function that gets the count of builds by org name.
+ CountBuildsForOrg(context.Context, string, map[string]interface{}) (int64, error)
+ // CountBuildsForRepo defines a function that gets the count of builds by repo ID.
+ CountBuildsForRepo(context.Context, *library.Repo, map[string]interface{}) (int64, error)
+ // CountBuildsForStatus defines a function that gets the count of builds by status.
+ CountBuildsForStatus(context.Context, string, map[string]interface{}) (int64, error)
+ // CreateBuild defines a function that creates a new build.
+ CreateBuild(context.Context, *library.Build) (*library.Build, error)
+ // DeleteBuild defines a function that deletes an existing build.
+ DeleteBuild(context.Context, *library.Build) error
+ // GetBuild defines a function that gets a build by ID.
+ GetBuild(context.Context, int64) (*library.Build, error)
+ // GetBuildForRepo defines a function that gets a build by repo ID and number.
+ GetBuildForRepo(context.Context, *library.Repo, int) (*library.Build, error)
+ // LastBuildForRepo defines a function that gets the last build ran by repo ID and branch.
+ LastBuildForRepo(context.Context, *library.Repo, string) (*library.Build, error)
+ // ListBuilds defines a function that gets a list of all builds.
+ ListBuilds(context.Context) ([]*library.Build, error)
+ // ListBuildsForDeployment defines a function that gets a list of builds by deployment url.
+ ListBuildsForDeployment(context.Context, *library.Deployment, map[string]interface{}, int, int) ([]*library.Build, int64, error)
+ // ListBuildsForOrg defines a function that gets a list of builds by org name.
+ ListBuildsForOrg(context.Context, string, map[string]interface{}, int, int) ([]*library.Build, int64, error)
+ // ListBuildsForRepo defines a function that gets a list of builds by repo ID.
+ ListBuildsForRepo(context.Context, *library.Repo, map[string]interface{}, int64, int64, int, int) ([]*library.Build, int64, error)
+ // ListPendingAndRunningBuilds defines a function that gets a list of pending and running builds.
+ ListPendingAndRunningBuilds(context.Context, string) ([]*library.BuildQueue, error)
+ // UpdateBuild defines a function that updates an existing build.
+ UpdateBuild(context.Context, *library.Build) (*library.Build, error)
+}
diff --git a/database/build/last_repo.go b/database/build/last_repo.go
new file mode 100644
index 000000000..81861c8b4
--- /dev/null
+++ b/database/build/last_repo.go
@@ -0,0 +1,48 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "errors"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+// LastBuildForRepo gets the last build by repo ID and branch from the database.
+func (e *engine) LastBuildForRepo(ctx context.Context, r *library.Repo, branch string) (*library.Build, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ }).Tracef("getting last build for repo %s from the database", r.GetFullName())
+
+ // variable to store query results
+ b := new(database.Build)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableBuild).
+ Where("repo_id = ?", r.GetID()).
+ Where("branch = ?", branch).
+ Order("number DESC").
+ Take(b).
+ Error
+ if err != nil {
+ // check if the query returned a record not found error
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ // the record will not exist if it is a new repo
+ return nil, nil
+ }
+
+ return nil, err
+ }
+
+ return b.ToLibrary(), nil
+}
diff --git a/database/build/last_repo_test.go b/database/build/last_repo_test.go
new file mode 100644
index 000000000..07e5549b2
--- /dev/null
+++ b/database/build/last_repo_test.go
@@ -0,0 +1,96 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestBuild_Engine_LastBuildForRepo(t *testing.T) {
+ // setup types
+ _build := testBuild()
+ _build.SetID(1)
+ _build.SetRepoID(1)
+ _build.SetNumber(1)
+ _build.SetDeployPayload(nil)
+ _build.SetBranch("master")
+
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetHash("baz")
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+ _repo.SetVisibility("public")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"}).
+ AddRow(1, 1, nil, 1, 0, "", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "master", "", "", "", "", "", "", 0)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "builds" WHERE repo_id = $1 AND branch = $2 ORDER BY number DESC LIMIT 1`).WithArgs(1, "master").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateBuild(context.TODO(), _build)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Build
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _build,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _build,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.LastBuildForRepo(context.TODO(), _repo, "master")
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("LastBuildForRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("LastBuildForRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("LastBuildForRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/build/list.go b/database/build/list.go
new file mode 100644
index 000000000..9809af5d9
--- /dev/null
+++ b/database/build/list.go
@@ -0,0 +1,56 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// ListBuilds gets a list of all builds from the database.
+func (e *engine) ListBuilds(ctx context.Context) ([]*library.Build, error) {
+ e.logger.Trace("listing all builds from the database")
+
+ // variables to store query results and return value
+ count := int64(0)
+ b := new([]database.Build)
+ builds := []*library.Build{}
+
+ // count the results
+ count, err := e.CountBuilds(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return builds, nil
+ }
+
+ // send query to the database and store result in variable
+ err = e.client.
+ Table(constants.TableBuild).
+ Find(&b).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // iterate through all query results
+ for _, build := range *b {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := build
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Build.ToLibrary
+ builds = append(builds, tmp.ToLibrary())
+ }
+
+ return builds, nil
+}
diff --git a/database/build/list_deployment.go b/database/build/list_deployment.go
new file mode 100644
index 000000000..0d931fae7
--- /dev/null
+++ b/database/build/list_deployment.go
@@ -0,0 +1,68 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// ListBuildsForDeployment gets a list of builds by deployment url from the database.
+//
+//nolint:lll // ignore long line length due to variable names
+func (e *engine) ListBuildsForDeployment(ctx context.Context, d *library.Deployment, filters map[string]interface{}, page, perPage int) ([]*library.Build, int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "deployment": d.GetURL(),
+ }).Tracef("listing builds for deployment %s from the database", d.GetURL())
+
+ // variables to store query results and return values
+ count := int64(0)
+ b := new([]database.Build)
+ builds := []*library.Build{}
+
+ // count the results
+ count, err := e.CountBuildsForDeployment(context.TODO(), d, filters)
+ if err != nil {
+ return builds, 0, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return builds, 0, nil
+ }
+
+ // calculate offset for pagination through results
+ offset := perPage * (page - 1)
+
+ err = e.client.
+ Table(constants.TableBuild).
+ Where("source = ?", d.GetURL()).
+ Where(filters).
+ Order("number DESC").
+ Limit(perPage).
+ Offset(offset).
+ Find(&b).
+ Error
+ if err != nil {
+ return nil, count, err
+ }
+
+ // iterate through all query results
+ for _, build := range *b {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := build
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Build.ToLibrary
+ builds = append(builds, tmp.ToLibrary())
+ }
+
+ return builds, count, nil
+}
diff --git a/database/build/list_deployment_test.go b/database/build/list_deployment_test.go
new file mode 100644
index 000000000..57a54522d
--- /dev/null
+++ b/database/build/list_deployment_test.go
@@ -0,0 +1,113 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestBuild_Engine_ListBuildsForDeployment(t *testing.T) {
+ // setup types
+ _buildOne := testBuild()
+ _buildOne.SetID(1)
+ _buildOne.SetRepoID(1)
+ _buildOne.SetNumber(1)
+ _buildOne.SetDeployPayload(nil)
+ _buildOne.SetSource("https://github.com/github/octocat/deployments/1")
+
+ _buildTwo := testBuild()
+ _buildTwo.SetID(2)
+ _buildTwo.SetRepoID(1)
+ _buildTwo.SetNumber(2)
+ _buildTwo.SetDeployPayload(nil)
+ _buildTwo.SetSource("https://github.com/github/octocat/deployments/1")
+
+ _deployment := testDeployment()
+ _deployment.SetID(1)
+ _deployment.SetRepoID(1)
+ _deployment.SetURL("https://github.com/github/octocat/deployments/1")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected count query result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the count query
+ _mock.ExpectQuery(`SELECT count(*) FROM "builds" WHERE source = $1`).WithArgs("https://github.com/github/octocat/deployments/1").WillReturnRows(_rows)
+
+ // create expected query result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"}).
+ AddRow(2, 1, nil, 2, 0, "", "", "", "", 0, 0, 0, 0, "", nil, "", "https://github.com/github/octocat/deployments/1", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0).
+ AddRow(1, 1, nil, 1, 0, "", "", "", "", 0, 0, 0, 0, "", nil, "", "https://github.com/github/octocat/deployments/1", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "builds" WHERE source = $1 ORDER BY number DESC LIMIT 10`).WithArgs("https://github.com/github/octocat/deployments/1").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateBuild(context.TODO(), _buildOne)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateBuild(context.TODO(), _buildTwo)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Build
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Build{_buildTwo, _buildOne},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.Build{_buildTwo, _buildOne},
+ },
+ }
+
+ filters := map[string]interface{}{}
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, _, err := test.database.ListBuildsForDeployment(context.TODO(), _deployment, filters, 1, 10)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListBuildsForDeployment for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListBuildsForDeployment for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListBuildsForDeployment for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/build/list_org.go b/database/build/list_org.go
new file mode 100644
index 000000000..254c0c9e9
--- /dev/null
+++ b/database/build/list_org.go
@@ -0,0 +1,71 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// ListBuildsForOrg gets a list of builds by org name from the database.
+//
+//nolint:lll // ignore long line length due to variable names
+func (e *engine) ListBuildsForOrg(ctx context.Context, org string, filters map[string]interface{}, page, perPage int) ([]*library.Build, int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": org,
+ }).Tracef("listing builds for org %s from the database", org)
+
+ // variables to store query results and return values
+ count := int64(0)
+ b := new([]database.Build)
+ builds := []*library.Build{}
+
+ // count the results
+ count, err := e.CountBuildsForOrg(ctx, org, filters)
+ if err != nil {
+ return builds, 0, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return builds, 0, nil
+ }
+
+ // calculate offset for pagination through results
+ offset := perPage * (page - 1)
+
+ err = e.client.
+ Table(constants.TableBuild).
+ Select("builds.*").
+ Joins("JOIN repos ON builds.repo_id = repos.id").
+ Where("repos.org = ?", org).
+ Where(filters).
+ Order("created DESC").
+ Order("id").
+ Limit(perPage).
+ Offset(offset).
+ Find(&b).
+ Error
+ if err != nil {
+ return nil, count, err
+ }
+
+ // iterate through all query results
+ for _, build := range *b {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := build
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Build.ToLibrary
+ builds = append(builds, tmp.ToLibrary())
+ }
+
+ return builds, count, nil
+}
diff --git a/database/build/list_org_test.go b/database/build/list_org_test.go
new file mode 100644
index 000000000..95fc9a456
--- /dev/null
+++ b/database/build/list_org_test.go
@@ -0,0 +1,205 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+func TestBuild_Engine_ListBuildsForOrg(t *testing.T) {
+ // setup types
+ _buildOne := testBuild()
+ _buildOne.SetID(1)
+ _buildOne.SetRepoID(1)
+ _buildOne.SetNumber(1)
+ _buildOne.SetDeployPayload(nil)
+ _buildOne.SetEvent("push")
+
+ _buildTwo := testBuild()
+ _buildTwo.SetID(2)
+ _buildTwo.SetRepoID(2)
+ _buildTwo.SetNumber(2)
+ _buildTwo.SetDeployPayload(nil)
+ _buildTwo.SetEvent("push")
+
+ _repoOne := testRepo()
+ _repoOne.SetID(1)
+ _repoOne.SetUserID(1)
+ _repoOne.SetHash("baz")
+ _repoOne.SetOrg("foo")
+ _repoOne.SetName("bar")
+ _repoOne.SetFullName("foo/bar")
+ _repoOne.SetVisibility("public")
+ _repoOne.SetPipelineType("yaml")
+ _repoOne.SetTopics([]string{})
+
+ _repoTwo := testRepo()
+ _repoTwo.SetID(2)
+ _repoTwo.SetUserID(1)
+ _repoTwo.SetHash("bar")
+ _repoTwo.SetOrg("foo")
+ _repoTwo.SetName("baz")
+ _repoTwo.SetFullName("foo/baz")
+ _repoTwo.SetVisibility("public")
+ _repoTwo.SetPipelineType("yaml")
+ _repoTwo.SetTopics([]string{})
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected count query without filters result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+ // ensure the mock expects the count query without filters
+ _mock.ExpectQuery(`SELECT count(*) FROM "builds" JOIN repos ON builds.repo_id = repos.id WHERE repos.org = $1`).WithArgs("foo").WillReturnRows(_rows)
+ // create expected query without filters result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"}).
+ AddRow(1, 1, nil, 1, 0, "push", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0).
+ AddRow(2, 2, nil, 2, 0, "push", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0)
+ // ensure the mock expects the query without filters
+ _mock.ExpectQuery(`SELECT builds.* FROM "builds" JOIN repos ON builds.repo_id = repos.id WHERE repos.org = $1 ORDER BY created DESC,id LIMIT 10`).WithArgs("foo").WillReturnRows(_rows)
+
+ // create expected count query with event filter result in mock
+ _rows = sqlmock.NewRows([]string{"count"}).AddRow(2)
+ // ensure the mock expects the count query with event filter
+ _mock.ExpectQuery(`SELECT count(*) FROM "builds" JOIN repos ON builds.repo_id = repos.id WHERE repos.org = $1 AND "event" = $2`).WithArgs("foo", "push").WillReturnRows(_rows)
+ // create expected query with event filter result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"}).
+ AddRow(1, 1, nil, 1, 0, "push", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0).
+ AddRow(2, 2, nil, 2, 0, "push", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0)
+ // ensure the mock expects the query with event filter
+ _mock.ExpectQuery(`SELECT builds.* FROM "builds" JOIN repos ON builds.repo_id = repos.id WHERE repos.org = $1 AND "event" = $2 ORDER BY created DESC,id LIMIT 10`).WithArgs("foo", "push").WillReturnRows(_rows)
+
+ // create expected count query with visibility filter result in mock
+ _rows = sqlmock.NewRows([]string{"count"}).AddRow(2)
+ // ensure the mock expects the count query with visibility filter
+ _mock.ExpectQuery(`SELECT count(*) FROM "builds" JOIN repos ON builds.repo_id = repos.id WHERE repos.org = $1 AND "visibility" = $2`).WithArgs("foo", "public").WillReturnRows(_rows)
+ // create expected query with visibility filter result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"}).
+ AddRow(1, 1, nil, 1, 0, "push", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0).
+ AddRow(2, 2, nil, 2, 0, "push", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0)
+ // ensure the mock expects the query with visibility filter
+ _mock.ExpectQuery(`SELECT builds.* FROM "builds" JOIN repos ON builds.repo_id = repos.id WHERE repos.org = $1 AND "visibility" = $2 ORDER BY created DESC,id LIMIT 10`).WithArgs("foo", "public").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateBuild(context.TODO(), _buildOne)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateBuild(context.TODO(), _buildTwo)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ err = _sqlite.client.AutoMigrate(&database.Repo{})
+ if err != nil {
+ t.Errorf("unable to create repo table for sqlite: %v", err)
+ }
+
+ err = _sqlite.client.Table(constants.TableRepo).Create(database.RepoFromLibrary(_repoOne)).Error
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ err = _sqlite.client.Table(constants.TableRepo).Create(database.RepoFromLibrary(_repoTwo)).Error
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ filters map[string]interface{}
+ want []*library.Build
+ }{
+ {
+ failure: false,
+ name: "postgres without filters",
+ database: _postgres,
+ filters: map[string]interface{}{},
+ want: []*library.Build{_buildOne, _buildTwo},
+ },
+ {
+ failure: false,
+ name: "postgres with event filter",
+ database: _postgres,
+ filters: map[string]interface{}{
+ "event": "push",
+ },
+ want: []*library.Build{_buildOne, _buildTwo},
+ },
+ {
+ failure: false,
+ name: "postgres with visibility filter",
+ database: _postgres,
+ filters: map[string]interface{}{
+ "visibility": "public",
+ },
+ want: []*library.Build{_buildOne, _buildTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite3 without filters",
+ database: _sqlite,
+ filters: map[string]interface{}{},
+ want: []*library.Build{_buildOne, _buildTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite3 with event filter",
+ database: _sqlite,
+ filters: map[string]interface{}{
+ "event": "push",
+ },
+ want: []*library.Build{_buildOne, _buildTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite3 with visibility filter",
+ database: _sqlite,
+ filters: map[string]interface{}{
+ "visibility": "public",
+ },
+ want: []*library.Build{_buildOne, _buildTwo},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, _, err := test.database.ListBuildsForOrg(context.TODO(), "foo", test.filters, 1, 10)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListBuildsForOrg for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListBuildsForOrg for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListBuildsForOrg for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/build/list_pending_running.go b/database/build/list_pending_running.go
new file mode 100644
index 000000000..dbee3e704
--- /dev/null
+++ b/database/build/list_pending_running.go
@@ -0,0 +1,48 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// ListPendingAndRunningBuilds gets a list of all pending and running builds in the provided timeframe from the database.
+func (e *engine) ListPendingAndRunningBuilds(ctx context.Context, after string) ([]*library.BuildQueue, error) {
+ e.logger.Trace("listing all pending and running builds from the database")
+
+ // variables to store query results and return value
+ b := new([]database.BuildQueue)
+ builds := []*library.BuildQueue{}
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableBuild).
+ Select("builds.created, builds.number, builds.status, repos.full_name").
+ InnerJoins("INNER JOIN repos ON builds.repo_id = repos.id").
+ Where("builds.created > ?", after).
+ Where("builds.status = 'running' OR builds.status = 'pending'").
+ Find(&b).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // iterate through all query results
+ for _, build := range *b {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := build
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Build.ToLibrary
+ builds = append(builds, tmp.ToLibrary())
+ }
+
+ return builds, nil
+}
diff --git a/database/build/list_pending_running_test.go b/database/build/list_pending_running_test.go
new file mode 100644
index 000000000..4ee71e1c7
--- /dev/null
+++ b/database/build/list_pending_running_test.go
@@ -0,0 +1,132 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+func TestBuild_Engine_ListPendingAndRunningBuilds(t *testing.T) {
+ // setup types
+ _buildOne := testBuild()
+ _buildOne.SetID(1)
+ _buildOne.SetRepoID(1)
+ _buildOne.SetNumber(1)
+ _buildOne.SetStatus("running")
+ _buildOne.SetCreated(1)
+ _buildOne.SetDeployPayload(nil)
+
+ _buildTwo := testBuild()
+ _buildTwo.SetID(2)
+ _buildTwo.SetRepoID(1)
+ _buildTwo.SetNumber(2)
+ _buildTwo.SetStatus("pending")
+ _buildTwo.SetCreated(1)
+ _buildTwo.SetDeployPayload(nil)
+
+ _queueOne := new(library.BuildQueue)
+ _queueOne.SetCreated(1)
+ _queueOne.SetFullName("foo/bar")
+ _queueOne.SetNumber(1)
+ _queueOne.SetStatus("running")
+
+ _queueTwo := new(library.BuildQueue)
+ _queueTwo.SetCreated(1)
+ _queueTwo.SetFullName("foo/bar")
+ _queueTwo.SetNumber(2)
+ _queueTwo.SetStatus("pending")
+
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetHash("baz")
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+ _repo.SetVisibility("public")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected name query result in mock
+ _rows := sqlmock.NewRows([]string{"created", "full_name", "number", "status"}).AddRow(1, "foo/bar", 2, "pending").AddRow(1, "foo/bar", 1, "running")
+
+ // ensure the mock expects the name query
+ _mock.ExpectQuery(`SELECT builds.created, builds.number, builds.status, repos.full_name FROM "builds" INNER JOIN repos ON builds.repo_id = repos.id WHERE builds.created > $1 AND (builds.status = 'running' OR builds.status = 'pending')`).WithArgs("0").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateBuild(context.TODO(), _buildOne)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateBuild(context.TODO(), _buildTwo)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ err = _sqlite.client.AutoMigrate(&database.Repo{})
+ if err != nil {
+ t.Errorf("unable to create repo table for sqlite: %v", err)
+ }
+
+ err = _sqlite.client.Table(constants.TableRepo).Create(database.RepoFromLibrary(_repo)).Error
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.BuildQueue
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.BuildQueue{_queueTwo, _queueOne},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.BuildQueue{_queueTwo, _queueOne},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.ListPendingAndRunningBuilds(context.TODO(), "0")
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListPendingAndRunningBuilds for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListPendingAndRunningBuilds for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListPendingAndRunningBuilds for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/build/list_repo.go b/database/build/list_repo.go
new file mode 100644
index 000000000..8ee78e35c
--- /dev/null
+++ b/database/build/list_repo.go
@@ -0,0 +1,71 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// ListBuildsForRepo gets a list of builds by repo ID from the database.
+//
+//nolint:lll // ignore long line length due to variable names
+func (e *engine) ListBuildsForRepo(ctx context.Context, r *library.Repo, filters map[string]interface{}, before, after int64, page, perPage int) ([]*library.Build, int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ }).Tracef("listing builds for repo %s from the database", r.GetFullName())
+
+ // variables to store query results and return values
+ count := int64(0)
+ b := new([]database.Build)
+ builds := []*library.Build{}
+
+ // count the results
+ count, err := e.CountBuildsForRepo(ctx, r, filters)
+ if err != nil {
+ return builds, 0, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return builds, 0, nil
+ }
+
+ // calculate offset for pagination through results
+ offset := perPage * (page - 1)
+
+ err = e.client.
+ Table(constants.TableBuild).
+ Where("repo_id = ?", r.GetID()).
+ Where("created < ?", before).
+ Where("created > ?", after).
+ Where(filters).
+ Order("number DESC").
+ Limit(perPage).
+ Offset(offset).
+ Find(&b).
+ Error
+ if err != nil {
+ return nil, count, err
+ }
+
+ // iterate through all query results
+ for _, build := range *b {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := build
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Build.ToLibrary
+ builds = append(builds, tmp.ToLibrary())
+ }
+
+ return builds, count, nil
+}
diff --git a/database/build/list_repo_test.go b/database/build/list_repo_test.go
new file mode 100644
index 000000000..f16ed714d
--- /dev/null
+++ b/database/build/list_repo_test.go
@@ -0,0 +1,118 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestBuild_Engine_ListBuildsForRepo(t *testing.T) {
+ // setup types
+ _buildOne := testBuild()
+ _buildOne.SetID(1)
+ _buildOne.SetRepoID(1)
+ _buildOne.SetNumber(1)
+ _buildOne.SetDeployPayload(nil)
+ _buildOne.SetCreated(1)
+
+ _buildTwo := testBuild()
+ _buildTwo.SetID(2)
+ _buildTwo.SetRepoID(1)
+ _buildTwo.SetNumber(2)
+ _buildTwo.SetDeployPayload(nil)
+ _buildTwo.SetCreated(2)
+
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetHash("baz")
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+ _repo.SetVisibility("public")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected count query result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the count query
+ _mock.ExpectQuery(`SELECT count(*) FROM "builds" WHERE repo_id = $1`).WithArgs(1).WillReturnRows(_rows)
+
+ // create expected query result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"}).
+ AddRow(2, 1, nil, 2, 0, "", "", "", "", 0, 2, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0).
+ AddRow(1, 1, nil, 1, 0, "", "", "", "", 0, 1, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "builds" WHERE repo_id = $1 AND created < $2 AND created > $3 ORDER BY number DESC LIMIT 10`).WithArgs(1, AnyArgument{}, 0).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateBuild(context.TODO(), _buildOne)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateBuild(context.TODO(), _buildTwo)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Build
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Build{_buildTwo, _buildOne},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.Build{_buildTwo, _buildOne},
+ },
+ }
+
+ filters := map[string]interface{}{}
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, _, err := test.database.ListBuildsForRepo(context.TODO(), _repo, filters, time.Now().UTC().Unix(), 0, 1, 10)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListBuildsForRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListBuildsForRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListBuildsForRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/build/list_test.go b/database/build/list_test.go
new file mode 100644
index 000000000..69d826755
--- /dev/null
+++ b/database/build/list_test.go
@@ -0,0 +1,104 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestBuild_Engine_ListBuilds(t *testing.T) {
+ // setup types
+ _buildOne := testBuild()
+ _buildOne.SetID(1)
+ _buildOne.SetRepoID(1)
+ _buildOne.SetNumber(1)
+ _buildOne.SetDeployPayload(nil)
+
+ _buildTwo := testBuild()
+ _buildTwo.SetID(2)
+ _buildTwo.SetRepoID(1)
+ _buildTwo.SetNumber(2)
+ _buildTwo.SetDeployPayload(nil)
+
+ _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 "builds"`).WillReturnRows(_rows)
+
+ // create expected result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "repo_id", "pipeline_id", "number", "parent", "event", "event_action", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"}).
+ AddRow(1, 1, nil, 1, 0, "", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0).
+ AddRow(2, 1, nil, 2, 0, "", "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "builds"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateBuild(context.TODO(), _buildOne)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateBuild(context.TODO(), _buildTwo)
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Build
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Build{_buildOne, _buildTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.Build{_buildOne, _buildTwo},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.ListBuilds(context.TODO())
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListBuilds for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListBuilds for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListBuilds for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/build/opts.go b/database/build/opts.go
new file mode 100644
index 000000000..69c912435
--- /dev/null
+++ b/database/build/opts.go
@@ -0,0 +1,55 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+// EngineOpt represents a configuration option to initialize the database engine for Builds.
+type EngineOpt func(*engine) error
+
+// WithClient sets the gorm.io/gorm client in the database engine for Builds.
+func WithClient(client *gorm.DB) EngineOpt {
+ return func(e *engine) error {
+ // set the gorm.io/gorm client in the build engine
+ e.client = client
+
+ return nil
+ }
+}
+
+// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for Builds.
+func WithLogger(logger *logrus.Entry) EngineOpt {
+ return func(e *engine) error {
+ // set the github.com/sirupsen/logrus logger in the build engine
+ e.logger = logger
+
+ return nil
+ }
+}
+
+// WithSkipCreation sets the skip creation logic in the database engine for Builds.
+func WithSkipCreation(skipCreation bool) EngineOpt {
+ return func(e *engine) error {
+ // set to skip creating tables and indexes in the build engine
+ e.config.SkipCreation = skipCreation
+
+ return nil
+ }
+}
+
+// WithContext sets the context in the database engine for Builds.
+func WithContext(ctx context.Context) EngineOpt {
+ return func(e *engine) error {
+ e.ctx = ctx
+
+ return nil
+ }
+}
diff --git a/database/build/opts_test.go b/database/build/opts_test.go
new file mode 100644
index 000000000..69afe0b4a
--- /dev/null
+++ b/database/build/opts_test.go
@@ -0,0 +1,211 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+func TestBuild_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 TestBuild_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 TestBuild_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)
+ }
+ })
+ }
+}
+
+func TestBuild_EngineOpt_WithContext(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ ctx context.Context
+ want context.Context
+ }{
+ {
+ failure: false,
+ name: "context set to TODO",
+ ctx: context.TODO(),
+ want: context.TODO(),
+ },
+ {
+ failure: false,
+ name: "context set to nil",
+ ctx: nil,
+ want: nil,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithContext(test.ctx)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithContext for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithContext returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.ctx, test.want) {
+ t.Errorf("WithContext is %v, want %v", e.ctx, test.want)
+ }
+ })
+ }
+}
diff --git a/database/build/table.go b/database/build/table.go
new file mode 100644
index 000000000..91499ef4a
--- /dev/null
+++ b/database/build/table.go
@@ -0,0 +1,112 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+)
+
+const (
+ // CreatePostgresTable represents a query to create the Postgres builds table.
+ CreatePostgresTable = `
+CREATE TABLE
+IF NOT EXISTS
+builds (
+ id SERIAL PRIMARY KEY,
+ repo_id INTEGER,
+ pipeline_id INTEGER,
+ number INTEGER,
+ parent INTEGER,
+ event VARCHAR(250),
+ event_action VARCHAR(250),
+ status VARCHAR(250),
+ error VARCHAR(1000),
+ enqueued INTEGER,
+ created INTEGER,
+ started INTEGER,
+ finished INTEGER,
+ deploy VARCHAR(500),
+ deploy_payload VARCHAR(2000),
+ clone VARCHAR(1000),
+ source VARCHAR(1000),
+ title VARCHAR(1000),
+ message VARCHAR(2000),
+ commit VARCHAR(500),
+ sender VARCHAR(250),
+ author VARCHAR(250),
+ email VARCHAR(500),
+ link VARCHAR(1000),
+ branch VARCHAR(500),
+ ref VARCHAR(500),
+ base_ref VARCHAR(500),
+ head_ref VARCHAR(500),
+ host VARCHAR(250),
+ runtime VARCHAR(250),
+ distribution VARCHAR(250),
+ timestamp INTEGER,
+ UNIQUE(repo_id, number)
+);
+`
+
+ // CreateSqliteTable represents a query to create the Sqlite builds table.
+ CreateSqliteTable = `
+CREATE TABLE
+IF NOT EXISTS
+builds (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ repo_id INTEGER,
+ pipeline_id INTEGER,
+ number INTEGER,
+ parent INTEGER,
+ event TEXT,
+ event_action TEXT,
+ status TEXT,
+ error TEXT,
+ enqueued INTEGER,
+ created INTEGER,
+ started INTEGER,
+ finished INTEGER,
+ deploy TEXT,
+ deploy_payload TEXT,
+ clone TEXT,
+ source TEXT,
+ title TEXT,
+ message TEXT,
+ 'commit' TEXT,
+ sender TEXT,
+ author TEXT,
+ email TEXT,
+ link TEXT,
+ branch TEXT,
+ ref TEXT,
+ base_ref TEXT,
+ head_ref TEXT,
+ host TEXT,
+ runtime TEXT,
+ distribution TEXT,
+ timestamp INTEGER,
+ UNIQUE(repo_id, number)
+);
+`
+)
+
+// CreateBuildTable creates the builds table in the database.
+func (e *engine) CreateBuildTable(ctx context.Context, driver string) error {
+ e.logger.Tracef("creating builds table in the database")
+
+ // handle the driver provided to create the table
+ switch driver {
+ case constants.DriverPostgres:
+ // create the builds table for Postgres
+ return e.client.Exec(CreatePostgresTable).Error
+ case constants.DriverSqlite:
+ fallthrough
+ default:
+ // create the builds table for Sqlite
+ return e.client.Exec(CreateSqliteTable).Error
+ }
+}
diff --git a/database/build/table_test.go b/database/build/table_test.go
new file mode 100644
index 000000000..a08997fce
--- /dev/null
+++ b/database/build/table_test.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestBuild_Engine_CreateBuildTable(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.CreateBuildTable(context.TODO(), test.name)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateBuildTable for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateBuildTable for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/build/update.go b/database/build/update.go
new file mode 100644
index 000000000..bdd506187
--- /dev/null
+++ b/database/build/update.go
@@ -0,0 +1,43 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code with create.go
+package build
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// UpdateBuild updates an existing build in the database.
+func (e *engine) UpdateBuild(ctx context.Context, b *library.Build) (*library.Build, error) {
+ e.logger.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ }).Tracef("updating build %d in the database", b.GetNumber())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#BuildFromLibrary
+ build := database.BuildFromLibrary(b)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Build.Validate
+ err := build.Validate()
+ if err != nil {
+ return nil, err
+ }
+
+ // crop build if any columns are too large
+ build = build.Crop()
+
+ // send query to the database
+ result := e.client.Table(constants.TableBuild).Save(build)
+
+ return build.ToLibrary(), result.Error
+}
diff --git a/database/build/update_test.go b/database/build/update_test.go
new file mode 100644
index 000000000..7e5441145
--- /dev/null
+++ b/database/build/update_test.go
@@ -0,0 +1,81 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package build
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestBuild_Engine_UpdateBuild(t *testing.T) {
+ // setup types
+ _build := testBuild()
+ _build.SetID(1)
+ _build.SetRepoID(1)
+ _build.SetNumber(1)
+ _build.SetDeployPayload(nil)
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`UPDATE "builds"
+SET "repo_id"=$1,"pipeline_id"=$2,"number"=$3,"parent"=$4,"event"=$5,"event_action"=$6,"status"=$7,"error"=$8,"enqueued"=$9,"created"=$10,"started"=$11,"finished"=$12,"deploy"=$13,"deploy_payload"=$14,"clone"=$15,"source"=$16,"title"=$17,"message"=$18,"commit"=$19,"sender"=$20,"author"=$21,"email"=$22,"link"=$23,"branch"=$24,"ref"=$25,"base_ref"=$26,"head_ref"=$27,"host"=$28,"runtime"=$29,"distribution"=$30
+WHERE "id" = $31`).
+ WithArgs(1, nil, 1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, AnyArgument{}, nil, nil, nil, nil, nil, nil, 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.CreateBuild(context.TODO(), _build)
+ if err != nil {
+ t.Errorf("unable to create test build 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) {
+ got, err := test.database.UpdateBuild(context.TODO(), _build)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("UpdateBuild for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("UpdateBuild for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, _build) {
+ t.Errorf("UpdateBuild for %s returned %s, want %s", test.name, got, _build)
+ }
+ })
+ }
+}
diff --git a/database/close.go b/database/close.go
new file mode 100644
index 000000000..0aff8e51d
--- /dev/null
+++ b/database/close.go
@@ -0,0 +1,18 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package database
+
+// Close stops and terminates the connection to the database.
+func (e *engine) Close() error {
+ e.logger.Tracef("closing connection to the %s database", e.Driver())
+
+ // capture database/sql database from gorm.io/gorm database
+ _sql, err := e.client.DB()
+ if err != nil {
+ return err
+ }
+
+ return _sql.Close()
+}
diff --git a/database/close_test.go b/database/close_test.go
new file mode 100644
index 000000000..fe0c55b00
--- /dev/null
+++ b/database/close_test.go
@@ -0,0 +1,83 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package database
+
+import (
+ "testing"
+
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+func TestDatabase_Engine_Close(t *testing.T) {
+ _postgres, _mock := testPostgres(t)
+ defer _postgres.Close()
+ // ensure the mock expects the close
+ _mock.ExpectClose()
+
+ // create a test database without mocking the call
+ _unmocked, _ := testPostgres(t)
+
+ _sqlite := testSqlite(t)
+ defer _sqlite.Close()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ }{
+ {
+ name: "success with postgres",
+ failure: false,
+ database: _postgres,
+ },
+ {
+ name: "success with sqlite3",
+ failure: false,
+ database: _sqlite,
+ },
+ {
+ name: "failure without mocked call",
+ failure: true,
+ database: _unmocked,
+ },
+ {
+ name: "failure with invalid gorm database",
+ failure: true,
+ database: &engine{
+ config: &config{
+ Driver: "invalid",
+ },
+ client: &gorm.DB{
+ Config: &gorm.Config{
+ ConnPool: nil,
+ },
+ },
+ logger: logrus.NewEntry(logrus.StandardLogger()),
+ },
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := test.database.Close()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("Close for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("Close for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/context.go b/database/context.go
index 7e315511c..fd08cc371 100644
--- a/database/context.go
+++ b/database/context.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
@@ -6,6 +6,9 @@ package database
import (
"context"
+
+ "github.com/sirupsen/logrus"
+ "github.com/urfave/cli/v2"
)
const key = "database"
@@ -15,14 +18,14 @@ type Setter interface {
Set(string, interface{})
}
-// FromContext returns the database Service associated with this context.
-func FromContext(c context.Context) Service {
+// FromContext returns the database Interface associated with this context.
+func FromContext(c context.Context) Interface {
v := c.Value(key)
if v == nil {
return nil
}
- d, ok := v.(Service)
+ d, ok := v.(Interface)
if !ok {
return nil
}
@@ -30,8 +33,24 @@ func FromContext(c context.Context) Service {
return d
}
-// ToContext adds the database Service to this context if it supports
+// ToContext adds the database Interface to this context if it supports
// the Setter interface.
-func ToContext(c Setter, d Service) {
+func ToContext(c Setter, d Interface) {
c.Set(key, d)
}
+
+// FromCLIContext creates and returns a database engine from the urfave/cli context.
+func FromCLIContext(c *cli.Context) (Interface, error) {
+ logrus.Debug("creating database engine from CLI configuration")
+
+ return New(
+ WithAddress(c.String("database.addr")),
+ WithCompressionLevel(c.Int("database.compression.level")),
+ WithConnectionLife(c.Duration("database.connection.life")),
+ WithConnectionIdle(c.Int("database.connection.idle")),
+ WithConnectionOpen(c.Int("database.connection.open")),
+ WithDriver(c.String("database.driver")),
+ WithEncryptionKey(c.String("database.encryption.key")),
+ WithSkipCreation(c.Bool("database.skip_creation")),
+ )
+}
diff --git a/database/context_test.go b/database/context_test.go
index 4c997ef04..4037b26c0 100644
--- a/database/context_test.go
+++ b/database/context_test.go
@@ -1,89 +1,152 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
package database
import (
+ "flag"
+ "reflect"
"testing"
+ "time"
"github.com/gin-gonic/gin"
- "github.com/go-vela/server/database/sqlite"
+ "github.com/urfave/cli/v2"
)
func TestDatabase_FromContext(t *testing.T) {
- // setup types
- want, _ := sqlite.NewTest()
- defer func() { _sql, _ := want.Sqlite.DB(); _sql.Close() }()
+ _postgres, _ := testPostgres(t)
+ defer _postgres.Close()
- // setup context
gin.SetMode(gin.TestMode)
- context, _ := gin.CreateTestContext(nil)
- context.Set(key, want)
-
- // run test
- got := FromContext(context)
+ ctx, _ := gin.CreateTestContext(nil)
+ ctx.Set(key, _postgres)
+
+ typeCtx, _ := gin.CreateTestContext(nil)
+ typeCtx.Set(key, nil)
+
+ nilCtx, _ := gin.CreateTestContext(nil)
+ nilCtx.Set(key, nil)
+
+ // setup tests
+ tests := []struct {
+ name string
+ context *gin.Context
+ want Interface
+ }{
+ {
+ name: "success",
+ context: ctx,
+ want: _postgres,
+ },
+ {
+ name: "failure with nil",
+ context: nilCtx,
+ want: nil,
+ },
+ {
+ name: "failure with wrong type",
+ context: typeCtx,
+ want: nil,
+ },
+ }
- if got != want {
- t.Errorf("FromContext is %v, want %v", got, want)
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got := FromContext(test.context)
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("FromContext for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
}
}
-func TestDatabase_FromContext_Bad(t *testing.T) {
- // setup context
- gin.SetMode(gin.TestMode)
+func TestDatabase_ToContext(t *testing.T) {
context, _ := gin.CreateTestContext(nil)
- context.Set(key, nil)
- // run test
- got := FromContext(context)
-
- if got != nil {
- t.Errorf("FromContext is %v, want nil", got)
+ _postgres, _ := testPostgres(t)
+ defer _postgres.Close()
+
+ _sqlite := testSqlite(t)
+ defer _sqlite.Close()
+
+ // setup tests
+ tests := []struct {
+ name string
+ database *engine
+ want *engine
+ }{
+ {
+ name: "success with postgres",
+ database: _postgres,
+ want: _postgres,
+ },
+ {
+ name: "success with sqlite3",
+ database: _sqlite,
+ want: _sqlite,
+ },
}
-}
-
-func TestDatabase_FromContext_WrongType(t *testing.T) {
- // setup context
- gin.SetMode(gin.TestMode)
- context, _ := gin.CreateTestContext(nil)
- context.Set(key, 1)
- // run test
- got := FromContext(context)
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ ToContext(context, test.want)
- if got != nil {
- t.Errorf("FromContext is %v, want nil", got)
+ got := context.Value(key)
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ToContext for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
}
}
-func TestDatabase_FromContext_Empty(t *testing.T) {
- // setup context
- gin.SetMode(gin.TestMode)
- context, _ := gin.CreateTestContext(nil)
-
- // run test
- got := FromContext(context)
-
- if got != nil {
- t.Errorf("FromContext is %v, want nil", got)
+func TestDatabase_FromCLIContext(t *testing.T) {
+ flags := flag.NewFlagSet("test", 0)
+ flags.String("database.driver", "sqlite3", "doc")
+ flags.String("database.addr", "file::memory:?cache=shared", "doc")
+ flags.Int("database.compression.level", 3, "doc")
+ flags.Duration("database.connection.life", 10*time.Second, "doc")
+ flags.Int("database.connection.idle", 5, "doc")
+ flags.Int("database.connection.open", 20, "doc")
+ flags.String("database.encryption.key", "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", "doc")
+ flags.Bool("database.skip_creation", true, "doc")
+
+ // setup tests
+ tests := []struct {
+ name string
+ failure bool
+ context *cli.Context
+ }{
+ {
+ name: "success",
+ failure: false,
+ context: cli.NewContext(&cli.App{Name: "vela"}, flags, nil),
+ },
+ {
+ name: "failure",
+ failure: true,
+ context: cli.NewContext(&cli.App{Name: "vela"}, flag.NewFlagSet("test", 0), nil),
+ },
}
-}
-func TestDatabase_ToContext(t *testing.T) {
- // setup types
- want, _ := sqlite.NewTest()
- defer func() { _sql, _ := want.Sqlite.DB(); _sql.Close() }()
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ _, err := FromCLIContext(test.context)
- // setup context
- gin.SetMode(gin.TestMode)
- context, _ := gin.CreateTestContext(nil)
- ToContext(context, want)
+ if test.failure {
+ if err == nil {
+ t.Errorf("FromCLIContext for %s should have returned err", test.name)
+ }
- // run test
- got := context.Value(key)
+ return
+ }
- if got != want {
- t.Errorf("ToContext is %v, want %v", got, want)
+ if err != nil {
+ t.Errorf("FromCLIContext for %s returned err: %v", test.name, err)
+ }
+ })
}
}
diff --git a/database/database.go b/database/database.go
index 884d8a734..ea93bfaaf 100644
--- a/database/database.go
+++ b/database/database.go
@@ -1,50 +1,174 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
package database
import (
+ "context"
"fmt"
+ "time"
+ "github.com/go-vela/server/database/build"
+ "github.com/go-vela/server/database/executable"
+ "github.com/go-vela/server/database/hook"
+ "github.com/go-vela/server/database/log"
+ "github.com/go-vela/server/database/pipeline"
+ "github.com/go-vela/server/database/repo"
+ "github.com/go-vela/server/database/schedule"
+ "github.com/go-vela/server/database/secret"
+ "github.com/go-vela/server/database/service"
+ "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"
-
"github.com/sirupsen/logrus"
+
+ "gorm.io/driver/postgres"
+ "gorm.io/driver/sqlite"
+ "gorm.io/gorm"
)
-// nolint: godot // top level comment ends in a list
-//
-// New creates and returns a Vela service capable of
-// integrating with the configured database provider.
+type (
+ // config represents the settings required to create the engine that implements the Interface.
+ config struct {
+ // specifies the address to use for the database engine
+ Address string
+ // specifies the level of compression to use for the database engine
+ CompressionLevel int
+ // specifies the connection duration to use for the database engine
+ ConnectionLife time.Duration
+ // specifies the maximum idle connections for the database engine
+ ConnectionIdle int
+ // specifies the maximum open connections for the database engine
+ ConnectionOpen int
+ // specifies the driver to use for the database engine
+ Driver string
+ // specifies the encryption key to use for the database engine
+ EncryptionKey string
+ // specifies to skip creating tables and indexes for the database engine
+ SkipCreation bool
+ }
+
+ // engine represents the functionality that implements the Interface.
+ engine struct {
+ // gorm.io/gorm database client used in database functions
+ client *gorm.DB
+ // engine configuration settings used in database functions
+ config *config
+ // engine context used in database functions
+ ctx context.Context
+ // sirupsen/logrus logger used in database functions
+ logger *logrus.Entry
+
+ build.BuildInterface
+ executable.BuildExecutableInterface
+ hook.HookInterface
+ log.LogInterface
+ pipeline.PipelineInterface
+ repo.RepoInterface
+ schedule.ScheduleInterface
+ secret.SecretInterface
+ service.ServiceInterface
+ step.StepInterface
+ user.UserInterface
+ worker.WorkerInterface
+ }
+)
+
+// New creates and returns an engine capable of integrating with the configured database provider.
//
-// Currently the following database providers are supported:
+// Currently, the following database providers are supported:
//
-// * Postgres
-// * Sqlite
-func New(s *Setup) (Service, error) {
- // validate the setup being provided
- //
- // https://pkg.go.dev/github.com/go-vela/server/database?tab=doc#Setup.Validate
- err := s.Validate()
+// * postgres
+// * sqlite3
+func New(opts ...EngineOpt) (Interface, error) {
+ // create new database engine
+ e := new(engine)
+
+ // create new fields
+ e.client = new(gorm.DB)
+ e.config = new(config)
+ e.logger = new(logrus.Entry)
+ e.ctx = context.TODO()
+
+ // apply all provided configuration options
+ for _, opt := range opts {
+ err := opt(e)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // validate the configuration being provided
+ err := e.config.Validate()
if err != nil {
return nil, err
}
- logrus.Debug("creating database service from setup")
+ // update the logger with additional metadata
+ e.logger = logrus.NewEntry(logrus.StandardLogger()).WithField("database", e.Driver())
+
+ e.logger.Trace("creating database engine from configuration")
// process the database driver being provided
- switch s.Driver {
+ switch e.config.Driver {
case constants.DriverPostgres:
- // handle the Postgres database driver being provided
- //
- // https://pkg.go.dev/github.com/go-vela/server/database?tab=doc#Setup.Postgres
- return s.Postgres()
+ // create the new Postgres database client
+ e.client, err = gorm.Open(postgres.Open(e.config.Address), &gorm.Config{})
+ if err != nil {
+ return nil, err
+ }
case constants.DriverSqlite:
- // handle the Sqlite database driver being provided
- //
- // https://pkg.go.dev/github.com/go-vela/server/database?tab=doc#Setup.Sqlite
- return s.Sqlite()
+ // create the new Sqlite database client
+ e.client, err = gorm.Open(sqlite.Open(e.config.Address), &gorm.Config{})
+ if err != nil {
+ return nil, err
+ }
default:
// handle an invalid database driver being provided
- return nil, fmt.Errorf("invalid database driver provided: %s", s.Driver)
+ return nil, fmt.Errorf("invalid database driver provided: %s", e.Driver())
}
+
+ // capture database/sql database from gorm.io/gorm database
+ db, err := e.client.DB()
+ if err != nil {
+ return nil, err
+ }
+
+ // set the maximum amount of time a connection may be reused
+ db.SetConnMaxLifetime(e.config.ConnectionLife)
+ // set the maximum number of connections in the idle connection pool
+ db.SetMaxIdleConns(e.config.ConnectionIdle)
+ // set the maximum number of open connections to the database
+ db.SetMaxOpenConns(e.config.ConnectionOpen)
+
+ // verify connection to the database
+ err = e.Ping()
+ if err != nil {
+ return nil, err
+ }
+
+ // create database agnostic engines for resources
+ err = e.NewResources(e.ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ return e, nil
+}
+
+// NewTest creates and returns an engine that integrates with an in-memory database provider.
+//
+// This function is ONLY intended to be used for testing purposes.
+func NewTest() (Interface, error) {
+ return New(
+ WithAddress("file::memory:?cache=shared"),
+ WithCompressionLevel(3),
+ WithConnectionLife(30*time.Minute),
+ WithConnectionIdle(2),
+ WithConnectionOpen(0),
+ WithDriver("sqlite3"),
+ WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"),
+ WithSkipCreation(false),
+ )
}
diff --git a/database/database_test.go b/database/database_test.go
index 14081a382..a1cfecc8a 100644
--- a/database/database_test.go
+++ b/database/database_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
@@ -7,17 +7,26 @@ package database
import (
"testing"
"time"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/driver/postgres"
+ "gorm.io/driver/sqlite"
+ "gorm.io/gorm"
)
func TestDatabase_New(t *testing.T) {
// setup tests
tests := []struct {
failure bool
- setup *Setup
+ name string
+ config *config
}{
{
+ name: "failure with postgres",
failure: true,
- setup: &Setup{
+ config: &config{
Driver: "postgres",
Address: "postgres://foo:bar@localhost:5432/vela",
CompressionLevel: 3,
@@ -29,8 +38,9 @@ func TestDatabase_New(t *testing.T) {
},
},
{
+ name: "success with sqlite3",
failure: false,
- setup: &Setup{
+ config: &config{
Driver: "sqlite3",
Address: "file::memory:?cache=shared",
CompressionLevel: 3,
@@ -42,10 +52,11 @@ func TestDatabase_New(t *testing.T) {
},
},
{
+ name: "failure with invalid config",
failure: true,
- setup: &Setup{
- Driver: "mysql",
- Address: "foo:bar@tcp(localhost:3306)/vela?charset=utf8mb4&parseTime=True&loc=Local",
+ config: &config{
+ Driver: "postgres",
+ Address: "",
CompressionLevel: 3,
ConnectionLife: 10 * time.Second,
ConnectionIdle: 5,
@@ -55,10 +66,11 @@ func TestDatabase_New(t *testing.T) {
},
},
{
+ name: "failure with invalid driver",
failure: true,
- setup: &Setup{
- Driver: "postgres",
- Address: "",
+ config: &config{
+ Driver: "mysql",
+ Address: "foo:bar@tcp(localhost:3306)/vela?charset=utf8mb4&parseTime=True&loc=Local",
CompressionLevel: 3,
ConnectionLife: 10 * time.Second,
ConnectionIdle: 5,
@@ -71,18 +83,99 @@ func TestDatabase_New(t *testing.T) {
// run tests
for _, test := range tests {
- _, err := New(test.setup)
+ t.Run(test.name, func(t *testing.T) {
+ _, err := New(
+ WithAddress(test.config.Address),
+ WithCompressionLevel(test.config.CompressionLevel),
+ WithConnectionLife(test.config.ConnectionLife),
+ WithConnectionIdle(test.config.ConnectionIdle),
+ WithConnectionOpen(test.config.ConnectionOpen),
+ WithDriver(test.config.Driver),
+ WithEncryptionKey(test.config.EncryptionKey),
+ WithSkipCreation(test.config.SkipCreation),
+ )
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("New for %s should have returned err", test.name)
+ }
- if test.failure {
- if err == nil {
- t.Errorf("New should have returned err")
+ return
}
- continue
- }
+ if err != nil {
+ t.Errorf("New for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
+
+// testPostgres is a helper function to create a Postgres engine for testing.
+func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) {
+ // create the engine with test configuration
+ _engine := &engine{
+ config: &config{
+ CompressionLevel: 3,
+ ConnectionLife: 30 * time.Minute,
+ ConnectionIdle: 2,
+ ConnectionOpen: 0,
+ Driver: "postgres",
+ EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ SkipCreation: false,
+ },
+ logger: logrus.NewEntry(logrus.StandardLogger()),
+ }
+
+ // create the new mock sql database
+ _sql, _mock, err := sqlmock.New(
+ sqlmock.MonitorPingsOption(true),
+ sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual),
+ )
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+ // ensure the mock expects the ping
+ _mock.ExpectPing()
+
+ // create the new mock Postgres database client
+ _engine.client, err = gorm.Open(
+ postgres.New(postgres.Config{Conn: _sql}),
+ &gorm.Config{SkipDefaultTransaction: true},
+ )
+ if err != nil {
+ t.Errorf("unable to create new test postgres database: %v", err)
+ }
+
+ return _engine, _mock
+}
+
+// testSqlite is a helper function to create a Sqlite engine for testing.
+func testSqlite(t *testing.T) *engine {
+ var err error
- if err != nil {
- t.Errorf("New returned err: %v", err)
- }
+ // create the engine with test configuration
+ _engine := &engine{
+ config: &config{
+ Address: "file::memory:?cache=shared",
+ CompressionLevel: 3,
+ ConnectionLife: 30 * time.Minute,
+ ConnectionIdle: 2,
+ ConnectionOpen: 0,
+ Driver: "sqlite3",
+ EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ SkipCreation: false,
+ },
+ logger: logrus.NewEntry(logrus.StandardLogger()),
}
+
+ // create the new mock Sqlite database client
+ _engine.client, err = gorm.Open(
+ sqlite.Open(_engine.config.Address),
+ &gorm.Config{SkipDefaultTransaction: true},
+ )
+ if err != nil {
+ t.Errorf("unable to create new test sqlite database: %v", err)
+ }
+
+ return _engine
}
diff --git a/database/doc.go b/database/doc.go
index 42b459f39..42fe1feae 100644
--- a/database/doc.go
+++ b/database/doc.go
@@ -7,5 +7,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/database"
+// import "github.com/go-vela/server/database"
package database
diff --git a/database/driver.go b/database/driver.go
new file mode 100644
index 000000000..1c3130094
--- /dev/null
+++ b/database/driver.go
@@ -0,0 +1,10 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package database
+
+// Driver outputs the configured database driver.
+func (e *engine) Driver() string {
+ return e.config.Driver
+}
diff --git a/database/driver_test.go b/database/driver_test.go
new file mode 100644
index 000000000..6d382d8cd
--- /dev/null
+++ b/database/driver_test.go
@@ -0,0 +1,47 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package database
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestDatabase_Engine_Driver(t *testing.T) {
+ _postgres, _ := testPostgres(t)
+ defer _postgres.Close()
+
+ _sqlite := testSqlite(t)
+ defer _sqlite.Close()
+
+ // setup tests
+ tests := []struct {
+ name string
+ database *engine
+ want string
+ }{
+ {
+ name: "success with postgres",
+ database: _postgres,
+ want: "postgres",
+ },
+ {
+ name: "success with sqlite3",
+ database: _sqlite,
+ want: "sqlite3",
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got := test.database.Driver()
+
+ if !strings.EqualFold(got, test.want) {
+ t.Errorf("Driver for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/executable/create.go b/database/executable/create.go
new file mode 100644
index 000000000..ac29ec768
--- /dev/null
+++ b/database/executable/create.go
@@ -0,0 +1,57 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package executable
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CreateBuildExecutable creates a new build executable in the database.
+func (e *engine) CreateBuildExecutable(ctx context.Context, b *library.BuildExecutable) error {
+ e.logger.WithFields(logrus.Fields{
+ "build": b.GetBuildID(),
+ }).Tracef("creating build executable for build %d in the database", b.GetBuildID())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#BuildExecutableFromLibrary
+ executable := database.BuildExecutableFromLibrary(b)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#BuildExecutable.Validate
+ err := executable.Validate()
+ if err != nil {
+ return err
+ }
+
+ // compress data for the build executable
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#BuildExecutable.Compress
+ err = executable.Compress(e.config.CompressionLevel)
+ if err != nil {
+ return err
+ }
+
+ // encrypt the data field for the build executable
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#BuildExecutable.Encrypt
+ err = executable.Encrypt(e.config.EncryptionKey)
+ if err != nil {
+ return fmt.Errorf("unable to encrypt build executable for build %d: %w", b.GetBuildID(), err)
+ }
+
+ // send query to the database
+ return e.client.
+ Table(constants.TableBuildExecutable).
+ Create(executable).
+ Error
+}
diff --git a/database/executable/create_test.go b/database/executable/create_test.go
new file mode 100644
index 000000000..e3f664314
--- /dev/null
+++ b/database/executable/create_test.go
@@ -0,0 +1,72 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package executable
+
+import (
+ "context"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestExecutable_Engine_CreateBuildExecutable(t *testing.T) {
+ // setup types
+ _bExecutable := testBuildExecutable()
+ _bExecutable.SetID(1)
+ _bExecutable.SetBuildID(1)
+
+ _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 "build_executables"
+("build_id","data","id")
+VALUES ($1,$2,$3) RETURNING "id"`).
+ WithArgs(1, AnyArgument{}, 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.CreateBuildExecutable(context.TODO(), _bExecutable)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateBuildExecutable for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateBuildExecutable for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/executable/executable.go b/database/executable/executable.go
new file mode 100644
index 000000000..556d86754
--- /dev/null
+++ b/database/executable/executable.go
@@ -0,0 +1,83 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package executable
+
+import (
+ "context"
+ "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 BuildExecutableService interface.
+ config struct {
+ // specifies the level of compression to use for the BuildExecutable engine
+ CompressionLevel int
+ // specifies the encryption key to use for the BuildExecutable engine
+ EncryptionKey string
+ // specifies to skip creating tables and indexes for the BuildExecutable engine
+ SkipCreation bool
+ // specifies the driver for proper popping query
+ Driver string
+ }
+
+ // engine represents the build executable functionality that implements the BuildExecutableService interface.
+ engine struct {
+ // engine configuration settings used in build executable functions
+ config *config
+
+ ctx context.Context
+
+ // gorm.io/gorm database client used in build executable functions
+ //
+ // https://pkg.go.dev/gorm.io/gorm#DB
+ client *gorm.DB
+
+ // sirupsen/logrus logger used in build executable functions
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus#Entry
+ logger *logrus.Entry
+ }
+)
+
+// New creates and returns a Vela service for integrating with build executables in the database.
+//
+//nolint:revive // ignore returning unexported engine
+func New(opts ...EngineOpt) (*engine, error) {
+ // create new BuildExecutable 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 build executable database objects
+ if e.config.SkipCreation {
+ e.logger.Warning("skipping creation of build executables table and indexes in the database")
+
+ return e, nil
+ }
+
+ // create the build executables table
+ err := e.CreateBuildExecutableTable(e.ctx, e.client.Config.Dialector.Name())
+ if err != nil {
+ return nil, fmt.Errorf("unable to create %s table: %w", constants.TableBuildExecutable, err)
+ }
+
+ return e, nil
+}
diff --git a/database/executable/executable_test.go b/database/executable/executable_test.go
new file mode 100644
index 000000000..20ebd6b49
--- /dev/null
+++ b/database/executable/executable_test.go
@@ -0,0 +1,213 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package executable
+
+import (
+ "database/sql/driver"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/driver/postgres"
+ "gorm.io/driver/sqlite"
+ "gorm.io/gorm"
+)
+
+func TestExecutable_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
+ level int
+ key string
+ logger *logrus.Entry
+ skipCreation bool
+ want *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ client: _postgres,
+ level: 1,
+ key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ client: _postgres,
+ config: &config{
+ CompressionLevel: 1,
+ EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ SkipCreation: false,
+ Driver: "postgres",
+ },
+ logger: logger,
+ },
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ client: _sqlite,
+ level: 1,
+ key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ client: _sqlite,
+ config: &config{
+ CompressionLevel: 1,
+ EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ SkipCreation: false,
+ Driver: "sqlite3",
+ },
+ logger: logger,
+ },
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := New(
+ WithClient(test.client),
+ WithCompressionLevel(test.level),
+ WithEncryptionKey(test.key),
+ WithLogger(test.logger),
+ WithSkipCreation(test.skipCreation),
+ WithDriver(test.name),
+ )
+
+ 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),
+ WithCompressionLevel(0),
+ WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"),
+ WithLogger(logrus.NewEntry(logrus.StandardLogger())),
+ WithSkipCreation(false),
+ WithDriver(constants.DriverPostgres),
+ )
+ if err != nil {
+ t.Errorf("unable to create new postgres build_itnerary 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),
+ WithCompressionLevel(0),
+ WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"),
+ WithLogger(logrus.NewEntry(logrus.StandardLogger())),
+ WithSkipCreation(false),
+ WithDriver(constants.DriverSqlite),
+ )
+ if err != nil {
+ t.Errorf("unable to create new sqlite build_itnerary engine: %v", err)
+ }
+
+ return _engine
+}
+
+// testBuildExecutable is a test helper function to create a library
+// BuildExecutable type with all fields set to their zero values.
+func testBuildExecutable() *library.BuildExecutable {
+ return &library.BuildExecutable{
+ ID: new(int64),
+ BuildID: new(int64),
+ Data: new([]byte),
+ }
+}
+
+// This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values
+// that are otherwise not easily compared. These typically would be values generated
+// before adding or updating them in the database.
+//
+// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime
+type AnyArgument struct{}
+
+// Match satisfies sqlmock.Argument interface.
+func (a AnyArgument) Match(v driver.Value) bool {
+ return v != nil
+}
diff --git a/database/executable/interface.go b/database/executable/interface.go
new file mode 100644
index 000000000..9e3bd50b3
--- /dev/null
+++ b/database/executable/interface.go
@@ -0,0 +1,29 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package executable
+
+import (
+ "context"
+
+ "github.com/go-vela/types/library"
+)
+
+// BuildExecutableInterface represents the Vela interface for build executable
+// functions with the supported Database backends.
+type BuildExecutableInterface interface {
+ // BuildExecutable Data Definition Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_definition_language
+ CreateBuildExecutableTable(context.Context, string) error
+
+ // BuildExecutable Data Manipulation Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_manipulation_language
+
+ // CreateBuildExecutable defines a function that creates a build executable.
+ CreateBuildExecutable(context.Context, *library.BuildExecutable) error
+ // PopBuildExecutable defines a function that gets and deletes a build executable.
+ PopBuildExecutable(context.Context, int64) (*library.BuildExecutable, error)
+}
diff --git a/database/executable/opts.go b/database/executable/opts.go
new file mode 100644
index 000000000..fa601a03d
--- /dev/null
+++ b/database/executable/opts.go
@@ -0,0 +1,85 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package executable
+
+import (
+ "context"
+
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+// EngineOpt represents a configuration option to initialize the database engine for build executables.
+type EngineOpt func(*engine) error
+
+// WithClient sets the gorm.io/gorm client in the database engine for build executables.
+func WithClient(client *gorm.DB) EngineOpt {
+ return func(e *engine) error {
+ // set the gorm.io/gorm client in the build executable engine
+ e.client = client
+
+ return nil
+ }
+}
+
+// WithCompressionLevel sets the compression level in the database engine for build executables.
+func WithCompressionLevel(level int) EngineOpt {
+ return func(e *engine) error {
+ // set the compression level in the build executable engine
+ e.config.CompressionLevel = level
+
+ return nil
+ }
+}
+
+// WithEncryptionKey sets the encryption key in the database engine for build executables.
+func WithEncryptionKey(key string) EngineOpt {
+ return func(e *engine) error {
+ // set the encryption key in the build executables engine
+ e.config.EncryptionKey = key
+
+ return nil
+ }
+}
+
+// WithDriver sets the driver type in the database engine for build executables.
+func WithDriver(driver string) EngineOpt {
+ return func(e *engine) error {
+ // set the driver type in the build executable engine
+ e.config.Driver = driver
+
+ return nil
+ }
+}
+
+// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for build executables.
+func WithLogger(logger *logrus.Entry) EngineOpt {
+ return func(e *engine) error {
+ // set the github.com/sirupsen/logrus logger in the build executable engine
+ e.logger = logger
+
+ return nil
+ }
+}
+
+// WithSkipCreation sets the skip creation logic in the database engine for build executables.
+func WithSkipCreation(skipCreation bool) EngineOpt {
+ return func(e *engine) error {
+ // set to skip creating tables and indexes in the build executable engine
+ e.config.SkipCreation = skipCreation
+
+ return nil
+ }
+}
+
+// WithContext sets the context in the database engine for build executables.
+func WithContext(ctx context.Context) EngineOpt {
+ return func(e *engine) error {
+ e.ctx = ctx
+
+ return nil
+ }
+}
diff --git a/database/executable/opts_test.go b/database/executable/opts_test.go
new file mode 100644
index 000000000..61876dd32
--- /dev/null
+++ b/database/executable/opts_test.go
@@ -0,0 +1,315 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package executable
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+func TestExecutable_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 TestExecutable_EngineOpt_WithCompressionLevel(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ level int
+ want int
+ }{
+ {
+ failure: false,
+ name: "compression level set to -1",
+ level: -1,
+ want: -1,
+ },
+ {
+ failure: false,
+ name: "compression level set to 0",
+ level: 0,
+ want: 0,
+ },
+ {
+ failure: false,
+ name: "compression level set to 1",
+ level: 1,
+ want: 1,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithCompressionLevel(test.level)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithCompressionLevel for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithCompressionLevel returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.config.CompressionLevel, test.want) {
+ t.Errorf("WithCompressionLevel is %v, want %v", e.config.CompressionLevel, test.want)
+ }
+ })
+ }
+}
+
+func TestExecutable_EngineOpt_WithEncryptionKey(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ key string
+ want string
+ }{
+ {
+ failure: false,
+ name: "encryption key set",
+ key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ want: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ },
+ {
+ failure: false,
+ name: "encryption key not set",
+ key: "",
+ want: "",
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithEncryptionKey(test.key)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithEncryptionKey for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithEncryptionKey returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.config.EncryptionKey, test.want) {
+ t.Errorf("WithEncryptionKey is %v, want %v", e.config.EncryptionKey, test.want)
+ }
+ })
+ }
+}
+
+func TestExecutable_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 TestExecutable_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)
+ }
+ })
+ }
+}
+
+func TestExecutable_EngineOpt_WithContext(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ ctx context.Context
+ want context.Context
+ }{
+ {
+ failure: false,
+ name: "context set to TODO",
+ ctx: context.TODO(),
+ want: context.TODO(),
+ },
+ {
+ failure: false,
+ name: "context set to nil",
+ ctx: nil,
+ want: nil,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithContext(test.ctx)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithContext for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithContext returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.ctx, test.want) {
+ t.Errorf("WithContext is %v, want %v", e.ctx, test.want)
+ }
+ })
+ }
+}
diff --git a/database/executable/pop.go b/database/executable/pop.go
new file mode 100644
index 000000000..64a2fbcf5
--- /dev/null
+++ b/database/executable/pop.go
@@ -0,0 +1,80 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package executable
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "gorm.io/gorm/clause"
+)
+
+// PopBuildExecutable pops a build executable by build_id from the database.
+func (e *engine) PopBuildExecutable(ctx context.Context, id int64) (*library.BuildExecutable, error) {
+ e.logger.Tracef("popping build executable for build %d from the database", id)
+
+ // variable to store query results
+ b := new(database.BuildExecutable)
+
+ // at the time of coding, GORM does not implement a version of Sqlite3 that supports RETURNING.
+ // so we have to select and delete for the Sqlite driver.
+ switch e.config.Driver {
+ case constants.DriverPostgres:
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableBuildExecutable).
+ Clauses(clause.Returning{}).
+ Where("build_id = ?", id).
+ Delete(b).
+ Error
+
+ if err != nil {
+ return nil, err
+ }
+
+ case constants.DriverSqlite:
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableBuildExecutable).
+ Where("id = ?", id).
+ Take(b).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // send query to the database to delete result just got
+ err = e.client.
+ Table(constants.TableBuildExecutable).
+ Delete(b).
+ Error
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // decrypt the fields for the build executable
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt
+ err := b.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ return nil, err
+ }
+
+ // decompress data for the build executable
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#BuildExecutable.Decompress
+ err = b.Decompress()
+ if err != nil {
+ return nil, err
+ }
+
+ // return the decompressed build executable
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#BuildExecutable.ToLibrary
+ return b.ToLibrary(), nil
+}
diff --git a/database/executable/pop_test.go b/database/executable/pop_test.go
new file mode 100644
index 000000000..cf282d013
--- /dev/null
+++ b/database/executable/pop_test.go
@@ -0,0 +1,85 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package executable
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestExecutable_Engine_PopBuildExecutable(t *testing.T) {
+ // setup types
+ _bExecutable := testBuildExecutable()
+ _bExecutable.SetID(1)
+ _bExecutable.SetBuildID(1)
+ _bExecutable.SetData([]byte("foo"))
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "build_id", "data"}).
+ AddRow(1, 1, "+//18dbf7mF+v7ZPK3Wo5h2TD6v4Zg95sCMUJYO2tpwY37DEgTxW5xdyt3Tey9w=")
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`DELETE FROM "build_executables" WHERE build_id = $1 RETURNING *`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateBuildExecutable(context.TODO(), _bExecutable)
+ if err != nil {
+ t.Errorf("unable to create test build executable for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.BuildExecutable
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _bExecutable,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _bExecutable,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.PopBuildExecutable(context.TODO(), 1)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("PopBuildExecutable for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("PopBuildExecutable for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("PopBuildExecutable for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/executable/table.go b/database/executable/table.go
new file mode 100644
index 000000000..c36834fe6
--- /dev/null
+++ b/database/executable/table.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 executable
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+)
+
+const (
+ // CreatePostgresTable represents a query to create the Postgres build_executables table.
+ CreatePostgresTable = `
+CREATE TABLE
+IF NOT EXISTS
+build_executables (
+ id SERIAL PRIMARY KEY,
+ build_id INTEGER,
+ data BYTEA,
+ UNIQUE(build_id)
+);
+`
+
+ // CreateSqliteTable represents a query to create the Sqlite build_executables table.
+ CreateSqliteTable = `
+CREATE TABLE
+IF NOT EXISTS
+build_executables (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ build_id INTEGER,
+ data BLOB,
+ UNIQUE(build_id)
+);
+`
+)
+
+// CreateBuildExecutableTable creates the build executables table in the database.
+func (e *engine) CreateBuildExecutableTable(ctx context.Context, driver string) error {
+ e.logger.Tracef("creating build_executables table in the database")
+
+ // handle the driver provided to create the table
+ switch driver {
+ case constants.DriverPostgres:
+ // create the build_executables table for Postgres
+ return e.client.Exec(CreatePostgresTable).Error
+ case constants.DriverSqlite:
+ fallthrough
+ default:
+ // create the build_executables table for Sqlite
+ return e.client.Exec(CreateSqliteTable).Error
+ }
+}
diff --git a/database/executable/table_test.go b/database/executable/table_test.go
new file mode 100644
index 000000000..b4f3cd057
--- /dev/null
+++ b/database/executable/table_test.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package executable
+
+import (
+ "context"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestExecutable_Engine_CreateBuildExecutableTable(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.CreateBuildExecutableTable(context.TODO(), test.name)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateBuildExecutableTable for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateBuildExecutableTable for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/flags.go b/database/flags.go
index 75d3c109b..f8d4fd36e 100644
--- a/database/flags.go
+++ b/database/flags.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
@@ -11,13 +11,8 @@ import (
"github.com/urfave/cli/v2"
)
-// Flags represents all supported command line
-// interface (CLI) flags for the database.
-//
-// https://pkg.go.dev/github.com/urfave/cli?tab=doc#Flag
+// Flags represents all supported command line interface (CLI) flags for the database.
var Flags = []cli.Flag{
- // Database Flags
-
&cli.StringFlag{
EnvVars: []string{"VELA_DATABASE_DRIVER", "DATABASE_DRIVER"},
FilePath: "/vela/database/driver",
diff --git a/database/hook/count.go b/database/hook/count.go
new file mode 100644
index 000000000..a1a9689f8
--- /dev/null
+++ b/database/hook/count.go
@@ -0,0 +1,25 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "github.com/go-vela/types/constants"
+)
+
+// CountHooks gets the count of all hooks from the database.
+func (e *engine) CountHooks() (int64, error) {
+ e.logger.Tracef("getting count of all hooks from the database")
+
+ // variable to store query results
+ var h int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableHook).
+ Count(&h).
+ Error
+
+ return h, err
+}
diff --git a/database/sqlite/hook_count.go b/database/hook/count_repo.go
similarity index 62%
rename from database/sqlite/hook_count.go
rename to database/hook/count_repo.go
index 793ec10b8..eb6d6763b 100644
--- a/database/sqlite/hook_count.go
+++ b/database/hook/count_repo.go
@@ -2,18 +2,17 @@
//
// Use of this source code is governed by the LICENSE file in this repository.
-package sqlite
+package hook
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"
)
-// GetRepoHookCount gets the count of webhooks by repo ID from the database.
-func (c *client) GetRepoHookCount(r *library.Repo) (int64, error) {
- c.Logger.WithFields(logrus.Fields{
+// CountHooksForRepo gets the count of hooks by repo ID from the database.
+func (e *engine) CountHooksForRepo(r *library.Repo) (int64, error) {
+ e.logger.WithFields(logrus.Fields{
"org": r.GetOrg(),
"repo": r.GetName(),
}).Tracef("getting count of hooks for repo %s from the database", r.GetFullName())
@@ -22,10 +21,11 @@ func (c *client) GetRepoHookCount(r *library.Repo) (int64, error) {
var h int64
// send query to the database and store result in variable
- err := c.Sqlite.
+ err := e.client.
Table(constants.TableHook).
- Raw(dml.SelectRepoHookCount, r.GetID()).
- Pluck("count", &h).Error
+ Where("repo_id = ?", r.GetID()).
+ Count(&h).
+ Error
return h, err
}
diff --git a/database/hook/count_repo_test.go b/database/hook/count_repo_test.go
new file mode 100644
index 000000000..5814764e6
--- /dev/null
+++ b/database/hook/count_repo_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 hook
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestHook_Engine_CountHooksForRepo(t *testing.T) {
+ // setup types
+ _hookOne := testHook()
+ _hookOne.SetID(1)
+ _hookOne.SetRepoID(1)
+ _hookOne.SetBuildID(1)
+ _hookOne.SetNumber(1)
+ _hookOne.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
+ _hookOne.SetWebhookID(1)
+
+ _hookTwo := testHook()
+ _hookTwo.SetID(2)
+ _hookTwo.SetRepoID(2)
+ _hookTwo.SetBuildID(2)
+ _hookTwo.SetNumber(2)
+ _hookTwo.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
+ _hookTwo.SetWebhookID(1)
+
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/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 "hooks" WHERE repo_id = $1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateHook(_hookOne)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateHook(_hookTwo)
+ if err != nil {
+ t.Errorf("unable to create test hook 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,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.CountHooksForRepo(_repo)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountHooksForRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountHooksForRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountHooksForRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/hook/count_test.go b/database/hook/count_test.go
new file mode 100644
index 000000000..dce97587f
--- /dev/null
+++ b/database/hook/count_test.go
@@ -0,0 +1,97 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestHook_Engine_CountHooks(t *testing.T) {
+ // setup types
+ _hookOne := testHook()
+ _hookOne.SetID(1)
+ _hookOne.SetRepoID(1)
+ _hookOne.SetBuildID(1)
+ _hookOne.SetNumber(1)
+ _hookOne.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
+ _hookOne.SetWebhookID(1)
+
+ _hookTwo := testHook()
+ _hookTwo.SetID(2)
+ _hookTwo.SetRepoID(1)
+ _hookTwo.SetBuildID(2)
+ _hookTwo.SetNumber(2)
+ _hookTwo.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
+ _hookTwo.SetWebhookID(1)
+
+ _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 "hooks"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateHook(_hookOne)
+ if err != nil {
+ t.Errorf("unable to create test hook for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateHook(_hookTwo)
+ if err != nil {
+ t.Errorf("unable to create test hook 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.CountHooks()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountHooks for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountHooks for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountHooks for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/hook/create.go b/database/hook/create.go
new file mode 100644
index 000000000..e2bf5489d
--- /dev/null
+++ b/database/hook/create.go
@@ -0,0 +1,37 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CreateHook creates a new hook in the database.
+func (e *engine) CreateHook(h *library.Hook) (*library.Hook, error) {
+ e.logger.WithFields(logrus.Fields{
+ "hook": h.GetNumber(),
+ }).Tracef("creating hook %d in the database", h.GetNumber())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#HookFromLibrary
+ hook := database.HookFromLibrary(h)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Hook.Validate
+ err := hook.Validate()
+ if err != nil {
+ return nil, err
+ }
+
+ result := e.client.Table(constants.TableHook).Create(hook)
+
+ // send query to the database
+ return hook.ToLibrary(), result.Error
+}
diff --git a/database/hook/create_test.go b/database/hook/create_test.go
new file mode 100644
index 000000000..f05bd8a9d
--- /dev/null
+++ b/database/hook/create_test.go
@@ -0,0 +1,75 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestHook_Engine_CreateHook(t *testing.T) {
+ // setup types
+ _hook := testHook()
+ _hook.SetID(1)
+ _hook.SetRepoID(1)
+ _hook.SetBuildID(1)
+ _hook.SetNumber(1)
+ _hook.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
+ _hook.SetWebhookID(1)
+
+ _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 "hooks"
+("repo_id","build_id","number","source_id","created","host","event","event_action","branch","error","status","link","webhook_id","id")
+VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) RETURNING "id"`).
+ WithArgs(1, 1, 1, "c8da1302-07d6-11ea-882f-4893bca275b8", nil, nil, nil, nil, nil, nil, nil, nil, 1, 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.CreateHook(_hook)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateHook for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateHook for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/hook/delete.go b/database/hook/delete.go
new file mode 100644
index 000000000..d4e688f1c
--- /dev/null
+++ b/database/hook/delete.go
@@ -0,0 +1,30 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// DeleteHook deletes an existing hook from the database.
+func (e *engine) DeleteHook(h *library.Hook) error {
+ e.logger.WithFields(logrus.Fields{
+ "hook": h.GetNumber(),
+ }).Tracef("deleting hook %d in the database", h.GetNumber())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#HookFromLibrary
+ hook := database.HookFromLibrary(h)
+
+ // send query to the database
+ return e.client.
+ Table(constants.TableHook).
+ Delete(hook).
+ Error
+}
diff --git a/database/hook/delete_test.go b/database/hook/delete_test.go
new file mode 100644
index 000000000..2fb91d567
--- /dev/null
+++ b/database/hook/delete_test.go
@@ -0,0 +1,75 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestHook_Engine_DeleteHook(t *testing.T) {
+ // setup types
+ _hook := testHook()
+ _hook.SetID(1)
+ _hook.SetRepoID(1)
+ _hook.SetBuildID(1)
+ _hook.SetNumber(1)
+ _hook.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
+ _hook.SetWebhookID(1)
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`DELETE FROM "hooks" WHERE "hooks"."id" = $1`).
+ WithArgs(1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateHook(_hook)
+ if err != nil {
+ t.Errorf("unable to create test hook 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.DeleteHook(_hook)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("DeleteHook for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("DeleteHook for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/hook/get.go b/database/hook/get.go
new file mode 100644
index 000000000..13547669c
--- /dev/null
+++ b/database/hook/get.go
@@ -0,0 +1,34 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// GetHook gets a hook by ID from the database.
+func (e *engine) GetHook(id int64) (*library.Hook, error) {
+ e.logger.Tracef("getting hook %d from the database", id)
+
+ // variable to store query results
+ h := new(database.Hook)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableHook).
+ Where("id = ?", id).
+ Take(h).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // return the hook
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Hook.ToLibrary
+ return h.ToLibrary(), nil
+}
diff --git a/database/hook/get_repo.go b/database/hook/get_repo.go
new file mode 100644
index 000000000..4c7e3b857
--- /dev/null
+++ b/database/hook/get_repo.go
@@ -0,0 +1,40 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// GetHookForRepo gets a hook by repo ID and number from the database.
+func (e *engine) GetHookForRepo(r *library.Repo, number int) (*library.Hook, error) {
+ e.logger.WithFields(logrus.Fields{
+ "hook": number,
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ }).Tracef("getting hook %s/%d from the database", r.GetFullName(), number)
+
+ // variable to store query results
+ h := new(database.Hook)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableHook).
+ Where("repo_id = ?", r.GetID()).
+ Where("number = ?", number).
+ Take(h).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // return the hook
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Hook.ToLibrary
+ return h.ToLibrary(), nil
+}
diff --git a/database/hook/get_repo_test.go b/database/hook/get_repo_test.go
new file mode 100644
index 000000000..32fb9a813
--- /dev/null
+++ b/database/hook/get_repo_test.go
@@ -0,0 +1,94 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestHook_Engine_GetHookForRepo(t *testing.T) {
+ // setup types
+ _hook := testHook()
+ _hook.SetID(1)
+ _hook.SetRepoID(1)
+ _hook.SetBuildID(1)
+ _hook.SetNumber(1)
+ _hook.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
+ _hook.SetWebhookID(1)
+
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/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", "source_id", "created", "host", "event", "event_action", "branch", "error", "status", "link", "webhook_id"}).
+ AddRow(1, 1, 1, 1, "c8da1302-07d6-11ea-882f-4893bca275b8", 0, "", "", "", "", "", "", "", 1)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "hooks" WHERE repo_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.CreateHook(_hook)
+ if err != nil {
+ t.Errorf("unable to create test hook for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Hook
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _hook,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _hook,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetHookForRepo(_repo, 1)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetHookForRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetHookForRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetHookForRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/hook/get_test.go b/database/hook/get_test.go
new file mode 100644
index 000000000..b84fe7b13
--- /dev/null
+++ b/database/hook/get_test.go
@@ -0,0 +1,87 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestHook_Engine_GetHook(t *testing.T) {
+ // setup types
+ _hook := testHook()
+ _hook.SetID(1)
+ _hook.SetRepoID(1)
+ _hook.SetBuildID(1)
+ _hook.SetNumber(1)
+ _hook.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
+ _hook.SetWebhookID(1)
+
+ _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", "source_id", "created", "host", "event", "event_action", "branch", "error", "status", "link", "webhook_id"},
+ ).AddRow(1, 1, 1, 1, "c8da1302-07d6-11ea-882f-4893bca275b8", 0, "", "", "", "", "", "", "", 1)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "hooks" WHERE id = $1 LIMIT 1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateHook(_hook)
+ if err != nil {
+ t.Errorf("unable to create test hook for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Hook
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _hook,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _hook,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetHook(1)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetHook for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetHook for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetHook for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/hook/hook.go b/database/hook/hook.go
new file mode 100644
index 000000000..5225f901a
--- /dev/null
+++ b/database/hook/hook.go
@@ -0,0 +1,80 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+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 HookInterface interface.
+ config struct {
+ // specifies to skip creating tables and indexes for the Hook engine
+ SkipCreation bool
+ }
+
+ // engine represents the hook functionality that implements the HookInterface interface.
+ engine struct {
+ // engine configuration settings used in hook functions
+ config *config
+
+ // gorm.io/gorm database client used in hook functions
+ //
+ // https://pkg.go.dev/gorm.io/gorm#DB
+ client *gorm.DB
+
+ // sirupsen/logrus logger used in hook functions
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus#Entry
+ logger *logrus.Entry
+ }
+)
+
+// New creates and returns a Vela service for integrating with hooks in the database.
+//
+//nolint:revive // ignore returning unexported engine
+func New(opts ...EngineOpt) (*engine, error) {
+ // create new Hook 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 hook database objects
+ if e.config.SkipCreation {
+ e.logger.Warning("skipping creation of hooks table and indexes in the database")
+
+ return e, nil
+ }
+
+ // create the hooks table
+ err := e.CreateHookTable(e.client.Config.Dialector.Name())
+ if err != nil {
+ return nil, fmt.Errorf("unable to create %s table: %w", constants.TableHook, err)
+ }
+
+ // create the indexes for the hooks table
+ err = e.CreateHookIndexes()
+ if err != nil {
+ return nil, fmt.Errorf("unable to create indexes for %s table: %w", constants.TableHook, err)
+ }
+
+ return e, nil
+}
diff --git a/database/hook/hook_test.go b/database/hook/hook_test.go
new file mode 100644
index 000000000..e8d3130cc
--- /dev/null
+++ b/database/hook/hook_test.go
@@ -0,0 +1,218 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+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 TestHook_New(t *testing.T) {
+ // setup types
+ logger := logrus.NewEntry(logrus.StandardLogger())
+
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+ defer _sql.Close()
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _config := &gorm.Config{SkipDefaultTransaction: true}
+
+ _postgres, err := gorm.Open(postgres.New(postgres.Config{Conn: _sql}), _config)
+ if err != nil {
+ t.Errorf("unable to create new postgres database: %v", err)
+ }
+
+ _sqlite, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), _config)
+ if err != nil {
+ t.Errorf("unable to create new sqlite database: %v", err)
+ }
+
+ defer func() { _sql, _ := _sqlite.DB(); _sql.Close() }()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ client *gorm.DB
+ key string
+ logger *logrus.Entry
+ skipCreation bool
+ want *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ client: _postgres,
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ client: _postgres,
+ config: &config{SkipCreation: false},
+ logger: logger,
+ },
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ client: _sqlite,
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ client: _sqlite,
+ config: &config{SkipCreation: false},
+ logger: logger,
+ },
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := New(
+ WithClient(test.client),
+ WithLogger(test.logger),
+ WithSkipCreation(test.skipCreation),
+ )
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("New for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("New for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("New for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
+
+// testPostgres is a helper function to create a Postgres engine for testing.
+func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) {
+ // create the new mock sql database
+ //
+ // https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#New
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateRepoIDIndex).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 hook 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 hook engine: %v", err)
+ }
+
+ return _engine
+}
+
+// testHook is a test helper function to create a library
+// Hook type with all fields set to their zero values.
+func testHook() *library.Hook {
+ return &library.Hook{
+ ID: new(int64),
+ RepoID: new(int64),
+ BuildID: new(int64),
+ Number: new(int),
+ SourceID: new(string),
+ Created: new(int64),
+ Host: new(string),
+ Event: new(string),
+ EventAction: new(string),
+ Branch: new(string),
+ Error: new(string),
+ Status: new(string),
+ Link: new(string),
+ WebhookID: new(int64),
+ }
+}
+
+// testRepo is a test helper function to create a library
+// Repo type with all fields set to their zero values.
+func testRepo() *library.Repo {
+ return &library.Repo{
+ ID: new(int64),
+ UserID: new(int64),
+ BuildLimit: new(int64),
+ Timeout: new(int64),
+ Counter: new(int),
+ PipelineType: new(string),
+ Hash: new(string),
+ Org: new(string),
+ Name: new(string),
+ FullName: new(string),
+ Link: new(string),
+ Clone: new(string),
+ Branch: new(string),
+ Visibility: new(string),
+ PreviousName: new(string),
+ Private: new(bool),
+ Trusted: new(bool),
+ Active: new(bool),
+ AllowPull: new(bool),
+ AllowPush: new(bool),
+ AllowDeploy: new(bool),
+ AllowTag: new(bool),
+ AllowComment: new(bool),
+ }
+}
diff --git a/database/hook/index.go b/database/hook/index.go
new file mode 100644
index 000000000..a2061eaf9
--- /dev/null
+++ b/database/hook/index.go
@@ -0,0 +1,24 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+const (
+ // CreateRepoIDIndex represents a query to create an
+ // index on the hooks table for the repo_id column.
+ CreateRepoIDIndex = `
+CREATE INDEX
+IF NOT EXISTS
+hooks_repo_id
+ON hooks (repo_id);
+`
+)
+
+// CreateHookIndexes creates the indexes for the hooks table in the database.
+func (e *engine) CreateHookIndexes() error {
+ e.logger.Tracef("creating indexes for hooks table in the database")
+
+ // create the repo_id column index for the hooks table
+ return e.client.Exec(CreateRepoIDIndex).Error
+}
diff --git a/database/hook/index_test.go b/database/hook/index_test.go
new file mode 100644
index 000000000..06ab40a95
--- /dev/null
+++ b/database/hook/index_test.go
@@ -0,0 +1,59 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestHook_Engine_CreateHookIndexes(t *testing.T) {
+ // setup types
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ _mock.ExpectExec(CreateRepoIDIndex).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.CreateHookIndexes()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateHookIndexes for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateHookIndexes for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/hook/interface.go b/database/hook/interface.go
new file mode 100644
index 000000000..4ed44fd84
--- /dev/null
+++ b/database/hook/interface.go
@@ -0,0 +1,49 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "github.com/go-vela/types/library"
+)
+
+// HookInterface represents the Vela interface for hook
+// functions with the supported Database backends.
+//
+//nolint:revive // ignore name stutter
+type HookInterface interface {
+ // Hook Data Definition Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_definition_language
+
+ // CreateHookIndexes defines a function that creates the indexes for the hooks table.
+ CreateHookIndexes() error
+ // CreateHookTable defines a function that creates the hooks table.
+ CreateHookTable(string) error
+
+ // Hook Data Manipulation Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_manipulation_language
+
+ // CountHooks defines a function that gets the count of all hooks.
+ CountHooks() (int64, error)
+ // CountHooksForRepo defines a function that gets the count of hooks by repo ID.
+ CountHooksForRepo(*library.Repo) (int64, error)
+ // CreateHook defines a function that creates a new hook.
+ CreateHook(*library.Hook) (*library.Hook, error)
+ // DeleteHook defines a function that deletes an existing hook.
+ DeleteHook(*library.Hook) error
+ // GetHook defines a function that gets a hook by ID.
+ GetHook(int64) (*library.Hook, error)
+ // GetHookForRepo defines a function that gets a hook by repo ID and number.
+ GetHookForRepo(*library.Repo, int) (*library.Hook, error)
+ // LastHookForRepo defines a function that gets the last hook by repo ID.
+ LastHookForRepo(*library.Repo) (*library.Hook, error)
+ // ListHooks defines a function that gets a list of all hooks.
+ ListHooks() ([]*library.Hook, error)
+ // ListHooksForRepo defines a function that gets a list of hooks by repo ID.
+ ListHooksForRepo(*library.Repo, int, int) ([]*library.Hook, int64, error)
+ // UpdateHook defines a function that updates an existing hook.
+ UpdateHook(*library.Hook) (*library.Hook, error)
+}
diff --git a/database/hook/last_repo.go b/database/hook/last_repo.go
new file mode 100644
index 000000000..22c3ef992
--- /dev/null
+++ b/database/hook/last_repo.go
@@ -0,0 +1,49 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "errors"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+// LastHookForRepo gets the last hook by repo ID from the database.
+func (e *engine) LastHookForRepo(r *library.Repo) (*library.Hook, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ }).Tracef("getting last hook for repo %s from the database", r.GetFullName())
+
+ // variable to store query results
+ h := new(database.Hook)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableHook).
+ Where("repo_id = ?", r.GetID()).
+ Order("number DESC").
+ Take(h).
+ Error
+ if err != nil {
+ // check if the query returned a record not found error
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ // the record will not exist if it is a new repo
+ return nil, nil
+ }
+
+ return nil, err
+ }
+
+ // return the hook
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Hook.ToLibrary
+ return h.ToLibrary(), nil
+}
diff --git a/database/hook/last_repo_test.go b/database/hook/last_repo_test.go
new file mode 100644
index 000000000..ecd4a3eea
--- /dev/null
+++ b/database/hook/last_repo_test.go
@@ -0,0 +1,94 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestHook_Engine_LastHookForRepo(t *testing.T) {
+ // setup types
+ _hook := testHook()
+ _hook.SetID(1)
+ _hook.SetRepoID(1)
+ _hook.SetBuildID(1)
+ _hook.SetNumber(1)
+ _hook.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
+ _hook.SetWebhookID(1)
+
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/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", "source_id", "created", "host", "event", "event_action", "branch", "error", "status", "link", "webhook_id"}).
+ AddRow(1, 1, 1, 1, "c8da1302-07d6-11ea-882f-4893bca275b8", 0, "", "", "", "", "", "", "", 1)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "hooks" WHERE repo_id = $1 ORDER BY number DESC LIMIT 1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateHook(_hook)
+ if err != nil {
+ t.Errorf("unable to create test hook for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Hook
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _hook,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _hook,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.LastHookForRepo(_repo)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("LastHookForRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("LastHookForRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("LastHookForRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/hook/list.go b/database/hook/list.go
new file mode 100644
index 000000000..1152738e6
--- /dev/null
+++ b/database/hook/list.go
@@ -0,0 +1,54 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// ListHooks gets a list of all hooks from the database.
+func (e *engine) ListHooks() ([]*library.Hook, error) {
+ e.logger.Trace("listing all hooks from the database")
+
+ // variables to store query results and return value
+ count := int64(0)
+ h := new([]database.Hook)
+ hooks := []*library.Hook{}
+
+ // count the results
+ count, err := e.CountHooks()
+ if err != nil {
+ return nil, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return hooks, nil
+ }
+
+ // send query to the database and store result in variable
+ err = e.client.
+ Table(constants.TableHook).
+ Find(&h).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // iterate through all query results
+ for _, hook := range *h {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := hook
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Hook.ToLibrary
+ hooks = append(hooks, tmp.ToLibrary())
+ }
+
+ return hooks, nil
+}
diff --git a/database/hook/list_repo.go b/database/hook/list_repo.go
new file mode 100644
index 000000000..1060f5863
--- /dev/null
+++ b/database/hook/list_repo.go
@@ -0,0 +1,65 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// ListHooksForRepo gets a list of hooks by repo ID from the database.
+func (e *engine) ListHooksForRepo(r *library.Repo, page, perPage int) ([]*library.Hook, int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ }).Tracef("listing hooks for repo %s from the database", r.GetFullName())
+
+ // variables to store query results and return value
+ count := int64(0)
+ h := new([]database.Hook)
+ hooks := []*library.Hook{}
+
+ // count the results
+ count, err := e.CountHooksForRepo(r)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return hooks, 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.TableHook).
+ Where("repo_id = ?", r.GetID()).
+ Order("id DESC").
+ Limit(perPage).
+ Offset(offset).
+ Find(&h).
+ Error
+ if err != nil {
+ return nil, count, err
+ }
+
+ // iterate through all query results
+ for _, hook := range *h {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := hook
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Hook.ToLibrary
+ hooks = append(hooks, tmp.ToLibrary())
+ }
+
+ return hooks, count, nil
+}
diff --git a/database/hook/list_repo_test.go b/database/hook/list_repo_test.go
new file mode 100644
index 000000000..c229f5674
--- /dev/null
+++ b/database/hook/list_repo_test.go
@@ -0,0 +1,114 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestHook_Engine_ListHooksForRepo(t *testing.T) {
+ // setup types
+ _hookOne := testHook()
+ _hookOne.SetID(1)
+ _hookOne.SetRepoID(1)
+ _hookOne.SetBuildID(1)
+ _hookOne.SetNumber(1)
+ _hookOne.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
+ _hookOne.SetWebhookID(1)
+
+ _hookTwo := testHook()
+ _hookTwo.SetID(2)
+ _hookTwo.SetRepoID(1)
+ _hookTwo.SetBuildID(2)
+ _hookTwo.SetNumber(2)
+ _hookTwo.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
+ _hookTwo.SetWebhookID(1)
+
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/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 "hooks" WHERE repo_id = $1`).WithArgs(1).WillReturnRows(_rows)
+
+ // create expected result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "repo_id", "build_id", "number", "source_id", "created", "host", "event", "event_action", "branch", "error", "status", "link", "webhook_id"}).
+ AddRow(2, 1, 2, 2, "c8da1302-07d6-11ea-882f-4893bca275b8", 0, "", "", "", "", "", "", "", 1).
+ AddRow(1, 1, 1, 1, "c8da1302-07d6-11ea-882f-4893bca275b8", 0, "", "", "", "", "", "", "", 1)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "hooks" WHERE repo_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.CreateHook(_hookOne)
+ if err != nil {
+ t.Errorf("unable to create test hook for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateHook(_hookTwo)
+ if err != nil {
+ t.Errorf("unable to create test hook for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Hook
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Hook{_hookTwo, _hookOne},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.Hook{_hookTwo, _hookOne},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, _, err := test.database.ListHooksForRepo(_repo, 1, 10)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListHooksForRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListHooksForRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListHooksForRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/hook/list_test.go b/database/hook/list_test.go
new file mode 100644
index 000000000..e2fb33772
--- /dev/null
+++ b/database/hook/list_test.go
@@ -0,0 +1,107 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestHook_Engine_ListHooks(t *testing.T) {
+ // setup types
+ _hookOne := testHook()
+ _hookOne.SetID(1)
+ _hookOne.SetRepoID(1)
+ _hookOne.SetBuildID(1)
+ _hookOne.SetNumber(1)
+ _hookOne.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
+ _hookOne.SetWebhookID(1)
+
+ _hookTwo := testHook()
+ _hookTwo.SetID(2)
+ _hookTwo.SetRepoID(1)
+ _hookTwo.SetBuildID(2)
+ _hookTwo.SetNumber(2)
+ _hookTwo.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
+ _hookTwo.SetWebhookID(1)
+
+ _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 "hooks"`).WillReturnRows(_rows)
+
+ // create expected result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "repo_id", "build_id", "number", "source_id", "created", "host", "event", "event_action", "branch", "error", "status", "link", "webhook_id"}).
+ AddRow(1, 1, 1, 1, "c8da1302-07d6-11ea-882f-4893bca275b8", 0, "", "", "", "", "", "", "", 1).
+ AddRow(2, 1, 2, 2, "c8da1302-07d6-11ea-882f-4893bca275b8", 0, "", "", "", "", "", "", "", 1)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "hooks"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateHook(_hookOne)
+ if err != nil {
+ t.Errorf("unable to create test hook for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateHook(_hookTwo)
+ if err != nil {
+ t.Errorf("unable to create test hook for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Hook
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Hook{_hookOne, _hookTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.Hook{_hookOne, _hookTwo},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.ListHooks()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListHooks for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListHooks for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListHooks for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/hook/opts.go b/database/hook/opts.go
new file mode 100644
index 000000000..e88e92da7
--- /dev/null
+++ b/database/hook/opts.go
@@ -0,0 +1,44 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+// EngineOpt represents a configuration option to initialize the database engine for Hooks.
+type EngineOpt func(*engine) error
+
+// WithClient sets the gorm.io/gorm client in the database engine for Hooks.
+func WithClient(client *gorm.DB) EngineOpt {
+ return func(e *engine) error {
+ // set the gorm.io/gorm client in the hook engine
+ e.client = client
+
+ return nil
+ }
+}
+
+// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for Hooks.
+func WithLogger(logger *logrus.Entry) EngineOpt {
+ return func(e *engine) error {
+ // set the github.com/sirupsen/logrus logger in the hook engine
+ e.logger = logger
+
+ return nil
+ }
+}
+
+// WithSkipCreation sets the skip creation logic in the database engine for Hooks.
+func WithSkipCreation(skipCreation bool) EngineOpt {
+ return func(e *engine) error {
+ // set to skip creating tables and indexes in the hook engine
+ e.config.SkipCreation = skipCreation
+
+ return nil
+ }
+}
diff --git a/database/hook/opts_test.go b/database/hook/opts_test.go
new file mode 100644
index 000000000..81946c8f4
--- /dev/null
+++ b/database/hook/opts_test.go
@@ -0,0 +1,161 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+func TestHook_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 TestHook_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 TestHook_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/hook/table.go b/database/hook/table.go
new file mode 100644
index 000000000..90419508f
--- /dev/null
+++ b/database/hook/table.go
@@ -0,0 +1,74 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "github.com/go-vela/types/constants"
+)
+
+const (
+ // CreatePostgresTable represents a query to create the Postgres hooks table.
+ CreatePostgresTable = `
+CREATE TABLE
+IF NOT EXISTS
+hooks (
+ id SERIAL PRIMARY KEY,
+ repo_id INTEGER,
+ build_id INTEGER,
+ number INTEGER,
+ source_id VARCHAR(250),
+ created INTEGER,
+ host VARCHAR(250),
+ event VARCHAR(250),
+ event_action VARCHAR(250),
+ branch VARCHAR(500),
+ error VARCHAR(500),
+ status VARCHAR(250),
+ link VARCHAR(1000),
+ webhook_id INTEGER,
+ UNIQUE(repo_id, number)
+);
+`
+
+ // CreateSqliteTable represents a query to create the Sqlite hooks table.
+ CreateSqliteTable = `
+CREATE TABLE
+IF NOT EXISTS
+hooks (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ repo_id INTEGER,
+ build_id INTEGER,
+ number INTEGER,
+ source_id TEXT,
+ created INTEGER,
+ host TEXT,
+ event TEXT,
+ event_action TEXT,
+ branch TEXT,
+ error TEXT,
+ status TEXT,
+ link TEXT,
+ webhook_id INTEGER,
+ UNIQUE(repo_id, number)
+);
+`
+)
+
+// CreateHookTable creates the hooks table in the database.
+func (e *engine) CreateHookTable(driver string) error {
+ e.logger.Tracef("creating hooks table in the database")
+
+ // handle the driver provided to create the table
+ switch driver {
+ case constants.DriverPostgres:
+ // create the hooks table for Postgres
+ return e.client.Exec(CreatePostgresTable).Error
+ case constants.DriverSqlite:
+ fallthrough
+ default:
+ // create the hooks table for Sqlite
+ return e.client.Exec(CreateSqliteTable).Error
+ }
+}
diff --git a/database/hook/table_test.go b/database/hook/table_test.go
new file mode 100644
index 000000000..915621718
--- /dev/null
+++ b/database/hook/table_test.go
@@ -0,0 +1,59 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestHook_Engine_CreateHookTable(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.CreateHookTable(test.name)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateHookTable for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateHookTable for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/hook/update.go b/database/hook/update.go
new file mode 100644
index 000000000..d7741f249
--- /dev/null
+++ b/database/hook/update.go
@@ -0,0 +1,37 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// UpdateHook updates an existing hook in the database.
+func (e *engine) UpdateHook(h *library.Hook) (*library.Hook, error) {
+ e.logger.WithFields(logrus.Fields{
+ "hook": h.GetNumber(),
+ }).Tracef("updating hook %d in the database", h.GetNumber())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#HookFromLibrary
+ hook := database.HookFromLibrary(h)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Hook.Validate
+ err := hook.Validate()
+ if err != nil {
+ return nil, err
+ }
+
+ result := e.client.Table(constants.TableHook).Save(hook)
+
+ // send query to the database
+ return hook.ToLibrary(), result.Error
+}
diff --git a/database/hook/update_test.go b/database/hook/update_test.go
new file mode 100644
index 000000000..cbb309897
--- /dev/null
+++ b/database/hook/update_test.go
@@ -0,0 +1,77 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package hook
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestHook_Engine_UpdateHook(t *testing.T) {
+ // setup types
+ _hook := testHook()
+ _hook.SetID(1)
+ _hook.SetRepoID(1)
+ _hook.SetBuildID(1)
+ _hook.SetNumber(1)
+ _hook.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
+ _hook.SetWebhookID(1)
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`UPDATE "hooks"
+SET "repo_id"=$1,"build_id"=$2,"number"=$3,"source_id"=$4,"created"=$5,"host"=$6,"event"=$7,"event_action"=$8,"branch"=$9,"error"=$10,"status"=$11,"link"=$12,"webhook_id"=$13
+WHERE "id" = $14`).
+ WithArgs(1, 1, 1, "c8da1302-07d6-11ea-882f-4893bca275b8", nil, nil, nil, nil, nil, nil, nil, nil, 1, 1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateHook(_hook)
+ if err != nil {
+ t.Errorf("unable to create test hook 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.UpdateHook(_hook)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("UpdateHook for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("UpdateHook for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/integration_test.go b/database/integration_test.go
new file mode 100644
index 000000000..30d6ff696
--- /dev/null
+++ b/database/integration_test.go
@@ -0,0 +1,2302 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package database
+
+import (
+ "context"
+ "os"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/go-vela/server/database/build"
+ "github.com/go-vela/server/database/executable"
+ "github.com/go-vela/server/database/hook"
+ "github.com/go-vela/server/database/log"
+ "github.com/go-vela/server/database/pipeline"
+ "github.com/go-vela/server/database/repo"
+ "github.com/go-vela/server/database/schedule"
+ "github.com/go-vela/server/database/secret"
+ "github.com/go-vela/server/database/service"
+ "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"
+ "github.com/go-vela/types/library"
+ "github.com/go-vela/types/raw"
+)
+
+// Resources represents the object containing test resources.
+type Resources struct {
+ Builds []*library.Build
+ Deployments []*library.Deployment
+ Executables []*library.BuildExecutable
+ Hooks []*library.Hook
+ Logs []*library.Log
+ Pipelines []*library.Pipeline
+ Repos []*library.Repo
+ Schedules []*library.Schedule
+ Secrets []*library.Secret
+ Services []*library.Service
+ Steps []*library.Step
+ Users []*library.User
+ Workers []*library.Worker
+}
+
+func TestDatabase_Integration(t *testing.T) {
+ // check if we should skip the integration test
+ //
+ // https://konradreiche.com/blog/how-to-separate-integration-tests-in-go
+ if os.Getenv("INTEGRATION") == "" {
+ t.Skipf("skipping %s integration test due to environment variable constraint", t.Name())
+ }
+
+ // setup tests
+ tests := []struct {
+ name string
+ config *config
+ }{
+ {
+ name: "postgres",
+ config: &config{
+ Driver: "postgres",
+ Address: os.Getenv("POSTGRES_ADDR"),
+ CompressionLevel: 3,
+ ConnectionLife: 10 * time.Second,
+ ConnectionIdle: 5,
+ ConnectionOpen: 20,
+ EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ SkipCreation: false,
+ },
+ },
+ {
+ name: "sqlite3",
+ config: &config{
+ Driver: "sqlite3",
+ Address: os.Getenv("SQLITE_ADDR"),
+ CompressionLevel: 3,
+ ConnectionLife: 10 * time.Second,
+ ConnectionIdle: 5,
+ ConnectionOpen: 20,
+ EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ SkipCreation: false,
+ },
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ // create resources for testing
+ resources := newResources()
+
+ db, err := New(
+ WithAddress(test.config.Address),
+ WithCompressionLevel(test.config.CompressionLevel),
+ WithConnectionLife(test.config.ConnectionLife),
+ WithConnectionIdle(test.config.ConnectionIdle),
+ WithConnectionOpen(test.config.ConnectionOpen),
+ WithDriver(test.config.Driver),
+ WithEncryptionKey(test.config.EncryptionKey),
+ WithSkipCreation(test.config.SkipCreation),
+ )
+ if err != nil {
+ t.Errorf("unable to create new database engine for %s: %v", test.name, err)
+ }
+
+ driver := db.Driver()
+ if !strings.EqualFold(driver, test.config.Driver) {
+ t.Errorf("Driver() is %v, want %v", driver, test.config.Driver)
+ }
+
+ err = db.Ping()
+ if err != nil {
+ t.Errorf("unable to ping database engine for %s: %v", test.name, err)
+ }
+
+ t.Run("test_builds", func(t *testing.T) { testBuilds(t, db, resources) })
+
+ t.Run("test_executables", func(t *testing.T) { testExecutables(t, db, resources) })
+
+ t.Run("test_hooks", func(t *testing.T) { testHooks(t, db, resources) })
+
+ t.Run("test_logs", func(t *testing.T) { testLogs(t, db, resources) })
+
+ t.Run("test_pipelines", func(t *testing.T) { testPipelines(t, db, resources) })
+
+ t.Run("test_repos", func(t *testing.T) { testRepos(t, db, resources) })
+
+ t.Run("test_schedules", func(t *testing.T) { testSchedules(t, db, resources) })
+
+ t.Run("test_secrets", func(t *testing.T) { testSecrets(t, db, resources) })
+
+ t.Run("test_services", func(t *testing.T) { testServices(t, db, resources) })
+
+ t.Run("test_steps", func(t *testing.T) { testSteps(t, db, resources) })
+
+ t.Run("test_users", func(t *testing.T) { testUsers(t, db, resources) })
+
+ t.Run("test_workers", func(t *testing.T) { testWorkers(t, db, resources) })
+
+ err = db.Close()
+ if err != nil {
+ t.Errorf("unable to close database engine for %s: %v", test.name, err)
+ }
+ })
+ }
+}
+
+func testBuilds(t *testing.T, db Interface, resources *Resources) {
+ // create a variable to track the number of methods called for builds
+ methods := make(map[string]bool)
+ // capture the element type of the build interface
+ element := reflect.TypeOf(new(build.BuildInterface)).Elem()
+ // iterate through all methods found in the build interface
+ for i := 0; i < element.NumMethod(); i++ {
+ // skip tracking the methods to create indexes and tables for builds
+ // since those are already called when the database engine starts
+ if strings.Contains(element.Method(i).Name, "Index") ||
+ strings.Contains(element.Method(i).Name, "Table") {
+ continue
+ }
+
+ // add the method name to the list of functions
+ methods[element.Method(i).Name] = false
+ }
+
+ // create the repos for build related functions
+ for _, repo := range resources.Repos {
+ _, err := db.CreateRepo(context.TODO(), repo)
+ if err != nil {
+ t.Errorf("unable to create repo %d: %v", repo.GetID(), err)
+ }
+ }
+
+ buildOne := new(library.BuildQueue)
+ buildOne.SetCreated(1563474076)
+ buildOne.SetFullName("github/octocat")
+ buildOne.SetNumber(1)
+ buildOne.SetStatus("running")
+
+ buildTwo := new(library.BuildQueue)
+ buildTwo.SetCreated(1563474076)
+ buildTwo.SetFullName("github/octocat")
+ buildTwo.SetNumber(2)
+ buildTwo.SetStatus("running")
+
+ queueBuilds := []*library.BuildQueue{buildOne, buildTwo}
+
+ // create the builds
+ for _, build := range resources.Builds {
+ _, err := db.CreateBuild(context.TODO(), build)
+ if err != nil {
+ t.Errorf("unable to create build %d: %v", build.GetID(), err)
+ }
+ }
+ methods["CreateBuild"] = true
+
+ // count the builds
+ count, err := db.CountBuilds(context.TODO())
+ if err != nil {
+ t.Errorf("unable to count builds: %v", err)
+ }
+ if int(count) != len(resources.Builds) {
+ t.Errorf("CountBuilds() is %v, want %v", count, len(resources.Builds))
+ }
+ methods["CountBuilds"] = true
+
+ // count the builds for a deployment
+ count, err = db.CountBuildsForDeployment(context.TODO(), resources.Deployments[0], nil)
+ if err != nil {
+ t.Errorf("unable to count builds for deployment %d: %v", resources.Deployments[0].GetID(), err)
+ }
+ if int(count) != len(resources.Builds) {
+ t.Errorf("CountBuildsForDeployment() is %v, want %v", count, len(resources.Builds))
+ }
+ methods["CountBuildsForDeployment"] = true
+
+ // count the builds for an org
+ count, err = db.CountBuildsForOrg(context.TODO(), resources.Repos[0].GetOrg(), nil)
+ if err != nil {
+ t.Errorf("unable to count builds for org %s: %v", resources.Repos[0].GetOrg(), err)
+ }
+ if int(count) != len(resources.Builds) {
+ t.Errorf("CountBuildsForOrg() is %v, want %v", count, len(resources.Builds))
+ }
+ methods["CountBuildsForOrg"] = true
+
+ // count the builds for a repo
+ count, err = db.CountBuildsForRepo(context.TODO(), resources.Repos[0], nil)
+ if err != nil {
+ t.Errorf("unable to count builds for repo %d: %v", resources.Repos[0].GetID(), err)
+ }
+ if int(count) != len(resources.Builds) {
+ t.Errorf("CountBuildsForRepo() is %v, want %v", count, len(resources.Builds))
+ }
+ methods["CountBuildsForRepo"] = true
+
+ // count the builds for a status
+ count, err = db.CountBuildsForStatus(context.TODO(), "running", nil)
+ if err != nil {
+ t.Errorf("unable to count builds for status %s: %v", "running", err)
+ }
+ if int(count) != len(resources.Builds) {
+ t.Errorf("CountBuildsForStatus() is %v, want %v", count, len(resources.Builds))
+ }
+ methods["CountBuildsForStatus"] = true
+
+ // list the builds
+ list, err := db.ListBuilds(context.TODO())
+ if err != nil {
+ t.Errorf("unable to list builds: %v", err)
+ }
+ if !reflect.DeepEqual(list, resources.Builds) {
+ t.Errorf("ListBuilds() is %v, want %v", list, resources.Builds)
+ }
+ methods["ListBuilds"] = true
+
+ // list the builds for a deployment
+ list, count, err = db.ListBuildsForDeployment(context.TODO(), resources.Deployments[0], nil, 1, 10)
+ if err != nil {
+ t.Errorf("unable to list builds for deployment %d: %v", resources.Deployments[0].GetID(), err)
+ }
+ if int(count) != len(resources.Builds) {
+ t.Errorf("ListBuildsForDeployment() is %v, want %v", count, len(resources.Builds))
+ }
+ if !reflect.DeepEqual(list, []*library.Build{resources.Builds[1], resources.Builds[0]}) {
+ t.Errorf("ListBuildsForDeployment() is %v, want %v", list, []*library.Build{resources.Builds[1], resources.Builds[0]})
+ }
+ methods["ListBuildsForDeployment"] = true
+
+ // list the builds for an org
+ list, count, err = db.ListBuildsForOrg(context.TODO(), resources.Repos[0].GetOrg(), nil, 1, 10)
+ if err != nil {
+ t.Errorf("unable to list builds for org %s: %v", resources.Repos[0].GetOrg(), err)
+ }
+ if int(count) != len(resources.Builds) {
+ t.Errorf("ListBuildsForOrg() is %v, want %v", count, len(resources.Builds))
+ }
+ if !reflect.DeepEqual(list, resources.Builds) {
+ t.Errorf("ListBuildsForOrg() is %v, want %v", list, resources.Builds)
+ }
+ methods["ListBuildsForOrg"] = true
+
+ // list the builds for a repo
+ list, count, err = db.ListBuildsForRepo(context.TODO(), resources.Repos[0], nil, time.Now().UTC().Unix(), 0, 1, 10)
+ if err != nil {
+ t.Errorf("unable to list builds for repo %d: %v", resources.Repos[0].GetID(), err)
+ }
+ if int(count) != len(resources.Builds) {
+ t.Errorf("ListBuildsForRepo() is %v, want %v", count, len(resources.Builds))
+ }
+ if !reflect.DeepEqual(list, []*library.Build{resources.Builds[1], resources.Builds[0]}) {
+ t.Errorf("ListBuildsForRepo() is %v, want %v", list, []*library.Build{resources.Builds[1], resources.Builds[0]})
+ }
+ methods["ListBuildsForRepo"] = true
+
+ // list the pending and running builds
+ queueList, err := db.ListPendingAndRunningBuilds(context.TODO(), "0")
+ if err != nil {
+ t.Errorf("unable to list pending and running builds: %v", err)
+ }
+ if !reflect.DeepEqual(queueList, queueBuilds) {
+ t.Errorf("ListPendingAndRunningBuilds() is %v, want %v", queueList, queueBuilds)
+ }
+ methods["ListPendingAndRunningBuilds"] = true
+
+ // lookup the last build by repo
+ got, err := db.LastBuildForRepo(context.TODO(), resources.Repos[0], "main")
+ if err != nil {
+ t.Errorf("unable to get last build for repo %d: %v", resources.Repos[0].GetID(), err)
+ }
+ if !reflect.DeepEqual(got, resources.Builds[1]) {
+ t.Errorf("LastBuildForRepo() is %v, want %v", got, resources.Builds[1])
+ }
+ methods["LastBuildForRepo"] = true
+
+ // lookup the builds by repo and number
+ for _, build := range resources.Builds {
+ repo := resources.Repos[build.GetRepoID()-1]
+ got, err = db.GetBuildForRepo(context.TODO(), repo, build.GetNumber())
+ if err != nil {
+ t.Errorf("unable to get build %d for repo %d: %v", build.GetID(), repo.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, build) {
+ t.Errorf("GetBuildForRepo() is %v, want %v", got, build)
+ }
+ }
+ methods["GetBuildForRepo"] = true
+
+ // clean the builds
+ count, err = db.CleanBuilds(context.TODO(), "integration testing", 1563474090)
+ if err != nil {
+ t.Errorf("unable to clean builds: %v", err)
+ }
+ if int(count) != len(resources.Builds) {
+ t.Errorf("CleanBuilds() is %v, want %v", count, len(resources.Builds))
+ }
+ methods["CleanBuilds"] = true
+
+ // update the builds
+ for _, build := range resources.Builds {
+ build.SetStatus("success")
+ _, err = db.UpdateBuild(context.TODO(), build)
+ if err != nil {
+ t.Errorf("unable to update build %d: %v", build.GetID(), err)
+ }
+
+ // lookup the build by ID
+ got, err = db.GetBuild(context.TODO(), build.GetID())
+ if err != nil {
+ t.Errorf("unable to get build %d by ID: %v", build.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, build) {
+ t.Errorf("GetBuild() is %v, want %v", got, build)
+ }
+ }
+ methods["UpdateBuild"] = true
+ methods["GetBuild"] = true
+
+ // delete the builds
+ for _, build := range resources.Builds {
+ err = db.DeleteBuild(context.TODO(), build)
+ if err != nil {
+ t.Errorf("unable to delete build %d: %v", build.GetID(), err)
+ }
+ }
+ methods["DeleteBuild"] = true
+
+ // delete the repos for build related functions
+ for _, repo := range resources.Repos {
+ err = db.DeleteRepo(context.TODO(), repo)
+ if err != nil {
+ t.Errorf("unable to delete repo %d: %v", repo.GetID(), err)
+ }
+ }
+
+ // ensure we called all the methods we expected to
+ for method, called := range methods {
+ if !called {
+ t.Errorf("method %s was not called for builds", method)
+ }
+ }
+}
+
+func testExecutables(t *testing.T, db Interface, resources *Resources) {
+ // create a variable to track the number of methods called for pipelines
+ methods := make(map[string]bool)
+ // capture the element type of the pipeline interface
+ element := reflect.TypeOf(new(executable.BuildExecutableInterface)).Elem()
+ // iterate through all methods found in the pipeline interface
+ for i := 0; i < element.NumMethod(); i++ {
+ // skip tracking the methods to create indexes and tables for pipelines
+ // since those are already called when the database engine starts
+ if strings.Contains(element.Method(i).Name, "Index") ||
+ strings.Contains(element.Method(i).Name, "Table") {
+ continue
+ }
+
+ // add the method name to the list of functions
+ methods[element.Method(i).Name] = false
+ }
+
+ // create the pipelines
+ for _, executable := range resources.Executables {
+ err := db.CreateBuildExecutable(context.TODO(), executable)
+ if err != nil {
+ t.Errorf("unable to create executable %d: %v", executable.GetID(), err)
+ }
+ }
+ methods["CreateBuildExecutable"] = true
+
+ // pop executables for builds
+ for _, executable := range resources.Executables {
+ got, err := db.PopBuildExecutable(context.TODO(), executable.GetBuildID())
+ if err != nil {
+ t.Errorf("unable to get executable %d for build %d: %v", executable.GetID(), executable.GetBuildID(), err)
+ }
+ if !reflect.DeepEqual(got, executable) {
+ t.Errorf("PopBuildExecutable() is %v, want %v", got, executable)
+ }
+ }
+ methods["PopBuildExecutable"] = true
+
+ // ensure we called all the methods we expected to
+ for method, called := range methods {
+ if !called {
+ t.Errorf("method %s was not called for pipelines", method)
+ }
+ }
+}
+
+func testHooks(t *testing.T, db Interface, resources *Resources) {
+ // create a variable to track the number of methods called for hooks
+ methods := make(map[string]bool)
+ // capture the element type of the hook interface
+ element := reflect.TypeOf(new(hook.HookInterface)).Elem()
+ // iterate through all methods found in the hook interface
+ for i := 0; i < element.NumMethod(); i++ {
+ // skip tracking the methods to create indexes and tables for hooks
+ // since those are already called when the database engine starts
+ if strings.Contains(element.Method(i).Name, "Index") ||
+ strings.Contains(element.Method(i).Name, "Table") {
+ continue
+ }
+
+ // add the method name to the list of functions
+ methods[element.Method(i).Name] = false
+ }
+
+ // create the hooks
+ for _, hook := range resources.Hooks {
+ _, err := db.CreateHook(hook)
+ if err != nil {
+ t.Errorf("unable to create hook %d: %v", hook.GetID(), err)
+ }
+ }
+ methods["CreateHook"] = true
+
+ // count the hooks
+ count, err := db.CountHooks()
+ if err != nil {
+ t.Errorf("unable to count hooks: %v", err)
+ }
+ if int(count) != len(resources.Hooks) {
+ t.Errorf("CountHooks() is %v, want %v", count, len(resources.Hooks))
+ }
+ methods["CountHooks"] = true
+
+ // count the hooks for a repo
+ count, err = db.CountHooksForRepo(resources.Repos[0])
+ if err != nil {
+ t.Errorf("unable to count hooks for repo %d: %v", resources.Repos[0].GetID(), err)
+ }
+ if int(count) != len(resources.Builds) {
+ t.Errorf("CountHooksForRepo() is %v, want %v", count, len(resources.Builds))
+ }
+ methods["CountHooksForRepo"] = true
+
+ // list the hooks
+ list, err := db.ListHooks()
+ if err != nil {
+ t.Errorf("unable to list hooks: %v", err)
+ }
+ if !reflect.DeepEqual(list, resources.Hooks) {
+ t.Errorf("ListHooks() is %v, want %v", list, resources.Hooks)
+ }
+ methods["ListHooks"] = true
+
+ // list the hooks for a repo
+ list, count, err = db.ListHooksForRepo(resources.Repos[0], 1, 10)
+ if err != nil {
+ t.Errorf("unable to list hooks for repo %d: %v", resources.Repos[0].GetID(), err)
+ }
+ if int(count) != len(resources.Hooks) {
+ t.Errorf("ListHooksForRepo() is %v, want %v", count, len(resources.Hooks))
+ }
+ if !reflect.DeepEqual(list, []*library.Hook{resources.Hooks[1], resources.Hooks[0]}) {
+ t.Errorf("ListHooksForRepo() is %v, want %v", list, []*library.Hook{resources.Hooks[1], resources.Hooks[0]})
+ }
+ methods["ListHooksForRepo"] = true
+
+ // lookup the last build by repo
+ got, err := db.LastHookForRepo(resources.Repos[0])
+ if err != nil {
+ t.Errorf("unable to get last hook for repo %d: %v", resources.Repos[0].GetID(), err)
+ }
+ if !reflect.DeepEqual(got, resources.Hooks[1]) {
+ t.Errorf("LastHookForRepo() is %v, want %v", got, resources.Hooks[1])
+ }
+ methods["LastHookForRepo"] = true
+
+ // lookup the hooks by name
+ for _, hook := range resources.Hooks {
+ repo := resources.Repos[hook.GetRepoID()-1]
+ got, err = db.GetHookForRepo(repo, hook.GetNumber())
+ if err != nil {
+ t.Errorf("unable to get hook %d for repo %d: %v", hook.GetID(), repo.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, hook) {
+ t.Errorf("GetHookForRepo() is %v, want %v", got, hook)
+ }
+ }
+ methods["GetHookForRepo"] = true
+
+ // update the hooks
+ for _, hook := range resources.Hooks {
+ hook.SetStatus("success")
+ _, err = db.UpdateHook(hook)
+ if err != nil {
+ t.Errorf("unable to update hook %d: %v", hook.GetID(), err)
+ }
+
+ // lookup the hook by ID
+ got, err = db.GetHook(hook.GetID())
+ if err != nil {
+ t.Errorf("unable to get hook %d by ID: %v", hook.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, hook) {
+ t.Errorf("GetHook() is %v, want %v", got, hook)
+ }
+ }
+ methods["UpdateHook"] = true
+ methods["GetHook"] = true
+
+ // delete the hooks
+ for _, hook := range resources.Hooks {
+ err = db.DeleteHook(hook)
+ if err != nil {
+ t.Errorf("unable to delete hook %d: %v", hook.GetID(), err)
+ }
+ }
+ methods["DeleteHook"] = true
+
+ // ensure we called all the methods we expected to
+ for method, called := range methods {
+ if !called {
+ t.Errorf("method %s was not called for hooks", method)
+ }
+ }
+}
+
+func testLogs(t *testing.T, db Interface, resources *Resources) {
+ // create a variable to track the number of methods called for logs
+ methods := make(map[string]bool)
+ // capture the element type of the log interface
+ element := reflect.TypeOf(new(log.LogInterface)).Elem()
+ // iterate through all methods found in the log interface
+ for i := 0; i < element.NumMethod(); i++ {
+ // skip tracking the methods to create indexes and tables for logs
+ // since those are already called when the database engine starts
+ if strings.Contains(element.Method(i).Name, "Index") ||
+ strings.Contains(element.Method(i).Name, "Table") {
+ continue
+ }
+
+ // add the method name to the list of functions
+ methods[element.Method(i).Name] = false
+ }
+
+ // create the logs
+ for _, log := range resources.Logs {
+ err := db.CreateLog(log)
+ if err != nil {
+ t.Errorf("unable to create log %d: %v", log.GetID(), err)
+ }
+ }
+ methods["CreateLog"] = true
+
+ // count the logs
+ count, err := db.CountLogs()
+ if err != nil {
+ t.Errorf("unable to count logs: %v", err)
+ }
+ if int(count) != len(resources.Logs) {
+ t.Errorf("CountLogs() is %v, want %v", count, len(resources.Logs))
+ }
+ methods["CountLogs"] = true
+
+ // count the logs for a build
+ count, err = db.CountLogsForBuild(resources.Builds[0])
+ if err != nil {
+ t.Errorf("unable to count logs for build %d: %v", resources.Builds[0].GetID(), err)
+ }
+ if int(count) != len(resources.Logs) {
+ t.Errorf("CountLogs() is %v, want %v", count, len(resources.Logs))
+ }
+ methods["CountLogsForBuild"] = true
+
+ // list the logs
+ list, err := db.ListLogs()
+ if err != nil {
+ t.Errorf("unable to list logs: %v", err)
+ }
+ if !reflect.DeepEqual(list, resources.Logs) {
+ t.Errorf("ListLogs() is %v, want %v", list, resources.Logs)
+ }
+ methods["ListLogs"] = true
+
+ // list the logs for a build
+ list, count, err = db.ListLogsForBuild(resources.Builds[0], 1, 10)
+ if err != nil {
+ t.Errorf("unable to list logs for build %d: %v", resources.Builds[0].GetID(), err)
+ }
+ if int(count) != len(resources.Logs) {
+ t.Errorf("ListLogsForBuild() is %v, want %v", count, len(resources.Logs))
+ }
+ if !reflect.DeepEqual(list, resources.Logs) {
+ t.Errorf("ListLogsForBuild() is %v, want %v", list, resources.Logs)
+ }
+ methods["ListLogsForBuild"] = true
+
+ // lookup the logs by service
+ for _, log := range []*library.Log{resources.Logs[0], resources.Logs[1]} {
+ service := resources.Services[log.GetServiceID()-1]
+ got, err := db.GetLogForService(service)
+ if err != nil {
+ t.Errorf("unable to get log %d for service %d: %v", log.GetID(), service.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, log) {
+ t.Errorf("GetLogForService() is %v, want %v", got, log)
+ }
+ }
+ methods["GetLogForService"] = true
+
+ // lookup the logs by service
+ for _, log := range []*library.Log{resources.Logs[2], resources.Logs[3]} {
+ step := resources.Steps[log.GetStepID()-1]
+ got, err := db.GetLogForStep(step)
+ if err != nil {
+ t.Errorf("unable to get log %d for step %d: %v", log.GetID(), step.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, log) {
+ t.Errorf("GetLogForStep() is %v, want %v", got, log)
+ }
+ }
+ methods["GetLogForStep"] = true
+
+ // update the logs
+ for _, log := range resources.Logs {
+ log.SetData([]byte("bar"))
+ err = db.UpdateLog(log)
+ if err != nil {
+ t.Errorf("unable to update log %d: %v", log.GetID(), err)
+ }
+
+ // lookup the log by ID
+ got, err := db.GetLog(log.GetID())
+ if err != nil {
+ t.Errorf("unable to get log %d by ID: %v", log.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, log) {
+ t.Errorf("GetLog() is %v, want %v", got, log)
+ }
+ }
+ methods["UpdateLog"] = true
+ methods["GetLog"] = true
+
+ // delete the logs
+ for _, log := range resources.Logs {
+ err = db.DeleteLog(log)
+ if err != nil {
+ t.Errorf("unable to delete log %d: %v", log.GetID(), err)
+ }
+ }
+ methods["DeleteLog"] = true
+
+ // ensure we called all the methods we expected to
+ for method, called := range methods {
+ if !called {
+ t.Errorf("method %s was not called for logs", method)
+ }
+ }
+}
+
+func testPipelines(t *testing.T, db Interface, resources *Resources) {
+ // create a variable to track the number of methods called for pipelines
+ methods := make(map[string]bool)
+ // capture the element type of the pipeline interface
+ element := reflect.TypeOf(new(pipeline.PipelineInterface)).Elem()
+ // iterate through all methods found in the pipeline interface
+ for i := 0; i < element.NumMethod(); i++ {
+ // skip tracking the methods to create indexes and tables for pipelines
+ // since those are already called when the database engine starts
+ if strings.Contains(element.Method(i).Name, "Index") ||
+ strings.Contains(element.Method(i).Name, "Table") {
+ continue
+ }
+
+ // add the method name to the list of functions
+ methods[element.Method(i).Name] = false
+ }
+
+ // create the pipelines
+ for _, pipeline := range resources.Pipelines {
+ _, err := db.CreatePipeline(context.TODO(), pipeline)
+ if err != nil {
+ t.Errorf("unable to create pipeline %d: %v", pipeline.GetID(), err)
+ }
+ }
+ methods["CreatePipeline"] = true
+
+ // count the pipelines
+ count, err := db.CountPipelines(context.TODO())
+ if err != nil {
+ t.Errorf("unable to count pipelines: %v", err)
+ }
+ if int(count) != len(resources.Pipelines) {
+ t.Errorf("CountPipelines() is %v, want %v", count, len(resources.Pipelines))
+ }
+ methods["CountPipelines"] = true
+
+ // count the pipelines for a repo
+ count, err = db.CountPipelinesForRepo(context.TODO(), resources.Repos[0])
+ if err != nil {
+ t.Errorf("unable to count pipelines for repo %d: %v", resources.Repos[0].GetID(), err)
+ }
+ if int(count) != len(resources.Pipelines) {
+ t.Errorf("CountPipelinesForRepo() is %v, want %v", count, len(resources.Pipelines))
+ }
+ methods["CountPipelinesForRepo"] = true
+
+ // list the pipelines
+ list, err := db.ListPipelines(context.TODO())
+ if err != nil {
+ t.Errorf("unable to list pipelines: %v", err)
+ }
+ if !reflect.DeepEqual(list, resources.Pipelines) {
+ t.Errorf("ListPipelines() is %v, want %v", list, resources.Pipelines)
+ }
+ methods["ListPipelines"] = true
+
+ // list the pipelines for a repo
+ list, count, err = db.ListPipelinesForRepo(context.TODO(), resources.Repos[0], 1, 10)
+ if err != nil {
+ t.Errorf("unable to list pipelines for repo %d: %v", resources.Repos[0].GetID(), err)
+ }
+ if int(count) != len(resources.Pipelines) {
+ t.Errorf("ListPipelinesForRepo() is %v, want %v", count, len(resources.Pipelines))
+ }
+ if !reflect.DeepEqual(list, resources.Pipelines) {
+ t.Errorf("ListPipelines() is %v, want %v", list, resources.Pipelines)
+ }
+ methods["ListPipelinesForRepo"] = true
+
+ // lookup the pipelines by name
+ for _, pipeline := range resources.Pipelines {
+ repo := resources.Repos[pipeline.GetRepoID()-1]
+ got, err := db.GetPipelineForRepo(context.TODO(), pipeline.GetCommit(), repo)
+ if err != nil {
+ t.Errorf("unable to get pipeline %d for repo %d: %v", pipeline.GetID(), repo.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, pipeline) {
+ t.Errorf("GetPipelineForRepo() is %v, want %v", got, pipeline)
+ }
+ }
+ methods["GetPipelineForRepo"] = true
+
+ // update the pipelines
+ for _, pipeline := range resources.Pipelines {
+ pipeline.SetVersion("2")
+ _, err = db.UpdatePipeline(context.TODO(), pipeline)
+ if err != nil {
+ t.Errorf("unable to update pipeline %d: %v", pipeline.GetID(), err)
+ }
+
+ // lookup the pipeline by ID
+ got, err := db.GetPipeline(context.TODO(), pipeline.GetID())
+ if err != nil {
+ t.Errorf("unable to get pipeline %d by ID: %v", pipeline.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, pipeline) {
+ t.Errorf("GetPipeline() is %v, want %v", got, pipeline)
+ }
+ }
+ methods["UpdatePipeline"] = true
+ methods["GetPipeline"] = true
+
+ // delete the pipelines
+ for _, pipeline := range resources.Pipelines {
+ err = db.DeletePipeline(context.TODO(), pipeline)
+ if err != nil {
+ t.Errorf("unable to delete pipeline %d: %v", pipeline.GetID(), err)
+ }
+ }
+ methods["DeletePipeline"] = true
+
+ // ensure we called all the methods we expected to
+ for method, called := range methods {
+ if !called {
+ t.Errorf("method %s was not called for pipelines", method)
+ }
+ }
+}
+
+func testRepos(t *testing.T, db Interface, resources *Resources) {
+ // create a variable to track the number of methods called for repos
+ methods := make(map[string]bool)
+ // capture the element type of the repo interface
+ element := reflect.TypeOf(new(repo.RepoInterface)).Elem()
+ // iterate through all methods found in the repo interface
+ for i := 0; i < element.NumMethod(); i++ {
+ // skip tracking the methods to create indexes and tables for repos
+ // since those are already called when the database engine starts
+ if strings.Contains(element.Method(i).Name, "Index") ||
+ strings.Contains(element.Method(i).Name, "Table") {
+ continue
+ }
+
+ // add the method name to the list of functions
+ methods[element.Method(i).Name] = false
+ }
+
+ // create the repos
+ for _, repo := range resources.Repos {
+ _, err := db.CreateRepo(context.TODO(), repo)
+ if err != nil {
+ t.Errorf("unable to create repo %d: %v", repo.GetID(), err)
+ }
+ }
+ methods["CreateRepo"] = true
+
+ // count the repos
+ count, err := db.CountRepos(context.TODO())
+ if err != nil {
+ t.Errorf("unable to count repos: %v", err)
+ }
+ if int(count) != len(resources.Repos) {
+ t.Errorf("CountRepos() is %v, want %v", count, len(resources.Repos))
+ }
+ methods["CountRepos"] = true
+
+ // count the repos for an org
+ count, err = db.CountReposForOrg(context.TODO(), resources.Repos[0].GetOrg(), nil)
+ if err != nil {
+ t.Errorf("unable to count repos for org %s: %v", resources.Repos[0].GetOrg(), err)
+ }
+ if int(count) != len(resources.Repos) {
+ t.Errorf("CountReposForOrg() is %v, want %v", count, len(resources.Repos))
+ }
+ methods["CountReposForOrg"] = true
+
+ // count the repos for a user
+ count, err = db.CountReposForUser(context.TODO(), resources.Users[0], nil)
+ if err != nil {
+ t.Errorf("unable to count repos for user %d: %v", resources.Users[0].GetID(), err)
+ }
+ if int(count) != len(resources.Repos) {
+ t.Errorf("CountReposForUser() is %v, want %v", count, len(resources.Repos))
+ }
+ methods["CountReposForUser"] = true
+
+ // list the repos
+ list, err := db.ListRepos(context.TODO())
+ if err != nil {
+ t.Errorf("unable to list repos: %v", err)
+ }
+ if !reflect.DeepEqual(list, resources.Repos) {
+ t.Errorf("ListRepos() is %v, want %v", list, resources.Repos)
+ }
+ methods["ListRepos"] = true
+
+ // list the repos for an org
+ list, count, err = db.ListReposForOrg(context.TODO(), resources.Repos[0].GetOrg(), "name", nil, 1, 10)
+ if err != nil {
+ t.Errorf("unable to list repos for org %s: %v", resources.Repos[0].GetOrg(), err)
+ }
+ if int(count) != len(resources.Repos) {
+ t.Errorf("ListReposForOrg() is %v, want %v", count, len(resources.Repos))
+ }
+ if !reflect.DeepEqual(list, resources.Repos) {
+ t.Errorf("ListReposForOrg() is %v, want %v", list, resources.Repos)
+ }
+ methods["ListReposForOrg"] = true
+
+ // list the repos for a user
+ list, count, err = db.ListReposForUser(context.TODO(), resources.Users[0], "name", nil, 1, 10)
+ if err != nil {
+ t.Errorf("unable to list repos for user %d: %v", resources.Users[0].GetID(), err)
+ }
+ if int(count) != len(resources.Repos) {
+ t.Errorf("ListReposForUser() is %v, want %v", count, len(resources.Repos))
+ }
+ if !reflect.DeepEqual(list, resources.Repos) {
+ t.Errorf("ListReposForUser() is %v, want %v", list, resources.Repos)
+ }
+ methods["ListReposForUser"] = true
+
+ // lookup the repos by name
+ for _, repo := range resources.Repos {
+ got, err := db.GetRepoForOrg(context.TODO(), repo.GetOrg(), repo.GetName())
+ if err != nil {
+ t.Errorf("unable to get repo %d by org: %v", repo.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, repo) {
+ t.Errorf("GetRepoForOrg() is %v, want %v", got, repo)
+ }
+ }
+ methods["GetRepoForOrg"] = true
+
+ // update the repos
+ for _, repo := range resources.Repos {
+ repo.SetActive(false)
+ _, err = db.UpdateRepo(context.TODO(), repo)
+ if err != nil {
+ t.Errorf("unable to update repo %d: %v", repo.GetID(), err)
+ }
+
+ // lookup the repo by ID
+ got, err := db.GetRepo(context.TODO(), repo.GetID())
+ if err != nil {
+ t.Errorf("unable to get repo %d by ID: %v", repo.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, repo) {
+ t.Errorf("GetRepo() is %v, want %v", got, repo)
+ }
+ }
+ methods["UpdateRepo"] = true
+ methods["GetRepo"] = true
+
+ // delete the repos
+ for _, repo := range resources.Repos {
+ err = db.DeleteRepo(context.TODO(), repo)
+ if err != nil {
+ t.Errorf("unable to delete repo %d: %v", repo.GetID(), err)
+ }
+ }
+ methods["DeleteRepo"] = true
+
+ // ensure we called all the methods we expected to
+ for method, called := range methods {
+ if !called {
+ t.Errorf("method %s was not called for repos", method)
+ }
+ }
+}
+
+func testSchedules(t *testing.T, db Interface, resources *Resources) {
+ // create a variable to track the number of methods called for schedules
+ methods := make(map[string]bool)
+ // capture the element type of the schedule interface
+ element := reflect.TypeOf(new(schedule.ScheduleInterface)).Elem()
+ // iterate through all methods found in the schedule interface
+ for i := 0; i < element.NumMethod(); i++ {
+ // skip tracking the methods to create indexes and tables for schedules
+ // since those are already called when the database engine starts
+ if strings.Contains(element.Method(i).Name, "Index") ||
+ strings.Contains(element.Method(i).Name, "Table") {
+ continue
+ }
+
+ // add the method name to the list of functions
+ methods[element.Method(i).Name] = false
+ }
+
+ ctx := context.TODO()
+
+ // create the schedules
+ for _, schedule := range resources.Schedules {
+ _, err := db.CreateSchedule(ctx, schedule)
+ if err != nil {
+ t.Errorf("unable to create schedule %d: %v", schedule.GetID(), err)
+ }
+ }
+ methods["CreateSchedule"] = true
+
+ // count the schedules
+ count, err := db.CountSchedules(ctx)
+ if err != nil {
+ t.Errorf("unable to count schedules: %v", err)
+ }
+ if int(count) != len(resources.Schedules) {
+ t.Errorf("CountSchedules() is %v, want %v", count, len(resources.Schedules))
+ }
+ methods["CountSchedules"] = true
+
+ // count the schedules for a repo
+ count, err = db.CountSchedulesForRepo(ctx, resources.Repos[0])
+ if err != nil {
+ t.Errorf("unable to count schedules for repo %d: %v", resources.Repos[0].GetID(), err)
+ }
+ if int(count) != len(resources.Schedules) {
+ t.Errorf("CountSchedulesForRepo() is %v, want %v", count, len(resources.Schedules))
+ }
+ methods["CountSchedulesForRepo"] = true
+
+ // list the schedules
+ list, err := db.ListSchedules(ctx)
+ if err != nil {
+ t.Errorf("unable to list schedules: %v", err)
+ }
+ if !reflect.DeepEqual(list, resources.Schedules) {
+ t.Errorf("ListSchedules() is %v, want %v", list, resources.Schedules)
+ }
+ methods["ListSchedules"] = true
+
+ // list the active schedules
+ list, err = db.ListActiveSchedules(ctx)
+ if err != nil {
+ t.Errorf("unable to list schedules: %v", err)
+ }
+ if !reflect.DeepEqual(list, resources.Schedules) {
+ t.Errorf("ListActiveSchedules() is %v, want %v", list, resources.Schedules)
+ }
+ methods["ListActiveSchedules"] = true
+
+ // list the schedules for a repo
+ list, count, err = db.ListSchedulesForRepo(ctx, resources.Repos[0], 1, 10)
+ if err != nil {
+ t.Errorf("unable to count schedules for repo %d: %v", resources.Repos[0].GetID(), err)
+ }
+ if int(count) != len(resources.Schedules) {
+ t.Errorf("ListSchedulesForRepo() is %v, want %v", count, len(resources.Schedules))
+ }
+ if !reflect.DeepEqual(list, []*library.Schedule{resources.Schedules[1], resources.Schedules[0]}) {
+ t.Errorf("ListSchedulesForRepo() is %v, want %v", list, []*library.Schedule{resources.Schedules[1], resources.Schedules[0]})
+ }
+ methods["ListSchedulesForRepo"] = true
+
+ // lookup the schedules by name
+ for _, schedule := range resources.Schedules {
+ repo := resources.Repos[schedule.GetRepoID()-1]
+ got, err := db.GetScheduleForRepo(ctx, repo, schedule.GetName())
+ if err != nil {
+ t.Errorf("unable to get schedule %d for repo %d: %v", schedule.GetID(), repo.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, schedule) {
+ t.Errorf("GetScheduleForRepo() is %v, want %v", got, schedule)
+ }
+ }
+ methods["GetScheduleForRepo"] = true
+
+ // update the schedules
+ for _, schedule := range resources.Schedules {
+ schedule.SetUpdatedAt(time.Now().UTC().Unix())
+ got, err := db.UpdateSchedule(ctx, schedule, true)
+ if err != nil {
+ t.Errorf("unable to update schedule %d: %v", schedule.GetID(), err)
+ }
+
+ if !reflect.DeepEqual(got, schedule) {
+ t.Errorf("GetSchedule() is %v, want %v", got, schedule)
+ }
+ }
+ methods["UpdateSchedule"] = true
+ methods["GetSchedule"] = true
+
+ // delete the schedules
+ for _, schedule := range resources.Schedules {
+ err = db.DeleteSchedule(ctx, schedule)
+ if err != nil {
+ t.Errorf("unable to delete schedule %d: %v", schedule.GetID(), err)
+ }
+ }
+ methods["DeleteSchedule"] = true
+
+ // ensure we called all the methods we expected to
+ for method, called := range methods {
+ if !called {
+ t.Errorf("method %s was not called for schedules", method)
+ }
+ }
+}
+
+func testSecrets(t *testing.T, db Interface, resources *Resources) {
+ // create a variable to track the number of methods called for secrets
+ methods := make(map[string]bool)
+ // capture the element type of the secret interface
+ element := reflect.TypeOf(new(secret.SecretInterface)).Elem()
+ // iterate through all methods found in the secret interface
+ for i := 0; i < element.NumMethod(); i++ {
+ // skip tracking the methods to create indexes and tables for secrets
+ // since those are already called when the database engine starts
+ if strings.Contains(element.Method(i).Name, "Index") ||
+ strings.Contains(element.Method(i).Name, "Table") {
+ continue
+ }
+
+ // add the method name to the list of functions
+ methods[element.Method(i).Name] = false
+ }
+
+ // create the secrets
+ for _, secret := range resources.Secrets {
+ _, err := db.CreateSecret(secret)
+ if err != nil {
+ t.Errorf("unable to create secret %d: %v", secret.GetID(), err)
+ }
+ }
+ methods["CreateSecret"] = true
+
+ // count the secrets
+ count, err := db.CountSecrets()
+ if err != nil {
+ t.Errorf("unable to count secrets: %v", err)
+ }
+ if int(count) != len(resources.Secrets) {
+ t.Errorf("CountSecrets() is %v, want %v", count, len(resources.Secrets))
+ }
+ methods["CountSecrets"] = true
+
+ for _, secret := range resources.Secrets {
+ switch secret.GetType() {
+ case constants.SecretOrg:
+ // count the secrets for an org
+ count, err = db.CountSecretsForOrg(secret.GetOrg(), nil)
+ if err != nil {
+ t.Errorf("unable to count secrets for org %s: %v", secret.GetOrg(), err)
+ }
+ if int(count) != 1 {
+ t.Errorf("CountSecretsForOrg() is %v, want %v", count, 1)
+ }
+ methods["CountSecretsForOrg"] = true
+ case constants.SecretRepo:
+ // count the secrets for a repo
+ count, err = db.CountSecretsForRepo(resources.Repos[0], nil)
+ if err != nil {
+ t.Errorf("unable to count secrets for repo %d: %v", resources.Repos[0].GetID(), err)
+ }
+ if int(count) != 1 {
+ t.Errorf("CountSecretsForRepo() is %v, want %v", count, 1)
+ }
+ methods["CountSecretsForRepo"] = true
+ case constants.SecretShared:
+ // count the secrets for a team
+ count, err = db.CountSecretsForTeam(secret.GetOrg(), secret.GetTeam(), nil)
+ if err != nil {
+ t.Errorf("unable to count secrets for team %s: %v", secret.GetTeam(), err)
+ }
+ if int(count) != 1 {
+ t.Errorf("CountSecretsForTeam() is %v, want %v", count, 1)
+ }
+ methods["CountSecretsForTeam"] = true
+
+ // count the secrets for a list of teams
+ count, err = db.CountSecretsForTeams(secret.GetOrg(), []string{secret.GetTeam()}, nil)
+ if err != nil {
+ t.Errorf("unable to count secrets for teams %s: %v", []string{secret.GetTeam()}, err)
+ }
+ if int(count) != 1 {
+ t.Errorf("CountSecretsForTeams() is %v, want %v", count, 1)
+ }
+ methods["CountSecretsForTeams"] = true
+ default:
+ t.Errorf("unsupported type %s for secret %d", secret.GetType(), secret.GetID())
+ }
+ }
+
+ // list the secrets
+ list, err := db.ListSecrets()
+ if err != nil {
+ t.Errorf("unable to list secrets: %v", err)
+ }
+ if !reflect.DeepEqual(list, resources.Secrets) {
+ t.Errorf("ListSecrets() is %v, want %v", list, resources.Secrets)
+ }
+ methods["ListSecrets"] = true
+
+ for _, secret := range resources.Secrets {
+ switch secret.GetType() {
+ case constants.SecretOrg:
+ // list the secrets for an org
+ list, count, err = db.ListSecretsForOrg(secret.GetOrg(), nil, 1, 10)
+ if err != nil {
+ t.Errorf("unable to list secrets for org %s: %v", secret.GetOrg(), err)
+ }
+ if int(count) != 1 {
+ t.Errorf("ListSecretsForOrg() is %v, want %v", count, 1)
+ }
+ if !reflect.DeepEqual(list, []*library.Secret{secret}) {
+ t.Errorf("ListSecretsForOrg() is %v, want %v", list, []*library.Secret{secret})
+ }
+ methods["ListSecretsForOrg"] = true
+ case constants.SecretRepo:
+ // list the secrets for a repo
+ list, count, err = db.ListSecretsForRepo(resources.Repos[0], nil, 1, 10)
+ if err != nil {
+ t.Errorf("unable to list secrets for repo %d: %v", resources.Repos[0].GetID(), err)
+ }
+ if int(count) != 1 {
+ t.Errorf("ListSecretsForRepo() is %v, want %v", count, 1)
+ }
+ if !reflect.DeepEqual(list, []*library.Secret{secret}) {
+ t.Errorf("ListSecretsForRepo() is %v, want %v", list, []*library.Secret{secret})
+ }
+ methods["ListSecretsForRepo"] = true
+ case constants.SecretShared:
+ // list the secrets for a team
+ list, count, err = db.ListSecretsForTeam(secret.GetOrg(), secret.GetTeam(), nil, 1, 10)
+ if err != nil {
+ t.Errorf("unable to list secrets for team %s: %v", secret.GetTeam(), err)
+ }
+ if int(count) != 1 {
+ t.Errorf("ListSecretsForTeam() is %v, want %v", count, 1)
+ }
+ if !reflect.DeepEqual(list, []*library.Secret{secret}) {
+ t.Errorf("ListSecretsForTeam() is %v, want %v", list, []*library.Secret{secret})
+ }
+ methods["ListSecretsForTeam"] = true
+
+ // list the secrets for a list of teams
+ list, count, err = db.ListSecretsForTeams(secret.GetOrg(), []string{secret.GetTeam()}, nil, 1, 10)
+ if err != nil {
+ t.Errorf("unable to list secrets for teams %s: %v", []string{secret.GetTeam()}, err)
+ }
+ if int(count) != 1 {
+ t.Errorf("ListSecretsForTeams() is %v, want %v", count, 1)
+ }
+ if !reflect.DeepEqual(list, []*library.Secret{secret}) {
+ t.Errorf("ListSecretsForTeams() is %v, want %v", list, []*library.Secret{secret})
+ }
+ methods["ListSecretsForTeams"] = true
+ default:
+ t.Errorf("unsupported type %s for secret %d", secret.GetType(), secret.GetID())
+ }
+ }
+
+ for _, secret := range resources.Secrets {
+ switch secret.GetType() {
+ case constants.SecretOrg:
+ // lookup the secret by org
+ got, err := db.GetSecretForOrg(secret.GetOrg(), secret.GetName())
+ if err != nil {
+ t.Errorf("unable to get secret %d for org %s: %v", secret.GetID(), secret.GetOrg(), err)
+ }
+ if !reflect.DeepEqual(got, secret) {
+ t.Errorf("GetSecretForOrg() is %v, want %v", got, secret)
+ }
+ methods["GetSecretForOrg"] = true
+ case constants.SecretRepo:
+ // lookup the secret by repo
+ got, err := db.GetSecretForRepo(secret.GetName(), resources.Repos[0])
+ if err != nil {
+ t.Errorf("unable to get secret %d for repo %d: %v", secret.GetID(), resources.Repos[0].GetID(), err)
+ }
+ if !reflect.DeepEqual(got, secret) {
+ t.Errorf("GetSecretForRepo() is %v, want %v", got, secret)
+ }
+ methods["GetSecretForRepo"] = true
+ case constants.SecretShared:
+ // lookup the secret by team
+ got, err := db.GetSecretForTeam(secret.GetOrg(), secret.GetTeam(), secret.GetName())
+ if err != nil {
+ t.Errorf("unable to get secret %d for team %s: %v", secret.GetID(), secret.GetTeam(), err)
+ }
+ if !reflect.DeepEqual(got, secret) {
+ t.Errorf("GetSecretForTeam() is %v, want %v", got, secret)
+ }
+ methods["GetSecretForTeam"] = true
+ default:
+ t.Errorf("unsupported type %s for secret %d", secret.GetType(), secret.GetID())
+ }
+ }
+
+ // update the secrets
+ for _, secret := range resources.Secrets {
+ secret.SetUpdatedAt(time.Now().UTC().Unix())
+ got, err := db.UpdateSecret(secret)
+ if err != nil {
+ t.Errorf("unable to update secret %d: %v", secret.GetID(), err)
+ }
+
+ if !reflect.DeepEqual(got, secret) {
+ t.Errorf("GetSecret() is %v, want %v", got, secret)
+ }
+ }
+ methods["UpdateSecret"] = true
+ methods["GetSecret"] = true
+
+ // delete the secrets
+ for _, secret := range resources.Secrets {
+ err = db.DeleteSecret(secret)
+ if err != nil {
+ t.Errorf("unable to delete secret %d: %v", secret.GetID(), err)
+ }
+ }
+ methods["DeleteSecret"] = true
+
+ // ensure we called all the methods we expected to
+ for method, called := range methods {
+ if !called {
+ t.Errorf("method %s was not called for secrets", method)
+ }
+ }
+}
+
+func testServices(t *testing.T, db Interface, resources *Resources) {
+ // create a variable to track the number of methods called for services
+ methods := make(map[string]bool)
+ // capture the element type of the service interface
+ element := reflect.TypeOf(new(service.ServiceInterface)).Elem()
+ // iterate through all methods found in the service interface
+ for i := 0; i < element.NumMethod(); i++ {
+ // skip tracking the methods to create indexes and tables for services
+ // since those are already called when the database engine starts
+ if strings.Contains(element.Method(i).Name, "Index") ||
+ strings.Contains(element.Method(i).Name, "Table") {
+ continue
+ }
+
+ // add the method name to the list of functions
+ methods[element.Method(i).Name] = false
+ }
+
+ // create the services
+ for _, service := range resources.Services {
+ _, err := db.CreateService(service)
+ if err != nil {
+ t.Errorf("unable to create service %d: %v", service.GetID(), err)
+ }
+ }
+ methods["CreateService"] = true
+
+ // count the services
+ count, err := db.CountServices()
+ if err != nil {
+ t.Errorf("unable to count services: %v", err)
+ }
+ if int(count) != len(resources.Services) {
+ t.Errorf("CountServices() is %v, want %v", count, len(resources.Services))
+ }
+ methods["CountServices"] = true
+
+ // count the services for a build
+ count, err = db.CountServicesForBuild(resources.Builds[0], nil)
+ if err != nil {
+ t.Errorf("unable to count services for build %d: %v", resources.Builds[0].GetID(), err)
+ }
+ if int(count) != len(resources.Services) {
+ t.Errorf("CountServicesForBuild() is %v, want %v", count, len(resources.Services))
+ }
+ methods["CountServicesForBuild"] = true
+
+ // list the services
+ list, err := db.ListServices()
+ if err != nil {
+ t.Errorf("unable to list services: %v", err)
+ }
+ if !reflect.DeepEqual(list, resources.Services) {
+ t.Errorf("ListServices() is %v, want %v", list, resources.Services)
+ }
+ methods["ListServices"] = true
+
+ // list the services for a build
+ list, count, err = db.ListServicesForBuild(resources.Builds[0], nil, 1, 10)
+ if err != nil {
+ t.Errorf("unable to list services for build %d: %v", resources.Builds[0].GetID(), err)
+ }
+ if !reflect.DeepEqual(list, []*library.Service{resources.Services[1], resources.Services[0]}) {
+ t.Errorf("ListServicesForBuild() is %v, want %v", list, []*library.Service{resources.Services[1], resources.Services[0]})
+ }
+ if int(count) != len(resources.Services) {
+ t.Errorf("ListServicesForBuild() is %v, want %v", count, len(resources.Services))
+ }
+ methods["ListServicesForBuild"] = true
+
+ expected := map[string]float64{
+ "#init": 1,
+ "target/vela-git:v0.3.0": 1,
+ }
+ images, err := db.ListServiceImageCount()
+ if err != nil {
+ t.Errorf("unable to list service image count: %v", err)
+ }
+ if !reflect.DeepEqual(images, expected) {
+ t.Errorf("ListServiceImageCount() is %v, want %v", images, expected)
+ }
+ methods["ListServiceImageCount"] = true
+
+ expected = map[string]float64{
+ "pending": 1,
+ "failure": 0,
+ "killed": 0,
+ "running": 1,
+ "success": 0,
+ }
+ statuses, err := db.ListServiceStatusCount()
+ if err != nil {
+ t.Errorf("unable to list service status count: %v", err)
+ }
+ if !reflect.DeepEqual(statuses, expected) {
+ t.Errorf("ListServiceStatusCount() is %v, want %v", statuses, expected)
+ }
+ methods["ListServiceStatusCount"] = true
+
+ // lookup the services by name
+ for _, service := range resources.Services {
+ build := resources.Builds[service.GetBuildID()-1]
+ got, err := db.GetServiceForBuild(build, service.GetNumber())
+ if err != nil {
+ t.Errorf("unable to get service %d for build %d: %v", service.GetID(), build.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, service) {
+ t.Errorf("GetServiceForBuild() is %v, want %v", got, service)
+ }
+ }
+ methods["GetServiceForBuild"] = true
+
+ // clean the services
+ count, err = db.CleanServices("integration testing", 1563474090)
+ if err != nil {
+ t.Errorf("unable to clean services: %v", err)
+ }
+ if int(count) != len(resources.Services) {
+ t.Errorf("CleanServices() is %v, want %v", count, len(resources.Services))
+ }
+ methods["CleanServices"] = true
+
+ // update the services
+ for _, service := range resources.Services {
+ service.SetStatus("success")
+ got, err := db.UpdateService(service)
+ if err != nil {
+ t.Errorf("unable to update service %d: %v", service.GetID(), err)
+ }
+
+ if !reflect.DeepEqual(got, service) {
+ t.Errorf("UpdateService() is %v, want %v", got, service)
+ }
+ }
+ methods["UpdateService"] = true
+ methods["GetService"] = true
+
+ // delete the services
+ for _, service := range resources.Services {
+ err = db.DeleteService(service)
+ if err != nil {
+ t.Errorf("unable to delete service %d: %v", service.GetID(), err)
+ }
+ }
+ methods["DeleteService"] = true
+
+ // ensure we called all the methods we expected to
+ for method, called := range methods {
+ if !called {
+ t.Errorf("method %s was not called for services", method)
+ }
+ }
+}
+
+func testSteps(t *testing.T, db Interface, resources *Resources) {
+ // create a variable to track the number of methods called for steps
+ methods := make(map[string]bool)
+ // capture the element type of the step interface
+ element := reflect.TypeOf(new(step.StepInterface)).Elem()
+ // iterate through all methods found in the step interface
+ for i := 0; i < element.NumMethod(); i++ {
+ // skip tracking the methods to create indexes and tables for steps
+ // since those are already called when the database engine starts
+ if strings.Contains(element.Method(i).Name, "Index") ||
+ strings.Contains(element.Method(i).Name, "Table") {
+ continue
+ }
+
+ // add the method name to the list of functions
+ methods[element.Method(i).Name] = false
+ }
+
+ // create the steps
+ for _, step := range resources.Steps {
+ _, err := db.CreateStep(step)
+ if err != nil {
+ t.Errorf("unable to create step %d: %v", step.GetID(), err)
+ }
+ }
+ methods["CreateStep"] = true
+
+ // count the steps
+ count, err := db.CountSteps()
+ if err != nil {
+ t.Errorf("unable to count steps: %v", err)
+ }
+ if int(count) != len(resources.Steps) {
+ t.Errorf("CountSteps() is %v, want %v", count, len(resources.Steps))
+ }
+ methods["CountSteps"] = true
+
+ // count the steps for a build
+ count, err = db.CountStepsForBuild(resources.Builds[0], nil)
+ if err != nil {
+ t.Errorf("unable to count steps for build %d: %v", resources.Builds[0].GetID(), err)
+ }
+ if int(count) != len(resources.Steps) {
+ t.Errorf("CountStepsForBuild() is %v, want %v", count, len(resources.Steps))
+ }
+ methods["CountStepsForBuild"] = true
+
+ // list the steps
+ list, err := db.ListSteps()
+ if err != nil {
+ t.Errorf("unable to list steps: %v", err)
+ }
+ if !reflect.DeepEqual(list, resources.Steps) {
+ t.Errorf("ListSteps() is %v, want %v", list, resources.Steps)
+ }
+ methods["ListSteps"] = true
+
+ // list the steps for a build
+ list, count, err = db.ListStepsForBuild(resources.Builds[0], nil, 1, 10)
+ if err != nil {
+ t.Errorf("unable to list steps for build %d: %v", resources.Builds[0].GetID(), err)
+ }
+ if !reflect.DeepEqual(list, []*library.Step{resources.Steps[1], resources.Steps[0]}) {
+ t.Errorf("ListStepsForBuild() is %v, want %v", list, []*library.Step{resources.Steps[1], resources.Steps[0]})
+ }
+ if int(count) != len(resources.Steps) {
+ t.Errorf("ListStepsForBuild() is %v, want %v", count, len(resources.Steps))
+ }
+ methods["ListStepsForBuild"] = true
+
+ expected := map[string]float64{
+ "#init": 1,
+ "target/vela-git:v0.3.0": 1,
+ }
+ images, err := db.ListStepImageCount()
+ if err != nil {
+ t.Errorf("unable to list step image count: %v", err)
+ }
+ if !reflect.DeepEqual(images, expected) {
+ t.Errorf("ListStepImageCount() is %v, want %v", images, expected)
+ }
+ methods["ListStepImageCount"] = true
+
+ expected = map[string]float64{
+ "pending": 1,
+ "failure": 0,
+ "killed": 0,
+ "running": 1,
+ "success": 0,
+ }
+ statuses, err := db.ListStepStatusCount()
+ if err != nil {
+ t.Errorf("unable to list step status count: %v", err)
+ }
+ if !reflect.DeepEqual(statuses, expected) {
+ t.Errorf("ListStepStatusCount() is %v, want %v", statuses, expected)
+ }
+ methods["ListStepStatusCount"] = true
+
+ // lookup the steps by name
+ for _, step := range resources.Steps {
+ build := resources.Builds[step.GetBuildID()-1]
+ got, err := db.GetStepForBuild(build, step.GetNumber())
+ if err != nil {
+ t.Errorf("unable to get step %d for build %d: %v", step.GetID(), build.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, step) {
+ t.Errorf("GetStepForBuild() is %v, want %v", got, step)
+ }
+ }
+ methods["GetStepForBuild"] = true
+
+ // clean the steps
+ count, err = db.CleanSteps("integration testing", 1563474090)
+ if err != nil {
+ t.Errorf("unable to clean steps: %v", err)
+ }
+ if int(count) != len(resources.Steps) {
+ t.Errorf("CleanSteps() is %v, want %v", count, len(resources.Steps))
+ }
+ methods["CleanSteps"] = true
+
+ // update the steps
+ for _, step := range resources.Steps {
+ step.SetStatus("success")
+ got, err := db.UpdateStep(step)
+ if err != nil {
+ t.Errorf("unable to update step %d: %v", step.GetID(), err)
+ }
+
+ if !reflect.DeepEqual(got, step) {
+ t.Errorf("GetStep() is %v, want %v", got, step)
+ }
+ }
+ methods["UpdateStep"] = true
+ methods["GetStep"] = true
+
+ // delete the steps
+ for _, step := range resources.Steps {
+ err = db.DeleteStep(step)
+ if err != nil {
+ t.Errorf("unable to delete step %d: %v", step.GetID(), err)
+ }
+ }
+ methods["DeleteStep"] = true
+
+ // ensure we called all the methods we expected to
+ for method, called := range methods {
+ if !called {
+ t.Errorf("method %s was not called for steps", method)
+ }
+ }
+}
+
+func testUsers(t *testing.T, db Interface, resources *Resources) {
+ // create a variable to track the number of methods called for users
+ methods := make(map[string]bool)
+ // capture the element type of the user interface
+ element := reflect.TypeOf(new(user.UserInterface)).Elem()
+ // iterate through all methods found in the user interface
+ for i := 0; i < element.NumMethod(); i++ {
+ // skip tracking the methods to create indexes and tables for users
+ // since those are already called when the database engine starts
+ if strings.Contains(element.Method(i).Name, "Index") ||
+ strings.Contains(element.Method(i).Name, "Table") {
+ continue
+ }
+
+ // add the method name to the list of functions
+ methods[element.Method(i).Name] = false
+ }
+
+ userOne := new(library.User)
+ userOne.SetID(1)
+ userOne.SetName("octocat")
+ userOne.SetToken("")
+ userOne.SetRefreshToken("")
+ userOne.SetHash("")
+ userOne.SetFavorites(nil)
+ userOne.SetActive(false)
+ userOne.SetAdmin(false)
+
+ userTwo := new(library.User)
+ userTwo.SetID(2)
+ userTwo.SetName("octokitty")
+ userTwo.SetToken("")
+ userTwo.SetRefreshToken("")
+ userTwo.SetHash("")
+ userTwo.SetFavorites(nil)
+ userTwo.SetActive(false)
+ userTwo.SetAdmin(false)
+
+ liteUsers := []*library.User{userOne, userTwo}
+
+ // create the users
+ for _, user := range resources.Users {
+ err := db.CreateUser(user)
+ if err != nil {
+ t.Errorf("unable to create user %d: %v", user.GetID(), err)
+ }
+ }
+ methods["CreateUser"] = true
+
+ // count the users
+ count, err := db.CountUsers()
+ if err != nil {
+ t.Errorf("unable to count users: %v", err)
+ }
+ if int(count) != len(resources.Users) {
+ t.Errorf("CountUsers() is %v, want %v", count, len(resources.Users))
+ }
+ methods["CountUsers"] = true
+
+ // list the users
+ list, err := db.ListUsers()
+ if err != nil {
+ t.Errorf("unable to list users: %v", err)
+ }
+ if !reflect.DeepEqual(list, resources.Users) {
+ t.Errorf("ListUsers() is %v, want %v", list, resources.Users)
+ }
+ methods["ListUsers"] = true
+
+ // lite list the users
+ list, count, err = db.ListLiteUsers(1, 10)
+ if err != nil {
+ t.Errorf("unable to list lite users: %v", err)
+ }
+ if !reflect.DeepEqual(list, liteUsers) {
+ t.Errorf("ListLiteUsers() is %v, want %v", list, liteUsers)
+ }
+ if int(count) != len(liteUsers) {
+ t.Errorf("ListLiteUsers() is %v, want %v", count, len(liteUsers))
+ }
+ methods["ListLiteUsers"] = true
+
+ // lookup the users by name
+ for _, user := range resources.Users {
+ got, err := db.GetUserForName(user.GetName())
+ if err != nil {
+ t.Errorf("unable to get user %d by name: %v", user.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, user) {
+ t.Errorf("GetUserForName() is %v, want %v", got, user)
+ }
+ }
+ methods["GetUserForName"] = true
+
+ // update the users
+ for _, user := range resources.Users {
+ user.SetActive(false)
+ err = db.UpdateUser(user)
+ if err != nil {
+ t.Errorf("unable to update user %d: %v", user.GetID(), err)
+ }
+
+ // lookup the user by ID
+ got, err := db.GetUser(user.GetID())
+ if err != nil {
+ t.Errorf("unable to get user %d by ID: %v", user.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, user) {
+ t.Errorf("GetUser() is %v, want %v", got, user)
+ }
+ }
+ methods["UpdateUser"] = true
+ methods["GetUser"] = true
+
+ // delete the users
+ for _, user := range resources.Users {
+ err = db.DeleteUser(user)
+ if err != nil {
+ t.Errorf("unable to delete user %d: %v", user.GetID(), err)
+ }
+ }
+ methods["DeleteUser"] = true
+
+ // ensure we called all the methods we expected to
+ for method, called := range methods {
+ if !called {
+ t.Errorf("method %s was not called for users", method)
+ }
+ }
+}
+
+func testWorkers(t *testing.T, db Interface, resources *Resources) {
+ // create a variable to track the number of methods called for workers
+ methods := make(map[string]bool)
+ // capture the element type of the worker interface
+ element := reflect.TypeOf(new(worker.WorkerInterface)).Elem()
+ // iterate through all methods found in the worker interface
+ for i := 0; i < element.NumMethod(); i++ {
+ // skip tracking the methods to create indexes and tables for workers
+ // since those are already called when the database engine starts
+ if strings.Contains(element.Method(i).Name, "Index") ||
+ strings.Contains(element.Method(i).Name, "Table") {
+ continue
+ }
+
+ // add the method name to the list of functions
+ methods[element.Method(i).Name] = false
+ }
+
+ // create the workers
+ for _, worker := range resources.Workers {
+ err := db.CreateWorker(worker)
+ if err != nil {
+ t.Errorf("unable to create worker %d: %v", worker.GetID(), err)
+ }
+ }
+ methods["CreateWorker"] = true
+
+ // count the workers
+ count, err := db.CountWorkers()
+ if err != nil {
+ t.Errorf("unable to count workers: %v", err)
+ }
+ if int(count) != len(resources.Workers) {
+ t.Errorf("CountWorkers() is %v, want %v", count, len(resources.Workers))
+ }
+ methods["CountWorkers"] = true
+
+ // list the workers
+ list, err := db.ListWorkers()
+ if err != nil {
+ t.Errorf("unable to list workers: %v", err)
+ }
+ if !reflect.DeepEqual(list, resources.Workers) {
+ t.Errorf("ListWorkers() is %v, want %v", list, resources.Workers)
+ }
+ methods["ListWorkers"] = true
+
+ // lookup the workers by hostname
+ for _, worker := range resources.Workers {
+ got, err := db.GetWorkerForHostname(worker.GetHostname())
+ if err != nil {
+ t.Errorf("unable to get worker %d by hostname: %v", worker.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, worker) {
+ t.Errorf("GetWorkerForHostname() is %v, want %v", got, worker)
+ }
+ }
+ methods["GetWorkerForHostname"] = true
+
+ // update the workers
+ for _, worker := range resources.Workers {
+ worker.SetActive(false)
+ err = db.UpdateWorker(worker)
+ if err != nil {
+ t.Errorf("unable to update worker %d: %v", worker.GetID(), err)
+ }
+
+ // lookup the worker by ID
+ got, err := db.GetWorker(worker.GetID())
+ if err != nil {
+ t.Errorf("unable to get worker %d by ID: %v", worker.GetID(), err)
+ }
+ if !reflect.DeepEqual(got, worker) {
+ t.Errorf("GetWorker() is %v, want %v", got, worker)
+ }
+ }
+ methods["UpdateWorker"] = true
+ methods["GetWorker"] = true
+
+ // delete the workers
+ for _, worker := range resources.Workers {
+ err = db.DeleteWorker(worker)
+ if err != nil {
+ t.Errorf("unable to delete worker %d: %v", worker.GetID(), err)
+ }
+ }
+ methods["DeleteWorker"] = true
+
+ // ensure we called all the methods we expected to
+ for method, called := range methods {
+ if !called {
+ t.Errorf("method %s was not called for workers", method)
+ }
+ }
+}
+
+func newResources() *Resources {
+ buildOne := new(library.Build)
+ buildOne.SetID(1)
+ buildOne.SetRepoID(1)
+ buildOne.SetPipelineID(1)
+ buildOne.SetNumber(1)
+ buildOne.SetParent(1)
+ buildOne.SetEvent("push")
+ buildOne.SetEventAction("")
+ buildOne.SetStatus("running")
+ buildOne.SetError("")
+ buildOne.SetEnqueued(1563474077)
+ buildOne.SetCreated(1563474076)
+ buildOne.SetStarted(1563474078)
+ buildOne.SetFinished(1563474079)
+ buildOne.SetDeploy("")
+ buildOne.SetDeployPayload(raw.StringSliceMap{"foo": "test1"})
+ buildOne.SetClone("https://github.com/github/octocat.git")
+ buildOne.SetSource("https://github.com/github/octocat/deployments/1")
+ buildOne.SetTitle("push received from https://github.com/github/octocat")
+ buildOne.SetMessage("First commit...")
+ buildOne.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163")
+ buildOne.SetSender("OctoKitty")
+ buildOne.SetAuthor("OctoKitty")
+ buildOne.SetEmail("OctoKitty@github.com")
+ buildOne.SetLink("https://example.company.com/github/octocat/1")
+ buildOne.SetBranch("main")
+ buildOne.SetRef("refs/heads/main")
+ buildOne.SetBaseRef("")
+ buildOne.SetHeadRef("changes")
+ buildOne.SetHost("example.company.com")
+ buildOne.SetRuntime("docker")
+ buildOne.SetDistribution("linux")
+
+ buildTwo := new(library.Build)
+ buildTwo.SetID(2)
+ buildTwo.SetRepoID(1)
+ buildTwo.SetPipelineID(1)
+ buildTwo.SetNumber(2)
+ buildTwo.SetParent(1)
+ buildTwo.SetEvent("pull_request")
+ buildTwo.SetEventAction("")
+ buildTwo.SetStatus("running")
+ buildTwo.SetError("")
+ buildTwo.SetEnqueued(1563474077)
+ buildTwo.SetCreated(1563474076)
+ buildTwo.SetStarted(1563474078)
+ buildTwo.SetFinished(1563474079)
+ buildTwo.SetDeploy("")
+ buildTwo.SetDeployPayload(raw.StringSliceMap{"foo": "test1"})
+ buildTwo.SetClone("https://github.com/github/octocat.git")
+ buildTwo.SetSource("https://github.com/github/octocat/deployments/1")
+ buildTwo.SetTitle("pull_request received from https://github.com/github/octocat")
+ buildTwo.SetMessage("Second commit...")
+ buildTwo.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135164")
+ buildTwo.SetSender("OctoKitty")
+ buildTwo.SetAuthor("OctoKitty")
+ buildTwo.SetEmail("OctoKitty@github.com")
+ buildTwo.SetLink("https://example.company.com/github/octocat/2")
+ buildTwo.SetBranch("main")
+ buildTwo.SetRef("refs/heads/main")
+ buildTwo.SetBaseRef("")
+ buildTwo.SetHeadRef("changes")
+ buildTwo.SetHost("example.company.com")
+ buildTwo.SetRuntime("docker")
+ buildTwo.SetDistribution("linux")
+
+ executableOne := new(library.BuildExecutable)
+ executableOne.SetID(1)
+ executableOne.SetBuildID(1)
+ executableOne.SetData([]byte("foo"))
+
+ executableTwo := new(library.BuildExecutable)
+ executableTwo.SetID(2)
+ executableTwo.SetBuildID(2)
+ executableTwo.SetData([]byte("foo"))
+
+ deploymentOne := new(library.Deployment)
+ deploymentOne.SetID(1)
+ deploymentOne.SetRepoID(1)
+ deploymentOne.SetURL("https://github.com/github/octocat/deployments/1")
+ deploymentOne.SetUser("octocat")
+ deploymentOne.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163")
+ deploymentOne.SetRef("refs/heads/master")
+ deploymentOne.SetTask("vela-deploy")
+ deploymentOne.SetTarget("production")
+ deploymentOne.SetDescription("Deployment request from Vela")
+ deploymentOne.SetPayload(map[string]string{"foo": "test1"})
+
+ deploymentTwo := new(library.Deployment)
+ deploymentTwo.SetID(1)
+ deploymentTwo.SetRepoID(1)
+ deploymentTwo.SetURL("https://github.com/github/octocat/deployments/2")
+ deploymentTwo.SetUser("octocat")
+ deploymentTwo.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135164")
+ deploymentTwo.SetRef("refs/heads/master")
+ deploymentTwo.SetTask("vela-deploy")
+ deploymentTwo.SetTarget("production")
+ deploymentTwo.SetDescription("Deployment request from Vela")
+ deploymentTwo.SetPayload(map[string]string{"foo": "test1"})
+
+ hookOne := new(library.Hook)
+ hookOne.SetID(1)
+ hookOne.SetRepoID(1)
+ hookOne.SetBuildID(1)
+ hookOne.SetNumber(1)
+ hookOne.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
+ hookOne.SetCreated(time.Now().UTC().Unix())
+ hookOne.SetHost("github.com")
+ hookOne.SetEvent("push")
+ hookOne.SetEventAction("")
+ hookOne.SetBranch("main")
+ hookOne.SetError("")
+ hookOne.SetStatus("success")
+ hookOne.SetLink("https://github.com/github/octocat/settings/hooks/1")
+ hookOne.SetWebhookID(123456)
+
+ hookTwo := new(library.Hook)
+ hookTwo.SetID(2)
+ hookTwo.SetRepoID(1)
+ hookTwo.SetBuildID(1)
+ hookTwo.SetNumber(2)
+ hookTwo.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
+ hookTwo.SetCreated(time.Now().UTC().Unix())
+ hookTwo.SetHost("github.com")
+ hookTwo.SetEvent("push")
+ hookTwo.SetEventAction("")
+ hookTwo.SetBranch("main")
+ hookTwo.SetError("")
+ hookTwo.SetStatus("success")
+ hookTwo.SetLink("https://github.com/github/octocat/settings/hooks/1")
+ hookTwo.SetWebhookID(123456)
+
+ logServiceOne := new(library.Log)
+ logServiceOne.SetID(1)
+ logServiceOne.SetBuildID(1)
+ logServiceOne.SetRepoID(1)
+ logServiceOne.SetServiceID(1)
+ logServiceOne.SetStepID(0)
+ logServiceOne.SetData([]byte("foo"))
+
+ logServiceTwo := new(library.Log)
+ logServiceTwo.SetID(2)
+ logServiceTwo.SetBuildID(1)
+ logServiceTwo.SetRepoID(1)
+ logServiceTwo.SetServiceID(2)
+ logServiceTwo.SetStepID(0)
+ logServiceTwo.SetData([]byte("foo"))
+
+ logStepOne := new(library.Log)
+ logStepOne.SetID(3)
+ logStepOne.SetBuildID(1)
+ logStepOne.SetRepoID(1)
+ logStepOne.SetServiceID(0)
+ logStepOne.SetStepID(1)
+ logStepOne.SetData([]byte("foo"))
+
+ logStepTwo := new(library.Log)
+ logStepTwo.SetID(4)
+ logStepTwo.SetBuildID(1)
+ logStepTwo.SetRepoID(1)
+ logStepTwo.SetServiceID(0)
+ logStepTwo.SetStepID(2)
+ logStepTwo.SetData([]byte("foo"))
+
+ pipelineOne := new(library.Pipeline)
+ pipelineOne.SetID(1)
+ pipelineOne.SetRepoID(1)
+ pipelineOne.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163")
+ pipelineOne.SetFlavor("large")
+ pipelineOne.SetPlatform("docker")
+ pipelineOne.SetRef("refs/heads/main")
+ pipelineOne.SetType("yaml")
+ pipelineOne.SetVersion("1")
+ pipelineOne.SetExternalSecrets(false)
+ pipelineOne.SetInternalSecrets(false)
+ pipelineOne.SetServices(true)
+ pipelineOne.SetStages(false)
+ pipelineOne.SetSteps(true)
+ pipelineOne.SetTemplates(false)
+ pipelineOne.SetData([]byte("version: 1"))
+
+ pipelineTwo := new(library.Pipeline)
+ pipelineTwo.SetID(2)
+ pipelineTwo.SetRepoID(1)
+ pipelineTwo.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135164")
+ pipelineTwo.SetFlavor("large")
+ pipelineTwo.SetPlatform("docker")
+ pipelineTwo.SetRef("refs/heads/main")
+ pipelineTwo.SetType("yaml")
+ pipelineTwo.SetVersion("1")
+ pipelineTwo.SetExternalSecrets(false)
+ pipelineTwo.SetInternalSecrets(false)
+ pipelineTwo.SetServices(true)
+ pipelineTwo.SetStages(false)
+ pipelineTwo.SetSteps(true)
+ pipelineTwo.SetTemplates(false)
+ pipelineTwo.SetData([]byte("version: 1"))
+
+ repoOne := new(library.Repo)
+ repoOne.SetID(1)
+ repoOne.SetUserID(1)
+ repoOne.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy")
+ repoOne.SetOrg("github")
+ repoOne.SetName("octocat")
+ repoOne.SetFullName("github/octocat")
+ repoOne.SetLink("https://github.com/github/octocat")
+ repoOne.SetClone("https://github.com/github/octocat.git")
+ repoOne.SetBranch("main")
+ repoOne.SetTopics([]string{"cloud", "security"})
+ repoOne.SetBuildLimit(10)
+ repoOne.SetTimeout(30)
+ repoOne.SetCounter(0)
+ repoOne.SetVisibility("public")
+ repoOne.SetPrivate(false)
+ repoOne.SetTrusted(false)
+ repoOne.SetActive(true)
+ repoOne.SetAllowPull(false)
+ repoOne.SetAllowPush(true)
+ repoOne.SetAllowDeploy(false)
+ repoOne.SetAllowTag(false)
+ repoOne.SetAllowComment(false)
+ repoOne.SetPipelineType("")
+ repoOne.SetPreviousName("")
+
+ repoTwo := new(library.Repo)
+ repoTwo.SetID(2)
+ repoTwo.SetUserID(1)
+ repoTwo.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy")
+ repoTwo.SetOrg("github")
+ repoTwo.SetName("octokitty")
+ repoTwo.SetFullName("github/octokitty")
+ repoTwo.SetLink("https://github.com/github/octokitty")
+ repoTwo.SetClone("https://github.com/github/octokitty.git")
+ repoTwo.SetBranch("main")
+ repoTwo.SetTopics([]string{"cloud", "security"})
+ repoTwo.SetBuildLimit(10)
+ repoTwo.SetTimeout(30)
+ repoTwo.SetCounter(0)
+ repoTwo.SetVisibility("public")
+ repoTwo.SetPrivate(false)
+ repoTwo.SetTrusted(false)
+ repoTwo.SetActive(true)
+ repoTwo.SetAllowPull(false)
+ repoTwo.SetAllowPush(true)
+ repoTwo.SetAllowDeploy(false)
+ repoTwo.SetAllowTag(false)
+ repoTwo.SetAllowComment(false)
+ repoTwo.SetPipelineType("")
+ repoTwo.SetPreviousName("")
+
+ scheduleOne := new(library.Schedule)
+ scheduleOne.SetID(1)
+ scheduleOne.SetRepoID(1)
+ scheduleOne.SetActive(true)
+ scheduleOne.SetName("nightly")
+ scheduleOne.SetEntry("0 0 * * *")
+ scheduleOne.SetCreatedAt(time.Now().UTC().Unix())
+ scheduleOne.SetCreatedBy("octocat")
+ scheduleOne.SetUpdatedAt(time.Now().Add(time.Hour * 1).UTC().Unix())
+ scheduleOne.SetUpdatedBy("octokitty")
+ scheduleOne.SetScheduledAt(time.Now().Add(time.Hour * 2).UTC().Unix())
+ scheduleOne.SetBranch("main")
+
+ scheduleTwo := new(library.Schedule)
+ scheduleTwo.SetID(2)
+ scheduleTwo.SetRepoID(1)
+ scheduleTwo.SetActive(true)
+ scheduleTwo.SetName("hourly")
+ scheduleTwo.SetEntry("0 * * * *")
+ scheduleTwo.SetCreatedAt(time.Now().UTC().Unix())
+ scheduleTwo.SetCreatedBy("octocat")
+ scheduleTwo.SetUpdatedAt(time.Now().Add(time.Hour * 1).UTC().Unix())
+ scheduleTwo.SetUpdatedBy("octokitty")
+ scheduleTwo.SetScheduledAt(time.Now().Add(time.Hour * 2).UTC().Unix())
+ scheduleTwo.SetBranch("main")
+
+ secretOrg := new(library.Secret)
+ secretOrg.SetID(1)
+ secretOrg.SetOrg("github")
+ secretOrg.SetRepo("*")
+ secretOrg.SetTeam("")
+ secretOrg.SetName("foo")
+ secretOrg.SetValue("bar")
+ secretOrg.SetType("org")
+ secretOrg.SetImages([]string{"alpine"})
+ secretOrg.SetEvents([]string{"push", "tag", "deployment"})
+ secretOrg.SetAllowCommand(true)
+ secretOrg.SetCreatedAt(time.Now().UTC().Unix())
+ secretOrg.SetCreatedBy("octocat")
+ secretOrg.SetUpdatedAt(time.Now().Add(time.Hour * 1).UTC().Unix())
+ secretOrg.SetUpdatedBy("octokitty")
+
+ secretRepo := new(library.Secret)
+ secretRepo.SetID(2)
+ secretRepo.SetOrg("github")
+ secretRepo.SetRepo("octocat")
+ secretRepo.SetTeam("")
+ secretRepo.SetName("foo")
+ secretRepo.SetValue("bar")
+ secretRepo.SetType("repo")
+ secretRepo.SetImages([]string{"alpine"})
+ secretRepo.SetEvents([]string{"push", "tag", "deployment"})
+ secretRepo.SetAllowCommand(true)
+ secretRepo.SetCreatedAt(time.Now().UTC().Unix())
+ secretRepo.SetCreatedBy("octocat")
+ secretRepo.SetUpdatedAt(time.Now().Add(time.Hour * 1).UTC().Unix())
+ secretRepo.SetUpdatedBy("octokitty")
+
+ secretShared := new(library.Secret)
+ secretShared.SetID(3)
+ secretShared.SetOrg("github")
+ secretShared.SetRepo("")
+ secretShared.SetTeam("octocat")
+ secretShared.SetName("foo")
+ secretShared.SetValue("bar")
+ secretShared.SetType("shared")
+ secretShared.SetImages([]string{"alpine"})
+ secretShared.SetEvents([]string{"push", "tag", "deployment"})
+ secretShared.SetAllowCommand(true)
+ secretShared.SetCreatedAt(time.Now().UTC().Unix())
+ secretShared.SetCreatedBy("octocat")
+ secretShared.SetUpdatedAt(time.Now().Add(time.Hour * 1).UTC().Unix())
+ secretShared.SetUpdatedBy("octokitty")
+
+ serviceOne := new(library.Service)
+ serviceOne.SetID(1)
+ serviceOne.SetBuildID(1)
+ serviceOne.SetRepoID(1)
+ serviceOne.SetNumber(1)
+ serviceOne.SetName("init")
+ serviceOne.SetImage("#init")
+ serviceOne.SetStatus("running")
+ serviceOne.SetError("")
+ serviceOne.SetExitCode(0)
+ serviceOne.SetCreated(1563474076)
+ serviceOne.SetStarted(1563474078)
+ serviceOne.SetFinished(1563474079)
+ serviceOne.SetHost("example.company.com")
+ serviceOne.SetRuntime("docker")
+ serviceOne.SetDistribution("linux")
+
+ serviceTwo := new(library.Service)
+ serviceTwo.SetID(2)
+ serviceTwo.SetBuildID(1)
+ serviceTwo.SetRepoID(1)
+ serviceTwo.SetNumber(2)
+ serviceTwo.SetName("clone")
+ serviceTwo.SetImage("target/vela-git:v0.3.0")
+ serviceTwo.SetStatus("pending")
+ serviceTwo.SetError("")
+ serviceTwo.SetExitCode(0)
+ serviceTwo.SetCreated(1563474086)
+ serviceTwo.SetStarted(1563474088)
+ serviceTwo.SetFinished(1563474089)
+ serviceTwo.SetHost("example.company.com")
+ serviceTwo.SetRuntime("docker")
+ serviceTwo.SetDistribution("linux")
+
+ stepOne := new(library.Step)
+ stepOne.SetID(1)
+ stepOne.SetBuildID(1)
+ stepOne.SetRepoID(1)
+ stepOne.SetNumber(1)
+ stepOne.SetName("init")
+ stepOne.SetImage("#init")
+ stepOne.SetStage("init")
+ stepOne.SetStatus("running")
+ stepOne.SetError("")
+ stepOne.SetExitCode(0)
+ stepOne.SetCreated(1563474076)
+ stepOne.SetStarted(1563474078)
+ stepOne.SetFinished(1563474079)
+ stepOne.SetHost("example.company.com")
+ stepOne.SetRuntime("docker")
+ stepOne.SetDistribution("linux")
+
+ stepTwo := new(library.Step)
+ stepTwo.SetID(2)
+ stepTwo.SetBuildID(1)
+ stepTwo.SetRepoID(1)
+ stepTwo.SetNumber(2)
+ stepTwo.SetName("clone")
+ stepTwo.SetImage("target/vela-git:v0.3.0")
+ stepTwo.SetStage("init")
+ stepTwo.SetStatus("pending")
+ stepTwo.SetError("")
+ stepTwo.SetExitCode(0)
+ stepTwo.SetCreated(1563474086)
+ stepTwo.SetStarted(1563474088)
+ stepTwo.SetFinished(1563474089)
+ stepTwo.SetHost("example.company.com")
+ stepTwo.SetRuntime("docker")
+ stepTwo.SetDistribution("linux")
+
+ userOne := new(library.User)
+ userOne.SetID(1)
+ userOne.SetName("octocat")
+ userOne.SetToken("superSecretToken")
+ userOne.SetRefreshToken("superSecretRefreshToken")
+ userOne.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy")
+ userOne.SetFavorites([]string{"github/octocat"})
+ userOne.SetActive(true)
+ userOne.SetAdmin(false)
+
+ userTwo := new(library.User)
+ userTwo.SetID(2)
+ userTwo.SetName("octokitty")
+ userTwo.SetToken("superSecretToken")
+ userTwo.SetRefreshToken("superSecretRefreshToken")
+ userTwo.SetHash("MzM4N2MzMDAtNmY4Mi00OTA5LWFhZDAtNWIzMTlkNTJkODMy")
+ userTwo.SetFavorites([]string{"github/octocat"})
+ userTwo.SetActive(true)
+ userTwo.SetAdmin(false)
+
+ workerOne := new(library.Worker)
+ workerOne.SetID(1)
+ workerOne.SetHostname("worker-1.example.com")
+ workerOne.SetAddress("https://worker-1.example.com")
+ workerOne.SetRoutes([]string{"vela"})
+ workerOne.SetActive(true)
+ workerOne.SetStatus("available")
+ workerOne.SetLastStatusUpdateAt(time.Now().UTC().Unix())
+ workerOne.SetRunningBuildIDs([]string{"12345"})
+ workerOne.SetLastBuildStartedAt(time.Now().UTC().Unix())
+ workerOne.SetLastBuildFinishedAt(time.Now().UTC().Unix())
+ workerOne.SetLastCheckedIn(time.Now().UTC().Unix())
+ workerOne.SetBuildLimit(1)
+
+ workerTwo := new(library.Worker)
+ workerTwo.SetID(2)
+ workerTwo.SetHostname("worker-2.example.com")
+ workerTwo.SetAddress("https://worker-2.example.com")
+ workerTwo.SetRoutes([]string{"vela"})
+ workerTwo.SetActive(true)
+ workerTwo.SetStatus("available")
+ workerTwo.SetLastStatusUpdateAt(time.Now().UTC().Unix())
+ workerTwo.SetRunningBuildIDs([]string{"12345"})
+ workerTwo.SetLastBuildStartedAt(time.Now().UTC().Unix())
+ workerTwo.SetLastBuildFinishedAt(time.Now().UTC().Unix())
+ workerTwo.SetLastCheckedIn(time.Now().UTC().Unix())
+ workerTwo.SetBuildLimit(1)
+
+ return &Resources{
+ Builds: []*library.Build{buildOne, buildTwo},
+ Deployments: []*library.Deployment{deploymentOne, deploymentTwo},
+ Executables: []*library.BuildExecutable{executableOne, executableTwo},
+ Hooks: []*library.Hook{hookOne, hookTwo},
+ Logs: []*library.Log{logServiceOne, logServiceTwo, logStepOne, logStepTwo},
+ Pipelines: []*library.Pipeline{pipelineOne, pipelineTwo},
+ Repos: []*library.Repo{repoOne, repoTwo},
+ Schedules: []*library.Schedule{scheduleOne, scheduleTwo},
+ Secrets: []*library.Secret{secretOrg, secretRepo, secretShared},
+ Services: []*library.Service{serviceOne, serviceTwo},
+ Steps: []*library.Step{stepOne, stepTwo},
+ Users: []*library.User{userOne, userTwo},
+ Workers: []*library.Worker{workerOne, workerTwo},
+ }
+}
diff --git a/database/interface.go b/database/interface.go
new file mode 100644
index 000000000..cc7428378
--- /dev/null
+++ b/database/interface.go
@@ -0,0 +1,72 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package database
+
+import (
+ "github.com/go-vela/server/database/build"
+ "github.com/go-vela/server/database/executable"
+ "github.com/go-vela/server/database/hook"
+ "github.com/go-vela/server/database/log"
+ "github.com/go-vela/server/database/pipeline"
+ "github.com/go-vela/server/database/repo"
+ "github.com/go-vela/server/database/schedule"
+ "github.com/go-vela/server/database/secret"
+ "github.com/go-vela/server/database/service"
+ "github.com/go-vela/server/database/step"
+ "github.com/go-vela/server/database/user"
+ "github.com/go-vela/server/database/worker"
+)
+
+// Interface represents the interface for integrating with the supported database providers.
+type Interface interface {
+ // Generic Interface Functions
+
+ // Close defines a function that stops and terminates the connection to the database.
+ Close() error
+
+ // Driver defines a function that outputs the configured database driver.
+ Driver() string
+
+ // Ping defines a function that sends a "ping" request to the configured database.
+ Ping() error
+
+ // Resource Interface Functions
+
+ // BuildInterface defines the interface for builds stored in the database.
+ build.BuildInterface
+
+ // BuildExecutableInterface defines the interface for build executables stored in the database.
+ executable.BuildExecutableInterface
+
+ // HookInterface defines the interface for hooks stored in the database.
+ hook.HookInterface
+
+ // LogInterface defines the interface for logs stored in the database.
+ log.LogInterface
+
+ // PipelineInterface defines the interface for pipelines stored in the database.
+ pipeline.PipelineInterface
+
+ // RepoInterface defines the interface for repos stored in the database.
+ repo.RepoInterface
+
+ // ScheduleInterface defines the interface for schedules stored in the database.
+ schedule.ScheduleInterface
+
+ // SecretInterface defines the interface for secrets stored in the database.
+ secret.SecretInterface
+
+ // ServiceInterface defines the interface for services stored in the database.
+ service.ServiceInterface
+
+ // StepInterface defines the interface for steps stored in the database.
+ step.StepInterface
+
+ // UserInterface defines the interface for users stored in the database.
+ user.UserInterface
+
+ // WorkerInterface defines the interface for workers stored in the database.
+ worker.WorkerInterface
+}
diff --git a/database/log/count.go b/database/log/count.go
new file mode 100644
index 000000000..e4ec570fe
--- /dev/null
+++ b/database/log/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 log
+
+import (
+ "github.com/go-vela/types/constants"
+)
+
+// CountLogs gets the count of all logs from the database.
+func (e *engine) CountLogs() (int64, error) {
+ e.logger.Tracef("getting count of all logs from the database")
+
+ // variable to store query results
+ var l int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableLog).
+ Count(&l).
+ Error
+
+ return l, err
+}
diff --git a/database/log/count_build.go b/database/log/count_build.go
new file mode 100644
index 000000000..6a7fb74fb
--- /dev/null
+++ b/database/log/count_build.go
@@ -0,0 +1,27 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+)
+
+// CountLogsForBuild gets the count of logs by build ID from the database.
+func (e *engine) CountLogsForBuild(b *library.Build) (int64, error) {
+ e.logger.Tracef("getting count of logs for build %d from the database", b.GetID())
+
+ // variable to store query results
+ var l int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableLog).
+ Where("build_id = ?", b.GetID()).
+ Count(&l).
+ Error
+
+ return l, err
+}
diff --git a/database/log/count_build_test.go b/database/log/count_build_test.go
new file mode 100644
index 000000000..d462eedf0
--- /dev/null
+++ b/database/log/count_build_test.go
@@ -0,0 +1,99 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestLog_Engine_CountLogsForBuild(t *testing.T) {
+ // setup types
+ _service := testLog()
+ _service.SetID(1)
+ _service.SetRepoID(1)
+ _service.SetBuildID(1)
+ _service.SetServiceID(1)
+
+ _step := testLog()
+ _step.SetID(2)
+ _step.SetRepoID(1)
+ _step.SetBuildID(1)
+ _step.SetStepID(1)
+
+ _build := testBuild()
+ _build.SetID(1)
+ _build.SetID(1)
+ _build.SetRepoID(1)
+ _build.SetNumber(1)
+
+ _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 "logs" WHERE build_id = $1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateLog(_service)
+ if err != nil {
+ t.Errorf("unable to create test service log for sqlite: %v", err)
+ }
+
+ err = _sqlite.CreateLog(_step)
+ if err != nil {
+ t.Errorf("unable to create test step log 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.CountLogsForBuild(_build)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountLogsForBuild for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountLogsForBuild for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountLogsForBuild for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/log/count_test.go b/database/log/count_test.go
new file mode 100644
index 000000000..99e75a767
--- /dev/null
+++ b/database/log/count_test.go
@@ -0,0 +1,93 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestLog_Engine_CountLogs(t *testing.T) {
+ // setup types
+ _service := testLog()
+ _service.SetID(1)
+ _service.SetRepoID(1)
+ _service.SetBuildID(1)
+ _service.SetServiceID(1)
+
+ _step := testLog()
+ _step.SetID(2)
+ _step.SetRepoID(1)
+ _step.SetBuildID(1)
+ _step.SetStepID(1)
+
+ _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 "logs"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateLog(_service)
+ if err != nil {
+ t.Errorf("unable to create test service log for sqlite: %v", err)
+ }
+
+ err = _sqlite.CreateLog(_step)
+ if err != nil {
+ t.Errorf("unable to create test step log 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.CountLogs()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountLogs for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountLogs for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountLogs for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/log/create.go b/database/log/create.go
new file mode 100644
index 000000000..978da9a1f
--- /dev/null
+++ b/database/log/create.go
@@ -0,0 +1,57 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code with create.go
+package log
+
+import (
+ "fmt"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// CreateLog creates a new log in the database.
+func (e *engine) CreateLog(l *library.Log) error {
+ // check what the log entry is for
+ switch {
+ case l.GetServiceID() > 0:
+ e.logger.Tracef("creating log for service %d for build %d in the database", l.GetServiceID(), l.GetBuildID())
+ case l.GetStepID() > 0:
+ e.logger.Tracef("creating log for step %d for build %d in the database", l.GetStepID(), l.GetBuildID())
+ }
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#LogFromLibrary
+ log := database.LogFromLibrary(l)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Log.Validate
+ err := log.Validate()
+ if err != nil {
+ return err
+ }
+
+ // compress log data for the resource
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Log.Compress
+ err = log.Compress(e.config.CompressionLevel)
+ if err != nil {
+ switch {
+ case l.GetServiceID() > 0:
+ return fmt.Errorf("unable to compress log for service %d for build %d: %w", l.GetServiceID(), l.GetBuildID(), err)
+ case l.GetStepID() > 0:
+ return fmt.Errorf("unable to compress log for step %d for build %d: %w", l.GetStepID(), l.GetBuildID(), err)
+ }
+ }
+
+ // send query to the database
+ return e.client.
+ Table(constants.TableLog).
+ Create(log).
+ Error
+}
diff --git a/database/log/create_test.go b/database/log/create_test.go
new file mode 100644
index 000000000..1574e88a8
--- /dev/null
+++ b/database/log/create_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 log
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestLog_Engine_CreateLog(t *testing.T) {
+ // setup types
+ _service := testLog()
+ _service.SetID(1)
+ _service.SetRepoID(1)
+ _service.SetBuildID(1)
+ _service.SetServiceID(1)
+
+ _step := testLog()
+ _step.SetID(2)
+ _step.SetRepoID(1)
+ _step.SetBuildID(1)
+ _step.SetStepID(1)
+
+ _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 service query
+ _mock.ExpectQuery(`INSERT INTO "logs"
+("build_id","repo_id","service_id","step_id","data","id")
+VALUES ($1,$2,$3,$4,$5,$6) RETURNING "id"`).
+ WithArgs(1, 1, 1, nil, AnyArgument{}, 1).
+ WillReturnRows(_rows)
+
+ // ensure the mock expects the step query
+ _mock.ExpectQuery(`INSERT INTO "logs"
+("build_id","repo_id","service_id","step_id","data","id")
+VALUES ($1,$2,$3,$4,$5,$6) RETURNING "id"`).
+ WithArgs(1, 1, nil, 1, AnyArgument{}, 2).
+ WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ logs []*library.Log
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ logs: []*library.Log{_service, _step},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ logs: []*library.Log{_service, _step},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ for _, log := range test.logs {
+ err := test.database.CreateLog(log)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateLog for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateLog for %s returned err: %v", test.name, err)
+ }
+ }
+ })
+ }
+}
diff --git a/database/log/delete.go b/database/log/delete.go
new file mode 100644
index 000000000..255e6213b
--- /dev/null
+++ b/database/log/delete.go
@@ -0,0 +1,33 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// DeleteLog deletes an existing log from the database.
+func (e *engine) DeleteLog(l *library.Log) error {
+ // check what the log entry is for
+ switch {
+ case l.GetServiceID() > 0:
+ e.logger.Tracef("deleting log for service %d for build %d in the database", l.GetServiceID(), l.GetBuildID())
+ case l.GetStepID() > 0:
+ e.logger.Tracef("deleting log for step %d for build %d in the database", l.GetStepID(), l.GetBuildID())
+ }
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#LogFromLibrary
+ log := database.LogFromLibrary(l)
+
+ // send query to the database
+ return e.client.
+ Table(constants.TableLog).
+ Delete(log).
+ Error
+}
diff --git a/database/log/delete_test.go b/database/log/delete_test.go
new file mode 100644
index 000000000..15329a0af
--- /dev/null
+++ b/database/log/delete_test.go
@@ -0,0 +1,73 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestLog_Engine_DeleteLog(t *testing.T) {
+ // setup types
+ _log := testLog()
+ _log.SetID(1)
+ _log.SetRepoID(1)
+ _log.SetBuildID(1)
+ _log.SetStepID(1)
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`DELETE FROM "logs" WHERE "logs"."id" = $1`).
+ WithArgs(1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateLog(_log)
+ if err != nil {
+ t.Errorf("unable to create test log 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.DeleteLog(_log)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("DeleteLog for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("DeleteLog for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/log/get.go b/database/log/get.go
new file mode 100644
index 000000000..d31c6e1ef
--- /dev/null
+++ b/database/log/get.go
@@ -0,0 +1,48 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// GetLog gets a log by ID from the database.
+func (e *engine) GetLog(id int64) (*library.Log, error) {
+ e.logger.Tracef("getting log %d from the database", id)
+
+ // variable to store query results
+ l := new(database.Log)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableLog).
+ Where("id = ?", id).
+ Take(l).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // decompress log data
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Log.Decompress
+ err = l.Decompress()
+ if err != nil {
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch uncompressed logs
+ e.logger.Errorf("unable to decompress log %d: %v", id, err)
+
+ // return the uncompressed log
+ return l.ToLibrary(), nil
+ }
+
+ // return the log
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Log.ToLibrary
+ return l.ToLibrary(), nil
+}
diff --git a/database/log/get_service.go b/database/log/get_service.go
new file mode 100644
index 000000000..38cf38a45
--- /dev/null
+++ b/database/log/get_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.
+
+//nolint:dupl // ignore similar code with get_step.go
+package log
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// GetLogForService gets a log by service ID from the database.
+func (e *engine) GetLogForService(s *library.Service) (*library.Log, error) {
+ e.logger.Tracef("getting log for service %d for build %d from the database", s.GetID(), s.GetBuildID())
+
+ // variable to store query results
+ l := new(database.Log)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableLog).
+ Where("service_id = ?", s.GetID()).
+ Take(l).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // decompress log data for the service
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Log.Decompress
+ err = l.Decompress()
+ if err != nil {
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch uncompressed logs
+ e.logger.Errorf("unable to decompress log for service %d for build %d: %v", s.GetID(), s.GetBuildID(), err)
+
+ // return the uncompressed log
+ return l.ToLibrary(), nil
+ }
+
+ // return the log
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Log.ToLibrary
+ return l.ToLibrary(), nil
+}
diff --git a/database/log/get_service_test.go b/database/log/get_service_test.go
new file mode 100644
index 000000000..26f42813c
--- /dev/null
+++ b/database/log/get_service_test.go
@@ -0,0 +1,93 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestLog_Engine_GetLogForService(t *testing.T) {
+ // setup types
+ _log := testLog()
+ _log.SetID(1)
+ _log.SetRepoID(1)
+ _log.SetBuildID(1)
+ _log.SetServiceID(1)
+ _log.SetData([]byte{})
+
+ _service := testService()
+ _service.SetID(1)
+ _service.SetID(1)
+ _service.SetRepoID(1)
+ _service.SetBuildID(1)
+ _service.SetNumber(1)
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "build_id", "repo_id", "service_id", "step_id", "data"}).
+ AddRow(1, 1, 1, 1, 0, []byte{})
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "logs" WHERE service_id = $1 LIMIT 1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateLog(_log)
+ if err != nil {
+ t.Errorf("unable to create test log for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Log
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _log,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _log,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetLogForService(_service)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetLogForService for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetLogForService for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetLogForService for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/log/get_step.go b/database/log/get_step.go
new file mode 100644
index 000000000..92e503852
--- /dev/null
+++ b/database/log/get_step.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.
+
+//nolint:dupl // ignore similar code with get_service.go
+package log
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// GetLogForStep gets a log by step ID from the database.
+func (e *engine) GetLogForStep(s *library.Step) (*library.Log, error) {
+ e.logger.Tracef("getting log for step %d for build %d from the database", s.GetID(), s.GetBuildID())
+
+ // variable to store query results
+ l := new(database.Log)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableLog).
+ Where("step_id = ?", s.GetID()).
+ Take(l).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // decompress log data for the step
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Log.Decompress
+ err = l.Decompress()
+ if err != nil {
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch uncompressed logs
+ e.logger.Errorf("unable to decompress log for step %d for build %d: %v", s.GetID(), s.GetBuildID(), err)
+
+ // return the uncompressed log
+ return l.ToLibrary(), nil
+ }
+
+ // return the log
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Log.ToLibrary
+ return l.ToLibrary(), nil
+}
diff --git a/database/log/get_step_test.go b/database/log/get_step_test.go
new file mode 100644
index 000000000..39f019039
--- /dev/null
+++ b/database/log/get_step_test.go
@@ -0,0 +1,93 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestLog_Engine_GetLogForStep(t *testing.T) {
+ // setup types
+ _log := testLog()
+ _log.SetID(1)
+ _log.SetRepoID(1)
+ _log.SetBuildID(1)
+ _log.SetStepID(1)
+ _log.SetData([]byte{})
+
+ _step := testStep()
+ _step.SetID(1)
+ _step.SetID(1)
+ _step.SetRepoID(1)
+ _step.SetBuildID(1)
+ _step.SetNumber(1)
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "build_id", "repo_id", "step_id", "step_id", "data"}).
+ AddRow(1, 1, 1, 0, 1, []byte{})
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "logs" WHERE step_id = $1 LIMIT 1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateLog(_log)
+ if err != nil {
+ t.Errorf("unable to create test log for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Log
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _log,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _log,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetLogForStep(_step)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetLogForStep for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetLogForStep for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetLogForStep for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/log/get_test.go b/database/log/get_test.go
new file mode 100644
index 000000000..31325e3ac
--- /dev/null
+++ b/database/log/get_test.go
@@ -0,0 +1,86 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestLog_Engine_GetLog(t *testing.T) {
+ // setup types
+ _log := testLog()
+ _log.SetID(1)
+ _log.SetRepoID(1)
+ _log.SetBuildID(1)
+ _log.SetStepID(1)
+ _log.SetData([]byte{})
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "build_id", "repo_id", "service_id", "step_id", "data"}).
+ AddRow(1, 1, 1, 0, 1, []byte{})
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "logs" WHERE id = $1 LIMIT 1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateLog(_log)
+ if err != nil {
+ t.Errorf("unable to create test log for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Log
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _log,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _log,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetLog(1)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetLog for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetLog for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetLog for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/log/index.go b/database/log/index.go
new file mode 100644
index 000000000..2ad50b642
--- /dev/null
+++ b/database/log/index.go
@@ -0,0 +1,24 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+const (
+ // CreateBuildIDIndex represents a query to create an
+ // index on the logs table for the build_id column.
+ CreateBuildIDIndex = `
+CREATE INDEX
+IF NOT EXISTS
+logs_build_id
+ON logs (build_id);
+`
+)
+
+// CreateLogIndexes creates the indexes for the logs table in the database.
+func (e *engine) CreateLogIndexes() error {
+ e.logger.Tracef("creating indexes for logs table in the database")
+
+ // create the build_id column index for the logs table
+ return e.client.Exec(CreateBuildIDIndex).Error
+}
diff --git a/database/log/index_test.go b/database/log/index_test.go
new file mode 100644
index 000000000..26c0045db
--- /dev/null
+++ b/database/log/index_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 log
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestLog_Engine_CreateLogIndexes(t *testing.T) {
+ // setup types
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ _mock.ExpectExec(CreateBuildIDIndex).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.CreateLogIndexes()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateLogIndexes for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateLogIndexes for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/log/interface.go b/database/log/interface.go
new file mode 100644
index 000000000..8c72a5098
--- /dev/null
+++ b/database/log/interface.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 log
+
+import (
+ "github.com/go-vela/types/library"
+)
+
+// LogInterface represents the Vela interface for log
+// functions with the supported Database backends.
+//
+//nolint:revive // ignore name stutter
+type LogInterface interface {
+ // Log Data Definition Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_definition_language
+
+ // CreateLogIndexes defines a function that creates the indexes for the logs table.
+ CreateLogIndexes() error
+ // CreateLogTable defines a function that creates the logs table.
+ CreateLogTable(string) error
+
+ // Log Data Manipulation Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_manipulation_language
+
+ // CountLogs defines a function that gets the count of all logs.
+ CountLogs() (int64, error)
+ // CountLogsForBuild defines a function that gets the count of logs by build ID.
+ CountLogsForBuild(*library.Build) (int64, error)
+ // CreateLog defines a function that creates a new log.
+ CreateLog(*library.Log) error
+ // DeleteLog defines a function that deletes an existing log.
+ DeleteLog(*library.Log) error
+ // GetLog defines a function that gets a log by ID.
+ GetLog(int64) (*library.Log, error)
+ // GetLogForService defines a function that gets a log by service ID.
+ GetLogForService(*library.Service) (*library.Log, error)
+ // GetLogForStep defines a function that gets a log by step ID.
+ GetLogForStep(*library.Step) (*library.Log, error)
+ // ListLogs defines a function that gets a list of all logs.
+ ListLogs() ([]*library.Log, error)
+ // ListLogsForBuild defines a function that gets a list of logs by build ID.
+ ListLogsForBuild(*library.Build, int, int) ([]*library.Log, int64, error)
+ // UpdateLog defines a function that updates an existing log.
+ UpdateLog(*library.Log) error
+}
diff --git a/database/log/list.go b/database/log/list.go
new file mode 100644
index 000000000..6a5381278
--- /dev/null
+++ b/database/log/list.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 log
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// ListLogs gets a list of all logs from the database.
+func (e *engine) ListLogs() ([]*library.Log, error) {
+ e.logger.Trace("listing all logs from the database")
+
+ // variables to store query results and return value
+ count := int64(0)
+ l := new([]database.Log)
+ logs := []*library.Log{}
+
+ // count the results
+ count, err := e.CountLogs()
+ if err != nil {
+ return nil, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return logs, nil
+ }
+
+ // send query to the database and store result in variable
+ err = e.client.
+ Table(constants.TableLog).
+ Find(&l).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // iterate through all query results
+ for _, log := range *l {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := log
+
+ // decompress log data
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Log.Decompress
+ err = tmp.Decompress()
+ if err != nil {
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch uncompressed logs
+ e.logger.Errorf("unable to decompress logs: %v", err)
+ }
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Log.ToLibrary
+ logs = append(logs, tmp.ToLibrary())
+ }
+
+ return logs, nil
+}
diff --git a/database/log/list_build.go b/database/log/list_build.go
new file mode 100644
index 000000000..ab083a706
--- /dev/null
+++ b/database/log/list_build.go
@@ -0,0 +1,73 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// ListLogsForBuild gets a list of logs by build ID from the database.
+func (e *engine) ListLogsForBuild(b *library.Build, page, perPage int) ([]*library.Log, int64, error) {
+ e.logger.Tracef("listing logs for build %d from the database", b.GetID())
+
+ // variables to store query results and return value
+ count := int64(0)
+ l := new([]database.Log)
+ logs := []*library.Log{}
+
+ // count the results
+ count, err := e.CountLogsForBuild(b)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return logs, 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.TableLog).
+ Where("build_id = ?", b.GetID()).
+ Order("service_id ASC NULLS LAST").
+ Order("step_id ASC").
+ Limit(perPage).
+ Offset(offset).
+ Find(&l).
+ Error
+ if err != nil {
+ return nil, count, err
+ }
+
+ // iterate through all query results
+ for _, log := range *l {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := log
+
+ // decompress log data for the build
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Log.Decompress
+ err = tmp.Decompress()
+ if err != nil {
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch uncompressed logs
+ e.logger.Errorf("unable to decompress logs for build %d: %v", b.GetID(), err)
+ }
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Log.ToLibrary
+ logs = append(logs, tmp.ToLibrary())
+ }
+
+ return logs, count, nil
+}
diff --git a/database/log/list_build_test.go b/database/log/list_build_test.go
new file mode 100644
index 000000000..cf20fb50e
--- /dev/null
+++ b/database/log/list_build_test.go
@@ -0,0 +1,110 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestLog_Engine_ListLogsForBuild(t *testing.T) {
+ // setup types
+ _service := testLog()
+ _service.SetID(1)
+ _service.SetRepoID(1)
+ _service.SetBuildID(1)
+ _service.SetServiceID(1)
+ _service.SetData([]byte{})
+
+ _step := testLog()
+ _step.SetID(2)
+ _step.SetRepoID(1)
+ _step.SetBuildID(1)
+ _step.SetStepID(1)
+ _step.SetData([]byte{})
+
+ _build := testBuild()
+ _build.SetID(1)
+ _build.SetID(1)
+ _build.SetRepoID(1)
+ _build.SetNumber(1)
+
+ _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 "logs" WHERE build_id = $1`).WithArgs(1).WillReturnRows(_rows)
+
+ // create expected result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "build_id", "repo_id", "service_id", "step_id", "data"}).
+ AddRow(1, 1, 1, 1, 0, []byte{}).AddRow(2, 1, 1, 0, 1, []byte{})
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "logs" WHERE build_id = $1 ORDER BY service_id ASC NULLS LAST,step_id ASC LIMIT 10`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateLog(_service)
+ if err != nil {
+ t.Errorf("unable to create test service log for sqlite: %v", err)
+ }
+
+ err = _sqlite.CreateLog(_step)
+ if err != nil {
+ t.Errorf("unable to create test step log for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Log
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Log{_service, _step},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.Log{_service, _step},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, _, err := test.database.ListLogsForBuild(_build, 1, 10)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListLogsForBuild for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListLogsForBuild for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListLogsForBuild for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/log/list_test.go b/database/log/list_test.go
new file mode 100644
index 000000000..0cf420255
--- /dev/null
+++ b/database/log/list_test.go
@@ -0,0 +1,104 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestLog_Engine_ListLogs(t *testing.T) {
+ // setup types
+ _service := testLog()
+ _service.SetID(1)
+ _service.SetRepoID(1)
+ _service.SetBuildID(1)
+ _service.SetServiceID(1)
+ _service.SetData([]byte{})
+
+ _step := testLog()
+ _step.SetID(2)
+ _step.SetRepoID(1)
+ _step.SetBuildID(1)
+ _step.SetStepID(1)
+ _step.SetData([]byte{})
+
+ _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 "logs"`).WillReturnRows(_rows)
+
+ // create expected result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "build_id", "repo_id", "service_id", "step_id", "data"}).
+ AddRow(1, 1, 1, 1, 0, []byte{}).AddRow(2, 1, 1, 0, 1, []byte{})
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "logs"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateLog(_service)
+ if err != nil {
+ t.Errorf("unable to create test service log for sqlite: %v", err)
+ }
+
+ err = _sqlite.CreateLog(_step)
+ if err != nil {
+ t.Errorf("unable to create test step log for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Log
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Log{_service, _step},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.Log{_service, _step},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.ListLogs()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListLogs for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListLogs for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListLogs for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/log/log.go b/database/log/log.go
new file mode 100644
index 000000000..35d25400f
--- /dev/null
+++ b/database/log/log.go
@@ -0,0 +1,82 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+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 LogInterface interface.
+ config struct {
+ // specifies the level of compression to use for the Log engine
+ CompressionLevel int
+ // specifies to skip creating tables and indexes for the Log engine
+ SkipCreation bool
+ }
+
+ // engine represents the log functionality that implements the LogInterface interface.
+ engine struct {
+ // engine configuration settings used in log functions
+ config *config
+
+ // gorm.io/gorm database client used in log functions
+ //
+ // https://pkg.go.dev/gorm.io/gorm#DB
+ client *gorm.DB
+
+ // sirupsen/logrus logger used in log functions
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus#Entry
+ logger *logrus.Entry
+ }
+)
+
+// New creates and returns a Vela service for integrating with logs in the database.
+//
+//nolint:revive // ignore returning unexported engine
+func New(opts ...EngineOpt) (*engine, error) {
+ // create new Log 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 log database objects
+ if e.config.SkipCreation {
+ e.logger.Warning("skipping creation of logs table and indexes in the database")
+
+ return e, nil
+ }
+
+ // create the logs table
+ err := e.CreateLogTable(e.client.Config.Dialector.Name())
+ if err != nil {
+ return nil, fmt.Errorf("unable to create %s table: %w", constants.TableLog, err)
+ }
+
+ // create the indexes for the logs table
+ err = e.CreateLogIndexes()
+ if err != nil {
+ return nil, fmt.Errorf("unable to create indexes for %s table: %w", constants.TableLog, err)
+ }
+
+ return e, nil
+}
diff --git a/database/log/log_test.go b/database/log/log_test.go
new file mode 100644
index 000000000..69e3835d5
--- /dev/null
+++ b/database/log/log_test.go
@@ -0,0 +1,276 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+import (
+ "database/sql/driver"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/driver/postgres"
+ "gorm.io/driver/sqlite"
+ "gorm.io/gorm"
+)
+
+func TestLog_New(t *testing.T) {
+ // setup types
+ logger := logrus.NewEntry(logrus.StandardLogger())
+
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+ defer _sql.Close()
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateBuildIDIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _config := &gorm.Config{SkipDefaultTransaction: true}
+
+ _postgres, err := gorm.Open(postgres.New(postgres.Config{Conn: _sql}), _config)
+ if err != nil {
+ t.Errorf("unable to create new postgres database: %v", err)
+ }
+
+ _sqlite, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), _config)
+ if err != nil {
+ t.Errorf("unable to create new sqlite database: %v", err)
+ }
+
+ defer func() { _sql, _ := _sqlite.DB(); _sql.Close() }()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ client *gorm.DB
+ key string
+ logger *logrus.Entry
+ skipCreation bool
+ want *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ client: _postgres,
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ client: _postgres,
+ config: &config{SkipCreation: false},
+ logger: logger,
+ },
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ client: _sqlite,
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ client: _sqlite,
+ config: &config{SkipCreation: false},
+ logger: logger,
+ },
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := New(
+ WithClient(test.client),
+ WithLogger(test.logger),
+ WithSkipCreation(test.skipCreation),
+ )
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("New for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("New for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("New for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
+
+// testPostgres is a helper function to create a Postgres engine for testing.
+func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) {
+ // create the new mock sql database
+ //
+ // https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#New
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateBuildIDIndex).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 log 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 log engine: %v", err)
+ }
+
+ return _engine
+}
+
+// This will be used with the github.com/DATA-DOG/go-sqlmock
+// library to compare values that are otherwise not easily
+// compared. These typically would be values generated before
+// adding or updating them in the database.
+//
+// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime
+type AnyArgument struct{}
+
+// Match satisfies sqlmock.Argument interface.
+func (a AnyArgument) Match(v driver.Value) bool {
+ return true
+}
+
+// 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),
+ }
+}
+
+// testLog is a test helper function to create a library
+// Log type with all fields set to their zero values.
+func testLog() *library.Log {
+ return &library.Log{
+ ID: new(int64),
+ RepoID: new(int64),
+ BuildID: new(int64),
+ ServiceID: new(int64),
+ StepID: new(int64),
+ Data: new([]byte),
+ }
+}
+
+// testService is a test helper function to create a library
+// Service type with all fields set to their zero values.
+func testService() *library.Service {
+ return &library.Service{
+ ID: new(int64),
+ BuildID: new(int64),
+ RepoID: new(int64),
+ Number: new(int),
+ Name: new(string),
+ Image: 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),
+ }
+}
+
+// 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/log/opts.go b/database/log/opts.go
new file mode 100644
index 000000000..ea1897ba9
--- /dev/null
+++ b/database/log/opts.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 log
+
+import (
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+// EngineOpt represents a configuration option to initialize the database engine for Logs.
+type EngineOpt func(*engine) error
+
+// WithClient sets the gorm.io/gorm client in the database engine for Logs.
+func WithClient(client *gorm.DB) EngineOpt {
+ return func(e *engine) error {
+ // set the gorm.io/gorm client in the log engine
+ e.client = client
+
+ return nil
+ }
+}
+
+// WithCompressionLevel sets the compression level in the database engine for Logs.
+func WithCompressionLevel(level int) EngineOpt {
+ return func(e *engine) error {
+ // set the compression level in the log engine
+ e.config.CompressionLevel = level
+
+ return nil
+ }
+}
+
+// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for Logs.
+func WithLogger(logger *logrus.Entry) EngineOpt {
+ return func(e *engine) error {
+ // set the github.com/sirupsen/logrus logger in the log engine
+ e.logger = logger
+
+ return nil
+ }
+}
+
+// WithSkipCreation sets the skip creation logic in the database engine for Logs.
+func WithSkipCreation(skipCreation bool) EngineOpt {
+ return func(e *engine) error {
+ // set to skip creating tables and indexes in the log engine
+ e.config.SkipCreation = skipCreation
+
+ return nil
+ }
+}
diff --git a/database/log/opts_test.go b/database/log/opts_test.go
new file mode 100644
index 000000000..c35dbaa48
--- /dev/null
+++ b/database/log/opts_test.go
@@ -0,0 +1,216 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+func TestLog_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 TestLog_EngineOpt_WithCompressionLevel(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ level int
+ want int
+ }{
+ {
+ failure: false,
+ name: "compression level set to -1",
+ level: -1,
+ want: -1,
+ },
+ {
+ failure: false,
+ name: "compression level set to 0",
+ level: 0,
+ want: 0,
+ },
+ {
+ failure: false,
+ name: "compression level set to 1",
+ level: 1,
+ want: 1,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithCompressionLevel(test.level)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithCompressionLevel for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithCompressionLevel returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.config.CompressionLevel, test.want) {
+ t.Errorf("WithCompressionLevel is %v, want %v", e.config.CompressionLevel, test.want)
+ }
+ })
+ }
+}
+
+func TestLog_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 TestLog_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/log/table.go b/database/log/table.go
new file mode 100644
index 000000000..2d5e5e0c5
--- /dev/null
+++ b/database/log/table.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+import (
+ "github.com/go-vela/types/constants"
+)
+
+const (
+ // CreatePostgresTable represents a query to create the Postgres logs table.
+ CreatePostgresTable = `
+CREATE TABLE
+IF NOT EXISTS
+logs (
+ id SERIAL PRIMARY KEY,
+ build_id INTEGER,
+ repo_id INTEGER,
+ service_id INTEGER,
+ step_id INTEGER,
+ data BYTEA,
+ UNIQUE(step_id),
+ UNIQUE(service_id)
+);
+`
+
+ // CreateSqliteTable represents a query to create the Sqlite logs table.
+ CreateSqliteTable = `
+CREATE TABLE
+IF NOT EXISTS
+logs (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ build_id INTEGER,
+ repo_id INTEGER,
+ service_id INTEGER,
+ step_id INTEGER,
+ data BLOB,
+ UNIQUE(step_id),
+ UNIQUE(service_id)
+);
+`
+)
+
+// CreateLogTable creates the logs table in the database.
+func (e *engine) CreateLogTable(driver string) error {
+ e.logger.Tracef("creating logs table in the database")
+
+ // handle the driver provided to create the table
+ switch driver {
+ case constants.DriverPostgres:
+ // create the logs table for Postgres
+ return e.client.Exec(CreatePostgresTable).Error
+ case constants.DriverSqlite:
+ fallthrough
+ default:
+ // create the logs table for Sqlite
+ return e.client.Exec(CreateSqliteTable).Error
+ }
+}
diff --git a/database/log/table_test.go b/database/log/table_test.go
new file mode 100644
index 000000000..066d9f38a
--- /dev/null
+++ b/database/log/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 log
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestLog_Engine_CreateLogTable(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.CreateLogTable(test.name)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateLogTable for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateLogTable for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/log/update.go b/database/log/update.go
new file mode 100644
index 000000000..fb7165004
--- /dev/null
+++ b/database/log/update.go
@@ -0,0 +1,57 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code with update.go
+package log
+
+import (
+ "fmt"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// UpdateLog updates an existing log in the database.
+func (e *engine) UpdateLog(l *library.Log) error {
+ // check what the log entry is for
+ switch {
+ case l.GetServiceID() > 0:
+ e.logger.Tracef("updating log for service %d for build %d in the database", l.GetServiceID(), l.GetBuildID())
+ case l.GetStepID() > 0:
+ e.logger.Tracef("updating log for step %d for build %d in the database", l.GetStepID(), l.GetBuildID())
+ }
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#LogFromLibrary
+ log := database.LogFromLibrary(l)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Log.Validate
+ err := log.Validate()
+ if err != nil {
+ return err
+ }
+
+ // compress log data for the resource
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Log.Compress
+ err = log.Compress(e.config.CompressionLevel)
+ if err != nil {
+ switch {
+ case l.GetServiceID() > 0:
+ return fmt.Errorf("unable to compress log for service %d for build %d: %w", l.GetServiceID(), l.GetBuildID(), err)
+ case l.GetStepID() > 0:
+ return fmt.Errorf("unable to compress log for step %d for build %d: %w", l.GetStepID(), l.GetBuildID(), err)
+ }
+ }
+
+ // send query to the database
+ return e.client.
+ Table(constants.TableLog).
+ Save(log).
+ Error
+}
diff --git a/database/log/update_test.go b/database/log/update_test.go
new file mode 100644
index 000000000..0b4e8e127
--- /dev/null
+++ b/database/log/update_test.go
@@ -0,0 +1,101 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package log
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestLog_Engine_UpdateLog(t *testing.T) {
+ // setup types
+ _service := testLog()
+ _service.SetID(1)
+ _service.SetRepoID(1)
+ _service.SetBuildID(1)
+ _service.SetServiceID(1)
+ _service.SetData([]byte{})
+
+ _step := testLog()
+ _step.SetID(2)
+ _step.SetRepoID(1)
+ _step.SetBuildID(1)
+ _step.SetStepID(1)
+ _step.SetData([]byte{})
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the service query
+ _mock.ExpectExec(`UPDATE "logs"
+SET "build_id"=$1,"repo_id"=$2,"service_id"=$3,"step_id"=$4,"data"=$5
+WHERE "id" = $6`).
+ WithArgs(1, 1, 1, nil, AnyArgument{}, 1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ // ensure the mock expects the step query
+ _mock.ExpectExec(`UPDATE "logs"
+SET "build_id"=$1,"repo_id"=$2,"service_id"=$3,"step_id"=$4,"data"=$5
+WHERE "id" = $6`).
+ WithArgs(1, 1, nil, 1, AnyArgument{}, 2).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateLog(_service)
+ if err != nil {
+ t.Errorf("unable to create test service log for sqlite: %v", err)
+ }
+
+ err = _sqlite.CreateLog(_step)
+ if err != nil {
+ t.Errorf("unable to create test step log for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ logs []*library.Log
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ logs: []*library.Log{_service, _step},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ logs: []*library.Log{_service, _step},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ for _, log := range test.logs {
+ err = test.database.UpdateLog(log)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("UpdateLog for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("UpdateLog for %s returned err: %v", test.name, err)
+ }
+ }
+ })
+ }
+}
diff --git a/database/opts.go b/database/opts.go
new file mode 100644
index 000000000..42ab3d033
--- /dev/null
+++ b/database/opts.go
@@ -0,0 +1,102 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package database
+
+import (
+ "context"
+ "time"
+)
+
+// EngineOpt represents a configuration option to initialize the database engine.
+type EngineOpt func(*engine) error
+
+// WithAddress sets the address in the database engine.
+func WithAddress(address string) EngineOpt {
+ return func(e *engine) error {
+ // set the fully qualified connection string in the database engine
+ e.config.Address = address
+
+ return nil
+ }
+}
+
+// WithCompressionLevel sets the compression level in the database engine.
+func WithCompressionLevel(level int) EngineOpt {
+ return func(e *engine) error {
+ // set the level of compression for resources in the database engine
+ e.config.CompressionLevel = level
+
+ return nil
+ }
+}
+
+// WithConnectionLife sets the life of connections in the database engine.
+func WithConnectionLife(connectionLife time.Duration) EngineOpt {
+ return func(e *engine) error {
+ // set the maximum duration of time for connection in the database engine
+ e.config.ConnectionLife = connectionLife
+
+ return nil
+ }
+}
+
+// WithConnectionIdle sets the idle connections in the database engine.
+func WithConnectionIdle(connectionIdle int) EngineOpt {
+ return func(e *engine) error {
+ // set the maximum allowed idle connections in the database engine
+ e.config.ConnectionIdle = connectionIdle
+
+ return nil
+ }
+}
+
+// WithConnectionOpen sets the open connections in the database engine.
+func WithConnectionOpen(connectionOpen int) EngineOpt {
+ return func(e *engine) error {
+ // set the maximum allowed open connections in the database engine
+ e.config.ConnectionOpen = connectionOpen
+
+ return nil
+ }
+}
+
+// WithDriver sets the driver in the database engine.
+func WithDriver(driver string) EngineOpt {
+ return func(e *engine) error {
+ // set the database type to interact with in the database engine
+ e.config.Driver = driver
+
+ return nil
+ }
+}
+
+// WithEncryptionKey sets the encryption key in the database engine.
+func WithEncryptionKey(encryptionKey string) EngineOpt {
+ return func(e *engine) error {
+ // set the key for encrypting resources in the database engine
+ e.config.EncryptionKey = encryptionKey
+
+ return nil
+ }
+}
+
+// WithSkipCreation sets the skip creation logic in the database engine.
+func WithSkipCreation(skipCreation bool) EngineOpt {
+ return func(e *engine) error {
+ // set to skip creating tables and indexes in the database engine
+ e.config.SkipCreation = skipCreation
+
+ return nil
+ }
+}
+
+// WithContext sets the context in the database engine.
+func WithContext(ctx context.Context) EngineOpt {
+ return func(e *engine) error {
+ e.ctx = ctx
+
+ return nil
+ }
+}
diff --git a/database/opts_test.go b/database/opts_test.go
new file mode 100644
index 000000000..446fd5903
--- /dev/null
+++ b/database/opts_test.go
@@ -0,0 +1,409 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package database
+
+import (
+ "reflect"
+ "testing"
+ "time"
+)
+
+func TestDatabase_EngineOpt_WithAddress(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ address string
+ want string
+ }{
+ {
+ failure: false,
+ name: "address set",
+ address: "file::memory:?cache=shared",
+ want: "file::memory:?cache=shared",
+ },
+ {
+ failure: false,
+ name: "address not set",
+ address: "",
+ want: "",
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithAddress(test.address)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithAddress for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithAddress for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(e.config.Address, test.want) {
+ t.Errorf("WithAddress for %s is %v, want %v", test.name, e.config.Address, test.want)
+ }
+ })
+ }
+}
+
+func TestDatabase_EngineOpt_WithCompressionLevel(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ level int
+ want int
+ }{
+ {
+ failure: false,
+ name: "compression level set to -1",
+ level: -1,
+ want: -1,
+ },
+ {
+ failure: false,
+ name: "compression level set to 0",
+ level: 0,
+ want: 0,
+ },
+ {
+ failure: false,
+ name: "compression level set to 1",
+ level: 1,
+ want: 1,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithCompressionLevel(test.level)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithCompressionLevel for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithCompressionLevel for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(e.config.CompressionLevel, test.want) {
+ t.Errorf("WithCompressionLevel for %s is %v, want %v", test.name, e.config.CompressionLevel, test.want)
+ }
+ })
+ }
+}
+
+func TestDatabase_EngineOpt_WithConnectionLife(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ life time.Duration
+ want time.Duration
+ }{
+ {
+ failure: false,
+ name: "life of connections set",
+ life: 30 * time.Minute,
+ want: 30 * time.Minute,
+ },
+ {
+ failure: false,
+ name: "life of connections not set",
+ life: 0,
+ want: 0,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithConnectionLife(test.life)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithConnectionLife for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithConnectionLife for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(e.config.ConnectionLife, test.want) {
+ t.Errorf("WithConnectionLife for %s is %v, want %v", test.name, e.config.ConnectionLife, test.want)
+ }
+ })
+ }
+}
+
+func TestDatabase_EngineOpt_WithConnectionIdle(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ open int
+ want int
+ }{
+ {
+ failure: false,
+ name: "idle connections set",
+ open: 2,
+ want: 2,
+ },
+ {
+ failure: false,
+ name: "idle connections not set",
+ open: 0,
+ want: 0,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithConnectionIdle(test.open)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithConnectionIdle for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithConnectionIdle for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(e.config.ConnectionIdle, test.want) {
+ t.Errorf("WithConnectionIdle for %s is %v, want %v", test.name, e.config.ConnectionIdle, test.want)
+ }
+ })
+ }
+}
+
+func TestDatabase_EngineOpt_WithConnectionOpen(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ open int
+ want int
+ }{
+ {
+ failure: false,
+ name: "open connections set",
+ open: 2,
+ want: 2,
+ },
+ {
+ failure: false,
+ name: "open connections not set",
+ open: 0,
+ want: 0,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithConnectionOpen(test.open)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithConnectionOpen for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithConnectionOpen for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(e.config.ConnectionOpen, test.want) {
+ t.Errorf("WithConnectionOpen for %s is %v, want %v", test.name, e.config.ConnectionOpen, test.want)
+ }
+ })
+ }
+}
+
+func TestDatabase_EngineOpt_WithDriver(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ driver string
+ want string
+ }{
+ {
+ failure: false,
+ name: "driver set",
+ driver: "sqlite3",
+ want: "sqlite3",
+ },
+ {
+ failure: false,
+ name: "driver not set",
+ driver: "",
+ want: "",
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithDriver(test.driver)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithDriver for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithDriver for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(e.config.Driver, test.want) {
+ t.Errorf("WithDriver for %s is %v, want %v", test.name, e.config.Driver, test.want)
+ }
+ })
+ }
+}
+
+func TestDatabase_EngineOpt_WithEncryptionKey(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ key string
+ want string
+ }{
+ {
+ failure: false,
+ name: "encryption key set",
+ key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ want: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ },
+ {
+ failure: false,
+ name: "encryption key not set",
+ key: "",
+ want: "",
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithEncryptionKey(test.key)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithEncryptionKey for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithEncryptionKey for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(e.config.EncryptionKey, test.want) {
+ t.Errorf("WithEncryptionKey for %s is %v, want %v", test.name, e.config.EncryptionKey, test.want)
+ }
+ })
+ }
+}
+
+func TestDatabase_EngineOpt_WithSkipCreation(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ skip bool
+ want bool
+ }{
+ {
+ failure: false,
+ name: "skip creation set to true",
+ skip: true,
+ want: true,
+ },
+ {
+ failure: false,
+ name: "skip creation set to false",
+ skip: false,
+ want: false,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithSkipCreation(test.skip)(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/ping.go b/database/ping.go
new file mode 100644
index 000000000..80968c152
--- /dev/null
+++ b/database/ping.go
@@ -0,0 +1,42 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package database
+
+import (
+ "fmt"
+ "time"
+)
+
+// Ping sends a "ping" request with backoff to the database.
+func (e *engine) Ping() error {
+ e.logger.Tracef("sending ping request to the %s database", e.Driver())
+
+ // create a loop to attempt ping requests 5 times
+ for i := 0; i < 5; i++ {
+ // capture database/sql database from gorm.io/gorm database
+ _sql, err := e.client.DB()
+ if err != nil {
+ return err
+ }
+
+ // send ping request to database
+ err = _sql.Ping()
+ if err != nil {
+ // create the duration of time to sleep for before attempting to retry
+ duration := time.Duration(i+1) * time.Second
+
+ e.logger.Warnf("unable to ping %s database - retrying in %v", e.Driver(), duration)
+
+ // sleep for loop iteration in seconds
+ time.Sleep(duration)
+
+ continue
+ }
+
+ return nil
+ }
+
+ return fmt.Errorf("unable to successfully ping %s database", e.Driver())
+}
diff --git a/database/ping_test.go b/database/ping_test.go
new file mode 100644
index 000000000..5b2fdb0c9
--- /dev/null
+++ b/database/ping_test.go
@@ -0,0 +1,82 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package database
+
+import (
+ "testing"
+
+ "github.com/sirupsen/logrus"
+ "gorm.io/gorm"
+)
+
+func TestDatabase_Engine_Ping(t *testing.T) {
+ _postgres, _mock := testPostgres(t)
+ defer _postgres.Close()
+ // ensure the mock expects the ping
+ _mock.ExpectPing()
+
+ // create a test database without mocking the call
+ _unmocked, _ := testPostgres(t)
+
+ _sqlite := testSqlite(t)
+ defer _sqlite.Close()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ }{
+ {
+ name: "success with postgres",
+ failure: false,
+ database: _postgres,
+ },
+ {
+ name: "success with sqlite",
+ failure: false,
+ database: _sqlite,
+ },
+ {
+ name: "failure without mocked call",
+ failure: true,
+ database: _unmocked,
+ },
+ {
+ name: "failure with invalid gorm database",
+ failure: true,
+ database: &engine{
+ config: &config{
+ Driver: "invalid",
+ },
+ client: &gorm.DB{
+ Config: &gorm.Config{
+ ConnPool: nil,
+ },
+ },
+ logger: logrus.NewEntry(logrus.StandardLogger()),
+ },
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := test.database.Ping()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("Ping for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("Ping for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/pipeline/count.go b/database/pipeline/count.go
new file mode 100644
index 000000000..33377d105
--- /dev/null
+++ b/database/pipeline/count.go
@@ -0,0 +1,27 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+)
+
+// CountPipelines gets the count of all pipelines from the database.
+func (e *engine) CountPipelines(ctx context.Context) (int64, error) {
+ e.logger.Tracef("getting count of all pipelines from the database")
+
+ // variable to store query results
+ var p int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TablePipeline).
+ Count(&p).
+ Error
+
+ return p, err
+}
diff --git a/database/pipeline/count_repo.go b/database/pipeline/count_repo.go
new file mode 100644
index 000000000..a3318a4e6
--- /dev/null
+++ b/database/pipeline/count_repo.go
@@ -0,0 +1,33 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CountPipelinesForRepo gets the count of pipelines by repo ID from the database.
+func (e *engine) CountPipelinesForRepo(ctx context.Context, r *library.Repo) (int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ }).Tracef("getting count of pipelines for repo %s from the database", r.GetFullName())
+
+ // variable to store query results
+ var p int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TablePipeline).
+ Where("repo_id = ?", r.GetID()).
+ Count(&p).
+ Error
+
+ return p, err
+}
diff --git a/database/pipeline/count_repo_test.go b/database/pipeline/count_repo_test.go
new file mode 100644
index 000000000..0d2469e77
--- /dev/null
+++ b/database/pipeline/count_repo_test.go
@@ -0,0 +1,99 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestPipeline_Engine_CountPipelinesForRepo(t *testing.T) {
+ // setup types
+ _pipelineOne := testPipeline()
+ _pipelineOne.SetID(1)
+ _pipelineOne.SetRepoID(1)
+ _pipelineOne.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163")
+ _pipelineOne.SetRef("refs/heads/master")
+ _pipelineOne.SetType("yaml")
+ _pipelineOne.SetVersion("1")
+
+ _pipelineTwo := testPipeline()
+ _pipelineTwo.SetID(2)
+ _pipelineTwo.SetRepoID(1)
+ _pipelineTwo.SetCommit("a49aaf4afae6431a79239c95247a2b169fd9f067")
+ _pipelineTwo.SetRef("refs/heads/main")
+ _pipelineTwo.SetType("yaml")
+ _pipelineTwo.SetVersion("1")
+
+ _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 "pipelines" WHERE repo_id = $1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreatePipeline(context.TODO(), _pipelineOne)
+ if err != nil {
+ t.Errorf("unable to create test pipeline for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreatePipeline(context.TODO(), _pipelineTwo)
+ if err != nil {
+ t.Errorf("unable to create test pipeline 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.CountPipelinesForRepo(context.TODO(), &library.Repo{ID: _pipelineOne.RepoID})
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountPipelinesForRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountPipelinesForRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountPipelinesForRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/pipeline/count_test.go b/database/pipeline/count_test.go
new file mode 100644
index 000000000..6b638420a
--- /dev/null
+++ b/database/pipeline/count_test.go
@@ -0,0 +1,98 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestPipeline_Engine_CountPipelines(t *testing.T) {
+ // setup types
+ _pipelineOne := testPipeline()
+ _pipelineOne.SetID(1)
+ _pipelineOne.SetRepoID(1)
+ _pipelineOne.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163")
+ _pipelineOne.SetRef("refs/heads/master")
+ _pipelineOne.SetType("yaml")
+ _pipelineOne.SetVersion("1")
+
+ _pipelineTwo := testPipeline()
+ _pipelineTwo.SetID(2)
+ _pipelineTwo.SetRepoID(2)
+ _pipelineTwo.SetCommit("a49aaf4afae6431a79239c95247a2b169fd9f067")
+ _pipelineTwo.SetRef("refs/heads/main")
+ _pipelineTwo.SetType("yaml")
+ _pipelineTwo.SetVersion("1")
+
+ _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 "pipelines"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreatePipeline(context.TODO(), _pipelineOne)
+ if err != nil {
+ t.Errorf("unable to create test pipeline for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreatePipeline(context.TODO(), _pipelineTwo)
+ if err != nil {
+ t.Errorf("unable to create test pipeline 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.CountPipelines(context.TODO())
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountPipelines for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountPipelines for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountPipelines for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/pipeline/create.go b/database/pipeline/create.go
new file mode 100644
index 000000000..04b344c95
--- /dev/null
+++ b/database/pipeline/create.go
@@ -0,0 +1,55 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CreatePipeline creates a new pipeline in the database.
+func (e *engine) CreatePipeline(ctx context.Context, p *library.Pipeline) (*library.Pipeline, error) {
+ e.logger.WithFields(logrus.Fields{
+ "pipeline": p.GetCommit(),
+ }).Tracef("creating pipeline %s in the database", p.GetCommit())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#PipelineFromLibrary
+ pipeline := database.PipelineFromLibrary(p)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Pipeline.Validate
+ err := pipeline.Validate()
+ if err != nil {
+ return nil, err
+ }
+
+ // compress data for the pipeline
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Pipeline.Compress
+ err = pipeline.Compress(e.config.CompressionLevel)
+ if err != nil {
+ return nil, err
+ }
+
+ // send query to the database
+ err = e.client.Table(constants.TablePipeline).Create(pipeline).Error
+ if err != nil {
+ return nil, err
+ }
+
+ err = pipeline.Decompress()
+ if err != nil {
+ return nil, err
+ }
+
+ return pipeline.ToLibrary(), nil
+}
diff --git a/database/pipeline/create_test.go b/database/pipeline/create_test.go
new file mode 100644
index 000000000..9077dad5e
--- /dev/null
+++ b/database/pipeline/create_test.go
@@ -0,0 +1,82 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestPipeline_Engine_CreatePipeline(t *testing.T) {
+ // setup types
+ _pipeline := testPipeline()
+ _pipeline.SetID(1)
+ _pipeline.SetRepoID(1)
+ _pipeline.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163")
+ _pipeline.SetRef("refs/heads/master")
+ _pipeline.SetType("yaml")
+ _pipeline.SetVersion("1")
+ _pipeline.SetData([]byte{})
+
+ _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 "pipelines"
+("repo_id","commit","flavor","platform","ref","type","version","external_secrets","internal_secrets","services","stages","steps","templates","data","id")
+VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15) RETURNING "id"`).
+ WithArgs(1, "48afb5bdc41ad69bf22588491333f7cf71135163", nil, nil, "refs/heads/master", "yaml", "1", false, false, false, false, false, false, AnyArgument{}, 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) {
+ got, err := test.database.CreatePipeline(context.TODO(), _pipeline)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreatePipeline for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreatePipeline for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, _pipeline) {
+ t.Errorf("CreatePipeline for %s returned %s, want %s", test.name, got, _pipeline)
+ }
+ })
+ }
+}
diff --git a/database/pipeline/delete.go b/database/pipeline/delete.go
new file mode 100644
index 000000000..d473d5d4f
--- /dev/null
+++ b/database/pipeline/delete.go
@@ -0,0 +1,32 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// DeletePipeline deletes an existing pipeline from the database.
+func (e *engine) DeletePipeline(ctx context.Context, p *library.Pipeline) error {
+ e.logger.WithFields(logrus.Fields{
+ "pipeline": p.GetCommit(),
+ }).Tracef("deleting pipeline %s from the database", p.GetCommit())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#PipelineFromLibrary
+ pipeline := database.PipelineFromLibrary(p)
+
+ // send query to the database
+ return e.client.
+ Table(constants.TablePipeline).
+ Delete(pipeline).
+ Error
+}
diff --git a/database/pipeline/delete_test.go b/database/pipeline/delete_test.go
new file mode 100644
index 000000000..39e47a7dd
--- /dev/null
+++ b/database/pipeline/delete_test.go
@@ -0,0 +1,76 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestPipeline_Engine_DeletePipeline(t *testing.T) {
+ // setup types
+ _pipeline := testPipeline()
+ _pipeline.SetID(1)
+ _pipeline.SetRepoID(1)
+ _pipeline.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163")
+ _pipeline.SetRef("refs/heads/master")
+ _pipeline.SetType("yaml")
+ _pipeline.SetVersion("1")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`DELETE FROM "pipelines" WHERE "pipelines"."id" = $1`).
+ WithArgs(1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreatePipeline(context.TODO(), _pipeline)
+ if err != nil {
+ t.Errorf("unable to create test pipeline 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.DeletePipeline(context.TODO(), _pipeline)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("DeletePipeline for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("DeletePipeline for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/pipeline/get.go b/database/pipeline/get.go
new file mode 100644
index 000000000..17ec6ab78
--- /dev/null
+++ b/database/pipeline/get.go
@@ -0,0 +1,44 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// GetPipeline gets a pipeline by ID from the database.
+func (e *engine) GetPipeline(ctx context.Context, id int64) (*library.Pipeline, error) {
+ e.logger.Tracef("getting pipeline %d from the database", id)
+
+ // variable to store query results
+ p := new(database.Pipeline)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TablePipeline).
+ Where("id = ?", id).
+ Take(p).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // decompress data for the pipeline
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Pipeline.Decompress
+ err = p.Decompress()
+ if err != nil {
+ return nil, err
+ }
+
+ // return the decompressed pipeline
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Pipeline.ToLibrary
+ return p.ToLibrary(), nil
+}
diff --git a/database/pipeline/get_repo.go b/database/pipeline/get_repo.go
new file mode 100644
index 000000000..f165c18eb
--- /dev/null
+++ b/database/pipeline/get_repo.go
@@ -0,0 +1,50 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// GetPipelineForRepo gets a pipeline by number and repo ID from the database.
+func (e *engine) GetPipelineForRepo(ctx context.Context, commit string, r *library.Repo) (*library.Pipeline, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "pipeline": commit,
+ "repo": r.GetName(),
+ }).Tracef("getting pipeline %s/%s from the database", r.GetFullName(), commit)
+
+ // variable to store query results
+ p := new(database.Pipeline)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TablePipeline).
+ Where("repo_id = ?", r.GetID()).
+ Where("\"commit\" = ?", commit).
+ Take(p).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // decompress data for the pipeline
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Pipeline.Decompress
+ err = p.Decompress()
+ if err != nil {
+ return nil, err
+ }
+
+ // return the decompressed pipeline
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Pipeline.ToLibrary
+ return p.ToLibrary(), nil
+}
diff --git a/database/pipeline/get_repo_test.go b/database/pipeline/get_repo_test.go
new file mode 100644
index 000000000..67161931d
--- /dev/null
+++ b/database/pipeline/get_repo_test.go
@@ -0,0 +1,89 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestPipeline_Engine_GetPipelineForRepo(t *testing.T) {
+ // setup types
+ _pipeline := testPipeline()
+ _pipeline.SetID(1)
+ _pipeline.SetRepoID(1)
+ _pipeline.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163")
+ _pipeline.SetRef("refs/heads/master")
+ _pipeline.SetType("yaml")
+ _pipeline.SetVersion("1")
+ _pipeline.SetData([]byte("foo"))
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "repo_id", "commit", "flavor", "platform", "ref", "type", "version", "services", "stages", "steps", "templates", "data"}).
+ AddRow(1, 1, "48afb5bdc41ad69bf22588491333f7cf71135163", "", "", "refs/heads/master", "yaml", "1", false, false, false, false, []byte{120, 94, 74, 203, 207, 7, 4, 0, 0, 255, 255, 2, 130, 1, 69})
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "pipelines" WHERE repo_id = $1 AND "commit" = $2 LIMIT 1`).WithArgs(1, "48afb5bdc41ad69bf22588491333f7cf71135163").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreatePipeline(context.TODO(), _pipeline)
+ if err != nil {
+ t.Errorf("unable to create test pipeline for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Pipeline
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _pipeline,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _pipeline,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetPipelineForRepo(context.TODO(), "48afb5bdc41ad69bf22588491333f7cf71135163", &library.Repo{ID: _pipeline.RepoID})
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetPipelineForRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetPipelineForRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetPipelineForRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/pipeline/get_test.go b/database/pipeline/get_test.go
new file mode 100644
index 000000000..ca6d789f2
--- /dev/null
+++ b/database/pipeline/get_test.go
@@ -0,0 +1,89 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestPipeline_Engine_GetPipeline(t *testing.T) {
+ // setup types
+ _pipeline := testPipeline()
+ _pipeline.SetID(1)
+ _pipeline.SetRepoID(1)
+ _pipeline.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163")
+ _pipeline.SetRef("refs/heads/master")
+ _pipeline.SetType("yaml")
+ _pipeline.SetVersion("1")
+ _pipeline.SetData([]byte("foo"))
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "repo_id", "commit", "flavor", "platform", "ref", "type", "version", "services", "stages", "steps", "templates", "data"}).
+ AddRow(1, 1, "48afb5bdc41ad69bf22588491333f7cf71135163", "", "", "refs/heads/master", "yaml", "1", false, false, false, false, []byte{120, 94, 74, 203, 207, 7, 4, 0, 0, 255, 255, 2, 130, 1, 69})
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "pipelines" WHERE id = $1 LIMIT 1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreatePipeline(context.TODO(), _pipeline)
+ if err != nil {
+ t.Errorf("unable to create test pipeline for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Pipeline
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _pipeline,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _pipeline,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetPipeline(context.TODO(), 1)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetPipeline for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetPipeline for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetPipeline for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/pipeline/index.go b/database/pipeline/index.go
new file mode 100644
index 000000000..3189b728d
--- /dev/null
+++ b/database/pipeline/index.go
@@ -0,0 +1,26 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import "context"
+
+const (
+ // CreateRepoIDIndex represents a query to create an
+ // index on the pipelines table for the repo_id column.
+ CreateRepoIDIndex = `
+CREATE INDEX
+IF NOT EXISTS
+pipelines_repo_id
+ON pipelines (repo_id);
+`
+)
+
+// CreatePipelineIndexes creates the indexes for the pipelines table in the database.
+func (e *engine) CreatePipelineIndexes(ctx context.Context) error {
+ e.logger.Tracef("creating indexes for pipelines table in the database")
+
+ // create the repo_id column index for the pipelines table
+ return e.client.Exec(CreateRepoIDIndex).Error
+}
diff --git a/database/pipeline/index_test.go b/database/pipeline/index_test.go
new file mode 100644
index 000000000..e72b5a593
--- /dev/null
+++ b/database/pipeline/index_test.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestPipeline_Engine_CreatePipelineIndexes(t *testing.T) {
+ // setup types
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ _mock.ExpectExec(CreateRepoIDIndex).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.CreatePipelineIndexes(context.TODO())
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreatePipelineIndexes for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreatePipelineIndexes for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/pipeline/interface.go b/database/pipeline/interface.go
new file mode 100644
index 000000000..ae75ebd54
--- /dev/null
+++ b/database/pipeline/interface.go
@@ -0,0 +1,49 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+
+ "github.com/go-vela/types/library"
+)
+
+// PipelineInterface represents the Vela interface for pipeline
+// functions with the supported Database backends.
+//
+//nolint:revive // ignore name stutter
+type PipelineInterface interface {
+ // Pipeline Data Definition Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_definition_language
+
+ // CreatePipelineIndexes defines a function that creates the indexes for the pipelines table.
+ CreatePipelineIndexes(context.Context) error
+ // CreatePipelineTable defines a function that creates the pipelines table.
+ CreatePipelineTable(context.Context, string) error
+
+ // Pipeline Data Manipulation Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_manipulation_language
+
+ // CountPipelines defines a function that gets the count of all pipelines.
+ CountPipelines(context.Context) (int64, error)
+ // CountPipelinesForRepo defines a function that gets the count of pipelines by repo ID.
+ CountPipelinesForRepo(context.Context, *library.Repo) (int64, error)
+ // CreatePipeline defines a function that creates a new pipeline.
+ CreatePipeline(context.Context, *library.Pipeline) (*library.Pipeline, error)
+ // DeletePipeline defines a function that deletes an existing pipeline.
+ DeletePipeline(context.Context, *library.Pipeline) error
+ // GetPipeline defines a function that gets a pipeline by ID.
+ GetPipeline(context.Context, int64) (*library.Pipeline, error)
+ // GetPipelineForRepo defines a function that gets a pipeline by commit SHA and repo ID.
+ GetPipelineForRepo(context.Context, string, *library.Repo) (*library.Pipeline, error)
+ // ListPipelines defines a function that gets a list of all pipelines.
+ ListPipelines(context.Context) ([]*library.Pipeline, error)
+ // ListPipelinesForRepo defines a function that gets a list of pipelines by repo ID.
+ ListPipelinesForRepo(context.Context, *library.Repo, int, int) ([]*library.Pipeline, int64, error)
+ // UpdatePipeline defines a function that updates an existing pipeline.
+ UpdatePipeline(context.Context, *library.Pipeline) (*library.Pipeline, error)
+}
diff --git a/database/pipeline/list.go b/database/pipeline/list.go
new file mode 100644
index 000000000..54f8015c4
--- /dev/null
+++ b/database/pipeline/list.go
@@ -0,0 +1,64 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// ListPipelines gets a list of all pipelines from the database.
+func (e *engine) ListPipelines(ctx context.Context) ([]*library.Pipeline, error) {
+ e.logger.Trace("listing all pipelines from the database")
+
+ // variables to store query results and return value
+ count := int64(0)
+ p := new([]database.Pipeline)
+ pipelines := []*library.Pipeline{}
+
+ // count the results
+ count, err := e.CountPipelines(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return pipelines, nil
+ }
+
+ // send query to the database and store result in variable
+ err = e.client.
+ Table(constants.TablePipeline).
+ Find(&p).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // iterate through all query results
+ for _, pipeline := range *p {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := pipeline
+
+ // decompress data for the pipeline
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Pipeline.Decompress
+ err = tmp.Decompress()
+ if err != nil {
+ return nil, err
+ }
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Pipeline.ToLibrary
+ pipelines = append(pipelines, tmp.ToLibrary())
+ }
+
+ return pipelines, nil
+}
diff --git a/database/pipeline/list_repo.go b/database/pipeline/list_repo.go
new file mode 100644
index 000000000..e8c38afab
--- /dev/null
+++ b/database/pipeline/list_repo.go
@@ -0,0 +1,75 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// ListPipelinesForRepo gets a list of pipelines by repo ID from the database.
+//
+//nolint:lll // ignore long line length due to variable names
+func (e *engine) ListPipelinesForRepo(ctx context.Context, r *library.Repo, page, perPage int) ([]*library.Pipeline, int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ }).Tracef("listing pipelines for repo %s from the database", r.GetFullName())
+
+ // variables to store query results and return values
+ count := int64(0)
+ p := new([]database.Pipeline)
+ pipelines := []*library.Pipeline{}
+
+ // count the results
+ count, err := e.CountPipelinesForRepo(context.TODO(), r)
+ if err != nil {
+ return pipelines, 0, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return pipelines, 0, nil
+ }
+
+ // calculate offset for pagination through results
+ offset := perPage * (page - 1)
+
+ err = e.client.
+ Table(constants.TablePipeline).
+ Where("repo_id = ?", r.GetID()).
+ Limit(perPage).
+ Offset(offset).
+ Find(&p).
+ Error
+ if err != nil {
+ return nil, count, err
+ }
+
+ // iterate through all query results
+ for _, pipeline := range *p {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := pipeline
+
+ // decompress data for the pipeline
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Pipeline.Decompress
+ err = tmp.Decompress()
+ if err != nil {
+ return nil, count, err
+ }
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Pipeline.ToLibrary
+ pipelines = append(pipelines, tmp.ToLibrary())
+ }
+
+ return pipelines, count, nil
+}
diff --git a/database/pipeline/list_repo_test.go b/database/pipeline/list_repo_test.go
new file mode 100644
index 000000000..35cc2e769
--- /dev/null
+++ b/database/pipeline/list_repo_test.go
@@ -0,0 +1,110 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestPipeline_Engine_ListPipelinesForRepo(t *testing.T) {
+ // setup types
+ _pipelineOne := testPipeline()
+ _pipelineOne.SetID(1)
+ _pipelineOne.SetRepoID(1)
+ _pipelineOne.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163")
+ _pipelineOne.SetRef("refs/heads/master")
+ _pipelineOne.SetType("yaml")
+ _pipelineOne.SetVersion("1")
+ _pipelineOne.SetData([]byte("foo"))
+
+ _pipelineTwo := testPipeline()
+ _pipelineTwo.SetID(2)
+ _pipelineTwo.SetRepoID(1)
+ _pipelineTwo.SetCommit("a49aaf4afae6431a79239c95247a2b169fd9f067")
+ _pipelineTwo.SetRef("refs/heads/main")
+ _pipelineTwo.SetType("yaml")
+ _pipelineTwo.SetVersion("1")
+ _pipelineTwo.SetData([]byte("foo"))
+
+ _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 "pipelines" WHERE repo_id = $1`).WithArgs(1).WillReturnRows(_rows)
+
+ // create expected result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "repo_id", "commit", "flavor", "platform", "ref", "type", "version", "services", "stages", "steps", "templates", "data"}).
+ AddRow(1, 1, "48afb5bdc41ad69bf22588491333f7cf71135163", "", "", "refs/heads/master", "yaml", "1", false, false, false, false, []byte{120, 94, 74, 203, 207, 7, 4, 0, 0, 255, 255, 2, 130, 1, 69}).
+ AddRow(2, 1, "a49aaf4afae6431a79239c95247a2b169fd9f067", "", "", "refs/heads/main", "yaml", "1", false, false, false, false, []byte{120, 94, 74, 203, 207, 7, 4, 0, 0, 255, 255, 2, 130, 1, 69})
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "pipelines" WHERE repo_id = $1 LIMIT 10`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreatePipeline(context.TODO(), _pipelineOne)
+ if err != nil {
+ t.Errorf("unable to create test pipeline for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreatePipeline(context.TODO(), _pipelineTwo)
+ if err != nil {
+ t.Errorf("unable to create test pipeline for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Pipeline
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Pipeline{_pipelineOne, _pipelineTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.Pipeline{_pipelineOne, _pipelineTwo},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, _, err := test.database.ListPipelinesForRepo(context.TODO(), &library.Repo{ID: _pipelineOne.RepoID}, 1, 10)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListPipelinesForRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListPipelinesForRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListPipelinesForRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/pipeline/list_test.go b/database/pipeline/list_test.go
new file mode 100644
index 000000000..e1d14166e
--- /dev/null
+++ b/database/pipeline/list_test.go
@@ -0,0 +1,110 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestPipeline_Engine_ListPipelines(t *testing.T) {
+ // setup types
+ _pipelineOne := testPipeline()
+ _pipelineOne.SetID(1)
+ _pipelineOne.SetRepoID(1)
+ _pipelineOne.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163")
+ _pipelineOne.SetRef("refs/heads/master")
+ _pipelineOne.SetType("yaml")
+ _pipelineOne.SetVersion("1")
+ _pipelineOne.SetData([]byte("foo"))
+
+ _pipelineTwo := testPipeline()
+ _pipelineTwo.SetID(2)
+ _pipelineTwo.SetRepoID(2)
+ _pipelineTwo.SetCommit("a49aaf4afae6431a79239c95247a2b169fd9f067")
+ _pipelineTwo.SetRef("refs/heads/main")
+ _pipelineTwo.SetType("yaml")
+ _pipelineTwo.SetVersion("1")
+ _pipelineTwo.SetData([]byte("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 "pipelines"`).WillReturnRows(_rows)
+
+ // create expected result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "repo_id", "commit", "flavor", "platform", "ref", "type", "version", "services", "stages", "steps", "templates", "data"}).
+ AddRow(1, 1, "48afb5bdc41ad69bf22588491333f7cf71135163", "", "", "refs/heads/master", "yaml", "1", false, false, false, false, []byte{120, 94, 74, 203, 207, 7, 4, 0, 0, 255, 255, 2, 130, 1, 69}).
+ AddRow(2, 2, "a49aaf4afae6431a79239c95247a2b169fd9f067", "", "", "refs/heads/main", "yaml", "1", false, false, false, false, []byte{120, 94, 74, 203, 207, 7, 4, 0, 0, 255, 255, 2, 130, 1, 69})
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "pipelines"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreatePipeline(context.TODO(), _pipelineOne)
+ if err != nil {
+ t.Errorf("unable to create test pipeline for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreatePipeline(context.TODO(), _pipelineTwo)
+ if err != nil {
+ t.Errorf("unable to create test pipeline for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Pipeline
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Pipeline{_pipelineOne, _pipelineTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.Pipeline{_pipelineOne, _pipelineTwo},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.ListPipelines(context.TODO())
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListPipelines for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListPipelines for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListPipelines for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/pipeline/opts.go b/database/pipeline/opts.go
new file mode 100644
index 000000000..e4d1e6d46
--- /dev/null
+++ b/database/pipeline/opts.go
@@ -0,0 +1,65 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+// EngineOpt represents a configuration option to initialize the database engine for Pipelines.
+type EngineOpt func(*engine) error
+
+// WithClient sets the gorm.io/gorm client in the database engine for Pipelines.
+func WithClient(client *gorm.DB) EngineOpt {
+ return func(e *engine) error {
+ // set the gorm.io/gorm client in the pipeline engine
+ e.client = client
+
+ return nil
+ }
+}
+
+// WithCompressionLevel sets the compression level in the database engine for Pipelines.
+func WithCompressionLevel(level int) EngineOpt {
+ return func(e *engine) error {
+ // set the compression level in the pipeline engine
+ e.config.CompressionLevel = level
+
+ return nil
+ }
+}
+
+// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for Pipelines.
+func WithLogger(logger *logrus.Entry) EngineOpt {
+ return func(e *engine) error {
+ // set the github.com/sirupsen/logrus logger in the pipeline engine
+ e.logger = logger
+
+ return nil
+ }
+}
+
+// WithSkipCreation sets the skip creation logic in the database engine for Pipelines.
+func WithSkipCreation(skipCreation bool) EngineOpt {
+ return func(e *engine) error {
+ // set to skip creating tables and indexes in the pipeline engine
+ e.config.SkipCreation = skipCreation
+
+ return nil
+ }
+}
+
+// WithContext sets the context in the database engine for Pipelines.
+func WithContext(ctx context.Context) EngineOpt {
+ return func(e *engine) error {
+ e.ctx = ctx
+
+ return nil
+ }
+}
diff --git a/database/pipeline/opts_test.go b/database/pipeline/opts_test.go
new file mode 100644
index 000000000..2380bdda7
--- /dev/null
+++ b/database/pipeline/opts_test.go
@@ -0,0 +1,266 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+func TestPipeline_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 TestPipeline_EngineOpt_WithCompressionLevel(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ level int
+ want int
+ }{
+ {
+ failure: false,
+ name: "compression level set to -1",
+ level: -1,
+ want: -1,
+ },
+ {
+ failure: false,
+ name: "compression level set to 0",
+ level: 0,
+ want: 0,
+ },
+ {
+ failure: false,
+ name: "compression level set to 1",
+ level: 1,
+ want: 1,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithCompressionLevel(test.level)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithCompressionLevel for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithCompressionLevel returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.config.CompressionLevel, test.want) {
+ t.Errorf("WithCompressionLevel is %v, want %v", e.config.CompressionLevel, test.want)
+ }
+ })
+ }
+}
+
+func TestPipeline_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 TestPipeline_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)
+ }
+ })
+ }
+}
+
+func TestPipeline_EngineOpt_WithContext(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ ctx context.Context
+ want context.Context
+ }{
+ {
+ failure: false,
+ name: "context set to TODO",
+ ctx: context.TODO(),
+ want: context.TODO(),
+ },
+ {
+ failure: false,
+ name: "context set to nil",
+ ctx: nil,
+ want: nil,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithContext(test.ctx)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithContext for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithContext returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.ctx, test.want) {
+ t.Errorf("WithContext is %v, want %v", e.ctx, test.want)
+ }
+ })
+ }
+}
diff --git a/database/pipeline/pipeline.go b/database/pipeline/pipeline.go
new file mode 100644
index 000000000..7c9f29d8f
--- /dev/null
+++ b/database/pipeline/pipeline.go
@@ -0,0 +1,86 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+ "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 PipelineInterface interface.
+ config struct {
+ // specifies the level of compression to use for the Pipeline engine
+ CompressionLevel int
+ // specifies to skip creating tables and indexes for the Pipeline engine
+ SkipCreation bool
+ }
+
+ // engine represents the pipeline functionality that implements the PipelineInterface interface.
+ engine struct {
+ // engine configuration settings used in pipeline functions
+ config *config
+
+ ctx context.Context
+
+ // gorm.io/gorm database client used in pipeline functions
+ //
+ // https://pkg.go.dev/gorm.io/gorm#DB
+ client *gorm.DB
+
+ // sirupsen/logrus logger used in pipeline functions
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus#Entry
+ logger *logrus.Entry
+ }
+)
+
+// New creates and returns a Vela service for integrating with pipelines in the database.
+//
+//nolint:revive // ignore returning unexported engine
+func New(opts ...EngineOpt) (*engine, error) {
+ // create new Pipeline engine
+ e := new(engine)
+
+ // create new fields
+ e.client = new(gorm.DB)
+ e.config = new(config)
+ e.logger = new(logrus.Entry)
+ e.ctx = context.TODO()
+
+ // apply all provided configuration options
+ for _, opt := range opts {
+ err := opt(e)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // check if we should skip creating pipeline database objects
+ if e.config.SkipCreation {
+ e.logger.Warning("skipping creation of pipelines table and indexes in the database")
+
+ return e, nil
+ }
+
+ // create the pipelines table
+ err := e.CreatePipelineTable(e.ctx, e.client.Config.Dialector.Name())
+ if err != nil {
+ return nil, fmt.Errorf("unable to create %s table: %w", constants.TablePipeline, err)
+ }
+
+ // create the indexes for the pipelines table
+ err = e.CreatePipelineIndexes(e.ctx)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create indexes for %s table: %w", constants.TablePipeline, err)
+ }
+
+ return e, nil
+}
diff --git a/database/pipeline/pipeline_test.go b/database/pipeline/pipeline_test.go
new file mode 100644
index 000000000..13d01393a
--- /dev/null
+++ b/database/pipeline/pipeline_test.go
@@ -0,0 +1,211 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+ "database/sql/driver"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/driver/postgres"
+ "gorm.io/driver/sqlite"
+ "gorm.io/gorm"
+)
+
+func TestPipeline_New(t *testing.T) {
+ // setup types
+ logger := logrus.NewEntry(logrus.StandardLogger())
+
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+ defer _sql.Close()
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateRepoIDIndex).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
+ level int
+ logger *logrus.Entry
+ skipCreation bool
+ want *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ client: _postgres,
+ level: 1,
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ client: _postgres,
+ config: &config{CompressionLevel: 1, SkipCreation: false},
+ ctx: context.TODO(),
+ logger: logger,
+ },
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ client: _sqlite,
+ level: 1,
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ client: _sqlite,
+ config: &config{CompressionLevel: 1, SkipCreation: false},
+ ctx: context.TODO(),
+ logger: logger,
+ },
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := New(
+ WithContext(context.TODO()),
+ WithClient(test.client),
+ WithCompressionLevel(test.level),
+ WithLogger(test.logger),
+ WithSkipCreation(test.skipCreation),
+ )
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("New for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("New for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("New for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
+
+// testPostgres is a helper function to create a Postgres engine for testing.
+func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) {
+ // create the new mock sql database
+ //
+ // https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#New
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateRepoIDIndex).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),
+ WithCompressionLevel(0),
+ WithLogger(logrus.NewEntry(logrus.StandardLogger())),
+ WithSkipCreation(false),
+ )
+ if err != nil {
+ t.Errorf("unable to create new postgres pipeline 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),
+ WithCompressionLevel(0),
+ WithLogger(logrus.NewEntry(logrus.StandardLogger())),
+ WithSkipCreation(false),
+ )
+ if err != nil {
+ t.Errorf("unable to create new sqlite pipeline engine: %v", err)
+ }
+
+ return _engine
+}
+
+// testPipeline is a test helper function to create a library
+// Pipeline type with all fields set to their zero values.
+func testPipeline() *library.Pipeline {
+ return &library.Pipeline{
+ ID: new(int64),
+ RepoID: new(int64),
+ Commit: new(string),
+ Flavor: new(string),
+ Platform: new(string),
+ Ref: new(string),
+ Type: new(string),
+ Version: new(string),
+ ExternalSecrets: new(bool),
+ InternalSecrets: new(bool),
+ Services: new(bool),
+ Stages: new(bool),
+ Steps: new(bool),
+ Templates: new(bool),
+ Data: new([]byte),
+ }
+}
+
+// This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values
+// that are otherwise not easily compared. These typically would be values generated
+// before adding or updating them in the database.
+//
+// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime
+type AnyArgument struct{}
+
+// Match satisfies sqlmock.Argument interface.
+func (a AnyArgument) Match(v driver.Value) bool {
+ return true
+}
diff --git a/database/pipeline/table.go b/database/pipeline/table.go
new file mode 100644
index 000000000..eef8b885a
--- /dev/null
+++ b/database/pipeline/table.go
@@ -0,0 +1,78 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+)
+
+const (
+ // CreatePostgresTable represents a query to create the Postgres pipelines table.
+ CreatePostgresTable = `
+CREATE TABLE
+IF NOT EXISTS
+pipelines (
+ id SERIAL PRIMARY KEY,
+ repo_id INTEGER,
+ commit VARCHAR(500),
+ flavor VARCHAR(100),
+ platform VARCHAR(100),
+ ref VARCHAR(500),
+ type VARCHAR(100),
+ version VARCHAR(50),
+ external_secrets BOOLEAN,
+ internal_secrets BOOLEAN,
+ services BOOLEAN,
+ stages BOOLEAN,
+ steps BOOLEAN,
+ templates BOOLEAN,
+ data BYTEA,
+ UNIQUE(repo_id, commit)
+);
+`
+
+ // CreateSqliteTable represents a query to create the Sqlite pipelines table.
+ CreateSqliteTable = `
+CREATE TABLE
+IF NOT EXISTS
+pipelines (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ repo_id INTEGER,
+ 'commit' TEXT,
+ flavor TEXT,
+ platform TEXT,
+ ref TEXT,
+ type TEXT,
+ version TEXT,
+ external_secrets BOOLEAN,
+ internal_secrets BOOLEAN,
+ services BOOLEAN,
+ stages BOOLEAN,
+ steps BOOLEAN,
+ templates BOOLEAN,
+ data BLOB,
+ UNIQUE(repo_id, 'commit')
+);
+`
+)
+
+// CreatePipelineTable creates the pipelines table in the database.
+func (e *engine) CreatePipelineTable(ctx context.Context, driver string) error {
+ e.logger.Tracef("creating pipelines table in the database")
+
+ // handle the driver provided to create the table
+ switch driver {
+ case constants.DriverPostgres:
+ // create the pipelines table for Postgres
+ return e.client.Exec(CreatePostgresTable).Error
+ case constants.DriverSqlite:
+ fallthrough
+ default:
+ // create the pipelines table for Sqlite
+ return e.client.Exec(CreateSqliteTable).Error
+ }
+}
diff --git a/database/pipeline/table_test.go b/database/pipeline/table_test.go
new file mode 100644
index 000000000..72add7aee
--- /dev/null
+++ b/database/pipeline/table_test.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestPipeline_Engine_CreatePipelineTable(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.CreatePipelineTable(context.TODO(), test.name)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreatePipelineTable for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreatePipelineTable for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/pipeline/update.go b/database/pipeline/update.go
new file mode 100644
index 000000000..64d9dd561
--- /dev/null
+++ b/database/pipeline/update.go
@@ -0,0 +1,56 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// UpdatePipeline updates an existing pipeline in the database.
+func (e *engine) UpdatePipeline(ctx context.Context, p *library.Pipeline) (*library.Pipeline, error) {
+ e.logger.WithFields(logrus.Fields{
+ "pipeline": p.GetCommit(),
+ }).Tracef("updating pipeline %s in the database", p.GetCommit())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#PipelineFromLibrary
+ pipeline := database.PipelineFromLibrary(p)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Pipeline.Validate
+ err := pipeline.Validate()
+ if err != nil {
+ return nil, err
+ }
+
+ // compress data for the pipeline
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Pipeline.Compress
+ err = pipeline.Compress(e.config.CompressionLevel)
+ if err != nil {
+ return nil, err
+ }
+
+ // send query to the database
+ err = e.client.Table(constants.TablePipeline).Save(pipeline).Error
+ if err != nil {
+ return nil, err
+ }
+
+ // decompress pipeline to return
+ err = pipeline.Decompress()
+ if err != nil {
+ return nil, err
+ }
+
+ return pipeline.ToLibrary(), nil
+}
diff --git a/database/pipeline/update_test.go b/database/pipeline/update_test.go
new file mode 100644
index 000000000..f8ab6b3d3
--- /dev/null
+++ b/database/pipeline/update_test.go
@@ -0,0 +1,84 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestPipeline_Engine_UpdatePipeline(t *testing.T) {
+ // setup types
+ _pipeline := testPipeline()
+ _pipeline.SetID(1)
+ _pipeline.SetRepoID(1)
+ _pipeline.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163")
+ _pipeline.SetRef("refs/heads/master")
+ _pipeline.SetType("yaml")
+ _pipeline.SetVersion("1")
+ _pipeline.SetData([]byte{})
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`UPDATE "pipelines"
+SET "repo_id"=$1,"commit"=$2,"flavor"=$3,"platform"=$4,"ref"=$5,"type"=$6,"version"=$7,"external_secrets"=$8,"internal_secrets"=$9,"services"=$10,"stages"=$11,"steps"=$12,"templates"=$13,"data"=$14
+WHERE "id" = $15`).
+ WithArgs(1, "48afb5bdc41ad69bf22588491333f7cf71135163", nil, nil, "refs/heads/master", "yaml", "1", false, false, false, false, false, false, AnyArgument{}, 1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreatePipeline(context.TODO(), _pipeline)
+ if err != nil {
+ t.Errorf("unable to create test pipeline 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) {
+ got, err := test.database.UpdatePipeline(context.TODO(), _pipeline)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("UpdatePipeline for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("UpdatePipeline for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, _pipeline) {
+ t.Errorf("UpdatePipeline for %s returned %s, want %s", test.name, got, _pipeline)
+ }
+ })
+ }
+}
diff --git a/database/postgres/build.go b/database/postgres/build.go
deleted file mode 100644
index 84e4d2140..000000000
--- a/database/postgres/build.go
+++ /dev/null
@@ -1,176 +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"
-)
-
-// GetBuild gets a build by number and repo ID from the database.
-//
-// nolint: dupl // ignore similar code with hook
-func (c *client) GetBuild(number int, r *library.Repo) (*library.Build, error) {
- c.Logger.WithFields(logrus.Fields{
- "build": number,
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("getting build %s/%d from the database", r.GetFullName(), number)
-
- // variable to store query results
- b := new(database.Build)
-
- // send query to the database and store result in variable
- result := c.Postgres.
- Table(constants.TableBuild).
- Raw(dml.SelectRepoBuild, r.GetID(), number).
- Scan(b)
-
- // 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 b.ToLibrary(), result.Error
-}
-
-// GetLastBuild gets the last build by repo ID from the database.
-func (c *client) GetLastBuild(r *library.Repo) (*library.Build, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("getting last build for repo %s from the database", r.GetFullName())
-
- // variable to store query results
- b := new(database.Build)
-
- // send query to the database and store result in variable
- result := c.Postgres.
- Table(constants.TableBuild).
- Raw(dml.SelectLastRepoBuild, r.GetID()).
- Scan(b)
-
- // 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 {
- // the record will not exist if it's a new repo
- return nil, nil
- }
-
- return b.ToLibrary(), result.Error
-}
-
-// GetLastBuildByBranch gets the last build by repo ID and branch from the database.
-func (c *client) GetLastBuildByBranch(r *library.Repo, branch string) (*library.Build, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("getting last build for repo %s on branch %s from the database", r.GetFullName(), branch)
-
- // variable to store query results
- b := new(database.Build)
-
- // send query to the database and store result in variable
- result := c.Postgres.
- Table(constants.TableBuild).
- Raw(dml.SelectLastRepoBuildByBranch, r.GetID(), branch).
- Scan(b)
-
- // 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 {
- // the record will not exist if it's a new repo
- return nil, nil
- }
-
- return b.ToLibrary(), result.Error
-}
-
-// GetPendingAndRunningBuilds returns the list of pending
-// and running builds within the given timeframe.
-func (c *client) GetPendingAndRunningBuilds(after string) ([]*library.BuildQueue, error) {
- c.Logger.Trace("getting pending and running builds from the database")
-
- // variable to store query results
- b := new([]database.BuildQueue)
-
- // send query to the database and store result in variable
- result := c.Postgres.
- Table(constants.TableBuild).
- Raw(dml.SelectPendingAndRunningBuilds, after).
- Scan(b)
-
- // variable we want to return
- builds := []*library.BuildQueue{}
-
- // iterate through all query results
- for _, build := range *b {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := build
-
- // convert query result to library type
- builds = append(builds, tmp.ToLibrary())
- }
-
- return builds, result.Error
-}
-
-// CreateBuild creates a new build in the database.
-func (c *client) CreateBuild(b *library.Build) error {
- c.Logger.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- }).Tracef("creating build %d in the database", b.GetNumber())
-
- // cast to database type
- build := database.BuildFromLibrary(b)
-
- // validate the necessary fields are populated
- err := build.Validate()
- if err != nil {
- return err
- }
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableBuild).
- Create(build.Crop()).Error
-}
-
-// UpdateBuild updates a build in the database.
-func (c *client) UpdateBuild(b *library.Build) error {
- c.Logger.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- }).Tracef("updating build %d in the database", b.GetNumber())
-
- // cast to database type
- build := database.BuildFromLibrary(b)
-
- // validate the necessary fields are populated
- err := build.Validate()
- if err != nil {
- return err
- }
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableBuild).
- Save(build.Crop()).Error
-}
-
-// DeleteBuild deletes a build by unique ID from the database.
-func (c *client) DeleteBuild(id int64) error {
- c.Logger.Tracef("deleting build %d in the database", id)
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableBuild).
- Exec(dml.DeleteBuild, id).Error
-}
diff --git a/database/postgres/build_count.go b/database/postgres/build_count.go
deleted file mode 100644
index 0d7af8f5f..000000000
--- a/database/postgres/build_count.go
+++ /dev/null
@@ -1,83 +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"
-)
-
-// GetBuildCount gets a count of all builds from the database.
-func (c *client) GetBuildCount() (int64, error) {
- c.Logger.Trace("getting count of builds from the database")
-
- // variable to store query results
- var b int64
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableBuild).
- Raw(dml.SelectBuildsCount).
- Pluck("count", &b).Error
-
- return b, err
-}
-
-// GetBuildCountByStatus gets a count of all builds by status from the database.
-func (c *client) GetBuildCountByStatus(status string) (int64, error) {
- c.Logger.Tracef("getting count of builds by status %s from the database", status)
-
- // variable to store query results
- var b int64
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableBuild).
- Raw(dml.SelectBuildsCountByStatus, status).
- Pluck("count", &b).Error
-
- return b, err
-}
-
-// GetOrgBuildCount gets the count of all builds by repo ID from the database.
-func (c *client) GetOrgBuildCount(org string, filters map[string]interface{}) (int64, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": org,
- }).Tracef("getting count of builds for org %s from the database", org)
-
- // variable to store query results
- var b int64
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableBuild).
- Joins("JOIN repos ON builds.repo_id = repos.id and repos.org = ?", org).
- Where(filters).
- Count(&b).Error
-
- return b, err
-}
-
-// GetRepoBuildCount gets the count of all builds by repo ID from the database.
-func (c *client) GetRepoBuildCount(r *library.Repo, filters map[string]interface{}) (int64, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": r.GetOrg(),
- "name": r.GetName(),
- }).Tracef("getting count of builds for repo %s from the database", r.GetFullName())
-
- // variable to store query results
- var b int64
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableBuild).
- Where("repo_id = ?", r.GetID()).
- Where(filters).
- Count(&b).Error
-
- return b, err
-}
diff --git a/database/postgres/build_count_test.go b/database/postgres/build_count_test.go
deleted file mode 100644
index ba5a795ca..000000000
--- a/database/postgres/build_count_test.go
+++ /dev/null
@@ -1,340 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "reflect"
- "testing"
-
- "github.com/DATA-DOG/go-sqlmock"
-
- "github.com/go-vela/server/database/postgres/dml"
-
- "gorm.io/gorm"
-)
-
-func TestPostgres_Client_GetBuildCount(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetDeployPayload(nil)
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectBuildsCount).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.GetBuildCount()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetBuildCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetBuildCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetBuildCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetBuildCountByStatus(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetDeployPayload(nil)
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectBuildsCountByStatus, "running").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.GetBuildCountByStatus("running")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetBuildCountByStatus should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetBuildCountByStatus returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetBuildCountByStatus is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetOrgBuildCount(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetDeployPayload(nil)
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
-
- // ensure the mock expects the query
- _mock.ExpectQuery("SELECT count(*) FROM \"builds\" JOIN repos ON builds.repo_id = repos.id and repos.org = $1").WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 2,
- },
- }
- filters := map[string]interface{}{}
- // run tests
- for _, test := range tests {
- got, err := _database.GetOrgBuildCount("foo", filters)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetOrgBuildCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetOrgBuildCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetOrgBuildCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetOrgBuildCountByEvent(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetDeployPayload(nil)
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
-
- // ensure the mock expects the query
- _mock.ExpectQuery("SELECT count(*) FROM \"builds\" JOIN repos ON builds.repo_id = repos.id and repos.org = $1 WHERE \"event\" = $2").WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 2,
- },
- }
- filters := map[string]interface{}{
- "event": "push",
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetOrgBuildCount("foo", filters)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetOrgBuildCountByEvent should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetOrgBuildCountByEvent returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetOrgBuildCountByEvent is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetRepoBuildCount(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetDeployPayload(nil)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
-
- // ensure the mock expects the query
- _mock.ExpectQuery(`SELECT count(*) FROM "builds" WHERE repo_id = $1`).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 2,
- },
- }
-
- filters := map[string]interface{}{}
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetRepoBuildCount(_repo, filters)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepoBuildCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepoBuildCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepoBuildCount is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/postgres/build_list.go b/database/postgres/build_list.go
deleted file mode 100644
index a40cb93c3..000000000
--- a/database/postgres/build_list.go
+++ /dev/null
@@ -1,178 +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"
-)
-
-// GetBuildList gets a list of all builds from the database.
-func (c *client) GetBuildList() ([]*library.Build, error) {
- c.Logger.Trace("listing builds from the database")
-
- // variable to store query results
- b := new([]database.Build)
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableBuild).
- Raw(dml.ListBuilds).
- Scan(b).Error
-
- // variable we want to return
- builds := []*library.Build{}
- // iterate through all query results
- for _, build := range *b {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := build
-
- // convert query result to library type
- builds = append(builds, tmp.ToLibrary())
- }
-
- return builds, err
-}
-
-// GetDeploymentBuildList gets a list of all builds from the database.
-func (c *client) GetDeploymentBuildList(deployment string) ([]*library.Build, error) {
- c.Logger.WithFields(logrus.Fields{
- "deployment": deployment,
- }).Tracef("listing builds for deployment %s from the database", deployment)
-
- // variable to store query results
- b := new([]database.Build)
-
- filters := map[string]string{}
- if len(deployment) > 0 {
- filters["source"] = deployment
- }
- // send query to the database and store result in variable
- //
- // nolint: gomnd // ignore magic number
- err := c.Postgres.
- Table(constants.TableBuild).
- Select("*").
- Where(filters).
- Limit(3).
- Order("number DESC").
- Scan(b).Error
-
- // variable we want to return
- builds := []*library.Build{}
- // iterate through all query results
- for _, build := range *b {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := build
-
- // convert query result to library type
- builds = append(builds, tmp.ToLibrary())
- }
-
- return builds, err
-}
-
-// GetOrgBuildList gets a list of all builds by org name and allows filters from the database.
-//
-// nolint: lll // ignore long line length due to variable names
-func (c *client) GetOrgBuildList(org string, filters map[string]interface{}, page, perPage int) ([]*library.Build, int64, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": org,
- }).Tracef("listing builds for org %s from the database", org)
-
- // variables to store query results
- b := new([]database.Build)
- builds := []*library.Build{}
- count := int64(0)
-
- // count the results
- count, err := c.GetOrgBuildCount(org, filters)
- if err != nil {
- return builds, 0, err
- }
-
- // short-circuit if there are no results
- if count == 0 {
- return builds, 0, nil
- }
-
- // 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.TableBuild).
- Select("builds.*").
- Joins("JOIN repos ON builds.repo_id = repos.id and repos.org = ?", org).
- Where(filters).
- Order("created DESC").
- Order("id").
- Limit(perPage).
- Offset(offset).
- Scan(b).Error
-
- // iterate through all query results
- for _, build := range *b {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := build
-
- // convert query result to library type
- builds = append(builds, tmp.ToLibrary())
- }
-
- return builds, count, err
-}
-
-// GetRepoBuildList gets a list of all builds by repo ID from the database.
-//
-// nolint: lll // ignore long line length due to variable names
-func (c *client) GetRepoBuildList(r *library.Repo, filters map[string]interface{}, page, perPage int) ([]*library.Build, int64, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("listing builds for repo %s from the database", r.GetFullName())
-
- // variable to store query results
- b := new([]database.Build)
- builds := []*library.Build{}
- count := int64(0)
-
- // count the results
- count, err := c.GetRepoBuildCount(r, filters)
- if err != nil {
- return builds, 0, err
- }
-
- // short-circuit if there are no results
- if count == 0 {
- return builds, 0, nil
- }
-
- // calculate offset for pagination through results
- offset := perPage * (page - 1)
-
- err = c.Postgres.
- Table(constants.TableBuild).
- Where("repo_id = ?", r.GetID()).
- Where(filters).
- Order("number DESC").
- Limit(perPage).
- Offset(offset).
- Scan(b).Error
-
- // iterate through all query results
- for _, build := range *b {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := build
-
- // convert query result to library type
- builds = append(builds, tmp.ToLibrary())
- }
-
- return builds, count, err
-}
diff --git a/database/postgres/build_list_test.go b/database/postgres/build_list_test.go
deleted file mode 100644
index 247e4c282..000000000
--- a/database/postgres/build_list_test.go
+++ /dev/null
@@ -1,446 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "reflect"
- "testing"
-
- "github.com/DATA-DOG/go-sqlmock"
-
- "github.com/go-vela/server/database/postgres/dml"
- "github.com/go-vela/types/library"
-
- "gorm.io/gorm"
-)
-
-func TestPostgres_Client_GetBuildList(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetDeployPayload(nil)
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.ListBuilds).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "repo_id", "number", "parent", "event", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"},
- ).AddRow(1, 1, 1, 0, "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0).
- AddRow(2, 1, 2, 0, "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0)
-
- // ensure the mock expects the query
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Build
- }{
- {
- failure: false,
- want: []*library.Build{_buildOne, _buildTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetBuildList()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetBuildList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetBuildList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetBuildList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetDeploymentBuildList(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetDeployPayload(nil)
- _buildOne.SetSource("https://github.com/github/octocat/deployments/1")
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetDeployPayload(nil)
- _buildTwo.SetSource("https://github.com/github/octocat/deployments/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() }()
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "repo_id", "number", "parent", "event", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"},
- ).AddRow(2, 1, 2, 0, "", "", "", 0, 0, 0, 0, "", nil, "", "https://github.com/github/octocat/deployments/1", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0).
- AddRow(1, 1, 1, 0, "", "", "", 0, 0, 0, 0, "", nil, "", "https://github.com/github/octocat/deployments/1", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0)
-
- // ensure the mock expects the query
- _mock.ExpectQuery("SELECT * FROM \"builds\" WHERE \"source\" = $1 ORDER BY number DESC LIMIT 3").WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Build
- }{
- {
- failure: false,
- want: []*library.Build{_buildTwo, _buildOne},
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetDeploymentBuildList("https://github.com/github/octocat/deployments/1")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetDeploymentBuildList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetDeploymentBuildList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetDeploymentBuildList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetOrgBuildList(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetDeployPayload(nil)
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
-
- // ensure the mock expects the query
- _mock.ExpectQuery("SELECT count(*) FROM \"builds\" JOIN repos ON builds.repo_id = repos.id and repos.org = $1").WillReturnRows(_rows)
-
- // create expected return in mock
- _rows = sqlmock.NewRows(
- []string{"id", "repo_id", "number", "parent", "event", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"},
- ).AddRow(1, 1, 1, 0, "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0).
- AddRow(2, 1, 2, 0, "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0)
-
- // ensure the mock expects the query
- _mock.ExpectQuery("SELECT builds.* FROM \"builds\" JOIN repos ON builds.repo_id = repos.id and repos.org = $1 ORDER BY created DESC,id LIMIT 10").WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Build
- }{
- {
- failure: false,
- want: []*library.Build{_buildOne, _buildTwo},
- },
- }
-
- filters := map[string]interface{}{}
-
- // run tests
- for _, test := range tests {
- got, _, err := _database.GetOrgBuildList("foo", filters, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetOrgBuildList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetOrgBuildList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetOrgBuildList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetOrgBuildList_NonAdmin(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetDeployPayload(nil)
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
-
- // ensure the mock expects the query
- _mock.ExpectQuery("SELECT count(*) FROM \"builds\" JOIN repos ON builds.repo_id = repos.id and repos.org = $1 WHERE \"visibility\" = $2").WillReturnRows(_rows)
-
- // create expected return in mock
- _rows = sqlmock.NewRows(
- []string{"id", "repo_id", "number", "parent", "event", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"},
- ).AddRow(1, 1, 1, 0, "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0)
-
- // ensure the mock expects the query
- _mock.ExpectQuery("SELECT builds.* FROM \"builds\" JOIN repos ON builds.repo_id = repos.id and repos.org = $1 WHERE \"visibility\" = $2 ORDER BY created DESC,id LIMIT 10").WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Build
- }{
- {
- failure: false,
- want: []*library.Build{_buildOne},
- },
- }
-
- filters := map[string]interface{}{
- "visibility": "public",
- }
-
- // run tests
- for _, test := range tests {
- got, _, err := _database.GetOrgBuildList("foo", filters, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetOrgBuildList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetOrgBuildList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetOrgBuildList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetOrgBuildListByEvent(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetDeployPayload(nil)
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
-
- // ensure the mock expects the query
- _mock.ExpectQuery("SELECT count(*) FROM \"builds\" JOIN repos ON builds.repo_id = repos.id and repos.org = $1 WHERE \"event\" = $2").WillReturnRows(_rows)
-
- // create expected return in mock
- _rows = sqlmock.NewRows(
- []string{"id", "repo_id", "number", "parent", "event", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"},
- ).AddRow(1, 1, 1, 0, "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0).
- AddRow(2, 1, 2, 0, "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0)
-
- // ensure the mock expects the query
- _mock.ExpectQuery("SELECT builds.* FROM \"builds\" JOIN repos ON builds.repo_id = repos.id and repos.org = $1 WHERE \"event\" = $2 ORDER BY created DESC,id LIMIT 10").WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Build
- }{
- {
- failure: false,
- want: []*library.Build{_buildOne, _buildTwo},
- },
- }
-
- filters := map[string]interface{}{
- "event": "push",
- }
-
- // run tests
- for _, test := range tests {
- got, _, err := _database.GetOrgBuildList("foo", filters, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetOrgBuildListByEvent should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetOrgBuildListByEvent returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetOrgBuildListByEvent is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetRepoBuildList(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetDeployPayload(nil)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
-
- // ensure the mock expects the query
- _mock.ExpectQuery(`SELECT count(*) FROM "builds" WHERE repo_id = $1`).WillReturnRows(_rows)
-
- // create expected return in mock
- _rows = sqlmock.NewRows(
- []string{"id", "repo_id", "number", "parent", "event", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"},
- ).AddRow(1, 1, 1, 0, "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0).
- AddRow(2, 1, 2, 0, "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0)
-
- // ensure the mock expects the query
- _mock.ExpectQuery(`SELECT * FROM "builds" WHERE repo_id = $1 ORDER BY number DESC LIMIT 10`).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Build
- }{
- {
- failure: false,
- want: []*library.Build{_buildOne, _buildTwo},
- },
- }
-
- filters := map[string]interface{}{}
-
- // run tests
- for _, test := range tests {
- got, _, err := _database.GetRepoBuildList(_repo, filters, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepoBuildList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepoBuildList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepoBuildList is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/postgres/build_test.go b/database/postgres/build_test.go
deleted file mode 100644
index ea15b41d8..000000000
--- a/database/postgres/build_test.go
+++ /dev/null
@@ -1,515 +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_GetBuild(t *testing.T) {
- // setup types
- _build := testBuild()
- _build.SetID(1)
- _build.SetRepoID(1)
- _build.SetNumber(1)
- _build.SetDeployPayload(nil)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectRepoBuild, 1, 1).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "repo_id", "number", "parent", "event", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"},
- ).AddRow(1, 1, 1, 0, "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 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.Build
- }{
- {
- failure: false,
- want: _build,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetBuild(1, _repo)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetBuild should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetBuild returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetBuild is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetLastBuild(t *testing.T) {
- // setup types
- _build := testBuild()
- _build.SetID(1)
- _build.SetRepoID(1)
- _build.SetNumber(1)
- _build.SetDeployPayload(nil)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectLastRepoBuild, 1).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "repo_id", "number", "parent", "event", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"},
- ).AddRow(1, 1, 1, 0, "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 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.Build
- }{
- {
- failure: false,
- want: _build,
- },
- {
- failure: false,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetLastBuild(_repo)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetLastBuild should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetLastBuild returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetLastBuild is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetLastBuildByBranch(t *testing.T) {
- // setup types
- _build := testBuild()
- _build.SetID(1)
- _build.SetRepoID(1)
- _build.SetNumber(1)
- _build.SetDeployPayload(nil)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectLastRepoBuildByBranch, 1, "master").Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "repo_id", "number", "parent", "event", "status", "error", "enqueued", "created", "started", "finished", "deploy", "deploy_payload", "clone", "source", "title", "message", "commit", "sender", "author", "email", "link", "branch", "ref", "base_ref", "head_ref", "host", "runtime", "distribution", "timestamp"},
- ).AddRow(1, 1, 1, 0, "", "", "", 0, 0, 0, 0, "", nil, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 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.Build
- }{
- {
- failure: false,
- want: _build,
- },
- {
- failure: false,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetLastBuildByBranch(_repo, "master")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetLastBuildByBranch should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetLastBuildByBranch returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetLastBuildByBranch is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetPendingAndRunningBuilds(t *testing.T) {
- // setup types
- _buildOne := new(library.BuildQueue)
- _buildOne.SetCreated(0)
- _buildOne.SetFullName("")
- _buildOne.SetNumber(1)
- _buildOne.SetStatus("")
-
- _buildTwo := new(library.BuildQueue)
- _buildTwo.SetCreated(0)
- _buildTwo.SetFullName("")
- _buildTwo.SetNumber(2)
- _buildTwo.SetStatus("")
-
- // 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.SelectPendingAndRunningBuilds, "").Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"created", "full_name", "number", "status"}).
- AddRow(0, "", 1, "").AddRow(0, "", 2, "")
-
- // 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.BuildQueue
- }{
- {
- failure: false,
- want: []*library.BuildQueue{_buildOne, _buildTwo},
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetPendingAndRunningBuilds("")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetPendingAndRunningBuilds should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetPendingAndRunningBuilds returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetPendingAndRunningBuilds is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_CreateBuild(t *testing.T) {
- // setup types
- _build := testBuild()
- _build.SetID(1)
- _build.SetRepoID(1)
- _build.SetNumber(1)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"id"}).AddRow(1)
-
- // ensure the mock expects the query
- _mock.ExpectQuery(`INSERT INTO "builds" ("repo_id","number","parent","event","status","error","enqueued","created","started","finished","deploy","deploy_payload","clone","source","title","message","commit","sender","author","email","link","branch","ref","base_ref","head_ref","host","runtime","distribution","id") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29) RETURNING "id"`).
- WithArgs(1, 1, nil, nil, nil, nil, nil, nil, nil, nil, nil, AnyArgument{}, nil, nil, nil, nil, nil, nil, 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.CreateBuild(_build)
-
- if test.failure {
- if err == nil {
- t.Errorf("CreateBuild should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("CreateBuild returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_Client_UpdateBuild(t *testing.T) {
- // setup types
- _build := testBuild()
- _build.SetID(1)
- _build.SetRepoID(1)
- _build.SetNumber(1)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // ensure the mock expects the query
- _mock.ExpectExec(`UPDATE "builds" SET "repo_id"=$1,"number"=$2,"parent"=$3,"event"=$4,"status"=$5,"error"=$6,"enqueued"=$7,"created"=$8,"started"=$9,"finished"=$10,"deploy"=$11,"deploy_payload"=$12,"clone"=$13,"source"=$14,"title"=$15,"message"=$16,"commit"=$17,"sender"=$18,"author"=$19,"email"=$20,"link"=$21,"branch"=$22,"ref"=$23,"base_ref"=$24,"head_ref"=$25,"host"=$26,"runtime"=$27,"distribution"=$28 WHERE "id" = $29`).
- WithArgs(1, 1, nil, nil, nil, nil, nil, nil, nil, nil, nil, AnyArgument{}, nil, nil, nil, nil, nil, nil, 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.UpdateBuild(_build)
-
- if test.failure {
- if err == nil {
- t.Errorf("UpdateBuild should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("UpdateBuild returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_Client_DeleteBuild(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.DeleteBuild, 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.DeleteBuild(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("DeleteBuild should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("DeleteBuild returned err: %v", err)
- }
- }
-}
-
-// testBuild is a test helper function to create a
-// library Build type with all fields set to their
-// zero values.
-func testBuild() *library.Build {
- i64 := int64(0)
- i := 0
- str := ""
-
- return &library.Build{
- ID: &i64,
- RepoID: &i64,
- Number: &i,
- Parent: &i,
- Event: &str,
- Status: &str,
- Error: &str,
- Enqueued: &i64,
- Created: &i64,
- Started: &i64,
- Finished: &i64,
- Deploy: &str,
- Clone: &str,
- Source: &str,
- Title: &str,
- Message: &str,
- Commit: &str,
- Sender: &str,
- Author: &str,
- Email: &str,
- Link: &str,
- Branch: &str,
- Ref: &str,
- BaseRef: &str,
- HeadRef: &str,
- Host: &str,
- Runtime: &str,
- Distribution: &str,
- }
-}
diff --git a/database/postgres/ddl/build.go b/database/postgres/ddl/build.go
deleted file mode 100644
index da8f60295..000000000
--- a/database/postgres/ddl/build.go
+++ /dev/null
@@ -1,74 +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 (
- // CreateBuildTable represents a query to
- // create the builds table for Vela.
- CreateBuildTable = `
-CREATE TABLE
-IF NOT EXISTS
-builds (
- id SERIAL PRIMARY KEY,
- repo_id INTEGER,
- number INTEGER,
- parent INTEGER,
- event VARCHAR(250),
- status VARCHAR(250),
- error VARCHAR(500),
- enqueued INTEGER,
- created INTEGER,
- started INTEGER,
- finished INTEGER,
- deploy VARCHAR(500),
- deploy_payload VARCHAR(2000),
- clone VARCHAR(1000),
- source VARCHAR(1000),
- title VARCHAR(1000),
- message VARCHAR(2000),
- commit VARCHAR(500),
- sender VARCHAR(250),
- author VARCHAR(250),
- email VARCHAR(500),
- link VARCHAR(1000),
- branch VARCHAR(500),
- ref VARCHAR(500),
- base_ref VARCHAR(500),
- head_ref VARCHAR(500),
- host VARCHAR(250),
- runtime VARCHAR(250),
- distribution VARCHAR(250),
- timestamp INTEGER,
- UNIQUE(repo_id, number)
-);
-`
-
- // CreateBuildRepoIDIndex represents a query to create an
- // index on the builds table for the repo_id column.
- CreateBuildRepoIDIndex = `
-CREATE INDEX
-IF NOT EXISTS
-builds_repo_id
-ON builds (repo_id);
-`
-
- // CreateBuildStatusIndex represents a query to create an
- // index on the builds table for the status column.
- CreateBuildStatusIndex = `
-CREATE INDEX
-IF NOT EXISTS
-builds_status
-ON builds (status);
-`
-
- // CreateBuildCreatedIndex represents a query to create an
- // index on the builds table for the created column.
- CreateBuildCreatedIndex = `
-CREATE INDEX CONCURRENTLY
-IF NOT EXISTS
-builds_created
-ON builds (created);
-`
-)
diff --git a/database/postgres/ddl/doc.go b/database/postgres/ddl/doc.go
deleted file mode 100644
index 6a21d9d02..000000000
--- a/database/postgres/ddl/doc.go
+++ /dev/null
@@ -1,12 +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 provides the Postgres data definition language (DDL) for Vela.
-//
-// https://en.wikipedia.org/wiki/Data_definition_language
-//
-// Usage:
-//
-// import "github.com/go-vela/server/database/postgres/ddl"
-package ddl
diff --git a/database/postgres/ddl/hook.go b/database/postgres/ddl/hook.go
deleted file mode 100644
index 98e93e1e1..000000000
--- a/database/postgres/ddl/hook.go
+++ /dev/null
@@ -1,38 +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 (
- // CreateHookTable represents a query to
- // create the hooks table for Vela.
- CreateHookTable = `
-CREATE TABLE
-IF NOT EXISTS
-hooks (
- id SERIAL PRIMARY KEY,
- repo_id INTEGER,
- build_id INTEGER,
- number INTEGER,
- source_id VARCHAR(250),
- created INTEGER,
- host VARCHAR(250),
- event VARCHAR(250),
- branch VARCHAR(500),
- error VARCHAR(500),
- status VARCHAR(250),
- link VARCHAR(1000),
- UNIQUE(repo_id, number)
-);
-`
-
- // CreateHookRepoIDIndex represents a query to create an
- // index on the hooks table for the repo_id column.
- CreateHookRepoIDIndex = `
-CREATE INDEX
-IF NOT EXISTS
-hooks_repo_id
-ON hooks (repo_id);
-`
-)
diff --git a/database/postgres/ddl/log.go b/database/postgres/ddl/log.go
deleted file mode 100644
index f9c9d7bd9..000000000
--- a/database/postgres/ddl/log.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 (
- // CreateLogTable represents a query to
- // create the logs table for Vela.
- CreateLogTable = `
-CREATE TABLE
-IF NOT EXISTS
-logs (
- id SERIAL PRIMARY KEY,
- build_id INTEGER,
- repo_id INTEGER,
- service_id INTEGER,
- step_id INTEGER,
- data BYTEA,
- UNIQUE(step_id),
- UNIQUE(service_id)
-);
-`
-
- // CreateLogBuildIDIndex represents a query to create an
- // index on the logs table for the build_id column.
- CreateLogBuildIDIndex = `
-CREATE INDEX
-IF NOT EXISTS
-logs_build_id
-ON logs (build_id);
-`
-)
diff --git a/database/postgres/ddl/repo.go b/database/postgres/ddl/repo.go
deleted file mode 100644
index f9b01d274..000000000
--- a/database/postgres/ddl/repo.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package ddl
-
-const (
- // CreateRepoTable represents a query to
- // create the repos table for Vela.
- CreateRepoTable = `
-CREATE TABLE
-IF NOT EXISTS
-repos (
- id SERIAL PRIMARY KEY,
- user_id INTEGER,
- hash VARCHAR(500),
- org VARCHAR(250),
- name VARCHAR(250),
- full_name VARCHAR(500),
- link VARCHAR(1000),
- clone VARCHAR(1000),
- branch VARCHAR(250),
- build_limit INTEGER,
- timeout INTEGER,
- counter INTEGER,
- visibility TEXT,
- private BOOLEAN,
- trusted BOOLEAN,
- active BOOLEAN,
- allow_pull BOOLEAN,
- allow_push BOOLEAN,
- allow_deploy BOOLEAN,
- allow_tag BOOLEAN,
- allow_comment BOOLEAN,
- pipeline_type TEXT,
- previous_name VARCHAR(100),
- UNIQUE(full_name)
-);
-`
-
- // CreateRepoOrgNameIndex represents a query to create an
- // index on the repos table for the org and name columns.
- CreateRepoOrgNameIndex = `
-CREATE INDEX
-IF NOT EXISTS
-repos_org_name
-ON repos (org, name);
-`
-)
diff --git a/database/postgres/ddl/secret.go b/database/postgres/ddl/secret.go
deleted file mode 100644
index 6139c783a..000000000
--- a/database/postgres/ddl/secret.go
+++ /dev/null
@@ -1,65 +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 (
- // CreateSecretTable represents a query to
- // create the secrets table for Vela.
- CreateSecretTable = `
-CREATE TABLE
-IF NOT EXISTS
-secrets (
- id SERIAL PRIMARY KEY,
- type VARCHAR(100),
- org VARCHAR(250),
- repo VARCHAR(250),
- team VARCHAR(250),
- name VARCHAR(250),
- value BYTEA,
- images VARCHAR(1000),
- events VARCHAR(1000),
- allow_command BOOLEAN,
- created_at INTEGER,
- created_by VARCHAR(250),
- updated_at INTEGER,
- updated_by VARCHAR(250),
- UNIQUE(type, org, repo, name),
- UNIQUE(type, org, team, name)
-);
-`
-
- // CreateSecretTypeOrgRepo represents a query to create an
- // index on the secrets table for the type, org and repo columns.
- //
- // nolint: gosec // ignore false positive
- CreateSecretTypeOrgRepo = `
-CREATE INDEX
-IF NOT EXISTS
-secrets_type_org_repo
-ON secrets (type, org, repo);
-`
-
- // CreateSecretTypeOrgTeam represents a query to create an
- // index on the secrets table for the type, org and team columns.
- //
- // nolint: gosec // ignore false positive
- CreateSecretTypeOrgTeam = `
-CREATE INDEX
-IF NOT EXISTS
-secrets_type_org_team
-ON secrets (type, org, team);
-`
-
- // CreateSecretTypeOrg represents a query to create an
- // index on the secrets table for the type, and org columns.
- //
- // nolint: gosec // ignore false positive
- CreateSecretTypeOrg = `
-CREATE INDEX
-IF NOT EXISTS
-secrets_type_org
-ON secrets (type, org);
-`
-)
diff --git a/database/postgres/ddl/service.go b/database/postgres/ddl/service.go
deleted file mode 100644
index b714586b5..000000000
--- a/database/postgres/ddl/service.go
+++ /dev/null
@@ -1,32 +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 (
- // CreateServiceTable represents a query to
- // create the services table for Vela.
- CreateServiceTable = `
-CREATE TABLE
-IF NOT EXISTS
-services (
- id SERIAL PRIMARY KEY,
- repo_id INTEGER,
- build_id INTEGER,
- number INTEGER,
- name VARCHAR(250),
- image VARCHAR(500),
- 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/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/ddl/user.go b/database/postgres/ddl/user.go
deleted file mode 100644
index b6fec0794..000000000
--- a/database/postgres/ddl/user.go
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package ddl
-
-const (
- // CreateUserTable represents a query to
- // create the users table for Vela.
- CreateUserTable = `
-CREATE TABLE
-IF NOT EXISTS
-users (
- id SERIAL PRIMARY KEY,
- name VARCHAR(250),
- refresh_token VARCHAR(500),
- token VARCHAR(500),
- hash VARCHAR(500),
- favorites VARCHAR(5000),
- active BOOLEAN,
- admin BOOLEAN,
- UNIQUE(name)
-);
-`
-
- // CreateUserRefreshIndex represents a query to create an
- // index on the users table for the refresh_token column.
- CreateUserRefreshIndex = `
-CREATE INDEX
-IF NOT EXISTS
-users_refresh
-ON users (refresh_token);
-`
-)
diff --git a/database/postgres/ddl/worker.go b/database/postgres/ddl/worker.go
deleted file mode 100644
index dd8658e7c..000000000
--- a/database/postgres/ddl/worker.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package ddl
-
-const (
- // CreateWorkerTable represents a query to
- // create the workers table for Vela.
- CreateWorkerTable = `
-CREATE TABLE
-IF NOT EXISTS
-workers (
- id SERIAL PRIMARY KEY,
- hostname VARCHAR(250),
- address VARCHAR(250),
- routes VARCHAR(1000),
- active BOOLEAN,
- last_checked_in INTEGER,
- build_limit INTEGER,
- UNIQUE(hostname)
-);
-`
-
- // CreateWorkerHostnameAddressIndex represents a query to create an
- // index on the workers table for the hostname and address columns.
- CreateWorkerHostnameAddressIndex = `
-CREATE INDEX
-IF NOT EXISTS
-workers_hostname_address
-ON workers (hostname, address);
-`
-)
diff --git a/database/postgres/dml/build.go b/database/postgres/dml/build.go
deleted file mode 100644
index 115e7f0a5..000000000
--- a/database/postgres/dml/build.go
+++ /dev/null
@@ -1,81 +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 (
- // ListBuilds represents a query to
- // list all builds in the database.
- ListBuilds = `
-SELECT *
-FROM builds;
-`
-
- // SelectRepoBuild represents a query to select
- // a build for a repo_id in the database.
- SelectRepoBuild = `
-SELECT *
-FROM builds
-WHERE repo_id = ?
-AND number = ?
-LIMIT 1;
-`
-
- // SelectLastRepoBuild represents a query to select
- // the last build for a repo_id in the database.
- SelectLastRepoBuild = `
-SELECT *
-FROM builds
-WHERE repo_id = ?
-ORDER BY number DESC
-LIMIT 1;
-`
-
- // SelectLastRepoBuildByBranch represents a query to
- // select the last build for a repo_id and branch name
- // in the database.
- SelectLastRepoBuildByBranch = `
-SELECT *
-FROM builds
-WHERE repo_id = ?
-AND branch = ?
-ORDER BY number DESC
-LIMIT 1;
-`
-
- // SelectBuildsCount represents a query to select
- // the count of builds in the database.
- SelectBuildsCount = `
-SELECT count(*) as count
-FROM builds;
-`
-
- // SelectBuildsCountByStatus represents a query to select
- // the count of builds for a status in the database.
- SelectBuildsCountByStatus = `
-SELECT count(*) as count
-FROM builds
-WHERE status = ?;
-`
-
- // DeleteBuild represents a query to
- // remove a build from the database.
- DeleteBuild = `
-DELETE
-FROM builds
-WHERE id = ?;
-`
-
- // SelectPendingAndRunningBuilds represents a joined query
- // between the builds & repos table to select
- // the created builds that are in pending or running builds status
- // since the specified timeframe.
- SelectPendingAndRunningBuilds = `
-SELECT builds.created, builds.number, builds.status, repos.full_name
-FROM builds INNER JOIN repos
-ON builds.repo_id = repos.id
-WHERE builds.created > ?
-AND (builds.status = 'running' OR builds.status = 'pending');
-`
-)
diff --git a/database/postgres/dml/doc.go b/database/postgres/dml/doc.go
deleted file mode 100644
index cdab7d21b..000000000
--- a/database/postgres/dml/doc.go
+++ /dev/null
@@ -1,12 +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 provides the Postgres data manipulation language (DML) for Vela.
-//
-// https://en.wikipedia.org/wiki/Data_manipulation_language
-//
-// Usage:
-//
-// import "github.com/go-vela/server/database/postgres/dml"
-package dml
diff --git a/database/postgres/dml/hook.go b/database/postgres/dml/hook.go
deleted file mode 100644
index b06d79ad5..000000000
--- a/database/postgres/dml/hook.go
+++ /dev/null
@@ -1,61 +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 (
- // ListHooks represents a query to
- // list all webhooks in the database.
- ListHooks = `
-SELECT *
-FROM hooks;
-`
-
- // ListRepoHooks represents a query to list
- // all webhooks for a repo_id in the database.
- ListRepoHooks = `
-SELECT *
-FROM hooks
-WHERE repo_id = ?
-ORDER BY id DESC
-LIMIT ?
-OFFSET ?;
-`
-
- // SelectRepoHookCount represents a query to select
- // the count of webhooks for a repo_id in the database.
- SelectRepoHookCount = `
-SELECT count(*) as count
-FROM hooks
-WHERE repo_id = ?;
-`
-
- // SelectRepoHook represents a query to select
- // a webhook for a repo_id in the database.
- SelectRepoHook = `
-SELECT *
-FROM hooks
-WHERE repo_id = ?
-AND number = ?
-LIMIT 1;
-`
-
- // SelectLastRepoHook represents a query to select
- // the last hook for a repo_id in the database.
- SelectLastRepoHook = `
-SELECT *
-FROM hooks
-WHERE repo_id = ?
-ORDER BY number DESC
-LIMIT 1;
-`
-
- // DeleteHook represents a query to
- // remove a webhook from the database.
- DeleteHook = `
-DELETE
-FROM hooks
-WHERE id = ?;
-`
-)
diff --git a/database/postgres/dml/log.go b/database/postgres/dml/log.go
deleted file mode 100644
index e3e904cc4..000000000
--- a/database/postgres/dml/log.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package dml
-
-const (
- // ListLogs represents a query to
- // list all logs in the database.
- ListLogs = `
-SELECT *
-FROM logs;
-`
-
- // ListBuildLogs represents a query to list
- // all logs for a build_id in the database.
- ListBuildLogs = `
-SELECT *
-FROM logs
-WHERE build_id = ?
-ORDER BY step_id ASC;
-`
-
- // SelectStepLog represents a query to select
- // a log for a step_id in the database.
- SelectStepLog = `
-SELECT *
-FROM logs
-WHERE step_id = ?
-LIMIT 1;
-`
-
- // SelectServiceLog represents a query to select
- // a log for a service_id in the database.
- SelectServiceLog = `
-SELECT *
-FROM logs
-WHERE service_id = ?
-LIMIT 1;
-`
-
- // DeleteLog represents a query to
- // remove a log from the database.
- DeleteLog = `
-DELETE
-FROM logs
-WHERE id = ?;
-`
-)
diff --git a/database/postgres/dml/repo.go b/database/postgres/dml/repo.go
deleted file mode 100644
index 3de708b3e..000000000
--- a/database/postgres/dml/repo.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package dml
-
-const (
- // ListRepos represents a query to
- // list all repos in the database.
- ListRepos = `
-SELECT *
-FROM repos;
-`
-
- // ListUserRepos represents a query to list
- // all repos for a user_id in the database.
- ListUserRepos = `
-SELECT *
-FROM repos
-WHERE user_id = ?
-ORDER BY id DESC
-LIMIT ?
-OFFSET ?;
-`
-
- // SelectRepo represents a query to select a
- // repo for an org and name in the database.
- SelectRepo = `
-SELECT *
-FROM repos
-WHERE org = ?
-AND name = ?
-LIMIT 1;
-`
-
- // SelectUserReposCount represents a query to select
- // the count of repos for a user_id in the database.
- SelectUserReposCount = `
-SELECT count(*) as count
-FROM repos
-WHERE user_id = ?;
-`
-
- // SelectReposCount represents a query to select
- // the count of repos in the database.
- SelectReposCount = `
-SELECT count(*) as count
-FROM repos;
-`
-
- // DeleteRepo represents a query to
- // remove a repo from the database.
- DeleteRepo = `
-DELETE
-FROM repos
-WHERE id = ?;
-`
-)
diff --git a/database/postgres/dml/secret.go b/database/postgres/dml/secret.go
deleted file mode 100644
index 9fe094a51..000000000
--- a/database/postgres/dml/secret.go
+++ /dev/null
@@ -1,146 +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 (
- // ListSecrets represents a query to
- // list all secrets in the database.
- //
- // nolint: gosec // ignore false positive
- ListSecrets = `
-SELECT *
-FROM secrets;
-`
-
- // ListOrgSecrets represents a query to list all
- // secrets for a type and org in the database.
- //
- // nolint: gosec // ignore false positive
- ListOrgSecrets = `
-SELECT *
-FROM secrets
-WHERE type = 'org'
-AND org = ?
-ORDER BY id DESC
-LIMIT ?
-OFFSET ?;
-`
-
- // ListRepoSecrets represents a query to list all
- // secrets for a type, org and repo in the database.
- //
- // nolint: gosec // ignore false positive
- ListRepoSecrets = `
-SELECT *
-FROM secrets
-WHERE type = 'repo'
-AND org = ?
-AND repo = ?
-ORDER BY id DESC
-LIMIT ?
-OFFSET ?;
-`
-
- // ListSharedSecrets represents a query to list all
- // secrets for a type, org and team in the database.
- //
- // nolint: gosec // ignore false positive
- ListSharedSecrets = `
-SELECT *
-FROM secrets
-WHERE type = 'shared'
-AND org = ?
-AND team = ?
-ORDER BY id DESC
-LIMIT ?
-OFFSET ?;
-`
-
- // SelectOrgSecretsCount represents a query to select the
- // count of org secrets for an org in the database.
- //
- // nolint: gosec // ignore false positive
- SelectOrgSecretsCount = `
-SELECT count(*) as count
-FROM secrets
-WHERE type = 'org'
-AND org = ?;
-`
-
- // SelectRepoSecretsCount represents a query to select the
- // count of repo secrets for an org and repo in the database.
- //
- // nolint: gosec // ignore false positive
- SelectRepoSecretsCount = `
-SELECT count(*) as count
-FROM secrets
-WHERE type = 'repo'
-AND org = ?
-AND repo = ?;
-`
-
- // SelectSharedSecretsCount represents a query to select the
- // count of shared secrets for an org and repo in the database.
- //
- // nolint: gosec // ignore false positive
- SelectSharedSecretsCount = `
-SELECT count(*) as count
-FROM secrets
-WHERE type = 'shared'
-AND org = ?
-AND team = ?;
-`
-
- // SelectOrgSecret represents a query to select a
- // secret for an org and name in the database.
- //
- // nolint: gosec // ignore false positive
- SelectOrgSecret = `
-SELECT *
-FROM secrets
-WHERE type = 'org'
-AND org = ?
-AND name = ?
-LIMIT 1;
-`
-
- // SelectRepoSecret represents a query to select a
- // secret for an org, repo and name in the database.
- //
- // nolint: gosec // ignore false positive
- SelectRepoSecret = `
-SELECT *
-FROM secrets
-WHERE type = 'repo'
-AND org = ?
-AND repo = ?
-AND name = ?
-LIMIT 1;
-`
-
- // SelectSharedSecret represents a query to select a
- // secret for an org, team and name in the database.
- //
- // nolint: gosec // ignore false positive
- SelectSharedSecret = `
-SELECT *
-FROM secrets
-WHERE type = 'shared'
-AND org = ?
-AND team = ?
-AND name = ?
-LIMIT 1;
-`
-
- // DeleteSecret represents a query to
- // remove a secret from the database.
- //
- // nolint: gosec // ignore false positive
- DeleteSecret = `
-DELETE
-FROM secrets
-WHERE id = ?;
-`
-)
diff --git a/database/postgres/dml/service.go b/database/postgres/dml/service.go
deleted file mode 100644
index 66e009406..000000000
--- a/database/postgres/dml/service.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 (
- // ListServices represents a query to
- // list all services in the database.
- ListServices = `
-SELECT *
-FROM services;
-`
-
- // ListBuildServices represents a query to list
- // all services for a build_id in the database.
- ListBuildServices = `
-SELECT *
-FROM services
-WHERE build_id = ?
-ORDER BY id DESC
-LIMIT ?
-OFFSET ?;
-`
-
- // SelectBuildServicesCount represents a query to select
- // the count of services for a build_id in the database.
- SelectBuildServicesCount = `
-SELECT count(*) as count
-FROM services
-WHERE build_id = ?
-`
-
- // SelectServiceImagesCount represents a query to select
- // the count of an images appearances in the database.
- SelectServiceImagesCount = `
-SELECT image, count(image) as count
-FROM services
-GROUP BY image
-`
-
- // SelectServiceStatusesCount represents a query to select
- // the count of service status appearances in the database.
- SelectServiceStatusesCount = `
-SELECT status, count(status) as count
-FROM services
-GROUP BY status;
-`
-
- // SelectBuildService represents a query to select a
- // service for a build_id and number in the database.
- SelectBuildService = `
-SELECT *
-FROM services
-WHERE build_id = ?
-AND number = ?
-LIMIT 1;
-`
-
- // DeleteService represents a query to
- // remove a service from the database.
- DeleteService = `
-DELETE
-FROM services
-WHERE id = ?;
-`
-)
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/dml/user.go b/database/postgres/dml/user.go
deleted file mode 100644
index 9febce658..000000000
--- a/database/postgres/dml/user.go
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package dml
-
-const (
- // ListUsers represents a query to
- // list all users in the database.
- ListUsers = `
-SELECT *
-FROM users;
-`
-
- // ListLiteUsers represents a query to
- // list all lite users in the database.
- ListLiteUsers = `
-SELECT id, name
-FROM users
-ORDER BY id DESC
-LIMIT ?
-OFFSET ?;
-`
-
- // SelectUser represents a query to select
- // a user for an id in the database.
- SelectUser = `
-SELECT *
-FROM users
-WHERE id = ?
-LIMIT 1;
-`
-
- // SelectUserName represents a query to select
- // a user for a name in the database.
- SelectUserName = `
-SELECT *
-FROM users
-WHERE name = ?
-LIMIT 1;
-`
-
- // SelectUsersCount represents a query to select
- // the count of users in the database.
- SelectUsersCount = `
-SELECT count(*) as count
-FROM users;
-`
-
- // SelectRefreshToken represents a query to select
- // a user for a refresh_token in the database.
- //
- // nolint: gosec // ignore false positive
- SelectRefreshToken = `
-SELECT *
-FROM users
-WHERE refresh_token = ?
-LIMIT 1;
-`
-
- // DeleteUser represents a query to
- // remove a user from the database.
- DeleteUser = `
-DELETE
-FROM users
-WHERE id = ?;
-`
-)
diff --git a/database/postgres/dml/worker.go b/database/postgres/dml/worker.go
deleted file mode 100644
index 3ac74fd2e..000000000
--- a/database/postgres/dml/worker.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package dml
-
-const (
- // ListWorkers represents a query to
- // list all workers in the database.
- ListWorkers = `
-SELECT *
-FROM workers;
-`
-
- // SelectWorkersCount represents a query to select the
- // count of workers in the database.
- SelectWorkersCount = `
-SELECT count(*) as count
-FROM workers;
-`
-
- // SelectWorker represents a query to select a
- // worker by hostname in the database.
- SelectWorker = `
-SELECT *
-FROM workers
-WHERE hostname = ?
-LIMIT 1;
-`
-
- // SelectWorkerByAddress represents a query to select a
- // worker by address in the database.
- SelectWorkerByAddress = `
-SELECT *
-FROM workers
-WHERE address = ?
-LIMIT 1;
-`
-
- // DeleteWorker represents a query to
- // remove a worker from the database.
- DeleteWorker = `
-DELETE
-FROM workers
-WHERE id = ?;
-`
-)
diff --git a/database/postgres/doc.go b/database/postgres/doc.go
deleted file mode 100644
index 13879fdbd..000000000
--- a/database/postgres/doc.go
+++ /dev/null
@@ -1,11 +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 provides the ability for Vela to
-// integrate with Postgres as a SQL backend.
-//
-// Usage:
-//
-// import "github.com/go-vela/server/database/postgres"
-package postgres
diff --git a/database/postgres/driver.go b/database/postgres/driver.go
deleted file mode 100644
index 2a61cae42..000000000
--- a/database/postgres/driver.go
+++ /dev/null
@@ -1,12 +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/types/constants"
-
-// Driver outputs the configured database driver.
-func (c *client) Driver() string {
- return constants.DriverPostgres
-}
diff --git a/database/postgres/driver_test.go b/database/postgres/driver_test.go
deleted file mode 100644
index de49bc671..000000000
--- a/database/postgres/driver_test.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "reflect"
- "testing"
-
- "github.com/go-vela/types/constants"
-)
-
-func TestPostgres_Client_Driver(t *testing.T) {
- // setup types
- want := constants.DriverPostgres
-
- // setup the test database client
- _database, _, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // run test
- got := _database.Driver()
-
- if !reflect.DeepEqual(got, want) {
- t.Errorf("Driver is %v, want %v", got, want)
- }
-}
diff --git a/database/postgres/hook.go b/database/postgres/hook.go
deleted file mode 100644
index 9bd5f8a2d..000000000
--- a/database/postgres/hook.go
+++ /dev/null
@@ -1,122 +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"
-)
-
-// GetHook gets a hook by number and repo ID from the database.
-//
-// nolint: dupl // ignore similar code with build
-func (c *client) GetHook(number int, r *library.Repo) (*library.Hook, error) {
- c.Logger.WithFields(logrus.Fields{
- "hook": number,
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("getting hook %s/%d from the database", r.GetFullName(), number)
-
- // variable to store query results
- h := new(database.Hook)
-
- // send query to the database and store result in variable
- result := c.Postgres.
- Table(constants.TableHook).
- Raw(dml.SelectRepoHook, r.GetID(), number).
- Scan(h)
-
- // 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 h.ToLibrary(), result.Error
-}
-
-// GetLastHook gets the last hook by repo ID from the database.
-func (c *client) GetLastHook(r *library.Repo) (*library.Hook, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("getting last hook for repo %s from the database", r.GetFullName())
-
- // variable to store query results
- h := new(database.Hook)
-
- // send query to the database and store result in variable
- result := c.Postgres.
- Table(constants.TableHook).
- Raw(dml.SelectLastRepoHook, r.GetID()).
- Scan(h)
-
- // 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 {
- // the record will not exist if it's a new repo
- return nil, nil
- }
-
- return h.ToLibrary(), result.Error
-}
-
-// CreateHook creates a new hook in the database.
-func (c *client) CreateHook(h *library.Hook) error {
- c.Logger.WithFields(logrus.Fields{
- "hook": h.GetNumber(),
- }).Tracef("creating hook %d in the database", h.GetNumber())
-
- // cast to database type
- hook := database.HookFromLibrary(h)
-
- // validate the necessary fields are populated
- err := hook.Validate()
- if err != nil {
- return err
- }
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableHook).
- Create(hook).Error
-}
-
-// UpdateHook updates a hook in the database.
-func (c *client) UpdateHook(h *library.Hook) error {
- c.Logger.WithFields(logrus.Fields{
- "hook": h.GetNumber(),
- }).Tracef("updating hook %d in the database", h.GetNumber())
-
- // cast to database type
- hook := database.HookFromLibrary(h)
-
- // validate the necessary fields are populated
- err := hook.Validate()
- if err != nil {
- return err
- }
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableHook).
- Save(hook).Error
-}
-
-// DeleteHook deletes a hook by unique ID from the database.
-func (c *client) DeleteHook(id int64) error {
- c.Logger.Tracef("deleting hook %d in the database", id)
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableHook).
- Exec(dml.DeleteHook, id).Error
-}
diff --git a/database/postgres/hook_count.go b/database/postgres/hook_count.go
deleted file mode 100644
index 7d65f873e..000000000
--- a/database/postgres/hook_count.go
+++ /dev/null
@@ -1,31 +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"
-)
-
-// GetRepoHookCount gets the count of webhooks by repo ID from the database.
-func (c *client) GetRepoHookCount(r *library.Repo) (int64, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("getting count of hooks for repo %s from the database", r.GetFullName())
-
- // variable to store query results
- var h int64
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableHook).
- Raw(dml.SelectRepoHookCount, r.GetID()).
- Pluck("count", &h).Error
-
- return h, err
-}
diff --git a/database/postgres/hook_count_test.go b/database/postgres/hook_count_test.go
deleted file mode 100644
index c86f29446..000000000
--- a/database/postgres/hook_count_test.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 (
- "reflect"
- "testing"
-
- sqlmock "github.com/DATA-DOG/go-sqlmock"
-
- "github.com/go-vela/server/database/postgres/dml"
-
- "gorm.io/gorm"
-)
-
-func TestPostgres_Client_GetRepoHookCount(t *testing.T) {
- // setup types
- _hookOne := testHook()
- _hookOne.SetID(1)
- _hookOne.SetRepoID(1)
- _hookOne.SetBuildID(1)
- _hookOne.SetNumber(1)
- _hookOne.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- _hookTwo := testHook()
- _hookTwo.SetID(2)
- _hookTwo.SetRepoID(1)
- _hookTwo.SetBuildID(2)
- _hookTwo.SetNumber(2)
- _hookTwo.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/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.SelectRepoHookCount, 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.GetRepoHookCount(_repo)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepoHookCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepoHookCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepoHookCount is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/postgres/hook_list.go b/database/postgres/hook_list.go
deleted file mode 100644
index c46867fec..000000000
--- a/database/postgres/hook_list.go
+++ /dev/null
@@ -1,72 +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"
-)
-
-// GetHookList gets a list of all hooks from the database.
-func (c *client) GetHookList() ([]*library.Hook, error) {
- c.Logger.Trace("listing hooks from the database")
-
- // variable to store query results
- h := new([]database.Hook)
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableHook).
- Raw(dml.ListHooks).
- Scan(h).Error
-
- // variable we want to return
- hooks := []*library.Hook{}
- // iterate through all query results
- for _, hook := range *h {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := hook
-
- // convert query result to library type
- hooks = append(hooks, tmp.ToLibrary())
- }
-
- return hooks, err
-}
-
-// GetRepoHookList gets a list of hooks by repo ID from the database.
-func (c *client) GetRepoHookList(r *library.Repo, page, perPage int) ([]*library.Hook, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("listing hooks for repo %s from the database", r.GetFullName())
-
- // variable to store query results
- h := new([]database.Hook)
- // 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.TableHook).
- Raw(dml.ListRepoHooks, r.GetID(), perPage, offset).
- Scan(h).Error
-
- // variable we want to return
- hooks := []*library.Hook{}
- // iterate through all query results
- for _, hook := range *h {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := hook
-
- // convert query result to library type
- hooks = append(hooks, tmp.ToLibrary())
- }
-
- return hooks, err
-}
diff --git a/database/postgres/hook_list_test.go b/database/postgres/hook_list_test.go
deleted file mode 100644
index dbc1524fe..000000000
--- a/database/postgres/hook_list_test.go
+++ /dev/null
@@ -1,164 +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_GetHookList(t *testing.T) {
- // setup types
- _hookOne := testHook()
- _hookOne.SetID(1)
- _hookOne.SetRepoID(1)
- _hookOne.SetBuildID(1)
- _hookOne.SetNumber(1)
- _hookOne.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- _hookTwo := testHook()
- _hookTwo.SetID(2)
- _hookTwo.SetRepoID(1)
- _hookTwo.SetBuildID(2)
- _hookTwo.SetNumber(2)
- _hookTwo.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- // 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.ListHooks).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "repo_id", "build_id", "number", "source_id", "created", "host", "event", "branch", "error", "status", "link"},
- ).AddRow(1, 1, 1, 1, "c8da1302-07d6-11ea-882f-4893bca275b8", 0, "", "", "", "", "", "").
- AddRow(2, 1, 2, 2, "c8da1302-07d6-11ea-882f-4893bca275b8", 0, "", "", "", "", "", "")
-
- // ensure the mock expects the query
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Hook
- }{
- {
- failure: false,
- want: []*library.Hook{_hookOne, _hookTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetHookList()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetHookList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetHookList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetHookList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetRepoHookList(t *testing.T) {
- // setup types
- _hookOne := testHook()
- _hookOne.SetID(1)
- _hookOne.SetRepoID(1)
- _hookOne.SetBuildID(1)
- _hookOne.SetNumber(1)
- _hookOne.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- _hookTwo := testHook()
- _hookTwo.SetID(2)
- _hookTwo.SetRepoID(1)
- _hookTwo.SetBuildID(2)
- _hookTwo.SetNumber(2)
- _hookTwo.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/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.ListRepoHooks, 1, 1, 10).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "repo_id", "build_id", "number", "source_id", "created", "host", "event", "branch", "error", "status", "link"},
- ).AddRow(1, 1, 1, 1, "c8da1302-07d6-11ea-882f-4893bca275b8", 0, "", "", "", "", "", "").
- AddRow(2, 1, 2, 2, "c8da1302-07d6-11ea-882f-4893bca275b8", 0, "", "", "", "", "", "")
-
- // ensure the mock expects the query
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Hook
- }{
- {
- failure: false,
- want: []*library.Hook{_hookOne, _hookTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetRepoHookList(_repo, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepoHookList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepoHookList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepoHookList is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/postgres/hook_test.go b/database/postgres/hook_test.go
deleted file mode 100644
index 8d94bd0a2..000000000
--- a/database/postgres/hook_test.go
+++ /dev/null
@@ -1,335 +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_GetHook(t *testing.T) {
- // setup types
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
-
- _hook := testHook()
- _hook.SetID(1)
- _hook.SetRepoID(1)
- _hook.SetBuildID(1)
- _hook.SetNumber(1)
- _hook.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- // 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.SelectRepoHook, 1, 1).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "repo_id", "build_id", "number", "source_id", "created", "host", "event", "branch", "error", "status", "link"},
- ).AddRow(1, 1, 1, 1, "c8da1302-07d6-11ea-882f-4893bca275b8", 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.Hook
- }{
- {
- failure: false,
- want: _hook,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetHook(1, _repo)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetHook should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetHook returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetHook is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetLastHook(t *testing.T) {
- // setup types
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
-
- _hook := testHook()
- _hook.SetID(1)
- _hook.SetRepoID(1)
- _hook.SetBuildID(1)
- _hook.SetNumber(1)
- _hook.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- // 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.SelectLastRepoHook, 1).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "repo_id", "build_id", "number", "source_id", "created", "host", "event", "branch", "error", "status", "link"},
- ).AddRow(1, 1, 1, 1, "c8da1302-07d6-11ea-882f-4893bca275b8", 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.Hook
- }{
- {
- failure: false,
- want: _hook,
- },
- {
- failure: false,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetLastHook(_repo)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetLastHook should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetLastHook returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetLastHook is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_CreateHook(t *testing.T) {
- // setup types
- _hook := testHook()
- _hook.SetID(1)
- _hook.SetRepoID(1)
- _hook.SetBuildID(1)
- _hook.SetNumber(1)
- _hook.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- // 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 "hooks" ("repo_id","build_id","number","source_id","created","host","event","branch","error","status","link","id") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) RETURNING "id"`).
- WithArgs(1, 1, 1, "c8da1302-07d6-11ea-882f-4893bca275b8", 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.CreateHook(_hook)
-
- if test.failure {
- if err == nil {
- t.Errorf("CreateHook should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("CreateHook returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_Client_UpdateHook(t *testing.T) {
- // setup types
- _hook := testHook()
- _hook.SetID(1)
- _hook.SetRepoID(1)
- _hook.SetBuildID(1)
- _hook.SetNumber(1)
- _hook.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- // 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 "hooks" SET "repo_id"=$1,"build_id"=$2,"number"=$3,"source_id"=$4,"created"=$5,"host"=$6,"event"=$7,"branch"=$8,"error"=$9,"status"=$10,"link"=$11 WHERE "id" = $12`).
- WithArgs(1, 1, 1, "c8da1302-07d6-11ea-882f-4893bca275b8", 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.UpdateHook(_hook)
-
- if test.failure {
- if err == nil {
- t.Errorf("UpdateHook should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("UpdateHook returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_Client_DeleteHook(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.DeleteHook, 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.DeleteHook(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("DeleteHook should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("DeleteHook returned err: %v", err)
- }
- }
-}
-
-// testHook is a test helper function to create a
-// library Hook type with all fields set to their
-// zero values.
-func testHook() *library.Hook {
- i := 0
- i64 := int64(0)
- str := ""
-
- return &library.Hook{
- ID: &i64,
- RepoID: &i64,
- BuildID: &i64,
- Number: &i,
- SourceID: &str,
- Created: &i64,
- Host: &str,
- Event: &str,
- Branch: &str,
- Error: &str,
- Status: &str,
- Link: &str,
- }
-}
diff --git a/database/postgres/log.go b/database/postgres/log.go
deleted file mode 100644
index 7ab708ced..000000000
--- a/database/postgres/log.go
+++ /dev/null
@@ -1,212 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "errors"
- "fmt"
-
- "github.com/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"
-)
-
-// GetBuildLogs gets a collection of logs for a build by unique ID from the database.
-func (c *client) GetBuildLogs(id int64) ([]*library.Log, error) {
- c.Logger.Tracef("listing logs for build %d from the database", id)
-
- // variable to store query results
- l := new([]database.Log)
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableLog).
- Raw(dml.ListBuildLogs, id).
- Scan(l).Error
- if err != nil {
- return nil, err
- }
-
- // variable we want to return
- logs := []*library.Log{}
- // iterate through all query results
- for _, log := range *l {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := log
-
- // decompress log data for the step
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Log.Decompress
- err = tmp.Decompress()
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch uncompressed logs
- c.Logger.Errorf("unable to decompress logs for build %d: %v", id, err)
- }
-
- // convert query result to library type
- logs = append(logs, tmp.ToLibrary())
- }
-
- return logs, nil
-}
-
-// GetStepLog gets a log by unique ID from the database.
-//
-// nolint: dupl // ignore similar code with service
-func (c *client) GetStepLog(id int64) (*library.Log, error) {
- c.Logger.Tracef("getting log for step %d from the database", id)
-
- // variable to store query results
- l := new(database.Log)
-
- // send query to the database and store result in variable
- result := c.Postgres.
- Table(constants.TableLog).
- Raw(dml.SelectStepLog, id).
- Scan(l)
-
- // 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
- }
-
- // decompress log data for the step
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Log.Decompress
- err := l.Decompress()
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch uncompressed logs
- c.Logger.Errorf("unable to decompress logs for step %d: %v", id, err)
-
- // return the uncompressed log
- return l.ToLibrary(), result.Error
- }
-
- // return the decompressed log
- return l.ToLibrary(), result.Error
-}
-
-// GetServiceLog gets a log by unique ID from the database.
-//
-// nolint: dupl // ignore similar code with step
-func (c *client) GetServiceLog(id int64) (*library.Log, error) {
- c.Logger.Tracef("getting log for service %d from the database", id)
-
- // variable to store query results
- l := new(database.Log)
-
- // send query to the database and store result in variable
- result := c.Postgres.
- Table(constants.TableLog).
- Raw(dml.SelectServiceLog, id).
- Scan(l)
-
- // 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
- }
-
- // decompress log data for the service
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Log.Decompress
- err := l.Decompress()
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allowing us to fetch uncompressed logs
- c.Logger.Errorf("unable to decompress logs for service %d: %v", id, err)
-
- // return the uncompressed log
- return l.ToLibrary(), result.Error
- }
-
- // return the decompressed log
- return l.ToLibrary(), result.Error
-}
-
-// CreateLog creates a new log in the database.
-//
-// nolint: dupl // ignore false positive of duplicate code
-func (c *client) CreateLog(l *library.Log) error {
- // check if the log entry is for a step
- if l.GetStepID() > 0 {
- c.Logger.Tracef("creating log for step %d in the database", l.GetStepID())
- } else {
- c.Logger.Tracef("creating log for service %d in the database", l.GetServiceID())
- }
-
- // cast to database type
- log := database.LogFromLibrary(l)
-
- // validate the necessary fields are populated
- err := log.Validate()
- if err != nil {
- return err
- }
-
- // compress log data for the resource
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Log.Compress
- err = log.Compress(c.config.CompressionLevel)
- if err != nil {
- return fmt.Errorf("unable to compress logs for step %d: %v", l.GetStepID(), err)
- }
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableLog).
- Create(log).Error
-}
-
-// UpdateLog updates a log in the database.
-//
-// nolint: dupl // ignore false positive of duplicate code
-func (c *client) UpdateLog(l *library.Log) error {
- // check if the log entry is for a step
- if l.GetStepID() > 0 {
- c.Logger.Tracef("updating log for step %d in the database", l.GetStepID())
- } else {
- c.Logger.Tracef("updating log for service %d in the database", l.GetServiceID())
- }
-
- // cast to database type
- log := database.LogFromLibrary(l)
-
- // validate the necessary fields are populated
- err := log.Validate()
- if err != nil {
- return err
- }
-
- // compress log data for the resource
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Log.Compress
- err = log.Compress(c.config.CompressionLevel)
- if err != nil {
- return fmt.Errorf("unable to compress logs for step %d: %v", l.GetStepID(), err)
- }
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableLog).
- Save(log).Error
-}
-
-// DeleteLog deletes a log by unique ID from the database.
-func (c *client) DeleteLog(id int64) error {
- c.Logger.Tracef("deleting log %d from the database", id)
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableLog).
- Exec(dml.DeleteLog, id).Error
-}
diff --git a/database/postgres/log_test.go b/database/postgres/log_test.go
deleted file mode 100644
index 1f4551378..000000000
--- a/database/postgres/log_test.go
+++ /dev/null
@@ -1,383 +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_GetBuildLogs(t *testing.T) {
- // setup types
- _logOne := testLog()
- _logOne.SetID(1)
- _logOne.SetStepID(1)
- _logOne.SetBuildID(1)
- _logOne.SetRepoID(1)
- _logOne.SetData([]byte{})
-
- _logTwo := testLog()
- _logTwo.SetID(2)
- _logTwo.SetServiceID(1)
- _logTwo.SetBuildID(1)
- _logTwo.SetRepoID(1)
- _logTwo.SetData([]byte{})
-
- // 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.ListBuildLogs, 1).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "build_id", "repo_id", "service_id", "step_id", "data"},
- ).AddRow(1, 1, 1, 0, 1, []byte{}).AddRow(2, 1, 1, 1, 0, []byte{})
-
- // ensure the mock expects the query
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Log
- }{
- {
- failure: false,
- want: []*library.Log{_logOne, _logTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetBuildLogs(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetBuildLogs should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetBuildLogs returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetBuildLogs is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetStepLog(t *testing.T) {
- // setup types
- _log := testLog()
- _log.SetID(1)
- _log.SetStepID(1)
- _log.SetBuildID(1)
- _log.SetRepoID(1)
- _log.SetData([]byte{})
-
- // 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.SelectStepLog, 1).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "build_id", "repo_id", "service_id", "step_id", "data"},
- ).AddRow(1, 1, 1, 0, 1, []byte{})
-
- // 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.Log
- }{
- {
- failure: false,
- want: _log,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetStepLog(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetStepLog should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetStepLog returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetStepLog is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetServiceLog(t *testing.T) {
- // setup types
- _log := testLog()
- _log.SetID(1)
- _log.SetServiceID(1)
- _log.SetBuildID(1)
- _log.SetRepoID(1)
- _log.SetData([]byte{})
-
- // 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.SelectServiceLog, 1).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "build_id", "repo_id", "service_id", "step_id", "data"},
- ).AddRow(1, 1, 1, 1, 0, []byte{})
-
- // 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.Log
- }{
- {
- failure: false,
- want: _log,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetServiceLog(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetServiceLog should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetServiceLog returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetServiceLog is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_CreateLog(t *testing.T) {
- // setup types
- _log := testLog()
- _log.SetID(1)
- _log.SetStepID(1)
- _log.SetBuildID(1)
- _log.SetRepoID(1)
- _log.SetData([]byte{})
-
- // 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 "logs" ("build_id","repo_id","service_id","step_id","data","id") VALUES ($1,$2,$3,$4,$5,$6) RETURNING "id"`).
- WithArgs(1, 1, nil, 1, AnyArgument{}, 1).
- WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := _database.CreateLog(_log)
-
- if test.failure {
- if err == nil {
- t.Errorf("CreateLog should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("CreateLog returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_Client_UpdateLog(t *testing.T) {
- // setup types
- _log := testLog()
- _log.SetID(1)
- _log.SetStepID(1)
- _log.SetBuildID(1)
- _log.SetRepoID(1)
- _log.SetData([]byte{})
-
- // 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 "logs" SET "build_id"=$1,"repo_id"=$2,"service_id"=$3,"step_id"=$4,"data"=$5 WHERE "id" = $6`).
- WithArgs(1, 1, nil, 1, AnyArgument{}, 1).
- WillReturnResult(sqlmock.NewResult(1, 1))
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := _database.UpdateLog(_log)
-
- if test.failure {
- if err == nil {
- t.Errorf("UpdateLog should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("UpdateLog returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_Client_DeleteLog(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.DeleteLog, 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.DeleteLog(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("DeleteLog should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("DeleteLog returned err: %v", err)
- }
- }
-}
-
-// testLog is a test helper function to create a
-// library Log type with all fields set to their
-// zero values.
-func testLog() *library.Log {
- i64 := int64(0)
- b := []byte{}
-
- return &library.Log{
- ID: &i64,
- BuildID: &i64,
- RepoID: &i64,
- ServiceID: &i64,
- StepID: &i64,
- Data: &b,
- }
-}
diff --git a/database/postgres/opts.go b/database/postgres/opts.go
deleted file mode 100644
index 0564f4c0d..000000000
--- a/database/postgres/opts.go
+++ /dev/null
@@ -1,107 +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 (
- "fmt"
- "time"
-)
-
-// ClientOpt represents a configuration option to initialize the database client for Postgres.
-type ClientOpt func(*client) error
-
-// WithAddress sets the address in the database client for Postgres.
-func WithAddress(address string) ClientOpt {
- return func(c *client) error {
- c.Logger.Trace("configuring address in postgres database client")
-
- // check if the Postgres address provided is empty
- if len(address) == 0 {
- return fmt.Errorf("no Postgres address provided")
- }
-
- // set the address in the postgres client
- c.config.Address = address
-
- return nil
- }
-}
-
-// WithCompressionLevel sets the compression level in the database client for Postgres.
-func WithCompressionLevel(level int) ClientOpt {
- return func(c *client) error {
- c.Logger.Trace("configuring compression level in postgres database client")
-
- // set the compression level in the postgres client
- c.config.CompressionLevel = level
-
- return nil
- }
-}
-
-// WithConnectionLife sets the connection duration in the database client for Postgres.
-func WithConnectionLife(duration time.Duration) ClientOpt {
- return func(c *client) error {
- c.Logger.Trace("configuring connection duration in postgres database client")
-
- // set the connection duration in the postgres client
- c.config.ConnectionLife = duration
-
- return nil
- }
-}
-
-// WithConnectionIdle sets the maximum idle connections in the database client for Postgres.
-func WithConnectionIdle(idle int) ClientOpt {
- return func(c *client) error {
- c.Logger.Trace("configuring maximum idle connections in postgres database client")
-
- // set the maximum idle connections in the postgres client
- c.config.ConnectionIdle = idle
-
- return nil
- }
-}
-
-// WithConnectionOpen sets the maximum open connections in the database client for Postgres.
-func WithConnectionOpen(open int) ClientOpt {
- return func(c *client) error {
- c.Logger.Trace("configuring maximum open connections in postgres database client")
-
- // set the maximum open connections in the postgres client
- c.config.ConnectionOpen = open
-
- return nil
- }
-}
-
-// WithEncryptionKey sets the encryption key in the database client for Postgres.
-func WithEncryptionKey(key string) ClientOpt {
- return func(c *client) error {
- c.Logger.Trace("configuring encryption key in postgres database client")
-
- // check if the Postgres encryption key provided is empty
- if len(key) == 0 {
- return fmt.Errorf("no Postgres encryption key provided")
- }
-
- // set the encryption key in the postgres client
- c.config.EncryptionKey = key
-
- return nil
- }
-}
-
-// WithSkipCreation sets the skip creation logic in the database client for Postgres.
-func WithSkipCreation(skipCreation bool) ClientOpt {
- return func(c *client) error {
- c.Logger.Trace("configuring skip creating objects in postgres database client")
-
- // set to skip creating tables and indexes in the postgres client
- c.config.SkipCreation = skipCreation
-
- return nil
- }
-}
diff --git a/database/postgres/opts_test.go b/database/postgres/opts_test.go
deleted file mode 100644
index cb3e687da..000000000
--- a/database/postgres/opts_test.go
+++ /dev/null
@@ -1,287 +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"
- "time"
-
- "github.com/sirupsen/logrus"
-)
-
-func TestPostgres_ClientOpt_WithAddress(t *testing.T) {
- // setup types
- c := new(client)
- c.config = new(config)
- logger := logrus.StandardLogger()
- c.Logger = logrus.NewEntry(logger)
-
- // setup tests
- tests := []struct {
- failure bool
- address string
- want string
- }{
- {
- failure: false,
- address: "postgres://foo:bar@localhost:5432/vela",
- want: "postgres://foo:bar@localhost:5432/vela",
- },
- {
- failure: true,
- address: "",
- want: "",
- },
- }
-
- // run tests
- for _, test := range tests {
- err := WithAddress(test.address)(c)
-
- if test.failure {
- if err == nil {
- t.Errorf("WithAddress should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("WithAddress returned err: %v", err)
- }
-
- if !reflect.DeepEqual(c.config.Address, test.want) {
- t.Errorf("WithAddress is %v, want %v", c.config.Address, test.want)
- }
- }
-}
-
-func TestPostgres_ClientOpt_WithCompressionLevel(t *testing.T) {
- // setup types
- c := new(client)
- c.config = new(config)
- logger := logrus.StandardLogger()
- c.Logger = logrus.NewEntry(logger)
-
- // setup tests
- tests := []struct {
- level int
- want int
- }{
- {
- level: 3,
- want: 3,
- },
- {
- level: 0,
- want: 0,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := WithCompressionLevel(test.level)(c)
-
- if err != nil {
- t.Errorf("WithCompressionLevel returned err: %v", err)
- }
-
- if !reflect.DeepEqual(c.config.CompressionLevel, test.want) {
- t.Errorf("WithCompressionLevel is %v, want %v", c.config.CompressionLevel, test.want)
- }
- }
-}
-
-func TestPostgres_ClientOpt_WithConnectionLife(t *testing.T) {
- // setup types
- c := new(client)
- c.config = new(config)
- logger := logrus.StandardLogger()
- c.Logger = logrus.NewEntry(logger)
-
- // setup tests
- tests := []struct {
- duration time.Duration
- want time.Duration
- }{
- {
- duration: 10 * time.Second,
- want: 10 * time.Second,
- },
- {
- duration: 0,
- want: 0,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := WithConnectionLife(test.duration)(c)
-
- if err != nil {
- t.Errorf("WithConnectionLife returned err: %v", err)
- }
-
- if !reflect.DeepEqual(c.config.ConnectionLife, test.want) {
- t.Errorf("WithConnectionLife is %v, want %v", c.config.ConnectionLife, test.want)
- }
- }
-}
-
-func TestPostgres_ClientOpt_WithConnectionIdle(t *testing.T) {
- // setup types
- c := new(client)
- c.config = new(config)
- logger := logrus.StandardLogger()
- c.Logger = logrus.NewEntry(logger)
-
- // setup tests
- tests := []struct {
- idle int
- want int
- }{
- {
- idle: 5,
- want: 5,
- },
- {
- idle: 0,
- want: 0,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := WithConnectionIdle(test.idle)(c)
-
- if err != nil {
- t.Errorf("WithConnectionIdle returned err: %v", err)
- }
-
- if !reflect.DeepEqual(c.config.ConnectionIdle, test.want) {
- t.Errorf("WithConnectionIdle is %v, want %v", c.config.ConnectionIdle, test.want)
- }
- }
-}
-
-func TestPostgres_ClientOpt_WithConnectionOpen(t *testing.T) {
- // setup types
- c := new(client)
- c.config = new(config)
- logger := logrus.StandardLogger()
- c.Logger = logrus.NewEntry(logger)
-
- // setup tests
- tests := []struct {
- open int
- want int
- }{
- {
- open: 10,
- want: 10,
- },
- {
- open: 0,
- want: 0,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := WithConnectionOpen(test.open)(c)
-
- if err != nil {
- t.Errorf("WithConnectionOpen returned err: %v", err)
- }
-
- if !reflect.DeepEqual(c.config.ConnectionOpen, test.want) {
- t.Errorf("WithConnectionOpen is %v, want %v", c.config.ConnectionOpen, test.want)
- }
- }
-}
-
-func TestPostgres_ClientOpt_WithEncryptionKey(t *testing.T) {
- // setup types
- c := new(client)
- c.config = new(config)
- logger := logrus.StandardLogger()
- c.Logger = logrus.NewEntry(logger)
-
- // setup tests
- tests := []struct {
- failure bool
- key string
- want string
- }{
- {
- failure: false,
- key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
- want: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
- },
- {
- failure: true,
- key: "",
- want: "",
- },
- }
-
- // run tests
- for _, test := range tests {
- err := WithEncryptionKey(test.key)(c)
-
- if test.failure {
- if err == nil {
- t.Errorf("WithEncryptionKey should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("WithEncryptionKey returned err: %v", err)
- }
-
- if !reflect.DeepEqual(c.config.EncryptionKey, test.want) {
- t.Errorf("WithEncryptionKey is %v, want %v", c.config.EncryptionKey, test.want)
- }
- }
-}
-
-func TestPostgres_ClientOpt_WithSkipCreation(t *testing.T) {
- // setup types
- c := new(client)
- c.config = new(config)
- logger := logrus.StandardLogger()
- c.Logger = logrus.NewEntry(logger)
-
- // setup tests
- tests := []struct {
- skipCreation bool
- want bool
- }{
- {
- skipCreation: true,
- want: true,
- },
- {
- skipCreation: false,
- want: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := WithSkipCreation(test.skipCreation)(c)
-
- if err != nil {
- t.Errorf("WithSkipCreation returned err: %v", err)
- }
-
- if !reflect.DeepEqual(c.config.SkipCreation, test.want) {
- t.Errorf("WithSkipCreation is %v, want %v", c.config.SkipCreation, test.want)
- }
- }
-}
diff --git a/database/postgres/ping.go b/database/postgres/ping.go
deleted file mode 100644
index acdc29782..000000000
--- a/database/postgres/ping.go
+++ /dev/null
@@ -1,45 +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 (
- "fmt"
- "time"
-)
-
-// Ping sends a "ping" request with backoff to the database.
-func (c *client) Ping() error {
- c.Logger.Trace("sending ping requests to the database")
-
- // create a loop to attempt ping requests 5 times
- for i := 0; i < 5; i++ {
- // capture database/sql database from gorm database
- //
- // https://pkg.go.dev/gorm.io/gorm#DB.DB
- _sql, err := c.Postgres.DB()
- if err != nil {
- return err
- }
-
- // send ping request to database
- //
- // https://pkg.go.dev/database/sql#DB.Ping
- err = _sql.Ping()
- if err != nil {
- c.Logger.Debugf("unable to ping database - retrying in %v", time.Duration(i)*time.Second)
-
- // sleep for loop iteration in seconds
- time.Sleep(time.Duration(i) * time.Second)
-
- // continue to next iteration of the loop
- continue
- }
-
- // able to ping database so return with no error
- return nil
- }
-
- return fmt.Errorf("unable to successfully ping database")
-}
diff --git a/database/postgres/ping_test.go b/database/postgres/ping_test.go
deleted file mode 100644
index 9853ecd90..000000000
--- a/database/postgres/ping_test.go
+++ /dev/null
@@ -1,65 +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 (
- "testing"
-)
-
-func TestPostgres_Client_Ping(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() }()
-
- // ensure the mock expects the ping
- _mock.ExpectPing()
-
- // setup the closed test database client
- _closed, _, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- // capture the closed test sql database
- _sql, _ := _closed.Postgres.DB()
- // close the test sql database to simulate failures to ping
- _sql.Close()
-
- // setup tests
- tests := []struct {
- failure bool
- database *client
- }{
- {
- failure: false,
- database: _database,
- },
- {
- failure: true,
- database: _closed,
- },
- }
-
- // run tests
- for _, test := range tests {
- err = test.database.Ping()
-
- if test.failure {
- if err == nil {
- t.Errorf("Ping should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("Ping returned err: %v", err)
- }
- }
-}
diff --git a/database/postgres/postgres.go b/database/postgres/postgres.go
deleted file mode 100644
index c0efe59b4..000000000
--- a/database/postgres/postgres.go
+++ /dev/null
@@ -1,337 +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 (
- "fmt"
- "time"
-
- "github.com/DATA-DOG/go-sqlmock"
- "github.com/go-vela/server/database/postgres/ddl"
- "github.com/go-vela/types/constants"
- "github.com/sirupsen/logrus"
-
- "gorm.io/driver/postgres"
- "gorm.io/gorm"
-)
-
-type (
- config struct {
- // specifies the address to use for the Postgres client
- Address string
- // specifies the level of compression to use for the Postgres client
- CompressionLevel int
- // specifies the connection duration to use for the Postgres client
- ConnectionLife time.Duration
- // specifies the maximum idle connections for the Postgres client
- ConnectionIdle int
- // specifies the maximum open connections for the Postgres client
- ConnectionOpen int
- // specifies the encryption key to use for the Postgres client
- EncryptionKey string
- // specifies to skip creating tables and indexes for the Postgres client
- SkipCreation bool
- }
-
- client struct {
- config *config
- Postgres *gorm.DB
- // https://pkg.go.dev/github.com/sirupsen/logrus#Entry
- Logger *logrus.Entry
- }
-)
-
-// New returns a Database implementation that integrates with a Postgres instance.
-//
-// nolint: revive // ignore returning unexported client
-func New(opts ...ClientOpt) (*client, error) {
- // create new Postgres client
- c := new(client)
-
- // create new fields
- c.config = new(config)
- c.Postgres = new(gorm.DB)
-
- // create new logger for the client
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#StandardLogger
- logger := logrus.StandardLogger()
-
- // create new logger for the client
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#NewEntry
- c.Logger = logrus.NewEntry(logger).WithField("database", c.Driver())
-
- // apply all provided configuration options
- for _, opt := range opts {
- err := opt(c)
- if err != nil {
- return nil, err
- }
- }
-
- // create the new Postgres database client
- //
- // https://pkg.go.dev/gorm.io/gorm#Open
- _postgres, err := gorm.Open(postgres.Open(c.config.Address), &gorm.Config{})
- if err != nil {
- return nil, err
- }
-
- // set the Postgres database client in the Postgres client
- c.Postgres = _postgres
-
- // setup database with proper configuration
- err = setupDatabase(c)
- if err != nil {
- return nil, err
- }
-
- return c, nil
-}
-
-// NewTest returns a Database implementation that integrates with a fake Postgres instance.
-//
-// This function is intended for running tests only.
-//
-// nolint: revive // ignore returning unexported client
-func NewTest() (*client, sqlmock.Sqlmock, error) {
- // create new Postgres client
- c := new(client)
-
- // create new fields
- c.config = &config{
- CompressionLevel: 3,
- ConnectionLife: 30 * time.Minute,
- ConnectionIdle: 2,
- ConnectionOpen: 0,
- EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
- SkipCreation: false,
- }
- c.Postgres = new(gorm.DB)
-
- // create new logger for the client
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#StandardLogger
- logger := logrus.StandardLogger()
-
- // create new logger for the client
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#NewEntry
- c.Logger = logrus.NewEntry(logger)
-
- // 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 {
- return nil, nil, err
- }
-
- // create the new mock Postgres database client
- //
- // https://pkg.go.dev/gorm.io/gorm#Open
- c.Postgres, err = gorm.Open(
- postgres.New(postgres.Config{Conn: _sql}),
- &gorm.Config{SkipDefaultTransaction: true},
- )
- if err != nil {
- return nil, nil, err
- }
-
- return c, _mock, nil
-}
-
-// setupDatabase is a helper function to setup
-// the database with the proper configuration.
-func setupDatabase(c *client) error {
- // capture database/sql database from gorm database
- //
- // https://pkg.go.dev/gorm.io/gorm#DB.DB
- _sql, err := c.Postgres.DB()
- if err != nil {
- return err
- }
-
- // set the maximum amount of time a connection may be reused
- //
- // https://golang.org/pkg/database/sql/#DB.SetConnMaxLifetime
- _sql.SetConnMaxLifetime(c.config.ConnectionLife)
-
- // set the maximum number of connections in the idle connection pool
- //
- // https://golang.org/pkg/database/sql/#DB.SetMaxIdleConns
- _sql.SetMaxIdleConns(c.config.ConnectionIdle)
-
- // set the maximum number of open connections to the database
- //
- // https://golang.org/pkg/database/sql/#DB.SetMaxOpenConns
- _sql.SetMaxOpenConns(c.config.ConnectionOpen)
-
- // verify connection to the database
- err = c.Ping()
- if err != nil {
- return err
- }
-
- // check if we should skip creating database objects
- if c.config.SkipCreation {
- c.Logger.Warning("skipping creation of data tables and indexes in the postgres database")
-
- return nil
- }
-
- // create the tables in the database
- err = createTables(c)
- if err != nil {
- return err
- }
-
- // create the indexes in the database
- err = createIndexes(c)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-// createTables is a helper function to setup
-// the database with the necessary tables.
-func createTables(c *client) error {
- c.Logger.Trace("creating data tables in the postgres database")
-
- // create the builds table
- err := c.Postgres.Exec(ddl.CreateBuildTable).Error
- if err != nil {
- return fmt.Errorf("unable to create %s table: %v", constants.TableBuild, err)
- }
-
- // create the hooks table
- err = c.Postgres.Exec(ddl.CreateHookTable).Error
- if err != nil {
- return fmt.Errorf("unable to create %s table: %v", constants.TableHook, err)
- }
-
- // create the logs table
- err = c.Postgres.Exec(ddl.CreateLogTable).Error
- if err != nil {
- return fmt.Errorf("unable to create %s table: %v", constants.TableLog, err)
- }
-
- // create the repos table
- err = c.Postgres.Exec(ddl.CreateRepoTable).Error
- if err != nil {
- return fmt.Errorf("unable to create %s table: %v", constants.TableRepo, err)
- }
-
- // create the secrets table
- err = c.Postgres.Exec(ddl.CreateSecretTable).Error
- if err != nil {
- return fmt.Errorf("unable to create %s table: %v", constants.TableSecret, err)
- }
-
- // create the services table
- err = c.Postgres.Exec(ddl.CreateServiceTable).Error
- if err != nil {
- return fmt.Errorf("unable to create %s table: %v", 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: %v", constants.TableStep, err)
- }
-
- // create the users table
- err = c.Postgres.Exec(ddl.CreateUserTable).Error
- if err != nil {
- return fmt.Errorf("unable to create %s table: %v", constants.TableUser, err)
- }
-
- // create the workers table
- err = c.Postgres.Exec(ddl.CreateWorkerTable).Error
- if err != nil {
- return fmt.Errorf("unable to create %s table: %v", constants.TableWorker, err)
- }
-
- return nil
-}
-
-// createIndexes is a helper function to setup
-// the database with the necessary indexes.
-//
-// nolint: lll // ignore long line length due to error messages
-func createIndexes(c *client) error {
- c.Logger.Trace("creating data indexes in the postgres database")
-
- // create the builds_repo_id index for the builds table
- err := c.Postgres.Exec(ddl.CreateBuildRepoIDIndex).Error
- if err != nil {
- return fmt.Errorf("unable to create builds_repo_id index for the %s table: %v", constants.TableBuild, err)
- }
-
- // create the builds_status index for the builds table
- err = c.Postgres.Exec(ddl.CreateBuildStatusIndex).Error
- if err != nil {
- return fmt.Errorf("unable to create builds_status index for the %s table: %v", constants.TableBuild, err)
- }
-
- // create the builds_created index for the builds table
- err = c.Postgres.Exec(ddl.CreateBuildCreatedIndex).Error
- if err != nil {
- return fmt.Errorf("unable to create builds_created index for the %s table: %v", constants.TableBuild, err)
- }
-
- // create the hooks_repo_id index for the hooks table
- err = c.Postgres.Exec(ddl.CreateHookRepoIDIndex).Error
- if err != nil {
- return fmt.Errorf("unable to create hooks_repo_id index for the %s table: %v", constants.TableHook, err)
- }
-
- // create the logs_build_id index for the logs table
- err = c.Postgres.Exec(ddl.CreateLogBuildIDIndex).Error
- if err != nil {
- return fmt.Errorf("unable to create logs_build_id index for the %s table: %v", constants.TableLog, err)
- }
-
- // create the repos_org_name index for the repos table
- err = c.Postgres.Exec(ddl.CreateRepoOrgNameIndex).Error
- if err != nil {
- return fmt.Errorf("unable to create repos_org_name index for the %s table: %v", constants.TableRepo, err)
- }
-
- // create the secrets_type_org_repo index for the secrets table
- err = c.Postgres.Exec(ddl.CreateSecretTypeOrgRepo).Error
- if err != nil {
- return fmt.Errorf("unable to create secrets_type_org_repo index for the %s table: %v", constants.TableSecret, err)
- }
-
- // create the secrets_type_org_team index for the secrets table
- err = c.Postgres.Exec(ddl.CreateSecretTypeOrgTeam).Error
- if err != nil {
- return fmt.Errorf("unable to create secrets_type_org_team index for the %s table: %v", constants.TableSecret, err)
- }
-
- // create the secrets_type_org index for the secrets table
- err = c.Postgres.Exec(ddl.CreateSecretTypeOrg).Error
- if err != nil {
- return fmt.Errorf("unable to create secrets_type_org index for the %s table: %v", constants.TableSecret, err)
- }
-
- // create the users_refresh index for the users table
- err = c.Postgres.Exec(ddl.CreateUserRefreshIndex).Error
- if err != nil {
- return fmt.Errorf("unable to create users_refresh index for the %s table: %v", constants.TableUser, err)
- }
-
- // create the workers_hostname_address index for the workers table
- err = c.Postgres.Exec(ddl.CreateWorkerHostnameAddressIndex).Error
- if err != nil {
- return fmt.Errorf("unable to create workers_hostname_address index for the %s table: %v", constants.TableWorker, err)
- }
-
- return nil
-}
diff --git a/database/postgres/postgres_test.go b/database/postgres/postgres_test.go
deleted file mode 100644
index 15859fd3a..000000000
--- a/database/postgres/postgres_test.go
+++ /dev/null
@@ -1,252 +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 (
- "database/sql/driver"
- "testing"
- "time"
-
- sqlmock "github.com/DATA-DOG/go-sqlmock"
- "github.com/go-vela/server/database/postgres/ddl"
-)
-
-func TestPostgres_New(t *testing.T) {
- // setup tests
- tests := []struct {
- failure bool
- address string
- want string
- }{
- {
- failure: true,
- address: "postgres://foo:bar@localhost:5432/vela",
- want: "postgres://foo:bar@localhost:5432/vela",
- },
- {
- failure: true,
- address: "",
- want: "",
- },
- }
-
- // run tests
- for _, test := range tests {
- _, err := New(
- WithAddress(test.address),
- WithCompressionLevel(3),
- WithConnectionLife(10*time.Second),
- WithConnectionIdle(5),
- WithConnectionOpen(20),
- WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"),
- WithSkipCreation(false),
- )
-
- if test.failure {
- if err == nil {
- t.Errorf("New should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("New returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_setupDatabase(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() }()
-
- // ensure the mock expects the ping
- _mock.ExpectPing()
-
- // ensure the mock expects the table queries
- _mock.ExpectExec(ddl.CreateBuildTable).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateHookTable).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateLogTable).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateRepoTable).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateSecretTable).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateServiceTable).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateStepTable).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateUserTable).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateWorkerTable).WillReturnResult(sqlmock.NewResult(1, 1))
-
- // ensure the mock expects the index queries
- _mock.ExpectExec(ddl.CreateBuildRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateBuildStatusIndex).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateBuildCreatedIndex).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateHookRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateLogBuildIDIndex).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateRepoOrgNameIndex).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateSecretTypeOrgRepo).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateSecretTypeOrgTeam).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateSecretTypeOrg).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1))
-
- // setup the skip test database client
- _skipDatabase, _skipMock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new skip postgres test database: %v", err)
- }
- defer func() { _sql, _ := _skipDatabase.Postgres.DB(); _sql.Close() }()
-
- err = WithSkipCreation(true)(_skipDatabase)
- if err != nil {
- t.Errorf("unable to set SkipCreation for postgres test database: %v", err)
- }
-
- // ensure the mock expects the ping
- _skipMock.ExpectPing()
-
- tests := []struct {
- failure bool
- database *client
- }{
- {
- failure: false,
- database: _database,
- },
- {
- failure: false,
- database: _skipDatabase,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := setupDatabase(test.database)
-
- if test.failure {
- if err == nil {
- t.Errorf("setupDatabase should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("setupDatabase returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_createTables(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() }()
-
- // ensure the mock expects the table queries
- _mock.ExpectExec(ddl.CreateBuildTable).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateHookTable).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateLogTable).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateRepoTable).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateSecretTable).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateServiceTable).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateStepTable).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateUserTable).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateWorkerTable).WillReturnResult(sqlmock.NewResult(1, 1))
-
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := createTables(_database)
-
- if test.failure {
- if err == nil {
- t.Errorf("createTables should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("createTables returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_createIndexes(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() }()
-
- // ensure the mock expects the index queries
- _mock.ExpectExec(ddl.CreateBuildRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateBuildStatusIndex).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateBuildCreatedIndex).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateHookRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateLogBuildIDIndex).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateRepoOrgNameIndex).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateSecretTypeOrgRepo).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateSecretTypeOrgTeam).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateSecretTypeOrg).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1))
- _mock.ExpectExec(ddl.CreateWorkerHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1))
-
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := createIndexes(_database)
-
- if test.failure {
- if err == nil {
- t.Errorf("createIndexes should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("createIndexes returned err: %v", err)
- }
- }
-}
-
-// This will be used with the github.com/DATA-DOG/go-sqlmock
-// library to compare values that are otherwise not easily
-// compared. These typically would be values generated before
-// adding or updating them in the database.
-//
-// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime
-type AnyArgument struct{}
-
-// Match satisfies sqlmock.Argument interface.
-func (a AnyArgument) Match(v driver.Value) bool {
- return true
-}
diff --git a/database/postgres/repo.go b/database/postgres/repo.go
deleted file mode 100644
index 8085c5a45..000000000
--- a/database/postgres/repo.go
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "errors"
- "fmt"
-
- "github.com/sirupsen/logrus"
-
- "github.com/go-vela/server/database/postgres/dml"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/database"
- "github.com/go-vela/types/library"
-
- "gorm.io/gorm"
-)
-
-// GetRepo gets a repo by org and name from the database.
-func (c *client) GetRepo(org, name string) (*library.Repo, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": org,
- "repo": name,
- }).Tracef("getting repo %s/%s from the database", org, name)
-
- // variable to store query results
- r := new(database.Repo)
-
- // send query to the database and store result in variable
- result := c.Postgres.
- Table(constants.TableRepo).
- Raw(dml.SelectRepo, org, name).
- Scan(r)
-
- // check if the query returned a record not found error or no rows were returned
- if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 {
- return nil, gorm.ErrRecordNotFound
- }
-
- // decrypt the fields for the repo
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt
- err := r.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted repos
- c.Logger.Errorf("unable to decrypt repo %s/%s: %v", org, name, err)
-
- // return the unencrypted repo
- return r.ToLibrary(), result.Error
- }
-
- // return the decrypted repo
- return r.ToLibrary(), result.Error
-}
-
-// CreateRepo creates a new repo in the database.
-//
-// nolint: dupl // ignore similar code with update
-func (c *client) CreateRepo(r *library.Repo) error {
- c.Logger.WithFields(logrus.Fields{
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("creating repo %s in the database", r.GetFullName())
-
- // cast to database type
- repo := database.RepoFromLibrary(r)
-
- // validate the necessary fields are populated
- err := repo.Validate()
- if err != nil {
- return err
- }
-
- // encrypt the fields for the repo
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Encrypt
- err = repo.Encrypt(c.config.EncryptionKey)
- if err != nil {
- return fmt.Errorf("unable to encrypt repo %s: %v", r.GetFullName(), err)
- }
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableRepo).
- Create(repo).Error
-}
-
-// UpdateRepo updates a repo in the database.
-//
-// nolint: dupl // ignore similar code with create
-func (c *client) UpdateRepo(r *library.Repo) error {
- c.Logger.WithFields(logrus.Fields{
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("updating repo %s in the database", r.GetFullName())
-
- // cast to database type
- repo := database.RepoFromLibrary(r)
-
- // validate the necessary fields are populated
- err := repo.Validate()
- if err != nil {
- return err
- }
-
- // encrypt the fields for the repo
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Encrypt
- err = repo.Encrypt(c.config.EncryptionKey)
- if err != nil {
- return fmt.Errorf("unable to encrypt repo %s: %v", r.GetFullName(), err)
- }
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableRepo).
- Save(repo).Error
-}
-
-// DeleteRepo deletes a repo by unique ID from the database.
-func (c *client) DeleteRepo(id int64) error {
- c.Logger.Tracef("deleting repo %d in the database", id)
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableRepo).
- Exec(dml.DeleteRepo, id).Error
-}
diff --git a/database/postgres/repo_count.go b/database/postgres/repo_count.go
deleted file mode 100644
index aa3408c1a..000000000
--- a/database/postgres/repo_count.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "github.com/go-vela/server/database/postgres/dml"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/library"
- "github.com/sirupsen/logrus"
-)
-
-// GetRepoCount gets a count of all repos from the database.
-func (c *client) GetRepoCount() (int64, error) {
- c.Logger.Trace("getting count of repos from the database")
-
- // variable to store query results
- var r int64
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableRepo).
- Raw(dml.SelectReposCount).
- Pluck("count", &r).Error
-
- return r, err
-}
-
-// GetOrgRepoCount gets a count of all repos for a specific org from the database.
-func (c *client) GetOrgRepoCount(org string, filters map[string]string) (int64, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": org,
- }).Tracef("getting count of repos for org %s from the database", org)
-
- // variable to store query results
- var r int64
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableRepo).
- Select("count(*)").
- Where("org = ?", org).
- Where(filters).
- Pluck("count", &r).Error
-
- return r, err
-}
-
-// GetUserRepoCount gets a count of all repos for a specific user from the database.
-func (c *client) GetUserRepoCount(u *library.User) (int64, error) {
- c.Logger.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Tracef("getting count of repos for user %s in the database", u.GetName())
-
- // variable to store query results
- var r int64
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableRepo).
- Raw(dml.SelectUserReposCount, u.GetID()).
- Pluck("count", &r).Error
-
- return r, err
-}
diff --git a/database/postgres/repo_count_test.go b/database/postgres/repo_count_test.go
deleted file mode 100644
index f5a03f7f1..000000000
--- a/database/postgres/repo_count_test.go
+++ /dev/null
@@ -1,297 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "reflect"
- "testing"
-
- sqlmock "github.com/DATA-DOG/go-sqlmock"
-
- "github.com/go-vela/server/database/postgres/dml"
- "github.com/go-vela/types/library"
-
- "gorm.io/gorm"
-)
-
-func TestPostgres_Client_GetRepoCount(t *testing.T) {
- // setup types
- _repoOne := testRepo()
- _repoOne.SetID(1)
- _repoOne.SetUserID(1)
- _repoOne.SetHash("baz")
- _repoOne.SetOrg("foo")
- _repoOne.SetName("bar")
- _repoOne.SetFullName("foo/bar")
- _repoOne.SetVisibility("public")
-
- _repoTwo := testRepo()
- _repoTwo.SetID(1)
- _repoTwo.SetUserID(1)
- _repoTwo.SetHash("baz")
- _repoTwo.SetOrg("bar")
- _repoTwo.SetName("foo")
- _repoTwo.SetFullName("bar/foo")
- _repoTwo.SetVisibility("public")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectReposCount).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
-
- // ensure the mock expects the query
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 2,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetRepoCount()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepoCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepoCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepoCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetUserRepoCount(t *testing.T) {
- // setup types
- _repoOne := testRepo()
- _repoOne.SetID(1)
- _repoOne.SetUserID(1)
- _repoOne.SetHash("baz")
- _repoOne.SetOrg("foo")
- _repoOne.SetName("bar")
- _repoOne.SetFullName("foo/bar")
- _repoOne.SetVisibility("public")
-
- _repoTwo := testRepo()
- _repoTwo.SetID(1)
- _repoTwo.SetUserID(1)
- _repoTwo.SetHash("baz")
- _repoTwo.SetOrg("bar")
- _repoTwo.SetName("foo")
- _repoTwo.SetFullName("bar/foo")
- _repoTwo.SetVisibility("public")
-
- _user := new(library.User)
- _user.SetID(1)
- _user.SetName("foo")
- _user.SetToken("bar")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectUserReposCount, 1).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
-
- // ensure the mock expects the query
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 2,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetUserRepoCount(_user)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetUserRepoCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetUserRepoCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetUserRepoCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetOrgRepoCount(t *testing.T) {
- // setup types
- _repoOne := testRepo()
- _repoOne.SetID(1)
- _repoOne.SetUserID(1)
- _repoOne.SetHash("baz")
- _repoOne.SetOrg("foo")
- _repoOne.SetName("bar")
- _repoOne.SetFullName("foo/bar")
- _repoOne.SetVisibility("public")
-
- _repoTwo := testRepo()
- _repoTwo.SetID(1)
- _repoTwo.SetUserID(1)
- _repoTwo.SetHash("baz")
- _repoTwo.SetOrg("bar")
- _repoTwo.SetName("foo")
- _repoTwo.SetFullName("bar/foo")
- _repoTwo.SetVisibility("public")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"count"}).AddRow(1)
-
- // ensure the mock expects the query
- _mock.ExpectQuery("SELECT count(*) FROM \"repos\" WHERE org = $1").WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 1,
- },
- }
- filters := map[string]string{}
- // run tests
- for _, test := range tests {
- got, err := _database.GetOrgRepoCount("foo", filters)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepoCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepoCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepoCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetOrgRepoCount_NonAdmin(t *testing.T) {
- // setup types
- _repoOne := testRepo()
- _repoOne.SetID(1)
- _repoOne.SetUserID(1)
- _repoOne.SetHash("baz")
- _repoOne.SetOrg("foo")
- _repoOne.SetName("bar")
- _repoOne.SetFullName("foo/bar")
- _repoOne.SetVisibility("public")
-
- _repoTwo := testRepo()
- _repoTwo.SetID(1)
- _repoTwo.SetUserID(1)
- _repoTwo.SetHash("baz")
- _repoTwo.SetOrg("bar")
- _repoTwo.SetName("foo")
- _repoTwo.SetFullName("bar/foo")
- _repoTwo.SetVisibility("private")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"count"}).AddRow(1)
-
- // ensure the mock expects the query
- _mock.ExpectQuery("SELECT count(*) FROM \"repos\" WHERE (org = $1) AND \"visibility\" = $2").WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 1,
- },
- }
- filters := map[string]string{}
- filters["visibility"] = "private"
- // run tests
- for _, test := range tests {
- got, err := _database.GetOrgRepoCount("foo", filters)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepoCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepoCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepoCount is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/postgres/repo_list.go b/database/postgres/repo_list.go
deleted file mode 100644
index 773d62666..000000000
--- a/database/postgres/repo_list.go
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "github.com/go-vela/server/database/postgres/dml"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/database"
- "github.com/go-vela/types/library"
- "github.com/sirupsen/logrus"
-)
-
-// GetRepoList gets a list of all repos from the database.
-//
-// nolint: dupl // ignore false positive of duplicate code
-func (c *client) GetRepoList() ([]*library.Repo, error) {
- c.Logger.Trace("listing repos from the database")
-
- // variable to store query results
- r := new([]database.Repo)
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableRepo).
- Raw(dml.ListRepos).
- Scan(r).Error
- if err != nil {
- return nil, err
- }
-
- // variable we want to return
- repos := []*library.Repo{}
- // iterate through all query results
- for _, repo := range *r {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := repo
-
- // decrypt the fields for the repo
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt
- err = tmp.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted repos
- c.Logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err)
- }
-
- // convert query result to library type
- repos = append(repos, tmp.ToLibrary())
- }
-
- return repos, nil
-}
-
-// GetOrgRepoList gets a list of all repos by org from the database.
-//
-// nolint: lll // ignore long line length due to variable names
-func (c *client) GetOrgRepoList(org string, filters map[string]string, page, perPage int) ([]*library.Repo, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": org,
- }).Tracef("listing repos for org %s from the database", org)
-
- // variable to store query results
- r := new([]database.Repo)
-
- // calculate offset for pagination through results
- offset := perPage * (page - 1)
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableRepo).
- Where("org = ?", org).
- Where(filters).
- Order("name").
- Limit(perPage).
- Offset(offset).
- Scan(r).Error
- if err != nil {
- return nil, err
- }
-
- // variable we want to return
- repos := []*library.Repo{}
- // iterate through all query results
- for _, repo := range *r {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := repo
-
- // decrypt the fields for the repo
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt
- err = tmp.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted repos
- c.Logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err)
- }
-
- // convert query result to library type
- repos = append(repos, tmp.ToLibrary())
- }
-
- return repos, nil
-}
-
-// GetUserRepoList gets a list of all repos by user ID from the database.
-func (c *client) GetUserRepoList(u *library.User, page, perPage int) ([]*library.Repo, error) {
- c.Logger.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Tracef("listing repos for user %s from the database", u.GetName())
-
- // variable to store query results
- r := new([]database.Repo)
- // calculate offset for pagination through results
- offset := perPage * (page - 1)
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableRepo).
- Raw(dml.ListUserRepos, u.GetID(), perPage, offset).
- Scan(r).Error
- if err != nil {
- return nil, err
- }
-
- // variable we want to return
- repos := []*library.Repo{}
- // iterate through all query results
- for _, repo := range *r {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := repo
-
- // decrypt the fields for the repo
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt
- err = tmp.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted repos
- c.Logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err)
- }
-
- // convert query result to library type
- repos = append(repos, tmp.ToLibrary())
- }
-
- return repos, nil
-}
diff --git a/database/postgres/repo_list_test.go b/database/postgres/repo_list_test.go
deleted file mode 100644
index 78caad661..000000000
--- a/database/postgres/repo_list_test.go
+++ /dev/null
@@ -1,324 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "reflect"
- "testing"
-
- sqlmock "github.com/DATA-DOG/go-sqlmock"
-
- "github.com/go-vela/server/database/postgres/dml"
- "github.com/go-vela/types/library"
-
- "gorm.io/gorm"
-)
-
-func TestPostgres_Client_GetRepoList(t *testing.T) {
- // setup types
- _repoOne := testRepo()
- _repoOne.SetID(1)
- _repoOne.SetUserID(1)
- _repoOne.SetHash("baz")
- _repoOne.SetOrg("foo")
- _repoOne.SetName("bar")
- _repoOne.SetFullName("foo/bar")
- _repoOne.SetVisibility("public")
- _repoOne.SetPipelineType("yaml")
- _repoOne.SetPreviousName("")
-
- _repoTwo := testRepo()
- _repoTwo.SetID(1)
- _repoTwo.SetUserID(1)
- _repoTwo.SetHash("baz")
- _repoTwo.SetOrg("bar")
- _repoTwo.SetName("foo")
- _repoTwo.SetFullName("bar/foo")
- _repoTwo.SetVisibility("public")
- _repoTwo.SetPipelineType("yaml")
- _repoTwo.SetPreviousName("oldName")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.ListRepos).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"},
- ).AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "").
- AddRow(1, 1, "baz", "bar", "foo", "bar/foo", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "oldName")
-
- // ensure the mock expects the query
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Repo
- }{
- {
- failure: false,
- want: []*library.Repo{_repoOne, _repoTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetRepoList()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepoList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepoList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepoList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetOrgRepoList(t *testing.T) {
- // setup types
- _repoOne := testRepo()
- _repoOne.SetID(1)
- _repoOne.SetUserID(1)
- _repoOne.SetHash("baz")
- _repoOne.SetOrg("foo")
- _repoOne.SetName("bar")
- _repoOne.SetFullName("foo/bar")
- _repoOne.SetVisibility("public")
- _repoOne.SetPipelineType("yaml")
- _repoOne.SetPreviousName("")
-
- _repoTwo := testRepo()
- _repoTwo.SetID(1)
- _repoTwo.SetUserID(1)
- _repoTwo.SetHash("baz")
- _repoTwo.SetOrg("foo")
- _repoTwo.SetName("baz")
- _repoTwo.SetFullName("foo/baz")
- _repoTwo.SetVisibility("public")
- _repoTwo.SetPipelineType("yaml")
- _repoTwo.SetPreviousName("oldName")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"},
- ).AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "").
- AddRow(1, 1, "baz", "foo", "baz", "foo/baz", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "oldName")
-
- // ensure the mock expects the query
- _mock.ExpectQuery("SELECT * FROM \"repos\" WHERE org = $1 ORDER BY name LIMIT 10").WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Repo
- }{
- {
- failure: false,
- want: []*library.Repo{_repoOne, _repoTwo},
- },
- }
- filters := map[string]string{}
- // run tests
- for _, test := range tests {
- got, err := _database.GetOrgRepoList("foo", filters, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetOrgRepoList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetOrgRepoList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetOrgRepoList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetOrgRepoList_NonAdmin(t *testing.T) {
- // setup types
- _repoOne := testRepo()
- _repoOne.SetID(1)
- _repoOne.SetUserID(1)
- _repoOne.SetHash("baz")
- _repoOne.SetOrg("foo")
- _repoOne.SetName("bar")
- _repoOne.SetFullName("foo/bar")
- _repoOne.SetVisibility("public")
- _repoOne.SetPipelineType("yaml")
- _repoOne.SetPreviousName("")
-
- _repoTwo := testRepo()
- _repoTwo.SetID(1)
- _repoTwo.SetUserID(1)
- _repoTwo.SetHash("baz")
- _repoTwo.SetOrg("foo")
- _repoTwo.SetName("baz")
- _repoTwo.SetFullName("foo/baz")
- _repoTwo.SetVisibility("private")
- _repoTwo.SetPipelineType("yaml")
- _repoTwo.SetPreviousName("oldName")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"},
- ).AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "")
-
- // ensure the mock expects the query
- _mock.ExpectQuery("SELECT * FROM \"repos\" WHERE (org = $1) AND \"visibility\" = $2 ORDER BY name LIMIT 10").WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Repo
- }{
- {
- failure: false,
- want: []*library.Repo{_repoOne},
- },
- }
- filters := map[string]string{}
- filters["visibility"] = "public"
- // run tests
- for _, test := range tests {
- got, err := _database.GetOrgRepoList("foo", filters, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetOrgRepoList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetOrgRepoList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetOrgRepoList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetUserRepoList(t *testing.T) {
- // setup types
- _repoOne := testRepo()
- _repoOne.SetID(1)
- _repoOne.SetUserID(1)
- _repoOne.SetHash("baz")
- _repoOne.SetOrg("foo")
- _repoOne.SetName("bar")
- _repoOne.SetFullName("foo/bar")
- _repoOne.SetVisibility("public")
- _repoOne.SetPipelineType("yaml")
- _repoOne.SetPreviousName("")
-
- _repoTwo := testRepo()
- _repoTwo.SetID(1)
- _repoTwo.SetUserID(1)
- _repoTwo.SetHash("baz")
- _repoTwo.SetOrg("bar")
- _repoTwo.SetName("foo")
- _repoTwo.SetFullName("bar/foo")
- _repoTwo.SetVisibility("public")
- _repoTwo.SetPipelineType("yaml")
- _repoTwo.SetPreviousName("")
-
- _user := new(library.User)
- _user.SetID(1)
- _user.SetName("foo")
- _user.SetToken("bar")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.ListUserRepos, 1, 1, 10).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"},
- ).AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "").
- AddRow(1, 1, "baz", "bar", "foo", "bar/foo", "", "", "", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "")
-
- // ensure the mock expects the query
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Repo
- }{
- {
- failure: false,
- want: []*library.Repo{_repoOne, _repoTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetUserRepoList(_user, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetUserRepoList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetUserRepoList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetUserRepoList is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/postgres/repo_test.go b/database/postgres/repo_test.go
deleted file mode 100644
index 17f4cd63e..000000000
--- a/database/postgres/repo_test.go
+++ /dev/null
@@ -1,277 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "reflect"
- "testing"
-
- "github.com/DATA-DOG/go-sqlmock"
-
- "github.com/go-vela/server/database/postgres/dml"
- "github.com/go-vela/types/library"
-
- "gorm.io/gorm"
-)
-
-func TestPostgres_Client_GetRepo(t *testing.T) {
- // setup types
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
- _repo.SetPipelineType("yaml")
- _repo.SetPreviousName("")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectRepo, "foo", "bar").Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"},
- ).AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", 0, 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "")
-
- // ensure the mock expects the query for test case 1
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
- // ensure the mock expects the error for test case 2
- _mock.ExpectQuery(_query.SQL.String()).WillReturnError(gorm.ErrRecordNotFound)
-
- // setup tests
- tests := []struct {
- failure bool
- want *library.Repo
- }{
- {
- failure: false,
- want: _repo,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetRepo("foo", "bar")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepo should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepo returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepo is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_CreateRepo(t *testing.T) {
- // setup types
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
- _repo.SetPipelineType("yaml")
- _repo.SetPreviousName("oldName")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"id"}).AddRow(1)
-
- // ensure the mock expects the query
- _mock.ExpectQuery(`INSERT INTO "repos" ("user_id","hash","org","name","full_name","link","clone","branch","build_limit","timeout","counter","visibility","private","trusted","active","allow_pull","allow_push","allow_deploy","allow_tag","allow_comment","pipeline_type","previous_name","id") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23) RETURNING "id"`).
- WithArgs(1, AnyArgument{}, "foo", "bar", "foo/bar", nil, nil, nil, AnyArgument{}, AnyArgument{}, AnyArgument{}, "public", false, false, false, false, false, false, false, false, "yaml", "oldName", 1).
- WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := _database.CreateRepo(_repo)
-
- if test.failure {
- if err == nil {
- t.Errorf("CreateRepo should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("CreateRepo returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_Client_UpdateRepo(t *testing.T) {
- // setup types
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
- _repo.SetPipelineType("yaml")
- _repo.SetPreviousName("oldName")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // ensure the mock expects the query
- _mock.ExpectExec(`UPDATE "repos" SET "user_id"=$1,"hash"=$2,"org"=$3,"name"=$4,"full_name"=$5,"link"=$6,"clone"=$7,"branch"=$8,"build_limit"=$9,"timeout"=$10,"counter"=$11,"visibility"=$12,"private"=$13,"trusted"=$14,"active"=$15,"allow_pull"=$16,"allow_push"=$17,"allow_deploy"=$18,"allow_tag"=$19,"allow_comment"=$20,"pipeline_type"=$21,"previous_name"=$22 WHERE "id" = $23`).
- WithArgs(1, AnyArgument{}, "foo", "bar", "foo/bar", nil, nil, nil, AnyArgument{}, AnyArgument{}, AnyArgument{}, "public", false, false, false, false, false, false, false, false, "yaml", "oldName", 1).
- WillReturnResult(sqlmock.NewResult(1, 1))
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := _database.UpdateRepo(_repo)
-
- if test.failure {
- if err == nil {
- t.Errorf("UpdateRepo should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("UpdateRepo returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_Client_DeleteRepo(t *testing.T) {
- // setup types
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Exec(dml.DeleteRepo, 1).Statement
-
- // ensure the mock expects the query
- _mock.ExpectExec(_query.SQL.String()).WillReturnResult(sqlmock.NewResult(1, 1))
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := _database.DeleteRepo(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("DeleteRepo should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("DeleteRepo returned err: %v", err)
- }
- }
-}
-
-// testRepo is a test helper function to create a
-// library Repo type with all fields set to their
-// zero values.
-func testRepo() *library.Repo {
- i64 := int64(0)
- i := 0
- str := ""
- b := false
-
- return &library.Repo{
- ID: &i64,
- PipelineType: &str,
- UserID: &i64,
- Hash: &str,
- Org: &str,
- Name: &str,
- FullName: &str,
- Link: &str,
- Clone: &str,
- Branch: &str,
- BuildLimit: &i64,
- Timeout: &i64,
- Counter: &i,
- Visibility: &str,
- Private: &b,
- Trusted: &b,
- Active: &b,
- AllowPull: &b,
- AllowPush: &b,
- AllowDeploy: &b,
- AllowTag: &b,
- AllowComment: &b,
- PreviousName: &str,
- }
-}
diff --git a/database/postgres/secret.go b/database/postgres/secret.go
deleted file mode 100644
index 9c5b2a577..000000000
--- a/database/postgres/secret.go
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "errors"
- "fmt"
- "strings"
-
- "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"
-)
-
-// GetSecret gets a secret by type, org, name (repo or team) and secret name from the database.
-func (c *client) GetSecret(t, o, n, secretName string) (*library.Secret, error) {
- // create log fields from secret metadata
- fields := logrus.Fields{
- "org": o,
- "repo": n,
- "secret": secretName,
- "type": t,
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(t, constants.SecretShared) {
- // update log fields from secret metadata
- fields = logrus.Fields{
- "org": o,
- "team": n,
- "secret": secretName,
- "type": t,
- }
- }
-
- // nolint: lll // ignore long line length due to parameters
- c.Logger.WithFields(fields).Tracef("getting %s secret %s for %s/%s from the database", t, secretName, o, n)
-
- var err error
-
- // variable to store query results
- s := new(database.Secret)
-
- // send query to the database and store result in variable
- switch t {
- case constants.SecretOrg:
- result := c.Postgres.
- Table(constants.TableSecret).
- Raw(dml.SelectOrgSecret, o, secretName).
- 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
- }
-
- err = result.Error
- case constants.SecretRepo:
- result := c.Postgres.
- Table(constants.TableSecret).
- Raw(dml.SelectRepoSecret, o, n, secretName).
- 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
- }
-
- err = result.Error
- case constants.SecretShared:
- result := c.Postgres.
- Table(constants.TableSecret).
- Raw(dml.SelectSharedSecret, o, n, secretName).
- 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
- }
-
- err = result.Error
- }
- if err != nil {
- return nil, err
- }
-
- // decrypt the value for the secret
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Decrypt
- err = s.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted secrets
- c.Logger.Errorf("unable to decrypt %s secret %s for %s/%s: %v", t, secretName, o, n, err)
-
- // return the unencrypted secret
- return s.ToLibrary(), nil
- }
-
- // return the decrypted secret
- return s.ToLibrary(), nil
-}
-
-// CreateSecret creates a new secret in the database.
-//
-// nolint: dupl // ignore similar code with update
-func (c *client) CreateSecret(s *library.Secret) error {
- // create log fields from secret metadata
- fields := logrus.Fields{
- "org": s.GetOrg(),
- "repo": s.GetRepo(),
- "secret": s.GetName(),
- "type": s.GetType(),
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(s.GetType(), constants.SecretShared) {
- // update log fields from secret metadata
- fields = logrus.Fields{
- "org": s.GetOrg(),
- "team": s.GetTeam(),
- "secret": s.GetName(),
- "type": s.GetType(),
- }
- }
-
- // nolint: lll // ignore long line length due to parameters
- c.Logger.WithFields(fields).Tracef("creating %s secret %s in the database", s.GetType(), s.GetName())
-
- // cast to database type
- secret := database.SecretFromLibrary(s)
-
- // validate the necessary fields are populated
- err := secret.Validate()
- if err != nil {
- return err
- }
-
- // encrypt the value for the secret
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Encrypt
- err = secret.Encrypt(c.config.EncryptionKey)
- if err != nil {
- return fmt.Errorf("unable to encrypt secret %s: %v", s.GetName(), err)
- }
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableSecret).
- Create(secret.Nullify()).Error
-}
-
-// UpdateSecret updates a secret in the database.
-//
-// nolint: dupl // ignore similar code with create
-func (c *client) UpdateSecret(s *library.Secret) error {
- // create log fields from secret metadata
- fields := logrus.Fields{
- "org": s.GetOrg(),
- "repo": s.GetRepo(),
- "secret": s.GetName(),
- "type": s.GetType(),
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(s.GetType(), constants.SecretShared) {
- // update log fields from secret metadata
- fields = logrus.Fields{
- "org": s.GetOrg(),
- "team": s.GetTeam(),
- "secret": s.GetName(),
- "type": s.GetType(),
- }
- }
-
- // nolint: lll // ignore long line length due to parameters
- c.Logger.WithFields(fields).Tracef("updating %s secret %s in the database", s.GetType(), s.GetName())
-
- // cast to database type
- secret := database.SecretFromLibrary(s)
-
- // validate the necessary fields are populated
- err := secret.Validate()
- if err != nil {
- return err
- }
-
- // encrypt the value for the secret
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Encrypt
- err = secret.Encrypt(c.config.EncryptionKey)
- if err != nil {
- return fmt.Errorf("unable to encrypt secret %s: %v", s.GetName(), err)
- }
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableSecret).
- Save(secret.Nullify()).Error
-}
-
-// DeleteSecret deletes a secret by unique ID from the database.
-func (c *client) DeleteSecret(id int64) error {
- c.Logger.Tracef("deleting secret %d from the database", id)
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableSecret).
- Exec(dml.DeleteSecret, id).Error
-}
diff --git a/database/postgres/secret_count.go b/database/postgres/secret_count.go
deleted file mode 100644
index 162ee17a5..000000000
--- a/database/postgres/secret_count.go
+++ /dev/null
@@ -1,78 +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 (
- "strings"
-
- "github.com/sirupsen/logrus"
-
- "github.com/go-vela/server/database/postgres/dml"
- "github.com/go-vela/types/constants"
-)
-
-// GetTypeSecretCount gets a count of secrets by type,
-// owner, and name (repo or team) from the database.
-func (c *client) GetTypeSecretCount(t, o, n string, teams []string) (int64, error) {
- // create log fields from secret metadata
- fields := logrus.Fields{
- "org": o,
- "repo": n,
- "type": t,
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(t, constants.SecretShared) {
- // update log fields from secret metadata
- fields = logrus.Fields{
- "org": o,
- "team": n,
- "type": t,
- }
- }
-
- // nolint: lll // ignore long line length due to parameters
- c.Logger.WithFields(fields).Tracef("getting count of %s secrets for %s/%s from the database", t, o, n)
-
- var err error
-
- // variable to store query results
- var s int64
-
- // send query to the database and store result in variable
- switch t {
- case constants.SecretOrg:
- err = c.Postgres.
- Table(constants.TableSecret).
- Raw(dml.SelectOrgSecretsCount, o).
- Pluck("count", &s).Error
- case constants.SecretRepo:
- err = c.Postgres.
- Table(constants.TableSecret).
- Raw(dml.SelectRepoSecretsCount, o, n).
- Pluck("count", &s).Error
- case constants.SecretShared:
- if n == "*" {
- // GitHub teams are not case-sensitive, the DB is lowercase everything for matching
- var lowerTeams []string
- for _, t := range teams {
- lowerTeams = append(lowerTeams, strings.ToLower(t))
- }
- err = c.Postgres.
- Table(constants.TableSecret).
- Select("count(*)").
- Where("type = 'shared' AND org = ?", o).
- Where("LOWER(team) IN (?)", lowerTeams).
- Pluck("count", &s).Error
- } else {
- err = c.Postgres.
- Table(constants.TableSecret).
- Raw(dml.SelectSharedSecretsCount, o, n).
- Pluck("count", &s).Error
- }
- }
-
- return s, err
-}
diff --git a/database/postgres/secret_count_test.go b/database/postgres/secret_count_test.go
deleted file mode 100644
index f5f465d5a..000000000
--- a/database/postgres/secret_count_test.go
+++ /dev/null
@@ -1,291 +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_GetTypeSecretCount_Org(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetRepo("*")
- _secretOne.SetName("baz")
- _secretOne.SetValue("foob")
- _secretOne.SetType("org")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(1)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetRepo("*")
- _secretTwo.SetName("foob")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("org")
-
- // 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.SelectOrgSecretsCount, "foo").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.GetTypeSecretCount("org", "foo", "*", []string{})
-
- if test.failure {
- if err == nil {
- t.Errorf("GetTypeSecretCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetTypeSecretCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetTypeSecretCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetTypeSecretCount_Repo(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetRepo("bar")
- _secretOne.SetName("baz")
- _secretOne.SetValue("foob")
- _secretOne.SetType("repo")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(1)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetRepo("bar")
- _secretTwo.SetName("foob")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("repo")
-
- // 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.SelectRepoSecretsCount, "foo", "bar").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.GetTypeSecretCount("repo", "foo", "bar", []string{})
-
- if test.failure {
- if err == nil {
- t.Errorf("GetTypeSecretCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetTypeSecretCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetTypeSecretCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetTypeSecretCount_Shared(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetTeam("bar")
- _secretOne.SetName("baz")
- _secretOne.SetValue("foob")
- _secretOne.SetType("shared")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(1)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetTeam("bar")
- _secretTwo.SetName("foob")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("shared")
-
- // 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.SelectSharedSecretsCount, "foo", "bar").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.GetTypeSecretCount("shared", "foo", "bar", []string{})
-
- if test.failure {
- if err == nil {
- t.Errorf("GetTypeSecretCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetTypeSecretCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetTypeSecretCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetTypeSecretCount_Shared_Wildcard(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetTeam("bar")
- _secretOne.SetName("baz")
- _secretOne.SetValue("foob")
- _secretOne.SetType("shared")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(1)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetTeam("bared")
- _secretTwo.SetName("foob")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("shared")
-
- // 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
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
-
- // ensure the mock expects the query
- _mock.ExpectQuery("SELECT count(*) FROM \"secrets\" WHERE (type = 'shared' AND org = $1) AND LOWER(team) IN ($2,$3)").WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 2,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetTypeSecretCount("shared", "foo", "*", []string{"bar", "bared"})
-
- if test.failure {
- if err == nil {
- t.Errorf("GetTypeSecretCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetTypeSecretCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetTypeSecretCount is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/postgres/secret_list.go b/database/postgres/secret_list.go
deleted file mode 100644
index bdc26c041..000000000
--- a/database/postgres/secret_list.go
+++ /dev/null
@@ -1,153 +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 (
- "strings"
-
- "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"
-)
-
-// GetSecretList gets a list of all secrets from the database.
-//
-// nolint: dupl // ignore false positive of duplicate code
-func (c *client) GetSecretList() ([]*library.Secret, error) {
- c.Logger.Tracef("listing secrets from the database")
-
- // variable to store query results
- s := new([]database.Secret)
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableSecret).
- Raw(dml.ListSecrets).
- Scan(s).Error
- if err != nil {
- return nil, err
- }
-
- // variable we want to return
- secrets := []*library.Secret{}
- // iterate through all query results
- for _, secret := range *s {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := secret
-
- // decrypt the value for the secret
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Decrypt
- err = tmp.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted secrets
- c.Logger.Errorf("unable to decrypt secret %d: %v", tmp.ID.Int64, err)
- }
-
- // convert query result to library type
- secrets = append(secrets, tmp.ToLibrary())
- }
-
- return secrets, nil
-}
-
-// GetTypeSecretList gets a list of secrets by type,
-// owner, and name (repo or team) from the database.
-//
-// nolint: lll // ignore long line length
-func (c *client) GetTypeSecretList(t, o, n string, page, perPage int, teams []string) ([]*library.Secret, error) {
- // create log fields from secret metadata
- fields := logrus.Fields{
- "org": o,
- "repo": n,
- "type": t,
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(t, constants.SecretShared) {
- // update log fields from secret metadata
- fields = logrus.Fields{
- "org": o,
- "team": n,
- "type": t,
- }
- }
-
- // nolint: lll // ignore long line length due to parameters
- c.Logger.WithFields(fields).Tracef("listing %s secrets for %s/%s from the database", t, o, n)
-
- var err error
- // variable to store query results
- s := new([]database.Secret)
- // calculate offset for pagination through results
- offset := perPage * (page - 1)
-
- // send query to the database and store result in variable
- switch t {
- case constants.SecretOrg:
- err = c.Postgres.
- Table(constants.TableSecret).
- Raw(dml.ListOrgSecrets, o, perPage, offset).
- Scan(s).Error
- case constants.SecretRepo:
- err = c.Postgres.
- Table(constants.TableSecret).
- Raw(dml.ListRepoSecrets, o, n, perPage, offset).
- Scan(s).Error
- case constants.SecretShared:
- if n == "*" {
- // GitHub teams are not case-sensitive, the DB is lowercase everything for matching
- var lowerTeams []string
- for _, t := range teams {
- lowerTeams = append(lowerTeams, strings.ToLower(t))
- }
- err = c.Postgres.
- Table(constants.TableSecret).
- Where("type = 'shared' AND org = ?", o).
- Where("LOWER(team) IN (?)", lowerTeams).
- Order("id DESC").
- Limit(perPage).
- Offset(offset).
- Scan(s).Error
- } else {
- err = c.Postgres.
- Table(constants.TableSecret).
- Raw(dml.ListSharedSecrets, o, n, perPage, offset).
- Scan(s).Error
- }
- }
- if err != nil {
- return nil, err
- }
-
- // variable we want to return
- secrets := []*library.Secret{}
- // iterate through all query results
- for _, secret := range *s {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := secret
-
- // decrypt the value for the secret
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Decrypt
- err = tmp.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted secrets
- c.Logger.Errorf("unable to decrypt secret %d: %v", tmp.ID.Int64, err)
- }
-
- // convert query result to library type
- secrets = append(secrets, tmp.ToLibrary())
- }
-
- return secrets, nil
-}
diff --git a/database/postgres/secret_list_test.go b/database/postgres/secret_list_test.go
deleted file mode 100644
index 12b4b4c34..000000000
--- a/database/postgres/secret_list_test.go
+++ /dev/null
@@ -1,412 +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_GetSecretList(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetRepo("bar")
- _secretOne.SetName("baz")
- _secretOne.SetValue("foob")
- _secretOne.SetType("repo")
- _secretOne.SetCreatedAt(1)
- _secretOne.SetCreatedBy("user")
- _secretOne.SetUpdatedAt(1)
- _secretOne.SetUpdatedBy("user2")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(1)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetRepo("bar")
- _secretTwo.SetName("foob")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("repo")
- _secretTwo.SetCreatedAt(1)
- _secretTwo.SetCreatedBy("user")
- _secretTwo.SetUpdatedAt(1)
- _secretTwo.SetUpdatedBy("user2")
-
- // 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.ListSecrets).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"},
- ).AddRow(1, "repo", "foo", "bar", "", "baz", "foob", "{}", "{}", false, 1, "user", 1, "user2").
- AddRow(1, "repo", "foo", "bar", "", "foob", "baz", "{}", "{}", false, 1, "user", 1, "user2")
-
- // ensure the mock expects the query
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Secret
- }{
- {
- failure: false,
- want: []*library.Secret{_secretOne, _secretTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetSecretList()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetSecretList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetSecretList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetSecretList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetTypeSecretList_Org(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetRepo("*")
- _secretOne.SetName("baz")
- _secretOne.SetValue("bar")
- _secretOne.SetType("org")
- _secretOne.SetCreatedAt(1)
- _secretOne.SetCreatedBy("user")
- _secretOne.SetUpdatedAt(1)
- _secretOne.SetUpdatedBy("user2")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(1)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetRepo("*")
- _secretTwo.SetName("bar")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("org")
- _secretTwo.SetCreatedAt(1)
- _secretTwo.SetCreatedBy("user")
- _secretTwo.SetUpdatedAt(1)
- _secretTwo.SetUpdatedBy("user2")
-
- // 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.ListOrgSecrets, "foo", 1, 10).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"},
- ).AddRow(1, "org", "foo", "*", "", "baz", "bar", "{}", "{}", false, 1, "user", 1, "user2").
- AddRow(1, "org", "foo", "*", "", "bar", "baz", "{}", "{}", false, 1, "user", 1, "user2")
-
- // ensure the mock expects the query
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Secret
- }{
- {
- failure: false,
- want: []*library.Secret{_secretOne, _secretTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetTypeSecretList("org", "foo", "*", 1, 10, []string{})
-
- if test.failure {
- if err == nil {
- t.Errorf("GetTypeSecretList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetTypeSecretList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetTypeSecretList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetTypeSecretList_Repo(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetRepo("bar")
- _secretOne.SetName("baz")
- _secretOne.SetValue("foob")
- _secretOne.SetType("repo")
- _secretOne.SetCreatedAt(1)
- _secretOne.SetCreatedBy("user")
- _secretOne.SetUpdatedAt(1)
- _secretOne.SetUpdatedBy("user2")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(1)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetRepo("bar")
- _secretTwo.SetName("foob")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("repo")
- _secretTwo.SetCreatedAt(1)
- _secretTwo.SetCreatedBy("user")
- _secretTwo.SetUpdatedAt(1)
- _secretTwo.SetUpdatedBy("user2")
-
- // 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.ListRepoSecrets, "foo", "bar", 1, 10).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"},
- ).AddRow(1, "repo", "foo", "bar", "", "baz", "foob", "{}", "{}", false, 1, "user", 1, "user2").
- AddRow(1, "repo", "foo", "bar", "", "foob", "baz", "{}", "{}", false, 1, "user", 1, "user2")
-
- // ensure the mock expects the query
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Secret
- }{
- {
- failure: false,
- want: []*library.Secret{_secretOne, _secretTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetTypeSecretList("repo", "foo", "bar", 1, 10, []string{})
-
- if test.failure {
- if err == nil {
- t.Errorf("GetTypeSecretList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetTypeSecretList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetTypeSecretList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetTypeSecretList_Shared(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetTeam("bar")
- _secretOne.SetName("baz")
- _secretOne.SetValue("foob")
- _secretOne.SetType("shared")
- _secretOne.SetCreatedAt(1)
- _secretOne.SetCreatedBy("user")
- _secretOne.SetUpdatedAt(1)
- _secretOne.SetUpdatedBy("user2")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(1)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetTeam("bar")
- _secretTwo.SetName("foob")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("shared")
- _secretTwo.SetCreatedAt(1)
- _secretTwo.SetCreatedBy("user")
- _secretTwo.SetUpdatedAt(1)
- _secretTwo.SetUpdatedBy("user2")
-
- // 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.ListSharedSecrets, "foo", "bar", 1, 10).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"},
- ).AddRow(1, "shared", "foo", "", "bar", "baz", "foob", "{}", "{}", false, 1, "user", 1, "user2").
- AddRow(1, "shared", "foo", "", "bar", "foob", "baz", "{}", "{}", false, 1, "user", 1, "user2")
-
- // ensure the mock expects the query
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Secret
- }{
- {
- failure: false,
- want: []*library.Secret{_secretOne, _secretTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetTypeSecretList("shared", "foo", "bar", 1, 10, []string{"bar"})
-
- if test.failure {
- if err == nil {
- t.Errorf("GetTypeSecretList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetTypeSecretList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetTypeSecretList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetTypeSecretList_Shared_Wildcard(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetTeam("bar")
- _secretOne.SetName("baz")
- _secretOne.SetValue("foob")
- _secretOne.SetType("shared")
- _secretOne.SetCreatedAt(1)
- _secretOne.SetCreatedBy("user")
- _secretOne.SetUpdatedAt(1)
- _secretOne.SetUpdatedBy("user2")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(1)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetTeam("bared")
- _secretTwo.SetName("foob")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("shared")
- _secretTwo.SetCreatedAt(1)
- _secretTwo.SetCreatedBy("user")
- _secretTwo.SetUpdatedAt(1)
- _secretTwo.SetUpdatedBy("user2")
-
- // 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", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"},
- ).AddRow(1, "shared", "foo", "", "bar", "baz", "foob", "{}", "{}", false, 1, "user", 1, "user2").
- AddRow(1, "shared", "foo", "", "bared", "foob", "baz", "{}", "{}", false, 1, "user", 1, "user2")
-
- // ensure the mock expects the query
- _mock.ExpectQuery("SELECT * FROM \"secrets\" WHERE (type = 'shared' AND org = $1) AND LOWER(team) IN ($2,$3) ORDER BY id DESC LIMIT 10").WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Secret
- }{
- {
- failure: false,
- want: []*library.Secret{_secretOne, _secretTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetTypeSecretList("shared", "foo", "*", 1, 10, []string{"bar", "bared"})
-
- if test.failure {
- if err == nil {
- t.Errorf("GetTypeSecretList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetTypeSecretList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetTypeSecretList is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/postgres/secret_test.go b/database/postgres/secret_test.go
deleted file mode 100644
index 9ea9c7c19..000000000
--- a/database/postgres/secret_test.go
+++ /dev/null
@@ -1,418 +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"
- "time"
-
- 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_GetSecret_Org(t *testing.T) {
- // setup types
- _secret := testSecret()
- _secret.SetID(1)
- _secret.SetOrg("foo")
- _secret.SetRepo("*")
- _secret.SetName("bar")
- _secret.SetValue("baz")
- _secret.SetType("org")
- _secret.SetCreatedAt(1)
- _secret.SetCreatedBy("user")
- _secret.SetUpdatedAt(1)
- _secret.SetUpdatedBy("user2")
-
- // 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.SelectOrgSecret, "foo", "bar").Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"},
- ).AddRow(1, "org", "foo", "*", "", "bar", "baz", "{}", "{}", false, 1, "user", 1, "user2")
-
- // 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.Secret
- }{
- {
- failure: false,
- want: _secret,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetSecret("org", "foo", "*", "bar")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetSecret should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetSecret returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetSecret is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetSecret_Repo(t *testing.T) {
- // setup types
- _secret := testSecret()
- _secret.SetID(1)
- _secret.SetOrg("foo")
- _secret.SetRepo("bar")
- _secret.SetName("baz")
- _secret.SetValue("foob")
- _secret.SetType("repo")
- _secret.SetCreatedAt(1)
- _secret.SetCreatedBy("user")
- _secret.SetUpdatedAt(1)
- _secret.SetUpdatedBy("user2")
-
- // 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.SelectRepoSecret, "foo", "bar", "baz").Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"},
- ).AddRow(1, "repo", "foo", "bar", "", "baz", "foob", "{}", "{}", false, 1, "user", 1, "user2")
-
- // 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.Secret
- }{
- {
- failure: false,
- want: _secret,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetSecret("repo", "foo", "bar", "baz")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetSecret should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetSecret returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetSecret is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetSecret_Shared(t *testing.T) {
- // setup types
- _secret := testSecret()
- _secret.SetID(1)
- _secret.SetOrg("foo")
- _secret.SetTeam("bar")
- _secret.SetName("baz")
- _secret.SetValue("foob")
- _secret.SetType("shared")
- _secret.SetCreatedAt(1)
- _secret.SetCreatedBy("user")
- _secret.SetUpdatedAt(1)
- _secret.SetUpdatedBy("user2")
-
- // 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.SelectSharedSecret, "foo", "bar", "baz").Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"},
- ).AddRow(1, "shared", "foo", "", "bar", "baz", "foob", "{}", "{}", false, 1, "user", 1, "user2")
-
- // 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.Secret
- }{
- {
- failure: false,
- want: _secret,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetSecret("shared", "foo", "bar", "baz")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetSecret should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetSecret returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetSecret is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_CreateSecret(t *testing.T) {
- // setup types
- _secret := testSecret()
- _secret.SetID(1)
- _secret.SetOrg("foo")
- _secret.SetRepo("bar")
- _secret.SetName("baz")
- _secret.SetValue("foob")
- _secret.SetType("repo")
- _secret.SetCreatedAt(1)
- _secret.SetCreatedBy("user")
- _secret.SetUpdatedAt(1)
- _secret.SetUpdatedBy("user2")
-
- // 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 "secrets" ("org","repo","team","name","value","type","images","events","allow_command","created_at","created_by","updated_at","updated_by","id") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) RETURNING "id"`).
- WithArgs("foo", "bar", nil, "baz", AnyArgument{}, "repo", "{}", "{}", false, 1, "user", 1, "user2", 1).
- WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := _database.CreateSecret(_secret)
-
- if test.failure {
- if err == nil {
- t.Errorf("CreateSecret should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("CreateSecret returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_Client_UpdateSecret(t *testing.T) {
- // setup types
- _secret := testSecret()
- _secret.SetID(1)
- _secret.SetOrg("foo")
- _secret.SetRepo("bar")
- _secret.SetName("baz")
- _secret.SetValue("foob")
- _secret.SetType("repo")
- _secret.SetCreatedAt(1)
- _secret.SetCreatedBy("user")
- _secret.SetUpdatedAt(1)
- _secret.SetUpdatedBy("user2")
-
- // 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 "secrets" SET "org"=$1,"repo"=$2,"team"=$3,"name"=$4,"value"=$5,"type"=$6,"images"=$7,"events"=$8,"allow_command"=$9,"created_at"=$10,"created_by"=$11,"updated_at"=$12,"updated_by"=$13 WHERE "id" = $14`).
- WithArgs("foo", "bar", nil, "baz", AnyArgument{}, "repo", "{}", "{}", false, 1, "user", time.Now().UTC().Unix(), "user2", 1).
- WillReturnResult(sqlmock.NewResult(1, 1))
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := _database.UpdateSecret(_secret)
-
- if test.failure {
- if err == nil {
- t.Errorf("UpdateSecret should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("UpdateSecret returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_Client_DeleteSecret(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.DeleteSecret, 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.DeleteSecret(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("DeleteSecret should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("DeleteSecret returned err: %v", err)
- }
- }
-}
-
-// testSecret is a test helper function to create a
-// library Secret type with all fields set to their
-// zero values.
-func testSecret() *library.Secret {
- i64 := int64(0)
- str := ""
- arr := []string{}
- booL := false
-
- return &library.Secret{
- ID: &i64,
- Org: &str,
- Repo: &str,
- Team: &str,
- Name: &str,
- Value: &str,
- Type: &str,
- Images: &arr,
- Events: &arr,
- AllowCommand: &booL,
- CreatedAt: &i64,
- CreatedBy: &str,
- UpdatedAt: &i64,
- UpdatedBy: &str,
- }
-}
diff --git a/database/postgres/service.go b/database/postgres/service.go
deleted file mode 100644
index 011a09770..000000000
--- a/database/postgres/service.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"
-)
-
-// GetService gets a service by number and build ID from the database.
-func (c *client) GetService(number int, b *library.Build) (*library.Service, error) {
- c.Logger.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "service": number,
- }).Tracef("getting service %d for build %d from the database", number, b.GetNumber())
-
- // variable to store query results
- s := new(database.Service)
-
- // send query to the database and store result in variable
- result := c.Postgres.
- Table(constants.TableService).
- Raw(dml.SelectBuildService, 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
-}
-
-// CreateService creates a new service in the database.
-func (c *client) CreateService(s *library.Service) error {
- c.Logger.WithFields(logrus.Fields{
- "service": s.GetNumber(),
- }).Tracef("creating service %s in the database", s.GetName())
-
- // cast to database type
- service := database.ServiceFromLibrary(s)
-
- // validate the necessary fields are populated
- err := service.Validate()
- if err != nil {
- return err
- }
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableService).
- Create(service).Error
-}
-
-// UpdateService updates a service in the database.
-func (c *client) UpdateService(s *library.Service) error {
- c.Logger.WithFields(logrus.Fields{
- "service": s.GetNumber(),
- }).Tracef("updating service %s in the database", s.GetName())
-
- // cast to database type
- service := database.ServiceFromLibrary(s)
-
- // validate the necessary fields are populated
- err := service.Validate()
- if err != nil {
- return err
- }
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableService).
- Save(service).Error
-}
-
-// DeleteService deletes a service by unique ID from the database.
-func (c *client) DeleteService(id int64) error {
- c.Logger.Tracef("deleting service %d from the database", id)
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableService).
- Exec(dml.DeleteService, id).Error
-}
diff --git a/database/postgres/service_count.go b/database/postgres/service_count.go
deleted file mode 100644
index 426239bc2..000000000
--- a/database/postgres/service_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"
-)
-
-// GetBuildServiceCount gets a count of all services by build ID from the database.
-func (c *client) GetBuildServiceCount(b *library.Build) (int64, error) {
- c.Logger.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- }).Tracef("getting count of services 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.TableService).
- Raw(dml.SelectBuildServicesCount, b.GetID()).
- Pluck("count", &s).Error
-
- return s, err
-}
-
-// GetServiceImageCount gets a count of all service images
-// and the count of their occurrence in the database.
-func (c *client) GetServiceImageCount() (map[string]float64, error) {
- c.Logger.Tracef("getting count of all images for services 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.TableService).
- Raw(dml.SelectServiceImagesCount).
- Scan(images).Error
-
- for _, image := range *images {
- counts[image.Image] = float64(image.Count)
- }
-
- return counts, err
-}
-
-// GetServiceStatusCount gets a list of all service statuses
-// and the count of their occurrence in the database.
-func (c *client) GetServiceStatusCount() (map[string]float64, error) {
- c.Logger.Trace("getting count of all statuses for services 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.TableService).
- Raw(dml.SelectServiceStatusesCount).
- Scan(s).Error
-
- for _, status := range *s {
- counts[status.Status] = float64(status.Count)
- }
-
- return counts, err
-}
diff --git a/database/postgres/service_count_test.go b/database/postgres/service_count_test.go
deleted file mode 100644
index 513b1ac8d..000000000
--- a/database/postgres/service_count_test.go
+++ /dev/null
@@ -1,193 +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_GetBuildServiceCount(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.SelectBuildServicesCount, 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.GetBuildServiceCount(_build)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetBuildServiceCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetBuildServiceCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetBuildServiceCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetServiceImageCount(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.SelectServiceImagesCount).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.GetServiceImageCount()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetServiceImageCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetServiceImageCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetServiceImageCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetServiceStatusCount(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.SelectServiceStatusesCount).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.GetServiceStatusCount()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetServiceStatusCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetServiceStatusCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetServiceStatusCount is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/postgres/service_list.go b/database/postgres/service_list.go
deleted file mode 100644
index 04b3fc949..000000000
--- a/database/postgres/service_list.go
+++ /dev/null
@@ -1,73 +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"
-)
-
-// GetServiceList gets a list of all services from the database.
-func (c *client) GetServiceList() ([]*library.Service, error) {
- c.Logger.Trace("listing services from the database")
-
- // variable to store query results
- s := new([]database.Service)
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableService).
- Raw(dml.ListServices).
- Scan(s).Error
-
- // variable we want to return
- services := []*library.Service{}
- // iterate through all query results
- for _, service := range *s {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := service
-
- // convert query result to library type
- services = append(services, tmp.ToLibrary())
- }
-
- return services, err
-}
-
-// GetBuildServiceList gets a list of services by build ID from the database.
-//
-// nolint: lll // ignore long line length due to parameters
-func (c *client) GetBuildServiceList(b *library.Build, page, perPage int) ([]*library.Service, error) {
- c.Logger.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- }).Tracef("listing services for build %d from the database", b.GetNumber())
-
- // variable to store query results
- s := new([]database.Service)
- // 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.TableService).
- Raw(dml.ListBuildServices, b.GetID(), perPage, offset).
- Scan(s).Error
-
- // variable we want to return
- services := []*library.Service{}
- // iterate through all query results
- for _, service := range *s {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := service
-
- // convert query result to library type
- services = append(services, tmp.ToLibrary())
- }
-
- return services, err
-}
diff --git a/database/postgres/service_list_test.go b/database/postgres/service_list_test.go
deleted file mode 100644
index 8787831f9..000000000
--- a/database/postgres/service_list_test.go
+++ /dev/null
@@ -1,166 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package 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_GetServiceList(t *testing.T) {
- // setup types
- _serviceOne := testService()
- _serviceOne.SetID(1)
- _serviceOne.SetRepoID(1)
- _serviceOne.SetBuildID(1)
- _serviceOne.SetNumber(1)
- _serviceOne.SetName("foo")
- _serviceOne.SetImage("bar")
-
- _serviceTwo := testService()
- _serviceTwo.SetID(2)
- _serviceTwo.SetRepoID(1)
- _serviceTwo.SetBuildID(1)
- _serviceTwo.SetNumber(1)
- _serviceTwo.SetName("bar")
- _serviceTwo.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.ListServices).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "repo_id", "build_id", "number", "name", "image", "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.Service
- }{
- {
- failure: false,
- want: []*library.Service{_serviceOne, _serviceTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetServiceList()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetServiceList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetServiceList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetServiceList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetBuildServiceList(t *testing.T) {
- // setup types
- _build := testBuild()
- _build.SetID(1)
- _build.SetRepoID(1)
- _build.SetNumber(1)
-
- _serviceOne := testService()
- _serviceOne.SetID(1)
- _serviceOne.SetRepoID(1)
- _serviceOne.SetBuildID(1)
- _serviceOne.SetNumber(1)
- _serviceOne.SetName("foo")
- _serviceOne.SetImage("bar")
-
- _serviceTwo := testService()
- _serviceTwo.SetID(2)
- _serviceTwo.SetRepoID(1)
- _serviceTwo.SetBuildID(1)
- _serviceTwo.SetNumber(1)
- _serviceTwo.SetName("bar")
- _serviceTwo.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.ListBuildServices, 1, 1, 10).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "repo_id", "build_id", "number", "name", "image", "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.Service
- }{
- {
- failure: false,
- want: []*library.Service{_serviceOne, _serviceTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetBuildServiceList(_build, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetBuildServiceList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetBuildServiceList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetBuildServiceList is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/postgres/service_test.go b/database/postgres/service_test.go
deleted file mode 100644
index 7689f7ead..000000000
--- a/database/postgres/service_test.go
+++ /dev/null
@@ -1,264 +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_GetService(t *testing.T) {
- // setup types
- _build := testBuild()
- _build.SetID(1)
- _build.SetRepoID(1)
- _build.SetNumber(1)
-
- _service := testService()
- _service.SetID(1)
- _service.SetRepoID(1)
- _service.SetBuildID(1)
- _service.SetNumber(1)
- _service.SetName("foo")
- _service.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.SelectBuildService, 1, 1).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "repo_id", "build_id", "number", "name", "image", "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.Service
- }{
- {
- failure: false,
- want: _service,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetService(1, _build)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetService should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetService returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetService is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_CreateService(t *testing.T) {
- // setup types
- _service := testService()
- _service.SetID(1)
- _service.SetRepoID(1)
- _service.SetBuildID(1)
- _service.SetNumber(1)
- _service.SetName("foo")
- _service.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 "services" ("build_id","repo_id","number","name","image","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) RETURNING "id"`).
- WithArgs(1, 1, 1, "foo", "bar", 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.CreateService(_service)
-
- if test.failure {
- if err == nil {
- t.Errorf("CreateService should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("CreateService returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_Client_UpdateService(t *testing.T) {
- // setup types
- _service := testService()
- _service.SetID(1)
- _service.SetRepoID(1)
- _service.SetBuildID(1)
- _service.SetNumber(1)
- _service.SetName("foo")
- _service.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 "services" SET "build_id"=$1,"repo_id"=$2,"number"=$3,"name"=$4,"image"=$5,"status"=$6,"error"=$7,"exit_code"=$8,"created"=$9,"started"=$10,"finished"=$11,"host"=$12,"runtime"=$13,"distribution"=$14 WHERE "id" = $15`).
- WithArgs(1, 1, 1, "foo", "bar", 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.UpdateService(_service)
-
- if test.failure {
- if err == nil {
- t.Errorf("UpdateService should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("UpdateService returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_Client_DeleteService(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.DeleteService, 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.DeleteService(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("DeleteService should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("DeleteService returned err: %v", err)
- }
- }
-}
-
-// testService is a test helper function to create a
-// library Service type with all fields set to their
-// zero values.
-func testService() *library.Service {
- i64 := int64(0)
- i := 0
- str := ""
-
- return &library.Service{
- ID: &i64,
- BuildID: &i64,
- RepoID: &i64,
- Number: &i,
- Name: &str,
- Image: &str,
- Status: &str,
- Error: &str,
- ExitCode: &i,
- Created: &i64,
- Started: &i64,
- Finished: &i64,
- Host: &str,
- Runtime: &str,
- Distribution: &str,
- }
-}
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 227ba5021..000000000
--- a/database/postgres/step_count_test.go
+++ /dev/null
@@ -1,193 +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 2c8ce586f..000000000
--- a/database/postgres/step_list_test.go
+++ /dev/null
@@ -1,166 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package 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 31f8dc26a..000000000
--- a/database/postgres/step_test.go
+++ /dev/null
@@ -1,265 +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/postgres/user.go b/database/postgres/user.go
deleted file mode 100644
index d7b8c5f87..000000000
--- a/database/postgres/user.go
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "errors"
- "fmt"
-
- "github.com/sirupsen/logrus"
-
- "github.com/go-vela/server/database/postgres/dml"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/database"
- "github.com/go-vela/types/library"
-
- "gorm.io/gorm"
-)
-
-// GetUser gets a user by unique ID from the database.
-func (c *client) GetUser(id int64) (*library.User, error) {
- c.Logger.Tracef("getting user %d from the database", id)
-
- // variable to store query results
- u := new(database.User)
-
- // send query to the database and store result in variable
- result := c.Postgres.
- Table(constants.TableUser).
- Raw(dml.SelectUser, id).
- Scan(u)
-
- // check if the query returned a record not found error or no rows were returned
- if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 {
- return nil, gorm.ErrRecordNotFound
- }
-
- // decrypt the fields for the user
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt
- err := u.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted users
- c.Logger.Errorf("unable to decrypt user %d: %v", id, err)
-
- // return the unencrypted user
- return u.ToLibrary(), result.Error
- }
-
- // return the decrypted user
- return u.ToLibrary(), result.Error
-}
-
-// GetUserName gets a user by name from the database.
-func (c *client) GetUserName(name string) (*library.User, error) {
- c.Logger.WithFields(logrus.Fields{
- "user": name,
- }).Tracef("getting user %s from the database", name)
-
- // variable to store query results
- u := new(database.User)
-
- // send query to the database and store result in variable
- result := c.Postgres.
- Table(constants.TableUser).
- Raw(dml.SelectUserName, name).
- Scan(u)
-
- // check if the query returned a record not found error or no rows were returned
- if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 {
- return nil, gorm.ErrRecordNotFound
- }
-
- // decrypt the fields for the user
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt
- err := u.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted users
- c.Logger.Errorf("unable to decrypt user %s: %v", name, err)
-
- // return the unencrypted user
- return u.ToLibrary(), result.Error
- }
-
- // return the decrypted user
- return u.ToLibrary(), result.Error
-}
-
-// CreateUser creates a new user in the database.
-//
-// nolint: dupl // ignore similar code with update
-func (c *client) CreateUser(u *library.User) error {
- c.Logger.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Tracef("creating user %s in the database", u.GetName())
-
- // cast to database type
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#UserFromLibrary
- user := database.UserFromLibrary(u)
-
- // validate the necessary fields are populated
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#User.Validate
- err := user.Validate()
- if err != nil {
- return err
- }
-
- // encrypt the fields for the user
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#User.Encrypt
- err = user.Encrypt(c.config.EncryptionKey)
- if err != nil {
- return fmt.Errorf("unable to encrypt user %s: %v", u.GetName(), err)
- }
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableUser).
- Create(user).Error
-}
-
-// UpdateUser updates a user in the database.
-//
-// nolint: dupl // ignore similar code with create
-func (c *client) UpdateUser(u *library.User) error {
- c.Logger.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Tracef("updating user %s in the database", u.GetName())
-
- // cast to database type
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#UserFromLibrary
- user := database.UserFromLibrary(u)
-
- // validate the necessary fields are populated
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#User.Validate
- err := user.Validate()
- if err != nil {
- return err
- }
-
- // encrypt the fields for the user
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#User.Encrypt
- err = user.Encrypt(c.config.EncryptionKey)
- if err != nil {
- return fmt.Errorf("unable to encrypt user %s: %v", u.GetName(), err)
- }
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableUser).
- Save(user).Error
-}
-
-// DeleteUser deletes a user by unique ID from the database.
-func (c *client) DeleteUser(id int64) error {
- c.Logger.Tracef("deleting user %d from the database", id)
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableUser).
- Exec(dml.DeleteUser, id).Error
-}
diff --git a/database/postgres/user_count.go b/database/postgres/user_count.go
deleted file mode 100644
index 9b41092ab..000000000
--- a/database/postgres/user_count.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "github.com/go-vela/server/database/postgres/dml"
- "github.com/go-vela/types/constants"
-)
-
-// GetUserCount gets a count of all users from the database.
-func (c *client) GetUserCount() (int64, error) {
- c.Logger.Trace("getting count of users from the database")
-
- // variable to store query results
- var u int64
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableUser).
- Raw(dml.SelectUsersCount).
- Pluck("count", &u).Error
-
- return u, err
-}
diff --git a/database/postgres/user_count_test.go b/database/postgres/user_count_test.go
deleted file mode 100644
index ccc22c873..000000000
--- a/database/postgres/user_count_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "reflect"
- "testing"
-
- sqlmock "github.com/DATA-DOG/go-sqlmock"
-
- "github.com/go-vela/server/database/postgres/dml"
-
- "gorm.io/gorm"
-)
-
-func TestPostgres_Client_GetUserCount(t *testing.T) {
- // setup types
- _userOne := testUser()
- _userOne.SetID(1)
- _userOne.SetName("foo")
- _userOne.SetToken("bar")
- _userOne.SetHash("baz")
-
- _userTwo := testUser()
- _userTwo.SetID(2)
- _userTwo.SetName("bar")
- _userTwo.SetToken("foo")
- _userTwo.SetHash("baz")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectUsersCount).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
-
- // ensure the mock expects the query
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 2,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetUserCount()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetUserCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetUserCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetUserCount is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/postgres/user_list.go b/database/postgres/user_list.go
deleted file mode 100644
index 0dfb227f4..000000000
--- a/database/postgres/user_list.go
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "github.com/go-vela/server/database/postgres/dml"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/database"
- "github.com/go-vela/types/library"
-)
-
-// GetUserList gets a list of all users from the database.
-//
-// nolint: dupl // ignore false positive of duplicate code
-func (c *client) GetUserList() ([]*library.User, error) {
- c.Logger.Trace("listing users from the database")
-
- // variable to store query results
- u := new([]database.User)
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableUser).
- Raw(dml.ListUsers).
- Scan(u).Error
- if err != nil {
- return nil, err
- }
-
- // variable we want to return
- users := []*library.User{}
- // iterate through all query results
- for _, user := range *u {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := user
-
- // decrypt the fields for the user
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt
- err = tmp.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted users
- c.Logger.Errorf("unable to decrypt user %d: %v", tmp.ID.Int64, err)
- }
-
- // convert query result to library type
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#User.ToLibrary
- users = append(users, tmp.ToLibrary())
- }
-
- return users, nil
-}
-
-// GetUserLiteList gets a lite list of all users from the database.
-func (c *client) GetUserLiteList(page, perPage int) ([]*library.User, error) {
- c.Logger.Trace("listing lite users from the database")
-
- // variable to store query results
- u := new([]database.User)
- // calculate offset for pagination through results
- offset := perPage * (page - 1)
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableUser).
- Raw(dml.ListLiteUsers, perPage, offset).
- Scan(u).Error
-
- // variable we want to return
- users := []*library.User{}
- // iterate through all query results
- for _, user := range *u {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := user
-
- // convert query result to library type
- users = append(users, tmp.ToLibrary())
- }
-
- return users, err
-}
diff --git a/database/postgres/user_list_test.go b/database/postgres/user_list_test.go
deleted file mode 100644
index eb69b6c90..000000000
--- a/database/postgres/user_list_test.go
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "reflect"
- "testing"
-
- sqlmock "github.com/DATA-DOG/go-sqlmock"
-
- "github.com/go-vela/server/database/postgres/dml"
- "github.com/go-vela/types/library"
-
- "gorm.io/gorm"
-)
-
-func TestPostgres_Client_GetUserList(t *testing.T) {
- // setup types
- _userOne := testUser()
- _userOne.SetID(1)
- _userOne.SetName("foo")
- _userOne.SetToken("bar")
- _userOne.SetHash("baz")
-
- _userTwo := testUser()
- _userTwo.SetID(2)
- _userTwo.SetName("bar")
- _userTwo.SetToken("foo")
- _userTwo.SetHash("baz")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.ListUsers).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "name", "refresh_token", "token", "hash", "favorites", "active", "admin"},
- ).AddRow(1, "foo", "", "bar", "baz", "{}", false, false).
- AddRow(2, "bar", "", "foo", "baz", "{}", false, false)
-
- // ensure the mock expects the query
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.User
- }{
- {
- failure: false,
- want: []*library.User{_userOne, _userTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetUserList()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetUserList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetUserList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetUserList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetUserLiteList(t *testing.T) {
- // setup types
- _userOne := testUser()
- _userOne.SetID(1)
- _userOne.SetName("foo")
- _userOne.SetFavorites(nil)
-
- _userTwo := testUser()
- _userTwo.SetID(2)
- _userTwo.SetName("bar")
- _userTwo.SetFavorites(nil)
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.ListLiteUsers, 1, 10).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"id", "name"}).AddRow(1, "foo").AddRow(2, "bar")
-
- // ensure the mock expects the query
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.User
- }{
- {
- failure: false,
- want: []*library.User{_userOne, _userTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetUserLiteList(1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetUserLiteList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetUserLiteList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetUserLiteList is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/postgres/user_test.go b/database/postgres/user_test.go
deleted file mode 100644
index 3dd2f3ebf..000000000
--- a/database/postgres/user_test.go
+++ /dev/null
@@ -1,247 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "reflect"
- "testing"
-
- sqlmock "github.com/DATA-DOG/go-sqlmock"
-
- "github.com/go-vela/server/database/postgres/dml"
- "github.com/go-vela/types/library"
-
- "gorm.io/gorm"
-)
-
-func TestPostgres_Client_GetUser(t *testing.T) {
- // setup types
- _user := testUser()
- _user.SetID(1)
- _user.SetName("foo")
- _user.SetToken("bar")
- _user.SetHash("baz")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectUser, 1).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "name", "refresh_token", "token", "hash", "favorites", "active", "admin"},
- ).AddRow(1, "foo", "", "bar", "baz", "{}", false, false)
-
- // ensure the mock expects the query for test case 1
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
- // ensure the mock expects the error for test case 2
- _mock.ExpectQuery(_query.SQL.String()).WillReturnError(gorm.ErrRecordNotFound)
-
- // setup tests
- tests := []struct {
- failure bool
- want *library.User
- }{
- {
- failure: false,
- want: _user,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetUser(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetUser should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetUser returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetUser is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_CreateUser(t *testing.T) {
- // setup types
- _user := testUser()
- _user.SetID(1)
- _user.SetName("foo")
- _user.SetToken("bar")
- _user.SetHash("baz")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"id"}).AddRow(1)
-
- // ensure the mock expects the query
- _mock.ExpectQuery(`INSERT INTO "users" ("name","refresh_token","token","hash","favorites","active","admin","id") VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING "id"`).
- WithArgs("foo", AnyArgument{}, AnyArgument{}, AnyArgument{}, "{}", false, false, 1).
- WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := _database.CreateUser(_user)
-
- if test.failure {
- if err == nil {
- t.Errorf("CreateUser should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("CreateUser returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_Client_UpdateUser(t *testing.T) {
- // setup types
- _user := testUser()
- _user.SetID(1)
- _user.SetName("foo")
- _user.SetToken("bar")
- _user.SetHash("baz")
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // ensure the mock expects the query
- _mock.ExpectExec(`UPDATE "users" SET "name"=$1,"refresh_token"=$2,"token"=$3,"hash"=$4,"favorites"=$5,"active"=$6,"admin"=$7 WHERE "id" = $8`).
- WithArgs("foo", AnyArgument{}, AnyArgument{}, AnyArgument{}, "{}", false, false, 1).
- WillReturnResult(sqlmock.NewResult(1, 1))
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := _database.UpdateUser(_user)
-
- if test.failure {
- if err == nil {
- t.Errorf("UpdateUser should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("UpdateUser returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_Client_DeleteUser(t *testing.T) {
- // setup types
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Exec(dml.DeleteUser, 1).Statement
-
- // ensure the mock expects the query
- _mock.ExpectExec(_query.SQL.String()).WillReturnResult(sqlmock.NewResult(1, 1))
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := _database.DeleteUser(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("DeleteUser should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("DeleteUser returned err: %v", err)
- }
- }
-}
-
-// testUser is a test helper function to create a
-// library User type with all fields set to their
-// zero values.
-func testUser() *library.User {
- i64 := int64(0)
- str := ""
- arr := []string{}
- b := false
-
- return &library.User{
- ID: &i64,
- Name: &str,
- RefreshToken: &str,
- Token: &str,
- Hash: &str,
- Favorites: &arr,
- Active: &b,
- Admin: &b,
- }
-}
diff --git a/database/postgres/worker.go b/database/postgres/worker.go
deleted file mode 100644
index 1d871f596..000000000
--- a/database/postgres/worker.go
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "errors"
-
- "github.com/sirupsen/logrus"
-
- "github.com/go-vela/server/database/postgres/dml"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/database"
- "github.com/go-vela/types/library"
-
- "gorm.io/gorm"
-)
-
-// GetWorker gets a worker by hostname from the database.
-func (c *client) GetWorker(hostname string) (*library.Worker, error) {
- c.Logger.WithFields(logrus.Fields{
- "worker": hostname,
- }).Tracef("getting worker %s from the database", hostname)
-
- // variable to store query results
- w := new(database.Worker)
-
- // send query to the database and store result in variable
- result := c.Postgres.
- Table(constants.TableWorker).
- Raw(dml.SelectWorker, hostname).
- Scan(w)
-
- // check if the query returned a record not found error or no rows were returned
- if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 {
- return nil, gorm.ErrRecordNotFound
- }
-
- return w.ToLibrary(), result.Error
-}
-
-// GetWorker gets a worker by address from the database.
-func (c *client) GetWorkerByAddress(address string) (*library.Worker, error) {
- c.Logger.Tracef("getting worker by address %s from the database", address)
-
- // variable to store query results
- w := new(database.Worker)
-
- // send query to the database and store result in variable
- result := c.Postgres.
- Table(constants.TableWorker).
- Raw(dml.SelectWorkerByAddress, address).
- Scan(w)
-
- // check if the query returned a record not found error or no rows were returned
- if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 {
- return nil, gorm.ErrRecordNotFound
- }
-
- return w.ToLibrary(), result.Error
-}
-
-// CreateWorker creates a new worker in the database.
-func (c *client) CreateWorker(w *library.Worker) error {
- c.Logger.WithFields(logrus.Fields{
- "worker": w.GetHostname(),
- }).Tracef("creating worker %s in the database", w.GetHostname())
-
- // cast to database type
- worker := database.WorkerFromLibrary(w)
-
- // validate the necessary fields are populated
- err := worker.Validate()
- if err != nil {
- return err
- }
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableWorker).
- Create(worker).Error
-}
-
-// UpdateWorker updates a worker in the database.
-func (c *client) UpdateWorker(w *library.Worker) error {
- c.Logger.WithFields(logrus.Fields{
- "worker": w.GetHostname(),
- }).Tracef("updating worker %s in the database", w.GetHostname())
-
- // cast to database type
- worker := database.WorkerFromLibrary(w)
-
- // validate the necessary fields are populated
- err := worker.Validate()
- if err != nil {
- return err
- }
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableWorker).
- Save(worker).Error
-}
-
-// DeleteWorker deletes a worker by unique ID from the database.
-func (c *client) DeleteWorker(id int64) error {
- c.Logger.Tracef("deleting worker %d in the database", id)
-
- // send query to the database
- return c.Postgres.
- Table(constants.TableWorker).
- Exec(dml.DeleteWorker, id).Error
-}
diff --git a/database/postgres/worker_count.go b/database/postgres/worker_count.go
deleted file mode 100644
index d5f867988..000000000
--- a/database/postgres/worker_count.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "github.com/go-vela/server/database/postgres/dml"
- "github.com/go-vela/types/constants"
-)
-
-// GetWorkerCount gets a count of all workers from the database.
-func (c *client) GetWorkerCount() (int64, error) {
- c.Logger.Trace("getting count of workers from the database")
-
- // variable to store query results
- var w int64
-
- // send query to the database and store result in variable
- err := c.Postgres.
- Table(constants.TableWorker).
- Raw(dml.SelectWorkersCount).
- Pluck("count", &w).Error
-
- return w, err
-}
diff --git a/database/postgres/worker_count_test.go b/database/postgres/worker_count_test.go
deleted file mode 100644
index 0c099c2e2..000000000
--- a/database/postgres/worker_count_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "reflect"
- "testing"
-
- sqlmock "github.com/DATA-DOG/go-sqlmock"
-
- "github.com/go-vela/server/database/postgres/dml"
-
- "gorm.io/gorm"
-)
-
-func TestPostgres_Client_GetWorkerCount(t *testing.T) {
- // setup types
- _workerOne := testWorker()
- _workerOne.SetID(1)
- _workerOne.SetHostname("worker_0")
- _workerOne.SetAddress("localhost")
- _workerOne.SetActive(true)
-
- _workerTwo := testWorker()
- _workerTwo.SetID(2)
- _workerTwo.SetHostname("worker_1")
- _workerTwo.SetAddress("localhost")
- _workerTwo.SetActive(true)
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectWorkersCount).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
-
- // ensure the mock expects the query
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 2,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetWorkerCount()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetWorkerCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetWorkerCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetWorkerCount is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/postgres/worker_list_test.go b/database/postgres/worker_list_test.go
deleted file mode 100644
index c6250062a..000000000
--- a/database/postgres/worker_list_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "reflect"
- "testing"
-
- sqlmock "github.com/DATA-DOG/go-sqlmock"
-
- "github.com/go-vela/server/database/postgres/dml"
- "github.com/go-vela/types/library"
-
- "gorm.io/gorm"
-)
-
-func TestPostgres_Client_GetWorkerList(t *testing.T) {
- // setup types
- _workerOne := testWorker()
- _workerOne.SetID(1)
- _workerOne.SetHostname("worker_0")
- _workerOne.SetAddress("localhost")
- _workerOne.SetActive(true)
-
- _workerTwo := testWorker()
- _workerTwo.SetID(2)
- _workerTwo.SetHostname("worker_1")
- _workerTwo.SetAddress("localhost")
- _workerTwo.SetActive(true)
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.ListWorkers).Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "hostname", "address", "routes", "active", "last_checked_in", "build_limit"},
- ).AddRow(1, "worker_0", "localhost", "{}", true, 0, 0).
- AddRow(2, "worker_1", "localhost", "{}", true, 0, 0)
-
- // ensure the mock expects the query
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Worker
- }{
- {
- failure: false,
- want: []*library.Worker{_workerOne, _workerTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetWorkerList()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetWorkerList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetWorkerList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetWorkerList is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/postgres/worker_test.go b/database/postgres/worker_test.go
deleted file mode 100644
index 5167c5cc3..000000000
--- a/database/postgres/worker_test.go
+++ /dev/null
@@ -1,312 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package postgres
-
-import (
- "reflect"
- "testing"
-
- sqlmock "github.com/DATA-DOG/go-sqlmock"
- "gorm.io/gorm"
-
- "github.com/go-vela/server/database/postgres/dml"
- "github.com/go-vela/types/library"
-)
-
-func TestPostgres_Client_GetWorker(t *testing.T) {
- // setup types
- _worker := testWorker()
- _worker.SetID(1)
- _worker.SetHostname("worker_0")
- _worker.SetAddress("localhost")
- _worker.SetActive(true)
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectWorker, "worker_0").Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "hostname", "address", "routes", "active", "last_checked_in", "build_limit"},
- ).AddRow(1, "worker_0", "localhost", "{}", true, 0, 0)
-
- // ensure the mock expects the query for test case 1
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
- // ensure the mock expects the error for test case 2
- _mock.ExpectQuery(_query.SQL.String()).WillReturnError(gorm.ErrRecordNotFound)
-
- // setup tests
- tests := []struct {
- failure bool
- want *library.Worker
- }{
- {
- failure: false,
- want: _worker,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetWorker("worker_0")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetWorker should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetWorker returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetWorker is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_GetWorkerByAddress(t *testing.T) {
- // setup types
- _worker := testWorker()
- _worker.SetID(1)
- _worker.SetHostname("worker_0")
- _worker.SetAddress("localhost")
- _worker.SetActive(true)
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Raw(dml.SelectWorkerByAddress, "localhost").Statement
-
- // create expected return in mock
- _rows := sqlmock.NewRows(
- []string{"id", "hostname", "address", "routes", "active", "last_checked_in", "build_limit"},
- ).AddRow(1, "worker_0", "localhost", "{}", true, 0, 0)
-
- // ensure the mock expects the query for test case 1
- _mock.ExpectQuery(_query.SQL.String()).WillReturnRows(_rows)
- // ensure the mock expects the error for test case 2
- _mock.ExpectQuery(_query.SQL.String()).WillReturnError(gorm.ErrRecordNotFound)
-
- // setup tests
- tests := []struct {
- failure bool
- want *library.Worker
- }{
- {
- failure: false,
- want: _worker,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- got, err := _database.GetWorkerByAddress("localhost")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetWorkerByAddress should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetWorkerByAddress returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetWorkerByAddress is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestPostgres_Client_CreateWorker(t *testing.T) {
- // setup types
- _worker := testWorker()
- _worker.SetID(1)
- _worker.SetHostname("worker_0")
- _worker.SetAddress("localhost")
- _worker.SetActive(true)
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // create expected return in mock
- _rows := sqlmock.NewRows([]string{"id"}).AddRow(1)
-
- // ensure the mock expects the query
- _mock.ExpectQuery(`INSERT INTO "workers" ("hostname","address","routes","active","last_checked_in","build_limit","id") VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING "id"`).
- WithArgs("worker_0", "localhost", "{}", true, nil, nil, 1).
- WillReturnRows(_rows)
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := _database.CreateWorker(_worker)
-
- if test.failure {
- if err == nil {
- t.Errorf("CreateWorker should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("CreateWorker returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_Client_UpdateWorker(t *testing.T) {
- // setup types
- _worker := testWorker()
- _worker.SetID(1)
- _worker.SetHostname("worker_0")
- _worker.SetAddress("localhost")
- _worker.SetActive(true)
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // ensure the mock expects the query
- _mock.ExpectExec(`UPDATE "workers" SET "hostname"=$1,"address"=$2,"routes"=$3,"active"=$4,"last_checked_in"=$5,"build_limit"=$6 WHERE "id" = $7`).
- WithArgs("worker_0", "localhost", "{}", true, nil, nil, 1).
- WillReturnResult(sqlmock.NewResult(1, 1))
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := _database.UpdateWorker(_worker)
-
- if test.failure {
- if err == nil {
- t.Errorf("UpdateWorker should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("UpdateWorker returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_Client_DeleteWorker(t *testing.T) {
- // setup types
-
- // setup the test database client
- _database, _mock, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new postgres test database: %v", err)
- }
- defer func() { _sql, _ := _database.Postgres.DB(); _sql.Close() }()
-
- // capture the current expected SQL query
- //
- // https://gorm.io/docs/sql_builder.html#DryRun-Mode
- _query := _database.Postgres.Session(&gorm.Session{DryRun: true}).Exec(dml.DeleteWorker, 1).Statement
-
- // ensure the mock expects the query
- _mock.ExpectExec(_query.SQL.String()).WillReturnResult(sqlmock.NewResult(1, 1))
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := _database.DeleteWorker(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("DeleteWorker should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("DeleteWorker returned err: %v", err)
- }
- }
-}
-
-// testWorker is a test helper function to create a
-// library Worker type with all fields set to their
-// zero values.
-func testWorker() *library.Worker {
- i64 := int64(0)
- str := ""
- arr := []string{}
- b := false
-
- return &library.Worker{
- ID: &i64,
- Hostname: &str,
- Address: &str,
- Routes: &arr,
- Active: &b,
- LastCheckedIn: &i64,
- BuildLimit: &i64,
- }
-}
diff --git a/database/repo/count.go b/database/repo/count.go
new file mode 100644
index 000000000..3ef5297d1
--- /dev/null
+++ b/database/repo/count.go
@@ -0,0 +1,27 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+)
+
+// CountRepos gets the count of all repos from the database.
+func (e *engine) CountRepos(ctx context.Context) (int64, error) {
+ e.logger.Tracef("getting count of all repos from the database")
+
+ // variable to store query results
+ var r int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableRepo).
+ Count(&r).
+ Error
+
+ return r, err
+}
diff --git a/database/repo/count_org.go b/database/repo/count_org.go
new file mode 100644
index 000000000..0e44516e9
--- /dev/null
+++ b/database/repo/count_org.go
@@ -0,0 +1,32 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/sirupsen/logrus"
+)
+
+// CountReposForOrg gets the count of repos by org name from the database.
+func (e *engine) CountReposForOrg(ctx context.Context, org string, filters map[string]interface{}) (int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": org,
+ }).Tracef("getting count of repos for org %s from the database", org)
+
+ // variable to store query results
+ var r int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableRepo).
+ Where("org = ?", org).
+ Where(filters).
+ Count(&r).
+ Error
+
+ return r, err
+}
diff --git a/database/repo/count_org_test.go b/database/repo/count_org_test.go
new file mode 100644
index 000000000..f3657f3eb
--- /dev/null
+++ b/database/repo/count_org_test.go
@@ -0,0 +1,102 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestRepo_Engine_CountReposForOrg(t *testing.T) {
+ // setup types
+ _repoOne := testRepo()
+ _repoOne.SetID(1)
+ _repoOne.SetUserID(1)
+ _repoOne.SetHash("baz")
+ _repoOne.SetOrg("foo")
+ _repoOne.SetName("bar")
+ _repoOne.SetFullName("foo/bar")
+ _repoOne.SetVisibility("public")
+
+ _repoTwo := testRepo()
+ _repoTwo.SetID(2)
+ _repoTwo.SetUserID(1)
+ _repoTwo.SetHash("baz")
+ _repoTwo.SetOrg("bar")
+ _repoTwo.SetName("foo")
+ _repoTwo.SetFullName("bar/foo")
+ _repoTwo.SetVisibility("public")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(1)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT count(*) FROM "repos" WHERE org = $1`).WithArgs("foo").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateRepo(context.TODO(), _repoOne)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateRepo(context.TODO(), _repoTwo)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want int64
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: 1,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: 1,
+ },
+ }
+
+ filters := map[string]interface{}{}
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.CountReposForOrg(context.TODO(), "foo", filters)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountReposForOrg for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountReposForOrg for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountReposForOrg for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/repo/count_test.go b/database/repo/count_test.go
new file mode 100644
index 000000000..668018315
--- /dev/null
+++ b/database/repo/count_test.go
@@ -0,0 +1,100 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestRepo_Engine_CountRepos(t *testing.T) {
+ // setup types
+ _repoOne := testRepo()
+ _repoOne.SetID(1)
+ _repoOne.SetUserID(1)
+ _repoOne.SetHash("baz")
+ _repoOne.SetOrg("foo")
+ _repoOne.SetName("bar")
+ _repoOne.SetFullName("foo/bar")
+ _repoOne.SetVisibility("public")
+
+ _repoTwo := testRepo()
+ _repoTwo.SetID(2)
+ _repoTwo.SetUserID(1)
+ _repoTwo.SetHash("baz")
+ _repoTwo.SetOrg("bar")
+ _repoTwo.SetName("foo")
+ _repoTwo.SetFullName("bar/foo")
+ _repoTwo.SetVisibility("public")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT count(*) FROM "repos"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateRepo(context.TODO(), _repoOne)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateRepo(context.TODO(), _repoTwo)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want int64
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: 2,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: 2,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.CountRepos(context.TODO())
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountRepos for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountRepos for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountRepos for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/repo/count_user.go b/database/repo/count_user.go
new file mode 100644
index 000000000..bfa7f56ee
--- /dev/null
+++ b/database/repo/count_user.go
@@ -0,0 +1,33 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CountReposForUser gets the count of repos by user ID from the database.
+func (e *engine) CountReposForUser(ctx context.Context, u *library.User, filters map[string]interface{}) (int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ }).Tracef("getting count of repos for user %s from the database", u.GetName())
+
+ // variable to store query results
+ var r int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableRepo).
+ Where("user_id = ?", u.GetID()).
+ Where(filters).
+ Count(&r).
+ Error
+
+ return r, err
+}
diff --git a/database/repo/count_user_test.go b/database/repo/count_user_test.go
new file mode 100644
index 000000000..58342226e
--- /dev/null
+++ b/database/repo/count_user_test.go
@@ -0,0 +1,108 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestRepo_Engine_CountReposForUser(t *testing.T) {
+ // setup types
+ _repoOne := testRepo()
+ _repoOne.SetID(1)
+ _repoOne.SetUserID(1)
+ _repoOne.SetHash("baz")
+ _repoOne.SetOrg("foo")
+ _repoOne.SetName("bar")
+ _repoOne.SetFullName("foo/bar")
+ _repoOne.SetVisibility("public")
+
+ _repoTwo := testRepo()
+ _repoTwo.SetID(2)
+ _repoTwo.SetUserID(1)
+ _repoTwo.SetHash("baz")
+ _repoTwo.SetOrg("bar")
+ _repoTwo.SetName("foo")
+ _repoTwo.SetFullName("bar/foo")
+ _repoTwo.SetVisibility("public")
+
+ _user := new(library.User)
+ _user.SetID(1)
+ _user.SetName("foo")
+ _user.SetToken("bar")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT count(*) FROM "repos" WHERE user_id = $1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateRepo(context.TODO(), _repoOne)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateRepo(context.TODO(), _repoTwo)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want int64
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: 2,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: 2,
+ },
+ }
+
+ filters := map[string]interface{}{}
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.CountReposForUser(context.TODO(), _user, filters)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountReposForUser for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountReposForUser for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountReposForUser for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/repo/create.go b/database/repo/create.go
new file mode 100644
index 000000000..800365e55
--- /dev/null
+++ b/database/repo/create.go
@@ -0,0 +1,62 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code with update.go
+package repo
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CreateRepo creates a new repo in the database.
+func (e *engine) CreateRepo(ctx context.Context, r *library.Repo) (*library.Repo, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ }).Tracef("creating repo %s in the database", r.GetFullName())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#RepoFromLibrary
+ repo := database.RepoFromLibrary(r)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Validate
+ err := repo.Validate()
+ if err != nil {
+ return nil, err
+ }
+
+ // encrypt the fields for the repo
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Encrypt
+ err = repo.Encrypt(e.config.EncryptionKey)
+ if err != nil {
+ return nil, fmt.Errorf("unable to encrypt repo %s: %w", r.GetFullName(), err)
+ }
+
+ // send query to the database
+ err = e.client.Table(constants.TableRepo).Create(repo).Error
+ if err != nil {
+ return nil, err
+ }
+
+ // decrypt the fields for the repo
+ err = repo.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // only log to preserve backwards compatibility
+ e.logger.Errorf("unable to decrypt repo %d: %v", r.GetID(), err)
+
+ return repo.ToLibrary(), nil
+ }
+
+ return repo.ToLibrary(), nil
+}
diff --git a/database/repo/create_test.go b/database/repo/create_test.go
new file mode 100644
index 000000000..4e59b2d99
--- /dev/null
+++ b/database/repo/create_test.go
@@ -0,0 +1,85 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestRepo_Engine_CreateRepo(t *testing.T) {
+ // setup types
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetHash("baz")
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+ _repo.SetVisibility("public")
+ _repo.SetPipelineType("yaml")
+ _repo.SetPreviousName("oldName")
+ _repo.SetTopics([]string{})
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows([]string{"id"}).AddRow(1)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`INSERT INTO "repos"
+("user_id","hash","org","name","full_name","link","clone","branch","topics","build_limit","timeout","counter","visibility","private","trusted","active","allow_pull","allow_push","allow_deploy","allow_tag","allow_comment","pipeline_type","previous_name","id")
+VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24) RETURNING "id"`).
+ WithArgs(1, AnyArgument{}, "foo", "bar", "foo/bar", nil, nil, nil, AnyArgument{}, AnyArgument{}, AnyArgument{}, AnyArgument{}, "public", false, false, false, false, false, false, false, false, "yaml", "oldName", 1).
+ WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.CreateRepo(context.TODO(), _repo)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, _repo) {
+ t.Errorf("CreateRepo for %s returned %s, want %s", test.name, got, _repo)
+ }
+ })
+ }
+}
diff --git a/database/repo/delete.go b/database/repo/delete.go
new file mode 100644
index 000000000..4e0df1918
--- /dev/null
+++ b/database/repo/delete.go
@@ -0,0 +1,33 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// DeleteRepo deletes an existing repo from the database.
+func (e *engine) DeleteRepo(ctx context.Context, r *library.Repo) error {
+ e.logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ }).Tracef("deleting repo %s from the database", r.GetFullName())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#RepoFromLibrary
+ repo := database.RepoFromLibrary(r)
+
+ // send query to the database
+ return e.client.
+ Table(constants.TableRepo).
+ Delete(repo).
+ Error
+}
diff --git a/database/repo/delete_test.go b/database/repo/delete_test.go
new file mode 100644
index 000000000..f7f64e1aa
--- /dev/null
+++ b/database/repo/delete_test.go
@@ -0,0 +1,77 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestRepo_Engine_DeleteRepo(t *testing.T) {
+ // setup types
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetHash("baz")
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+ _repo.SetVisibility("public")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`DELETE FROM "repos" WHERE "repos"."id" = $1`).
+ WithArgs(1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateRepo(context.TODO(), _repo)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err = test.database.DeleteRepo(context.TODO(), _repo)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("DeleteRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("DeleteRepo for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/repo/get.go b/database/repo/get.go
new file mode 100644
index 000000000..3adbaaffd
--- /dev/null
+++ b/database/repo/get.go
@@ -0,0 +1,54 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// GetRepo gets a repo by ID from the database.
+func (e *engine) GetRepo(ctx context.Context, id int64) (*library.Repo, error) {
+ e.logger.Tracef("getting repo %d from the database", id)
+
+ // variable to store query results
+ r := new(database.Repo)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableRepo).
+ Where("id = ?", id).
+ Take(r).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // decrypt the fields for the repo
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt
+ err = r.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // TODO: remove backwards compatibility before 1.x.x release
+ //
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch unencrypted repos
+ e.logger.Errorf("unable to decrypt repo %d: %v", id, err)
+
+ // return the unencrypted repo
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary
+ return r.ToLibrary(), nil
+ }
+
+ // return the decrypted repo
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary
+ return r.ToLibrary(), nil
+}
diff --git a/database/repo/get_org.go b/database/repo/get_org.go
new file mode 100644
index 000000000..2188e0a5e
--- /dev/null
+++ b/database/repo/get_org.go
@@ -0,0 +1,59 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// GetRepoForOrg gets a repo by org and repo name from the database.
+func (e *engine) GetRepoForOrg(ctx context.Context, org, name string) (*library.Repo, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": org,
+ "repo": name,
+ }).Tracef("getting repo %s/%s from the database", org, name)
+
+ // variable to store query results
+ r := new(database.Repo)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableRepo).
+ Where("org = ?", org).
+ Where("name = ?", name).
+ Take(r).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // decrypt the fields for the repo
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt
+ err = r.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // TODO: remove backwards compatibility before 1.x.x release
+ //
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch unencrypted repos
+ e.logger.Errorf("unable to decrypt repo %s/%s: %v", org, name, err)
+
+ // return the unencrypted repo
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary
+ return r.ToLibrary(), nil
+ }
+
+ // return the decrypted repo
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary
+ return r.ToLibrary(), nil
+}
diff --git a/database/repo/get_org_test.go b/database/repo/get_org_test.go
new file mode 100644
index 000000000..1eb1b5940
--- /dev/null
+++ b/database/repo/get_org_test.go
@@ -0,0 +1,91 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestRepo_Engine_GetRepoForOrg(t *testing.T) {
+ // setup types
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetHash("baz")
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+ _repo.SetVisibility("public")
+ _repo.SetPipelineType("yaml")
+ _repo.SetTopics([]string{})
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}).
+ AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "")
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "repos" WHERE org = $1 AND name = $2 LIMIT 1`).WithArgs("foo", "bar").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateRepo(context.TODO(), _repo)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Repo
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _repo,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _repo,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetRepoForOrg(context.TODO(), "foo", "bar")
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetRepoForOrg for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetRepoForOrg for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetRepoForOrg for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/repo/get_test.go b/database/repo/get_test.go
new file mode 100644
index 000000000..02ea9dd98
--- /dev/null
+++ b/database/repo/get_test.go
@@ -0,0 +1,91 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestRepo_Engine_GetRepo(t *testing.T) {
+ // setup types
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetHash("baz")
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+ _repo.SetVisibility("public")
+ _repo.SetPipelineType("yaml")
+ _repo.SetTopics([]string{})
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "build_limit", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}).
+ AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", "")
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "repos" WHERE id = $1 LIMIT 1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateRepo(context.TODO(), _repo)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Repo
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _repo,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _repo,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetRepo(context.TODO(), 1)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/repo/index.go b/database/repo/index.go
new file mode 100644
index 000000000..8c90262a7
--- /dev/null
+++ b/database/repo/index.go
@@ -0,0 +1,26 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import "context"
+
+const (
+ // CreateOrgNameIndex represents a query to create an
+ // index on the repos table for the org and name columns.
+ CreateOrgNameIndex = `
+CREATE INDEX
+IF NOT EXISTS
+repos_org_name
+ON repos (org, name);
+`
+)
+
+// CreateRepoIndexes creates the indexes for the repos table in the database.
+func (e *engine) CreateRepoIndexes(ctx context.Context) error {
+ e.logger.Tracef("creating indexes for repos table in the database")
+
+ // create the org and name columns index for the repos table
+ return e.client.Exec(CreateOrgNameIndex).Error
+}
diff --git a/database/repo/index_test.go b/database/repo/index_test.go
new file mode 100644
index 000000000..d9a527b61
--- /dev/null
+++ b/database/repo/index_test.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestRepo_Engine_CreateRepoIndexes(t *testing.T) {
+ // setup types
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ _mock.ExpectExec(CreateOrgNameIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := test.database.CreateRepoIndexes(context.TODO())
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateRepoIndexes for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateRepoIndexes for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/repo/interface.go b/database/repo/interface.go
new file mode 100644
index 000000000..6dad94d20
--- /dev/null
+++ b/database/repo/interface.go
@@ -0,0 +1,53 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+
+ "github.com/go-vela/types/library"
+)
+
+// RepoInterface represents the Vela interface for repo
+// functions with the supported Database backends.
+//
+//nolint:revive // ignore name stutter
+type RepoInterface interface {
+ // Repo Data Definition Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_definition_language
+
+ // CreateRepoIndexes defines a function that creates the indexes for the repos table.
+ CreateRepoIndexes(context.Context) error
+ // CreateRepoTable defines a function that creates the repos table.
+ CreateRepoTable(context.Context, string) error
+
+ // Repo Data Manipulation Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_manipulation_language
+
+ // CountRepos defines a function that gets the count of all repos.
+ CountRepos(context.Context) (int64, error)
+ // CountReposForOrg defines a function that gets the count of repos by org name.
+ CountReposForOrg(context.Context, string, map[string]interface{}) (int64, error)
+ // CountReposForUser defines a function that gets the count of repos by user ID.
+ CountReposForUser(context.Context, *library.User, map[string]interface{}) (int64, error)
+ // CreateRepo defines a function that creates a new repo.
+ CreateRepo(context.Context, *library.Repo) (*library.Repo, error)
+ // DeleteRepo defines a function that deletes an existing repo.
+ DeleteRepo(context.Context, *library.Repo) error
+ // GetRepo defines a function that gets a repo by ID.
+ GetRepo(context.Context, int64) (*library.Repo, error)
+ // GetRepoForOrg defines a function that gets a repo by org and repo name.
+ GetRepoForOrg(context.Context, string, string) (*library.Repo, error)
+ // ListRepos defines a function that gets a list of all repos.
+ ListRepos(context.Context) ([]*library.Repo, error)
+ // ListReposForOrg defines a function that gets a list of repos by org name.
+ ListReposForOrg(context.Context, string, string, map[string]interface{}, int, int) ([]*library.Repo, int64, error)
+ // ListReposForUser defines a function that gets a list of repos by user ID.
+ ListReposForUser(context.Context, *library.User, string, map[string]interface{}, int, int) ([]*library.Repo, int64, error)
+ // UpdateRepo defines a function that updates an existing repo.
+ UpdateRepo(context.Context, *library.Repo) (*library.Repo, error)
+}
diff --git a/database/repo/list.go b/database/repo/list.go
new file mode 100644
index 000000000..c65754c16
--- /dev/null
+++ b/database/repo/list.go
@@ -0,0 +1,69 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// ListRepos gets a list of all repos from the database.
+func (e *engine) ListRepos(ctx context.Context) ([]*library.Repo, error) {
+ e.logger.Trace("listing all repos from the database")
+
+ // variables to store query results and return value
+ count := int64(0)
+ r := new([]database.Repo)
+ repos := []*library.Repo{}
+
+ // count the results
+ count, err := e.CountRepos(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return repos, nil
+ }
+
+ // send query to the database and store result in variable
+ err = e.client.
+ Table(constants.TableRepo).
+ Find(&r).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // iterate through all query results
+ for _, repo := range *r {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := repo
+
+ // decrypt the fields for the repo
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt
+ err = tmp.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // TODO: remove backwards compatibility before 1.x.x release
+ //
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch unencrypted repos
+ e.logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err)
+ }
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary
+ repos = append(repos, tmp.ToLibrary())
+ }
+
+ return repos, nil
+}
diff --git a/database/repo/list_org.go b/database/repo/list_org.go
new file mode 100644
index 000000000..9809716bf
--- /dev/null
+++ b/database/repo/list_org.go
@@ -0,0 +1,106 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// ListReposForOrg gets a list of repos by org name from the database.
+//
+//nolint:lll // ignore long line length due to variable names
+func (e *engine) ListReposForOrg(ctx context.Context, org, sortBy string, filters map[string]interface{}, page, perPage int) ([]*library.Repo, int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": org,
+ }).Tracef("listing repos for org %s from the database", org)
+
+ // variables to store query results and return values
+ count := int64(0)
+ r := new([]database.Repo)
+ repos := []*library.Repo{}
+
+ // count the results
+ count, err := e.CountReposForOrg(ctx, org, filters)
+ if err != nil {
+ return repos, 0, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return repos, 0, nil
+ }
+
+ // calculate offset for pagination through results
+ offset := perPage * (page - 1)
+
+ switch sortBy {
+ case "latest":
+ query := e.client.
+ Table(constants.TableBuild).
+ Select("repos.id, MAX(builds.created) AS latest_build").
+ Joins("INNER JOIN repos repos ON builds.repo_id = repos.id").
+ Where("repos.org = ?", org).
+ Group("repos.id")
+
+ err = e.client.
+ Table(constants.TableRepo).
+ Select("repos.*").
+ Joins("LEFT JOIN (?) t on repos.id = t.id", query).
+ Order("latest_build DESC NULLS LAST").
+ Limit(perPage).
+ Offset(offset).
+ Find(&r).
+ Error
+ if err != nil {
+ return nil, count, err
+ }
+ case "name":
+ fallthrough
+ default:
+ err = e.client.
+ Table(constants.TableRepo).
+ Where("org = ?", org).
+ Where(filters).
+ Order("name").
+ Limit(perPage).
+ Offset(offset).
+ Find(&r).
+ Error
+ if err != nil {
+ return nil, count, err
+ }
+ }
+
+ // iterate through all query results
+ for _, repo := range *r {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := repo
+
+ // decrypt the fields for the repo
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt
+ err = tmp.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // TODO: remove backwards compatibility before 1.x.x release
+ //
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch unencrypted repos
+ e.logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err)
+ }
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary
+ repos = append(repos, tmp.ToLibrary())
+ }
+
+ return repos, count, nil
+}
diff --git a/database/repo/list_org_test.go b/database/repo/list_org_test.go
new file mode 100644
index 000000000..c87707385
--- /dev/null
+++ b/database/repo/list_org_test.go
@@ -0,0 +1,178 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+func TestRepo_Engine_ListReposForOrg(t *testing.T) {
+ // setup types
+ _buildOne := new(library.Build)
+ _buildOne.SetID(1)
+ _buildOne.SetRepoID(1)
+ _buildOne.SetNumber(1)
+ _buildOne.SetCreated(time.Now().UTC().Unix())
+
+ _buildTwo := new(library.Build)
+ _buildTwo.SetID(2)
+ _buildTwo.SetRepoID(2)
+ _buildTwo.SetNumber(1)
+ _buildTwo.SetCreated(time.Now().UTC().Unix())
+
+ _repoOne := testRepo()
+ _repoOne.SetID(1)
+ _repoOne.SetUserID(1)
+ _repoOne.SetHash("baz")
+ _repoOne.SetOrg("foo")
+ _repoOne.SetName("bar")
+ _repoOne.SetFullName("foo/bar")
+ _repoOne.SetVisibility("public")
+ _repoOne.SetPipelineType("yaml")
+ _repoOne.SetTopics([]string{})
+
+ _repoTwo := testRepo()
+ _repoTwo.SetID(2)
+ _repoTwo.SetUserID(1)
+ _repoTwo.SetHash("bar")
+ _repoTwo.SetOrg("foo")
+ _repoTwo.SetName("baz")
+ _repoTwo.SetFullName("foo/baz")
+ _repoTwo.SetVisibility("public")
+ _repoTwo.SetPipelineType("yaml")
+ _repoTwo.SetTopics([]string{})
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected name count query result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the name count query
+ _mock.ExpectQuery(`SELECT count(*) FROM "repos" WHERE org = $1`).WithArgs("foo").WillReturnRows(_rows)
+
+ // create expected name query result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}).
+ AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil).
+ AddRow(2, 1, "bar", "foo", "baz", "foo/baz", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil)
+
+ // ensure the mock expects the name query
+ _mock.ExpectQuery(`SELECT * FROM "repos" WHERE org = $1 ORDER BY name LIMIT 10`).WithArgs("foo").WillReturnRows(_rows)
+
+ // create expected latest count query result in mock
+ _rows = sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the latest count query
+ _mock.ExpectQuery(`SELECT count(*) FROM "repos" WHERE org = $1`).WithArgs("foo").WillReturnRows(_rows)
+
+ // create expected latest query result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}).
+ AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil).
+ AddRow(2, 1, "bar", "foo", "baz", "foo/baz", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil)
+
+ // ensure the mock expects the latest query
+ _mock.ExpectQuery(`SELECT repos.* FROM "repos" LEFT JOIN (SELECT repos.id, MAX(builds.created) AS latest_build FROM "builds" INNER JOIN repos repos ON builds.repo_id = repos.id WHERE repos.org = $1 GROUP BY "repos"."id") t on repos.id = t.id ORDER BY latest_build DESC NULLS LAST LIMIT 10`).WithArgs("foo").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateRepo(context.TODO(), _repoOne)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateRepo(context.TODO(), _repoTwo)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ err = _sqlite.client.AutoMigrate(&database.Build{})
+ if err != nil {
+ t.Errorf("unable to create build table for sqlite: %v", err)
+ }
+
+ err = _sqlite.client.Table(constants.TableBuild).Create(database.BuildFromLibrary(_buildOne).Crop()).Error
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ err = _sqlite.client.Table(constants.TableBuild).Create(database.BuildFromLibrary(_buildTwo).Crop()).Error
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ sort string
+ database *engine
+ want []*library.Repo
+ }{
+ {
+ failure: false,
+ name: "postgres with name",
+ database: _postgres,
+ sort: "name",
+ want: []*library.Repo{_repoOne, _repoTwo},
+ },
+ {
+ failure: false,
+ name: "postgres with latest",
+ database: _postgres,
+ sort: "latest",
+ want: []*library.Repo{_repoOne, _repoTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite with name",
+ database: _sqlite,
+ sort: "name",
+ want: []*library.Repo{_repoOne, _repoTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite with latest",
+ database: _sqlite,
+ sort: "latest",
+ want: []*library.Repo{_repoOne, _repoTwo},
+ },
+ }
+
+ filters := map[string]interface{}{}
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, _, err := test.database.ListReposForOrg(context.TODO(), "foo", test.sort, filters, 1, 10)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListReposForOrg for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListReposForOrg for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListReposForOrg for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/repo/list_test.go b/database/repo/list_test.go
new file mode 100644
index 000000000..01b6a257f
--- /dev/null
+++ b/database/repo/list_test.go
@@ -0,0 +1,114 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestRepo_Engine_ListRepos(t *testing.T) {
+ // setup types
+ _repoOne := testRepo()
+ _repoOne.SetID(1)
+ _repoOne.SetUserID(1)
+ _repoOne.SetHash("baz")
+ _repoOne.SetOrg("foo")
+ _repoOne.SetName("bar")
+ _repoOne.SetFullName("foo/bar")
+ _repoOne.SetVisibility("public")
+ _repoOne.SetPipelineType("yaml")
+ _repoOne.SetTopics([]string{})
+
+ _repoTwo := testRepo()
+ _repoTwo.SetID(2)
+ _repoTwo.SetUserID(1)
+ _repoTwo.SetHash("baz")
+ _repoTwo.SetOrg("bar")
+ _repoTwo.SetName("foo")
+ _repoTwo.SetFullName("bar/foo")
+ _repoTwo.SetVisibility("public")
+ _repoTwo.SetPipelineType("yaml")
+ _repoTwo.SetTopics([]string{})
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT count(*) FROM "repos"`).WillReturnRows(_rows)
+
+ // create expected result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}).
+ AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil).
+ AddRow(2, 1, "baz", "bar", "foo", "bar/foo", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "repos"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateRepo(context.TODO(), _repoOne)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateRepo(context.TODO(), _repoTwo)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Repo
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Repo{_repoOne, _repoTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.Repo{_repoOne, _repoTwo},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.ListRepos(context.TODO())
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListRepos for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListRepos for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListRepos for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/repo/list_user.go b/database/repo/list_user.go
new file mode 100644
index 000000000..bf9b6ea8b
--- /dev/null
+++ b/database/repo/list_user.go
@@ -0,0 +1,106 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// ListReposForUser gets a list of repos by user ID from the database.
+//
+//nolint:lll // ignore long line length due to variable names
+func (e *engine) ListReposForUser(ctx context.Context, u *library.User, sortBy string, filters map[string]interface{}, page, perPage int) ([]*library.Repo, int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ }).Tracef("listing repos for user %s from the database", u.GetName())
+
+ // variables to store query results and return values
+ count := int64(0)
+ r := new([]database.Repo)
+ repos := []*library.Repo{}
+
+ // count the results
+ count, err := e.CountReposForUser(ctx, u, filters)
+ if err != nil {
+ return repos, 0, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return repos, 0, nil
+ }
+
+ // calculate offset for pagination through results
+ offset := perPage * (page - 1)
+
+ switch sortBy {
+ case "latest":
+ query := e.client.
+ Table(constants.TableBuild).
+ Select("repos.id, MAX(builds.created) AS latest_build").
+ Joins("INNER JOIN repos repos ON builds.repo_id = repos.id").
+ Where("repos.user_id = ?", u.GetID()).
+ Group("repos.id")
+
+ err = e.client.
+ Table(constants.TableRepo).
+ Select("repos.*").
+ Joins("LEFT JOIN (?) t on repos.id = t.id", query).
+ Order("latest_build DESC NULLS LAST").
+ Limit(perPage).
+ Offset(offset).
+ Find(&r).
+ Error
+ if err != nil {
+ return nil, count, err
+ }
+ case "name":
+ fallthrough
+ default:
+ err = e.client.
+ Table(constants.TableRepo).
+ Where("user_id = ?", u.GetID()).
+ Where(filters).
+ Order("name").
+ Limit(perPage).
+ Offset(offset).
+ Find(&r).
+ Error
+ if err != nil {
+ return nil, count, err
+ }
+ }
+
+ // iterate through all query results
+ for _, repo := range *r {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := repo
+
+ // decrypt the fields for the repo
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt
+ err = tmp.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // TODO: remove backwards compatibility before 1.x.x release
+ //
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch unencrypted repos
+ e.logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err)
+ }
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Repo.ToLibrary
+ repos = append(repos, tmp.ToLibrary())
+ }
+
+ return repos, count, nil
+}
diff --git a/database/repo/list_user_test.go b/database/repo/list_user_test.go
new file mode 100644
index 000000000..7f4fe0629
--- /dev/null
+++ b/database/repo/list_user_test.go
@@ -0,0 +1,183 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+func TestRepo_Engine_ListReposForUser(t *testing.T) {
+ // setup types
+ _buildOne := new(library.Build)
+ _buildOne.SetID(1)
+ _buildOne.SetRepoID(1)
+ _buildOne.SetNumber(1)
+ _buildOne.SetCreated(time.Now().UTC().Unix())
+
+ _buildTwo := new(library.Build)
+ _buildTwo.SetID(2)
+ _buildTwo.SetRepoID(2)
+ _buildTwo.SetNumber(1)
+ _buildTwo.SetCreated(time.Now().UTC().Unix())
+
+ _repoOne := testRepo()
+ _repoOne.SetID(1)
+ _repoOne.SetUserID(1)
+ _repoOne.SetHash("baz")
+ _repoOne.SetOrg("foo")
+ _repoOne.SetName("bar")
+ _repoOne.SetFullName("foo/bar")
+ _repoOne.SetVisibility("public")
+ _repoOne.SetPipelineType("yaml")
+ _repoOne.SetTopics([]string{})
+
+ _repoTwo := testRepo()
+ _repoTwo.SetID(2)
+ _repoTwo.SetUserID(1)
+ _repoTwo.SetHash("baz")
+ _repoTwo.SetOrg("bar")
+ _repoTwo.SetName("foo")
+ _repoTwo.SetFullName("bar/foo")
+ _repoTwo.SetVisibility("public")
+ _repoTwo.SetPipelineType("yaml")
+ _repoTwo.SetTopics([]string{})
+
+ _user := new(library.User)
+ _user.SetID(1)
+ _user.SetName("foo")
+ _user.SetToken("bar")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected name count query result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the name count query
+ _mock.ExpectQuery(`SELECT count(*) FROM "repos" WHERE user_id = $1`).WithArgs(1).WillReturnRows(_rows)
+
+ // create expected name query result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}).
+ AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil).
+ AddRow(2, 1, "baz", "bar", "foo", "bar/foo", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil)
+
+ // ensure the mock expects the name query
+ _mock.ExpectQuery(`SELECT * FROM "repos" WHERE user_id = $1 ORDER BY name LIMIT 10`).WithArgs(1).WillReturnRows(_rows)
+
+ // create expected latest count query result in mock
+ _rows = sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the latest count query
+ _mock.ExpectQuery(`SELECT count(*) FROM "repos" WHERE user_id = $1`).WithArgs(1).WillReturnRows(_rows)
+
+ // create expected latest query result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "user_id", "hash", "org", "name", "full_name", "link", "clone", "branch", "topics", "timeout", "counter", "visibility", "private", "trusted", "active", "allow_pull", "allow_push", "allow_deploy", "allow_tag", "allow_comment", "pipeline_type", "previous_name"}).
+ AddRow(1, 1, "baz", "foo", "bar", "foo/bar", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil).
+ AddRow(2, 1, "baz", "bar", "foo", "bar/foo", "", "", "", "{}", 0, 0, "public", false, false, false, false, false, false, false, false, "yaml", nil)
+
+ // ensure the mock expects the latest query
+ _mock.ExpectQuery(`SELECT repos.* FROM "repos" LEFT JOIN (SELECT repos.id, MAX(builds.created) AS latest_build FROM "builds" INNER JOIN repos repos ON builds.repo_id = repos.id WHERE repos.user_id = $1 GROUP BY "repos"."id") t on repos.id = t.id ORDER BY latest_build DESC NULLS LAST LIMIT 10`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateRepo(context.TODO(), _repoOne)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateRepo(context.TODO(), _repoTwo)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ err = _sqlite.client.AutoMigrate(&database.Build{})
+ if err != nil {
+ t.Errorf("unable to create build table for sqlite: %v", err)
+ }
+
+ err = _sqlite.client.Table(constants.TableBuild).Create(database.BuildFromLibrary(_buildOne).Crop()).Error
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ err = _sqlite.client.Table(constants.TableBuild).Create(database.BuildFromLibrary(_buildTwo).Crop()).Error
+ if err != nil {
+ t.Errorf("unable to create test build for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ sort string
+ database *engine
+ want []*library.Repo
+ }{
+ {
+ failure: false,
+ name: "postgres with name",
+ database: _postgres,
+ sort: "name",
+ want: []*library.Repo{_repoOne, _repoTwo},
+ },
+ {
+ failure: false,
+ name: "postgres with latest",
+ database: _postgres,
+ sort: "latest",
+ want: []*library.Repo{_repoOne, _repoTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite with name",
+ database: _sqlite,
+ sort: "name",
+ want: []*library.Repo{_repoOne, _repoTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite with latest",
+ database: _sqlite,
+ sort: "latest",
+ want: []*library.Repo{_repoOne, _repoTwo},
+ },
+ }
+
+ filters := map[string]interface{}{}
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, _, err := test.database.ListReposForUser(context.TODO(), _user, test.sort, filters, 1, 10)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListReposForUser for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListReposForUser for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListReposForUser for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/repo/opts.go b/database/repo/opts.go
new file mode 100644
index 000000000..41a2b9492
--- /dev/null
+++ b/database/repo/opts.go
@@ -0,0 +1,65 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+// EngineOpt represents a configuration option to initialize the database engine for Repos.
+type EngineOpt func(*engine) error
+
+// WithClient sets the gorm.io/gorm client in the database engine for Repos.
+func WithClient(client *gorm.DB) EngineOpt {
+ return func(e *engine) error {
+ // set the gorm.io/gorm client in the repo engine
+ e.client = client
+
+ return nil
+ }
+}
+
+// WithEncryptionKey sets the encryption key in the database engine for Repos.
+func WithEncryptionKey(key string) EngineOpt {
+ return func(e *engine) error {
+ // set the encryption key in the repo engine
+ e.config.EncryptionKey = key
+
+ return nil
+ }
+}
+
+// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for Repos.
+func WithLogger(logger *logrus.Entry) EngineOpt {
+ return func(e *engine) error {
+ // set the github.com/sirupsen/logrus logger in the repo engine
+ e.logger = logger
+
+ return nil
+ }
+}
+
+// WithSkipCreation sets the skip creation logic in the database engine for Repos.
+func WithSkipCreation(skipCreation bool) EngineOpt {
+ return func(e *engine) error {
+ // set to skip creating tables and indexes in the repo engine
+ e.config.SkipCreation = skipCreation
+
+ return nil
+ }
+}
+
+// WithContext sets the context in the database engine for Repos.
+func WithContext(ctx context.Context) EngineOpt {
+ return func(e *engine) error {
+ e.ctx = ctx
+
+ return nil
+ }
+}
diff --git a/database/repo/opts_test.go b/database/repo/opts_test.go
new file mode 100644
index 000000000..4f9e981ae
--- /dev/null
+++ b/database/repo/opts_test.go
@@ -0,0 +1,260 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+func TestRepo_EngineOpt_WithClient(t *testing.T) {
+ // setup types
+ e := &engine{client: new(gorm.DB)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ client *gorm.DB
+ want *gorm.DB
+ }{
+ {
+ failure: false,
+ name: "client set to new database",
+ client: new(gorm.DB),
+ want: new(gorm.DB),
+ },
+ {
+ failure: false,
+ name: "client set to nil",
+ client: nil,
+ want: nil,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithClient(test.client)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithClient for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithClient returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.client, test.want) {
+ t.Errorf("WithClient is %v, want %v", e.client, test.want)
+ }
+ })
+ }
+}
+
+func TestRepo_EngineOpt_WithEncryptionKey(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ key string
+ want string
+ }{
+ {
+ failure: false,
+ name: "encryption key set",
+ key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ want: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ },
+ {
+ failure: false,
+ name: "encryption key not set",
+ key: "",
+ want: "",
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithEncryptionKey(test.key)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithEncryptionKey for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithEncryptionKey returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.config.EncryptionKey, test.want) {
+ t.Errorf("WithEncryptionKey is %v, want %v", e.config.EncryptionKey, test.want)
+ }
+ })
+ }
+}
+
+func TestRepo_EngineOpt_WithLogger(t *testing.T) {
+ // setup types
+ e := &engine{logger: new(logrus.Entry)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ logger *logrus.Entry
+ want *logrus.Entry
+ }{
+ {
+ failure: false,
+ name: "logger set to new entry",
+ logger: new(logrus.Entry),
+ want: new(logrus.Entry),
+ },
+ {
+ failure: false,
+ name: "logger set to nil",
+ logger: nil,
+ want: nil,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithLogger(test.logger)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithLogger for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithLogger returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.logger, test.want) {
+ t.Errorf("WithLogger is %v, want %v", e.logger, test.want)
+ }
+ })
+ }
+}
+
+func TestRepo_EngineOpt_WithSkipCreation(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ skipCreation bool
+ want bool
+ }{
+ {
+ failure: false,
+ name: "skip creation set to true",
+ skipCreation: true,
+ want: true,
+ },
+ {
+ failure: false,
+ name: "skip creation set to false",
+ skipCreation: false,
+ want: false,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithSkipCreation(test.skipCreation)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithSkipCreation for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithSkipCreation returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.config.SkipCreation, test.want) {
+ t.Errorf("WithSkipCreation is %v, want %v", e.config.SkipCreation, test.want)
+ }
+ })
+ }
+}
+
+func TestRepo_EngineOpt_WithContext(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ ctx context.Context
+ want context.Context
+ }{
+ {
+ failure: false,
+ name: "context set to TODO",
+ ctx: context.TODO(),
+ want: context.TODO(),
+ },
+ {
+ failure: false,
+ name: "context set to nil",
+ ctx: nil,
+ want: nil,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithContext(test.ctx)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithContext for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithContext returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.ctx, test.want) {
+ t.Errorf("WithContext is %v, want %v", e.ctx, test.want)
+ }
+ })
+ }
+}
diff --git a/database/repo/repo.go b/database/repo/repo.go
new file mode 100644
index 000000000..a74c34956
--- /dev/null
+++ b/database/repo/repo.go
@@ -0,0 +1,85 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+ "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 RepoInterface interface.
+ config struct {
+ // specifies the encryption key to use for the Repo engine
+ EncryptionKey string
+ // specifies to skip creating tables and indexes for the Repo engine
+ SkipCreation bool
+ }
+
+ // engine represents the repo functionality that implements the RepoInterface interface.
+ engine struct {
+ // engine configuration settings used in repo functions
+ config *config
+
+ ctx context.Context
+
+ // gorm.io/gorm database client used in repo functions
+ //
+ // https://pkg.go.dev/gorm.io/gorm#DB
+ client *gorm.DB
+
+ // sirupsen/logrus logger used in repo functions
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus#Entry
+ logger *logrus.Entry
+ }
+)
+
+// New creates and returns a Vela service for integrating with repos in the database.
+//
+//nolint:revive // ignore returning unexported engine
+func New(opts ...EngineOpt) (*engine, error) {
+ // create new Repo engine
+ e := new(engine)
+
+ // create new fields
+ e.client = new(gorm.DB)
+ e.config = new(config)
+ e.logger = new(logrus.Entry)
+
+ // apply all provided configuration options
+ for _, opt := range opts {
+ err := opt(e)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // check if we should skip creating repo database objects
+ if e.config.SkipCreation {
+ e.logger.Warning("skipping creation of repos table and indexes in the database")
+
+ return e, nil
+ }
+
+ // create the repos table
+ err := e.CreateRepoTable(e.ctx, e.client.Config.Dialector.Name())
+ if err != nil {
+ return nil, fmt.Errorf("unable to create %s table: %w", constants.TableRepo, err)
+ }
+
+ // create the indexes for the repos table
+ err = e.CreateRepoIndexes(e.ctx)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create indexes for %s table: %w", constants.TableRepo, err)
+ }
+
+ return e, nil
+}
diff --git a/database/repo/repo_test.go b/database/repo/repo_test.go
new file mode 100644
index 000000000..8729a56f7
--- /dev/null
+++ b/database/repo/repo_test.go
@@ -0,0 +1,215 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "database/sql/driver"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/driver/postgres"
+ "gorm.io/driver/sqlite"
+ "gorm.io/gorm"
+)
+
+func TestRepo_New(t *testing.T) {
+ // setup types
+ logger := logrus.NewEntry(logrus.StandardLogger())
+
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+ defer _sql.Close()
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateOrgNameIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _config := &gorm.Config{SkipDefaultTransaction: true}
+
+ _postgres, err := gorm.Open(postgres.New(postgres.Config{Conn: _sql}), _config)
+ if err != nil {
+ t.Errorf("unable to create new postgres database: %v", err)
+ }
+
+ _sqlite, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), _config)
+ if err != nil {
+ t.Errorf("unable to create new sqlite database: %v", err)
+ }
+
+ defer func() { _sql, _ := _sqlite.DB(); _sql.Close() }()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ client *gorm.DB
+ key string
+ logger *logrus.Entry
+ skipCreation bool
+ want *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ client: _postgres,
+ key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ client: _postgres,
+ config: &config{EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", SkipCreation: false},
+ logger: logger,
+ },
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ client: _sqlite,
+ key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ client: _sqlite,
+ config: &config{EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", SkipCreation: false},
+ logger: logger,
+ },
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := New(
+ WithClient(test.client),
+ WithEncryptionKey(test.key),
+ WithLogger(test.logger),
+ WithSkipCreation(test.skipCreation),
+ )
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("New for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("New for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("New for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
+
+// testPostgres is a helper function to create a Postgres engine for testing.
+func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) {
+ // create the new mock sql database
+ //
+ // https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#New
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateOrgNameIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+
+ // create the new mock Postgres database client
+ //
+ // https://pkg.go.dev/gorm.io/gorm#Open
+ _postgres, err := gorm.Open(
+ postgres.New(postgres.Config{Conn: _sql}),
+ &gorm.Config{SkipDefaultTransaction: true},
+ )
+ if err != nil {
+ t.Errorf("unable to create new postgres database: %v", err)
+ }
+
+ _engine, err := New(
+ WithClient(_postgres),
+ WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"),
+ WithLogger(logrus.NewEntry(logrus.StandardLogger())),
+ WithSkipCreation(false),
+ )
+ if err != nil {
+ t.Errorf("unable to create new postgres repo engine: %v", err)
+ }
+
+ return _engine, _mock
+}
+
+// testSqlite is a helper function to create a Sqlite engine for testing.
+func testSqlite(t *testing.T) *engine {
+ _sqlite, err := gorm.Open(
+ sqlite.Open("file::memory:?cache=shared"),
+ &gorm.Config{SkipDefaultTransaction: true},
+ )
+ if err != nil {
+ t.Errorf("unable to create new sqlite database: %v", err)
+ }
+
+ _engine, err := New(
+ WithClient(_sqlite),
+ WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"),
+ WithLogger(logrus.NewEntry(logrus.StandardLogger())),
+ WithSkipCreation(false),
+ )
+ if err != nil {
+ t.Errorf("unable to create new sqlite repo engine: %v", err)
+ }
+
+ return _engine
+}
+
+// testRepo is a test helper function to create a library
+// Repo type with all fields set to their zero values.
+func testRepo() *library.Repo {
+ return &library.Repo{
+ ID: new(int64),
+ UserID: new(int64),
+ BuildLimit: new(int64),
+ Timeout: new(int64),
+ Counter: new(int),
+ PipelineType: new(string),
+ Hash: new(string),
+ Org: new(string),
+ Name: new(string),
+ FullName: new(string),
+ Link: new(string),
+ Clone: new(string),
+ Branch: new(string),
+ Visibility: new(string),
+ PreviousName: new(string),
+ Private: new(bool),
+ Trusted: new(bool),
+ Active: new(bool),
+ AllowPull: new(bool),
+ AllowPush: new(bool),
+ AllowDeploy: new(bool),
+ AllowTag: new(bool),
+ AllowComment: new(bool),
+ }
+}
+
+// This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values
+// that are otherwise not easily compared. These typically would be values generated
+// before adding or updating them in the database.
+//
+// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime
+type AnyArgument struct{}
+
+// Match satisfies sqlmock.Argument interface.
+func (a AnyArgument) Match(v driver.Value) bool {
+ return true
+}
diff --git a/database/repo/table.go b/database/repo/table.go
new file mode 100644
index 000000000..aa570dd43
--- /dev/null
+++ b/database/repo/table.go
@@ -0,0 +1,96 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+)
+
+const (
+ // CreatePostgresTable represents a query to create the Postgres repos table.
+ CreatePostgresTable = `
+CREATE TABLE
+IF NOT EXISTS
+repos (
+ id SERIAL PRIMARY KEY,
+ user_id INTEGER,
+ hash VARCHAR(500),
+ org VARCHAR(250),
+ name VARCHAR(250),
+ full_name VARCHAR(500),
+ link VARCHAR(1000),
+ clone VARCHAR(1000),
+ branch VARCHAR(250),
+ topics VARCHAR(1020),
+ build_limit INTEGER,
+ timeout INTEGER,
+ counter INTEGER,
+ visibility TEXT,
+ private BOOLEAN,
+ trusted BOOLEAN,
+ active BOOLEAN,
+ allow_pull BOOLEAN,
+ allow_push BOOLEAN,
+ allow_deploy BOOLEAN,
+ allow_tag BOOLEAN,
+ allow_comment BOOLEAN,
+ pipeline_type TEXT,
+ previous_name VARCHAR(100),
+ UNIQUE(full_name)
+);
+`
+
+ // CreateSqliteTable represents a query to create the Sqlite repos table.
+ CreateSqliteTable = `
+CREATE TABLE
+IF NOT EXISTS
+repos (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ user_id INTEGER,
+ hash TEXT,
+ org TEXT,
+ name TEXT,
+ full_name TEXT,
+ link TEXT,
+ clone TEXT,
+ branch TEXT,
+ topics TEXT,
+ build_limit INTEGER,
+ timeout INTEGER,
+ counter INTEGER,
+ visibility TEXT,
+ private BOOLEAN,
+ trusted BOOLEAN,
+ active BOOLEAN,
+ allow_pull BOOLEAN,
+ allow_push BOOLEAN,
+ allow_deploy BOOLEAN,
+ allow_tag BOOLEAN,
+ allow_comment BOOLEAN,
+ pipeline_type TEXT,
+ previous_name TEXT,
+ UNIQUE(full_name)
+);
+`
+)
+
+// CreateRepoTable creates the repos table in the database.
+func (e *engine) CreateRepoTable(ctx context.Context, driver string) error {
+ e.logger.Tracef("creating repos table in the database")
+
+ // handle the driver provided to create the table
+ switch driver {
+ case constants.DriverPostgres:
+ // create the repos table for Postgres
+ return e.client.Exec(CreatePostgresTable).Error
+ case constants.DriverSqlite:
+ fallthrough
+ default:
+ // create the repos table for Sqlite
+ return e.client.Exec(CreateSqliteTable).Error
+ }
+}
diff --git a/database/repo/table_test.go b/database/repo/table_test.go
new file mode 100644
index 000000000..021f76673
--- /dev/null
+++ b/database/repo/table_test.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestRepo_Engine_CreateRepoTable(t *testing.T) {
+ // setup types
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := test.database.CreateRepoTable(context.TODO(), test.name)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateRepoTable for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateRepoTable for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/repo/update.go b/database/repo/update.go
new file mode 100644
index 000000000..fbbce6b64
--- /dev/null
+++ b/database/repo/update.go
@@ -0,0 +1,62 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code with create.go
+package repo
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// UpdateRepo updates an existing repo in the database.
+func (e *engine) UpdateRepo(ctx context.Context, r *library.Repo) (*library.Repo, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ }).Tracef("creating repo %s in the database", r.GetFullName())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#RepoFromLibrary
+ repo := database.RepoFromLibrary(r)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Validate
+ err := repo.Validate()
+ if err != nil {
+ return nil, err
+ }
+
+ // encrypt the fields for the repo
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Encrypt
+ err = repo.Encrypt(e.config.EncryptionKey)
+ if err != nil {
+ return nil, fmt.Errorf("unable to encrypt repo %s: %w", r.GetFullName(), err)
+ }
+
+ // send query to the database
+ err = e.client.Table(constants.TableRepo).Save(repo).Error
+ if err != nil {
+ return nil, err
+ }
+
+ // decrypt the fields for the repo
+ err = repo.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // only log to preserve backwards compatibility
+ e.logger.Errorf("unable to decrypt repo %d: %v", r.GetID(), err)
+
+ return repo.ToLibrary(), nil
+ }
+
+ return repo.ToLibrary(), nil
+}
diff --git a/database/repo/update_test.go b/database/repo/update_test.go
new file mode 100644
index 000000000..459a4325a
--- /dev/null
+++ b/database/repo/update_test.go
@@ -0,0 +1,87 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package repo
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestRepo_Engine_UpdateRepo(t *testing.T) {
+ // setup types
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetHash("baz")
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+ _repo.SetVisibility("public")
+ _repo.SetPipelineType("yaml")
+ _repo.SetPreviousName("oldName")
+ _repo.SetTopics([]string{})
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`UPDATE "repos"
+SET "user_id"=$1,"hash"=$2,"org"=$3,"name"=$4,"full_name"=$5,"link"=$6,"clone"=$7,"branch"=$8,"topics"=$9,"build_limit"=$10,"timeout"=$11,"counter"=$12,"visibility"=$13,"private"=$14,"trusted"=$15,"active"=$16,"allow_pull"=$17,"allow_push"=$18,"allow_deploy"=$19,"allow_tag"=$20,"allow_comment"=$21,"pipeline_type"=$22,"previous_name"=$23
+WHERE "id" = $24`).
+ WithArgs(1, AnyArgument{}, "foo", "bar", "foo/bar", nil, nil, nil, AnyArgument{}, AnyArgument{}, AnyArgument{}, AnyArgument{}, "public", false, false, false, false, false, false, false, false, "yaml", "oldName", 1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateRepo(context.TODO(), _repo)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.UpdateRepo(context.TODO(), _repo)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("UpdateRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("UpdateRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, _repo) {
+ t.Errorf("UpdateRepo for %s returned %s, want %s", test.name, got, _repo)
+ }
+ })
+ }
+}
diff --git a/database/resource.go b/database/resource.go
new file mode 100644
index 000000000..74a4b8fe5
--- /dev/null
+++ b/database/resource.go
@@ -0,0 +1,163 @@
+// Copyright (c) 2023 Target Brands, Ine. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package database
+
+import (
+ "context"
+
+ "github.com/go-vela/server/database/build"
+ "github.com/go-vela/server/database/executable"
+ "github.com/go-vela/server/database/hook"
+ "github.com/go-vela/server/database/log"
+ "github.com/go-vela/server/database/pipeline"
+ "github.com/go-vela/server/database/repo"
+ "github.com/go-vela/server/database/schedule"
+ "github.com/go-vela/server/database/secret"
+ "github.com/go-vela/server/database/service"
+ "github.com/go-vela/server/database/step"
+ "github.com/go-vela/server/database/user"
+ "github.com/go-vela/server/database/worker"
+)
+
+// NewResources creates and returns the database agnostic engines for resources.
+func (e *engine) NewResources(ctx context.Context) error {
+ var err error
+
+ // create the database agnostic engine for builds
+ e.BuildInterface, err = build.New(
+ build.WithContext(e.ctx),
+ build.WithClient(e.client),
+ build.WithLogger(e.logger),
+ build.WithSkipCreation(e.config.SkipCreation),
+ )
+ if err != nil {
+ return err
+ }
+
+ // create the database agnostic engine for build_executables
+ e.BuildExecutableInterface, err = executable.New(
+ executable.WithContext(e.ctx),
+ executable.WithClient(e.client),
+ executable.WithLogger(e.logger),
+ executable.WithSkipCreation(e.config.SkipCreation),
+ executable.WithEncryptionKey(e.config.EncryptionKey),
+ executable.WithDriver(e.config.Driver),
+ )
+ if err != nil {
+ return err
+ }
+
+ // create the database agnostic engine for hooks
+ e.HookInterface, err = hook.New(
+ hook.WithClient(e.client),
+ hook.WithLogger(e.logger),
+ hook.WithSkipCreation(e.config.SkipCreation),
+ )
+ if err != nil {
+ return err
+ }
+
+ // create the database agnostic engine for logs
+ e.LogInterface, err = log.New(
+ log.WithClient(e.client),
+ log.WithCompressionLevel(e.config.CompressionLevel),
+ log.WithLogger(e.logger),
+ log.WithSkipCreation(e.config.SkipCreation),
+ )
+ if err != nil {
+ return err
+ }
+
+ // create the database agnostic engine for pipelines
+ e.PipelineInterface, err = pipeline.New(
+ pipeline.WithContext(e.ctx),
+ pipeline.WithClient(e.client),
+ pipeline.WithCompressionLevel(e.config.CompressionLevel),
+ pipeline.WithLogger(e.logger),
+ pipeline.WithSkipCreation(e.config.SkipCreation),
+ )
+ if err != nil {
+ return err
+ }
+
+ // create the database agnostic engine for repos
+ e.RepoInterface, err = repo.New(
+ repo.WithContext(e.ctx),
+ repo.WithClient(e.client),
+ repo.WithEncryptionKey(e.config.EncryptionKey),
+ repo.WithLogger(e.logger),
+ repo.WithSkipCreation(e.config.SkipCreation),
+ )
+ if err != nil {
+ return err
+ }
+
+ // create the database agnostic engine for schedules
+ e.ScheduleInterface, err = schedule.New(
+ schedule.WithContext(e.ctx),
+ schedule.WithClient(e.client),
+ schedule.WithLogger(e.logger),
+ schedule.WithSkipCreation(e.config.SkipCreation),
+ )
+ if err != nil {
+ return err
+ }
+
+ // create the database agnostic engine for secrets
+ //
+ // https://pkg.go.dev/github.com/go-vela/server/database/secret#New
+ e.SecretInterface, err = secret.New(
+ secret.WithClient(e.client),
+ secret.WithEncryptionKey(e.config.EncryptionKey),
+ secret.WithLogger(e.logger),
+ secret.WithSkipCreation(e.config.SkipCreation),
+ )
+ if err != nil {
+ return err
+ }
+
+ // create the database agnostic engine for services
+ e.ServiceInterface, err = service.New(
+ service.WithClient(e.client),
+ service.WithLogger(e.logger),
+ service.WithSkipCreation(e.config.SkipCreation),
+ )
+ if err != nil {
+ return err
+ }
+
+ // create the database agnostic engine for steps
+ e.StepInterface, err = step.New(
+ step.WithClient(e.client),
+ step.WithLogger(e.logger),
+ step.WithSkipCreation(e.config.SkipCreation),
+ )
+ if err != nil {
+ return err
+ }
+
+ // create the database agnostic engine for users
+ e.UserInterface, err = user.New(
+ user.WithClient(e.client),
+ user.WithEncryptionKey(e.config.EncryptionKey),
+ user.WithLogger(e.logger),
+ user.WithSkipCreation(e.config.SkipCreation),
+ )
+ if err != nil {
+ return err
+ }
+
+ // create the database agnostic engine for workers
+ e.WorkerInterface, err = worker.New(
+ worker.WithClient(e.client),
+ worker.WithLogger(e.logger),
+ worker.WithSkipCreation(e.config.SkipCreation),
+ )
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/database/resource_test.go b/database/resource_test.go
new file mode 100644
index 000000000..328f04f58
--- /dev/null
+++ b/database/resource_test.go
@@ -0,0 +1,116 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package database
+
+import (
+ "context"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/server/database/build"
+ "github.com/go-vela/server/database/executable"
+ "github.com/go-vela/server/database/hook"
+ "github.com/go-vela/server/database/log"
+ "github.com/go-vela/server/database/pipeline"
+ "github.com/go-vela/server/database/repo"
+ "github.com/go-vela/server/database/schedule"
+ "github.com/go-vela/server/database/secret"
+ "github.com/go-vela/server/database/service"
+ "github.com/go-vela/server/database/step"
+ "github.com/go-vela/server/database/user"
+ "github.com/go-vela/server/database/worker"
+)
+
+func TestDatabase_Engine_NewResources(t *testing.T) {
+ _postgres, _mock := testPostgres(t)
+ defer _postgres.Close()
+
+ // ensure the mock expects the build queries
+ _mock.ExpectExec(build.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(build.CreateCreatedIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(build.CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(build.CreateSourceIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(build.CreateStatusIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ // ensure the mock expects the build executable queries
+ _mock.ExpectExec(executable.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ // ensure the mock expects the hook queries
+ _mock.ExpectExec(hook.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(hook.CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ // ensure the mock expects the log queries
+ _mock.ExpectExec(log.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(log.CreateBuildIDIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ // ensure the mock expects the pipeline queries
+ _mock.ExpectExec(pipeline.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(pipeline.CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ // ensure the mock expects the repo queries
+ _mock.ExpectExec(repo.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(repo.CreateOrgNameIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ // ensure the mock expects the schedule queries
+ _mock.ExpectExec(schedule.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(schedule.CreateRepoIDIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+ // ensure the mock expects the secret queries
+ _mock.ExpectExec(secret.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _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 service queries
+ _mock.ExpectExec(service.CreatePostgresTable).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))
+ // ensure the mock expects the worker queries
+ _mock.ExpectExec(worker.CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(worker.CreateHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+
+ // create a test database without mocking the call
+ _unmocked, _ := testPostgres(t)
+
+ _sqlite := testSqlite(t)
+ defer _sqlite.Close()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ }{
+ {
+ name: "success with postgres",
+ failure: false,
+ database: _postgres,
+ },
+ {
+ name: "success with sqlite3",
+ failure: false,
+ database: _sqlite,
+ },
+ {
+ name: "failure without mocked call",
+ failure: true,
+ database: _unmocked,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := test.database.NewResources(context.TODO())
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("NewResources for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("NewResources for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/schedule/count.go b/database/schedule/count.go
new file mode 100644
index 000000000..7a0d8c1ee
--- /dev/null
+++ b/database/schedule/count.go
@@ -0,0 +1,26 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "github.com/go-vela/types/constants"
+)
+
+// CountSchedules gets the count of all schedules from the database.
+func (e *engine) CountSchedules(ctx context.Context) (int64, error) {
+ e.logger.Tracef("getting count of all schedules 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.TableSchedule).
+ Count(&s).
+ Error
+
+ return s, err
+}
diff --git a/database/schedule/count_active.go b/database/schedule/count_active.go
new file mode 100644
index 000000000..d65315186
--- /dev/null
+++ b/database/schedule/count_active.go
@@ -0,0 +1,27 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "github.com/go-vela/types/constants"
+)
+
+// CountActiveSchedules gets the count of all active schedules from the database.
+func (e *engine) CountActiveSchedules(ctx context.Context) (int64, error) {
+ e.logger.Tracef("getting count of all active schedules 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.TableSchedule).
+ Where("active = ?", true).
+ Count(&s).
+ Error
+
+ return s, err
+}
diff --git a/database/schedule/count_active_test.go b/database/schedule/count_active_test.go
new file mode 100644
index 000000000..dcf9d2cb0
--- /dev/null
+++ b/database/schedule/count_active_test.go
@@ -0,0 +1,105 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestSchedule_Engine_CountActiveSchedules(t *testing.T) {
+ _scheduleOne := testSchedule()
+ _scheduleOne.SetID(1)
+ _scheduleOne.SetRepoID(1)
+ _scheduleOne.SetActive(true)
+ _scheduleOne.SetName("nightly")
+ _scheduleOne.SetEntry("0 0 * * *")
+ _scheduleOne.SetCreatedAt(1)
+ _scheduleOne.SetCreatedBy("user1")
+ _scheduleOne.SetUpdatedAt(1)
+ _scheduleOne.SetUpdatedBy("user2")
+ _scheduleOne.SetBranch("main")
+
+ _scheduleTwo := testSchedule()
+ _scheduleTwo.SetID(2)
+ _scheduleTwo.SetRepoID(2)
+ _scheduleTwo.SetActive(false)
+ _scheduleTwo.SetName("hourly")
+ _scheduleTwo.SetEntry("0 * * * *")
+ _scheduleTwo.SetCreatedAt(1)
+ _scheduleTwo.SetCreatedBy("user1")
+ _scheduleTwo.SetUpdatedAt(1)
+ _scheduleTwo.SetUpdatedBy("user2")
+ _scheduleTwo.SetBranch("main")
+
+ _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 "schedules" WHERE active = $1`).WithArgs(true).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSchedule(context.TODO(), _scheduleOne)
+ if err != nil {
+ t.Errorf("unable to create test schedule for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSchedule(context.TODO(), _scheduleTwo)
+ if err != nil {
+ t.Errorf("unable to create test schedule 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,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.CountActiveSchedules(context.TODO())
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountActiveSchedules for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountActiveSchedules for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountActiveSchedules for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/schedule/count_repo.go b/database/schedule/count_repo.go
new file mode 100644
index 000000000..da806151c
--- /dev/null
+++ b/database/schedule/count_repo.go
@@ -0,0 +1,32 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CountSchedulesForRepo gets the count of schedules by repo ID from the database.
+func (e *engine) CountSchedulesForRepo(ctx context.Context, r *library.Repo) (int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ }).Tracef("getting count of schedules for repo %s from the database", r.GetFullName())
+
+ // variable to store query results
+ var s int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableSchedule).
+ Where("repo_id = ?", r.GetID()).
+ Count(&s).
+ Error
+
+ return s, err
+}
diff --git a/database/schedule/count_repo_test.go b/database/schedule/count_repo_test.go
new file mode 100644
index 000000000..35c530d7c
--- /dev/null
+++ b/database/schedule/count_repo_test.go
@@ -0,0 +1,109 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestSchedule_Engine_CountSchedulesForRepo(t *testing.T) {
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+
+ _scheduleOne := testSchedule()
+ _scheduleOne.SetID(1)
+ _scheduleOne.SetRepoID(1)
+ _scheduleOne.SetName("nightly")
+ _scheduleOne.SetEntry("0 0 * * *")
+ _scheduleOne.SetCreatedAt(1)
+ _scheduleOne.SetCreatedBy("user1")
+ _scheduleOne.SetUpdatedAt(1)
+ _scheduleOne.SetUpdatedBy("user2")
+ _scheduleOne.SetBranch("main")
+
+ _scheduleTwo := testSchedule()
+ _scheduleTwo.SetID(2)
+ _scheduleTwo.SetRepoID(2)
+ _scheduleTwo.SetName("hourly")
+ _scheduleTwo.SetEntry("0 * * * *")
+ _scheduleTwo.SetCreatedAt(1)
+ _scheduleTwo.SetCreatedBy("user1")
+ _scheduleTwo.SetUpdatedAt(1)
+ _scheduleTwo.SetUpdatedBy("user2")
+ _scheduleTwo.SetBranch("main")
+
+ _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 "schedules" WHERE repo_id = $1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSchedule(context.TODO(), _scheduleOne)
+ if err != nil {
+ t.Errorf("unable to create test schedule for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSchedule(context.TODO(), _scheduleTwo)
+ if err != nil {
+ t.Errorf("unable to create test schedule 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,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.CountSchedulesForRepo(context.TODO(), _repo)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountSchedulesForRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountSchedulesForRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountSchedulesForRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/schedule/count_test.go b/database/schedule/count_test.go
new file mode 100644
index 000000000..c98ba7224
--- /dev/null
+++ b/database/schedule/count_test.go
@@ -0,0 +1,103 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestSchedule_Engine_CountSchedules(t *testing.T) {
+ _scheduleOne := testSchedule()
+ _scheduleOne.SetID(1)
+ _scheduleOne.SetRepoID(1)
+ _scheduleOne.SetName("nightly")
+ _scheduleOne.SetEntry("0 0 * * *")
+ _scheduleOne.SetCreatedAt(1)
+ _scheduleOne.SetCreatedBy("user1")
+ _scheduleOne.SetUpdatedAt(1)
+ _scheduleOne.SetUpdatedBy("user2")
+ _scheduleOne.SetBranch("main")
+
+ _scheduleTwo := testSchedule()
+ _scheduleTwo.SetID(2)
+ _scheduleTwo.SetRepoID(2)
+ _scheduleTwo.SetName("hourly")
+ _scheduleTwo.SetEntry("0 * * * *")
+ _scheduleTwo.SetCreatedAt(1)
+ _scheduleTwo.SetCreatedBy("user1")
+ _scheduleTwo.SetUpdatedAt(1)
+ _scheduleTwo.SetUpdatedBy("user2")
+ _scheduleTwo.SetBranch("main")
+
+ _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 "schedules"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSchedule(context.TODO(), _scheduleOne)
+ if err != nil {
+ t.Errorf("unable to create test schedule for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSchedule(context.TODO(), _scheduleTwo)
+ if err != nil {
+ t.Errorf("unable to create test schedule 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.CountSchedules(context.TODO())
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountSchedules for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountSchedules for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountSchedules for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/schedule/create.go b/database/schedule/create.go
new file mode 100644
index 000000000..8a5907263
--- /dev/null
+++ b/database/schedule/create.go
@@ -0,0 +1,36 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code with update.go
+package schedule
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CreateSchedule creates a new schedule in the database.
+func (e *engine) CreateSchedule(ctx context.Context, s *library.Schedule) (*library.Schedule, error) {
+ e.logger.WithFields(logrus.Fields{
+ "schedule": s.GetName(),
+ }).Tracef("creating schedule %s in the database", s.GetName())
+
+ // cast the library type to database type
+ schedule := database.ScheduleFromLibrary(s)
+
+ // validate the necessary fields are populated
+ err := schedule.Validate()
+ if err != nil {
+ return nil, err
+ }
+
+ // send query to the database
+ result := e.client.Table(constants.TableSchedule).Create(schedule)
+
+ return schedule.ToLibrary(), result.Error
+}
diff --git a/database/schedule/create_test.go b/database/schedule/create_test.go
new file mode 100644
index 000000000..272e91a41
--- /dev/null
+++ b/database/schedule/create_test.go
@@ -0,0 +1,83 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestSchedule_Engine_CreateSchedule(t *testing.T) {
+ _schedule := testSchedule()
+ _schedule.SetID(1)
+ _schedule.SetRepoID(1)
+ _schedule.SetName("nightly")
+ _schedule.SetEntry("0 0 * * *")
+ _schedule.SetCreatedAt(1)
+ _schedule.SetCreatedBy("user1")
+ _schedule.SetUpdatedAt(1)
+ _schedule.SetUpdatedBy("user2")
+ _schedule.SetBranch("main")
+
+ _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 "schedules"
+("repo_id","active","name","entry","created_at","created_by","updated_at","updated_by","scheduled_at","branch","id")
+VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING "id"`).
+ WithArgs(1, false, "nightly", "0 0 * * *", 1, "user1", 1, "user2", nil, "main", 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) {
+ got, err := test.database.CreateSchedule(context.TODO(), _schedule)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateSchedule for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateSchedule for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, _schedule) {
+ t.Errorf("CreateSchedule for %s returned %s, want %s", test.name, got, _schedule)
+ }
+ })
+ }
+}
diff --git a/database/schedule/delete.go b/database/schedule/delete.go
new file mode 100644
index 000000000..765b31120
--- /dev/null
+++ b/database/schedule/delete.go
@@ -0,0 +1,29 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// DeleteSchedule deletes an existing schedule from the database.
+func (e *engine) DeleteSchedule(ctx context.Context, s *library.Schedule) error {
+ e.logger.WithFields(logrus.Fields{
+ "schedule": s.GetName(),
+ }).Tracef("deleting schedule %s in the database", s.GetName())
+
+ // cast the library type to database type
+ schedule := database.ScheduleFromLibrary(s)
+
+ // send query to the database
+ return e.client.
+ Table(constants.TableSchedule).
+ Delete(schedule).
+ Error
+}
diff --git a/database/schedule/delete_test.go b/database/schedule/delete_test.go
new file mode 100644
index 000000000..8dff8ad78
--- /dev/null
+++ b/database/schedule/delete_test.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 schedule
+
+import (
+ "context"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestSchedule_Engine_DeleteSchedule(t *testing.T) {
+ _schedule := testSchedule()
+ _schedule.SetID(1)
+ _schedule.SetRepoID(1)
+ _schedule.SetName("nightly")
+ _schedule.SetEntry("0 0 * * *")
+ _schedule.SetCreatedAt(1)
+ _schedule.SetCreatedBy("user1")
+ _schedule.SetUpdatedAt(1)
+ _schedule.SetUpdatedBy("user2")
+ _schedule.SetBranch("main")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`DELETE FROM "schedules" WHERE "schedules"."id" = $1`).
+ WithArgs(1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSchedule(context.TODO(), _schedule)
+ if err != nil {
+ t.Errorf("unable to create test schedule 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.DeleteSchedule(context.TODO(), _schedule)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("DeleteSchedule for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("DeleteSchedule for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/schedule/get.go b/database/schedule/get.go
new file mode 100644
index 000000000..e4540f0f0
--- /dev/null
+++ b/database/schedule/get.go
@@ -0,0 +1,32 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// GetSchedule gets a schedule by ID from the database.
+func (e *engine) GetSchedule(ctx context.Context, id int64) (*library.Schedule, error) {
+ e.logger.Tracef("getting schedule %d from the database", id)
+
+ // variable to store query results
+ s := new(database.Schedule)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableSchedule).
+ Where("id = ?", id).
+ Take(s).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ return s.ToLibrary(), nil
+}
diff --git a/database/schedule/get_repo.go b/database/schedule/get_repo.go
new file mode 100644
index 000000000..d0ddc4271
--- /dev/null
+++ b/database/schedule/get_repo.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 schedule
+
+import (
+ "context"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// GetScheduleForRepo gets a schedule by repo ID and name from the database.
+func (e *engine) GetScheduleForRepo(ctx context.Context, r *library.Repo, name string) (*library.Schedule, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ "schedule": name,
+ }).Tracef("getting schedule %s/%s from the database", r.GetFullName(), name)
+
+ // variable to store query results
+ s := new(database.Schedule)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableSchedule).
+ Where("repo_id = ?", r.GetID()).
+ Where("name = ?", name).
+ Take(s).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ return s.ToLibrary(), nil
+}
diff --git a/database/schedule/get_repo_test.go b/database/schedule/get_repo_test.go
new file mode 100644
index 000000000..998b58fc3
--- /dev/null
+++ b/database/schedule/get_repo_test.go
@@ -0,0 +1,96 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestSchedule_Engine_GetScheduleForRepo(t *testing.T) {
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+
+ _schedule := testSchedule()
+ _schedule.SetID(1)
+ _schedule.SetRepoID(1)
+ _schedule.SetName("nightly")
+ _schedule.SetEntry("0 0 * * *")
+ _schedule.SetCreatedAt(1)
+ _schedule.SetCreatedBy("user1")
+ _schedule.SetUpdatedAt(1)
+ _schedule.SetUpdatedBy("user2")
+ _schedule.SetBranch("main")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "repo_id", "active", "name", "entry", "created_at", "created_by", "updated_at", "updated_by", "scheduled_at", "branch"},
+ ).AddRow(1, 1, false, "nightly", "0 0 * * *", 1, "user1", 1, "user2", nil, "main")
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "schedules" WHERE repo_id = $1 AND name = $2 LIMIT 1`).WithArgs(1, "nightly").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSchedule(context.TODO(), _schedule)
+ if err != nil {
+ t.Errorf("unable to create test schedule for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Schedule
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _schedule,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _schedule,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetScheduleForRepo(context.TODO(), _repo, "nightly")
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetScheduleForRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetScheduleForRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetScheduleForRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/schedule/get_test.go b/database/schedule/get_test.go
new file mode 100644
index 000000000..bc4a61f75
--- /dev/null
+++ b/database/schedule/get_test.go
@@ -0,0 +1,90 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestSchedule_Engine_GetSchedule(t *testing.T) {
+ _schedule := testSchedule()
+ _schedule.SetID(1)
+ _schedule.SetRepoID(1)
+ _schedule.SetName("nightly")
+ _schedule.SetEntry("0 0 * * *")
+ _schedule.SetCreatedAt(1)
+ _schedule.SetCreatedBy("user1")
+ _schedule.SetUpdatedAt(1)
+ _schedule.SetUpdatedBy("user2")
+ _schedule.SetBranch("main")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "repo_id", "active", "name", "entry", "created_at", "created_by", "updated_at", "updated_by", "scheduled_at", "branch"},
+ ).AddRow(1, 1, false, "nightly", "0 0 * * *", 1, "user1", 1, "user2", nil, "main")
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "schedules" WHERE id = $1 LIMIT 1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSchedule(context.TODO(), _schedule)
+ if err != nil {
+ t.Errorf("unable to create test schedule for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Schedule
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _schedule,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _schedule,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetSchedule(context.TODO(), 1)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetSchedule for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetSchedule for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetSchedule for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/schedule/index.go b/database/schedule/index.go
new file mode 100644
index 000000000..6b88199fa
--- /dev/null
+++ b/database/schedule/index.go
@@ -0,0 +1,26 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import "context"
+
+const (
+ // CreateRepoIDIndex represents a query to create an
+ // index on the schedules table for the repo_id column.
+ CreateRepoIDIndex = `
+CREATE INDEX
+IF NOT EXISTS
+schedules_repo_id
+ON schedules (repo_id);
+`
+)
+
+// CreateScheduleIndexes creates the indexes for the schedules table in the database.
+func (e *engine) CreateScheduleIndexes(ctx context.Context) error {
+ e.logger.Tracef("creating indexes for schedules table in the database")
+
+ // create the repo_id column index for the schedules table
+ return e.client.Exec(CreateRepoIDIndex).Error
+}
diff --git a/database/schedule/index_test.go b/database/schedule/index_test.go
new file mode 100644
index 000000000..145fd5ac7
--- /dev/null
+++ b/database/schedule/index_test.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestSchedule_Engine_CreateScheduleIndexes(t *testing.T) {
+ // setup types
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ _mock.ExpectExec(CreateRepoIDIndex).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.CreateScheduleIndexes(context.TODO())
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateScheduleIndexes for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateScheduleIndexes for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/schedule/interface.go b/database/schedule/interface.go
new file mode 100644
index 000000000..402499ab1
--- /dev/null
+++ b/database/schedule/interface.go
@@ -0,0 +1,51 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+
+ "github.com/go-vela/types/library"
+)
+
+// ScheduleInterface represents the Vela interface for schedule
+// functions with the supported Database backends.
+//
+//nolint:revive // ignore name stutter
+type ScheduleInterface interface {
+ // Schedule Data Definition Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_definition_language
+
+ // CreateScheduleIndexes defines a function that creates the indexes for the schedules table.
+ CreateScheduleIndexes(context.Context) error
+ // CreateScheduleTable defines a function that creates the schedules table.
+ CreateScheduleTable(context.Context, string) error
+
+ // Schedule Data Manipulation Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_manipulation_language
+
+ // CountSchedules defines a function that gets the count of all schedules.
+ CountSchedules(context.Context) (int64, error)
+ // CountSchedulesForRepo defines a function that gets the count of schedules by repo ID.
+ CountSchedulesForRepo(context.Context, *library.Repo) (int64, error)
+ // CreateSchedule defines a function that creates a new schedule.
+ CreateSchedule(context.Context, *library.Schedule) (*library.Schedule, error)
+ // DeleteSchedule defines a function that deletes an existing schedule.
+ DeleteSchedule(context.Context, *library.Schedule) error
+ // GetSchedule defines a function that gets a schedule by ID.
+ GetSchedule(context.Context, int64) (*library.Schedule, error)
+ // GetScheduleForRepo defines a function that gets a schedule by repo ID and name.
+ GetScheduleForRepo(context.Context, *library.Repo, string) (*library.Schedule, error)
+ // ListActiveSchedules defines a function that gets a list of all active schedules.
+ ListActiveSchedules(context.Context) ([]*library.Schedule, error)
+ // ListSchedules defines a function that gets a list of all schedules.
+ ListSchedules(context.Context) ([]*library.Schedule, error)
+ // ListSchedulesForRepo defines a function that gets a list of schedules by repo ID.
+ ListSchedulesForRepo(context.Context, *library.Repo, int, int) ([]*library.Schedule, int64, error)
+ // UpdateSchedule defines a function that updates an existing schedule.
+ UpdateSchedule(context.Context, *library.Schedule, bool) (*library.Schedule, error)
+}
diff --git a/database/schedule/list.go b/database/schedule/list.go
new file mode 100644
index 000000000..fbba87ca2
--- /dev/null
+++ b/database/schedule/list.go
@@ -0,0 +1,53 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// ListSchedules gets a list of all schedules from the database.
+func (e *engine) ListSchedules(ctx context.Context) ([]*library.Schedule, error) {
+ e.logger.Trace("listing all schedules from the database")
+
+ // variables to store query results and return value
+ count := int64(0)
+ s := new([]database.Schedule)
+ schedules := []*library.Schedule{}
+
+ // count the results
+ count, err := e.CountSchedules(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return schedules, nil
+ }
+
+ // send query to the database and store result in variable
+ err = e.client.
+ Table(constants.TableSchedule).
+ Find(&s).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // iterate through all query results
+ for _, schedule := range *s {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := schedule
+
+ // convert query result to API type
+ schedules = append(schedules, tmp.ToLibrary())
+ }
+
+ return schedules, nil
+}
diff --git a/database/schedule/list_active.go b/database/schedule/list_active.go
new file mode 100644
index 000000000..0afd4ddc5
--- /dev/null
+++ b/database/schedule/list_active.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 schedule
+
+import (
+ "context"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// ListActiveSchedules gets a list of all active schedules from the database.
+func (e *engine) ListActiveSchedules(ctx context.Context) ([]*library.Schedule, error) {
+ e.logger.Trace("listing all active schedules from the database")
+
+ // variables to store query results and return value
+ count := int64(0)
+ s := new([]database.Schedule)
+ schedules := []*library.Schedule{}
+
+ // count the results
+ count, err := e.CountActiveSchedules(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return schedules, nil
+ }
+
+ // send query to the database and store result in variable
+ err = e.client.
+ Table(constants.TableSchedule).
+ Where("active = ?", true).
+ Find(&s).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // iterate through all query results
+ for _, schedule := range *s {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := schedule
+
+ // convert query result to API type
+ schedules = append(schedules, tmp.ToLibrary())
+ }
+
+ return schedules, nil
+}
diff --git a/database/schedule/list_active_test.go b/database/schedule/list_active_test.go
new file mode 100644
index 000000000..2bce7ed06
--- /dev/null
+++ b/database/schedule/list_active_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 schedule
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestSchedule_Engine_ListActiveSchedules(t *testing.T) {
+ _scheduleOne := testSchedule()
+ _scheduleOne.SetID(1)
+ _scheduleOne.SetRepoID(1)
+ _scheduleOne.SetActive(true)
+ _scheduleOne.SetName("nightly")
+ _scheduleOne.SetEntry("0 0 * * *")
+ _scheduleOne.SetCreatedAt(1)
+ _scheduleOne.SetCreatedBy("user1")
+ _scheduleOne.SetUpdatedAt(1)
+ _scheduleOne.SetUpdatedBy("user2")
+ _scheduleOne.SetBranch("main")
+
+ _scheduleTwo := testSchedule()
+ _scheduleTwo.SetID(2)
+ _scheduleTwo.SetRepoID(2)
+ _scheduleTwo.SetActive(false)
+ _scheduleTwo.SetName("hourly")
+ _scheduleTwo.SetEntry("0 * * * *")
+ _scheduleTwo.SetCreatedAt(1)
+ _scheduleTwo.SetCreatedBy("user1")
+ _scheduleTwo.SetUpdatedAt(1)
+ _scheduleTwo.SetUpdatedBy("user2")
+ _scheduleTwo.SetBranch("main")
+
+ _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 "schedules" WHERE active = $1`).WithArgs(true).WillReturnRows(_rows)
+
+ // create expected result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "repo_id", "active", "name", "entry", "created_at", "created_by", "updated_at", "updated_by", "scheduled_at", "branch"}).
+ AddRow(1, 1, true, "nightly", "0 0 * * *", 1, "user1", 1, "user2", nil, "main")
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "schedules" WHERE active = $1`).WithArgs(true).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSchedule(context.TODO(), _scheduleOne)
+ if err != nil {
+ t.Errorf("unable to create test schedule for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSchedule(context.TODO(), _scheduleTwo)
+ if err != nil {
+ t.Errorf("unable to create test schedule for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Schedule
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Schedule{_scheduleOne},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.Schedule{_scheduleOne},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.ListActiveSchedules(context.TODO())
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListActiveSchedules for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListActiveSchedules for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListActiveSchedules for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/schedule/list_repo.go b/database/schedule/list_repo.go
new file mode 100644
index 000000000..9d0cfed5e
--- /dev/null
+++ b/database/schedule/list_repo.go
@@ -0,0 +1,64 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// ListSchedulesForRepo gets a list of schedules by repo ID from the database.
+func (e *engine) ListSchedulesForRepo(ctx context.Context, r *library.Repo, page, perPage int) ([]*library.Schedule, int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ }).Tracef("listing schedules for repo %s from the database", r.GetFullName())
+
+ // variables to store query results and return value
+ count := int64(0)
+ s := new([]database.Schedule)
+ schedules := []*library.Schedule{}
+
+ // count the results
+ count, err := e.CountSchedulesForRepo(ctx, r)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return schedules, 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.TableSchedule).
+ Where("repo_id = ?", r.GetID()).
+ Order("id DESC").
+ Limit(perPage).
+ Offset(offset).
+ Find(&s).
+ Error
+ if err != nil {
+ return nil, count, err
+ }
+
+ // iterate through all query results
+ for _, schedule := range *s {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := schedule
+
+ // convert query result to library type
+ schedules = append(schedules, tmp.ToLibrary())
+ }
+
+ return schedules, count, nil
+}
diff --git a/database/schedule/list_repo_test.go b/database/schedule/list_repo_test.go
new file mode 100644
index 000000000..2d573448d
--- /dev/null
+++ b/database/schedule/list_repo_test.go
@@ -0,0 +1,118 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestSchedule_Engine_ListSchedulesForRepo(t *testing.T) {
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+
+ _scheduleOne := testSchedule()
+ _scheduleOne.SetID(1)
+ _scheduleOne.SetRepoID(1)
+ _scheduleOne.SetName("nightly")
+ _scheduleOne.SetEntry("0 0 * * *")
+ _scheduleOne.SetCreatedAt(1)
+ _scheduleOne.SetCreatedBy("user1")
+ _scheduleOne.SetUpdatedAt(1)
+ _scheduleOne.SetUpdatedBy("user2")
+ _scheduleOne.SetBranch("main")
+
+ _scheduleTwo := testSchedule()
+ _scheduleTwo.SetID(2)
+ _scheduleTwo.SetRepoID(2)
+ _scheduleTwo.SetName("hourly")
+ _scheduleTwo.SetEntry("0 * * * *")
+ _scheduleTwo.SetCreatedAt(1)
+ _scheduleTwo.SetCreatedBy("user1")
+ _scheduleTwo.SetUpdatedAt(1)
+ _scheduleTwo.SetUpdatedBy("user2")
+ _scheduleTwo.SetBranch("main")
+
+ _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 "schedules" WHERE repo_id = $1`).WithArgs(1).WillReturnRows(_rows)
+
+ // create expected result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "repo_id", "active", "name", "entry", "created_at", "created_by", "updated_at", "updated_by", "scheduled_at", "branch"}).
+ AddRow(1, 1, false, "nightly", "0 0 * * *", 1, "user1", 1, "user2", nil, "main")
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "schedules" WHERE repo_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.CreateSchedule(context.TODO(), _scheduleOne)
+ if err != nil {
+ t.Errorf("unable to create test schedule for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSchedule(context.TODO(), _scheduleTwo)
+ if err != nil {
+ t.Errorf("unable to create test schedule for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Schedule
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Schedule{_scheduleOne},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.Schedule{_scheduleOne},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, _, err := test.database.ListSchedulesForRepo(context.TODO(), _repo, 1, 10)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListSchedulesForRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListSchedulesForRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListSchedulesForRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/schedule/list_test.go b/database/schedule/list_test.go
new file mode 100644
index 000000000..5b4095724
--- /dev/null
+++ b/database/schedule/list_test.go
@@ -0,0 +1,113 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestSchedule_Engine_ListSchedules(t *testing.T) {
+ _scheduleOne := testSchedule()
+ _scheduleOne.SetID(1)
+ _scheduleOne.SetRepoID(1)
+ _scheduleOne.SetName("nightly")
+ _scheduleOne.SetEntry("0 0 * * *")
+ _scheduleOne.SetCreatedAt(1)
+ _scheduleOne.SetCreatedBy("user1")
+ _scheduleOne.SetUpdatedAt(1)
+ _scheduleOne.SetUpdatedBy("user2")
+ _scheduleOne.SetBranch("main")
+
+ _scheduleTwo := testSchedule()
+ _scheduleTwo.SetID(2)
+ _scheduleTwo.SetRepoID(2)
+ _scheduleTwo.SetName("hourly")
+ _scheduleTwo.SetEntry("0 * * * *")
+ _scheduleTwo.SetCreatedAt(1)
+ _scheduleTwo.SetCreatedBy("user1")
+ _scheduleTwo.SetUpdatedAt(1)
+ _scheduleTwo.SetUpdatedBy("user2")
+ _scheduleTwo.SetBranch("main")
+
+ _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 "schedules"`).WillReturnRows(_rows)
+
+ // create expected result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "repo_id", "active", "name", "entry", "created_at", "created_by", "updated_at", "updated_by", "scheduled_at", "branch"}).
+ AddRow(1, 1, false, "nightly", "0 0 * * *", 1, "user1", 1, "user2", nil, "main").
+ AddRow(2, 2, false, "hourly", "0 * * * *", 1, "user1", 1, "user2", nil, "main")
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "schedules"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSchedule(context.TODO(), _scheduleOne)
+ if err != nil {
+ t.Errorf("unable to create test schedule for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSchedule(context.TODO(), _scheduleTwo)
+ if err != nil {
+ t.Errorf("unable to create test schedule for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Schedule
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Schedule{_scheduleOne, _scheduleTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.Schedule{_scheduleOne, _scheduleTwo},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.ListSchedules(context.TODO())
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListSchedules for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListSchedules for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListSchedules for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/schedule/opts.go b/database/schedule/opts.go
new file mode 100644
index 000000000..cb2c89cfe
--- /dev/null
+++ b/database/schedule/opts.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 schedule
+
+import (
+ "context"
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+// EngineOpt represents a configuration option to initialize the database engine for Schedules.
+type EngineOpt func(*engine) error
+
+// WithClient sets the gorm.io/gorm client in the database engine for Schedules.
+func WithClient(client *gorm.DB) EngineOpt {
+ return func(e *engine) error {
+ // set the gorm.io/gorm client in the schedule engine
+ e.client = client
+
+ return nil
+ }
+}
+
+// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for Schedules.
+func WithLogger(logger *logrus.Entry) EngineOpt {
+ return func(e *engine) error {
+ // set the github.com/sirupsen/logrus logger in the schedule engine
+ e.logger = logger
+
+ return nil
+ }
+}
+
+// WithSkipCreation sets the skip creation logic in the database engine for Schedules.
+func WithSkipCreation(skipCreation bool) EngineOpt {
+ return func(e *engine) error {
+ // set to skip creating tables and indexes in the schedule engine
+ e.config.SkipCreation = skipCreation
+
+ return nil
+ }
+}
+
+// WithContext sets the context in the database engine for Schedules.
+func WithContext(ctx context.Context) EngineOpt {
+ return func(e *engine) error {
+ e.ctx = ctx
+
+ return nil
+ }
+}
diff --git a/database/schedule/opts_test.go b/database/schedule/opts_test.go
new file mode 100644
index 000000000..6159801e3
--- /dev/null
+++ b/database/schedule/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 schedule
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+func TestSchedule_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 TestSchedule_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 TestSchedule_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/schedule/schedule.go b/database/schedule/schedule.go
new file mode 100644
index 000000000..269278f52
--- /dev/null
+++ b/database/schedule/schedule.go
@@ -0,0 +1,83 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "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 ScheduleInterface interface.
+ config struct {
+ // specifies to skip creating tables and indexes for the Schedule engine
+ SkipCreation bool
+ }
+
+ // engine represents the schedule functionality that implements the ScheduleInterface interface.
+ engine struct {
+ // engine configuration settings used in schedule functions
+ config *config
+
+ ctx context.Context
+
+ // gorm.io/gorm database client used in schedule functions
+ //
+ // https://pkg.go.dev/gorm.io/gorm#DB
+ client *gorm.DB
+
+ // sirupsen/logrus logger used in schedule functions
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus#Entry
+ logger *logrus.Entry
+ }
+)
+
+// New creates and returns a Vela service for integrating with schedules in the database.
+//
+//nolint:revive // ignore returning unexported engine
+func New(opts ...EngineOpt) (*engine, error) {
+ // create new Schedule 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 schedule database objects
+ if e.config.SkipCreation {
+ e.logger.Warning("skipping creation of schedules table and indexes in the database")
+
+ return e, nil
+ }
+
+ // create the schedules table
+ err := e.CreateScheduleTable(e.ctx, e.client.Config.Dialector.Name())
+ if err != nil {
+ return nil, fmt.Errorf("unable to create %s table: %w", constants.TableSchedule, err)
+ }
+
+ // create the indexes for the schedules table
+ err = e.CreateScheduleIndexes(e.ctx)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create indexes for %s table: %w", constants.TableSchedule, err)
+ }
+
+ return e, nil
+}
diff --git a/database/schedule/schedule_test.go b/database/schedule/schedule_test.go
new file mode 100644
index 000000000..b5916b3e3
--- /dev/null
+++ b/database/schedule/schedule_test.go
@@ -0,0 +1,239 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "database/sql/driver"
+ "reflect"
+ "testing"
+ "time"
+
+ "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 TestSchedule_New(t *testing.T) {
+ // setup types
+ logger := logrus.NewEntry(logrus.StandardLogger())
+
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+ defer _sql.Close()
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateRepoIDIndex).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{
+ ctx: context.TODO(),
+ client: _postgres,
+ config: &config{SkipCreation: false},
+ logger: logger,
+ },
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ client: _sqlite,
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ ctx: context.TODO(),
+ 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(
+ WithContext(context.TODO()),
+ WithClient(test.client),
+ WithLogger(test.logger),
+ WithSkipCreation(test.skipCreation),
+ )
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("New for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("New for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("New for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
+
+// testPostgres is a helper function to create a Postgres engine for testing.
+func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) {
+ // create the new mock sql database
+ //
+ // https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#New
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateRepoIDIndex).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(
+ WithContext(context.TODO()),
+ WithClient(_postgres),
+ WithLogger(logrus.NewEntry(logrus.StandardLogger())),
+ WithSkipCreation(false),
+ )
+ if err != nil {
+ t.Errorf("unable to create new postgres schedule 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(
+ WithContext(context.TODO()),
+ WithClient(_sqlite),
+ WithLogger(logrus.NewEntry(logrus.StandardLogger())),
+ WithSkipCreation(false),
+ )
+ if err != nil {
+ t.Errorf("unable to create new sqlite schedule engine: %v", err)
+ }
+
+ return _engine
+}
+
+// testSchedule is a test helper function to create an API Schedule type with all fields set to their zero values.
+func testSchedule() *library.Schedule {
+ return &library.Schedule{
+ ID: new(int64),
+ RepoID: new(int64),
+ Active: new(bool),
+ Name: new(string),
+ Entry: new(string),
+ CreatedAt: new(int64),
+ CreatedBy: new(string),
+ UpdatedAt: new(int64),
+ UpdatedBy: new(string),
+ ScheduledAt: new(int64),
+ Branch: new(string),
+ }
+}
+
+// testRepo is a test helper function to create a library Repo type with all fields set to their zero values.
+func testRepo() *library.Repo {
+ return &library.Repo{
+ ID: new(int64),
+ UserID: new(int64),
+ BuildLimit: new(int64),
+ Timeout: new(int64),
+ Counter: new(int),
+ PipelineType: new(string),
+ Hash: new(string),
+ Org: new(string),
+ Name: new(string),
+ FullName: new(string),
+ Link: new(string),
+ Clone: new(string),
+ Branch: new(string),
+ Visibility: new(string),
+ PreviousName: new(string),
+ Private: new(bool),
+ Trusted: new(bool),
+ Active: new(bool),
+ AllowPull: new(bool),
+ AllowPush: new(bool),
+ AllowDeploy: new(bool),
+ AllowTag: new(bool),
+ AllowComment: new(bool),
+ }
+}
+
+// This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values
+// that are otherwise not easily compared. These typically would be values generated
+// before adding or updating them in the database.
+//
+// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime
+type NowTimestamp struct{}
+
+// Match satisfies sqlmock.Argument interface.
+func (t NowTimestamp) Match(v driver.Value) bool {
+ ts, ok := v.(int64)
+ if !ok {
+ return false
+ }
+ now := time.Now().Unix()
+
+ return now-ts < 10
+}
diff --git a/database/schedule/table.go b/database/schedule/table.go
new file mode 100644
index 000000000..9ebccf864
--- /dev/null
+++ b/database/schedule/table.go
@@ -0,0 +1,70 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+)
+
+const (
+ // CreatePostgresTable represents a query to create the Postgres schedules table.
+ CreatePostgresTable = `
+CREATE TABLE
+IF NOT EXISTS
+schedules (
+ id SERIAL PRIMARY KEY,
+ repo_id INTEGER,
+ active BOOLEAN,
+ name VARCHAR(100),
+ entry VARCHAR(100),
+ created_at INTEGER,
+ created_by VARCHAR(250),
+ updated_at INTEGER,
+ updated_by VARCHAR(250),
+ scheduled_at INTEGER,
+ branch VARCHAR(250),
+ UNIQUE(repo_id, name)
+);
+`
+
+ // CreateSqliteTable represents a query to create the Sqlite schedules table.
+ CreateSqliteTable = `
+CREATE TABLE
+IF NOT EXISTS
+schedules (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ repo_id INTEGER,
+ active BOOLEAN,
+ name TEXT,
+ entry TEXT,
+ created_at INTEGER,
+ created_by TEXT,
+ updated_at INTEGER,
+ updated_by TEXT,
+ scheduled_at INTEGER,
+ branch TEXT,
+ UNIQUE(repo_id, name)
+);
+`
+)
+
+// CreateScheduleTable creates the schedules table in the database.
+func (e *engine) CreateScheduleTable(ctx context.Context, driver string) error {
+ e.logger.Tracef("creating schedules table in the database")
+
+ // handle the driver provided to create the table
+ switch driver {
+ case constants.DriverPostgres:
+ // create the schedules table for Postgres
+ return e.client.Exec(CreatePostgresTable).Error
+ case constants.DriverSqlite:
+ fallthrough
+ default:
+ // create the schedules table for Sqlite
+ return e.client.Exec(CreateSqliteTable).Error
+ }
+}
diff --git a/database/schedule/table_test.go b/database/schedule/table_test.go
new file mode 100644
index 000000000..c0d785ed1
--- /dev/null
+++ b/database/schedule/table_test.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestSchedule_Engine_CreateScheduleTable(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.CreateScheduleTable(context.TODO(), test.name)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateScheduleTable for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateScheduleTable for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/schedule/update.go b/database/schedule/update.go
new file mode 100644
index 000000000..23ab855ba
--- /dev/null
+++ b/database/schedule/update.go
@@ -0,0 +1,42 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// UpdateSchedule updates an existing schedule in the database.
+func (e *engine) UpdateSchedule(ctx context.Context, s *library.Schedule, fields bool) (*library.Schedule, error) {
+ e.logger.WithFields(logrus.Fields{
+ "schedule": s.GetName(),
+ }).Tracef("updating schedule %s in the database", s.GetName())
+
+ // cast the library type to database type
+ schedule := database.ScheduleFromLibrary(s)
+
+ // validate the necessary fields are populated
+ err := schedule.Validate()
+ if err != nil {
+ return nil, err
+ }
+
+ // If "fields" is true, update entire record; otherwise, just update scheduled_at (part of processSchedule)
+ //
+ // we do this because Gorm will automatically set `updated_at` with the Save function
+ // and the `updated_at` field should reflect the last time a user updated the record, rather than the scheduler
+ if fields {
+ err = e.client.Table(constants.TableSchedule).Save(schedule).Error
+ } else {
+ err = e.client.Table(constants.TableSchedule).Model(schedule).UpdateColumn("scheduled_at", s.GetScheduledAt()).Error
+ }
+
+ return schedule.ToLibrary(), err
+}
diff --git a/database/schedule/update_test.go b/database/schedule/update_test.go
new file mode 100644
index 000000000..235ccfc43
--- /dev/null
+++ b/database/schedule/update_test.go
@@ -0,0 +1,169 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestSchedule_Engine_UpdateSchedule_Config(t *testing.T) {
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+
+ _schedule := testSchedule()
+ _schedule.SetID(1)
+ _schedule.SetRepoID(1)
+ _schedule.SetName("nightly")
+ _schedule.SetEntry("0 0 * * *")
+ _schedule.SetCreatedAt(1)
+ _schedule.SetCreatedBy("user1")
+ _schedule.SetUpdatedAt(1)
+ _schedule.SetUpdatedBy("user2")
+ _schedule.SetBranch("main")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`UPDATE "schedules"
+SET "repo_id"=$1,"active"=$2,"name"=$3,"entry"=$4,"created_at"=$5,"created_by"=$6,"updated_at"=$7,"updated_by"=$8,"scheduled_at"=$9,"branch"=$10
+WHERE "id" = $11`).
+ WithArgs(1, false, "nightly", "0 0 * * *", 1, "user1", NowTimestamp{}, "user2", nil, "main", 1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSchedule(context.TODO(), _schedule)
+ if err != nil {
+ t.Errorf("unable to create test schedule 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) {
+ got, err := test.database.UpdateSchedule(context.TODO(), _schedule, true)
+ _schedule.SetUpdatedAt(got.GetUpdatedAt())
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("UpdateSchedule for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("UpdateSchedule for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, _schedule) {
+ t.Errorf("UpdateSchedule for %s returned %s, want %s", test.name, got, _schedule)
+ }
+ })
+ }
+}
+
+func TestSchedule_Engine_UpdateSchedule_NotConfig(t *testing.T) {
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+
+ _schedule := testSchedule()
+ _schedule.SetID(1)
+ _schedule.SetRepoID(1)
+ _schedule.SetName("nightly")
+ _schedule.SetEntry("0 0 * * *")
+ _schedule.SetCreatedAt(1)
+ _schedule.SetCreatedBy("user1")
+ _schedule.SetUpdatedAt(1)
+ _schedule.SetUpdatedBy("user2")
+ _schedule.SetScheduledAt(1)
+ _schedule.SetBranch("main")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`UPDATE "schedules" SET "scheduled_at"=$1 WHERE "id" = $2`).
+ WithArgs(1, 1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSchedule(context.TODO(), _schedule)
+ if err != nil {
+ t.Errorf("unable to create test schedule 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) {
+ got, err := test.database.UpdateSchedule(context.TODO(), _schedule, false)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("UpdateSchedule for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("UpdateSchedule for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, _schedule) {
+ t.Errorf("CreateSchedule for %s returned %s, want %s", test.name, got, _schedule)
+ }
+ })
+ }
+}
diff --git a/database/secret/count.go b/database/secret/count.go
new file mode 100644
index 000000000..0926c875c
--- /dev/null
+++ b/database/secret/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 secret
+
+import (
+ "github.com/go-vela/types/constants"
+)
+
+// CountSecrets gets the count of all secrets from the database.
+func (e *engine) CountSecrets() (int64, error) {
+ e.logger.Tracef("getting count of all secrets 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.TableSecret).
+ Count(&s).
+ Error
+
+ return s, err
+}
diff --git a/database/secret/count_org.go b/database/secret/count_org.go
new file mode 100644
index 000000000..751389800
--- /dev/null
+++ b/database/secret/count_org.go
@@ -0,0 +1,32 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/sirupsen/logrus"
+)
+
+// CountSecretsForOrg gets the count of secrets by org name from the database.
+func (e *engine) CountSecretsForOrg(org string, filters map[string]interface{}) (int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": org,
+ "type": constants.SecretOrg,
+ }).Tracef("getting count of secrets for org %s from the database", org)
+
+ // variable to store query results
+ var s int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableSecret).
+ Where("type = ?", constants.SecretOrg).
+ Where("org = ?", org).
+ Where(filters).
+ Count(&s).
+ Error
+
+ return s, err
+}
diff --git a/database/secret/count_org_test.go b/database/secret/count_org_test.go
new file mode 100644
index 000000000..6ce684c5e
--- /dev/null
+++ b/database/secret/count_org_test.go
@@ -0,0 +1,109 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/constants"
+)
+
+func TestSecret_Engine_CountSecretsForOrg(t *testing.T) {
+ // setup types
+ _secretOne := testSecret()
+ _secretOne.SetID(1)
+ _secretOne.SetOrg("foo")
+ _secretOne.SetRepo("*")
+ _secretOne.SetName("baz")
+ _secretOne.SetValue("bar")
+ _secretOne.SetType("org")
+ _secretOne.SetCreatedAt(1)
+ _secretOne.SetCreatedBy("user")
+ _secretOne.SetUpdatedAt(1)
+ _secretOne.SetUpdatedBy("user2")
+
+ _secretTwo := testSecret()
+ _secretTwo.SetID(2)
+ _secretTwo.SetOrg("bar")
+ _secretTwo.SetRepo("*")
+ _secretTwo.SetName("foo")
+ _secretTwo.SetValue("baz")
+ _secretTwo.SetType("org")
+ _secretTwo.SetCreatedAt(1)
+ _secretTwo.SetCreatedBy("user")
+ _secretTwo.SetUpdatedAt(1)
+ _secretTwo.SetUpdatedBy("user2")
+
+ _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 "secrets" WHERE type = $1 AND org = $2`).
+ WithArgs(constants.SecretOrg, "foo").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSecret(_secretOne)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSecret(_secretTwo)
+ if err != nil {
+ t.Errorf("unable to create test secret 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.CountSecretsForOrg("foo", filters)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountSecretsForOrg for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountSecretsForOrg for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountSecretsForOrg for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/secret/count_repo.go b/database/secret/count_repo.go
new file mode 100644
index 000000000..58100ebc6
--- /dev/null
+++ b/database/secret/count_repo.go
@@ -0,0 +1,35 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CountSecretsForRepo gets the count of secrets by org and repo name from the database.
+func (e *engine) CountSecretsForRepo(r *library.Repo, filters map[string]interface{}) (int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ "type": constants.SecretRepo,
+ }).Tracef("getting count of secrets for repo %s from the database", r.GetFullName())
+
+ // variable to store query results
+ var s int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableSecret).
+ Where("type = ?", constants.SecretRepo).
+ Where("org = ?", r.GetOrg()).
+ Where("repo = ?", r.GetName()).
+ Where(filters).
+ Count(&s).
+ Error
+
+ return s, err
+}
diff --git a/database/secret/count_repo_test.go b/database/secret/count_repo_test.go
new file mode 100644
index 000000000..8db30f4c5
--- /dev/null
+++ b/database/secret/count_repo_test.go
@@ -0,0 +1,120 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/go-vela/types/constants"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestSecret_Engine_CountSecretsForRepo(t *testing.T) {
+ // setup types
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetHash("baz")
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+ _repo.SetVisibility("public")
+ _repo.SetPipelineType("yaml")
+
+ _secretOne := testSecret()
+ _secretOne.SetID(1)
+ _secretOne.SetOrg("foo")
+ _secretOne.SetRepo("bar")
+ _secretOne.SetName("baz")
+ _secretOne.SetValue("foob")
+ _secretOne.SetType("repo")
+ _secretOne.SetCreatedAt(1)
+ _secretOne.SetCreatedBy("user")
+ _secretOne.SetUpdatedAt(1)
+ _secretOne.SetUpdatedBy("user2")
+
+ _secretTwo := testSecret()
+ _secretTwo.SetID(2)
+ _secretTwo.SetOrg("bar")
+ _secretTwo.SetRepo("foo")
+ _secretTwo.SetName("foob")
+ _secretTwo.SetValue("baz")
+ _secretTwo.SetType("repo")
+ _secretTwo.SetCreatedAt(1)
+ _secretTwo.SetCreatedBy("user")
+ _secretTwo.SetUpdatedAt(1)
+ _secretTwo.SetUpdatedBy("user2")
+
+ _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 "secrets" WHERE type = $1 AND org = $2 AND repo = $3`).
+ WithArgs(constants.SecretRepo, "foo", "bar").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSecret(_secretOne)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSecret(_secretTwo)
+ if err != nil {
+ t.Errorf("unable to create test secret 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.CountSecretsForRepo(_repo, filters)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountSecretsForRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountSecretsForRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountSecretsForRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/secret/count_team.go b/database/secret/count_team.go
new file mode 100644
index 000000000..30c7a4255
--- /dev/null
+++ b/database/secret/count_team.go
@@ -0,0 +1,68 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "strings"
+
+ "github.com/go-vela/types/constants"
+ "github.com/sirupsen/logrus"
+)
+
+// CountSecretsForTeam gets the count of secrets by org and team name from the database.
+func (e *engine) CountSecretsForTeam(org, team string, filters map[string]interface{}) (int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": org,
+ "team": team,
+ "type": constants.SecretShared,
+ }).Tracef("getting count of secrets for team %s/%s from the database", org, team)
+
+ // variable to store query results
+ var s int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableSecret).
+ Where("type = ?", constants.SecretShared).
+ Where("org = ?", org).
+ Where("team = ?", team).
+ Where(filters).
+ Count(&s).
+ Error
+
+ return s, err
+}
+
+// CountSecretsForTeams gets the count of secrets by teams within an org from the database.
+func (e *engine) CountSecretsForTeams(org string, teams []string, filters map[string]interface{}) (int64, error) {
+ // lower case team names for not case-sensitive values from the SCM i.e. GitHub
+ //
+ // iterate through the list of teams provided
+ for index, team := range teams {
+ // ensure the team name is lower case
+ teams[index] = strings.ToLower(team)
+ }
+
+ e.logger.WithFields(logrus.Fields{
+ "org": org,
+ "teams": teams,
+ "type": constants.SecretShared,
+ }).Tracef("getting count of secrets for teams %s in org %s from the database", teams, org)
+
+ // variable to store query results
+ var s int64
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableSecret).
+ Where("type = ?", constants.SecretShared).
+ Where("org = ?", org).
+ Where("LOWER(team) IN (?)", teams).
+ Where(filters).
+ Count(&s).
+ Error
+
+ return s, err
+}
diff --git a/database/secret/count_team_test.go b/database/secret/count_team_test.go
new file mode 100644
index 000000000..50bad4ae4
--- /dev/null
+++ b/database/secret/count_team_test.go
@@ -0,0 +1,216 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/go-vela/types/constants"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestSecret_Engine_CountSecretsForTeam(t *testing.T) {
+ // setup types
+ _secretOne := testSecret()
+ _secretOne.SetID(1)
+ _secretOne.SetOrg("foo")
+ _secretOne.SetTeam("bar")
+ _secretOne.SetName("baz")
+ _secretOne.SetValue("foob")
+ _secretOne.SetType("shared")
+ _secretOne.SetCreatedAt(1)
+ _secretOne.SetCreatedBy("user")
+ _secretOne.SetUpdatedAt(1)
+ _secretOne.SetUpdatedBy("user2")
+
+ _secretTwo := testSecret()
+ _secretTwo.SetID(2)
+ _secretTwo.SetOrg("bar")
+ _secretTwo.SetTeam("foo")
+ _secretTwo.SetName("foob")
+ _secretTwo.SetValue("baz")
+ _secretTwo.SetType("shared")
+ _secretTwo.SetCreatedAt(1)
+ _secretTwo.SetCreatedBy("user")
+ _secretTwo.SetUpdatedAt(1)
+ _secretTwo.SetUpdatedBy("user2")
+
+ _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 "secrets" WHERE type = $1 AND org = $2 AND team = $3`).
+ WithArgs(constants.SecretShared, "foo", "bar").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSecret(_secretOne)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSecret(_secretTwo)
+ if err != nil {
+ t.Errorf("unable to create test secret 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.CountSecretsForTeam("foo", "bar", filters)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountSecretsForTeam for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountSecretsForTeam for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountSecretsForTeam for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
+
+func TestSecret_Engine_CountSecretsForTeams(t *testing.T) {
+ // setup types
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetHash("baz")
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+ _repo.SetVisibility("public")
+ _repo.SetPipelineType("yaml")
+
+ _secretOne := testSecret()
+ _secretOne.SetID(1)
+ _secretOne.SetOrg("foo")
+ _secretOne.SetTeam("bar")
+ _secretOne.SetName("baz")
+ _secretOne.SetValue("foob")
+ _secretOne.SetType("shared")
+ _secretOne.SetCreatedAt(1)
+ _secretOne.SetCreatedBy("user")
+ _secretOne.SetUpdatedAt(1)
+ _secretOne.SetUpdatedBy("user2")
+
+ _secretTwo := testSecret()
+ _secretTwo.SetID(2)
+ _secretTwo.SetOrg("bar")
+ _secretTwo.SetTeam("foo")
+ _secretTwo.SetName("foob")
+ _secretTwo.SetValue("baz")
+ _secretTwo.SetType("shared")
+ _secretTwo.SetCreatedAt(1)
+ _secretTwo.SetCreatedBy("user")
+ _secretTwo.SetUpdatedAt(1)
+ _secretTwo.SetUpdatedBy("user2")
+
+ _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 "secrets" WHERE type = $1 AND org = $2 AND LOWER(team) IN ($3,$4)`).
+ WithArgs(constants.SecretShared, "foo", "foo", "bar").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSecret(_secretOne)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSecret(_secretTwo)
+ if err != nil {
+ t.Errorf("unable to create test secret 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.CountSecretsForTeams("foo", []string{"foo", "bar"}, filters)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountSecretsForTeams for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountSecretsForTeams for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountSecretsForTeams for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/secret/count_test.go b/database/secret/count_test.go
new file mode 100644
index 000000000..d3b3f3c48
--- /dev/null
+++ b/database/secret/count_test.go
@@ -0,0 +1,105 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestSecret_Engine_CountSecrets(t *testing.T) {
+ // setup types
+ _secretOne := testSecret()
+ _secretOne.SetID(1)
+ _secretOne.SetOrg("foo")
+ _secretOne.SetRepo("bar")
+ _secretOne.SetName("baz")
+ _secretOne.SetValue("foob")
+ _secretOne.SetType("repo")
+ _secretOne.SetCreatedAt(1)
+ _secretOne.SetCreatedBy("user")
+ _secretOne.SetUpdatedAt(1)
+ _secretOne.SetUpdatedBy("user2")
+
+ _secretTwo := testSecret()
+ _secretTwo.SetID(2)
+ _secretTwo.SetOrg("bar")
+ _secretTwo.SetRepo("foo")
+ _secretTwo.SetName("foob")
+ _secretTwo.SetValue("baz")
+ _secretTwo.SetType("repo")
+ _secretTwo.SetCreatedAt(1)
+ _secretTwo.SetCreatedBy("user")
+ _secretTwo.SetUpdatedAt(1)
+ _secretTwo.SetUpdatedBy("user2")
+
+ _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 "secrets"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSecret(_secretOne)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSecret(_secretTwo)
+ if err != nil {
+ t.Errorf("unable to create test repo for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want int64
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: 2,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: 2,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.CountSecrets()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountSecrets for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountSecrets for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountSecrets for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/secret/create.go b/database/secret/create.go
new file mode 100644
index 000000000..734281cf1
--- /dev/null
+++ b/database/secret/create.go
@@ -0,0 +1,82 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code with update.go
+package secret
+
+import (
+ "fmt"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CreateSecret creates a new secret in the database.
+func (e *engine) CreateSecret(s *library.Secret) (*library.Secret, error) {
+ // handle the secret based off the type
+ switch s.GetType() {
+ case constants.SecretShared:
+ e.logger.WithFields(logrus.Fields{
+ "org": s.GetOrg(),
+ "team": s.GetTeam(),
+ "secret": s.GetName(),
+ "type": s.GetType(),
+ }).Tracef("creating secret %s/%s/%s/%s in the database", s.GetType(), s.GetOrg(), s.GetTeam(), s.GetName())
+ default:
+ e.logger.WithFields(logrus.Fields{
+ "org": s.GetOrg(),
+ "repo": s.GetRepo(),
+ "secret": s.GetName(),
+ "type": s.GetType(),
+ }).Tracef("creating secret %s/%s/%s/%s in the database", s.GetType(), s.GetOrg(), s.GetRepo(), s.GetName())
+ }
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#SecretFromLibrary
+ secret := database.SecretFromLibrary(s)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Validate
+ err := secret.Validate()
+ if err != nil {
+ return nil, err
+ }
+
+ // encrypt the fields for the secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Encrypt
+ err = secret.Encrypt(e.config.EncryptionKey)
+ if err != nil {
+ switch s.GetType() {
+ case constants.SecretShared:
+ return nil, fmt.Errorf("unable to encrypt secret %s/%s/%s/%s: %w", s.GetType(), s.GetOrg(), s.GetTeam(), s.GetName(), err)
+ default:
+ return nil, fmt.Errorf("unable to encrypt secret %s/%s/%s/%s: %w", s.GetType(), s.GetOrg(), s.GetRepo(), s.GetName(), err)
+ }
+ }
+
+ // create secret record
+ result := e.client.Table(constants.TableSecret).Create(secret.Nullify())
+
+ if result.Error != nil {
+ return nil, result.Error
+ }
+
+ // decrypt the fields for the secret to return
+ err = secret.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ switch s.GetType() {
+ case constants.SecretShared:
+ return nil, fmt.Errorf("unable to decrypt secret %s/%s/%s/%s: %w", s.GetType(), s.GetOrg(), s.GetTeam(), s.GetName(), err)
+ default:
+ return nil, fmt.Errorf("unable to decrypt secret %s/%s/%s/%s: %w", s.GetType(), s.GetOrg(), s.GetRepo(), s.GetName(), err)
+ }
+ }
+
+ return secret.ToLibrary(), nil
+}
diff --git a/database/secret/create_test.go b/database/secret/create_test.go
new file mode 100644
index 000000000..ec4fa38e3
--- /dev/null
+++ b/database/secret/create_test.go
@@ -0,0 +1,150 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestSecret_Engine_CreateSecret(t *testing.T) {
+ // setup types
+ _secretRepo := testSecret()
+ _secretRepo.SetID(1)
+ _secretRepo.SetOrg("foo")
+ _secretRepo.SetRepo("bar")
+ _secretRepo.SetName("baz")
+ _secretRepo.SetValue("foob")
+ _secretRepo.SetType("repo")
+ _secretRepo.SetCreatedAt(1)
+ _secretRepo.SetCreatedBy("user")
+ _secretRepo.SetUpdatedAt(1)
+ _secretRepo.SetUpdatedBy("user2")
+
+ _secretOrg := testSecret()
+ _secretOrg.SetID(2)
+ _secretOrg.SetOrg("foo")
+ _secretOrg.SetRepo("*")
+ _secretOrg.SetName("bar")
+ _secretOrg.SetValue("baz")
+ _secretOrg.SetType("org")
+ _secretOrg.SetCreatedAt(1)
+ _secretOrg.SetCreatedBy("user")
+ _secretOrg.SetUpdatedAt(1)
+ _secretOrg.SetUpdatedBy("user2")
+
+ _secretShared := testSecret()
+ _secretShared.SetID(3)
+ _secretShared.SetOrg("foo")
+ _secretShared.SetTeam("bar")
+ _secretShared.SetName("baz")
+ _secretShared.SetValue("foob")
+ _secretShared.SetType("shared")
+ _secretShared.SetCreatedAt(1)
+ _secretShared.SetCreatedBy("user")
+ _secretShared.SetUpdatedAt(1)
+ _secretShared.SetUpdatedBy("user2")
+
+ _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 repo secrets query
+ _mock.ExpectQuery(`INSERT INTO "secrets"
+("org","repo","team","name","value","type","images","events","allow_command","created_at","created_by","updated_at","updated_by","id")
+VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) RETURNING "id"`).
+ WithArgs("foo", "bar", nil, "baz", AnyArgument{}, "repo", nil, nil, false, 1, "user", 1, "user2", 1).
+ WillReturnRows(_rows)
+
+ // ensure the mock expects the org secrets query
+ _mock.ExpectQuery(`INSERT INTO "secrets"
+("org","repo","team","name","value","type","images","events","allow_command","created_at","created_by","updated_at","updated_by","id")
+VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) RETURNING "id"`).
+ WithArgs("foo", "*", nil, "bar", AnyArgument{}, "org", nil, nil, false, 1, "user", 1, "user2", 2).
+ WillReturnRows(_rows)
+
+ // ensure the mock expects the shared secrets query
+ _mock.ExpectQuery(`INSERT INTO "secrets"
+("org","repo","team","name","value","type","images","events","allow_command","created_at","created_by","updated_at","updated_by","id")
+VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) RETURNING "id"`).
+ WithArgs("foo", nil, "bar", "baz", AnyArgument{}, "shared", nil, nil, false, 1, "user", 1, "user2", 3).
+ WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ secret *library.Secret
+ }{
+ {
+ failure: false,
+ name: "postgres with repo",
+ database: _postgres,
+ secret: _secretRepo,
+ },
+ {
+ failure: false,
+ name: "postgres with org",
+ database: _postgres,
+ secret: _secretOrg,
+ },
+ {
+ failure: false,
+ name: "postgres with shared",
+ database: _postgres,
+ secret: _secretShared,
+ },
+ {
+ failure: false,
+ name: "sqlite3 with repo",
+ database: _sqlite,
+ secret: _secretRepo,
+ },
+ {
+ failure: false,
+ name: "sqlite3 with org",
+ database: _sqlite,
+ secret: _secretOrg,
+ },
+ {
+ failure: false,
+ name: "sqlite3 with shared",
+ database: _sqlite,
+ secret: _secretShared,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.CreateSecret(test.secret)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateSecret for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateSecret for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.secret) {
+ t.Errorf("CreateSecret is %s, want %s", got, test.secret)
+ }
+ })
+ }
+}
diff --git a/database/secret/delete.go b/database/secret/delete.go
new file mode 100644
index 000000000..a9207bbd9
--- /dev/null
+++ b/database/secret/delete.go
@@ -0,0 +1,46 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// DeleteSecret deletes an existing secret from the database.
+func (e *engine) DeleteSecret(s *library.Secret) error {
+ // handle the secret based off the type
+ //
+ //nolint:dupl // ignore similar code with update.go
+ switch s.GetType() {
+ case constants.SecretShared:
+ e.logger.WithFields(logrus.Fields{
+ "org": s.GetOrg(),
+ "team": s.GetTeam(),
+ "secret": s.GetName(),
+ "type": s.GetType(),
+ }).Tracef("deleting secret %s/%s/%s/%s from the database", s.GetType(), s.GetOrg(), s.GetTeam(), s.GetName())
+ default:
+ e.logger.WithFields(logrus.Fields{
+ "org": s.GetOrg(),
+ "repo": s.GetRepo(),
+ "secret": s.GetName(),
+ "type": s.GetType(),
+ }).Tracef("deleting secret %s/%s/%s/%s from the database", s.GetType(), s.GetOrg(), s.GetRepo(), s.GetName())
+ }
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#SecretFromLibrary
+ secret := database.SecretFromLibrary(s)
+
+ // send query to the database
+ return e.client.
+ Table(constants.TableSecret).
+ Delete(secret).
+ Error
+}
diff --git a/database/secret/delete_test.go b/database/secret/delete_test.go
new file mode 100644
index 000000000..46def2c55
--- /dev/null
+++ b/database/secret/delete_test.go
@@ -0,0 +1,151 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestSecret_Engine_DeleteSecret(t *testing.T) {
+ // setup types
+ _secretRepo := testSecret()
+ _secretRepo.SetID(1)
+ _secretRepo.SetOrg("foo")
+ _secretRepo.SetRepo("bar")
+ _secretRepo.SetName("baz")
+ _secretRepo.SetValue("foob")
+ _secretRepo.SetType("repo")
+ _secretRepo.SetCreatedAt(1)
+ _secretRepo.SetCreatedBy("user")
+ _secretRepo.SetUpdatedAt(1)
+ _secretRepo.SetUpdatedBy("user2")
+
+ _secretOrg := testSecret()
+ _secretOrg.SetID(2)
+ _secretOrg.SetOrg("foo")
+ _secretOrg.SetRepo("*")
+ _secretOrg.SetName("bar")
+ _secretOrg.SetValue("baz")
+ _secretOrg.SetType("org")
+ _secretOrg.SetCreatedAt(1)
+ _secretOrg.SetCreatedBy("user")
+ _secretOrg.SetUpdatedAt(1)
+ _secretOrg.SetUpdatedBy("user2")
+
+ _secretShared := testSecret()
+ _secretShared.SetID(3)
+ _secretShared.SetOrg("foo")
+ _secretShared.SetTeam("bar")
+ _secretShared.SetName("baz")
+ _secretShared.SetValue("foob")
+ _secretShared.SetType("shared")
+ _secretShared.SetCreatedAt(1)
+ _secretShared.SetCreatedBy("user")
+ _secretShared.SetUpdatedAt(1)
+ _secretShared.SetUpdatedBy("user2")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the repo query
+ _mock.ExpectExec(`DELETE FROM "secrets" WHERE "secrets"."id" = $1`).
+ WithArgs(1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ // ensure the mock expects the org query
+ _mock.ExpectExec(`DELETE FROM "secrets" WHERE "secrets"."id" = $1`).
+ WithArgs(2).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ // ensure the mock expects the shared query
+ _mock.ExpectExec(`DELETE FROM "secrets" WHERE "secrets"."id" = $1`).
+ WithArgs(3).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSecret(_secretRepo)
+ if err != nil {
+ t.Errorf("unable to create test repo secret for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSecret(_secretOrg)
+ if err != nil {
+ t.Errorf("unable to create test org secret for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSecret(_secretShared)
+ if err != nil {
+ t.Errorf("unable to create test shared secret for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ secret *library.Secret
+ }{
+ {
+ failure: false,
+ name: "postgres with repo",
+ database: _postgres,
+ secret: _secretRepo,
+ },
+ {
+ failure: false,
+ name: "postgres with org",
+ database: _postgres,
+ secret: _secretOrg,
+ },
+ {
+ failure: false,
+ name: "postgres with shared",
+ database: _postgres,
+ secret: _secretShared,
+ },
+ {
+ failure: false,
+ name: "sqlite3 with repo",
+ database: _sqlite,
+ secret: _secretRepo,
+ },
+ {
+ failure: false,
+ name: "sqlite3 with org",
+ database: _sqlite,
+ secret: _secretOrg,
+ },
+ {
+ failure: false,
+ name: "sqlite3 with shared",
+ database: _sqlite,
+ secret: _secretShared,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err = test.database.DeleteSecret(test.secret)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("DeleteSecret for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("DeleteSecret for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/secret/get.go b/database/secret/get.go
new file mode 100644
index 000000000..67579c000
--- /dev/null
+++ b/database/secret/get.go
@@ -0,0 +1,52 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// GetSecret gets a secret by ID from the database.
+func (e *engine) GetSecret(id int64) (*library.Secret, error) {
+ e.logger.Tracef("getting secret %d from the database", id)
+
+ // variable to store query results
+ s := new(database.Secret)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableSecret).
+ Where("id = ?", id).
+ Take(s).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // decrypt the fields for the secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Decrypt
+ err = s.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // TODO: remove backwards compatibility before 1.x.x release
+ //
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch unencrypted secrets
+ e.logger.Errorf("unable to decrypt secret %d: %v", id, err)
+
+ // return the unencrypted secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.ToLibrary
+ return s.ToLibrary(), nil
+ }
+
+ // return the decrypted secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.ToLibrary
+ return s.ToLibrary(), nil
+}
diff --git a/database/secret/get_org.go b/database/secret/get_org.go
new file mode 100644
index 000000000..f7f3e9adc
--- /dev/null
+++ b/database/secret/get_org.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 secret
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// GetSecretForOrg gets a secret by org name from the database.
+func (e *engine) GetSecretForOrg(org, name string) (*library.Secret, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": org,
+ "secret": name,
+ "type": constants.SecretOrg,
+ }).Tracef("getting org secret %s/%s from the database", org, name)
+
+ // variable to store query results
+ s := new(database.Secret)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableSecret).
+ Where("type = ?", constants.SecretOrg).
+ Where("org = ?", org).
+ Where("name = ?", name).
+ Take(s).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // decrypt the fields for the secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Decrypt
+ err = s.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // TODO: remove backwards compatibility before 1.x.x release
+ //
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch unencrypted secrets
+ e.logger.Errorf("unable to decrypt org secret %s/%s: %v", org, name, err)
+
+ // return the unencrypted secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.ToLibrary
+ return s.ToLibrary(), nil
+ }
+
+ // return the decrypted secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.ToLibrary
+ return s.ToLibrary(), nil
+}
diff --git a/database/secret/get_org_test.go b/database/secret/get_org_test.go
new file mode 100644
index 000000000..7747838b8
--- /dev/null
+++ b/database/secret/get_org_test.go
@@ -0,0 +1,93 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+)
+
+func TestSecret_Engine_GetSecretForOrg(t *testing.T) {
+ // setup types
+ _secret := testSecret()
+ _secret.SetID(1)
+ _secret.SetOrg("foo")
+ _secret.SetRepo("*")
+ _secret.SetName("baz")
+ _secret.SetValue("bar")
+ _secret.SetType("org")
+ _secret.SetCreatedAt(1)
+ _secret.SetCreatedBy("user")
+ _secret.SetUpdatedAt(1)
+ _secret.SetUpdatedBy("user2")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}).
+ AddRow(1, "org", "foo", "*", "", "baz", "bar", nil, nil, false, 1, "user", 1, "user2")
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "secrets" WHERE type = $1 AND org = $2 AND name = $3 LIMIT 1`).
+ WithArgs(constants.SecretOrg, "foo", "baz").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSecret(_secret)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Secret
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _secret,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _secret,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetSecretForOrg("foo", "baz")
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetSecretForOrg for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetSecretForOrg for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetSecretForOrg for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/secret/get_repo.go b/database/secret/get_repo.go
new file mode 100644
index 000000000..6d7ca9dd9
--- /dev/null
+++ b/database/secret/get_repo.go
@@ -0,0 +1,61 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// GetSecretForRepo gets a secret by org and repo name from the database.
+func (e *engine) GetSecretForRepo(name string, r *library.Repo) (*library.Secret, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ "secret": name,
+ "type": constants.SecretRepo,
+ }).Tracef("getting repo secret %s/%s from the database", r.GetFullName(), name)
+
+ // variable to store query results
+ s := new(database.Secret)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableSecret).
+ Where("type = ?", constants.SecretRepo).
+ Where("org = ?", r.GetOrg()).
+ Where("repo = ?", r.GetName()).
+ Where("name = ?", name).
+ Take(s).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // decrypt the fields for the secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Decrypt
+ err = s.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // TODO: remove backwards compatibility before 1.x.x release
+ //
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch unencrypted secrets
+ e.logger.Errorf("unable to decrypt repo secret %s/%s: %v", r.GetFullName(), name, err)
+
+ // return the unencrypted secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.ToLibrary
+ return s.ToLibrary(), nil
+ }
+
+ // return the decrypted secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.ToLibrary
+ return s.ToLibrary(), nil
+}
diff --git a/database/secret/get_repo_test.go b/database/secret/get_repo_test.go
new file mode 100644
index 000000000..05d872373
--- /dev/null
+++ b/database/secret/get_repo_test.go
@@ -0,0 +1,103 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+)
+
+func TestSecret_Engine_GetSecretForRepo(t *testing.T) {
+ // setup types
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetHash("baz")
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+ _repo.SetVisibility("public")
+ _repo.SetPipelineType("yaml")
+
+ _secret := testSecret()
+ _secret.SetID(1)
+ _secret.SetOrg("foo")
+ _secret.SetRepo("bar")
+ _secret.SetName("baz")
+ _secret.SetValue("foob")
+ _secret.SetType("repo")
+ _secret.SetCreatedAt(1)
+ _secret.SetCreatedBy("user")
+ _secret.SetUpdatedAt(1)
+ _secret.SetUpdatedBy("user2")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}).
+ AddRow(1, "repo", "foo", "bar", "", "baz", "foob", nil, nil, false, 1, "user", 1, "user2")
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "secrets" WHERE type = $1 AND org = $2 AND repo = $3 AND name = $4 LIMIT 1`).
+ WithArgs(constants.SecretRepo, "foo", "bar", "baz").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSecret(_secret)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Secret
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _secret,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _secret,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetSecretForRepo("baz", _repo)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetSecretForRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetSecretForRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetSecretForRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/secret/get_team.go b/database/secret/get_team.go
new file mode 100644
index 000000000..306923ee1
--- /dev/null
+++ b/database/secret/get_team.go
@@ -0,0 +1,61 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// GetSecretForTeam gets a secret by org and team name from the database.
+func (e *engine) GetSecretForTeam(org, team, name string) (*library.Secret, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": org,
+ "team": team,
+ "secret": name,
+ "type": constants.SecretShared,
+ }).Tracef("getting shared secret %s/%s/%s from the database", org, team, name)
+
+ // variable to store query results
+ s := new(database.Secret)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableSecret).
+ Where("type = ?", constants.SecretShared).
+ Where("org = ?", org).
+ Where("team = ?", team).
+ Where("name = ?", name).
+ Take(s).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // decrypt the fields for the secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Decrypt
+ err = s.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // TODO: remove backwards compatibility before 1.x.x release
+ //
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch unencrypted secrets
+ e.logger.Errorf("unable to decrypt shared secret %s/%s/%s: %v", org, team, name, err)
+
+ // return the unencrypted secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.ToLibrary
+ return s.ToLibrary(), nil
+ }
+
+ // return the decrypted secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.ToLibrary
+ return s.ToLibrary(), nil
+}
diff --git a/database/secret/get_team_test.go b/database/secret/get_team_test.go
new file mode 100644
index 000000000..217415189
--- /dev/null
+++ b/database/secret/get_team_test.go
@@ -0,0 +1,93 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+)
+
+func TestSecret_Engine_GetSecretForTeam(t *testing.T) {
+ // setup types
+ _secret := testSecret()
+ _secret.SetID(1)
+ _secret.SetOrg("foo")
+ _secret.SetTeam("bar")
+ _secret.SetName("baz")
+ _secret.SetValue("foob")
+ _secret.SetType("shared")
+ _secret.SetCreatedAt(1)
+ _secret.SetCreatedBy("user")
+ _secret.SetUpdatedAt(1)
+ _secret.SetUpdatedBy("user2")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}).
+ AddRow(1, "shared", "foo", "", "bar", "baz", "foob", nil, nil, false, 1, "user", 1, "user2")
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "secrets" WHERE type = $1 AND org = $2 AND team = $3 AND name = $4 LIMIT 1`).
+ WithArgs(constants.SecretShared, "foo", "bar", "baz").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSecret(_secret)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Secret
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _secret,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _secret,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetSecretForTeam("foo", "bar", "baz")
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetSecretForTeam for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetSecretForTeam for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetSecretForTeam for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/secret/get_test.go b/database/secret/get_test.go
new file mode 100644
index 000000000..e19e89291
--- /dev/null
+++ b/database/secret/get_test.go
@@ -0,0 +1,91 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestSecret_Engine_GetSecret(t *testing.T) {
+ // setup types
+ _secret := testSecret()
+ _secret.SetID(1)
+ _secret.SetOrg("foo")
+ _secret.SetRepo("bar")
+ _secret.SetName("baz")
+ _secret.SetValue("foob")
+ _secret.SetType("repo")
+ _secret.SetCreatedAt(1)
+ _secret.SetCreatedBy("user")
+ _secret.SetUpdatedAt(1)
+ _secret.SetUpdatedBy("user2")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}).
+ AddRow(1, "repo", "foo", "bar", "", "baz", "foob", nil, nil, false, 1, "user", 1, "user2")
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "secrets" WHERE id = $1 LIMIT 1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSecret(_secret)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Secret
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _secret,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _secret,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetSecret(1)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetSecret for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetSecret for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetSecret for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/secret/index.go b/database/secret/index.go
new file mode 100644
index 000000000..7b6a2047a
--- /dev/null
+++ b/database/secret/index.go
@@ -0,0 +1,52 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+const (
+ // CreateTypeOrgRepo represents a query to create an
+ // index on the secrets table for the type, org and repo columns.
+ CreateTypeOrgRepo = `
+CREATE INDEX
+IF NOT EXISTS
+secrets_type_org_repo
+ON secrets (type, org, repo);
+`
+ // CreateTypeOrgTeam represents a query to create an
+ // index on the secrets table for the type, org and team columns.
+ CreateTypeOrgTeam = `
+CREATE INDEX
+IF NOT EXISTS
+secrets_type_org_team
+ON secrets (type, org, team);
+`
+ // CreateTypeOrg represents a query to create an
+ // index on the secrets table for the type, and org columns.
+ CreateTypeOrg = `
+CREATE INDEX
+IF NOT EXISTS
+secrets_type_org
+ON secrets (type, org);
+`
+)
+
+// CreateSecretIndexes creates the indexes for the secrets table in the database.
+func (e *engine) CreateSecretIndexes() error {
+ e.logger.Tracef("creating indexes for secrets table in the database")
+
+ // create the type, org and repo columns index for the secrets table
+ err := e.client.Exec(CreateTypeOrgRepo).Error
+ if err != nil {
+ return err
+ }
+
+ // create the type, org and team columns index for the secrets table
+ err = e.client.Exec(CreateTypeOrgTeam).Error
+ if err != nil {
+ return err
+ }
+
+ // create the type and org columns index for the secrets table
+ return e.client.Exec(CreateTypeOrg).Error
+}
diff --git a/database/secret/index_test.go b/database/secret/index_test.go
new file mode 100644
index 000000000..67a64d09d
--- /dev/null
+++ b/database/secret/index_test.go
@@ -0,0 +1,61 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestSecret_Engine_CreateSecretIndexes(t *testing.T) {
+ // setup types
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ _mock.ExpectExec(CreateTypeOrgRepo).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateTypeOrgTeam).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateTypeOrg).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.CreateSecretIndexes()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateSecretIndexes for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateSecretIndexes for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/secret/interface.go b/database/secret/interface.go
new file mode 100644
index 000000000..dfb134d9b
--- /dev/null
+++ b/database/secret/interface.go
@@ -0,0 +1,63 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "github.com/go-vela/types/library"
+)
+
+// SecretInterface represents the Vela interface for secret
+// functions with the supported Database backends.
+//
+//nolint:revive // ignore name stutter
+type SecretInterface interface {
+ // Secret Data Definition Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_definition_language
+
+ // CreateSecretIndexes defines a function that creates the indexes for the secrets table.
+ CreateSecretIndexes() error
+ // CreateSecretTable defines a function that creates the secrets table.
+ CreateSecretTable(string) error
+
+ // Secret Data Manipulation Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_manipulation_language
+
+ // CountSecrets defines a function that gets the count of all secrets.
+ CountSecrets() (int64, error)
+ // CountSecretsForOrg defines a function that gets the count of secrets by org name.
+ CountSecretsForOrg(string, map[string]interface{}) (int64, error)
+ // CountSecretsForRepo defines a function that gets the count of secrets by org and repo name.
+ CountSecretsForRepo(*library.Repo, map[string]interface{}) (int64, error)
+ // CountSecretsForTeam defines a function that gets the count of secrets by org and team name.
+ CountSecretsForTeam(string, string, map[string]interface{}) (int64, error)
+ // CountSecretsForTeams defines a function that gets the count of secrets by teams within an org.
+ CountSecretsForTeams(string, []string, map[string]interface{}) (int64, error)
+ // CreateSecret defines a function that creates a new secret.
+ CreateSecret(*library.Secret) (*library.Secret, error)
+ // DeleteSecret defines a function that deletes an existing secret.
+ DeleteSecret(*library.Secret) error
+ // GetSecret defines a function that gets a secret by ID.
+ GetSecret(int64) (*library.Secret, error)
+ // GetSecretForOrg defines a function that gets a secret by org name.
+ GetSecretForOrg(string, string) (*library.Secret, error)
+ // GetSecretForRepo defines a function that gets a secret by org and repo name.
+ GetSecretForRepo(string, *library.Repo) (*library.Secret, error)
+ // GetSecretForTeam defines a function that gets a secret by org and team name.
+ GetSecretForTeam(string, string, string) (*library.Secret, error)
+ // ListSecrets defines a function that gets a list of all secrets.
+ ListSecrets() ([]*library.Secret, error)
+ // ListSecretsForOrg defines a function that gets a list of secrets by org name.
+ ListSecretsForOrg(string, map[string]interface{}, int, int) ([]*library.Secret, int64, error)
+ // ListSecretsForRepo defines a function that gets a list of secrets by org and repo name.
+ ListSecretsForRepo(*library.Repo, map[string]interface{}, int, int) ([]*library.Secret, int64, error)
+ // ListSecretsForTeam defines a function that gets a list of secrets by org and team name.
+ ListSecretsForTeam(string, string, map[string]interface{}, int, int) ([]*library.Secret, int64, error)
+ // ListSecretsForTeams defines a function that gets a list of secrets by teams within an org.
+ ListSecretsForTeams(string, []string, map[string]interface{}, int, int) ([]*library.Secret, int64, error)
+ // UpdateSecret defines a function that updates an existing secret.
+ UpdateSecret(*library.Secret) (*library.Secret, error)
+}
diff --git a/database/secret/list.go b/database/secret/list.go
new file mode 100644
index 000000000..fd01dc295
--- /dev/null
+++ b/database/secret/list.go
@@ -0,0 +1,67 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// ListSecrets gets a list of all secrets from the database.
+func (e *engine) ListSecrets() ([]*library.Secret, error) {
+ e.logger.Trace("listing all secrets from the database")
+
+ // variables to store query results and return value
+ count := int64(0)
+ s := new([]database.Secret)
+ secrets := []*library.Secret{}
+
+ // count the results
+ count, err := e.CountSecrets()
+ if err != nil {
+ return nil, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return secrets, nil
+ }
+
+ // send query to the database and store result in variable
+ err = e.client.
+ Table(constants.TableSecret).
+ Find(&s).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // iterate through all query results
+ for _, secret := range *s {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := secret
+
+ // decrypt the fields for the secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Decrypt
+ err = tmp.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // TODO: remove backwards compatibility before 1.x.x release
+ //
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch unencrypted secrets
+ e.logger.Errorf("unable to decrypt secret %d: %v", tmp.ID.Int64, err)
+ }
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.ToLibrary
+ secrets = append(secrets, tmp.ToLibrary())
+ }
+
+ return secrets, nil
+}
diff --git a/database/secret/list_org.go b/database/secret/list_org.go
new file mode 100644
index 000000000..27078a0f3
--- /dev/null
+++ b/database/secret/list_org.go
@@ -0,0 +1,82 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// ListSecretsForOrg gets a list of secrets by org name from the database.
+//
+//nolint:lll // ignore long line length due to variable names
+func (e *engine) ListSecretsForOrg(org string, filters map[string]interface{}, page, perPage int) ([]*library.Secret, int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": org,
+ "type": constants.SecretOrg,
+ }).Tracef("listing secrets for org %s from the database", org)
+
+ // variables to store query results and return values
+ count := int64(0)
+ s := new([]database.Secret)
+ secrets := []*library.Secret{}
+
+ // count the results
+ count, err := e.CountSecretsForOrg(org, filters)
+ if err != nil {
+ return secrets, 0, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return secrets, 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.TableSecret).
+ Where("type = ?", constants.SecretOrg).
+ Where("org = ?", org).
+ 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 _, secret := range *s {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := secret
+
+ // decrypt the fields for the secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Decrypt
+ err = tmp.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // TODO: remove backwards compatibility before 1.x.x release
+ //
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch unencrypted secrets
+ e.logger.Errorf("unable to decrypt secret %d: %v", tmp.ID.Int64, err)
+ }
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.ToLibrary
+ secrets = append(secrets, tmp.ToLibrary())
+ }
+
+ return secrets, count, nil
+}
diff --git a/database/secret/list_org_test.go b/database/secret/list_org_test.go
new file mode 100644
index 000000000..23acd7ead
--- /dev/null
+++ b/database/secret/list_org_test.go
@@ -0,0 +1,120 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+)
+
+func TestSecret_Engine_ListSecretsForOrg(t *testing.T) {
+ // setup types
+ _secretOne := testSecret()
+ _secretOne.SetID(1)
+ _secretOne.SetOrg("foo")
+ _secretOne.SetRepo("*")
+ _secretOne.SetName("baz")
+ _secretOne.SetValue("bar")
+ _secretOne.SetType("org")
+ _secretOne.SetCreatedAt(1)
+ _secretOne.SetCreatedBy("user")
+ _secretOne.SetUpdatedAt(1)
+ _secretOne.SetUpdatedBy("user2")
+
+ _secretTwo := testSecret()
+ _secretTwo.SetID(2)
+ _secretTwo.SetOrg("foo")
+ _secretTwo.SetRepo("*")
+ _secretTwo.SetName("bar")
+ _secretTwo.SetValue("baz")
+ _secretTwo.SetType("org")
+ _secretTwo.SetCreatedAt(1)
+ _secretTwo.SetCreatedBy("user")
+ _secretTwo.SetUpdatedAt(1)
+ _secretTwo.SetUpdatedBy("user2")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected name count query result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the name count query
+ _mock.ExpectQuery(`SELECT count(*) FROM "secrets" WHERE type = $1 AND org = $2`).
+ WithArgs(constants.SecretOrg, "foo").WillReturnRows(_rows)
+
+ // create expected name query result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}).
+ AddRow(2, "org", "foo", "*", "", "bar", "baz", nil, nil, false, 1, "user", 1, "user2").
+ AddRow(1, "org", "foo", "*", "", "baz", "bar", nil, nil, false, 1, "user", 1, "user2")
+
+ // ensure the mock expects the name query
+ _mock.ExpectQuery(`SELECT * FROM "secrets" WHERE type = $1 AND org = $2 ORDER BY id DESC LIMIT 10`).
+ WithArgs(constants.SecretOrg, "foo").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSecret(_secretOne)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSecret(_secretTwo)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Secret
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Secret{_secretTwo, _secretOne},
+ },
+ {
+ failure: false,
+ name: "sqlite",
+ database: _sqlite,
+ want: []*library.Secret{_secretTwo, _secretOne},
+ },
+ }
+
+ filters := map[string]interface{}{}
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, _, err := test.database.ListSecretsForOrg("foo", filters, 1, 10)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListSecretsForOrg for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListSecretsForOrg for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListSecretsForOrg for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/secret/list_repo.go b/database/secret/list_repo.go
new file mode 100644
index 000000000..c89ba1500
--- /dev/null
+++ b/database/secret/list_repo.go
@@ -0,0 +1,84 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// ListSecretsForRepo gets a list of secrets by org name from the database.
+//
+//nolint:lll // ignore long line length due to variable names
+func (e *engine) ListSecretsForRepo(r *library.Repo, filters map[string]interface{}, page, perPage int) ([]*library.Secret, int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ "type": constants.SecretRepo,
+ }).Tracef("listing secrets for repo %s from the database", r.GetFullName())
+
+ // variables to store query results and return values
+ count := int64(0)
+ s := new([]database.Secret)
+ secrets := []*library.Secret{}
+
+ // count the results
+ count, err := e.CountSecretsForRepo(r, filters)
+ if err != nil {
+ return secrets, 0, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return secrets, 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.TableSecret).
+ Where("type = ?", constants.SecretRepo).
+ Where("org = ?", r.GetOrg()).
+ Where("repo = ?", r.GetName()).
+ 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 _, secret := range *s {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := secret
+
+ // decrypt the fields for the secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Decrypt
+ err = tmp.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // TODO: remove backwards compatibility before 1.x.x release
+ //
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch unencrypted secrets
+ e.logger.Errorf("unable to decrypt secret %d: %v", tmp.ID.Int64, err)
+ }
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.ToLibrary
+ secrets = append(secrets, tmp.ToLibrary())
+ }
+
+ return secrets, count, nil
+}
diff --git a/database/secret/list_repo_test.go b/database/secret/list_repo_test.go
new file mode 100644
index 000000000..db26d2543
--- /dev/null
+++ b/database/secret/list_repo_test.go
@@ -0,0 +1,131 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/go-vela/types/constants"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestSecret_Engine_ListSecretsForRepo(t *testing.T) {
+ // setup types
+ _repo := testRepo()
+ _repo.SetID(1)
+ _repo.SetUserID(1)
+ _repo.SetHash("baz")
+ _repo.SetOrg("foo")
+ _repo.SetName("bar")
+ _repo.SetFullName("foo/bar")
+ _repo.SetVisibility("public")
+ _repo.SetPipelineType("yaml")
+
+ _secretOne := testSecret()
+ _secretOne.SetID(1)
+ _secretOne.SetOrg("foo")
+ _secretOne.SetRepo("bar")
+ _secretOne.SetName("baz")
+ _secretOne.SetValue("foob")
+ _secretOne.SetType("repo")
+ _secretOne.SetCreatedAt(1)
+ _secretOne.SetCreatedBy("user")
+ _secretOne.SetUpdatedAt(1)
+ _secretOne.SetUpdatedBy("user2")
+
+ _secretTwo := testSecret()
+ _secretTwo.SetID(2)
+ _secretTwo.SetOrg("foo")
+ _secretTwo.SetRepo("bar")
+ _secretTwo.SetName("foob")
+ _secretTwo.SetValue("baz")
+ _secretTwo.SetType("repo")
+ _secretTwo.SetCreatedAt(1)
+ _secretTwo.SetCreatedBy("user")
+ _secretTwo.SetUpdatedAt(1)
+ _secretTwo.SetUpdatedBy("user2")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected name count query result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the name count query
+ _mock.ExpectQuery(`SELECT count(*) FROM "secrets" WHERE type = $1 AND org = $2 AND repo = $3`).
+ WithArgs(constants.SecretRepo, "foo", "bar").WillReturnRows(_rows)
+
+ // create expected name query result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}).
+ AddRow(2, "repo", "foo", "bar", "", "foob", "baz", nil, nil, false, 1, "user", 1, "user2").
+ AddRow(1, "repo", "foo", "bar", "", "baz", "foob", nil, nil, false, 1, "user", 1, "user2")
+
+ // ensure the mock expects the name query
+ _mock.ExpectQuery(`SELECT * FROM "secrets" WHERE type = $1 AND org = $2 AND repo = $3 ORDER BY id DESC LIMIT 10`).
+ WithArgs(constants.SecretRepo, "foo", "bar").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSecret(_secretOne)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSecret(_secretTwo)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Secret
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Secret{_secretTwo, _secretOne},
+ },
+ {
+ failure: false,
+ name: "sqlite",
+ database: _sqlite,
+ want: []*library.Secret{_secretTwo, _secretOne},
+ },
+ }
+
+ filters := map[string]interface{}{}
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, _, err := test.database.ListSecretsForRepo(_repo, filters, 1, 10)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListSecretsForRepo for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListSecretsForRepo for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListSecretsForRepo for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/secret/list_team.go b/database/secret/list_team.go
new file mode 100644
index 000000000..0897fe949
--- /dev/null
+++ b/database/secret/list_team.go
@@ -0,0 +1,162 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "strings"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// ListSecretsForTeam gets a list of secrets by org and team name from the database.
+//
+//nolint:lll // ignore long line length due to variable names
+func (e *engine) ListSecretsForTeam(org, team string, filters map[string]interface{}, page, perPage int) ([]*library.Secret, int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "org": org,
+ "team": team,
+ "type": constants.SecretShared,
+ }).Tracef("listing secrets for team %s/%s from the database", org, team)
+
+ // variables to store query results and return values
+ count := int64(0)
+ s := new([]database.Secret)
+ secrets := []*library.Secret{}
+
+ // count the results
+ count, err := e.CountSecretsForTeam(org, team, filters)
+ if err != nil {
+ return secrets, 0, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return secrets, 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.TableSecret).
+ Where("type = ?", constants.SecretShared).
+ Where("org = ?", org).
+ Where("team = ?", team).
+ 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 _, secret := range *s {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := secret
+
+ // decrypt the fields for the secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Decrypt
+ err = tmp.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // TODO: remove backwards compatibility before 1.x.x release
+ //
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch unencrypted secrets
+ e.logger.Errorf("unable to decrypt secret %d: %v", tmp.ID.Int64, err)
+ }
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.ToLibrary
+ secrets = append(secrets, tmp.ToLibrary())
+ }
+
+ return secrets, count, nil
+}
+
+// ListSecretsForTeams gets a list of secrets by teams within an org from the database.
+func (e *engine) ListSecretsForTeams(org string, teams []string, filters map[string]interface{}, page, perPage int) ([]*library.Secret, int64, error) {
+ // iterate through the list of teams provided
+ for index, team := range teams {
+ // ensure the team name is lower case
+ teams[index] = strings.ToLower(team)
+ }
+
+ e.logger.WithFields(logrus.Fields{
+ "org": org,
+ "teams": teams,
+ "type": constants.SecretShared,
+ }).Tracef("listing secrets for teams %s in org %s from the database", teams, org)
+
+ // variables to store query results and return values
+ count := int64(0)
+ s := new([]database.Secret)
+ secrets := []*library.Secret{}
+
+ // count the results
+ count, err := e.CountSecretsForTeams(org, teams, filters)
+ if err != nil {
+ return secrets, 0, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return secrets, 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.TableSecret).
+ Where("type = ?", constants.SecretShared).
+ Where("org = ?", org).
+ Where("LOWER(team) IN (?)", teams).
+ 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 _, secret := range *s {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := secret
+
+ // decrypt the fields for the secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Decrypt
+ err = tmp.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // TODO: remove backwards compatibility before 1.x.x release
+ //
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch unencrypted secrets
+ e.logger.Errorf("unable to decrypt secret %d: %v", tmp.ID.Int64, err)
+ }
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.ToLibrary
+ secrets = append(secrets, tmp.ToLibrary())
+ }
+
+ return secrets, count, nil
+}
diff --git a/database/secret/list_team_test.go b/database/secret/list_team_test.go
new file mode 100644
index 000000000..546877659
--- /dev/null
+++ b/database/secret/list_team_test.go
@@ -0,0 +1,227 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/go-vela/types/constants"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestSecret_Engine_ListSecretsForTeam(t *testing.T) {
+ // setup types
+ _secretOne := testSecret()
+ _secretOne.SetID(1)
+ _secretOne.SetOrg("foo")
+ _secretOne.SetTeam("bar")
+ _secretOne.SetName("baz")
+ _secretOne.SetValue("foob")
+ _secretOne.SetType("shared")
+ _secretOne.SetCreatedAt(1)
+ _secretOne.SetCreatedBy("user")
+ _secretOne.SetUpdatedAt(1)
+ _secretOne.SetUpdatedBy("user2")
+
+ _secretTwo := testSecret()
+ _secretTwo.SetID(2)
+ _secretTwo.SetOrg("foo")
+ _secretTwo.SetTeam("bar")
+ _secretTwo.SetName("foob")
+ _secretTwo.SetValue("baz")
+ _secretTwo.SetType("shared")
+ _secretTwo.SetCreatedAt(1)
+ _secretTwo.SetCreatedBy("user")
+ _secretTwo.SetUpdatedAt(1)
+ _secretTwo.SetUpdatedBy("user2")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected name count query result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the name count query
+ _mock.ExpectQuery(`SELECT count(*) FROM "secrets" WHERE type = $1 AND org = $2 AND team = $3`).
+ WithArgs(constants.SecretShared, "foo", "bar").WillReturnRows(_rows)
+
+ // create expected name query result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}).
+ AddRow(2, "shared", "foo", "", "bar", "foob", "baz", nil, nil, false, 1, "user", 1, "user2").
+ AddRow(1, "shared", "foo", "", "bar", "baz", "foob", nil, nil, false, 1, "user", 1, "user2")
+
+ // ensure the mock expects the name query
+ _mock.ExpectQuery(`SELECT * FROM "secrets" WHERE type = $1 AND org = $2 AND team = $3 ORDER BY id DESC LIMIT 10`).
+ WithArgs(constants.SecretShared, "foo", "bar").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSecret(_secretOne)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSecret(_secretTwo)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Secret
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Secret{_secretTwo, _secretOne},
+ },
+ {
+ failure: false,
+ name: "sqlite",
+ database: _sqlite,
+ want: []*library.Secret{_secretTwo, _secretOne},
+ },
+ }
+
+ filters := map[string]interface{}{}
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, _, err := test.database.ListSecretsForTeam("foo", "bar", filters, 1, 10)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListSecretsForTeam for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListSecretsForTeam for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListSecretsForTeam for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
+
+func TestSecret_Engine_ListSecretsForTeams(t *testing.T) {
+ // setup types
+ _secretOne := testSecret()
+ _secretOne.SetID(1)
+ _secretOne.SetOrg("foo")
+ _secretOne.SetTeam("bar")
+ _secretOne.SetName("baz")
+ _secretOne.SetValue("foob")
+ _secretOne.SetType("shared")
+ _secretOne.SetCreatedAt(1)
+ _secretOne.SetCreatedBy("user")
+ _secretOne.SetUpdatedAt(1)
+ _secretOne.SetUpdatedBy("user2")
+
+ _secretTwo := testSecret()
+ _secretTwo.SetID(2)
+ _secretTwo.SetOrg("foo")
+ _secretTwo.SetTeam("bar")
+ _secretTwo.SetName("foob")
+ _secretTwo.SetValue("baz")
+ _secretTwo.SetType("shared")
+ _secretTwo.SetCreatedAt(1)
+ _secretTwo.SetCreatedBy("user")
+ _secretTwo.SetUpdatedAt(1)
+ _secretTwo.SetUpdatedBy("user2")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected name count query result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the name count query
+ _mock.ExpectQuery(`SELECT count(*) FROM "secrets" WHERE type = $1 AND org = $2 AND LOWER(team) IN ($3,$4)`).
+ WithArgs(constants.SecretShared, "foo", "foo", "bar").WillReturnRows(_rows)
+
+ // create expected name query result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}).
+ AddRow(2, "shared", "foo", "", "bar", "foob", "baz", nil, nil, false, 1, "user", 1, "user2").
+ AddRow(1, "shared", "foo", "", "bar", "baz", "foob", nil, nil, false, 1, "user", 1, "user2")
+
+ // ensure the mock expects the name query
+ _mock.ExpectQuery(`SELECT * FROM "secrets" WHERE type = $1 AND org = $2 AND LOWER(team) IN ($3,$4) ORDER BY id DESC LIMIT 10`).
+ WithArgs(constants.SecretShared, "foo", "foo", "bar").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSecret(_secretOne)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSecret(_secretTwo)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Secret
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Secret{_secretTwo, _secretOne},
+ },
+ {
+ failure: false,
+ name: "sqlite",
+ database: _sqlite,
+ want: []*library.Secret{_secretTwo, _secretOne},
+ },
+ }
+
+ filters := map[string]interface{}{}
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, _, err := test.database.ListSecretsForTeams("foo", []string{"foo", "bar"}, filters, 1, 10)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListSecretsForTeams for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListSecretsForTeams for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListSecretsForTeams for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/secret/list_test.go b/database/secret/list_test.go
new file mode 100644
index 000000000..d0f4d9768
--- /dev/null
+++ b/database/secret/list_test.go
@@ -0,0 +1,115 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestSecret_Engine_ListSecrets(t *testing.T) {
+ // setup types
+ _secretOne := testSecret()
+ _secretOne.SetID(1)
+ _secretOne.SetOrg("foo")
+ _secretOne.SetRepo("bar")
+ _secretOne.SetName("baz")
+ _secretOne.SetValue("foob")
+ _secretOne.SetType("repo")
+ _secretOne.SetCreatedAt(1)
+ _secretOne.SetCreatedBy("user")
+ _secretOne.SetUpdatedAt(1)
+ _secretOne.SetUpdatedBy("user2")
+
+ _secretTwo := testSecret()
+ _secretTwo.SetID(2)
+ _secretTwo.SetOrg("foo")
+ _secretTwo.SetRepo("bar")
+ _secretTwo.SetName("foob")
+ _secretTwo.SetValue("baz")
+ _secretTwo.SetType("repo")
+ _secretTwo.SetCreatedAt(1)
+ _secretTwo.SetCreatedBy("user")
+ _secretTwo.SetUpdatedAt(1)
+ _secretTwo.SetUpdatedBy("user2")
+
+ _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 "secrets"`).WillReturnRows(_rows)
+
+ // create expected result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "type", "org", "repo", "team", "name", "value", "images", "events", "allow_command", "created_at", "created_by", "updated_at", "updated_by"}).
+ AddRow(1, "repo", "foo", "bar", "", "baz", "foob", nil, nil, false, 1, "user", 1, "user2").
+ AddRow(2, "repo", "foo", "bar", "", "foob", "baz", nil, nil, false, 1, "user", 1, "user2")
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "secrets"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSecret(_secretOne)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSecret(_secretTwo)
+ if err != nil {
+ t.Errorf("unable to create test secret for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Secret
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Secret{_secretOne, _secretTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.Secret{_secretOne, _secretTwo},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.ListSecrets()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListSecrets for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListSecrets for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListSecrets for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/secret/opts.go b/database/secret/opts.go
new file mode 100644
index 000000000..0e6e7935f
--- /dev/null
+++ b/database/secret/opts.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 secret
+
+import (
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+// EngineOpt represents a configuration option to initialize the database engine for Secrets.
+type EngineOpt func(*engine) error
+
+// WithClient sets the gorm.io/gorm client in the database engine for Secrets.
+func WithClient(client *gorm.DB) EngineOpt {
+ return func(e *engine) error {
+ // set the gorm.io/gorm client in the secret engine
+ e.client = client
+
+ return nil
+ }
+}
+
+// WithEncryptionKey sets the encryption key in the database engine for Secrets.
+func WithEncryptionKey(key string) EngineOpt {
+ return func(e *engine) error {
+ // set the encryption key in the secret engine
+ e.config.EncryptionKey = key
+
+ return nil
+ }
+}
+
+// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for Secrets.
+func WithLogger(logger *logrus.Entry) EngineOpt {
+ return func(e *engine) error {
+ // set the github.com/sirupsen/logrus logger in the secret engine
+ e.logger = logger
+
+ return nil
+ }
+}
+
+// WithSkipCreation sets the skip creation logic in the database engine for Secrets.
+func WithSkipCreation(skipCreation bool) EngineOpt {
+ return func(e *engine) error {
+ // set to skip creating tables and indexes in the secret engine
+ e.config.SkipCreation = skipCreation
+
+ return nil
+ }
+}
diff --git a/database/secret/opts_test.go b/database/secret/opts_test.go
new file mode 100644
index 000000000..0e123abdb
--- /dev/null
+++ b/database/secret/opts_test.go
@@ -0,0 +1,210 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+func TestSecret_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 TestSecret_EngineOpt_WithEncryptionKey(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ key string
+ want string
+ }{
+ {
+ failure: false,
+ name: "encryption key set",
+ key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ want: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ },
+ {
+ failure: false,
+ name: "encryption key not set",
+ key: "",
+ want: "",
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithEncryptionKey(test.key)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithEncryptionKey for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithEncryptionKey returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.config.EncryptionKey, test.want) {
+ t.Errorf("WithEncryptionKey is %v, want %v", e.config.EncryptionKey, test.want)
+ }
+ })
+ }
+}
+
+func TestSecret_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 TestSecret_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/secret/secret.go b/database/secret/secret.go
new file mode 100644
index 000000000..fa342f5d5
--- /dev/null
+++ b/database/secret/secret.go
@@ -0,0 +1,82 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+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 SecretInterface interface.
+ config struct {
+ // specifies the encryption key to use for the Secret engine
+ EncryptionKey string
+ // specifies to skip creating tables and indexes for the Secret engine
+ SkipCreation bool
+ }
+
+ // engine represents the secret functionality that implements the SecretInterface interface.
+ engine struct {
+ // engine configuration settings used in secret functions
+ config *config
+
+ // gorm.io/gorm database client used in secret functions
+ //
+ // https://pkg.go.dev/gorm.io/gorm#DB
+ client *gorm.DB
+
+ // sirupsen/logrus logger used in secret functions
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus#Entry
+ logger *logrus.Entry
+ }
+)
+
+// New creates and returns a Vela service for integrating with secrets in the database.
+//
+//nolint:revive // ignore returning unexported engine
+func New(opts ...EngineOpt) (*engine, error) {
+ // create new Secret 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 secret database objects
+ if e.config.SkipCreation {
+ e.logger.Warning("skipping creation of secrets table and indexes in the database")
+
+ return e, nil
+ }
+
+ // create the secrets table
+ err := e.CreateSecretTable(e.client.Config.Dialector.Name())
+ if err != nil {
+ return nil, fmt.Errorf("unable to create %s table: %w", constants.TableSecret, err)
+ }
+
+ // create the indexes for the secrets table
+ err = e.CreateSecretIndexes()
+ if err != nil {
+ return nil, fmt.Errorf("unable to create indexes for %s table: %w", constants.TableSecret, err)
+ }
+
+ return e, nil
+}
diff --git a/database/secret/secret_test.go b/database/secret/secret_test.go
new file mode 100644
index 000000000..0d6a61c25
--- /dev/null
+++ b/database/secret/secret_test.go
@@ -0,0 +1,255 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "database/sql/driver"
+ "reflect"
+ "testing"
+ "time"
+
+ "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 TestSecret_New(t *testing.T) {
+ // setup types
+ logger := logrus.NewEntry(logrus.StandardLogger())
+
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+ defer _sql.Close()
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateTypeOrgRepo).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateTypeOrgTeam).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateTypeOrg).WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _config := &gorm.Config{SkipDefaultTransaction: true}
+
+ _postgres, err := gorm.Open(postgres.New(postgres.Config{Conn: _sql}), _config)
+ if err != nil {
+ t.Errorf("unable to create new postgres database: %v", err)
+ }
+
+ _sqlite, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), _config)
+ if err != nil {
+ t.Errorf("unable to create new sqlite database: %v", err)
+ }
+
+ defer func() { _sql, _ := _sqlite.DB(); _sql.Close() }()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ client *gorm.DB
+ key string
+ logger *logrus.Entry
+ skipCreation bool
+ want *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ client: _postgres,
+ key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ client: _postgres,
+ config: &config{EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", SkipCreation: false},
+ logger: logger,
+ },
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ client: _sqlite,
+ key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ client: _sqlite,
+ config: &config{EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", SkipCreation: false},
+ logger: logger,
+ },
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := New(
+ WithClient(test.client),
+ WithEncryptionKey(test.key),
+ WithLogger(test.logger),
+ WithSkipCreation(test.skipCreation),
+ )
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("New for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("New for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("New for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
+
+// testPostgres is a helper function to create a Postgres engine for testing.
+func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) {
+ // create the new mock sql database
+ //
+ // https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#New
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateTypeOrgRepo).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateTypeOrgTeam).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateTypeOrg).WillReturnResult(sqlmock.NewResult(1, 1))
+
+ // create the new mock Postgres database client
+ //
+ // https://pkg.go.dev/gorm.io/gorm#Open
+ _postgres, err := gorm.Open(
+ postgres.New(postgres.Config{Conn: _sql}),
+ &gorm.Config{SkipDefaultTransaction: true},
+ )
+ if err != nil {
+ t.Errorf("unable to create new postgres database: %v", err)
+ }
+
+ _engine, err := New(
+ WithClient(_postgres),
+ WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"),
+ WithLogger(logrus.NewEntry(logrus.StandardLogger())),
+ WithSkipCreation(false),
+ )
+ if err != nil {
+ t.Errorf("unable to create new postgres secret engine: %v", err)
+ }
+
+ return _engine, _mock
+}
+
+// testSqlite is a helper function to create a Sqlite engine for testing.
+func testSqlite(t *testing.T) *engine {
+ _sqlite, err := gorm.Open(
+ sqlite.Open("file::memory:?cache=shared"),
+ &gorm.Config{SkipDefaultTransaction: true},
+ )
+ if err != nil {
+ t.Errorf("unable to create new sqlite database: %v", err)
+ }
+
+ _engine, err := New(
+ WithClient(_sqlite),
+ WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"),
+ WithLogger(logrus.NewEntry(logrus.StandardLogger())),
+ WithSkipCreation(false),
+ )
+ if err != nil {
+ t.Errorf("unable to create new sqlite secret engine: %v", err)
+ }
+
+ return _engine
+}
+
+// testRepo is a test helper function to create a library
+// Repo type with all fields set to their zero values.
+func testRepo() *library.Repo {
+ return &library.Repo{
+ ID: new(int64),
+ UserID: new(int64),
+ BuildLimit: new(int64),
+ Timeout: new(int64),
+ Counter: new(int),
+ PipelineType: new(string),
+ Hash: new(string),
+ Org: new(string),
+ Name: new(string),
+ FullName: new(string),
+ Link: new(string),
+ Clone: new(string),
+ Branch: new(string),
+ Visibility: new(string),
+ PreviousName: new(string),
+ Private: new(bool),
+ Trusted: new(bool),
+ Active: new(bool),
+ AllowPull: new(bool),
+ AllowPush: new(bool),
+ AllowDeploy: new(bool),
+ AllowTag: new(bool),
+ AllowComment: new(bool),
+ }
+}
+
+// testSecret is a test helper function to create a library
+// Secret type with all fields set to their zero values.
+func testSecret() *library.Secret {
+ return &library.Secret{
+ ID: new(int64),
+ Org: new(string),
+ Repo: new(string),
+ Team: new(string),
+ Name: new(string),
+ Value: new(string),
+ Type: new(string),
+ Images: new([]string),
+ Events: new([]string),
+ AllowCommand: new(bool),
+ CreatedAt: new(int64),
+ CreatedBy: new(string),
+ UpdatedAt: new(int64),
+ UpdatedBy: new(string),
+ }
+}
+
+// This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values
+// that are otherwise not easily compared. These typically would be values generated
+// before adding or updating them in the database.
+//
+// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime
+type AnyArgument struct{}
+
+// Match satisfies sqlmock.Argument interface.
+func (a AnyArgument) Match(_ driver.Value) bool {
+ return true
+}
+
+// NowTimestamp is used to test whether timestamps get updated correctly to the current time with lenience.
+type NowTimestamp struct{}
+
+// Match satisfies sqlmock.Argument interface.
+func (t NowTimestamp) Match(v driver.Value) bool {
+ ts, ok := v.(int64)
+ if !ok {
+ return false
+ }
+ now := time.Now().Unix()
+
+ return now-ts < 10
+}
diff --git a/database/secret/table.go b/database/secret/table.go
new file mode 100644
index 000000000..b36b6c4e9
--- /dev/null
+++ b/database/secret/table.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 secret
+
+import "github.com/go-vela/types/constants"
+
+const (
+ // CreatePostgresTable represents a query to create the Postgres secrets table.
+ CreatePostgresTable = `
+CREATE TABLE
+IF NOT EXISTS
+secrets (
+ id SERIAL PRIMARY KEY,
+ type VARCHAR(100),
+ org VARCHAR(250),
+ repo VARCHAR(250),
+ team VARCHAR(250),
+ name VARCHAR(250),
+ value BYTEA,
+ images VARCHAR(1000),
+ events VARCHAR(1000),
+ allow_command BOOLEAN,
+ created_at INTEGER,
+ created_by VARCHAR(250),
+ updated_at INTEGER,
+ updated_by VARCHAR(250),
+ UNIQUE(type, org, repo, name),
+ UNIQUE(type, org, team, name)
+);
+`
+
+ // CreateSqliteTable represents a query to create the Sqlite secrets table.
+ CreateSqliteTable = `
+CREATE TABLE
+IF NOT EXISTS
+secrets (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ type TEXT,
+ org TEXT,
+ repo TEXT,
+ team TEXT,
+ name TEXT,
+ value TEXT,
+ images TEXT,
+ events TEXT,
+ allow_command BOOLEAN,
+ created_at INTEGER,
+ created_by TEXT,
+ updated_at INTEGER,
+ updated_by TEXT,
+ UNIQUE(type, org, repo, name),
+ UNIQUE(type, org, team, name)
+);
+`
+)
+
+// CreateSecretTable creates the secrets table in the database.
+func (e *engine) CreateSecretTable(driver string) error {
+ e.logger.Tracef("creating secrets table in the database")
+
+ // handle the driver provided to create the table
+ switch driver {
+ case constants.DriverPostgres:
+ // create the secrets table for Postgres
+ return e.client.Exec(CreatePostgresTable).Error
+ case constants.DriverSqlite:
+ fallthrough
+ default:
+ // create the secrets table for Sqlite
+ return e.client.Exec(CreateSqliteTable).Error
+ }
+}
diff --git a/database/secret/table_test.go b/database/secret/table_test.go
new file mode 100644
index 000000000..055d84fa6
--- /dev/null
+++ b/database/secret/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 secret
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestSecret_Engine_CreateSecretTable(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.CreateSecretTable(test.name)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateSecretTable for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateSecretTable for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/secret/update.go b/database/secret/update.go
new file mode 100644
index 000000000..823ede644
--- /dev/null
+++ b/database/secret/update.go
@@ -0,0 +1,79 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code with create.go
+package secret
+
+import (
+ "fmt"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// UpdateSecret updates an existing secret in the database.
+func (e *engine) UpdateSecret(s *library.Secret) (*library.Secret, error) {
+ // handle the secret based off the type
+ switch s.GetType() {
+ case constants.SecretShared:
+ e.logger.WithFields(logrus.Fields{
+ "org": s.GetOrg(),
+ "team": s.GetTeam(),
+ "secret": s.GetName(),
+ "type": s.GetType(),
+ }).Tracef("updating secret %s/%s/%s/%s in the database", s.GetType(), s.GetOrg(), s.GetTeam(), s.GetName())
+ default:
+ e.logger.WithFields(logrus.Fields{
+ "org": s.GetOrg(),
+ "repo": s.GetRepo(),
+ "secret": s.GetName(),
+ "type": s.GetType(),
+ }).Tracef("updating secret %s/%s/%s/%s in the database", s.GetType(), s.GetOrg(), s.GetRepo(), s.GetName())
+ }
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#SecretFromLibrary
+ secret := database.SecretFromLibrary(s)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Validate
+ err := secret.Validate()
+ if err != nil {
+ return nil, err
+ }
+
+ // encrypt the fields for the secret
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Encrypt
+ err = secret.Encrypt(e.config.EncryptionKey)
+ if err != nil {
+ switch s.GetType() {
+ case constants.SecretShared:
+ return nil, fmt.Errorf("unable to encrypt secret %s/%s/%s/%s: %w", s.GetType(), s.GetOrg(), s.GetTeam(), s.GetName(), err)
+ default:
+ return nil, fmt.Errorf("unable to encrypt secret %s/%s/%s/%s: %w", s.GetType(), s.GetOrg(), s.GetRepo(), s.GetName(), err)
+ }
+ }
+
+ err = e.client.Table(constants.TableSecret).Save(secret.Nullify()).Error
+ if err != nil {
+ return nil, err
+ }
+
+ err = secret.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ switch s.GetType() {
+ case constants.SecretShared:
+ return nil, fmt.Errorf("unable to decrypt secret %s/%s/%s/%s: %w", s.GetType(), s.GetOrg(), s.GetTeam(), s.GetName(), err)
+ default:
+ return nil, fmt.Errorf("unable to decrypt secret %s/%s/%s/%s: %w", s.GetType(), s.GetOrg(), s.GetRepo(), s.GetName(), err)
+ }
+ }
+
+ return secret.ToLibrary(), nil
+}
diff --git a/database/secret/update_test.go b/database/secret/update_test.go
new file mode 100644
index 000000000..247b9d229
--- /dev/null
+++ b/database/secret/update_test.go
@@ -0,0 +1,163 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package secret
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestSecret_Engine_UpdateSecret(t *testing.T) {
+ // setup types
+ _secretRepo := testSecret()
+ _secretRepo.SetID(1)
+ _secretRepo.SetOrg("foo")
+ _secretRepo.SetRepo("bar")
+ _secretRepo.SetName("baz")
+ _secretRepo.SetValue("foob")
+ _secretRepo.SetType("repo")
+ _secretRepo.SetCreatedAt(1)
+ _secretRepo.SetCreatedBy("user")
+ _secretRepo.SetUpdatedAt(1)
+ _secretRepo.SetUpdatedBy("user2")
+
+ _secretOrg := testSecret()
+ _secretOrg.SetID(2)
+ _secretOrg.SetOrg("foo")
+ _secretOrg.SetRepo("*")
+ _secretOrg.SetName("bar")
+ _secretOrg.SetValue("baz")
+ _secretOrg.SetType("org")
+ _secretOrg.SetCreatedAt(1)
+ _secretOrg.SetCreatedBy("user")
+ _secretOrg.SetUpdatedAt(1)
+ _secretOrg.SetUpdatedBy("user2")
+
+ _secretShared := testSecret()
+ _secretShared.SetID(3)
+ _secretShared.SetOrg("foo")
+ _secretShared.SetTeam("bar")
+ _secretShared.SetName("baz")
+ _secretShared.SetValue("foob")
+ _secretShared.SetType("shared")
+ _secretShared.SetCreatedAt(1)
+ _secretShared.SetCreatedBy("user")
+ _secretShared.SetUpdatedAt(1)
+ _secretShared.SetUpdatedBy("user2")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the repo query
+ _mock.ExpectExec(`UPDATE "secrets"
+SET "org"=$1,"repo"=$2,"team"=$3,"name"=$4,"value"=$5,"type"=$6,"images"=$7,"events"=$8,"allow_command"=$9,"created_at"=$10,"created_by"=$11,"updated_at"=$12,"updated_by"=$13
+WHERE "id" = $14`).
+ WithArgs("foo", "bar", nil, "baz", AnyArgument{}, "repo", nil, nil, false, 1, "user", AnyArgument{}, "user2", 1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ // ensure the mock expects the org query
+ _mock.ExpectExec(`UPDATE "secrets"
+SET "org"=$1,"repo"=$2,"team"=$3,"name"=$4,"value"=$5,"type"=$6,"images"=$7,"events"=$8,"allow_command"=$9,"created_at"=$10,"created_by"=$11,"updated_at"=$12,"updated_by"=$13
+WHERE "id" = $14`).
+ WithArgs("foo", "*", nil, "bar", AnyArgument{}, "org", nil, nil, false, 1, "user", AnyArgument{}, "user2", 2).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ // ensure the mock expects the shared query
+ _mock.ExpectExec(`UPDATE "secrets"
+SET "org"=$1,"repo"=$2,"team"=$3,"name"=$4,"value"=$5,"type"=$6,"images"=$7,"events"=$8,"allow_command"=$9,"created_at"=$10,"created_by"=$11,"updated_at"=$12,"updated_by"=$13
+WHERE "id" = $14`).
+ WithArgs("foo", nil, "bar", "baz", AnyArgument{}, "shared", nil, nil, false, 1, "user", NowTimestamp{}, "user2", 3).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateSecret(_secretRepo)
+ if err != nil {
+ t.Errorf("unable to create test repo secret for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSecret(_secretOrg)
+ if err != nil {
+ t.Errorf("unable to create test org secret for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateSecret(_secretShared)
+ if err != nil {
+ t.Errorf("unable to create test shared secret for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ secret *library.Secret
+ }{
+ {
+ failure: false,
+ name: "postgres with repo",
+ database: _postgres,
+ secret: _secretRepo,
+ },
+ {
+ failure: false,
+ name: "postgres with org",
+ database: _postgres,
+ secret: _secretOrg,
+ },
+ {
+ failure: false,
+ name: "postgres with shared",
+ database: _postgres,
+ secret: _secretShared,
+ },
+ {
+ failure: false,
+ name: "sqlite3 with repo",
+ database: _sqlite,
+ secret: _secretRepo,
+ },
+ {
+ failure: false,
+ name: "sqlite3 with org",
+ database: _sqlite,
+ secret: _secretOrg,
+ },
+ {
+ failure: false,
+ name: "sqlite3 with shared",
+ database: _sqlite,
+ secret: _secretShared,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.UpdateSecret(test.secret)
+ got.SetUpdatedAt(test.secret.GetUpdatedAt())
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("UpdateSecret for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("UpdateSecret for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.secret) {
+ t.Errorf("UpdateSecret for %s is %s, want %s", test.name, got, test.secret)
+ }
+ })
+ }
+}
diff --git a/database/service.go b/database/service.go
deleted file mode 100644
index 314f434e1..000000000
--- a/database/service.go
+++ /dev/null
@@ -1,287 +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 database
-
-import (
- "github.com/go-vela/types/library"
-)
-
-// Service represents the interface for Vela integrating
-// with the different supported Database backends.
-type Service interface {
- // Database Interface Functions
-
- // Driver defines a function that outputs
- // the configured database driver.
- Driver() string
-
- // Build Database Interface Functions
-
- // GetBuild defines a function that
- // gets a build by number and repo ID.
- GetBuild(int, *library.Repo) (*library.Build, error)
- // GetLastBuild defines a function that
- // gets the last build ran by repo ID.
- GetLastBuild(*library.Repo) (*library.Build, error)
- // GetLastBuildByBranch defines a function that
- // gets the last build ran by repo ID and branch.
- GetLastBuildByBranch(*library.Repo, string) (*library.Build, error)
- // GetBuildCount defines a function that
- // gets the count of builds.
- GetBuildCount() (int64, error)
- // GetBuildCountByStatus defines a function that
- // gets a the count of builds by status.
- GetBuildCountByStatus(string) (int64, error)
- // GetBuildList defines a function that gets
- // a list of all builds.
- GetBuildList() ([]*library.Build, error)
- // GetDeploymentBuildList defines a function that gets
- // a list of builds related to a deployment.
- GetDeploymentBuildList(string) ([]*library.Build, error)
- // GetRepoBuildList defines a function that
- // gets a list of builds by repo ID.
- GetRepoBuildList(*library.Repo, map[string]interface{}, int, int) ([]*library.Build, int64, error)
- // GetOrgBuildList defines a function that
- // gets a list of builds by org.
- GetOrgBuildList(string, map[string]interface{}, int, int) ([]*library.Build, int64, error)
- // GetRepoBuildCount defines a function that
- // gets the count of builds by repo ID.
- GetRepoBuildCount(*library.Repo, map[string]interface{}) (int64, error)
- // GetOrgBuildCount defines a function that
- // gets the count of builds by org.
- GetOrgBuildCount(string, map[string]interface{}) (int64, error)
- // GetPendingAndRunningBuilds defines a function that
- // gets the list of pending and running builds.
- GetPendingAndRunningBuilds(string) ([]*library.BuildQueue, error)
- // CreateBuild defines a function that
- // creates a new build.
- CreateBuild(*library.Build) error
- // UpdateBuild defines a function that
- // updates a build.
- UpdateBuild(*library.Build) error
- // DeleteBuild defines a function that
- // deletes a build by unique ID.
- DeleteBuild(int64) error
-
- // Hook Database Interface Functions
-
- // GetHook defines a function that
- // gets a webhook by number and repo ID.
- GetHook(int, *library.Repo) (*library.Hook, error)
- // GetLastHook defines a function that
- // gets the last hook by repo ID.
- GetLastHook(*library.Repo) (*library.Hook, error)
- // GetHookList defines a function that gets
- // a list of all webhooks.
- GetHookList() ([]*library.Hook, error)
- // GetRepoHookList defines a function that
- // gets a list of webhooks by repo ID.
- GetRepoHookList(*library.Repo, int, int) ([]*library.Hook, error)
- // GetRepoHookCount defines a function that
- // gets the count of webhooks by repo ID.
- GetRepoHookCount(*library.Repo) (int64, error)
- // CreateHook defines a function that
- // creates a new webhook.
- CreateHook(*library.Hook) error
- // UpdateHook defines a function that
- // updates a webhook.
- UpdateHook(*library.Hook) error
- // DeleteHook defines a function that
- // deletes a webhook by unique ID.
- DeleteHook(int64) error
-
- // Log Database Interface Functions
-
- // GetStepLog defines a function that
- // gets a step log by unique ID.
- GetStepLog(int64) (*library.Log, error)
- // GetServiceLog defines a function that
- // gets a service log by unique ID.
- GetServiceLog(int64) (*library.Log, error)
- // GetBuildLogs defines a function that
- // gets a list of logs by build ID.
- GetBuildLogs(int64) ([]*library.Log, error)
- // CreateLog defines a function that
- // creates a new log.
- CreateLog(*library.Log) error
- // UpdateLog defines a function that
- // updates a log.
- UpdateLog(*library.Log) error
- // DeleteLog defines a function that
- // deletes a log by unique ID.
- DeleteLog(int64) error
-
- // Repo Database Interface Functions
-
- // GetRepo defines a function that
- // gets a repo by org and name.
- GetRepo(string, string) (*library.Repo, error)
- // GetRepoList defines a function that
- // gets a list of all repos.
- GetRepoList() ([]*library.Repo, error)
- // GetOrgRepoList defines a function that
- // gets a list of all repos by org excluding repos specified.
- GetOrgRepoList(string, map[string]string, int, int) ([]*library.Repo, error)
- // GetOrgRepoCount defines a function that
- // gets the count of repos for an org.
- GetOrgRepoCount(string, map[string]string) (int64, error)
- // GetRepoCount defines a function that
- // gets the count of repos.
- GetRepoCount() (int64, error)
- // GetUserRepoList defines a function
- // that gets a list of repos by user ID.
- GetUserRepoList(*library.User, int, int) ([]*library.Repo, error)
- // GetUserRepoCount defines a function that
- // gets the count of repos for a user.
- GetUserRepoCount(*library.User) (int64, error)
- // CreateRepo defines a function that
- // creates a new repo.
- CreateRepo(*library.Repo) error
- // UpdateRepo defines a function that
- // updates a repo.
- UpdateRepo(*library.Repo) error
- // DeleteRepo defines a function that
- // deletes a repo by unique ID.
- DeleteRepo(int64) error
-
- // Secret Database Interface Functions
-
- // GetSecret defines a function that gets a secret
- // by type, org, name (repo or team) and secret name.
- GetSecret(string, string, string, string) (*library.Secret, error)
- // GetSecretList defines a function that
- // gets a list of all secrets.
- GetSecretList() ([]*library.Secret, error)
- // GetTypeSecretList defines a function that gets a list
- // of secrets by type, owner, and name (repo or team).
- GetTypeSecretList(string, string, string, int, int, []string) ([]*library.Secret, error)
- // GetTypeSecretCount defines a function that gets a count
- // of secrets by type, owner, and name (repo or team).
- GetTypeSecretCount(string, string, string, []string) (int64, error)
- // CreateSecret defines a function that
- // creates a new secret.
- CreateSecret(*library.Secret) error
- // UpdateSecret defines a function that
- // updates a secret.
- UpdateSecret(*library.Secret) error
- // DeleteSecret defines a function that
- // deletes a secret by unique ID.
- DeleteSecret(int64) error
-
- // 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
-
- // Service Database Interface Functions
-
- // GetService defines a function that
- // gets a step by number and build ID.
- GetService(int, *library.Build) (*library.Service, error)
- // GetServiceList defines a function that
- // gets a list of all steps.
- GetServiceList() ([]*library.Service, error)
- // GetBuildServiceList defines a function
- // that gets a list of steps by build ID.
- GetBuildServiceList(*library.Build, int, int) ([]*library.Service, error)
- // GetBuildServiceCount defines a function
- // that gets the count of steps by build ID.
- GetBuildServiceCount(*library.Build) (int64, error)
- // GetServiceImageCount defines a function that
- // gets a list of all service images and the
- // count of their occurrence.
- GetServiceImageCount() (map[string]float64, error)
- // GetServiceStatusCount defines a function that
- // gets a list of all service statuses and the
- // count of their occurrence.
- GetServiceStatusCount() (map[string]float64, error)
- // CreateService defines a function that
- // creates a new step.
- CreateService(*library.Service) error
- // UpdateService defines a function that
- // updates a step.
- UpdateService(*library.Service) error
- // DeleteService defines a function that
- // deletes a step by unique ID.
- DeleteService(int64) error
-
- // User Database Interface Functions
-
- // GetUser defines a function that
- // gets a user by unique ID.
- GetUser(int64) (*library.User, error)
- // GetUserName defines a function that
- // gets a user by name.
- GetUserName(string) (*library.User, error)
- // GetUserList defines a function that
- // gets a list of all users.
- GetUserList() ([]*library.User, error)
- // GetUserCount defines a function that
- // gets the count of users.
- GetUserCount() (int64, error)
- // GetUserLiteList defines a function
- // that gets a lite list of users.
- GetUserLiteList(int, int) ([]*library.User, error)
- // CreateUser defines a function that
- // creates a new user.
- CreateUser(*library.User) error
- // UpdateUser defines a function that
- // updates a user.
- UpdateUser(*library.User) error
- // DeleteUser defines a function that
- // deletes a user by unique ID.
- DeleteUser(int64) error
-
- // Worker Database Interface Functions
-
- // GetWorker defines a function that
- // gets a worker by hostname.
- GetWorker(string) (*library.Worker, error)
- // GetWorkerAddress defines a function that
- // gets a worker by address.
- GetWorkerByAddress(string) (*library.Worker, error)
- // GetWorkerList defines a function that
- // gets a list of all workers.
- GetWorkerList() ([]*library.Worker, error)
- // GetWorkerCount defines a function that
- // gets the count of workers.
- GetWorkerCount() (int64, error)
- // CreateWorker defines a function that
- // creates a new worker.
- CreateWorker(*library.Worker) error
- // UpdateWorker defines a function that
- // updates a worker by unique ID.
- UpdateWorker(*library.Worker) error
- // DeleteWorker defines a function that
- // deletes a worker by hostname.
- DeleteWorker(int64) error
-}
diff --git a/database/service/clean.go b/database/service/clean.go
new file mode 100644
index 000000000..e8bcac16d
--- /dev/null
+++ b/database/service/clean.go
@@ -0,0 +1,35 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package service
+
+import (
+ "time"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CleanServices updates services to an error with a created timestamp prior to a defined moment.
+func (e *engine) CleanServices(msg string, before int64) (int64, error) {
+ logrus.Tracef("cleaning pending or running steps in the database created prior to %d", before)
+
+ s := new(library.Service)
+ s.SetStatus(constants.StatusError)
+ s.SetError(msg)
+ s.SetFinished(time.Now().UTC().Unix())
+
+ service := database.ServiceFromLibrary(s)
+
+ // send query to the database
+ result := e.client.
+ Table(constants.TableService).
+ Where("created < ?", before).
+ Where("status = 'running' OR status = 'pending'").
+ Updates(service)
+
+ return result.RowsAffected, result.Error
+}
diff --git a/database/service/clean_test.go b/database/service/clean_test.go
new file mode 100644
index 000000000..17301e119
--- /dev/null
+++ b/database/service/clean_test.go
@@ -0,0 +1,130 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package service
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestService_Engine_CleanService(t *testing.T) {
+ // setup types
+ _serviceOne := testService()
+ _serviceOne.SetID(1)
+ _serviceOne.SetRepoID(1)
+ _serviceOne.SetBuildID(1)
+ _serviceOne.SetNumber(1)
+ _serviceOne.SetName("foo")
+ _serviceOne.SetImage("bar")
+ _serviceOne.SetCreated(1)
+ _serviceOne.SetStatus("running")
+
+ _serviceTwo := testService()
+ _serviceTwo.SetID(2)
+ _serviceTwo.SetRepoID(1)
+ _serviceTwo.SetBuildID(1)
+ _serviceTwo.SetNumber(2)
+ _serviceTwo.SetName("foo")
+ _serviceTwo.SetImage("bar")
+ _serviceTwo.SetCreated(1)
+ _serviceTwo.SetStatus("pending")
+
+ _serviceThree := testService()
+ _serviceThree.SetID(3)
+ _serviceThree.SetRepoID(1)
+ _serviceThree.SetBuildID(1)
+ _serviceThree.SetNumber(3)
+ _serviceThree.SetName("foo")
+ _serviceThree.SetImage("bar")
+ _serviceThree.SetCreated(1)
+ _serviceThree.SetStatus("success")
+
+ _serviceFour := testService()
+ _serviceFour.SetID(4)
+ _serviceFour.SetRepoID(1)
+ _serviceFour.SetBuildID(1)
+ _serviceFour.SetNumber(4)
+ _serviceFour.SetName("foo")
+ _serviceFour.SetImage("bar")
+ _serviceFour.SetCreated(5)
+ _serviceFour.SetStatus("pending")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the name query
+ _mock.ExpectExec(`UPDATE "services" SET "status"=$1,"error"=$2,"finished"=$3 WHERE created < $4 AND (status = 'running' OR status = 'pending')`).
+ WithArgs("error", "msg", NowTimestamp{}, 3).
+ WillReturnResult(sqlmock.NewResult(1, 2))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateService(_serviceOne)
+ if err != nil {
+ t.Errorf("unable to create test service for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateService(_serviceTwo)
+ if err != nil {
+ t.Errorf("unable to create test service for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateService(_serviceThree)
+ if err != nil {
+ t.Errorf("unable to create test service for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateService(_serviceFour)
+ if err != nil {
+ t.Errorf("unable to create test service 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.CleanServices("msg", 3)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CleanServices for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CleanServices for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CleanServices for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/service/count.go b/database/service/count.go
new file mode 100644
index 000000000..08b488933
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "github.com/go-vela/types/constants"
+)
+
+// CountServices gets the count of all services from the database.
+func (e *engine) CountServices() (int64, error) {
+ e.logger.Tracef("getting count of all services 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.TableService).
+ Count(&s).
+ Error
+
+ return s, err
+}
diff --git a/database/service/count_build.go b/database/service/count_build.go
new file mode 100644
index 000000000..0cd773570
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CountServicesForBuild gets the count of services by build ID from the database.
+func (e *engine) CountServicesForBuild(b *library.Build, filters map[string]interface{}) (int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ }).Tracef("getting count of services 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.TableService).
+ Where("build_id = ?", b.GetID()).
+ Where(filters).
+ Count(&s).
+ Error
+
+ return s, err
+}
diff --git a/database/service/count_build_test.go b/database/service/count_build_test.go
new file mode 100644
index 000000000..160dfa5f8
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestService_Engine_CountServicesForBuild(t *testing.T) {
+ // setup types
+ _build := testBuild()
+ _build.SetID(1)
+ _build.SetRepoID(1)
+ _build.SetNumber(1)
+
+ _serviceOne := testService()
+ _serviceOne.SetID(1)
+ _serviceOne.SetRepoID(1)
+ _serviceOne.SetBuildID(1)
+ _serviceOne.SetNumber(1)
+ _serviceOne.SetName("foo")
+ _serviceOne.SetImage("bar")
+
+ _serviceTwo := testService()
+ _serviceTwo.SetID(2)
+ _serviceTwo.SetRepoID(1)
+ _serviceTwo.SetBuildID(2)
+ _serviceTwo.SetNumber(1)
+ _serviceTwo.SetName("foo")
+ _serviceTwo.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 "services" WHERE build_id = $1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateService(_serviceOne)
+ if err != nil {
+ t.Errorf("unable to create test service for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateService(_serviceTwo)
+ if err != nil {
+ t.Errorf("unable to create test service 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.CountServicesForBuild(_build, filters)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountServicesForBuild for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountServicesForBuild for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountServicesForBuild for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/service/count_test.go b/database/service/count_test.go
new file mode 100644
index 000000000..4152c18e9
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestService_Engine_CountServices(t *testing.T) {
+ // setup types
+ _serviceOne := testService()
+ _serviceOne.SetID(1)
+ _serviceOne.SetRepoID(1)
+ _serviceOne.SetBuildID(1)
+ _serviceOne.SetNumber(1)
+ _serviceOne.SetName("foo")
+ _serviceOne.SetImage("bar")
+
+ _serviceTwo := testService()
+ _serviceTwo.SetID(2)
+ _serviceTwo.SetRepoID(1)
+ _serviceTwo.SetBuildID(2)
+ _serviceTwo.SetNumber(1)
+ _serviceTwo.SetName("foo")
+ _serviceTwo.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 "services"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateService(_serviceOne)
+ if err != nil {
+ t.Errorf("unable to create test service for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateService(_serviceTwo)
+ if err != nil {
+ t.Errorf("unable to create test service 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.CountServices()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountServices for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountServices for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountServices for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/service/create.go b/database/service/create.go
new file mode 100644
index 000000000..f748be077
--- /dev/null
+++ b/database/service/create.go
@@ -0,0 +1,37 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package service
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CreateService creates a new service in the database.
+func (e *engine) CreateService(s *library.Service) (*library.Service, error) {
+ e.logger.WithFields(logrus.Fields{
+ "service": s.GetNumber(),
+ }).Tracef("creating service %s in the database", s.GetName())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#ServiceFromLibrary
+ service := database.ServiceFromLibrary(s)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Service.Validate
+ err := service.Validate()
+ if err != nil {
+ return nil, err
+ }
+
+ // send query to the database
+ result := e.client.Table(constants.TableService).Create(service)
+
+ return service.ToLibrary(), result.Error
+}
diff --git a/database/service/create_test.go b/database/service/create_test.go
new file mode 100644
index 000000000..26771f31c
--- /dev/null
+++ b/database/service/create_test.go
@@ -0,0 +1,80 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package service
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestService_Engine_CreateService(t *testing.T) {
+ // setup types
+ _service := testService()
+ _service.SetID(1)
+ _service.SetRepoID(1)
+ _service.SetBuildID(1)
+ _service.SetNumber(1)
+ _service.SetName("foo")
+ _service.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 "services"
+("build_id","repo_id","number","name","image","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) RETURNING "id"`).
+ WithArgs(1, 1, 1, "foo", "bar", 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) {
+ got, err := test.database.CreateService(_service)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateService for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateService for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, _service) {
+ t.Errorf("CreateService for %s returned %s, want %s", test.name, got, _service)
+ }
+ })
+ }
+}
diff --git a/database/service/delete.go b/database/service/delete.go
new file mode 100644
index 000000000..86c0c21b2
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// DeleteService deletes an existing service from the database.
+func (e *engine) DeleteService(s *library.Service) error {
+ e.logger.WithFields(logrus.Fields{
+ "service": s.GetNumber(),
+ }).Tracef("deleting service %s from the database", s.GetName())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#ServiceFromLibrary
+ service := database.ServiceFromLibrary(s)
+
+ // send query to the database
+ return e.client.
+ Table(constants.TableService).
+ Delete(service).
+ Error
+}
diff --git a/database/service/delete_test.go b/database/service/delete_test.go
new file mode 100644
index 000000000..04d08fdc8
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestService_Engine_DeleteService(t *testing.T) {
+ // setup types
+ _service := testService()
+ _service.SetID(1)
+ _service.SetRepoID(1)
+ _service.SetBuildID(1)
+ _service.SetNumber(1)
+ _service.SetName("foo")
+ _service.SetImage("bar")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`DELETE FROM "services" WHERE "services"."id" = $1`).
+ WithArgs(1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateService(_service)
+ if err != nil {
+ t.Errorf("unable to create test service 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.DeleteService(_service)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("DeleteService for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("DeleteService for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/service/get.go b/database/service/get.go
new file mode 100644
index 000000000..7678168b2
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// GetService gets a service by ID from the database.
+func (e *engine) GetService(id int64) (*library.Service, error) {
+ e.logger.Tracef("getting service %d from the database", id)
+
+ // variable to store query results
+ s := new(database.Service)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableService).
+ Where("id = ?", id).
+ Take(s).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // return the service
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Service.ToLibrary
+ return s.ToLibrary(), nil
+}
diff --git a/database/service/get_build.go b/database/service/get_build.go
new file mode 100644
index 000000000..5321cc728
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// GetServiceForBuild gets a service by number and build ID from the database.
+func (e *engine) GetServiceForBuild(b *library.Build, number int) (*library.Service, error) {
+ e.logger.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ "service": number,
+ }).Tracef("getting service %d from the database", number)
+
+ // variable to store query results
+ s := new(database.Service)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableService).
+ Where("build_id = ?", b.GetID()).
+ Where("number = ?", number).
+ Take(s).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // return the service
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Service.ToLibrary
+ return s.ToLibrary(), nil
+}
diff --git a/database/service/get_build_test.go b/database/service/get_build_test.go
new file mode 100644
index 000000000..4b6a629de
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestService_Engine_GetServiceForBuild(t *testing.T) {
+ // setup types
+ _build := testBuild()
+ _build.SetID(1)
+ _build.SetRepoID(1)
+ _build.SetNumber(1)
+
+ _service := testService()
+ _service.SetID(1)
+ _service.SetRepoID(1)
+ _service.SetBuildID(1)
+ _service.SetNumber(1)
+ _service.SetName("foo")
+ _service.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 "services" 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.CreateService(_service)
+ if err != nil {
+ t.Errorf("unable to create test service for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Service
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _service,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _service,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetServiceForBuild(_build, 1)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetServiceForBuild for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetServiceForBuild for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetServiceForBuild for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/service/get_test.go b/database/service/get_test.go
new file mode 100644
index 000000000..1c2734b36
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestService_Engine_GetService(t *testing.T) {
+ // setup types
+ _service := testService()
+ _service.SetID(1)
+ _service.SetRepoID(1)
+ _service.SetBuildID(1)
+ _service.SetNumber(1)
+ _service.SetName("foo")
+ _service.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 "services" WHERE id = $1 LIMIT 1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateService(_service)
+ if err != nil {
+ t.Errorf("unable to create test service for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Service
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _service,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _service,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetService(1)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetService for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetService for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetService for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/service/interface.go b/database/service/interface.go
new file mode 100644
index 000000000..0a05c626d
--- /dev/null
+++ b/database/service/interface.go
@@ -0,0 +1,51 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package service
+
+import (
+ "github.com/go-vela/types/library"
+)
+
+// ServiceInterface represents the Vela interface for service
+// functions with the supported Database backends.
+//
+//nolint:revive // ignore name stutter
+type ServiceInterface interface {
+ // Service Data Definition Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_definition_language
+
+ // CreateServiceTable defines a function that creates the services table.
+ CreateServiceTable(string) error
+
+ // Service Data Manipulation Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_manipulation_language
+
+ // CleanServices defines a function that sets running or pending services to error status before a given created time.
+ CleanServices(string, int64) (int64, error)
+ // CountServices defines a function that gets the count of all services.
+ CountServices() (int64, error)
+ // CountServicesForBuild defines a function that gets the count of services by build ID.
+ CountServicesForBuild(*library.Build, map[string]interface{}) (int64, error)
+ // CreateService defines a function that creates a new service.
+ CreateService(*library.Service) (*library.Service, error)
+ // DeleteService defines a function that deletes an existing service.
+ DeleteService(*library.Service) error
+ // GetService defines a function that gets a service by ID.
+ GetService(int64) (*library.Service, error)
+ // GetServiceForBuild defines a function that gets a service by number and build ID.
+ GetServiceForBuild(*library.Build, int) (*library.Service, error)
+ // ListServices defines a function that gets a list of all services.
+ ListServices() ([]*library.Service, error)
+ // ListServicesForBuild defines a function that gets a list of services by build ID.
+ ListServicesForBuild(*library.Build, map[string]interface{}, int, int) ([]*library.Service, int64, error)
+ // ListServiceImageCount defines a function that gets a list of all service images and the count of their occurrence.
+ ListServiceImageCount() (map[string]float64, error)
+ // ListServiceStatusCount defines a function that gets a list of all service statuses and the count of their occurrence.
+ ListServiceStatusCount() (map[string]float64, error)
+ // UpdateService defines a function that updates an existing service.
+ UpdateService(*library.Service) (*library.Service, error)
+}
diff --git a/database/service/list.go b/database/service/list.go
new file mode 100644
index 000000000..c7b1b6b13
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// ListServices gets a list of all services from the database.
+func (e *engine) ListServices() ([]*library.Service, error) {
+ e.logger.Trace("listing all services from the database")
+
+ // variables to store query results and return value
+ count := int64(0)
+ w := new([]database.Service)
+ services := []*library.Service{}
+
+ // count the results
+ count, err := e.CountServices()
+ if err != nil {
+ return nil, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return services, nil
+ }
+
+ // send query to the database and store result in variable
+ err = e.client.
+ Table(constants.TableService).
+ Find(&w).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // iterate through all query results
+ for _, service := range *w {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := service
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Service.ToLibrary
+ services = append(services, tmp.ToLibrary())
+ }
+
+ return services, nil
+}
diff --git a/database/service/list_build.go b/database/service/list_build.go
new file mode 100644
index 000000000..caffb2d5c
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// ListServicesForBuild gets a list of all services from the database.
+func (e *engine) ListServicesForBuild(b *library.Build, filters map[string]interface{}, page int, perPage int) ([]*library.Service, int64, error) {
+ e.logger.WithFields(logrus.Fields{
+ "build": b.GetNumber(),
+ }).Tracef("listing services for build %d from the database", b.GetNumber())
+
+ // variables to store query results and return value
+ count := int64(0)
+ s := new([]database.Service)
+ services := []*library.Service{}
+
+ // count the results
+ count, err := e.CountServicesForBuild(b, filters)
+ if err != nil {
+ return services, 0, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return services, 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.TableService).
+ 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 _, service := range *s {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := service
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Service.ToLibrary
+ services = append(services, tmp.ToLibrary())
+ }
+
+ return services, count, nil
+}
diff --git a/database/service/list_build_test.go b/database/service/list_build_test.go
new file mode 100644
index 000000000..1f0a860f4
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestService_Engine_ListServicesForBuild(t *testing.T) {
+ // setup types
+ _build := testBuild()
+ _build.SetID(1)
+ _build.SetRepoID(1)
+ _build.SetNumber(1)
+
+ _serviceOne := testService()
+ _serviceOne.SetID(1)
+ _serviceOne.SetRepoID(1)
+ _serviceOne.SetBuildID(1)
+ _serviceOne.SetNumber(1)
+ _serviceOne.SetName("foo")
+ _serviceOne.SetImage("bar")
+
+ _serviceTwo := testService()
+ _serviceTwo.SetID(2)
+ _serviceTwo.SetRepoID(1)
+ _serviceTwo.SetBuildID(1)
+ _serviceTwo.SetNumber(2)
+ _serviceTwo.SetName("foo")
+ _serviceTwo.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 "services" 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 "services" 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.CreateService(_serviceOne)
+ if err != nil {
+ t.Errorf("unable to create test service for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateService(_serviceTwo)
+ if err != nil {
+ t.Errorf("unable to create test service for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Service
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Service{_serviceTwo, _serviceOne},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.Service{_serviceTwo, _serviceOne},
+ },
+ }
+
+ filters := map[string]interface{}{}
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, _, err := test.database.ListServicesForBuild(_build, filters, 1, 10)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListServicesForBuild for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListServicesForBuild for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListServicesForBuild for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/service/list_image.go b/database/service/list_image.go
new file mode 100644
index 000000000..6625420dc
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "database/sql"
+
+ "github.com/go-vela/types/constants"
+)
+
+// ListServiceImageCount gets a list of all service images and the count of their occurrence from the database.
+func (e *engine) ListServiceImageCount() (map[string]float64, error) {
+ e.logger.Tracef("getting count of all images for services 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.TableService).
+ 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/service/list_image_test.go b/database/service/list_image_test.go
new file mode 100644
index 000000000..8f0d588d0
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestService_Engine_ListServiceImageCount(t *testing.T) {
+ // setup types
+ _serviceOne := testService()
+ _serviceOne.SetID(1)
+ _serviceOne.SetRepoID(1)
+ _serviceOne.SetBuildID(1)
+ _serviceOne.SetNumber(1)
+ _serviceOne.SetName("foo")
+ _serviceOne.SetImage("bar")
+
+ _serviceTwo := testService()
+ _serviceTwo.SetID(2)
+ _serviceTwo.SetRepoID(1)
+ _serviceTwo.SetBuildID(1)
+ _serviceTwo.SetNumber(2)
+ _serviceTwo.SetName("foo")
+ _serviceTwo.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 "services" GROUP BY "image"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateService(_serviceOne)
+ if err != nil {
+ t.Errorf("unable to create test service for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateService(_serviceTwo)
+ if err != nil {
+ t.Errorf("unable to create test service 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.ListServiceImageCount()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListServiceImageCount for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListServiceImageCount for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListServiceImageCount for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/service/list_status.go b/database/service/list_status.go
new file mode 100644
index 000000000..01aedeac1
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "database/sql"
+
+ "github.com/go-vela/types/constants"
+)
+
+// ListServiceStatusCount gets a list of all service statuses and the count of their occurrence from the database.
+func (e *engine) ListServiceStatusCount() (map[string]float64, error) {
+ e.logger.Tracef("getting count of all statuses for services 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.TableService).
+ 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/service/list_status_test.go b/database/service/list_status_test.go
new file mode 100644
index 000000000..d1cb46649
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestService_Engine_ListServiceStatusCount(t *testing.T) {
+ // setup types
+ _serviceOne := testService()
+ _serviceOne.SetID(1)
+ _serviceOne.SetRepoID(1)
+ _serviceOne.SetBuildID(1)
+ _serviceOne.SetNumber(1)
+ _serviceOne.SetName("foo")
+ _serviceOne.SetImage("bar")
+
+ _serviceTwo := testService()
+ _serviceTwo.SetID(2)
+ _serviceTwo.SetRepoID(1)
+ _serviceTwo.SetBuildID(1)
+ _serviceTwo.SetNumber(2)
+ _serviceTwo.SetName("foo")
+ _serviceTwo.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 "services" GROUP BY "status"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateService(_serviceOne)
+ if err != nil {
+ t.Errorf("unable to create test service for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateService(_serviceTwo)
+ if err != nil {
+ t.Errorf("unable to create test service 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.ListServiceStatusCount()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListServiceStatusCount for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListServiceStatusCount for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListServiceStatusCount for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/service/list_test.go b/database/service/list_test.go
new file mode 100644
index 000000000..a7351a8b6
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestService_Engine_ListServices(t *testing.T) {
+ // setup types
+ _serviceOne := testService()
+ _serviceOne.SetID(1)
+ _serviceOne.SetRepoID(1)
+ _serviceOne.SetBuildID(1)
+ _serviceOne.SetNumber(1)
+ _serviceOne.SetName("foo")
+ _serviceOne.SetImage("bar")
+
+ _serviceTwo := testService()
+ _serviceTwo.SetID(2)
+ _serviceTwo.SetRepoID(1)
+ _serviceTwo.SetBuildID(2)
+ _serviceTwo.SetNumber(1)
+ _serviceTwo.SetName("bar")
+ _serviceTwo.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 "services"`).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 "services"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ _, err := _sqlite.CreateService(_serviceOne)
+ if err != nil {
+ t.Errorf("unable to create test service for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateService(_serviceTwo)
+ if err != nil {
+ t.Errorf("unable to create test service for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Service
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Service{_serviceOne, _serviceTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.Service{_serviceOne, _serviceTwo},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.ListServices()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListServices for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListServices for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListServices for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/service/opts.go b/database/service/opts.go
new file mode 100644
index 000000000..2201b1abd
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+// EngineOpt represents a configuration option to initialize the database engine for Services.
+type EngineOpt func(*engine) error
+
+// WithClient sets the gorm.io/gorm client in the database engine for Services.
+func WithClient(client *gorm.DB) EngineOpt {
+ return func(e *engine) error {
+ // set the gorm.io/gorm client in the service engine
+ e.client = client
+
+ return nil
+ }
+}
+
+// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for Services.
+func WithLogger(logger *logrus.Entry) EngineOpt {
+ return func(e *engine) error {
+ // set the github.com/sirupsen/logrus logger in the service engine
+ e.logger = logger
+
+ return nil
+ }
+}
+
+// WithSkipCreation sets the skip creation logic in the database engine for Services.
+func WithSkipCreation(skipCreation bool) EngineOpt {
+ return func(e *engine) error {
+ // set to skip creating tables and indexes in the service engine
+ e.config.SkipCreation = skipCreation
+
+ return nil
+ }
+}
diff --git a/database/service/opts_test.go b/database/service/opts_test.go
new file mode 100644
index 000000000..62ce5adf8
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+func TestService_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 TestService_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 TestService_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/service/service.go b/database/service/service.go
new file mode 100644
index 000000000..548164c0f
--- /dev/null
+++ b/database/service/service.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 service
+
+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 ServiceInterface interface.
+ config struct {
+ // specifies to skip creating tables and indexes for the Service engine
+ SkipCreation bool
+ }
+
+ // engine represents the service functionality that implements the ServiceInterface interface.
+ engine struct {
+ // engine configuration settings used in service functions
+ config *config
+
+ // gorm.io/gorm database client used in service functions
+ //
+ // https://pkg.go.dev/gorm.io/gorm#DB
+ client *gorm.DB
+
+ // sirupsen/logrus logger used in service functions
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus#Entry
+ logger *logrus.Entry
+ }
+)
+
+// New creates and returns a Vela service for integrating with services in the database.
+//
+//nolint:revive // ignore returning unexported engine
+func New(opts ...EngineOpt) (*engine, error) {
+ // create new Service 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 service database objects
+ if e.config.SkipCreation {
+ e.logger.Warning("skipping creation of services table in the database")
+
+ return e, nil
+ }
+
+ // create the services table
+ err := e.CreateServiceTable(e.client.Config.Dialector.Name())
+ if err != nil {
+ return nil, fmt.Errorf("unable to create %s table: %w", constants.TableService, err)
+ }
+
+ return e, nil
+}
diff --git a/database/service/service_test.go b/database/service/service_test.go
new file mode 100644
index 000000000..c9d658769
--- /dev/null
+++ b/database/service/service_test.go
@@ -0,0 +1,246 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package service
+
+import (
+ "database/sql/driver"
+ "reflect"
+ "testing"
+ "time"
+
+ "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 TestService_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 service 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 service 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),
+ }
+}
+
+// testService is a test helper function to create a library
+// Service type with all fields set to their zero values.
+func testService() *library.Service {
+ return &library.Service{
+ ID: new(int64),
+ BuildID: new(int64),
+ RepoID: new(int64),
+ Number: new(int),
+ Name: new(string),
+ Image: 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),
+ }
+}
+
+// This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values
+// that are otherwise not easily compared. These typically would be values generated
+// before adding or updating them in the database.
+//
+// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime
+
+// NowTimestamp is used to test whether timestamps get updated correctly to the current time with lenience.
+type NowTimestamp struct{}
+
+// Match satisfies sqlmock.Argument interface.
+func (t NowTimestamp) Match(v driver.Value) bool {
+ ts, ok := v.(int64)
+ if !ok {
+ return false
+ }
+ now := time.Now().Unix()
+
+ return now-ts < 10
+}
diff --git a/database/service/table.go b/database/service/table.go
new file mode 100644
index 000000000..48f8dc712
--- /dev/null
+++ b/database/service/table.go
@@ -0,0 +1,76 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package service
+
+import (
+ "github.com/go-vela/types/constants"
+)
+
+const (
+ // CreatePostgresTable represents a query to create the Postgres services table.
+ CreatePostgresTable = `
+CREATE TABLE
+IF NOT EXISTS
+services (
+ id SERIAL PRIMARY KEY,
+ repo_id INTEGER,
+ build_id INTEGER,
+ number INTEGER,
+ name VARCHAR(250),
+ image VARCHAR(500),
+ 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 services table.
+ CreateSqliteTable = `
+CREATE TABLE
+IF NOT EXISTS
+services (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ repo_id INTEGER,
+ build_id INTEGER,
+ number INTEGER,
+ name TEXT,
+ image TEXT,
+ status TEXT,
+ error TEXT,
+ exit_code INTEGER,
+ created INTEGER,
+ started INTEGER,
+ finished INTEGER,
+ host TEXT,
+ runtime TEXT,
+ distribution TEXT,
+ UNIQUE(build_id, number)
+);
+`
+)
+
+// CreateServiceTable creates the services table in the database.
+func (e *engine) CreateServiceTable(driver string) error {
+ e.logger.Tracef("creating services table in the database")
+
+ // handle the driver provided to create the table
+ switch driver {
+ case constants.DriverPostgres:
+ // create the services table for Postgres
+ return e.client.Exec(CreatePostgresTable).Error
+ case constants.DriverSqlite:
+ fallthrough
+ default:
+ // create the services table for Sqlite
+ return e.client.Exec(CreateSqliteTable).Error
+ }
+}
diff --git a/database/service/table_test.go b/database/service/table_test.go
new file mode 100644
index 000000000..1b4d1b7de
--- /dev/null
+++ b/database/service/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 service
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestService_Engine_CreateServiceTable(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.CreateServiceTable(test.name)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateServiceTable for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateServiceTable for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/service/update.go b/database/service/update.go
new file mode 100644
index 000000000..d9b0bd595
--- /dev/null
+++ b/database/service/update.go
@@ -0,0 +1,37 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package service
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// UpdateService updates an existing service in the database.
+func (e *engine) UpdateService(s *library.Service) (*library.Service, error) {
+ e.logger.WithFields(logrus.Fields{
+ "service": s.GetNumber(),
+ }).Tracef("updating service %s in the database", s.GetName())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#ServiceFromLibrary
+ service := database.ServiceFromLibrary(s)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Service.Validate
+ err := service.Validate()
+ if err != nil {
+ return nil, err
+ }
+
+ // send query to the database
+ result := e.client.Table(constants.TableService).Save(service)
+
+ return service.ToLibrary(), result.Error
+}
diff --git a/database/service/update_test.go b/database/service/update_test.go
new file mode 100644
index 000000000..12979dce9
--- /dev/null
+++ b/database/service/update_test.go
@@ -0,0 +1,80 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package service
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestService_Engine_UpdateService(t *testing.T) {
+ // setup types
+ _service := testService()
+ _service.SetID(1)
+ _service.SetRepoID(1)
+ _service.SetBuildID(1)
+ _service.SetNumber(1)
+ _service.SetName("foo")
+ _service.SetImage("bar")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`UPDATE "services" SET "build_id"=$1,"repo_id"=$2,"number"=$3,"name"=$4,"image"=$5,"status"=$6,"error"=$7,"exit_code"=$8,"created"=$9,"started"=$10,"finished"=$11,"host"=$12,"runtime"=$13,"distribution"=$14 WHERE "id" = $15`).
+ WithArgs(1, 1, 1, "foo", "bar", 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.CreateService(_service)
+ if err != nil {
+ t.Errorf("unable to create test service 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) {
+ got, err := test.database.UpdateService(_service)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("UpdateService for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("UpdateService for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, _service) {
+ t.Errorf("UpdateService for %s returned %s, want %s", test.name, got, _service)
+ }
+ })
+ }
+}
diff --git a/database/setup.go b/database/setup.go
deleted file mode 100644
index e8f3b0516..000000000
--- a/database/setup.go
+++ /dev/null
@@ -1,144 +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 database
-
-import (
- "fmt"
- "strings"
- "time"
-
- "github.com/go-vela/server/database/postgres"
- "github.com/go-vela/server/database/sqlite"
- "github.com/go-vela/types/constants"
- "github.com/sirupsen/logrus"
-)
-
-// Setup represents the configuration necessary for
-// creating a Vela service capable of integrating
-// with a configured database system.
-type Setup struct {
- // Database Configuration
-
- // specifies the driver to use for the database client
- Driver string
- // specifies the address to use for the database client
- Address string
- // specifies the level of compression to use for the database client
- CompressionLevel int
- // specifies the connection duration to use for the database client
- ConnectionLife time.Duration
- // specifies the maximum idle connections for the database client
- ConnectionIdle int
- // specifies the maximum open connections for the database client
- ConnectionOpen int
- // specifies the encryption key to use for the database client
- EncryptionKey string
- // specifies to skip creating tables and indexes for the database client
- SkipCreation bool
-}
-
-// Postgres creates and returns a Vela service capable of
-// integrating with a Postgres database system.
-func (s *Setup) Postgres() (Service, error) {
- logrus.Trace("creating postgres database client from setup")
-
- // create new Postgres database service
- //
- // https://pkg.go.dev/github.com/go-vela/server/database/postgres?tab=doc#New
- return postgres.New(
- postgres.WithAddress(s.Address),
- postgres.WithCompressionLevel(s.CompressionLevel),
- postgres.WithConnectionLife(s.ConnectionLife),
- postgres.WithConnectionIdle(s.ConnectionIdle),
- postgres.WithConnectionOpen(s.ConnectionOpen),
- postgres.WithEncryptionKey(s.EncryptionKey),
- postgres.WithSkipCreation(s.SkipCreation),
- )
-}
-
-// Sqlite creates and returns a Vela service capable of
-// integrating with a Sqlite database system.
-func (s *Setup) Sqlite() (Service, error) {
- logrus.Trace("creating sqlite database client from setup")
-
- // create new Sqlite database service
- //
- // https://pkg.go.dev/github.com/go-vela/server/database/sqlite?tab=doc#New
- return sqlite.New(
- sqlite.WithAddress(s.Address),
- sqlite.WithCompressionLevel(s.CompressionLevel),
- sqlite.WithConnectionLife(s.ConnectionLife),
- sqlite.WithConnectionIdle(s.ConnectionIdle),
- sqlite.WithConnectionOpen(s.ConnectionOpen),
- sqlite.WithEncryptionKey(s.EncryptionKey),
- sqlite.WithSkipCreation(s.SkipCreation),
- )
-}
-
-// Validate verifies the necessary fields for the
-// provided configuration are populated correctly.
-func (s *Setup) Validate() error {
- logrus.Trace("validating database setup for client")
-
- // verify a database driver was provided
- if len(s.Driver) == 0 {
- return fmt.Errorf("no database driver provided")
- }
-
- // verify a database address was provided
- if len(s.Address) == 0 {
- return fmt.Errorf("no database address provided")
- }
-
- // check if the database address has a trailing slash
- if strings.HasSuffix(s.Address, "/") {
- return fmt.Errorf("database address must not have trailing slash")
- }
-
- // verify a database encryption key was provided
- if len(s.EncryptionKey) == 0 {
- return fmt.Errorf("no database encryption key provided")
- }
-
- // verify the database compression level is valid
- switch s.CompressionLevel {
- case constants.CompressionNegOne:
- fallthrough
- case constants.CompressionZero:
- fallthrough
- case constants.CompressionOne:
- fallthrough
- case constants.CompressionTwo:
- fallthrough
- case constants.CompressionThree:
- fallthrough
- case constants.CompressionFour:
- fallthrough
- case constants.CompressionFive:
- fallthrough
- case constants.CompressionSix:
- fallthrough
- case constants.CompressionSeven:
- fallthrough
- case constants.CompressionEight:
- fallthrough
- case constants.CompressionNine:
- break
- default:
- // nolint:lll // ignoring line length due to error message
- return fmt.Errorf("database compression level must be between %d and %d - provided level: %d", constants.CompressionNegOne, constants.CompressionNine, s.CompressionLevel)
- }
-
- // enforce AES-256 for the encryption key - explicitly check for 32 characters in the key
- //
- // nolint: gomnd // ignore magic number
- if len(s.EncryptionKey) != 32 {
- // nolint: lll // ignore long line length due to long error message
- return fmt.Errorf("database encryption key must have 32 characters - provided length: %d", len(s.EncryptionKey))
- }
-
- // setup is valid
- return nil
-}
diff --git a/database/sqlite/build.go b/database/sqlite/build.go
deleted file mode 100644
index 5a3347b09..000000000
--- a/database/sqlite/build.go
+++ /dev/null
@@ -1,176 +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"
-)
-
-// GetBuild gets a build by number and repo ID from the database.
-//
-// nolint: dupl // ignore similar code with hook
-func (c *client) GetBuild(number int, r *library.Repo) (*library.Build, error) {
- c.Logger.WithFields(logrus.Fields{
- "build": number,
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("getting build %s/%d from the database", r.GetFullName(), number)
-
- // variable to store query results
- b := new(database.Build)
-
- // send query to the database and store result in variable
- result := c.Sqlite.
- Table(constants.TableBuild).
- Raw(dml.SelectRepoBuild, r.GetID(), number).
- Scan(b)
-
- // 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 b.ToLibrary(), result.Error
-}
-
-// GetLastBuild gets the last build by repo ID from the database.
-func (c *client) GetLastBuild(r *library.Repo) (*library.Build, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("getting last build for repo %s from the database", r.GetFullName())
-
- // variable to store query results
- b := new(database.Build)
-
- // send query to the database and store result in variable
- result := c.Sqlite.
- Table(constants.TableBuild).
- Raw(dml.SelectLastRepoBuild, r.GetID()).
- Scan(b)
-
- // 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 {
- // the record will not exist if it's a new repo
- return nil, nil
- }
-
- return b.ToLibrary(), result.Error
-}
-
-// GetLastBuildByBranch gets the last build by repo ID and branch from the database.
-func (c *client) GetLastBuildByBranch(r *library.Repo, branch string) (*library.Build, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("getting last build for repo %s on branch %s from the database", r.GetFullName(), branch)
-
- // variable to store query results
- b := new(database.Build)
-
- // send query to the database and store result in variable
- result := c.Sqlite.
- Table(constants.TableBuild).
- Raw(dml.SelectLastRepoBuildByBranch, r.GetID(), branch).
- Scan(b)
-
- // 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 {
- // the record will not exist if it's a new repo
- return nil, nil
- }
-
- return b.ToLibrary(), result.Error
-}
-
-// GetPendingAndRunningBuilds returns the list of pending
-// and running builds within the given timeframe.
-func (c *client) GetPendingAndRunningBuilds(after string) ([]*library.BuildQueue, error) {
- c.Logger.Trace("getting pending and running builds from the database")
-
- // variable to store query results
- b := new([]database.BuildQueue)
-
- // send query to the database and store result in variable
- result := c.Sqlite.
- Table(constants.TableBuild).
- Raw(dml.SelectPendingAndRunningBuilds, after).
- Scan(b)
-
- // variable we want to return
- builds := []*library.BuildQueue{}
-
- // iterate through all query results
- for _, build := range *b {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := build
-
- // convert query result to library type
- builds = append(builds, tmp.ToLibrary())
- }
-
- return builds, result.Error
-}
-
-// CreateBuild creates a new build in the database.
-func (c *client) CreateBuild(b *library.Build) error {
- c.Logger.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- }).Tracef("creating build %d in the database", b.GetNumber())
-
- // cast to database type
- build := database.BuildFromLibrary(b)
-
- // validate the necessary fields are populated
- err := build.Validate()
- if err != nil {
- return err
- }
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableBuild).
- Create(build.Crop()).Error
-}
-
-// UpdateBuild updates a build in the database.
-func (c *client) UpdateBuild(b *library.Build) error {
- c.Logger.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- }).Tracef("updating build %d in the database", b.GetNumber())
-
- // cast to database type
- build := database.BuildFromLibrary(b)
-
- // validate the necessary fields are populated
- err := build.Validate()
- if err != nil {
- return err
- }
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableBuild).
- Save(build.Crop()).Error
-}
-
-// DeleteBuild deletes a build by unique ID from the database.
-func (c *client) DeleteBuild(id int64) error {
- c.Logger.Tracef("deleting build %d in the database", id)
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableBuild).
- Exec(dml.DeleteBuild, id).Error
-}
diff --git a/database/sqlite/build_count.go b/database/sqlite/build_count.go
deleted file mode 100644
index 2a363ddcf..000000000
--- a/database/sqlite/build_count.go
+++ /dev/null
@@ -1,83 +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"
-)
-
-// GetBuildCount gets a count of all builds from the database.
-func (c *client) GetBuildCount() (int64, error) {
- c.Logger.Trace("getting count of builds from the database")
-
- // variable to store query results
- var b int64
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableBuild).
- Raw(dml.SelectBuildsCount).
- Pluck("count", &b).Error
-
- return b, err
-}
-
-// GetBuildCountByStatus gets a count of all builds by status from the database.
-func (c *client) GetBuildCountByStatus(status string) (int64, error) {
- c.Logger.Tracef("getting count of builds by status %s from the database", status)
-
- // variable to store query results
- var b int64
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableBuild).
- Raw(dml.SelectBuildsCountByStatus, status).
- Pluck("count", &b).Error
-
- return b, err
-}
-
-// GetOrgBuildCount gets the count of all builds by repo ID from the database.
-func (c *client) GetOrgBuildCount(org string, filters map[string]interface{}) (int64, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": org,
- }).Tracef("getting count of builds for org %s from the database", org)
-
- // variable to store query results
- var b int64
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableBuild).
- Joins("JOIN repos ON builds.repo_id = repos.id and repos.org = ?", org).
- Where(filters).
- Count(&b).Error
-
- return b, err
-}
-
-// GetRepoBuildCount gets the count of all builds by repo ID from the database.
-func (c *client) GetRepoBuildCount(r *library.Repo, filters map[string]interface{}) (int64, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": r.GetOrg(),
- "name": r.GetName(),
- }).Tracef("getting count of builds for repo %s from the database", r.GetFullName())
-
- // variable to store query results
- var b int64
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableBuild).
- Where("repo_id = ?", r.GetID()).
- Where(filters).
- Count(&b).Error
-
- return b, err
-}
diff --git a/database/sqlite/build_count_test.go b/database/sqlite/build_count_test.go
deleted file mode 100644
index 9740aa59d..000000000
--- a/database/sqlite/build_count_test.go
+++ /dev/null
@@ -1,434 +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 build table
- err = _database.Sqlite.Exec(ddl.CreateBuildTable).Error
- if err != nil {
- log.Fatalf("unable to create %s table: %v", constants.TableBuild, err)
- }
-}
-
-func TestSqlite_Client_GetBuildCount(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetDeployPayload(nil)
-
- // 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 builds table
- defer _database.Sqlite.Exec("delete from builds;")
-
- // create the builds in the database
- err := _database.CreateBuild(_buildOne)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
-
- err = _database.CreateBuild(_buildTwo)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
-
- got, err := _database.GetBuildCount()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetBuildCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetBuildCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetBuildCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetBuildCountByStatus(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetStatus("running")
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetStatus("running")
- _buildTwo.SetDeployPayload(nil)
-
- // 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 builds table
- defer _database.Sqlite.Exec("delete from builds;")
-
- // create the builds in the database
- err := _database.CreateBuild(_buildOne)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
-
- err = _database.CreateBuild(_buildTwo)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
-
- got, err := _database.GetBuildCountByStatus("running")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetBuildCountByStatus should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetBuildCountByStatus returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetBuildCountByStatus is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetOrgBuildCount(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetDeployPayload(nil)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 2,
- },
- }
-
- filters := map[string]interface{}{}
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- // create the repo in the database
- err := _database.CreateRepo(_repo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
-
- // defer cleanup of the builds table
- defer _database.Sqlite.Exec("delete from builds;")
-
- // create the builds in the database
- err = _database.CreateBuild(_buildOne)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
-
- err = _database.CreateBuild(_buildTwo)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
-
- got, err := _database.GetOrgBuildCount("foo", filters)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetOrgBuildCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetOrgBuildCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetOrgBuildCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetOrgBuildCountByEvent(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetEvent("push")
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetEvent("push")
- _buildTwo.SetDeployPayload(nil)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 2,
- },
- }
-
- filters := map[string]interface{}{
- "event": "push",
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- // create the repo in the database
- err := _database.CreateRepo(_repo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
-
- // defer cleanup of the builds table
- defer _database.Sqlite.Exec("delete from builds;")
-
- // create the builds in the database
- err = _database.CreateBuild(_buildOne)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
-
- err = _database.CreateBuild(_buildTwo)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
-
- got, err := _database.GetOrgBuildCount("foo", filters)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetOrgBuildCountByEvent should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetOrgBuildCountByEvent returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetOrgBuildCountByEvent is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetRepoBuildCount(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetDeployPayload(nil)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 2,
- },
- }
-
- filters := map[string]interface{}{}
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- // create the repo in the database
- err := _database.CreateRepo(_repo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
-
- // defer cleanup of the builds table
- defer _database.Sqlite.Exec("delete from builds;")
-
- // create the builds in the database
- err = _database.CreateBuild(_buildOne)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
-
- err = _database.CreateBuild(_buildTwo)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
-
- got, err := _database.GetRepoBuildCount(_repo, filters)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepoBuildCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepoBuildCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepoBuildCount is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/sqlite/build_list.go b/database/sqlite/build_list.go
deleted file mode 100644
index e5ca79cc0..000000000
--- a/database/sqlite/build_list.go
+++ /dev/null
@@ -1,179 +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"
-)
-
-// GetBuildList gets a list of all builds from the database.
-func (c *client) GetBuildList() ([]*library.Build, error) {
- c.Logger.Trace("listing builds from the database")
-
- // variable to store query results
- b := new([]database.Build)
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableBuild).
- Raw(dml.ListBuilds).
- Scan(b).Error
-
- // variable we want to return
- builds := []*library.Build{}
- // iterate through all query results
- for _, build := range *b {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := build
-
- // convert query result to library type
- builds = append(builds, tmp.ToLibrary())
- }
-
- return builds, err
-}
-
-// GetDeploymentBuildList gets a list of all builds from the database.
-func (c *client) GetDeploymentBuildList(deployment string) ([]*library.Build, error) {
- c.Logger.WithFields(logrus.Fields{
- "deployment": deployment,
- }).Tracef("listing builds for deployment %s from the database", deployment)
-
- // variable to store query results
- b := new([]database.Build)
- filters := map[string]string{}
- if len(deployment) > 0 {
- filters["source"] = deployment
- }
- // send query to the database and store result in variable
- //
- // nolint: gomnd // ignore magic number
- err := c.Sqlite.
- Table(constants.TableBuild).
- Select("*").
- Where(filters).
- Limit(3).
- Order("number DESC").
- Scan(b).Error
-
- // variable we want to return
- builds := []*library.Build{}
- // iterate through all query results
- for _, build := range *b {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := build
-
- // convert query result to library type
- builds = append(builds, tmp.ToLibrary())
- }
-
- return builds, err
-}
-
-// GetOrgBuildList gets a list of all builds by org name from the database.
-//
-// nolint: lll // ignore long line length due to variable names
-func (c *client) GetOrgBuildList(org string, filters map[string]interface{}, page int, perPage int) ([]*library.Build, int64, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": org,
- }).Tracef("listing builds for org %s from the database", org)
-
- // variable to store query results
- b := new([]database.Build)
- builds := []*library.Build{}
- count := int64(0)
-
- // // count the results
- count, err := c.GetOrgBuildCount(org, filters)
-
- if err != nil {
- return builds, 0, err
- }
-
- // short-circuit if there are no results
- if count == 0 {
- return builds, 0, nil
- }
-
- // 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.TableBuild).
- Select("builds.*").
- Joins("JOIN repos ON builds.repo_id = repos.id AND repos.org = ?", org).
- Where(filters).
- Order("created DESC").
- Order("id").
- Limit(perPage).
- Offset(offset).
- Scan(b).Error
-
- // iterate through all query results
- for _, build := range *b {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := build
-
- // convert query result to library type
- builds = append(builds, tmp.ToLibrary())
- }
-
- return builds, count, err
-}
-
-// GetRepoBuildList gets a list of all builds by repo ID from the database.
-//
-// nolint: lll // ignore long line length due to variable names
-func (c *client) GetRepoBuildList(r *library.Repo, filters map[string]interface{}, page, perPage int) ([]*library.Build, int64, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("listing builds for repo %s from the database", r.GetFullName())
-
- // variable to store query results
- b := new([]database.Build)
- builds := []*library.Build{}
- count := int64(0)
-
- // count the results
- count, err := c.GetRepoBuildCount(r, filters)
- if err != nil {
- return builds, 0, err
- }
-
- // short-circuit if there are no results
- if count == 0 {
- return builds, 0, nil
- }
-
- // 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.TableBuild).
- Where("repo_id = ?", r.GetID()).
- Where(filters).
- Order("number DESC").
- Limit(perPage).
- Offset(offset).
- Scan(b).Error
-
- // iterate through all query results
- for _, build := range *b {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := build
-
- // convert query result to library type
- builds = append(builds, tmp.ToLibrary())
- }
-
- return builds, count, err
-}
diff --git a/database/sqlite/build_list_test.go b/database/sqlite/build_list_test.go
deleted file mode 100644
index 1a07c291c..000000000
--- a/database/sqlite/build_list_test.go
+++ /dev/null
@@ -1,523 +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 build table
- err = _database.Sqlite.Exec(ddl.CreateBuildTable).Error
- if err != nil {
- log.Fatalf("unable to create %s table: %v", constants.TableBuild, err)
- }
-}
-
-func TestSqlite_Client_GetBuildList(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetDeployPayload(nil)
-
- // 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.Build
- }{
- {
- failure: false,
- want: []*library.Build{_buildOne, _buildTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the builds table
- defer _database.Sqlite.Exec("delete from builds;")
-
- for _, build := range test.want {
- // create the build in the database
- err := _database.CreateBuild(build)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
- }
-
- got, err := _database.GetBuildList()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetBuildList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetBuildList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetBuildList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetDeploymentBuildList(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetEvent("deployment")
- _buildOne.SetDeployPayload(nil)
- _buildOne.SetSource("https://github.com/github/octocat/deployments/1")
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildOne.SetEvent("deployment")
- _buildTwo.SetDeployPayload(nil)
- _buildTwo.SetSource("https://github.com/github/octocat/deployments/1")
-
- // 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.Build
- }{
- {
- failure: false,
- want: []*library.Build{_buildTwo, _buildOne},
- },
- }
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- // defer cleanup of the builds table
- defer _database.Sqlite.Exec("delete from builds;")
-
- for _, build := range test.want {
- // create the build in the database
- err := _database.CreateBuild(build)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
- }
-
- got, err := _database.GetDeploymentBuildList("https://github.com/github/octocat/deployments/1")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetDeploymentBuildList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetDeploymentBuildList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetDeploymentBuildList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetOrgBuildList(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetEvent("push")
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildOne.SetEvent("deployment")
- _buildTwo.SetDeployPayload(nil)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Build
- }{
- {
- failure: false,
- want: []*library.Build{_buildOne, _buildTwo},
- },
- }
-
- filters := map[string]interface{}{}
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- // create the repo in the database
- err := _database.CreateRepo(_repo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
-
- // defer cleanup of the builds table
- defer _database.Sqlite.Exec("delete from builds;")
-
- for _, build := range test.want {
- // create the build in the database
- err := _database.CreateBuild(build)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
- }
-
- got, _, err := _database.GetOrgBuildList("foo", filters, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetOrgBuildList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetOrgBuildList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetOrgBuildList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetOrgBuildList_NonAdmin(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(2)
- _buildTwo.SetNumber(2)
- _buildTwo.SetDeployPayload(nil)
-
- _repoOne := testRepo()
- _repoOne.SetID(1)
- _repoOne.SetUserID(1)
- _repoOne.SetHash("baz")
- _repoOne.SetOrg("foo")
- _repoOne.SetName("bar")
- _repoOne.SetFullName("foo/bar")
- _repoOne.SetVisibility("public")
-
- _repoTwo := testRepo()
- _repoTwo.SetID(2)
- _repoTwo.SetUserID(1)
- _repoTwo.SetHash("baz")
- _repoTwo.SetOrg("bar")
- _repoTwo.SetName("foo")
- _repoTwo.SetFullName("bar/foo")
- _repoTwo.SetVisibility("private")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Build
- }{
- {
- failure: false,
- want: []*library.Build{_buildOne},
- },
- }
-
- filters := map[string]interface{}{}
-
- repos := []*library.Repo{_repoOne, _repoTwo}
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- for _, repo := range repos {
- // create the repo in the database
- err := _database.CreateRepo(repo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
- }
-
- // defer cleanup of the builds table
- defer _database.Sqlite.Exec("delete from builds;")
-
- for _, build := range test.want {
- // create the build in the database
- err := _database.CreateBuild(build)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
- }
-
- got, _, err := _database.GetOrgBuildList("foo", filters, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetOrgBuildList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetOrgBuildList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetOrgBuildList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetOrgBuildListByEvent(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetEvent("push")
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetEvent("deployment")
- _buildTwo.SetDeployPayload(nil)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Build
- }{
- {
- failure: false,
- want: []*library.Build{_buildOne},
- },
- }
-
- filters := map[string]interface{}{
- "event": "push",
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- // create the repo in the database
- err := _database.CreateRepo(_repo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
-
- // defer cleanup of the builds table
- defer _database.Sqlite.Exec("delete from builds;")
-
- for _, build := range []*library.Build{_buildTwo, _buildOne} {
- // create the build in the database
- err := _database.CreateBuild(build)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
- }
-
- got, _, err := _database.GetOrgBuildList("foo", filters, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetOrgBuildListByEvent should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetOrgBuildListByEvent returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetOrgBuildListByEvent is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetRepoBuildList(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetDeployPayload(nil)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Build
- }{
- {
- failure: false,
- want: []*library.Build{_buildTwo, _buildOne},
- },
- }
-
- filters := map[string]interface{}{}
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- // create the repo in the database
- err := _database.CreateRepo(_repo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
-
- // defer cleanup of the builds table
- defer _database.Sqlite.Exec("delete from builds;")
-
- for _, build := range test.want {
- // create the build in the database
- err := _database.CreateBuild(build)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
- }
-
- got, _, err := _database.GetRepoBuildList(_repo, filters, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepoBuildList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepoBuildList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepoBuildList is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/sqlite/build_test.go b/database/sqlite/build_test.go
deleted file mode 100644
index 4cb95fccb..000000000
--- a/database/sqlite/build_test.go
+++ /dev/null
@@ -1,538 +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_GetBuild(t *testing.T) {
- // setup types
- _build := testBuild()
- _build.SetID(1)
- _build.SetRepoID(1)
- _build.SetNumber(1)
- _build.SetDeployPayload(nil)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want *library.Build
- }{
- {
- failure: false,
- want: _build,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- if test.want != nil {
- // create the build in the database
- err := _database.CreateBuild(test.want)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
- }
-
- got, err := _database.GetBuild(1, _repo)
-
- // cleanup the builds table
- _ = _database.Sqlite.Exec("DELETE FROM builds;")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetBuild should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetBuild returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetBuild is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetLastBuild(t *testing.T) {
- // setup types
- _build := testBuild()
- _build.SetID(1)
- _build.SetRepoID(1)
- _build.SetNumber(1)
- _build.SetDeployPayload(nil)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want *library.Build
- }{
- {
- failure: false,
- want: _build,
- },
- {
- failure: false,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- if test.want != nil {
- // create the build in the database
- err := _database.CreateBuild(test.want)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
- }
-
- got, err := _database.GetLastBuild(_repo)
-
- // cleanup the builds table
- _ = _database.Sqlite.Exec("DELETE FROM builds;")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetLastBuild should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetLastBuild returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetLastBuild is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetLastBuildByBranch(t *testing.T) {
- // setup types
- _build := testBuild()
- _build.SetID(1)
- _build.SetRepoID(1)
- _build.SetNumber(1)
- _build.SetBranch("master")
- _build.SetDeployPayload(nil)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want *library.Build
- }{
- {
- failure: false,
- want: _build,
- },
- {
- failure: false,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- if test.want != nil {
- // create the build in the database
- err := _database.CreateBuild(test.want)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
- }
-
- got, err := _database.GetLastBuildByBranch(_repo, "master")
-
- // cleanup the builds table
- _ = _database.Sqlite.Exec("DELETE FROM builds;")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetLastBuildByBranch should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetLastBuildByBranch returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetLastBuildByBranch is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetPendingAndRunningBuilds(t *testing.T) {
- // setup types
- _buildOne := testBuild()
- _buildOne.SetID(1)
- _buildOne.SetRepoID(1)
- _buildOne.SetNumber(1)
- _buildOne.SetStatus("running")
- _buildOne.SetCreated(1)
- _buildOne.SetDeployPayload(nil)
-
- _buildTwo := testBuild()
- _buildTwo.SetID(2)
- _buildTwo.SetRepoID(1)
- _buildTwo.SetNumber(2)
- _buildTwo.SetStatus("pending")
- _buildTwo.SetCreated(1)
- _buildTwo.SetDeployPayload(nil)
-
- _queueOne := new(library.BuildQueue)
- _queueOne.SetCreated(1)
- _queueOne.SetFullName("foo/bar")
- _queueOne.SetNumber(1)
- _queueOne.SetStatus("running")
-
- _queueTwo := new(library.BuildQueue)
- _queueTwo.SetCreated(1)
- _queueTwo.SetFullName("foo/bar")
- _queueTwo.SetNumber(2)
- _queueTwo.SetStatus("pending")
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.BuildQueue
- }{
- {
- failure: false,
- want: []*library.BuildQueue{_queueOne, _queueTwo},
- },
- {
- failure: false,
- want: []*library.BuildQueue{},
- },
- }
-
- // run tests
- for _, test := range tests {
- // create the repo in the database
- err := _database.CreateRepo(_repo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
-
- if len(test.want) > 0 {
- // create the builds in the database
- err = _database.CreateBuild(_buildOne)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
-
- err = _database.CreateBuild(_buildTwo)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
- }
-
- got, err := _database.GetPendingAndRunningBuilds("0")
-
- // cleanup the repos table
- _ = _database.Sqlite.Exec("DELETE FROM repos;")
- // cleanup the builds table
- _ = _database.Sqlite.Exec("DELETE FROM builds;")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetPendingAndRunningBuilds should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetPendingAndRunningBuilds returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetPendingAndRunningBuilds is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_CreateBuild(t *testing.T) {
- // setup types
- _build := testBuild()
- _build.SetID(1)
- _build.SetRepoID(1)
- _build.SetNumber(1)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the builds table
- defer _database.Sqlite.Exec("delete from builds;")
-
- err := _database.CreateBuild(_build)
-
- if test.failure {
- if err == nil {
- t.Errorf("CreateBuild should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("CreateBuild returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_Client_UpdateBuild(t *testing.T) {
- // setup types
- _build := testBuild()
- _build.SetID(1)
- _build.SetRepoID(1)
- _build.SetNumber(1)
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the builds table
- defer _database.Sqlite.Exec("delete from builds;")
-
- // create the build in the database
- err = _database.CreateBuild(_build)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
-
- err := _database.UpdateBuild(_build)
-
- if test.failure {
- if err == nil {
- t.Errorf("UpdateBuild should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("UpdateBuild returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_Client_DeleteBuild(t *testing.T) {
- // setup types
- _build := testBuild()
- _build.SetID(1)
- _build.SetRepoID(1)
- _build.SetNumber(1)
-
- // 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 builds table
- defer _database.Sqlite.Exec("delete from builds;")
-
- // create the build in the database
- err = _database.CreateBuild(_build)
- if err != nil {
- t.Errorf("unable to create test build: %v", err)
- }
-
- err = _database.DeleteBuild(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("DeleteBuild should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("DeleteBuild returned err: %v", err)
- }
- }
-}
-
-// testBuild is a test helper function to create a
-// library Build type with all fields set to their
-// zero values.
-func testBuild() *library.Build {
- i64 := int64(0)
- i := 0
- str := ""
-
- return &library.Build{
- ID: &i64,
- RepoID: &i64,
- Number: &i,
- Parent: &i,
- Event: &str,
- Status: &str,
- Error: &str,
- Enqueued: &i64,
- Created: &i64,
- Started: &i64,
- Finished: &i64,
- Deploy: &str,
- Clone: &str,
- Source: &str,
- Title: &str,
- Message: &str,
- Commit: &str,
- Sender: &str,
- Author: &str,
- Email: &str,
- Link: &str,
- Branch: &str,
- Ref: &str,
- BaseRef: &str,
- HeadRef: &str,
- Host: &str,
- Runtime: &str,
- Distribution: &str,
- }
-}
diff --git a/database/sqlite/ddl/build.go b/database/sqlite/ddl/build.go
deleted file mode 100644
index d9ed4ff86..000000000
--- a/database/sqlite/ddl/build.go
+++ /dev/null
@@ -1,74 +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 (
- // CreateBuildTable represents a query to
- // create the builds table for Vela.
- CreateBuildTable = `
-CREATE TABLE
-IF NOT EXISTS
-builds (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- repo_id INTEGER,
- number INTEGER,
- parent INTEGER,
- event TEXT,
- status TEXT,
- error TEXT,
- enqueued INTEGER,
- created INTEGER,
- started INTEGER,
- finished INTEGER,
- deploy TEXT,
- deploy_payload TEXT,
- clone TEXT,
- source TEXT,
- title TEXT,
- message TEXT,
- 'commit' TEXT,
- sender TEXT,
- author TEXT,
- email TEXT,
- link TEXT,
- branch TEXT,
- ref TEXT,
- base_ref TEXT,
- head_ref TEXT,
- host TEXT,
- runtime TEXT,
- distribution TEXT,
- timestamp INTEGER,
- UNIQUE(repo_id, number)
-);
-`
-
- // CreateBuildRepoIDIndex represents a query to create an
- // index on the builds table for the repo_id column.
- CreateBuildRepoIDIndex = `
-CREATE INDEX
-IF NOT EXISTS
-builds_repo_id
-ON builds (repo_id);
-`
-
- // CreateBuildStatusIndex represents a query to create an
- // index on the builds table for the status column.
- CreateBuildStatusIndex = `
-CREATE INDEX
-IF NOT EXISTS
-builds_status
-ON builds (status);
-`
-
- // CreateBuildCreatedIndex represents a query to create an
- // index on the builds table for the created column.
- CreateBuildCreatedIndex = `
-CREATE INDEX
-IF NOT EXISTS
-builds_created
-ON builds (created);
-`
-)
diff --git a/database/sqlite/ddl/doc.go b/database/sqlite/ddl/doc.go
deleted file mode 100644
index fb2942402..000000000
--- a/database/sqlite/ddl/doc.go
+++ /dev/null
@@ -1,12 +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 provides the Sqlite data definition language (DDL) for Vela.
-//
-// https://en.wikipedia.org/wiki/Data_definition_language
-//
-// Usage:
-//
-// import "github.com/go-vela/server/database/sqlite/ddl"
-package ddl
diff --git a/database/sqlite/ddl/hook.go b/database/sqlite/ddl/hook.go
deleted file mode 100644
index 80b3397bc..000000000
--- a/database/sqlite/ddl/hook.go
+++ /dev/null
@@ -1,38 +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 (
- // CreateHookTable represents a query to
- // create the hooks table for Vela.
- CreateHookTable = `
-CREATE TABLE
-IF NOT EXISTS
-hooks (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- repo_id INTEGER,
- build_id INTEGER,
- number INTEGER,
- source_id TEXT,
- created INTEGER,
- host TEXT,
- event TEXT,
- branch TEXT,
- error TEXT,
- status TEXT,
- link TEXT,
- UNIQUE(repo_id, build_id)
-);
-`
-
- // CreateHookRepoIDIndex represents a query to create an
- // index on the hooks table for the repo_id column.
- CreateHookRepoIDIndex = `
-CREATE INDEX
-IF NOT EXISTS
-hooks_repo_id
-ON hooks (repo_id);
-`
-)
diff --git a/database/sqlite/ddl/log.go b/database/sqlite/ddl/log.go
deleted file mode 100644
index 225155bfb..000000000
--- a/database/sqlite/ddl/log.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 (
- // CreateLogTable represents a query to
- // create the logs table for Vela.
- CreateLogTable = `
-CREATE TABLE
-IF NOT EXISTS
-logs (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- build_id INTEGER,
- repo_id INTEGER,
- service_id INTEGER,
- step_id INTEGER,
- data BLOB,
- UNIQUE(step_id),
- UNIQUE(service_id)
-);
-`
-
- // CreateLogBuildIDIndex represents a query to create an
- // index on the logs table for the build_id column.
- CreateLogBuildIDIndex = `
-CREATE INDEX
-IF NOT EXISTS
-logs_build_id
-ON logs (build_id);
-`
-)
diff --git a/database/sqlite/ddl/repo.go b/database/sqlite/ddl/repo.go
deleted file mode 100644
index 2fcb723c1..000000000
--- a/database/sqlite/ddl/repo.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package ddl
-
-const (
- // CreateRepoTable represents a query to
- // create the repos table for Vela.
- CreateRepoTable = `
-CREATE TABLE
-IF NOT EXISTS
-repos (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- user_id INTEGER,
- hash TEXT,
- org TEXT,
- name TEXT,
- full_name TEXT,
- link TEXT,
- clone TEXT,
- branch TEXT,
- build_limit INTEGER,
- timeout INTEGER,
- counter INTEGER,
- visibility TEXT,
- private BOOLEAN,
- trusted BOOLEAN,
- active BOOLEAN,
- allow_pull BOOLEAN,
- allow_push BOOLEAN,
- allow_deploy BOOLEAN,
- allow_tag BOOLEAN,
- allow_comment BOOLEAN,
- pipeline_type TEXT,
- previous_name TEXT,
- UNIQUE(full_name)
-);
-`
-
- // CreateRepoOrgNameIndex represents a query to create an
- // index on the repos table for the org and name columns.
- CreateRepoOrgNameIndex = `
-CREATE INDEX
-IF NOT EXISTS
-repos_org_name
-ON repos (org, name);
-`
-)
diff --git a/database/sqlite/ddl/secret.go b/database/sqlite/ddl/secret.go
deleted file mode 100644
index 4348ec1de..000000000
--- a/database/sqlite/ddl/secret.go
+++ /dev/null
@@ -1,65 +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 (
- // CreateSecretTable represents a query to
- // create the secrets table for Vela.
- CreateSecretTable = `
-CREATE TABLE
-IF NOT EXISTS
-secrets (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- type TEXT,
- org TEXT,
- repo TEXT,
- team TEXT,
- name TEXT,
- value TEXT,
- images TEXT,
- events TEXT,
- allow_command BOOLEAN,
- created_at INTEGER,
- created_by TEXT,
- updated_at INTEGER,
- updated_by TEXT,
- UNIQUE(type, org, repo, name),
- UNIQUE(type, org, team, name)
-);
-`
-
- // CreateSecretTypeOrgRepo represents a query to create an
- // index on the secrets table for the type, org and repo columns.
- //
- // nolint: gosec // ignore false positive
- CreateSecretTypeOrgRepo = `
-CREATE INDEX
-IF NOT EXISTS
-secrets_type_org_repo
-ON secrets (type, org, repo);
-`
-
- // CreateSecretTypeOrgTeam represents a query to create an
- // index on the secrets table for the type, org and team columns.
- //
- // nolint: gosec // ignore false positive
- CreateSecretTypeOrgTeam = `
-CREATE INDEX
-IF NOT EXISTS
-secrets_type_org_team
-ON secrets (type, org, team);
-`
-
- // CreateSecretTypeOrg represents a query to create an
- // index on the secrets table for the type, and org columns.
- //
- // nolint: gosec // ignore false positive
- CreateSecretTypeOrg = `
-CREATE INDEX
-IF NOT EXISTS
-secrets_type_org
-ON secrets (type, org);
-`
-)
diff --git a/database/sqlite/ddl/service.go b/database/sqlite/ddl/service.go
deleted file mode 100644
index 3b8f9527e..000000000
--- a/database/sqlite/ddl/service.go
+++ /dev/null
@@ -1,32 +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 (
- // CreateServiceTable represents a query to
- // create the services table for Vela.
- CreateServiceTable = `
-CREATE TABLE
-IF NOT EXISTS
-services (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- repo_id INTEGER,
- build_id INTEGER,
- number INTEGER,
- name TEXT,
- image 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/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/ddl/user.go b/database/sqlite/ddl/user.go
deleted file mode 100644
index 3f8d77e90..000000000
--- a/database/sqlite/ddl/user.go
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package ddl
-
-const (
- // CreateUserTable represents a query to
- // create the users table for Vela.
- CreateUserTable = `
-CREATE TABLE
-IF NOT EXISTS
-users (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- name TEXT,
- refresh_token TEXT,
- token TEXT,
- hash TEXT,
- favorites TEXT,
- active BOOLEAN,
- admin BOOLEAN,
- UNIQUE(name)
-);
-`
-
- // CreateUserRefreshIndex represents a query to create an
- // index on the users table for the refresh_token column.
- CreateUserRefreshIndex = `
-CREATE INDEX
-IF NOT EXISTS
-users_refresh
-ON users (refresh_token);
-`
-)
diff --git a/database/sqlite/ddl/worker.go b/database/sqlite/ddl/worker.go
deleted file mode 100644
index 0e5ba8f4a..000000000
--- a/database/sqlite/ddl/worker.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package ddl
-
-const (
- // CreateWorkerTable represents a query to
- // create the workers table for Vela.
- CreateWorkerTable = `
-CREATE TABLE
-IF NOT EXISTS
-workers (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- hostname TEXT,
- address TEXT,
- routes TEXT,
- active TEXT,
- last_checked_in INTEGER,
- build_limit INTEGER,
- UNIQUE(hostname)
-);
-`
-
- // CreateWorkerHostnameAddressIndex represents a query to create an
- // index on the workers table for the hostname and address columns.
- CreateWorkerHostnameAddressIndex = `
-CREATE INDEX
-IF NOT EXISTS
-workers_hostname_address
-ON workers (hostname, address);
-`
-)
diff --git a/database/sqlite/dml/build.go b/database/sqlite/dml/build.go
deleted file mode 100644
index ff8863e8c..000000000
--- a/database/sqlite/dml/build.go
+++ /dev/null
@@ -1,80 +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 (
- // ListBuilds represents a query to
- // list all builds in the database.
- ListBuilds = `
-SELECT *
-FROM builds;
-`
-
- // SelectRepoBuild represents a query to select
- // a build for a repo_id in the database.
- SelectRepoBuild = `
-SELECT *
-FROM builds
-WHERE repo_id = ?
-AND number = ?
-LIMIT 1;
-`
-
- // SelectLastRepoBuild represents a query to select
- // the last build for a repo_id in the database.
- SelectLastRepoBuild = `
-SELECT *
-FROM builds
-WHERE repo_id = ?
-ORDER BY number DESC
-LIMIT 1;
-`
- // SelectLastRepoBuildByBranch represents a query to
- // select the last build for a repo_id and branch name
- // in the database.
- SelectLastRepoBuildByBranch = `
-SELECT *
-FROM builds
-WHERE repo_id = ?
-AND branch = ?
-ORDER BY number DESC
-LIMIT 1;
-`
-
- // SelectBuildsCount represents a query to select
- // the count of builds in the database.
- SelectBuildsCount = `
-SELECT count(*) as count
-FROM builds;
-`
-
- // SelectBuildsCountByStatus represents a query to select
- // the count of builds for a status in the database.
- SelectBuildsCountByStatus = `
-SELECT count(*) as count
-FROM builds
-WHERE status = ?;
-`
-
- // DeleteBuild represents a query to
- // remove a build from the database.
- DeleteBuild = `
-DELETE
-FROM builds
-WHERE id = ?;
-`
-
- // SelectPendingAndRunningBuilds represents a joined query
- // between the builds & repos table to select
- // the created builds that are in pending or running builds status
- // since the specified timeframe.
- SelectPendingAndRunningBuilds = `
-SELECT builds.created, builds.number, builds.status, repos.full_name
-FROM builds INNER JOIN repos
-ON builds.repo_id = repos.id
-WHERE builds.created > ?
-AND (builds.status = 'running' OR builds.status = 'pending');
-`
-)
diff --git a/database/sqlite/dml/doc.go b/database/sqlite/dml/doc.go
deleted file mode 100644
index fb4a8ecba..000000000
--- a/database/sqlite/dml/doc.go
+++ /dev/null
@@ -1,12 +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 provides the Sqlite data manipulation language (DML) for Vela.
-//
-// https://en.wikipedia.org/wiki/Data_manipulation_language
-//
-// Usage:
-//
-// import "github.com/go-vela/server/database/sqlite/dml"
-package dml
diff --git a/database/sqlite/dml/hook.go b/database/sqlite/dml/hook.go
deleted file mode 100644
index b06d79ad5..000000000
--- a/database/sqlite/dml/hook.go
+++ /dev/null
@@ -1,61 +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 (
- // ListHooks represents a query to
- // list all webhooks in the database.
- ListHooks = `
-SELECT *
-FROM hooks;
-`
-
- // ListRepoHooks represents a query to list
- // all webhooks for a repo_id in the database.
- ListRepoHooks = `
-SELECT *
-FROM hooks
-WHERE repo_id = ?
-ORDER BY id DESC
-LIMIT ?
-OFFSET ?;
-`
-
- // SelectRepoHookCount represents a query to select
- // the count of webhooks for a repo_id in the database.
- SelectRepoHookCount = `
-SELECT count(*) as count
-FROM hooks
-WHERE repo_id = ?;
-`
-
- // SelectRepoHook represents a query to select
- // a webhook for a repo_id in the database.
- SelectRepoHook = `
-SELECT *
-FROM hooks
-WHERE repo_id = ?
-AND number = ?
-LIMIT 1;
-`
-
- // SelectLastRepoHook represents a query to select
- // the last hook for a repo_id in the database.
- SelectLastRepoHook = `
-SELECT *
-FROM hooks
-WHERE repo_id = ?
-ORDER BY number DESC
-LIMIT 1;
-`
-
- // DeleteHook represents a query to
- // remove a webhook from the database.
- DeleteHook = `
-DELETE
-FROM hooks
-WHERE id = ?;
-`
-)
diff --git a/database/sqlite/dml/log.go b/database/sqlite/dml/log.go
deleted file mode 100644
index e3e904cc4..000000000
--- a/database/sqlite/dml/log.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package dml
-
-const (
- // ListLogs represents a query to
- // list all logs in the database.
- ListLogs = `
-SELECT *
-FROM logs;
-`
-
- // ListBuildLogs represents a query to list
- // all logs for a build_id in the database.
- ListBuildLogs = `
-SELECT *
-FROM logs
-WHERE build_id = ?
-ORDER BY step_id ASC;
-`
-
- // SelectStepLog represents a query to select
- // a log for a step_id in the database.
- SelectStepLog = `
-SELECT *
-FROM logs
-WHERE step_id = ?
-LIMIT 1;
-`
-
- // SelectServiceLog represents a query to select
- // a log for a service_id in the database.
- SelectServiceLog = `
-SELECT *
-FROM logs
-WHERE service_id = ?
-LIMIT 1;
-`
-
- // DeleteLog represents a query to
- // remove a log from the database.
- DeleteLog = `
-DELETE
-FROM logs
-WHERE id = ?;
-`
-)
diff --git a/database/sqlite/dml/repo.go b/database/sqlite/dml/repo.go
deleted file mode 100644
index 3de708b3e..000000000
--- a/database/sqlite/dml/repo.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package dml
-
-const (
- // ListRepos represents a query to
- // list all repos in the database.
- ListRepos = `
-SELECT *
-FROM repos;
-`
-
- // ListUserRepos represents a query to list
- // all repos for a user_id in the database.
- ListUserRepos = `
-SELECT *
-FROM repos
-WHERE user_id = ?
-ORDER BY id DESC
-LIMIT ?
-OFFSET ?;
-`
-
- // SelectRepo represents a query to select a
- // repo for an org and name in the database.
- SelectRepo = `
-SELECT *
-FROM repos
-WHERE org = ?
-AND name = ?
-LIMIT 1;
-`
-
- // SelectUserReposCount represents a query to select
- // the count of repos for a user_id in the database.
- SelectUserReposCount = `
-SELECT count(*) as count
-FROM repos
-WHERE user_id = ?;
-`
-
- // SelectReposCount represents a query to select
- // the count of repos in the database.
- SelectReposCount = `
-SELECT count(*) as count
-FROM repos;
-`
-
- // DeleteRepo represents a query to
- // remove a repo from the database.
- DeleteRepo = `
-DELETE
-FROM repos
-WHERE id = ?;
-`
-)
diff --git a/database/sqlite/dml/secret.go b/database/sqlite/dml/secret.go
deleted file mode 100644
index 9fe094a51..000000000
--- a/database/sqlite/dml/secret.go
+++ /dev/null
@@ -1,146 +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 (
- // ListSecrets represents a query to
- // list all secrets in the database.
- //
- // nolint: gosec // ignore false positive
- ListSecrets = `
-SELECT *
-FROM secrets;
-`
-
- // ListOrgSecrets represents a query to list all
- // secrets for a type and org in the database.
- //
- // nolint: gosec // ignore false positive
- ListOrgSecrets = `
-SELECT *
-FROM secrets
-WHERE type = 'org'
-AND org = ?
-ORDER BY id DESC
-LIMIT ?
-OFFSET ?;
-`
-
- // ListRepoSecrets represents a query to list all
- // secrets for a type, org and repo in the database.
- //
- // nolint: gosec // ignore false positive
- ListRepoSecrets = `
-SELECT *
-FROM secrets
-WHERE type = 'repo'
-AND org = ?
-AND repo = ?
-ORDER BY id DESC
-LIMIT ?
-OFFSET ?;
-`
-
- // ListSharedSecrets represents a query to list all
- // secrets for a type, org and team in the database.
- //
- // nolint: gosec // ignore false positive
- ListSharedSecrets = `
-SELECT *
-FROM secrets
-WHERE type = 'shared'
-AND org = ?
-AND team = ?
-ORDER BY id DESC
-LIMIT ?
-OFFSET ?;
-`
-
- // SelectOrgSecretsCount represents a query to select the
- // count of org secrets for an org in the database.
- //
- // nolint: gosec // ignore false positive
- SelectOrgSecretsCount = `
-SELECT count(*) as count
-FROM secrets
-WHERE type = 'org'
-AND org = ?;
-`
-
- // SelectRepoSecretsCount represents a query to select the
- // count of repo secrets for an org and repo in the database.
- //
- // nolint: gosec // ignore false positive
- SelectRepoSecretsCount = `
-SELECT count(*) as count
-FROM secrets
-WHERE type = 'repo'
-AND org = ?
-AND repo = ?;
-`
-
- // SelectSharedSecretsCount represents a query to select the
- // count of shared secrets for an org and repo in the database.
- //
- // nolint: gosec // ignore false positive
- SelectSharedSecretsCount = `
-SELECT count(*) as count
-FROM secrets
-WHERE type = 'shared'
-AND org = ?
-AND team = ?;
-`
-
- // SelectOrgSecret represents a query to select a
- // secret for an org and name in the database.
- //
- // nolint: gosec // ignore false positive
- SelectOrgSecret = `
-SELECT *
-FROM secrets
-WHERE type = 'org'
-AND org = ?
-AND name = ?
-LIMIT 1;
-`
-
- // SelectRepoSecret represents a query to select a
- // secret for an org, repo and name in the database.
- //
- // nolint: gosec // ignore false positive
- SelectRepoSecret = `
-SELECT *
-FROM secrets
-WHERE type = 'repo'
-AND org = ?
-AND repo = ?
-AND name = ?
-LIMIT 1;
-`
-
- // SelectSharedSecret represents a query to select a
- // secret for an org, team and name in the database.
- //
- // nolint: gosec // ignore false positive
- SelectSharedSecret = `
-SELECT *
-FROM secrets
-WHERE type = 'shared'
-AND org = ?
-AND team = ?
-AND name = ?
-LIMIT 1;
-`
-
- // DeleteSecret represents a query to
- // remove a secret from the database.
- //
- // nolint: gosec // ignore false positive
- DeleteSecret = `
-DELETE
-FROM secrets
-WHERE id = ?;
-`
-)
diff --git a/database/sqlite/dml/service.go b/database/sqlite/dml/service.go
deleted file mode 100644
index 66e009406..000000000
--- a/database/sqlite/dml/service.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 (
- // ListServices represents a query to
- // list all services in the database.
- ListServices = `
-SELECT *
-FROM services;
-`
-
- // ListBuildServices represents a query to list
- // all services for a build_id in the database.
- ListBuildServices = `
-SELECT *
-FROM services
-WHERE build_id = ?
-ORDER BY id DESC
-LIMIT ?
-OFFSET ?;
-`
-
- // SelectBuildServicesCount represents a query to select
- // the count of services for a build_id in the database.
- SelectBuildServicesCount = `
-SELECT count(*) as count
-FROM services
-WHERE build_id = ?
-`
-
- // SelectServiceImagesCount represents a query to select
- // the count of an images appearances in the database.
- SelectServiceImagesCount = `
-SELECT image, count(image) as count
-FROM services
-GROUP BY image
-`
-
- // SelectServiceStatusesCount represents a query to select
- // the count of service status appearances in the database.
- SelectServiceStatusesCount = `
-SELECT status, count(status) as count
-FROM services
-GROUP BY status;
-`
-
- // SelectBuildService represents a query to select a
- // service for a build_id and number in the database.
- SelectBuildService = `
-SELECT *
-FROM services
-WHERE build_id = ?
-AND number = ?
-LIMIT 1;
-`
-
- // DeleteService represents a query to
- // remove a service from the database.
- DeleteService = `
-DELETE
-FROM services
-WHERE id = ?;
-`
-)
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/dml/user.go b/database/sqlite/dml/user.go
deleted file mode 100644
index 9febce658..000000000
--- a/database/sqlite/dml/user.go
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package dml
-
-const (
- // ListUsers represents a query to
- // list all users in the database.
- ListUsers = `
-SELECT *
-FROM users;
-`
-
- // ListLiteUsers represents a query to
- // list all lite users in the database.
- ListLiteUsers = `
-SELECT id, name
-FROM users
-ORDER BY id DESC
-LIMIT ?
-OFFSET ?;
-`
-
- // SelectUser represents a query to select
- // a user for an id in the database.
- SelectUser = `
-SELECT *
-FROM users
-WHERE id = ?
-LIMIT 1;
-`
-
- // SelectUserName represents a query to select
- // a user for a name in the database.
- SelectUserName = `
-SELECT *
-FROM users
-WHERE name = ?
-LIMIT 1;
-`
-
- // SelectUsersCount represents a query to select
- // the count of users in the database.
- SelectUsersCount = `
-SELECT count(*) as count
-FROM users;
-`
-
- // SelectRefreshToken represents a query to select
- // a user for a refresh_token in the database.
- //
- // nolint: gosec // ignore false positive
- SelectRefreshToken = `
-SELECT *
-FROM users
-WHERE refresh_token = ?
-LIMIT 1;
-`
-
- // DeleteUser represents a query to
- // remove a user from the database.
- DeleteUser = `
-DELETE
-FROM users
-WHERE id = ?;
-`
-)
diff --git a/database/sqlite/dml/worker.go b/database/sqlite/dml/worker.go
deleted file mode 100644
index 64967c9b9..000000000
--- a/database/sqlite/dml/worker.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package dml
-
-const (
- // ListWorkers represents a query to
- // list all workers in the database.
- ListWorkers = `
-SELECT *
-FROM workers;
-`
-
- // SelectWorkersCount represents a query to select the
- // count of workers in the database.
- SelectWorkersCount = `
-SELECT count(*) as count
-FROM workers;
-`
-
- // SelectWorker represents a query to select a
- // worker in the database.
- SelectWorker = `
-SELECT *
-FROM workers
-WHERE hostname = ?
-LIMIT 1;
-`
-
- // SelectWorkerByAddress represents a query to select a
- // worker by address in the database.
- SelectWorkerByAddress = `
-SELECT *
-FROM workers
-WHERE address = ?
-LIMIT 1;
-`
-
- // DeleteWorker represents a query to
- // remove a worker from the database.
- DeleteWorker = `
-DELETE
-FROM workers
-WHERE id = ?;
-`
-)
diff --git a/database/sqlite/doc.go b/database/sqlite/doc.go
deleted file mode 100644
index c0ceaf5aa..000000000
--- a/database/sqlite/doc.go
+++ /dev/null
@@ -1,11 +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 provides the ability for Vela to
-// integrate with Sqlite as a SQL backend.
-//
-// Usage:
-//
-// import "github.com/go-vela/server/database/sqlite"
-package sqlite
diff --git a/database/sqlite/driver.go b/database/sqlite/driver.go
deleted file mode 100644
index 208853dd5..000000000
--- a/database/sqlite/driver.go
+++ /dev/null
@@ -1,12 +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/types/constants"
-
-// Driver outputs the configured database driver.
-func (c *client) Driver() string {
- return constants.DriverSqlite
-}
diff --git a/database/sqlite/driver_test.go b/database/sqlite/driver_test.go
deleted file mode 100644
index 92a80c129..000000000
--- a/database/sqlite/driver_test.go
+++ /dev/null
@@ -1,31 +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/constants"
-)
-
-func TestSqlite_Client_Driver(t *testing.T) {
- // setup types
- want := constants.DriverSqlite
-
- // 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() }()
-
- // run test
- got := _database.Driver()
-
- if !reflect.DeepEqual(got, want) {
- t.Errorf("Driver is %v, want %v", got, want)
- }
-}
diff --git a/database/sqlite/hook.go b/database/sqlite/hook.go
deleted file mode 100644
index 5ebd054e3..000000000
--- a/database/sqlite/hook.go
+++ /dev/null
@@ -1,122 +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"
-)
-
-// GetHook gets a hook by number and repo ID from the database.
-//
-// nolint: dupl // ignore similar code with build
-func (c *client) GetHook(number int, r *library.Repo) (*library.Hook, error) {
- c.Logger.WithFields(logrus.Fields{
- "hook": number,
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("getting hook %s/%d from the database", r.GetFullName(), number)
-
- // variable to store query results
- h := new(database.Hook)
-
- // send query to the database and store result in variable
- result := c.Sqlite.
- Table(constants.TableHook).
- Raw(dml.SelectRepoHook, r.GetID(), number).
- Scan(h)
-
- // 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 h.ToLibrary(), result.Error
-}
-
-// GetLastHook gets the last hook by repo ID from the database.
-func (c *client) GetLastHook(r *library.Repo) (*library.Hook, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("getting last hook for repo %s from the database", r.GetFullName())
-
- // variable to store query results
- h := new(database.Hook)
-
- // send query to the database and store result in variable
- result := c.Sqlite.
- Table(constants.TableHook).
- Raw(dml.SelectLastRepoHook, r.GetID()).
- Scan(h)
-
- // 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 {
- // the record will not exist if it's a new repo
- return nil, nil
- }
-
- return h.ToLibrary(), result.Error
-}
-
-// CreateHook creates a new hook in the database.
-func (c *client) CreateHook(h *library.Hook) error {
- c.Logger.WithFields(logrus.Fields{
- "hook": h.GetNumber(),
- }).Tracef("creating hook %d in the database", h.GetNumber())
-
- // cast to database type
- hook := database.HookFromLibrary(h)
-
- // validate the necessary fields are populated
- err := hook.Validate()
- if err != nil {
- return err
- }
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableHook).
- Create(hook).Error
-}
-
-// UpdateHook updates a hook in the database.
-func (c *client) UpdateHook(h *library.Hook) error {
- c.Logger.WithFields(logrus.Fields{
- "hook": h.GetNumber(),
- }).Tracef("updating hook %d in the database", h.GetNumber())
-
- // cast to database type
- hook := database.HookFromLibrary(h)
-
- // validate the necessary fields are populated
- err := hook.Validate()
- if err != nil {
- return err
- }
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableHook).
- Save(hook).Error
-}
-
-// DeleteHook deletes a hook by unique ID from the database.
-func (c *client) DeleteHook(id int64) error {
- c.Logger.Tracef("deleting hook %d in the database", id)
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableHook).
- Exec(dml.DeleteHook, id).Error
-}
diff --git a/database/sqlite/hook_count_test.go b/database/sqlite/hook_count_test.go
deleted file mode 100644
index 8eea9515d..000000000
--- a/database/sqlite/hook_count_test.go
+++ /dev/null
@@ -1,105 +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 hook table
- err = _database.Sqlite.Exec(ddl.CreateHookTable).Error
- if err != nil {
- log.Fatalf("unable to create %s table: %v", constants.TableHook, err)
- }
-}
-
-func TestSqlite_Client_GetRepoHookCount(t *testing.T) {
- // setup types
- _hookOne := testHook()
- _hookOne.SetID(1)
- _hookOne.SetRepoID(1)
- _hookOne.SetBuildID(1)
- _hookOne.SetNumber(1)
- _hookOne.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- _hookTwo := testHook()
- _hookTwo.SetID(2)
- _hookTwo.SetRepoID(1)
- _hookTwo.SetBuildID(2)
- _hookTwo.SetNumber(2)
- _hookTwo.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 2,
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the hooks table
- defer _database.Sqlite.Exec("delete from hooks;")
-
- // create the hooks in the database
- err := _database.CreateHook(_hookOne)
- if err != nil {
- t.Errorf("unable to create test hook: %v", err)
- }
-
- err = _database.CreateHook(_hookTwo)
- if err != nil {
- t.Errorf("unable to create test hook: %v", err)
- }
-
- got, err := _database.GetRepoHookCount(_repo)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepoHookCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepoHookCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepoHookCount is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/sqlite/hook_list.go b/database/sqlite/hook_list.go
deleted file mode 100644
index ca388254b..000000000
--- a/database/sqlite/hook_list.go
+++ /dev/null
@@ -1,72 +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"
-)
-
-// GetHookList gets a list of all hooks from the database.
-func (c *client) GetHookList() ([]*library.Hook, error) {
- c.Logger.Trace("listing hooks from the database")
-
- // variable to store query results
- h := new([]database.Hook)
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableHook).
- Raw(dml.ListHooks).
- Scan(h).Error
-
- // variable we want to return
- hooks := []*library.Hook{}
- // iterate through all query results
- for _, hook := range *h {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := hook
-
- // convert query result to library type
- hooks = append(hooks, tmp.ToLibrary())
- }
-
- return hooks, err
-}
-
-// GetRepoHookList gets a list of hooks by repo ID from the database.
-func (c *client) GetRepoHookList(r *library.Repo, page, perPage int) ([]*library.Hook, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("listing hooks for repo %s from the database", r.GetFullName())
-
- // variable to store query results
- h := new([]database.Hook)
- // 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.TableHook).
- Raw(dml.ListRepoHooks, r.GetID(), perPage, offset).
- Scan(h).Error
-
- // variable we want to return
- hooks := []*library.Hook{}
- // iterate through all query results
- for _, hook := range *h {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := hook
-
- // convert query result to library type
- hooks = append(hooks, tmp.ToLibrary())
- }
-
- return hooks, err
-}
diff --git a/database/sqlite/hook_list_test.go b/database/sqlite/hook_list_test.go
deleted file mode 100644
index ed44e5945..000000000
--- a/database/sqlite/hook_list_test.go
+++ /dev/null
@@ -1,170 +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 hook table
- err = _database.Sqlite.Exec(ddl.CreateHookTable).Error
- if err != nil {
- log.Fatalf("unable to create %s table: %v", constants.TableHook, err)
- }
-}
-
-func TestSqlite_Client_GetHookList(t *testing.T) {
- // setup types
- _hookOne := testHook()
- _hookOne.SetID(1)
- _hookOne.SetRepoID(1)
- _hookOne.SetBuildID(1)
- _hookOne.SetNumber(1)
- _hookOne.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- _hookTwo := testHook()
- _hookTwo.SetID(2)
- _hookTwo.SetRepoID(1)
- _hookTwo.SetBuildID(2)
- _hookTwo.SetNumber(2)
- _hookTwo.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- // 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.Hook
- }{
- {
- failure: false,
- want: []*library.Hook{_hookOne, _hookTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the hooks table
- defer _database.Sqlite.Exec("delete from hooks;")
-
- for _, hook := range test.want {
- // create the hook in the database
- err := _database.CreateHook(hook)
- if err != nil {
- t.Errorf("unable to create test hook: %v", err)
- }
- }
-
- got, err := _database.GetHookList()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetHookList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetHookList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetHookList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetRepoHookList(t *testing.T) {
- // setup types
- _hookOne := testHook()
- _hookOne.SetID(1)
- _hookOne.SetRepoID(1)
- _hookOne.SetBuildID(1)
- _hookOne.SetNumber(1)
- _hookOne.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- _hookTwo := testHook()
- _hookTwo.SetID(2)
- _hookTwo.SetRepoID(1)
- _hookTwo.SetBuildID(2)
- _hookTwo.SetNumber(2)
- _hookTwo.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/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.Hook
- }{
- {
- failure: false,
- want: []*library.Hook{_hookTwo, _hookOne},
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the hooks table
- defer _database.Sqlite.Exec("delete from hooks;")
-
- for _, hook := range test.want {
- // create the hook in the database
- err := _database.CreateHook(hook)
- if err != nil {
- t.Errorf("unable to create test hook: %v", err)
- }
- }
-
- got, err := _database.GetRepoHookList(_repo, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepoHookList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepoHookList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepoHookList is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/sqlite/hook_test.go b/database/sqlite/hook_test.go
deleted file mode 100644
index 371c787e8..000000000
--- a/database/sqlite/hook_test.go
+++ /dev/null
@@ -1,328 +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_GetHook(t *testing.T) {
- // setup types
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
-
- _hook := testHook()
- _hook.SetID(1)
- _hook.SetRepoID(1)
- _hook.SetBuildID(1)
- _hook.SetNumber(1)
- _hook.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- // 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.Hook
- }{
- {
- failure: false,
- want: _hook,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- if test.want != nil {
- // create the hook in the database
- err := _database.CreateHook(test.want)
- if err != nil {
- t.Errorf("unable to create test hook: %v", err)
- }
- }
-
- got, err := _database.GetHook(1, _repo)
-
- // cleanup the hooks table
- _ = _database.Sqlite.Exec("DELETE FROM hooks;")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetHook should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetHook returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetHook is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetLastHook(t *testing.T) {
- // setup types
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
-
- _hook := testHook()
- _hook.SetID(1)
- _hook.SetRepoID(1)
- _hook.SetBuildID(1)
- _hook.SetNumber(1)
- _hook.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- // 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.Hook
- }{
- {
- failure: false,
- want: _hook,
- },
- {
- failure: false,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- if test.want != nil {
- // create the hook in the database
- err := _database.CreateHook(test.want)
- if err != nil {
- t.Errorf("unable to create test hook: %v", err)
- }
- }
-
- got, err := _database.GetLastHook(_repo)
-
- // cleanup the hooks table
- _ = _database.Sqlite.Exec("DELETE FROM hooks;")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetLastHook should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetLastHook returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetLastHook is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_CreateHook(t *testing.T) {
- // setup types
- _hook := testHook()
- _hook.SetID(1)
- _hook.SetRepoID(1)
- _hook.SetBuildID(1)
- _hook.SetNumber(1)
- _hook.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- // 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 hooks table
- defer _database.Sqlite.Exec("delete from hooks;")
-
- err := _database.CreateHook(_hook)
-
- if test.failure {
- if err == nil {
- t.Errorf("CreateHook should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("CreateHook returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_Client_UpdateHook(t *testing.T) {
- // setup types
- _hook := testHook()
- _hook.SetID(1)
- _hook.SetRepoID(1)
- _hook.SetBuildID(1)
- _hook.SetNumber(1)
- _hook.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- // 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 hooks table
- defer _database.Sqlite.Exec("delete from hooks;")
-
- // create the hook in the database
- err := _database.CreateHook(_hook)
- if err != nil {
- t.Errorf("unable to create test hook: %v", err)
- }
-
- err = _database.UpdateHook(_hook)
-
- if test.failure {
- if err == nil {
- t.Errorf("UpdateHook should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("UpdateHook returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_Client_DeleteHook(t *testing.T) {
- // setup types
- _hook := testHook()
- _hook.SetID(1)
- _hook.SetRepoID(1)
- _hook.SetBuildID(1)
- _hook.SetNumber(1)
- _hook.SetSourceID("c8da1302-07d6-11ea-882f-4893bca275b8")
-
- // 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 hooks table
- defer _database.Sqlite.Exec("delete from hooks;")
-
- // create the hook in the database
- err := _database.CreateHook(_hook)
- if err != nil {
- t.Errorf("unable to create test hook: %v", err)
- }
-
- err = _database.DeleteHook(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("DeleteHook should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("DeleteHook returned err: %v", err)
- }
- }
-}
-
-// testHook is a test helper function to create a
-// library Hook type with all fields set to their
-// zero values.
-func testHook() *library.Hook {
- i := 0
- i64 := int64(0)
- str := ""
-
- return &library.Hook{
- ID: &i64,
- RepoID: &i64,
- BuildID: &i64,
- Number: &i,
- SourceID: &str,
- Created: &i64,
- Host: &str,
- Event: &str,
- Branch: &str,
- Error: &str,
- Status: &str,
- Link: &str,
- }
-}
diff --git a/database/sqlite/log.go b/database/sqlite/log.go
deleted file mode 100644
index 605d50064..000000000
--- a/database/sqlite/log.go
+++ /dev/null
@@ -1,212 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package sqlite
-
-import (
- "errors"
- "fmt"
-
- "github.com/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"
-)
-
-// GetBuildLogs gets a collection of logs for a build by unique ID from the database.
-func (c *client) GetBuildLogs(id int64) ([]*library.Log, error) {
- c.Logger.Tracef("listing logs for build %d from the database", id)
-
- // variable to store query results
- l := new([]database.Log)
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableLog).
- Raw(dml.ListBuildLogs, id).
- Scan(l).Error
- if err != nil {
- return nil, err
- }
-
- // variable we want to return
- logs := []*library.Log{}
- // iterate through all query results
- for _, log := range *l {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := log
-
- // decompress log data for the step
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Log.Decompress
- err = tmp.Decompress()
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch uncompressed logs
- c.Logger.Errorf("unable to decompress logs for build %d: %v", id, err)
- }
-
- // convert query result to library type
- logs = append(logs, tmp.ToLibrary())
- }
-
- return logs, nil
-}
-
-// GetStepLog gets a log by unique ID from the database.
-//
-// nolint: dupl // ignore similar code with service
-func (c *client) GetStepLog(id int64) (*library.Log, error) {
- c.Logger.Tracef("getting log for step %d from the database", id)
-
- // variable to store query results
- l := new(database.Log)
-
- // send query to the database and store result in variable
- result := c.Sqlite.
- Table(constants.TableLog).
- Raw(dml.SelectStepLog, id).
- Scan(l)
-
- // 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
- }
-
- // decompress log data for the step
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Log.Decompress
- err := l.Decompress()
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch uncompressed logs
- c.Logger.Errorf("unable to decompress logs for step %d: %v", id, err)
-
- // return the uncompressed log
- return l.ToLibrary(), result.Error
- }
-
- // return the decompressed log
- return l.ToLibrary(), result.Error
-}
-
-// GetServiceLog gets a log by unique ID from the database.
-//
-// nolint: dupl // ignore similar code with step
-func (c *client) GetServiceLog(id int64) (*library.Log, error) {
- c.Logger.Tracef("getting log for service %d from the database", id)
-
- // variable to store query results
- l := new(database.Log)
-
- // send query to the database and store result in variable
- result := c.Sqlite.
- Table(constants.TableLog).
- Raw(dml.SelectServiceLog, id).
- Scan(l)
-
- // 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
- }
-
- // decompress log data for the service
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Log.Decompress
- err := l.Decompress()
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allowing us to fetch uncompressed logs
- c.Logger.Errorf("unable to decompress logs for service %d: %v", id, err)
-
- // return the uncompressed log
- return l.ToLibrary(), result.Error
- }
-
- // return the decompressed log
- return l.ToLibrary(), result.Error
-}
-
-// CreateLog creates a new log in the database.
-//
-// nolint: dupl // ignore false positive of duplicate code
-func (c *client) CreateLog(l *library.Log) error {
- // check if the log entry is for a step
- if l.GetStepID() > 0 {
- c.Logger.Tracef("creating log for step %d in the database", l.GetStepID())
- } else {
- c.Logger.Tracef("creating log for service %d in the database", l.GetServiceID())
- }
-
- // cast to database type
- log := database.LogFromLibrary(l)
-
- // validate the necessary fields are populated
- err := log.Validate()
- if err != nil {
- return err
- }
-
- // compress log data for the resource
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Log.Compress
- err = log.Compress(c.config.CompressionLevel)
- if err != nil {
- return fmt.Errorf("unable to compress logs for step %d: %v", l.GetStepID(), err)
- }
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableLog).
- Create(log).Error
-}
-
-// UpdateLog updates a log in the database.
-//
-// nolint: dupl // ignore false positive of duplicate code
-func (c *client) UpdateLog(l *library.Log) error {
- // check if the log entry is for a step
- if l.GetStepID() > 0 {
- c.Logger.Tracef("updating log for step %d in the database", l.GetStepID())
- } else {
- c.Logger.Tracef("updating log for service %d in the database", l.GetServiceID())
- }
-
- // cast to database type
- log := database.LogFromLibrary(l)
-
- // validate the necessary fields are populated
- err := log.Validate()
- if err != nil {
- return err
- }
-
- // compress log data for the resource
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Log.Compress
- err = log.Compress(c.config.CompressionLevel)
- if err != nil {
- return fmt.Errorf("unable to compress logs for step %d: %v", l.GetStepID(), err)
- }
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableLog).
- Save(log).Error
-}
-
-// DeleteLog deletes a log by unique ID from the database.
-func (c *client) DeleteLog(id int64) error {
- c.Logger.Tracef("deleting log %d from the database", id)
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableLog).
- Exec(dml.DeleteLog, id).Error
-}
diff --git a/database/sqlite/log_test.go b/database/sqlite/log_test.go
deleted file mode 100644
index 280f06bce..000000000
--- a/database/sqlite/log_test.go
+++ /dev/null
@@ -1,374 +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_GetBuildLogs(t *testing.T) {
- // setup types
- _logOne := testLog()
- _logOne.SetID(1)
- _logOne.SetStepID(1)
- _logOne.SetBuildID(1)
- _logOne.SetRepoID(1)
- _logOne.SetData([]byte{})
-
- _logTwo := testLog()
- _logTwo.SetID(2)
- _logTwo.SetServiceID(1)
- _logTwo.SetBuildID(1)
- _logTwo.SetRepoID(1)
- _logTwo.SetData([]byte{})
-
- // 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.Log
- }{
- {
- failure: false,
- want: []*library.Log{_logTwo, _logOne},
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the logs table
- defer _database.Sqlite.Exec("delete from logs;")
-
- for _, log := range test.want {
- // create the log in the database
- err := _database.CreateLog(log)
- if err != nil {
- t.Errorf("unable to create test log: %v", err)
- }
- }
-
- got, err := _database.GetBuildLogs(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetBuildLogs should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetBuildLogs returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetBuildLogs is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetStepLog(t *testing.T) {
- // setup types
- _log := testLog()
- _log.SetID(1)
- _log.SetStepID(1)
- _log.SetBuildID(1)
- _log.SetRepoID(1)
- _log.SetData([]byte{})
-
- // 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.Log
- }{
- {
- failure: false,
- want: _log,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- if test.want != nil {
- // create the log in the database
- err := _database.CreateLog(test.want)
- if err != nil {
- t.Errorf("unable to create test log: %v", err)
- }
- }
-
- got, err := _database.GetStepLog(1)
-
- // cleanup the logs table
- _ = _database.Sqlite.Exec("DELETE FROM logs;")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetStepLog should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetStepLog returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetStepLog is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetServiceLog(t *testing.T) {
- // setup types
- _log := testLog()
- _log.SetID(1)
- _log.SetServiceID(1)
- _log.SetBuildID(1)
- _log.SetRepoID(1)
- _log.SetData([]byte{})
-
- // 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.Log
- }{
- {
- failure: false,
- want: _log,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- if test.want != nil {
- // create the log in the database
- err := _database.CreateLog(test.want)
- if err != nil {
- t.Errorf("unable to create test log: %v", err)
- }
- }
-
- got, err := _database.GetServiceLog(1)
-
- // cleanup the logs table
- _ = _database.Sqlite.Exec("DELETE FROM logs;")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetServiceLog should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetServiceLog returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetServiceLog is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_CreateLog(t *testing.T) {
- // setup types
- _log := testLog()
- _log.SetID(1)
- _log.SetStepID(1)
- _log.SetBuildID(1)
- _log.SetRepoID(1)
- _log.SetData([]byte{})
-
- // 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 logs table
- defer _database.Sqlite.Exec("delete from logs;")
-
- err := _database.CreateLog(_log)
-
- if test.failure {
- if err == nil {
- t.Errorf("CreateLog should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("CreateLog returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_Client_UpdateLog(t *testing.T) {
- // setup types
- _log := testLog()
- _log.SetID(1)
- _log.SetStepID(1)
- _log.SetBuildID(1)
- _log.SetRepoID(1)
- _log.SetData([]byte{})
-
- // 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 logs table
- defer _database.Sqlite.Exec("delete from logs;")
-
- // create the log in the database
- err := _database.CreateLog(_log)
- if err != nil {
- t.Errorf("unable to create test log: %v", err)
- }
-
- err = _database.UpdateLog(_log)
-
- if test.failure {
- if err == nil {
- t.Errorf("UpdateLog should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("UpdateLog returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_Client_DeleteLog(t *testing.T) {
- // setup types
- _log := testLog()
- _log.SetID(1)
- _log.SetStepID(1)
- _log.SetBuildID(1)
- _log.SetRepoID(1)
- _log.SetData([]byte{})
-
- // 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 logs table
- defer _database.Sqlite.Exec("delete from logs;")
-
- // create the log in the database
- err := _database.CreateLog(_log)
- if err != nil {
- t.Errorf("unable to create test log: %v", err)
- }
-
- err = _database.DeleteLog(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("DeleteLog should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("DeleteLog returned err: %v", err)
- }
- }
-}
-
-// testLog is a test helper function to create a
-// library Log type with all fields set to their
-// zero values.
-func testLog() *library.Log {
- i64 := int64(0)
- b := []byte{}
-
- return &library.Log{
- ID: &i64,
- BuildID: &i64,
- RepoID: &i64,
- ServiceID: &i64,
- StepID: &i64,
- Data: &b,
- }
-}
diff --git a/database/sqlite/opts.go b/database/sqlite/opts.go
deleted file mode 100644
index 406dddcc8..000000000
--- a/database/sqlite/opts.go
+++ /dev/null
@@ -1,107 +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 (
- "fmt"
- "time"
-)
-
-// ClientOpt represents a configuration option to initialize the database client for Sqlite.
-type ClientOpt func(*client) error
-
-// WithAddress sets the Sqlite address in the database client for Sqlite.
-func WithAddress(address string) ClientOpt {
- return func(c *client) error {
- c.Logger.Trace("configuring address in sqlite database client")
-
- // check if the Sqlite address provided is empty
- if len(address) == 0 {
- return fmt.Errorf("no Sqlite address provided")
- }
-
- // set the address in the sqlite client
- c.config.Address = address
-
- return nil
- }
-}
-
-// WithCompressionLevel sets the compression level in the database client for Sqlite.
-func WithCompressionLevel(level int) ClientOpt {
- return func(c *client) error {
- c.Logger.Trace("configuring compression level in sqlite database client")
-
- // set the compression level in the sqlite client
- c.config.CompressionLevel = level
-
- return nil
- }
-}
-
-// WithConnectionLife sets the connection duration in the database client for Sqlite.
-func WithConnectionLife(duration time.Duration) ClientOpt {
- return func(c *client) error {
- c.Logger.Trace("configuring connection duration in sqlite database client")
-
- // set the connection duration in the sqlite client
- c.config.ConnectionLife = duration
-
- return nil
- }
-}
-
-// WithConnectionIdle sets the maximum idle connections in the database client for Sqlite.
-func WithConnectionIdle(idle int) ClientOpt {
- return func(c *client) error {
- c.Logger.Trace("configuring maximum idle connections in sqlite database client")
-
- // set the maximum idle connections in the sqlite client
- c.config.ConnectionIdle = idle
-
- return nil
- }
-}
-
-// WithConnectionOpen sets the maximum open connections in the database client for Sqlite.
-func WithConnectionOpen(open int) ClientOpt {
- return func(c *client) error {
- c.Logger.Trace("configuring maximum open connections in sqlite database client")
-
- // set the maximum open connections in the sqlite client
- c.config.ConnectionOpen = open
-
- return nil
- }
-}
-
-// WithEncryptionKey sets the encryption key in the database client for Sqlite.
-func WithEncryptionKey(key string) ClientOpt {
- return func(c *client) error {
- c.Logger.Trace("configuring encryption key in sqlite database client")
-
- // check if the Sqlite encryption key provided is empty
- if len(key) == 0 {
- return fmt.Errorf("no Sqlite encryption key provided")
- }
-
- // set the encryption key in the sqlite client
- c.config.EncryptionKey = key
-
- return nil
- }
-}
-
-// WithSkipCreation sets the skip creation logic in the database client for Sqlite.
-func WithSkipCreation(skipCreation bool) ClientOpt {
- return func(c *client) error {
- c.Logger.Trace("configuring skip creating objects in sqlite database client")
-
- // set to skip creating tables and indexes in the sqlite client
- c.config.SkipCreation = skipCreation
-
- return nil
- }
-}
diff --git a/database/sqlite/opts_test.go b/database/sqlite/opts_test.go
deleted file mode 100644
index 42f70c58e..000000000
--- a/database/sqlite/opts_test.go
+++ /dev/null
@@ -1,287 +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"
- "time"
-
- "github.com/sirupsen/logrus"
-)
-
-func TestSqlite_ClientOpt_WithAddress(t *testing.T) {
- // setup types
- c := new(client)
- c.config = new(config)
- logger := logrus.StandardLogger()
- c.Logger = logrus.NewEntry(logger)
-
- // setup tests
- tests := []struct {
- failure bool
- address string
- want string
- }{
- {
- failure: false,
- address: "sqlite://foo:bar@localhost:5432/vela",
- want: "sqlite://foo:bar@localhost:5432/vela",
- },
- {
- failure: true,
- address: "",
- want: "",
- },
- }
-
- // run tests
- for _, test := range tests {
- err := WithAddress(test.address)(c)
-
- if test.failure {
- if err == nil {
- t.Errorf("WithAddress should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("WithAddress returned err: %v", err)
- }
-
- if !reflect.DeepEqual(c.config.Address, test.want) {
- t.Errorf("WithAddress is %v, want %v", c.config.Address, test.want)
- }
- }
-}
-
-func TestSqlite_ClientOpt_WithCompressionLevel(t *testing.T) {
- // setup types
- c := new(client)
- c.config = new(config)
- logger := logrus.StandardLogger()
- c.Logger = logrus.NewEntry(logger)
-
- // setup tests
- tests := []struct {
- level int
- want int
- }{
- {
- level: 3,
- want: 3,
- },
- {
- level: 0,
- want: 0,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := WithCompressionLevel(test.level)(c)
-
- if err != nil {
- t.Errorf("WithCompressionLevel returned err: %v", err)
- }
-
- if !reflect.DeepEqual(c.config.CompressionLevel, test.want) {
- t.Errorf("WithCompressionLevel is %v, want %v", c.config.CompressionLevel, test.want)
- }
- }
-}
-
-func TestSqlite_ClientOpt_WithConnectionLife(t *testing.T) {
- // setup types
- c := new(client)
- c.config = new(config)
- logger := logrus.StandardLogger()
- c.Logger = logrus.NewEntry(logger)
-
- // setup tests
- tests := []struct {
- duration time.Duration
- want time.Duration
- }{
- {
- duration: 10 * time.Second,
- want: 10 * time.Second,
- },
- {
- duration: 0,
- want: 0,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := WithConnectionLife(test.duration)(c)
-
- if err != nil {
- t.Errorf("WithConnectionLife returned err: %v", err)
- }
-
- if !reflect.DeepEqual(c.config.ConnectionLife, test.want) {
- t.Errorf("WithConnectionLife is %v, want %v", c.config.ConnectionLife, test.want)
- }
- }
-}
-
-func TestSqlite_ClientOpt_WithConnectionIdle(t *testing.T) {
- // setup types
- c := new(client)
- c.config = new(config)
- logger := logrus.StandardLogger()
- c.Logger = logrus.NewEntry(logger)
-
- // setup tests
- tests := []struct {
- idle int
- want int
- }{
- {
- idle: 5,
- want: 5,
- },
- {
- idle: 0,
- want: 0,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := WithConnectionIdle(test.idle)(c)
-
- if err != nil {
- t.Errorf("WithConnectionIdle returned err: %v", err)
- }
-
- if !reflect.DeepEqual(c.config.ConnectionIdle, test.want) {
- t.Errorf("WithConnectionIdle is %v, want %v", c.config.ConnectionIdle, test.want)
- }
- }
-}
-
-func TestSqlite_ClientOpt_WithConnectionOpen(t *testing.T) {
- // setup types
- c := new(client)
- c.config = new(config)
- logger := logrus.StandardLogger()
- c.Logger = logrus.NewEntry(logger)
-
- // setup tests
- tests := []struct {
- open int
- want int
- }{
- {
- open: 10,
- want: 10,
- },
- {
- open: 0,
- want: 0,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := WithConnectionOpen(test.open)(c)
-
- if err != nil {
- t.Errorf("WithConnectionOpen returned err: %v", err)
- }
-
- if !reflect.DeepEqual(c.config.ConnectionOpen, test.want) {
- t.Errorf("WithConnectionOpen is %v, want %v", c.config.ConnectionOpen, test.want)
- }
- }
-}
-
-func TestSqlite_ClientOpt_WithEncryptionKey(t *testing.T) {
- // setup types
- c := new(client)
- c.config = new(config)
- logger := logrus.StandardLogger()
- c.Logger = logrus.NewEntry(logger)
-
- // setup tests
- tests := []struct {
- failure bool
- key string
- want string
- }{
- {
- failure: false,
- key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
- want: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
- },
- {
- failure: true,
- key: "",
- want: "",
- },
- }
-
- // run tests
- for _, test := range tests {
- err := WithEncryptionKey(test.key)(c)
-
- if test.failure {
- if err == nil {
- t.Errorf("WithEncryptionKey should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("WithEncryptionKey returned err: %v", err)
- }
-
- if !reflect.DeepEqual(c.config.EncryptionKey, test.want) {
- t.Errorf("WithEncryptionKey is %v, want %v", c.config.EncryptionKey, test.want)
- }
- }
-}
-
-func TestSqlite_ClientOpt_WithSkipCreation(t *testing.T) {
- // setup types
- c := new(client)
- c.config = new(config)
- logger := logrus.StandardLogger()
- c.Logger = logrus.NewEntry(logger)
-
- // setup tests
- tests := []struct {
- skipCreation bool
- want bool
- }{
- {
- skipCreation: true,
- want: true,
- },
- {
- skipCreation: false,
- want: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := WithSkipCreation(test.skipCreation)(c)
-
- if err != nil {
- t.Errorf("WithSkipCreation returned err: %v", err)
- }
-
- if !reflect.DeepEqual(c.config.SkipCreation, test.want) {
- t.Errorf("WithSkipCreation is %v, want %v", c.config.SkipCreation, test.want)
- }
- }
-}
diff --git a/database/sqlite/ping.go b/database/sqlite/ping.go
deleted file mode 100644
index a23b9f707..000000000
--- a/database/sqlite/ping.go
+++ /dev/null
@@ -1,45 +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 (
- "fmt"
- "time"
-)
-
-// Ping sends a "ping" request with backoff to the database.
-func (c *client) Ping() error {
- c.Logger.Trace("sending ping requests to the database")
-
- // create a loop to attempt ping requests 5 times
- for i := 0; i < 5; i++ {
- // capture database/sql database from gorm database
- //
- // https://pkg.go.dev/gorm.io/gorm#DB.DB
- _sql, err := c.Sqlite.DB()
- if err != nil {
- return err
- }
-
- // send ping request to database
- //
- // https://pkg.go.dev/database/sql#DB.Ping
- err = _sql.Ping()
- if err != nil {
- c.Logger.Debugf("unable to ping database - retrying in %v", time.Duration(i)*time.Second)
-
- // sleep for loop iteration in seconds
- time.Sleep(time.Duration(i) * time.Second)
-
- // continue to next iteration of the loop
- continue
- }
-
- // able to ping database so return with no error
- return nil
- }
-
- return fmt.Errorf("unable to successfully ping database")
-}
diff --git a/database/sqlite/ping_test.go b/database/sqlite/ping_test.go
deleted file mode 100644
index aee71b003..000000000
--- a/database/sqlite/ping_test.go
+++ /dev/null
@@ -1,61 +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 (
- "testing"
-)
-
-func TestSqlite_Client_Ping(t *testing.T) {
- // setup types
- _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()
- }()
-
- _bad, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- // close the bad database to simulate failures to ping
- _sql, _ := _bad.Sqlite.DB()
- _sql.Close()
-
- // setup tests
- tests := []struct {
- failure bool
- database *client
- }{
- {
- failure: false,
- database: _database,
- },
- {
- failure: true,
- database: _bad,
- },
- }
-
- // run tests
- for _, test := range tests {
- err = test.database.Ping()
-
- if test.failure {
- if err == nil {
- t.Errorf("Ping should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("Ping returned err: %v", err)
- }
- }
-}
diff --git a/database/sqlite/repo.go b/database/sqlite/repo.go
deleted file mode 100644
index 81bb01f8d..000000000
--- a/database/sqlite/repo.go
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package sqlite
-
-import (
- "errors"
- "fmt"
-
- "github.com/sirupsen/logrus"
-
- "github.com/go-vela/server/database/sqlite/dml"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/database"
- "github.com/go-vela/types/library"
-
- "gorm.io/gorm"
-)
-
-// GetRepo gets a repo by org and name from the database.
-func (c *client) GetRepo(org, name string) (*library.Repo, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": org,
- "repo": name,
- }).Tracef("getting repo %s/%s from the database", org, name)
-
- // variable to store query results
- r := new(database.Repo)
-
- // send query to the database and store result in variable
- result := c.Sqlite.
- Table(constants.TableRepo).
- Raw(dml.SelectRepo, org, name).
- Scan(r)
-
- // check if the query returned a record not found error or no rows were returned
- if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 {
- return nil, gorm.ErrRecordNotFound
- }
-
- // decrypt the fields for the repo
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt
- err := r.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted repos
- c.Logger.Errorf("unable to decrypt repo %s/%s: %v", org, name, err)
-
- // return the unencrypted repo
- return r.ToLibrary(), result.Error
- }
-
- // return the decrypted repo
- return r.ToLibrary(), result.Error
-}
-
-// CreateRepo creates a new repo in the database.
-//
-// nolint: dupl // ignore similar code with update
-func (c *client) CreateRepo(r *library.Repo) error {
- c.Logger.WithFields(logrus.Fields{
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("creating repo %s in the database", r.GetFullName())
-
- // cast to database type
- repo := database.RepoFromLibrary(r)
-
- // validate the necessary fields are populated
- err := repo.Validate()
- if err != nil {
- return err
- }
-
- // encrypt the fields for the repo
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Encrypt
- err = repo.Encrypt(c.config.EncryptionKey)
- if err != nil {
- return fmt.Errorf("unable to encrypt repo %s: %v", r.GetFullName(), err)
- }
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableRepo).
- Create(repo).Error
-}
-
-// UpdateRepo updates a repo in the database.
-//
-// nolint: dupl // ignore similar code with create
-func (c *client) UpdateRepo(r *library.Repo) error {
- c.Logger.WithFields(logrus.Fields{
- "org": r.GetOrg(),
- "repo": r.GetName(),
- }).Tracef("updating repo %s in the database", r.GetFullName())
-
- // cast to database type
- repo := database.RepoFromLibrary(r)
-
- // validate the necessary fields are populated
- err := repo.Validate()
- if err != nil {
- return err
- }
-
- // encrypt the fields for the repo
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Encrypt
- err = repo.Encrypt(c.config.EncryptionKey)
- if err != nil {
- return fmt.Errorf("unable to encrypt repo %s: %v", r.GetFullName(), err)
- }
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableRepo).
- Save(repo).Error
-}
-
-// DeleteRepo deletes a repo by unique ID from the database.
-func (c *client) DeleteRepo(id int64) error {
- c.Logger.Tracef("deleting repo %d in the database", id)
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableRepo).
- Exec(dml.DeleteRepo, id).Error
-}
diff --git a/database/sqlite/repo_count.go b/database/sqlite/repo_count.go
deleted file mode 100644
index 8438db1ee..000000000
--- a/database/sqlite/repo_count.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package sqlite
-
-import (
- "github.com/go-vela/server/database/sqlite/dml"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/library"
- "github.com/sirupsen/logrus"
-)
-
-// GetRepoCount gets a count of all repos from the database.
-func (c *client) GetRepoCount() (int64, error) {
- c.Logger.Trace("getting count of repos from the database")
-
- // variable to store query results
- var r int64
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableRepo).
- Raw(dml.SelectReposCount).
- Pluck("count", &r).Error
-
- return r, err
-}
-
-// GetOrgRepoCount gets a count of all repos for a specific org from the database.
-func (c *client) GetOrgRepoCount(org string, filters map[string]string) (int64, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": org,
- }).Tracef("getting count of repos for org %s from the database", org)
-
- // variable to store query results
- var r int64
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableRepo).
- Select("count(*)").
- Where("org = ?", org).
- Where(filters).
- Pluck("count", &r).Error
-
- return r, err
-}
-
-// GetUserRepoCount gets a count of all repos for a specific user from the database.
-func (c *client) GetUserRepoCount(u *library.User) (int64, error) {
- c.Logger.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Tracef("getting count of repos for user %s in the database", u.GetName())
-
- // variable to store query results
- var r int64
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableRepo).
- Raw(dml.SelectUserReposCount, u.GetID()).
- Pluck("count", &r).Error
-
- return r, err
-}
diff --git a/database/sqlite/repo_count_test.go b/database/sqlite/repo_count_test.go
deleted file mode 100644
index ed2e77de4..000000000
--- a/database/sqlite/repo_count_test.go
+++ /dev/null
@@ -1,337 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package sqlite
-
-import (
- "log"
- "reflect"
- "testing"
-
- "github.com/go-vela/server/database/sqlite/ddl"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/library"
-)
-
-func init() {
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- log.Fatalf("unable to create new sqlite test database: %v", err)
- }
-
- // create the repo table
- err = _database.Sqlite.Exec(ddl.CreateRepoTable).Error
- if err != nil {
- log.Fatalf("unable to create %s table: %v", constants.TableRepo, err)
- }
-}
-
-func TestSqlite_Client_GetRepoCount(t *testing.T) {
- // setup types
- _repoOne := testRepo()
- _repoOne.SetID(1)
- _repoOne.SetUserID(1)
- _repoOne.SetHash("baz")
- _repoOne.SetOrg("foo")
- _repoOne.SetName("bar")
- _repoOne.SetFullName("foo/bar")
- _repoOne.SetVisibility("public")
-
- _repoTwo := testRepo()
- _repoTwo.SetID(2)
- _repoTwo.SetUserID(1)
- _repoTwo.SetHash("baz")
- _repoTwo.SetOrg("bar")
- _repoTwo.SetName("foo")
- _repoTwo.SetFullName("bar/foo")
- _repoTwo.SetVisibility("public")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 2,
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- // create the repos in the database
- err := _database.CreateRepo(_repoOne)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
-
- err = _database.CreateRepo(_repoTwo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
-
- got, err := _database.GetRepoCount()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepoCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepoCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepoCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetUserRepoCount(t *testing.T) {
- // setup types
- _repoOne := testRepo()
- _repoOne.SetID(1)
- _repoOne.SetUserID(1)
- _repoOne.SetHash("baz")
- _repoOne.SetOrg("foo")
- _repoOne.SetName("bar")
- _repoOne.SetFullName("foo/bar")
- _repoOne.SetVisibility("public")
-
- _repoTwo := testRepo()
- _repoTwo.SetID(2)
- _repoTwo.SetUserID(1)
- _repoTwo.SetHash("baz")
- _repoTwo.SetOrg("bar")
- _repoTwo.SetName("foo")
- _repoTwo.SetFullName("bar/foo")
- _repoTwo.SetVisibility("public")
-
- _user := new(library.User)
- _user.SetID(1)
- _user.SetName("foo")
- _user.SetToken("bar")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 2,
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- // create the repos in the database
- err := _database.CreateRepo(_repoOne)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
-
- err = _database.CreateRepo(_repoTwo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
-
- got, err := _database.GetUserRepoCount(_user)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetUserRepoCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetUserRepoCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetUserRepoCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetOrgRepoCount(t *testing.T) {
- // setup types
- _repoOne := testRepo()
- _repoOne.SetID(1)
- _repoOne.SetUserID(1)
- _repoOne.SetHash("baz")
- _repoOne.SetOrg("foo")
- _repoOne.SetName("bar")
- _repoOne.SetFullName("foo/bar")
- _repoOne.SetVisibility("public")
-
- _repoTwo := testRepo()
- _repoTwo.SetID(2)
- _repoTwo.SetUserID(1)
- _repoTwo.SetHash("baz")
- _repoTwo.SetOrg("bar")
- _repoTwo.SetName("foo")
- _repoTwo.SetFullName("bar/foo")
- _repoTwo.SetVisibility("public")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 1,
- },
- }
- filters := map[string]string{}
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- // create the repos in the database
- err := _database.CreateRepo(_repoOne)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
-
- err = _database.CreateRepo(_repoTwo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
-
- got, err := _database.GetOrgRepoCount("foo", filters)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepoCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepoCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepoCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetOrgRepoCount_NonAdmin(t *testing.T) {
- // setup types
- _repoOne := testRepo()
- _repoOne.SetID(1)
- _repoOne.SetUserID(1)
- _repoOne.SetHash("baz")
- _repoOne.SetOrg("foo")
- _repoOne.SetName("bar")
- _repoOne.SetFullName("foo/bar")
- _repoOne.SetVisibility("public")
-
- _repoTwo := testRepo()
- _repoTwo.SetID(2)
- _repoTwo.SetUserID(1)
- _repoTwo.SetHash("baz")
- _repoTwo.SetOrg("foo")
- _repoTwo.SetName("foo")
- _repoTwo.SetFullName("foo/foo")
- _repoTwo.SetVisibility("private")
-
- _repoThree := testRepo()
- _repoThree.SetID(3)
- _repoThree.SetUserID(1)
- _repoThree.SetHash("baz")
- _repoThree.SetOrg("bar")
- _repoThree.SetName("foo")
- _repoThree.SetFullName("bar/foo")
- _repoThree.SetVisibility("private")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 1,
- },
- }
- filters := map[string]string{}
- filters["visibility"] = "public"
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- for _, repo := range []*library.Repo{_repoOne, _repoTwo, _repoThree} {
- // create the repos in the database
- err := _database.CreateRepo(repo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
- }
-
- got, err := _database.GetOrgRepoCount("foo", filters)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepoCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepoCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepoCount is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/sqlite/repo_list.go b/database/sqlite/repo_list.go
deleted file mode 100644
index 72afbe125..000000000
--- a/database/sqlite/repo_list.go
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package sqlite
-
-import (
- "github.com/go-vela/server/database/sqlite/dml"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/database"
- "github.com/go-vela/types/library"
- "github.com/sirupsen/logrus"
-)
-
-// GetRepoList gets a list of all repos from the database.
-//
-// nolint: dupl // ignore false positive of duplicate code
-func (c *client) GetRepoList() ([]*library.Repo, error) {
- c.Logger.Trace("listing repos from the database")
-
- // variable to store query results
- r := new([]database.Repo)
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableRepo).
- Raw(dml.ListRepos).
- Scan(r).Error
- if err != nil {
- return nil, err
- }
-
- // variable we want to return
- repos := []*library.Repo{}
- // iterate through all query results
- for _, repo := range *r {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := repo
-
- // decrypt the fields for the repo
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt
- err = tmp.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted repos
- c.Logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err)
- }
-
- // convert query result to library type
- repos = append(repos, tmp.ToLibrary())
- }
-
- return repos, nil
-}
-
-// GetOrgRepoList gets a list of all repos by org from the database.
-//
-// nolint: lll // ignore long line length due to variable names
-func (c *client) GetOrgRepoList(org string, filters map[string]string, page, perPage int) ([]*library.Repo, error) {
- c.Logger.WithFields(logrus.Fields{
- "org": org,
- }).Tracef("listing repos for org %s from the database", org)
-
- // variable to store query results
- r := new([]database.Repo)
-
- // calculate offset for pagination through results
- offset := perPage * (page - 1)
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableRepo).
- Where("org = ?", org).
- Where(filters).
- Limit(perPage).
- Offset(offset).
- Scan(r).Error
- if err != nil {
- return nil, err
- }
-
- // variable we want to return
- repos := []*library.Repo{}
- // iterate through all query results
- for _, repo := range *r {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := repo
-
- // decrypt the fields for the repo
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt
- err = tmp.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted repos
- c.Logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err)
- }
-
- // convert query result to library type
- repos = append(repos, tmp.ToLibrary())
- }
-
- return repos, nil
-}
-
-// GetUserRepoList gets a list of all repos by user ID from the database.
-func (c *client) GetUserRepoList(u *library.User, page, perPage int) ([]*library.Repo, error) {
- c.Logger.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Tracef("listing repos for user %s from the database", u.GetName())
-
- // variable to store query results
- r := new([]database.Repo)
- // calculate offset for pagination through results
- offset := perPage * (page - 1)
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableRepo).
- Raw(dml.ListUserRepos, u.GetID(), perPage, offset).
- Scan(r).Error
- if err != nil {
- return nil, err
- }
-
- // variable we want to return
- repos := []*library.Repo{}
- // iterate through all query results
- for _, repo := range *r {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := repo
-
- // decrypt the fields for the repo
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Repo.Decrypt
- err = tmp.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted repos
- c.Logger.Errorf("unable to decrypt repo %d: %v", tmp.ID.Int64, err)
- }
-
- // convert query result to library type
- repos = append(repos, tmp.ToLibrary())
- }
-
- return repos, nil
-}
diff --git a/database/sqlite/repo_list_test.go b/database/sqlite/repo_list_test.go
deleted file mode 100644
index 1d9567cdf..000000000
--- a/database/sqlite/repo_list_test.go
+++ /dev/null
@@ -1,335 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package sqlite
-
-import (
- "log"
- "reflect"
- "testing"
-
- "github.com/go-vela/server/database/sqlite/ddl"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/library"
-)
-
-func init() {
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- log.Fatalf("unable to create new sqlite test database: %v", err)
- }
-
- // create the repo table
- err = _database.Sqlite.Exec(ddl.CreateRepoTable).Error
- if err != nil {
- log.Fatalf("unable to create %s table: %v", constants.TableRepo, err)
- }
-}
-
-func TestSqlite_Client_GetRepoList(t *testing.T) {
- // setup types
- _repoOne := testRepo()
- _repoOne.SetID(1)
- _repoOne.SetUserID(1)
- _repoOne.SetHash("baz")
- _repoOne.SetOrg("foo")
- _repoOne.SetName("bar")
- _repoOne.SetFullName("foo/bar")
- _repoOne.SetVisibility("public")
- _repoOne.SetPipelineType("yaml")
- _repoOne.SetPreviousName("")
-
- _repoTwo := testRepo()
- _repoTwo.SetID(2)
- _repoTwo.SetUserID(1)
- _repoTwo.SetHash("baz")
- _repoTwo.SetOrg("bar")
- _repoTwo.SetName("foo")
- _repoTwo.SetFullName("bar/foo")
- _repoTwo.SetVisibility("public")
- _repoTwo.SetPipelineType("yaml")
- _repoTwo.SetPreviousName("oldName")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Repo
- }{
- {
- failure: false,
- want: []*library.Repo{_repoOne, _repoTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- for _, repo := range test.want {
- // create the repo in the database
- err := _database.CreateRepo(repo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
- }
-
- got, err := _database.GetRepoList()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepoList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepoList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepoList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetOrgRepoList(t *testing.T) {
- // setup types
- _repoOne := testRepo()
- _repoOne.SetID(1)
- _repoOne.SetUserID(1)
- _repoOne.SetHash("baz")
- _repoOne.SetOrg("foo")
- _repoOne.SetName("bar")
- _repoOne.SetFullName("foo/bar")
- _repoOne.SetVisibility("public")
- _repoOne.SetPipelineType("yaml")
- _repoOne.SetPreviousName("oldName")
-
- _repoTwo := testRepo()
- _repoTwo.SetID(2)
- _repoTwo.SetUserID(1)
- _repoTwo.SetHash("baz")
- _repoTwo.SetOrg("foo")
- _repoTwo.SetName("baz")
- _repoTwo.SetFullName("foo/baz")
- _repoTwo.SetVisibility("public")
- _repoTwo.SetPipelineType("yaml")
- _repoTwo.SetPreviousName("")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Repo
- }{
- {
- failure: false,
- want: []*library.Repo{_repoOne, _repoTwo},
- },
- }
- filters := map[string]string{}
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- for _, repo := range test.want {
- // create the repo in the database
- err := _database.CreateRepo(repo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
- }
-
- got, err := _database.GetOrgRepoList("foo", filters, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetOrgRepoList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetOrgRepoList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetOrgRepoList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetOrgRepoList_NonAdmin(t *testing.T) {
- // setup types
- _repoOne := testRepo()
- _repoOne.SetID(1)
- _repoOne.SetUserID(1)
- _repoOne.SetHash("baz")
- _repoOne.SetOrg("foo")
- _repoOne.SetName("bar")
- _repoOne.SetFullName("foo/bar")
- _repoOne.SetVisibility("public")
- _repoOne.SetPipelineType("yaml")
- _repoOne.SetPreviousName("")
-
- _repoTwo := testRepo()
- _repoTwo.SetID(2)
- _repoTwo.SetUserID(1)
- _repoTwo.SetHash("baz")
- _repoTwo.SetOrg("foo")
- _repoTwo.SetName("baz")
- _repoTwo.SetFullName("foo/baz")
- _repoTwo.SetVisibility("private")
- _repoTwo.SetPipelineType("yaml")
- _repoTwo.SetPreviousName("")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Repo
- }{
- {
- failure: false,
- want: []*library.Repo{_repoOne},
- },
- }
- filters := map[string]string{}
- filters["visibility"] = "public"
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- for _, repo := range test.want {
- // create the repo in the database
- err := _database.CreateRepo(repo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
- }
-
- got, err := _database.GetOrgRepoList("foo", filters, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetOrgRepoList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetOrgRepoList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetOrgRepoList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetUserRepoList(t *testing.T) {
- // setup types
- _repoOne := testRepo()
- _repoOne.SetID(1)
- _repoOne.SetUserID(1)
- _repoOne.SetHash("baz")
- _repoOne.SetOrg("foo")
- _repoOne.SetName("bar")
- _repoOne.SetFullName("foo/bar")
- _repoOne.SetVisibility("public")
- _repoOne.SetPipelineType("yaml")
- _repoOne.SetPreviousName("")
-
- _repoTwo := testRepo()
- _repoTwo.SetID(2)
- _repoTwo.SetUserID(1)
- _repoTwo.SetHash("baz")
- _repoTwo.SetOrg("bar")
- _repoTwo.SetName("foo")
- _repoTwo.SetFullName("bar/foo")
- _repoTwo.SetVisibility("public")
- _repoTwo.SetPipelineType("yaml")
- _repoTwo.SetPreviousName("")
-
- _user := new(library.User)
- _user.SetID(1)
- _user.SetName("foo")
- _user.SetToken("bar")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Repo
- }{
- {
- failure: false,
- want: []*library.Repo{_repoTwo, _repoOne},
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- for _, repo := range test.want {
- // create the repo in the database
- err := _database.CreateRepo(repo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
- }
-
- got, err := _database.GetUserRepoList(_user, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetUserRepoList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetUserRepoList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetUserRepoList is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/sqlite/repo_test.go b/database/sqlite/repo_test.go
deleted file mode 100644
index a5fc9a925..000000000
--- a/database/sqlite/repo_test.go
+++ /dev/null
@@ -1,274 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package sqlite
-
-import (
- "reflect"
- "testing"
-
- "github.com/go-vela/types/library"
-)
-
-func TestSqlite_Client_GetRepo(t *testing.T) {
- // setup types
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
- _repo.SetPipelineType("yaml")
- _repo.SetPreviousName("")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want *library.Repo
- }{
- {
- failure: false,
- want: _repo,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- if test.want != nil {
- // create the repo in the database
- err := _database.CreateRepo(test.want)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
- }
-
- got, err := _database.GetRepo("foo", "bar")
-
- // cleanup the repos table
- _ = _database.Sqlite.Exec("DELETE FROM repos;")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetRepo should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetRepo returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetRepo is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_CreateRepo(t *testing.T) {
- // setup types
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
- _repo.SetPreviousName("")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- err := _database.CreateRepo(_repo)
-
- if test.failure {
- if err == nil {
- t.Errorf("CreateRepo should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("CreateRepo returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_Client_UpdateRepo(t *testing.T) {
- // setup types
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
- _repo.SetPreviousName("")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- // create the repo in the database
- err := _database.CreateRepo(_repo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
-
- err = _database.UpdateRepo(_repo)
-
- if test.failure {
- if err == nil {
- t.Errorf("UpdateRepo should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("UpdateRepo returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_Client_DeleteRepo(t *testing.T) {
- // setup types
- _repo := testRepo()
- _repo.SetID(1)
- _repo.SetUserID(1)
- _repo.SetHash("baz")
- _repo.SetOrg("foo")
- _repo.SetName("bar")
- _repo.SetFullName("foo/bar")
- _repo.SetVisibility("public")
- _repo.SetPreviousName("")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the repos table
- defer _database.Sqlite.Exec("delete from repos;")
-
- // create the repo in the database
- err = _database.CreateRepo(_repo)
- if err != nil {
- t.Errorf("unable to create test repo: %v", err)
- }
-
- err := _database.DeleteRepo(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("DeleteRepo should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("DeleteRepo returned err: %v", err)
- }
- }
-}
-
-// testRepo is a test helper function to create a
-// library Repo type with all fields set to their
-// zero values.
-func testRepo() *library.Repo {
- i64 := int64(0)
- i := 0
- str := ""
- b := false
-
- return &library.Repo{
- ID: &i64,
- UserID: &i64,
- Hash: &str,
- Org: &str,
- Name: &str,
- FullName: &str,
- Link: &str,
- Clone: &str,
- Branch: &str,
- BuildLimit: &i64,
- Timeout: &i64,
- Counter: &i,
- Visibility: &str,
- Private: &b,
- Trusted: &b,
- Active: &b,
- AllowPull: &b,
- AllowPush: &b,
- AllowDeploy: &b,
- AllowTag: &b,
- AllowComment: &b,
- PreviousName: &str,
- }
-}
diff --git a/database/sqlite/secret.go b/database/sqlite/secret.go
deleted file mode 100644
index 98076763a..000000000
--- a/database/sqlite/secret.go
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package sqlite
-
-import (
- "errors"
- "fmt"
- "strings"
-
- "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"
-)
-
-// GetSecret gets a secret by type, org, name (repo or team) and secret name from the database.
-func (c *client) GetSecret(t, o, n, secretName string) (*library.Secret, error) {
- // create log fields from secret metadata
- fields := logrus.Fields{
- "org": o,
- "repo": n,
- "secret": secretName,
- "type": t,
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(t, constants.SecretShared) {
- // update log fields from secret metadata
- fields = logrus.Fields{
- "org": o,
- "team": n,
- "secret": secretName,
- "type": t,
- }
- }
-
- // nolint: lll // ignore long line length due to parameters
- c.Logger.WithFields(fields).Tracef("getting %s secret %s for %s/%s from the database", t, secretName, o, n)
-
- var err error
-
- // variable to store query results
- s := new(database.Secret)
-
- // send query to the database and store result in variable
- switch t {
- case constants.SecretOrg:
- result := c.Sqlite.
- Table(constants.TableSecret).
- Raw(dml.SelectOrgSecret, o, secretName).
- 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
- }
-
- err = result.Error
- case constants.SecretRepo:
- result := c.Sqlite.
- Table(constants.TableSecret).
- Raw(dml.SelectRepoSecret, o, n, secretName).
- 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
- }
-
- err = result.Error
- case constants.SecretShared:
- result := c.Sqlite.
- Table(constants.TableSecret).
- Raw(dml.SelectSharedSecret, o, n, secretName).
- 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
- }
-
- err = result.Error
- }
- if err != nil {
- return nil, err
- }
-
- // decrypt the value for the secret
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Decrypt
- err = s.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted secrets
- c.Logger.Errorf("unable to decrypt %s secret %s for %s/%s: %v", t, secretName, o, n, err)
-
- // return the unencrypted secret
- return s.ToLibrary(), nil
- }
-
- // return the decrypted secret
- return s.ToLibrary(), nil
-}
-
-// CreateSecret creates a new secret in the database.
-//
-// nolint: dupl // ignore similar code with update
-func (c *client) CreateSecret(s *library.Secret) error {
- // create log fields from secret metadata
- fields := logrus.Fields{
- "org": s.GetOrg(),
- "repo": s.GetRepo(),
- "secret": s.GetName(),
- "type": s.GetType(),
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(s.GetType(), constants.SecretShared) {
- // update log fields from secret metadata
- fields = logrus.Fields{
- "org": s.GetOrg(),
- "team": s.GetTeam(),
- "secret": s.GetName(),
- "type": s.GetType(),
- }
- }
-
- // nolint: lll // ignore long line length due to parameters
- c.Logger.WithFields(fields).Tracef("creating %s secret %s in the database", s.GetType(), s.GetName())
-
- // cast to database type
- secret := database.SecretFromLibrary(s)
-
- // validate the necessary fields are populated
- err := secret.Validate()
- if err != nil {
- return err
- }
-
- // encrypt the value for the secret
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Encrypt
- err = secret.Encrypt(c.config.EncryptionKey)
- if err != nil {
- return fmt.Errorf("unable to encrypt secret %s: %v", s.GetName(), err)
- }
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableSecret).
- Create(secret.Nullify()).Error
-}
-
-// UpdateSecret updates a secret in the database.
-//
-// nolint: dupl // ignore similar code with create
-func (c *client) UpdateSecret(s *library.Secret) error {
- // create log fields from secret metadata
- fields := logrus.Fields{
- "org": s.GetOrg(),
- "repo": s.GetRepo(),
- "secret": s.GetName(),
- "type": s.GetType(),
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(s.GetType(), constants.SecretShared) {
- // update log fields from secret metadata
- fields = logrus.Fields{
- "org": s.GetOrg(),
- "team": s.GetTeam(),
- "secret": s.GetName(),
- "type": s.GetType(),
- }
- }
-
- // nolint: lll // ignore long line length due to parameters
- c.Logger.WithFields(fields).Tracef("updating %s secret %s in the database", s.GetType(), s.GetName())
-
- // cast to database type
- secret := database.SecretFromLibrary(s)
-
- // validate the necessary fields are populated
- err := secret.Validate()
- if err != nil {
- return err
- }
-
- // encrypt the value for the secret
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Encrypt
- err = secret.Encrypt(c.config.EncryptionKey)
- if err != nil {
- return fmt.Errorf("unable to encrypt secret %s: %v", s.GetName(), err)
- }
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableSecret).
- Save(secret.Nullify()).Error
-}
-
-// DeleteSecret deletes a secret by unique ID from the database.
-func (c *client) DeleteSecret(id int64) error {
- c.Logger.Tracef("Deleting secret %d from the database", id)
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableSecret).
- Exec(dml.DeleteSecret, id).Error
-}
diff --git a/database/sqlite/secret_count.go b/database/sqlite/secret_count.go
deleted file mode 100644
index 0cc6c4815..000000000
--- a/database/sqlite/secret_count.go
+++ /dev/null
@@ -1,78 +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 (
- "strings"
-
- "github.com/sirupsen/logrus"
-
- "github.com/go-vela/server/database/sqlite/dml"
- "github.com/go-vela/types/constants"
-)
-
-// GetTypeSecretCount gets a count of secrets by type,
-// owner, and name (repo or team) from the database.
-func (c *client) GetTypeSecretCount(t, o, n string, teams []string) (int64, error) {
- // create log fields from secret metadata
- fields := logrus.Fields{
- "org": o,
- "repo": n,
- "type": t,
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(t, constants.SecretShared) {
- // update log fields from secret metadata
- fields = logrus.Fields{
- "org": o,
- "team": n,
- "type": t,
- }
- }
-
- // nolint: lll // ignore long line length due to parameters
- c.Logger.WithFields(fields).Tracef("getting count of %s secrets for %s/%s from the database", t, o, n)
-
- var err error
-
- // variable to store query results
- var s int64
-
- // send query to the database and store result in variable
- switch t {
- case constants.SecretOrg:
- err = c.Sqlite.
- Table(constants.TableSecret).
- Raw(dml.SelectOrgSecretsCount, o).
- Pluck("count", &s).Error
- case constants.SecretRepo:
- err = c.Sqlite.
- Table(constants.TableSecret).
- Raw(dml.SelectRepoSecretsCount, o, n).
- Pluck("count", &s).Error
- case constants.SecretShared:
- if n == "*" {
- // GitHub teams are not case-sensitive, the DB is lowercase everything for matching
- var lowerTeams []string
- for _, t := range teams {
- lowerTeams = append(lowerTeams, strings.ToLower(t))
- }
- err = c.Sqlite.
- Table(constants.TableSecret).
- Select("count(*)").
- Where("type = 'shared' AND org = ?", o).
- Where("LOWER(team) IN (?)", lowerTeams).
- Pluck("count", &s).Error
- } else {
- err = c.Sqlite.
- Table(constants.TableSecret).
- Raw(dml.SelectSharedSecretsCount, o, n).
- Pluck("count", &s).Error
- }
- }
-
- return s, err
-}
diff --git a/database/sqlite/secret_count_test.go b/database/sqlite/secret_count_test.go
deleted file mode 100644
index 4e5f20ee2..000000000
--- a/database/sqlite/secret_count_test.go
+++ /dev/null
@@ -1,348 +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 secret table
- err = _database.Sqlite.Exec(ddl.CreateSecretTable).Error
- if err != nil {
- log.Fatalf("unable to create %s table: %v", constants.TableSecret, err)
- }
-}
-
-func TestSqlite_Client_GetTypeSecretCount_Org(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetRepo("*")
- _secretOne.SetName("baz")
- _secretOne.SetValue("bar")
- _secretOne.SetType("org")
- _secretOne.SetCreatedAt(1)
- _secretOne.SetCreatedBy("user")
- _secretOne.SetUpdatedAt(1)
- _secretOne.SetUpdatedBy("user2")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(2)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetRepo("*")
- _secretTwo.SetName("bar")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("org")
- _secretTwo.SetCreatedAt(1)
- _secretTwo.SetCreatedBy("user")
- _secretTwo.SetUpdatedAt(1)
- _secretTwo.SetUpdatedBy("user2")
-
- // 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 secrets table
- defer _database.Sqlite.Exec("delete from secrets;")
-
- // create the secrets in the database
- err := _database.CreateSecret(_secretOne)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
-
- err = _database.CreateSecret(_secretTwo)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
-
- got, err := _database.GetTypeSecretCount("org", "foo", "*", []string{})
-
- if test.failure {
- if err == nil {
- t.Errorf("GetTypeSecretCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetTypeSecretCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetTypeSecretCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetTypeSecretCount_Repo(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetRepo("bar")
- _secretOne.SetName("baz")
- _secretOne.SetValue("foob")
- _secretOne.SetType("repo")
- _secretOne.SetCreatedAt(1)
- _secretOne.SetCreatedBy("user")
- _secretOne.SetUpdatedAt(1)
- _secretOne.SetUpdatedBy("user2")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(2)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetRepo("bar")
- _secretTwo.SetName("foob")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("repo")
- _secretTwo.SetCreatedAt(1)
- _secretTwo.SetCreatedBy("user")
- _secretTwo.SetUpdatedAt(1)
- _secretTwo.SetUpdatedBy("user2")
-
- // 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 secrets table
- defer _database.Sqlite.Exec("delete from secrets;")
-
- // create the secrets in the database
- err := _database.CreateSecret(_secretOne)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
-
- err = _database.CreateSecret(_secretTwo)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
-
- got, err := _database.GetTypeSecretCount("repo", "foo", "bar", []string{})
-
- if test.failure {
- if err == nil {
- t.Errorf("GetTypeSecretCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetTypeSecretCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetTypeSecretCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetTypeSecretCount_Shared(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetTeam("bar")
- _secretOne.SetName("baz")
- _secretOne.SetValue("foob")
- _secretOne.SetType("shared")
- _secretOne.SetCreatedAt(1)
- _secretOne.SetCreatedBy("user")
- _secretOne.SetUpdatedAt(1)
- _secretOne.SetUpdatedBy("user2")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(2)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetTeam("bar")
- _secretTwo.SetName("foob")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("shared")
- _secretTwo.SetCreatedAt(1)
- _secretTwo.SetCreatedBy("user")
- _secretTwo.SetUpdatedAt(1)
- _secretTwo.SetUpdatedBy("user2")
-
- // 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 secrets table
- defer _database.Sqlite.Exec("delete from secrets;")
-
- // create the secrets in the database
- err := _database.CreateSecret(_secretOne)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
-
- err = _database.CreateSecret(_secretTwo)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
-
- got, err := _database.GetTypeSecretCount("shared", "foo", "bar", []string{})
-
- if test.failure {
- if err == nil {
- t.Errorf("GetTypeSecretCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetTypeSecretCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetTypeSecretCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetTypeSecretCount_Shared_Wildcard(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetTeam("bar")
- _secretOne.SetName("baz")
- _secretOne.SetValue("foob")
- _secretOne.SetType("shared")
- _secretOne.SetCreatedAt(1)
- _secretOne.SetCreatedBy("user")
- _secretOne.SetUpdatedAt(1)
- _secretOne.SetUpdatedBy("user2")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(2)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetTeam("bared")
- _secretTwo.SetName("foob")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("shared")
- _secretTwo.SetCreatedAt(1)
- _secretTwo.SetCreatedBy("user")
- _secretTwo.SetUpdatedAt(1)
- _secretTwo.SetUpdatedBy("user2")
-
- // 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 secrets table
- defer _database.Sqlite.Exec("delete from secrets;")
-
- // create the secrets in the database
- err := _database.CreateSecret(_secretOne)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
-
- err = _database.CreateSecret(_secretTwo)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
-
- got, err := _database.GetTypeSecretCount("shared", "foo", "*", []string{"bar", "bared"})
-
- if test.failure {
- if err == nil {
- t.Errorf("GetTypeSecretCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetTypeSecretCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetTypeSecretCount is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/sqlite/secret_list.go b/database/sqlite/secret_list.go
deleted file mode 100644
index 41650b985..000000000
--- a/database/sqlite/secret_list.go
+++ /dev/null
@@ -1,154 +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 (
- "strings"
-
- "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"
-)
-
-// GetSecretList gets a list of all secrets from the database.
-//
-// nolint: dupl // ignore false positive of duplicate code
-func (c *client) GetSecretList() ([]*library.Secret, error) {
- c.Logger.Tracef("listing secrets from the database")
-
- // variable to store query results
- s := new([]database.Secret)
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableSecret).
- Raw(dml.ListSecrets).
- Scan(s).Error
- if err != nil {
- return nil, err
- }
-
- // variable we want to return
- secrets := []*library.Secret{}
- // iterate through all query results
- for _, secret := range *s {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := secret
-
- // decrypt the value for the secret
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Decrypt
- err = tmp.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted secrets
- c.Logger.Errorf("unable to decrypt secret %d: %v", tmp.ID.Int64, err)
- }
-
- // convert query result to library type
- secrets = append(secrets, tmp.ToLibrary())
- }
-
- return secrets, nil
-}
-
-// GetTypeSecretList gets a list of secrets by type,
-// owner, and name (repo or team) from the database.
-//
-// nolint: lll // ignore long line length
-func (c *client) GetTypeSecretList(t, o, n string, page, perPage int, teams []string) ([]*library.Secret, error) {
- // create log fields from secret metadata
- fields := logrus.Fields{
- "org": o,
- "repo": n,
- "type": t,
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(t, constants.SecretShared) {
- // update log fields from secret metadata
- fields = logrus.Fields{
- "org": o,
- "team": n,
- "type": t,
- }
- }
-
- // nolint: lll // ignore long line length due to parameters
- c.Logger.WithFields(fields).Tracef("listing %s secrets for %s/%s from the database", t, o, n)
-
- var err error
-
- // variable to store query results
- s := new([]database.Secret)
- // calculate offset for pagination through results
- offset := perPage * (page - 1)
-
- // send query to the database and store result in variable
- switch t {
- case constants.SecretOrg:
- err = c.Sqlite.
- Table(constants.TableSecret).
- Raw(dml.ListOrgSecrets, o, perPage, offset).
- Scan(s).Error
- case constants.SecretRepo:
- err = c.Sqlite.
- Table(constants.TableSecret).
- Raw(dml.ListRepoSecrets, o, n, perPage, offset).
- Scan(s).Error
- case constants.SecretShared:
- if n == "*" {
- // GitHub teams are not case-sensitive, the DB is lowercase everything for matching
- var lowerTeams []string
- for _, t := range teams {
- lowerTeams = append(lowerTeams, strings.ToLower(t))
- }
- err = c.Sqlite.
- Table(constants.TableSecret).
- Where("type = 'shared' AND org = ?", o).
- Where("LOWER(team) IN (?)", lowerTeams).
- Order("id DESC").
- Limit(perPage).
- Offset(offset).
- Scan(s).Error
- } else {
- err = c.Sqlite.
- Table(constants.TableSecret).
- Raw(dml.ListSharedSecrets, o, n, perPage, offset).
- Scan(s).Error
- }
- }
- if err != nil {
- return nil, err
- }
-
- // variable we want to return
- secrets := []*library.Secret{}
- // iterate through all query results
- for _, secret := range *s {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := secret
-
- // decrypt the value for the secret
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#Secret.Decrypt
- err = tmp.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted secrets
- c.Logger.Errorf("unable to decrypt secret %d: %v", tmp.ID.Int64, err)
- }
-
- // convert query result to library type
- secrets = append(secrets, tmp.ToLibrary())
- }
-
- return secrets, nil
-}
diff --git a/database/sqlite/secret_list_test.go b/database/sqlite/secret_list_test.go
deleted file mode 100644
index f9a48a183..000000000
--- a/database/sqlite/secret_list_test.go
+++ /dev/null
@@ -1,374 +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 secret table
- err = _database.Sqlite.Exec(ddl.CreateSecretTable).Error
- if err != nil {
- log.Fatalf("unable to create %s table: %v", constants.TableSecret, err)
- }
-}
-
-func TestSqlite_Client_GetSecretList(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetRepo("bar")
- _secretOne.SetName("baz")
- _secretOne.SetValue("foob")
- _secretOne.SetType("repo")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(2)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetRepo("bar")
- _secretTwo.SetName("foob")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("repo")
-
- // 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.Secret
- }{
- {
- failure: false,
- want: []*library.Secret{_secretOne, _secretTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the secrets table
- defer _database.Sqlite.Exec("delete from secrets;")
-
- for _, secret := range test.want {
- // create the secret in the database
- err := _database.CreateSecret(secret)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
- }
-
- got, err := _database.GetSecretList()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetSecretList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetSecretList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetSecretList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetTypeSecretList_Org(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetRepo("*")
- _secretOne.SetName("baz")
- _secretOne.SetValue("bar")
- _secretOne.SetType("org")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(2)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetRepo("*")
- _secretTwo.SetName("bar")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("org")
-
- // 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.Secret
- }{
- {
- failure: false,
- want: []*library.Secret{_secretTwo, _secretOne},
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the secrets table
- defer _database.Sqlite.Exec("delete from secrets;")
-
- for _, secret := range test.want {
- // create the secret in the database
- err := _database.CreateSecret(secret)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
- }
-
- got, err := _database.GetTypeSecretList("org", "foo", "*", 1, 10, []string{})
-
- if test.failure {
- if err == nil {
- t.Errorf("GetTypeSecretList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetTypeSecretList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetTypeSecretList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetTypeSecretList_Repo(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetRepo("bar")
- _secretOne.SetName("baz")
- _secretOne.SetValue("foob")
- _secretOne.SetType("repo")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(2)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetRepo("bar")
- _secretTwo.SetName("foob")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("repo")
-
- // 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.Secret
- }{
- {
- failure: false,
- want: []*library.Secret{_secretTwo, _secretOne},
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the secrets table
- defer _database.Sqlite.Exec("delete from secrets;")
-
- for _, secret := range test.want {
- // create the secret in the database
- err := _database.CreateSecret(secret)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
- }
-
- got, err := _database.GetTypeSecretList("repo", "foo", "bar", 1, 10, []string{})
-
- if test.failure {
- if err == nil {
- t.Errorf("GetTypeSecretList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetTypeSecretList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetTypeSecretList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetTypeSecretList_Shared(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetTeam("bar")
- _secretOne.SetName("baz")
- _secretOne.SetValue("foob")
- _secretOne.SetType("shared")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(2)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetTeam("bar")
- _secretTwo.SetName("foob")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("shared")
-
- // 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.Secret
- }{
- {
- failure: false,
- want: []*library.Secret{_secretTwo, _secretOne},
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the secrets table
- defer _database.Sqlite.Exec("delete from secrets;")
-
- for _, secret := range test.want {
- // create the secret in the database
- err := _database.CreateSecret(secret)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
- }
-
- got, err := _database.GetTypeSecretList("shared", "foo", "bar", 1, 10, []string{})
-
- if test.failure {
- if err == nil {
- t.Errorf("GetTypeSecretList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetTypeSecretList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetTypeSecretList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetTypeSecretList_Shared_wildcard(t *testing.T) {
- // setup types
- _secretOne := testSecret()
- _secretOne.SetID(1)
- _secretOne.SetOrg("foo")
- _secretOne.SetTeam("bar")
- _secretOne.SetName("baz")
- _secretOne.SetValue("foob")
- _secretOne.SetType("shared")
-
- _secretTwo := testSecret()
- _secretTwo.SetID(2)
- _secretTwo.SetOrg("foo")
- _secretTwo.SetTeam("bared")
- _secretTwo.SetName("foob")
- _secretTwo.SetValue("baz")
- _secretTwo.SetType("shared")
-
- // 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.Secret
- }{
- {
- failure: false,
- want: []*library.Secret{_secretTwo, _secretOne},
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the secrets table
- defer _database.Sqlite.Exec("delete from secrets;")
-
- for _, secret := range test.want {
- // create the secret in the database
- err := _database.CreateSecret(secret)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
- }
-
- got, err := _database.GetTypeSecretList("shared", "foo", "*", 1, 10, []string{"bar", "bared"})
-
- if test.failure {
- if err == nil {
- t.Errorf("GetTypeSecretList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetTypeSecretList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetTypeSecretList is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/sqlite/secret_test.go b/database/sqlite/secret_test.go
deleted file mode 100644
index 2d92fa710..000000000
--- a/database/sqlite/secret_test.go
+++ /dev/null
@@ -1,410 +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_GetSecret_Org(t *testing.T) {
- // setup types
- _secret := testSecret()
- _secret.SetID(1)
- _secret.SetOrg("foo")
- _secret.SetRepo("*")
- _secret.SetName("bar")
- _secret.SetValue("baz")
- _secret.SetType("org")
- _secret.SetCreatedAt(1)
- _secret.SetCreatedBy("user")
- _secret.SetUpdatedAt(1)
- _secret.SetUpdatedBy("user2")
- // 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.Secret
- }{
- {
- failure: false,
- want: _secret,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- if test.want != nil {
- // create the secret in the database
- err := _database.CreateSecret(test.want)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
- }
-
- got, err := _database.GetSecret("org", "foo", "*", "bar")
-
- // cleanup the secrets table
- _ = _database.Sqlite.Exec("DELETE FROM secrets;")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetSecret should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetSecret returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetSecret is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetSecret_Repo(t *testing.T) {
- // setup types
- _secret := testSecret()
- _secret.SetID(1)
- _secret.SetOrg("foo")
- _secret.SetRepo("bar")
- _secret.SetName("baz")
- _secret.SetValue("foob")
- _secret.SetType("repo")
- _secret.SetCreatedAt(1)
- _secret.SetCreatedBy("user")
- _secret.SetUpdatedAt(1)
- _secret.SetUpdatedBy("user2")
-
- // 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.Secret
- }{
- {
- failure: false,
- want: _secret,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- if test.want != nil {
- // create the secret in the database
- err := _database.CreateSecret(test.want)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
- }
-
- got, err := _database.GetSecret("repo", "foo", "bar", "baz")
-
- // cleanup the secrets table
- _ = _database.Sqlite.Exec("DELETE FROM secrets;")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetSecret should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetSecret returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetSecret is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetSecret_Shared(t *testing.T) {
- // setup types
- _secret := testSecret()
- _secret.SetID(1)
- _secret.SetOrg("foo")
- _secret.SetTeam("bar")
- _secret.SetName("baz")
- _secret.SetValue("foob")
- _secret.SetType("shared")
- _secret.SetCreatedAt(1)
- _secret.SetCreatedBy("user")
- _secret.SetUpdatedAt(1)
- _secret.SetUpdatedBy("user2")
-
- // 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.Secret
- }{
- {
- failure: false,
- want: _secret,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- if test.want != nil {
- // create the secret in the database
- err := _database.CreateSecret(test.want)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
- }
-
- got, err := _database.GetSecret("shared", "foo", "bar", "baz")
-
- // cleanup the secrets table
- _ = _database.Sqlite.Exec("DELETE FROM secrets;")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetSecret should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetSecret returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetSecret is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_CreateSecret(t *testing.T) {
- // setup types
- _secret := testSecret()
- _secret.SetID(1)
- _secret.SetOrg("foo")
- _secret.SetRepo("bar")
- _secret.SetName("baz")
- _secret.SetValue("foob")
- _secret.SetType("repo")
- _secret.SetCreatedAt(1)
- _secret.SetCreatedBy("user")
- _secret.SetUpdatedAt(1)
- _secret.SetUpdatedBy("user2")
-
- // 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 secrets table
- defer _database.Sqlite.Exec("delete from secrets;")
-
- err := _database.CreateSecret(_secret)
-
- if test.failure {
- if err == nil {
- t.Errorf("CreateSecret should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("CreateSecret returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_Client_UpdateSecret(t *testing.T) {
- // setup types
- _secret := testSecret()
- _secret.SetID(1)
- _secret.SetOrg("foo")
- _secret.SetRepo("bar")
- _secret.SetName("baz")
- _secret.SetValue("foob")
- _secret.SetType("repo")
- _secret.SetCreatedAt(1)
- _secret.SetCreatedBy("user")
- _secret.SetUpdatedAt(1)
- _secret.SetUpdatedBy("user2")
-
- // 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 secrets table
- defer _database.Sqlite.Exec("delete from secrets;")
-
- // create the secret in the database
- err := _database.CreateSecret(_secret)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
-
- err = _database.UpdateSecret(_secret)
-
- if test.failure {
- if err == nil {
- t.Errorf("UpdateSecret should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("UpdateSecret returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_Client_DeleteSecret(t *testing.T) {
- // setup types
- _secret := testSecret()
- _secret.SetID(1)
- _secret.SetOrg("foo")
- _secret.SetRepo("bar")
- _secret.SetName("baz")
- _secret.SetValue("foob")
- _secret.SetType("repo")
- _secret.SetCreatedAt(1)
- _secret.SetCreatedBy("user")
- _secret.SetUpdatedAt(1)
- _secret.SetUpdatedBy("user2")
-
- // 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 secrets table
- defer _database.Sqlite.Exec("delete from secrets;")
-
- // create the secret in the database
- err := _database.CreateSecret(_secret)
- if err != nil {
- t.Errorf("unable to create test secret: %v", err)
- }
-
- err = _database.DeleteSecret(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("DeleteSecret should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("DeleteSecret returned err: %v", err)
- }
- }
-}
-
-// testSecret is a test helper function to create a
-// library Secret type with all fields set to their
-// zero values.
-func testSecret() *library.Secret {
- i64 := int64(0)
- str := ""
- booL := false
- var arr []string
-
- return &library.Secret{
- ID: &i64,
- Org: &str,
- Repo: &str,
- Team: &str,
- Name: &str,
- Value: &str,
- Type: &str,
- Images: &arr,
- Events: &arr,
- AllowCommand: &booL,
- CreatedAt: &i64,
- CreatedBy: &str,
- UpdatedAt: &i64,
- UpdatedBy: &str,
- }
-}
diff --git a/database/sqlite/service.go b/database/sqlite/service.go
deleted file mode 100644
index ecc9dad6b..000000000
--- a/database/sqlite/service.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"
-)
-
-// GetService gets a service by number and build ID from the database.
-func (c *client) GetService(number int, b *library.Build) (*library.Service, error) {
- c.Logger.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- "service": number,
- }).Tracef("getting service %d for build %d from the database", number, b.GetNumber())
-
- // variable to store query results
- s := new(database.Service)
-
- // send query to the database and store result in variable
- result := c.Sqlite.
- Table(constants.TableService).
- Raw(dml.SelectBuildService, 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
-}
-
-// CreateService creates a new service in the database.
-func (c *client) CreateService(s *library.Service) error {
- c.Logger.WithFields(logrus.Fields{
- "service": s.GetNumber(),
- }).Tracef("creating service %s in the database", s.GetName())
-
- // cast to database type
- service := database.ServiceFromLibrary(s)
-
- // validate the necessary fields are populated
- err := service.Validate()
- if err != nil {
- return err
- }
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableService).
- Create(service).Error
-}
-
-// UpdateService updates a service in the database.
-func (c *client) UpdateService(s *library.Service) error {
- c.Logger.WithFields(logrus.Fields{
- "service": s.GetNumber(),
- }).Tracef("updating service %s in the database", s.GetName())
-
- // cast to database type
- service := database.ServiceFromLibrary(s)
-
- // validate the necessary fields are populated
- err := service.Validate()
- if err != nil {
- return err
- }
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableService).
- Save(service).Error
-}
-
-// DeleteService deletes a service by unique ID from the database.
-func (c *client) DeleteService(id int64) error {
- c.Logger.Tracef("deleting service %d from the database", id)
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableService).
- Exec(dml.DeleteService, id).Error
-}
diff --git a/database/sqlite/service_count.go b/database/sqlite/service_count.go
deleted file mode 100644
index 4979e9527..000000000
--- a/database/sqlite/service_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"
-)
-
-// GetBuildServiceCount gets a count of all services by build ID from the database.
-func (c *client) GetBuildServiceCount(b *library.Build) (int64, error) {
- c.Logger.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- }).Tracef("getting count of services 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.TableService).
- Raw(dml.SelectBuildServicesCount, b.GetID()).
- Pluck("count", &s).Error
-
- return s, err
-}
-
-// GetServiceImageCount gets a count of all service images
-// and the count of their occurrence in the database.
-func (c *client) GetServiceImageCount() (map[string]float64, error) {
- c.Logger.Tracef("getting count of all images for services 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.TableService).
- Raw(dml.SelectServiceImagesCount).
- Scan(images).Error
-
- for _, image := range *images {
- counts[image.Image] = float64(image.Count)
- }
-
- return counts, err
-}
-
-// GetServiceStatusCount gets a list of all service statuses
-// and the count of their occurrence in the database.
-func (c *client) GetServiceStatusCount() (map[string]float64, error) {
- c.Logger.Trace("getting count of all statuses for services 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.TableService).
- Raw(dml.SelectServiceStatusesCount).
- Scan(s).Error
-
- for _, status := range *s {
- counts[status.Status] = float64(status.Count)
- }
-
- return counts, err
-}
diff --git a/database/sqlite/service_count_test.go b/database/sqlite/service_count_test.go
deleted file mode 100644
index 7b0d5eaba..000000000
--- a/database/sqlite/service_count_test.go
+++ /dev/null
@@ -1,197 +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 service table
- err = _database.Sqlite.Exec(ddl.CreateServiceTable).Error
- if err != nil {
- log.Fatalf("unable to create %s table: %v", constants.TableService, err)
- }
-}
-
-func TestSqlite_Client_GetBuildServiceCount(t *testing.T) {
- // setup types
- _build := testBuild()
- _build.SetID(1)
- _build.SetRepoID(1)
- _build.SetNumber(1)
-
- _serviceOne := testService()
- _serviceOne.SetID(1)
- _serviceOne.SetRepoID(1)
- _serviceOne.SetBuildID(1)
- _serviceOne.SetNumber(1)
- _serviceOne.SetName("foo")
- _serviceOne.SetImage("bar")
-
- _serviceTwo := testService()
- _serviceTwo.SetID(2)
- _serviceTwo.SetRepoID(1)
- _serviceTwo.SetBuildID(1)
- _serviceTwo.SetNumber(2)
- _serviceTwo.SetName("bar")
- _serviceTwo.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 services table
- defer _database.Sqlite.Exec("delete from services;")
-
- // create the services in the database
- err := _database.CreateService(_serviceOne)
- if err != nil {
- t.Errorf("unable to create test service: %v", err)
- }
-
- err = _database.CreateService(_serviceTwo)
- if err != nil {
- t.Errorf("unable to create test service: %v", err)
- }
-
- got, err := _database.GetBuildServiceCount(_build)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetBuildServiceCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetBuildServiceCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetBuildServiceCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetServiceImageCount(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.GetServiceImageCount()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetServiceImageCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetServiceImageCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetServiceImageCount is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetServiceStatusCount(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.GetServiceStatusCount()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetServiceStatusCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetServiceStatusCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetServiceStatusCount is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/sqlite/service_list.go b/database/sqlite/service_list.go
deleted file mode 100644
index 390fbdf76..000000000
--- a/database/sqlite/service_list.go
+++ /dev/null
@@ -1,73 +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"
-)
-
-// GetServiceList gets a list of all services from the database.
-func (c *client) GetServiceList() ([]*library.Service, error) {
- c.Logger.Trace("listing services from the database")
-
- // variable to store query results
- s := new([]database.Service)
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableService).
- Raw(dml.ListServices).
- Scan(s).Error
-
- // variable we want to return
- services := []*library.Service{}
- // iterate through all query results
- for _, service := range *s {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := service
-
- // convert query result to library type
- services = append(services, tmp.ToLibrary())
- }
-
- return services, err
-}
-
-// GetBuildServiceList gets a list of services by build ID from the database.
-//
-// nolint: lll // ignore long line length due to parameters
-func (c *client) GetBuildServiceList(b *library.Build, page, perPage int) ([]*library.Service, error) {
- c.Logger.WithFields(logrus.Fields{
- "build": b.GetNumber(),
- }).Tracef("listing services for build %d from the database", b.GetNumber())
-
- // variable to store query results
- s := new([]database.Service)
- // 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.TableService).
- Raw(dml.ListBuildServices, b.GetID(), perPage, offset).
- Scan(s).Error
-
- // variable we want to return
- services := []*library.Service{}
- // iterate through all query results
- for _, service := range *s {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := service
-
- // convert query result to library type
- services = append(services, tmp.ToLibrary())
- }
-
- return services, err
-}
diff --git a/database/sqlite/service_list_test.go b/database/sqlite/service_list_test.go
deleted file mode 100644
index 200316325..000000000
--- a/database/sqlite/service_list_test.go
+++ /dev/null
@@ -1,172 +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 service table
- err = _database.Sqlite.Exec(ddl.CreateServiceTable).Error
- if err != nil {
- log.Fatalf("unable to create %s table: %v", constants.TableService, err)
- }
-}
-
-func TestSqlite_Client_GetServiceList(t *testing.T) {
- // setup types
- _serviceOne := testService()
- _serviceOne.SetID(1)
- _serviceOne.SetRepoID(1)
- _serviceOne.SetBuildID(1)
- _serviceOne.SetNumber(1)
- _serviceOne.SetName("foo")
- _serviceOne.SetImage("bar")
-
- _serviceTwo := testService()
- _serviceTwo.SetID(2)
- _serviceTwo.SetRepoID(1)
- _serviceTwo.SetBuildID(1)
- _serviceTwo.SetNumber(2)
- _serviceTwo.SetName("bar")
- _serviceTwo.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.Service
- }{
- {
- failure: false,
- want: []*library.Service{_serviceOne, _serviceTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the services table
- defer _database.Sqlite.Exec("delete from services;")
-
- for _, service := range test.want {
- // create the service in the database
- err := _database.CreateService(service)
- if err != nil {
- t.Errorf("unable to create test service: %v", err)
- }
- }
-
- got, err := _database.GetServiceList()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetServiceList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetServiceList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetServiceList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetBuildServiceList(t *testing.T) {
- // setup types
- _build := testBuild()
- _build.SetID(1)
- _build.SetRepoID(1)
- _build.SetNumber(1)
-
- _serviceOne := testService()
- _serviceOne.SetID(1)
- _serviceOne.SetRepoID(1)
- _serviceOne.SetBuildID(1)
- _serviceOne.SetNumber(1)
- _serviceOne.SetName("foo")
- _serviceOne.SetImage("bar")
-
- _serviceTwo := testService()
- _serviceTwo.SetID(2)
- _serviceTwo.SetRepoID(1)
- _serviceTwo.SetBuildID(1)
- _serviceTwo.SetNumber(2)
- _serviceTwo.SetName("bar")
- _serviceTwo.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.Service
- }{
- {
- failure: false,
- want: []*library.Service{_serviceTwo, _serviceOne},
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the services table
- defer _database.Sqlite.Exec("delete from services;")
-
- for _, service := range test.want {
- // create the service in the database
- err := _database.CreateService(service)
- if err != nil {
- t.Errorf("unable to create test service: %v", err)
- }
- }
-
- got, err := _database.GetBuildServiceList(_build, 1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetBuildServiceList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetBuildServiceList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetBuildServiceList is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/sqlite/service_test.go b/database/sqlite/service_test.go
deleted file mode 100644
index 006a3ece6..000000000
--- a/database/sqlite/service_test.go
+++ /dev/null
@@ -1,262 +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_GetService(t *testing.T) {
- // setup types
- _build := testBuild()
- _build.SetID(1)
- _build.SetRepoID(1)
- _build.SetNumber(1)
-
- _service := testService()
- _service.SetID(1)
- _service.SetRepoID(1)
- _service.SetBuildID(1)
- _service.SetNumber(1)
- _service.SetName("foo")
- _service.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.Service
- }{
- {
- failure: false,
- want: _service,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- if test.want != nil {
- // create the service in the database
- err := _database.CreateService(test.want)
- if err != nil {
- t.Errorf("unable to create test service: %v", err)
- }
- }
-
- got, err := _database.GetService(1, _build)
-
- // cleanup the services table
- _ = _database.Sqlite.Exec("DELETE FROM services;")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetService should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetService returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetService is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_CreateService(t *testing.T) {
- // setup types
- _service := testService()
- _service.SetID(1)
- _service.SetRepoID(1)
- _service.SetBuildID(1)
- _service.SetNumber(1)
- _service.SetName("foo")
- _service.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 services table
- defer _database.Sqlite.Exec("delete from services;")
-
- err := _database.CreateService(_service)
-
- if test.failure {
- if err == nil {
- t.Errorf("CreateService should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("CreateService returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_Client_UpdateService(t *testing.T) {
- // setup types
- _service := testService()
- _service.SetID(1)
- _service.SetRepoID(1)
- _service.SetBuildID(1)
- _service.SetNumber(1)
- _service.SetName("foo")
- _service.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 services table
- defer _database.Sqlite.Exec("delete from services;")
-
- // create the service in the database
- err := _database.CreateService(_service)
- if err != nil {
- t.Errorf("unable to create test service: %v", err)
- }
-
- err = _database.UpdateService(_service)
-
- if test.failure {
- if err == nil {
- t.Errorf("UpdateService should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("UpdateService returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_Client_DeleteService(t *testing.T) {
- // setup types
- _service := testService()
- _service.SetID(1)
- _service.SetRepoID(1)
- _service.SetBuildID(1)
- _service.SetNumber(1)
- _service.SetName("foo")
- _service.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 services table
- defer _database.Sqlite.Exec("delete from services;")
-
- // create the service in the database
- err := _database.CreateService(_service)
- if err != nil {
- t.Errorf("unable to create test service: %v", err)
- }
-
- err = _database.DeleteService(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("DeleteService should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("DeleteService returned err: %v", err)
- }
- }
-}
-
-// testService is a test helper function to create a
-// library Service type with all fields set to their
-// zero values.
-func testService() *library.Service {
- i64 := int64(0)
- i := 0
- str := ""
-
- return &library.Service{
- ID: &i64,
- BuildID: &i64,
- RepoID: &i64,
- Number: &i,
- Name: &str,
- Image: &str,
- Status: &str,
- Error: &str,
- ExitCode: &i,
- Created: &i64,
- Started: &i64,
- Finished: &i64,
- Host: &str,
- Runtime: &str,
- Distribution: &str,
- }
-}
diff --git a/database/sqlite/sqlite.go b/database/sqlite/sqlite.go
deleted file mode 100644
index 6750540bd..000000000
--- a/database/sqlite/sqlite.go
+++ /dev/null
@@ -1,336 +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 (
- "fmt"
- "time"
-
- "github.com/go-vela/server/database/sqlite/ddl"
- "github.com/go-vela/types/constants"
- "github.com/sirupsen/logrus"
-
- "gorm.io/driver/sqlite"
- "gorm.io/gorm"
-)
-
-type (
- config struct {
- // specifies the address to use for the Sqlite client
- Address string
- // specifies the level of compression to use for the Sqlite client
- CompressionLevel int
- // specifies the connection duration to use for the Sqlite client
- ConnectionLife time.Duration
- // specifies the maximum idle connections for the Sqlite client
- ConnectionIdle int
- // specifies the maximum open connections for the Sqlite client
- ConnectionOpen int
- // specifies the encryption key to use for the Sqlite client
- EncryptionKey string
- // specifies to skip creating tables and indexes for the Sqlite client
- SkipCreation bool
- }
-
- client struct {
- config *config
- Sqlite *gorm.DB
- // https://pkg.go.dev/github.com/sirupsen/logrus#Entry
- Logger *logrus.Entry
- }
-)
-
-// New returns a Database implementation that integrates with a Sqlite instance.
-//
-// nolint: revive // ignore returning unexported client
-func New(opts ...ClientOpt) (*client, error) {
- // create new Sqlite client
- c := new(client)
-
- // create new fields
- c.config = new(config)
- c.Sqlite = new(gorm.DB)
-
- // create new logger for the client
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#StandardLogger
- logger := logrus.StandardLogger()
-
- // create new logger for the client
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#NewEntry
- c.Logger = logrus.NewEntry(logger).WithField("database", c.Driver())
-
- // apply all provided configuration options
- for _, opt := range opts {
- err := opt(c)
- if err != nil {
- return nil, err
- }
- }
-
- // create the new Sqlite database client
- //
- // https://pkg.go.dev/gorm.io/gorm#Open
- _sqlite, err := gorm.Open(sqlite.Open(c.config.Address), &gorm.Config{})
- if err != nil {
- return nil, err
- }
-
- // set the Sqlite database client in the Sqlite client
- c.Sqlite = _sqlite
-
- // setup database with proper configuration
- err = setupDatabase(c)
- if err != nil {
- return nil, err
- }
-
- return c, nil
-}
-
-// NewTest returns a Database implementation that integrates with a fake Sqlite instance.
-//
-// This function is intended for running tests only.
-//
-// nolint: revive // ignore returning unexported client
-func NewTest() (*client, error) {
- // create new Sqlite client
- c := new(client)
-
- // create new fields
- c.config = &config{
- CompressionLevel: 3,
- ConnectionLife: 30 * time.Minute,
- ConnectionIdle: 2,
- ConnectionOpen: 0,
- EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
- SkipCreation: false,
- }
- c.Sqlite = new(gorm.DB)
-
- // create new logger for the client
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#StandardLogger
- logger := logrus.StandardLogger()
-
- // create new logger for the client
- //
- // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#NewEntry
- c.Logger = logrus.NewEntry(logger)
-
- // create the new Sqlite database client
- //
- // https://pkg.go.dev/gorm.io/gorm#Open
- _sqlite, err := gorm.Open(
- sqlite.Open("file::memory:?cache=shared"),
- &gorm.Config{SkipDefaultTransaction: true},
- )
- if err != nil {
- return nil, err
- }
-
- c.Sqlite = _sqlite
-
- // create the tables in the database
- err = createTables(c)
- if err != nil {
- return nil, err
- }
-
- return c, nil
-}
-
-// setupDatabase is a helper function to setup
-// the database with the proper configuration.
-func setupDatabase(c *client) error {
- // capture database/sql database from gorm database
- //
- // https://pkg.go.dev/gorm.io/gorm#DB.DB
- _sql, err := c.Sqlite.DB()
- if err != nil {
- return err
- }
-
- // set the maximum amount of time a connection may be reused
- //
- // https://golang.org/pkg/database/sql/#DB.SetConnMaxLifetime
- _sql.SetConnMaxLifetime(c.config.ConnectionLife)
-
- // set the maximum number of connections in the idle connection pool
- //
- // https://golang.org/pkg/database/sql/#DB.SetMaxIdleConns
- _sql.SetMaxIdleConns(c.config.ConnectionIdle)
-
- // set the maximum number of open connections to the database
- //
- // https://golang.org/pkg/database/sql/#DB.SetMaxOpenConns
- _sql.SetMaxOpenConns(c.config.ConnectionOpen)
-
- // verify connection to the database
- err = c.Ping()
- if err != nil {
- return err
- }
-
- // check if we should skip creating database objects
- if c.config.SkipCreation {
- c.Logger.Warning("skipping creation of data tables and indexes in the sqlite database")
-
- return nil
- }
-
- // create the tables in the database
- err = createTables(c)
- if err != nil {
- return err
- }
-
- // create the indexes in the database
- err = createIndexes(c)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-// createTables is a helper function to setup
-// the database with the necessary tables.
-func createTables(c *client) error {
- c.Logger.Trace("creating data tables in the sqlite database")
-
- // create the builds table
- err := c.Sqlite.Exec(ddl.CreateBuildTable).Error
- if err != nil {
- return fmt.Errorf("unable to create %s table: %v", constants.TableBuild, err)
- }
-
- // create the hooks table
- err = c.Sqlite.Exec(ddl.CreateHookTable).Error
- if err != nil {
- return fmt.Errorf("unable to create %s table: %v", constants.TableHook, err)
- }
-
- // create the logs table
- err = c.Sqlite.Exec(ddl.CreateLogTable).Error
- if err != nil {
- return fmt.Errorf("unable to create %s table: %v", constants.TableLog, err)
- }
-
- // create the repos table
- err = c.Sqlite.Exec(ddl.CreateRepoTable).Error
- if err != nil {
- return fmt.Errorf("unable to create %s table: %v", constants.TableRepo, err)
- }
-
- // create the secrets table
- err = c.Sqlite.Exec(ddl.CreateSecretTable).Error
- if err != nil {
- return fmt.Errorf("unable to create %s table: %v", constants.TableSecret, err)
- }
-
- // create the services table
- err = c.Sqlite.Exec(ddl.CreateServiceTable).Error
- if err != nil {
- return fmt.Errorf("unable to create %s table: %v", 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: %v", constants.TableStep, err)
- }
-
- // create the users table
- err = c.Sqlite.Exec(ddl.CreateUserTable).Error
- if err != nil {
- return fmt.Errorf("unable to create %s table: %v", constants.TableUser, err)
- }
-
- // create the workers table
- err = c.Sqlite.Exec(ddl.CreateWorkerTable).Error
- if err != nil {
- return fmt.Errorf("unable to create %s table: %v", constants.TableWorker, err)
- }
-
- return nil
-}
-
-// createIndexes is a helper function to setup
-// the database with the necessary indexes.
-//
-// nolint: lll // ignore long line length due to error messages
-func createIndexes(c *client) error {
- c.Logger.Trace("creating data indexes in the sqlite database")
-
- // create the builds_repo_id index for the builds table
- err := c.Sqlite.Exec(ddl.CreateBuildRepoIDIndex).Error
- if err != nil {
- return fmt.Errorf("unable to create builds_repo_id index for the %s table: %v", constants.TableBuild, err)
- }
-
- // create the builds_status index for the builds table
- err = c.Sqlite.Exec(ddl.CreateBuildStatusIndex).Error
- if err != nil {
- return fmt.Errorf("unable to create builds_status index for the %s table: %v", constants.TableBuild, err)
- }
-
- // create the builds_created index for the builds table
- err = c.Sqlite.Exec(ddl.CreateBuildCreatedIndex).Error
- if err != nil {
- return fmt.Errorf("unable to create builds_created index for the %s table: %v", constants.TableBuild, err)
- }
-
- // create the hooks_repo_id index for the hooks table
- err = c.Sqlite.Exec(ddl.CreateHookRepoIDIndex).Error
- if err != nil {
- return fmt.Errorf("unable to create hooks_repo_id index for the %s table: %v", constants.TableHook, err)
- }
-
- // create the logs_build_id index for the logs table
- err = c.Sqlite.Exec(ddl.CreateLogBuildIDIndex).Error
- if err != nil {
- return fmt.Errorf("unable to create logs_build_id index for the %s table: %v", constants.TableLog, err)
- }
-
- // create the repos_org_name index for the repos table
- err = c.Sqlite.Exec(ddl.CreateRepoOrgNameIndex).Error
- if err != nil {
- return fmt.Errorf("unable to create repos_org_name index for the %s table: %v", constants.TableRepo, err)
- }
-
- // create the secrets_type_org_repo index for the secrets table
- err = c.Sqlite.Exec(ddl.CreateSecretTypeOrgRepo).Error
- if err != nil {
- return fmt.Errorf("unable to create secrets_type_org_repo index for the %s table: %v", constants.TableSecret, err)
- }
-
- // create the secrets_type_org_team index for the secrets table
- err = c.Sqlite.Exec(ddl.CreateSecretTypeOrgTeam).Error
- if err != nil {
- return fmt.Errorf("unable to create secrets_type_org_team index for the %s table: %v", constants.TableSecret, err)
- }
-
- // create the secrets_type_org index for the secrets table
- err = c.Sqlite.Exec(ddl.CreateSecretTypeOrg).Error
- if err != nil {
- return fmt.Errorf("unable to create secrets_type_org index for the %s table: %v", constants.TableSecret, err)
- }
-
- // create the users_refresh index for the users table
- err = c.Sqlite.Exec(ddl.CreateUserRefreshIndex).Error
- if err != nil {
- return fmt.Errorf("unable to create users_refresh index for the %s table: %v", constants.TableUser, err)
- }
-
- // create the workers_hostname_address index for the workers table
- err = c.Sqlite.Exec(ddl.CreateWorkerHostnameAddressIndex).Error
- if err != nil {
- return fmt.Errorf("unable to create workers_hostname_address index for the %s table: %v", constants.TableWorker, err)
- }
-
- return nil
-}
diff --git a/database/sqlite/sqlite_test.go b/database/sqlite/sqlite_test.go
deleted file mode 100644
index 99831a70b..000000000
--- a/database/sqlite/sqlite_test.go
+++ /dev/null
@@ -1,181 +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 (
- "testing"
- "time"
-)
-
-func TestSqlite_New(t *testing.T) {
- // setup tests
- tests := []struct {
- failure bool
- address string
- want string
- }{
- {
- failure: false,
- address: ":memory:",
- want: ":memory:",
- },
- {
- failure: true,
- address: "",
- want: "",
- },
- }
-
- // run tests
- for _, test := range tests {
- _, err := New(
- WithAddress(test.address),
- WithCompressionLevel(3),
- WithConnectionLife(10*time.Second),
- WithConnectionIdle(5),
- WithConnectionOpen(20),
- WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"),
- WithSkipCreation(false),
- )
-
- if test.failure {
- if err == nil {
- t.Errorf("New should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("New returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_setupDatabase(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 the skip test database client
- _skipDatabase, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new skip sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _skipDatabase.Sqlite.DB(); _sql.Close() }()
-
- err = WithSkipCreation(true)(_skipDatabase)
- if err != nil {
- t.Errorf("unable to set SkipCreation for sqlite test database: %v", err)
- }
-
- tests := []struct {
- failure bool
- database *client
- }{
- {
- failure: false,
- database: _database,
- },
- {
- failure: false,
- database: _skipDatabase,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := setupDatabase(test.database)
-
- if test.failure {
- if err == nil {
- t.Errorf("setupDatabase should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("setupDatabase returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_createTables(t *testing.T) {
- // setup types
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := createTables(_database)
-
- if test.failure {
- if err == nil {
- t.Errorf("createTables should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("createTables returned err: %v", err)
- }
- }
-}
-
-func TestPostgres_createIndexes(t *testing.T) {
- // setup types
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- err := createIndexes(_database)
-
- if test.failure {
- if err == nil {
- t.Errorf("createIndexes should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("createIndexes returned err: %v", err)
- }
- }
-}
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 1ac7e5f39..000000000
--- a/database/sqlite/step_count_test.go
+++ /dev/null
@@ -1,197 +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 23303adf5..000000000
--- a/database/sqlite/step_list_test.go
+++ /dev/null
@@ -1,172 +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 52d73e47a..000000000
--- a/database/sqlite/step_test.go
+++ /dev/null
@@ -1,263 +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/sqlite/user.go b/database/sqlite/user.go
deleted file mode 100644
index dae174edd..000000000
--- a/database/sqlite/user.go
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package sqlite
-
-import (
- "errors"
- "fmt"
-
- "github.com/sirupsen/logrus"
-
- "github.com/go-vela/server/database/sqlite/dml"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/database"
- "github.com/go-vela/types/library"
-
- "gorm.io/gorm"
-)
-
-// GetUser gets a user by unique ID from the database.
-func (c *client) GetUser(id int64) (*library.User, error) {
- c.Logger.Tracef("getting user %d from the database", id)
-
- // variable to store query results
- u := new(database.User)
-
- // send query to the database and store result in variable
- result := c.Sqlite.
- Table(constants.TableUser).
- Raw(dml.SelectUser, id).
- Scan(u)
-
- // check if the query returned a record not found error or no rows were returned
- if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 {
- return nil, gorm.ErrRecordNotFound
- }
-
- // decrypt the fields for the user
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt
- err := u.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted users
- c.Logger.Errorf("unable to decrypt user %d: %v", id, err)
-
- // return the unencrypted user
- return u.ToLibrary(), result.Error
- }
-
- // return the decrypted user
- return u.ToLibrary(), result.Error
-}
-
-// GetUserName gets a user by name from the database.
-func (c *client) GetUserName(name string) (*library.User, error) {
- c.Logger.WithFields(logrus.Fields{
- "user": name,
- }).Tracef("getting user %s from the database", name)
-
- // variable to store query results
- u := new(database.User)
-
- // send query to the database and store result in variable
- result := c.Sqlite.
- Table(constants.TableUser).
- Raw(dml.SelectUserName, name).
- Scan(u)
-
- // check if the query returned a record not found error or no rows were returned
- if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 {
- return nil, gorm.ErrRecordNotFound
- }
-
- // decrypt the fields for the user
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt
- err := u.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted users
- c.Logger.Errorf("unable to decrypt user %s: %v", name, err)
-
- // return the unencrypted user
- return u.ToLibrary(), result.Error
- }
-
- // return the decrypted user
- return u.ToLibrary(), result.Error
-}
-
-// CreateUser creates a new user in the database.
-//
-// nolint: dupl // ignore similar code with update
-func (c *client) CreateUser(u *library.User) error {
- c.Logger.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Tracef("creating user %s in the database", u.GetName())
-
- // cast to database type
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#UserFromLibrary
- user := database.UserFromLibrary(u)
-
- // validate the necessary fields are populated
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#User.Validate
- err := user.Validate()
- if err != nil {
- return err
- }
-
- // encrypt the fields for the user
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#User.Encrypt
- err = user.Encrypt(c.config.EncryptionKey)
- if err != nil {
- return fmt.Errorf("unable to encrypt user %s: %v", u.GetName(), err)
- }
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableUser).
- Create(user).Error
-}
-
-// UpdateUser updates a user in the database.
-//
-// nolint: dupl // ignore similar code with create
-func (c *client) UpdateUser(u *library.User) error {
- c.Logger.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Tracef("updating user %s in the database", u.GetName())
-
- // cast to database type
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#UserFromLibrary
- user := database.UserFromLibrary(u)
-
- // validate the necessary fields are populated
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#User.Validate
- err := user.Validate()
- if err != nil {
- return err
- }
-
- // encrypt the fields for the user
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#User.Encrypt
- err = user.Encrypt(c.config.EncryptionKey)
- if err != nil {
- return fmt.Errorf("unable to encrypt user %s: %v", u.GetName(), err)
- }
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableUser).
- Save(user).Error
-}
-
-// DeleteUser deletes a user by unique ID from the database.
-func (c *client) DeleteUser(id int64) error {
- c.Logger.Tracef("deleting user %d from the database", id)
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableUser).
- Exec(dml.DeleteUser, id).Error
-}
diff --git a/database/sqlite/user_count_test.go b/database/sqlite/user_count_test.go
deleted file mode 100644
index f866f0aa7..000000000
--- a/database/sqlite/user_count_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package sqlite
-
-import (
- "log"
- "reflect"
- "testing"
-
- "github.com/go-vela/server/database/sqlite/ddl"
- "github.com/go-vela/types/constants"
-)
-
-func init() {
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- log.Fatalf("unable to create new sqlite test database: %v", err)
- }
-
- // create the user table
- err = _database.Sqlite.Exec(ddl.CreateUserTable).Error
- if err != nil {
- log.Fatalf("unable to create %s table: %v", constants.TableUser, err)
- }
-}
-
-func TestSqlite_Client_GetUserCount(t *testing.T) {
- // setup types
- _userOne := testUser()
- _userOne.SetID(1)
- _userOne.SetName("foo")
- _userOne.SetToken("bar")
- _userOne.SetHash("baz")
-
- _userTwo := testUser()
- _userTwo.SetID(2)
- _userTwo.SetName("bar")
- _userTwo.SetToken("foo")
- _userTwo.SetHash("baz")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 2,
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the users table
- defer _database.Sqlite.Exec("delete from users;")
-
- // create the users in the database
- err := _database.CreateUser(_userOne)
- if err != nil {
- t.Errorf("unable to create test user: %v", err)
- }
-
- err = _database.CreateUser(_userTwo)
- if err != nil {
- t.Errorf("unable to create test user: %v", err)
- }
-
- got, err := _database.GetUserCount()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetUserCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetUserCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetUserCount is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/sqlite/user_list.go b/database/sqlite/user_list.go
deleted file mode 100644
index d90863d6b..000000000
--- a/database/sqlite/user_list.go
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package sqlite
-
-import (
- "github.com/go-vela/server/database/sqlite/dml"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/database"
- "github.com/go-vela/types/library"
-)
-
-// GetUserList gets a list of all users from the database.
-//
-// nolint: dupl // ignore false positive of duplicate code
-func (c *client) GetUserList() ([]*library.User, error) {
- c.Logger.Trace("listing users from the database")
-
- // variable to store query results
- u := new([]database.User)
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableUser).
- Raw(dml.ListUsers).
- Scan(u).Error
- if err != nil {
- return nil, err
- }
-
- // variable we want to return
- users := []*library.User{}
- // iterate through all query results
- for _, user := range *u {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := user
-
- // decrypt the fields for the user
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt
- err = tmp.Decrypt(c.config.EncryptionKey)
- if err != nil {
- // ensures that the change is backwards compatible
- // by logging the error instead of returning it
- // which allows us to fetch unencrypted users
- c.Logger.Errorf("unable to decrypt user %d: %v", tmp.ID.Int64, err)
- }
-
- // convert query result to library type
- //
- // https://pkg.go.dev/github.com/go-vela/types/database#User.ToLibrary
- users = append(users, tmp.ToLibrary())
- }
-
- return users, nil
-}
-
-// GetUserLiteList gets a lite list of all users from the database.
-func (c *client) GetUserLiteList(page, perPage int) ([]*library.User, error) {
- c.Logger.Trace("listing lite users from the database")
-
- // variable to store query results
- u := new([]database.User)
- // calculate offset for pagination through results
- offset := perPage * (page - 1)
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableUser).
- Raw(dml.ListLiteUsers, perPage, offset).
- Scan(u).Error
-
- // variable we want to return
- users := []*library.User{}
- // iterate through all query results
- for _, user := range *u {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := user
-
- // convert query result to library type
- users = append(users, tmp.ToLibrary())
- }
-
- return users, err
-}
diff --git a/database/sqlite/user_list_test.go b/database/sqlite/user_list_test.go
deleted file mode 100644
index 0ee623f72..000000000
--- a/database/sqlite/user_list_test.go
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package sqlite
-
-import (
- "log"
- "reflect"
- "testing"
-
- "github.com/go-vela/server/database/sqlite/ddl"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/library"
-)
-
-func init() {
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- log.Fatalf("unable to create new sqlite test database: %v", err)
- }
-
- // create the user table
- err = _database.Sqlite.Exec(ddl.CreateUserTable).Error
- if err != nil {
- log.Fatalf("unable to create %s table: %v", constants.TableUser, err)
- }
-}
-
-func TestSqlite_Client_GetUserList(t *testing.T) {
- // setup types
- _userOne := testUser()
- _userOne.SetID(1)
- _userOne.SetName("foo")
- _userOne.SetToken("bar")
- _userOne.SetHash("baz")
-
- _userTwo := testUser()
- _userTwo.SetID(2)
- _userTwo.SetName("bar")
- _userTwo.SetToken("foo")
- _userTwo.SetHash("baz")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.User
- }{
- {
- failure: false,
- want: []*library.User{_userOne, _userTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the users table
- defer _database.Sqlite.Exec("delete from users;")
-
- for _, user := range test.want {
- // create the user in the database
- err := _database.CreateUser(user)
- if err != nil {
- t.Errorf("unable to create test user: %v", err)
- }
- }
-
- got, err := _database.GetUserList()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetUserList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetUserList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetUserList is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetUserLiteList(t *testing.T) {
- // setup types
- _userOne := testUser()
- _userOne.SetID(1)
- _userOne.SetName("foo")
-
- _userTwo := testUser()
- _userTwo.SetID(2)
- _userTwo.SetName("bar")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.User
- }{
- {
- failure: false,
- want: []*library.User{_userTwo, _userOne},
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the users table
- defer _database.Sqlite.Exec("delete from users;")
-
- for _, user := range test.want {
- // set the required fields for the user
- user.SetToken("baz")
- user.SetHash("foob")
-
- // create the user in the database
- err := _database.CreateUser(user)
- if err != nil {
- t.Errorf("unable to create test user: %v", err)
- }
-
- // clear the required fields for the user
- // so we get back the expected data
- user.SetToken("")
- user.SetHash("")
- }
-
- got, err := _database.GetUserLiteList(1, 10)
-
- if test.failure {
- if err == nil {
- t.Errorf("GetUserLiteList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetUserLiteList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetUserLiteList is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/sqlite/user_test.go b/database/sqlite/user_test.go
deleted file mode 100644
index ef97c6eda..000000000
--- a/database/sqlite/user_test.go
+++ /dev/null
@@ -1,243 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package sqlite
-
-import (
- "reflect"
- "testing"
-
- "github.com/go-vela/types/library"
-)
-
-func TestSqlite_Client_GetUser(t *testing.T) {
- // setup types
- _user := testUser()
- _user.SetID(1)
- _user.SetName("foo")
- _user.SetToken("bar")
- _user.SetHash("baz")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want *library.User
- }{
- {
- failure: false,
- want: _user,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- if test.want != nil {
- // create the user in the database
- err := _database.CreateUser(test.want)
- if err != nil {
- t.Errorf("unable to create test user: %v", err)
- }
- }
-
- got, err := _database.GetUser(1)
-
- // cleanup the users table
- _ = _database.Sqlite.Exec("DELETE FROM users;")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetUser should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetUser returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetUser is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_CreateUser(t *testing.T) {
- // setup types
- _user := testUser()
- _user.SetID(1)
- _user.SetName("foo")
- _user.SetToken("bar")
- _user.SetHash("baz")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the users table
- defer _database.Sqlite.Exec("delete from users;")
-
- err := _database.CreateUser(_user)
-
- if test.failure {
- if err == nil {
- t.Errorf("CreateUser should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("CreateUser returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_Client_UpdateUser(t *testing.T) {
- // setup types
- _user := testUser()
- _user.SetID(1)
- _user.SetName("foo")
- _user.SetToken("bar")
- _user.SetHash("baz")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the users table
- defer _database.Sqlite.Exec("delete from users;")
-
- // create the user in the database
- err := _database.CreateUser(_user)
- if err != nil {
- t.Errorf("unable to create test user: %v", err)
- }
-
- err = _database.UpdateUser(_user)
-
- if test.failure {
- if err == nil {
- t.Errorf("UpdateUser should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("UpdateUser returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_Client_DeleteUser(t *testing.T) {
- // setup types
- _user := testUser()
- _user.SetID(1)
- _user.SetName("foo")
- _user.SetToken("bar")
- _user.SetHash("baz")
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the users table
- defer _database.Sqlite.Exec("delete from users;")
-
- // create the user in the database
- err := _database.CreateUser(_user)
- if err != nil {
- t.Errorf("unable to create test user: %v", err)
- }
-
- err = _database.DeleteUser(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("DeleteUser should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("DeleteUser returned err: %v", err)
- }
- }
-}
-
-// testUser is a test helper function to create a
-// library User type with all fields set to their
-// zero values.
-func testUser() *library.User {
- i64 := int64(0)
- str := ""
- b := false
- var arr []string
-
- return &library.User{
- ID: &i64,
- Name: &str,
- RefreshToken: &str,
- Token: &str,
- Hash: &str,
- Favorites: &arr,
- Active: &b,
- Admin: &b,
- }
-}
diff --git a/database/sqlite/worker.go b/database/sqlite/worker.go
deleted file mode 100644
index 25ad1392b..000000000
--- a/database/sqlite/worker.go
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package sqlite
-
-import (
- "errors"
-
- "github.com/sirupsen/logrus"
-
- "github.com/go-vela/server/database/sqlite/dml"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/database"
- "github.com/go-vela/types/library"
-
- "gorm.io/gorm"
-)
-
-// GetWorker gets a worker by hostname from the database.
-func (c *client) GetWorker(hostname string) (*library.Worker, error) {
- c.Logger.WithFields(logrus.Fields{
- "worker": hostname,
- }).Tracef("getting worker %s from the database", hostname)
-
- // variable to store query results
- w := new(database.Worker)
-
- // send query to the database and store result in variable
- result := c.Sqlite.
- Table(constants.TableWorker).
- Raw(dml.SelectWorker, hostname).
- Scan(w)
-
- // check if the query returned a record not found error or no rows were returned
- if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 {
- return nil, gorm.ErrRecordNotFound
- }
-
- return w.ToLibrary(), result.Error
-}
-
-// GetWorker gets a worker by address from the database.
-func (c *client) GetWorkerByAddress(address string) (*library.Worker, error) {
- c.Logger.Tracef("getting worker by address %s from the database", address)
-
- // variable to store query results
- w := new(database.Worker)
-
- // send query to the database and store result in variable
- result := c.Sqlite.
- Table(constants.TableWorker).
- Raw(dml.SelectWorkerByAddress, address).
- Scan(w)
-
- // check if the query returned a record not found error or no rows were returned
- if errors.Is(result.Error, gorm.ErrRecordNotFound) || result.RowsAffected == 0 {
- return nil, gorm.ErrRecordNotFound
- }
-
- return w.ToLibrary(), result.Error
-}
-
-// CreateWorker creates a new worker in the database.
-func (c *client) CreateWorker(w *library.Worker) error {
- c.Logger.WithFields(logrus.Fields{
- "worker": w.GetHostname(),
- }).Tracef("creating worker %s in the database", w.GetHostname())
-
- // cast to database type
- worker := database.WorkerFromLibrary(w)
-
- // validate the necessary fields are populated
- err := worker.Validate()
- if err != nil {
- return err
- }
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableWorker).
- Create(worker).Error
-}
-
-// UpdateWorker updates a worker in the database.
-func (c *client) UpdateWorker(w *library.Worker) error {
- c.Logger.WithFields(logrus.Fields{
- "worker": w.GetHostname(),
- }).Tracef("updating worker %s in the database", w.GetHostname())
-
- // cast to database type
- worker := database.WorkerFromLibrary(w)
-
- // validate the necessary fields are populated
- err := worker.Validate()
- if err != nil {
- return err
- }
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableWorker).
- Save(worker).Error
-}
-
-// DeleteWorker deletes a worker by unique ID from the database.
-func (c *client) DeleteWorker(id int64) error {
- c.Logger.Tracef("deleting worker %d in the database", id)
-
- // send query to the database
- return c.Sqlite.
- Table(constants.TableWorker).
- Exec(dml.DeleteWorker, id).Error
-}
diff --git a/database/sqlite/worker_count_test.go b/database/sqlite/worker_count_test.go
deleted file mode 100644
index fdd652852..000000000
--- a/database/sqlite/worker_count_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package sqlite
-
-import (
- "log"
- "reflect"
- "testing"
-
- "github.com/go-vela/server/database/sqlite/ddl"
- "github.com/go-vela/types/constants"
-)
-
-func init() {
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- log.Fatalf("unable to create new sqlite test database: %v", err)
- }
-
- // create the worker table
- err = _database.Sqlite.Exec(ddl.CreateWorkerTable).Error
- if err != nil {
- log.Fatalf("unable to create %s table: %v", constants.TableWorker, err)
- }
-}
-
-func TestSqlite_Client_GetWorkerCount(t *testing.T) {
- // setup types
- _workerOne := testWorker()
- _workerOne.SetID(1)
- _workerOne.SetHostname("worker_0")
- _workerOne.SetAddress("localhost")
- _workerOne.SetActive(true)
-
- _workerTwo := testWorker()
- _workerTwo.SetID(2)
- _workerTwo.SetHostname("worker_1")
- _workerTwo.SetAddress("localhost")
- _workerTwo.SetActive(true)
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want int64
- }{
- {
- failure: false,
- want: 2,
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the workers table
- defer _database.Sqlite.Exec("delete from workers;")
-
- // create the workers in the database
- err := _database.CreateWorker(_workerOne)
- if err != nil {
- t.Errorf("unable to create test worker: %v", err)
- }
-
- err = _database.CreateWorker(_workerTwo)
- if err != nil {
- t.Errorf("unable to create test worker: %v", err)
- }
-
- got, err := _database.GetWorkerCount()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetWorkerCount should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetWorkerCount returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetWorkerCount is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/sqlite/worker_list.go b/database/sqlite/worker_list.go
deleted file mode 100644
index e16d99071..000000000
--- a/database/sqlite/worker_list.go
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package sqlite
-
-import (
- "github.com/go-vela/server/database/sqlite/dml"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/database"
- "github.com/go-vela/types/library"
-)
-
-// GetWorkerList gets a list of all workers from the database.
-func (c *client) GetWorkerList() ([]*library.Worker, error) {
- c.Logger.Trace("listing workers from the database")
-
- // variable to store query results
- w := new([]database.Worker)
-
- // send query to the database and store result in variable
- err := c.Sqlite.
- Table(constants.TableWorker).
- Raw(dml.ListWorkers).
- Scan(w).Error
-
- // variable we want to return
- workers := []*library.Worker{}
- // iterate through all query results
- for _, worker := range *w {
- // https://golang.org/doc/faq#closures_and_goroutines
- tmp := worker
-
- // convert query result to library type
- workers = append(workers, tmp.ToLibrary())
- }
-
- return workers, err
-}
diff --git a/database/sqlite/worker_list_test.go b/database/sqlite/worker_list_test.go
deleted file mode 100644
index 8a6c0eba1..000000000
--- a/database/sqlite/worker_list_test.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 (
- "log"
- "reflect"
- "testing"
-
- "github.com/go-vela/server/database/sqlite/ddl"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/library"
-)
-
-func init() {
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- log.Fatalf("unable to create new sqlite test database: %v", err)
- }
-
- // create the worker table
- err = _database.Sqlite.Exec(ddl.CreateWorkerTable).Error
- if err != nil {
- log.Fatalf("unable to create %s table: %v", constants.TableWorker, err)
- }
-}
-
-func TestSqlite_Client_GetWorkerList(t *testing.T) {
- // setup types
- _workerOne := testWorker()
- _workerOne.SetID(1)
- _workerOne.SetHostname("worker_0")
- _workerOne.SetAddress("localhost")
- _workerOne.SetActive(true)
-
- _workerTwo := testWorker()
- _workerTwo.SetID(2)
- _workerTwo.SetHostname("worker_1")
- _workerTwo.SetAddress("localhost")
- _workerTwo.SetActive(true)
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want []*library.Worker
- }{
- {
- failure: false,
- want: []*library.Worker{_workerOne, _workerTwo},
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the workers table
- defer _database.Sqlite.Exec("delete from workers;")
-
- for _, worker := range test.want {
- // create the worker in the database
- err := _database.CreateWorker(worker)
- if err != nil {
- t.Errorf("unable to create test worker: %v", err)
- }
- }
-
- got, err := _database.GetWorkerList()
-
- if test.failure {
- if err == nil {
- t.Errorf("GetWorkerList should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetWorkerList returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetWorkerList is %v, want %v", got, test.want)
- }
- }
-}
diff --git a/database/sqlite/worker_test.go b/database/sqlite/worker_test.go
deleted file mode 100644
index 219351e6d..000000000
--- a/database/sqlite/worker_test.go
+++ /dev/null
@@ -1,305 +0,0 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
-//
-// Use of this source code is governed by the LICENSE file in this repository.
-
-package sqlite
-
-import (
- "reflect"
- "testing"
-
- "github.com/go-vela/types/library"
-)
-
-func TestSqlite_Client_GetWorker(t *testing.T) {
- // setup types
- _worker := testWorker()
- _worker.SetID(1)
- _worker.SetHostname("worker_0")
- _worker.SetAddress("localhost")
- _worker.SetActive(true)
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want *library.Worker
- }{
- {
- failure: false,
- want: _worker,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- if test.want != nil {
- // create the worker in the database
- err := _database.CreateWorker(test.want)
- if err != nil {
- t.Errorf("unable to create test worker: %v", err)
- }
- }
-
- got, err := _database.GetWorker("worker_0")
-
- // cleanup the workers table
- _ = _database.Sqlite.Exec("DELETE FROM workers;")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetWorker should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetWorker returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetWorker is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_GetWorkerByAddress(t *testing.T) {
- // setup types
- _worker := testWorker()
- _worker.SetID(1)
- _worker.SetHostname("worker_0")
- _worker.SetAddress("localhost")
- _worker.SetActive(true)
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- want *library.Worker
- }{
- {
- failure: false,
- want: _worker,
- },
- {
- failure: true,
- want: nil,
- },
- }
-
- // run tests
- for _, test := range tests {
- if test.want != nil {
- // create the worker in the database
- err := _database.CreateWorker(test.want)
- if err != nil {
- t.Errorf("unable to create test worker: %v", err)
- }
- }
-
- got, err := _database.GetWorkerByAddress("localhost")
-
- // cleanup the workers table
- _ = _database.Sqlite.Exec("DELETE FROM workers;")
-
- if test.failure {
- if err == nil {
- t.Errorf("GetWorkerByAddress should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("GetWorkerByAddress returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("GetWorkerByAddress is %v, want %v", got, test.want)
- }
- }
-}
-
-func TestSqlite_Client_CreateWorker(t *testing.T) {
- // setup types
- _worker := testWorker()
- _worker.SetID(1)
- _worker.SetHostname("worker_0")
- _worker.SetAddress("localhost")
- _worker.SetActive(true)
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the workers table
- defer _database.Sqlite.Exec("delete from workers;")
-
- err := _database.CreateWorker(_worker)
-
- if test.failure {
- if err == nil {
- t.Errorf("CreateWorker should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("CreateWorker returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_Client_UpdateWorker(t *testing.T) {
- // setup types
- _worker := testWorker()
- _worker.SetID(1)
- _worker.SetHostname("worker_0")
- _worker.SetAddress("localhost")
- _worker.SetActive(true)
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the workers table
- defer _database.Sqlite.Exec("delete from workers;")
-
- // create the worker in the database
- err := _database.CreateWorker(_worker)
- if err != nil {
- t.Errorf("unable to create test worker: %v", err)
- }
-
- err = _database.UpdateWorker(_worker)
-
- if test.failure {
- if err == nil {
- t.Errorf("UpdateWorker should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("UpdateWorker returned err: %v", err)
- }
- }
-}
-
-func TestSqlite_Client_DeleteWorker(t *testing.T) {
- // setup types
- _worker := testWorker()
- _worker.SetID(1)
- _worker.SetHostname("worker_0")
- _worker.SetAddress("localhost")
- _worker.SetActive(true)
-
- // setup the test database client
- _database, err := NewTest()
- if err != nil {
- t.Errorf("unable to create new sqlite test database: %v", err)
- }
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
-
- // setup tests
- tests := []struct {
- failure bool
- }{
- {
- failure: false,
- },
- }
-
- // run tests
- for _, test := range tests {
- // defer cleanup of the workers table
- defer _database.Sqlite.Exec("delete from workers;")
-
- // create the worker in the database
- err := _database.CreateWorker(_worker)
- if err != nil {
- t.Errorf("unable to create test worker: %v", err)
- }
-
- err = _database.DeleteWorker(1)
-
- if test.failure {
- if err == nil {
- t.Errorf("DeleteWorker should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("DeleteWorker returned err: %v", err)
- }
- }
-}
-
-// testWorker is a test helper function to create a
-// library Worker type with all fields set to their
-// zero values.
-func testWorker() *library.Worker {
- i64 := int64(0)
- str := ""
- b := false
- var arr []string
-
- return &library.Worker{
- ID: &i64,
- Hostname: &str,
- Address: &str,
- Routes: &arr,
- Active: &b,
- LastCheckedIn: &i64,
- BuildLimit: &i64,
- }
-}
diff --git a/database/step/clean.go b/database/step/clean.go
new file mode 100644
index 000000000..54e686e86
--- /dev/null
+++ b/database/step/clean.go
@@ -0,0 +1,35 @@
+// 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 (
+ "time"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CleanSteps updates steps to an error with a created timestamp prior to a defined moment.
+func (e *engine) CleanSteps(msg string, before int64) (int64, error) {
+ logrus.Tracef("cleaning pending or running steps in the database created prior to %d", before)
+
+ s := new(library.Step)
+ s.SetStatus(constants.StatusError)
+ s.SetError(msg)
+ s.SetFinished(time.Now().UTC().Unix())
+
+ step := database.StepFromLibrary(s)
+
+ // send query to the database
+ result := e.client.
+ Table(constants.TableStep).
+ Where("created < ?", before).
+ Where("status = 'running' OR status = 'pending'").
+ Updates(step)
+
+ return result.RowsAffected, result.Error
+}
diff --git a/database/step/clean_test.go b/database/step/clean_test.go
new file mode 100644
index 000000000..e772751ac
--- /dev/null
+++ b/database/step/clean_test.go
@@ -0,0 +1,130 @@
+// 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_CleanStep(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")
+ _stepOne.SetCreated(1)
+ _stepOne.SetStatus("running")
+
+ _stepTwo := testStep()
+ _stepTwo.SetID(2)
+ _stepTwo.SetRepoID(1)
+ _stepTwo.SetBuildID(1)
+ _stepTwo.SetNumber(2)
+ _stepTwo.SetName("foo")
+ _stepTwo.SetImage("bar")
+ _stepTwo.SetCreated(1)
+ _stepTwo.SetStatus("pending")
+
+ _stepThree := testStep()
+ _stepThree.SetID(3)
+ _stepThree.SetRepoID(1)
+ _stepThree.SetBuildID(1)
+ _stepThree.SetNumber(3)
+ _stepThree.SetName("foo")
+ _stepThree.SetImage("bar")
+ _stepThree.SetCreated(1)
+ _stepThree.SetStatus("success")
+
+ _stepFour := testStep()
+ _stepFour.SetID(4)
+ _stepFour.SetRepoID(1)
+ _stepFour.SetBuildID(1)
+ _stepFour.SetNumber(4)
+ _stepFour.SetName("foo")
+ _stepFour.SetImage("bar")
+ _stepFour.SetCreated(5)
+ _stepFour.SetStatus("pending")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the name query
+ _mock.ExpectExec(`UPDATE "steps" SET "status"=$1,"error"=$2,"finished"=$3 WHERE created < $4 AND (status = 'running' OR status = 'pending')`).
+ WithArgs("error", "msg", NowTimestamp{}, 3).
+ WillReturnResult(sqlmock.NewResult(1, 2))
+
+ _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)
+ }
+
+ _, err = _sqlite.CreateStep(_stepThree)
+ if err != nil {
+ t.Errorf("unable to create test step for sqlite: %v", err)
+ }
+
+ _, err = _sqlite.CreateStep(_stepFour)
+ 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.CleanSteps("msg", 3)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CleanSteps for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CleanSteps for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CleanSteps for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
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..df5a830f4
--- /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..dc473bd96
--- /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..2d18bc9ce
--- /dev/null
+++ b/database/step/create.go
@@ -0,0 +1,37 @@
+// 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) (*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 nil, err
+ }
+
+ // send query to the database
+ result := e.client.Table(constants.TableStep).Create(step)
+
+ return step.ToLibrary(), result.Error
+}
diff --git a/database/step/create_test.go b/database/step/create_test.go
new file mode 100644
index 000000000..0bf3ffca6
--- /dev/null
+++ b/database/step/create_test.go
@@ -0,0 +1,80 @@
+// 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_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) {
+ got, 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)
+ }
+
+ if !reflect.DeepEqual(got, _step) {
+ t.Errorf("CreateStep for %s returned %s, want %s", test.name, got, _step)
+ }
+ })
+ }
+}
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..d77f81a25
--- /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..8428598f5
--- /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..b30ced1b0
--- /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/interface.go b/database/step/interface.go
new file mode 100644
index 000000000..e7a377d26
--- /dev/null
+++ b/database/step/interface.go
@@ -0,0 +1,51 @@
+// 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"
+)
+
+// StepInterface represents the Vela interface for step
+// functions with the supported Database backends.
+//
+//nolint:revive // ignore name stutter
+type StepInterface 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
+
+ // CleanSteps defines a function that sets running or pending steps to error status before a given created time.
+ CleanSteps(string, int64) (int64, error)
+ // 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) (*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) (*library.Step, error)
+}
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..4ebe4c0be
--- /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..b7170634f
--- /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..7716b02e9
--- /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..c98852b1b
--- /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/step.go b/database/step/step.go
new file mode 100644
index 000000000..43e905797
--- /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 StepInterface interface.
+ config struct {
+ // specifies to skip creating tables and indexes for the Step engine
+ SkipCreation bool
+ }
+
+ // engine represents the step functionality that implements the StepInterface 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..136d5ca40
--- /dev/null
+++ b/database/step/step_test.go
@@ -0,0 +1,247 @@
+// 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/driver"
+ "reflect"
+ "testing"
+ "time"
+
+ "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),
+ }
+}
+
+// This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values
+// that are otherwise not easily compared. These typically would be values generated
+// before adding or updating them in the database.
+//
+// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime
+
+// NowTimestamp is used to test whether timestamps get updated correctly to the current time with lenience.
+type NowTimestamp struct{}
+
+// Match satisfies sqlmock.Argument interface.
+func (t NowTimestamp) Match(v driver.Value) bool {
+ ts, ok := v.(int64)
+ if !ok {
+ return false
+ }
+ now := time.Now().Unix()
+
+ return now-ts < 10
+}
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..c9e5f73d0
--- /dev/null
+++ b/database/step/update.go
@@ -0,0 +1,37 @@
+// 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) (*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 nil, err
+ }
+
+ // send query to the database
+ result := e.client.Table(constants.TableStep).Save(step)
+
+ return step.ToLibrary(), result.Error
+}
diff --git a/database/step/update_test.go b/database/step/update_test.go
new file mode 100644
index 000000000..38d8c1bdb
--- /dev/null
+++ b/database/step/update_test.go
@@ -0,0 +1,82 @@
+// 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_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) {
+ got, 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)
+ }
+
+ if !reflect.DeepEqual(got, _step) {
+ t.Errorf("UpdateStep for %s returned %s, want %s", test.name, got, _step)
+ }
+ })
+ }
+}
diff --git a/database/sqlite/user_count.go b/database/user/count.go
similarity index 53%
rename from database/sqlite/user_count.go
rename to database/user/count.go
index 72b70b003..074a5ef66 100644
--- a/database/sqlite/user_count.go
+++ b/database/user/count.go
@@ -2,25 +2,24 @@
//
// Use of this source code is governed by the LICENSE file in this repository.
-package sqlite
+package user
import (
- "github.com/go-vela/server/database/sqlite/dml"
"github.com/go-vela/types/constants"
)
-// GetUserCount gets a count of all users from the database.
-func (c *client) GetUserCount() (int64, error) {
- c.Logger.Trace("getting count of users from the database")
+// CountUsers gets the count of all users from the database.
+func (e *engine) CountUsers() (int64, error) {
+ e.logger.Tracef("getting count of all users from the database")
// variable to store query results
var u int64
// send query to the database and store result in variable
- err := c.Sqlite.
+ err := e.client.
Table(constants.TableUser).
- Raw(dml.SelectUsersCount).
- Pluck("count", &u).Error
+ Count(&u).
+ Error
return u, err
}
diff --git a/database/user/count_test.go b/database/user/count_test.go
new file mode 100644
index 000000000..be7cb6437
--- /dev/null
+++ b/database/user/count_test.go
@@ -0,0 +1,93 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestUser_Engine_CountUsers(t *testing.T) {
+ // setup types
+ _userOne := testUser()
+ _userOne.SetID(1)
+ _userOne.SetName("foo")
+ _userOne.SetToken("bar")
+ _userOne.SetHash("baz")
+
+ _userTwo := testUser()
+ _userTwo.SetID(2)
+ _userTwo.SetName("baz")
+ _userTwo.SetToken("bar")
+ _userTwo.SetHash("foo")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT count(*) FROM "users"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateUser(_userOne)
+ if err != nil {
+ t.Errorf("unable to create test user for sqlite: %v", err)
+ }
+
+ err = _sqlite.CreateUser(_userTwo)
+ if err != nil {
+ t.Errorf("unable to create test user for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want int64
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: 2,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: 2,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.CountUsers()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountUsers for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountUsers for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountUsers for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/user/create.go b/database/user/create.go
new file mode 100644
index 000000000..f527979b9
--- /dev/null
+++ b/database/user/create.go
@@ -0,0 +1,49 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code in update.go
+package user
+
+import (
+ "fmt"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CreateUser creates a new user in the database.
+func (e *engine) CreateUser(u *library.User) error {
+ e.logger.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ }).Tracef("creating user %s in the database", u.GetName())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#UserFromLibrary
+ user := database.UserFromLibrary(u)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#User.Validate
+ err := user.Validate()
+ if err != nil {
+ return err
+ }
+
+ // encrypt the fields for the user
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#User.Encrypt
+ err = user.Encrypt(e.config.EncryptionKey)
+ if err != nil {
+ return fmt.Errorf("unable to encrypt user %s: %w", u.GetName(), err)
+ }
+
+ // send query to the database
+ return e.client.
+ Table(constants.TableUser).
+ Create(user).
+ Error
+}
diff --git a/database/user/create_test.go b/database/user/create_test.go
new file mode 100644
index 000000000..12de80f96
--- /dev/null
+++ b/database/user/create_test.go
@@ -0,0 +1,73 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestUser_Engine_CreateUser(t *testing.T) {
+ // setup types
+ _user := testUser()
+ _user.SetID(1)
+ _user.SetName("foo")
+ _user.SetToken("bar")
+ _user.SetHash("baz")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows([]string{"id"}).AddRow(1)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`INSERT INTO "users"
+("name","refresh_token","token","hash","favorites","active","admin","id")
+VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING "id"`).
+ WithArgs("foo", AnyArgument{}, AnyArgument{}, AnyArgument{}, nil, false, false, 1).
+ WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := test.database.CreateUser(_user)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateUser for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateUser for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/user/delete.go b/database/user/delete.go
new file mode 100644
index 000000000..95b77ff64
--- /dev/null
+++ b/database/user/delete.go
@@ -0,0 +1,30 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// DeleteUser deletes an existing user from the database.
+func (e *engine) DeleteUser(u *library.User) error {
+ e.logger.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ }).Tracef("deleting user %s from the database", u.GetName())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#UserFromLibrary
+ user := database.UserFromLibrary(u)
+
+ // send query to the database
+ return e.client.
+ Table(constants.TableUser).
+ Delete(user).
+ Error
+}
diff --git a/database/user/delete_test.go b/database/user/delete_test.go
new file mode 100644
index 000000000..0dec0a6b2
--- /dev/null
+++ b/database/user/delete_test.go
@@ -0,0 +1,73 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestUser_Engine_DeleteUser(t *testing.T) {
+ // setup types
+ _user := testUser()
+ _user.SetID(1)
+ _user.SetName("foo")
+ _user.SetToken("bar")
+ _user.SetHash("baz")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`DELETE FROM "users" WHERE "users"."id" = $1`).
+ WithArgs(1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateUser(_user)
+ if err != nil {
+ t.Errorf("unable to create test user for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err = test.database.DeleteUser(_user)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("DeleteUser for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("DeleteUser for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/user/get.go b/database/user/get.go
new file mode 100644
index 000000000..d37275c80
--- /dev/null
+++ b/database/user/get.go
@@ -0,0 +1,47 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// GetUser gets a user by ID from the database.
+func (e *engine) GetUser(id int64) (*library.User, error) {
+ e.logger.Tracef("getting user %d from the database", id)
+
+ // variable to store query results
+ u := new(database.User)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableUser).
+ Where("id = ?", id).
+ Take(u).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // decrypt the fields for the user
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt
+ err = u.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // TODO: remove backwards compatibility before 1.x.x release
+ //
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch unencrypted users
+ e.logger.Errorf("unable to decrypt user %d: %v", u.ID.Int64, err)
+ }
+
+ // return the decrypted user
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#User.ToLibrary
+ return u.ToLibrary(), nil
+}
diff --git a/database/user/get_name.go b/database/user/get_name.go
new file mode 100644
index 000000000..4e8da5550
--- /dev/null
+++ b/database/user/get_name.go
@@ -0,0 +1,50 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// GetUserForName gets a user by name from the database.
+func (e *engine) GetUserForName(name string) (*library.User, error) {
+ e.logger.WithFields(logrus.Fields{
+ "user": name,
+ }).Tracef("getting user %s from the database", name)
+
+ // variable to store query results
+ u := new(database.User)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableUser).
+ Where("name = ?", name).
+ Take(u).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // decrypt the fields for the user
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt
+ err = u.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // TODO: remove backwards compatibility before 1.x.x release
+ //
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch unencrypted users
+ e.logger.Errorf("unable to decrypt user %d: %v", u.ID.Int64, err)
+ }
+
+ // return the decrypted user
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#User.ToLibrary
+ return u.ToLibrary(), nil
+}
diff --git a/database/user/get_name_test.go b/database/user/get_name_test.go
new file mode 100644
index 000000000..4b0a6446b
--- /dev/null
+++ b/database/user/get_name_test.go
@@ -0,0 +1,86 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestUser_Engine_GetUserForName(t *testing.T) {
+ // setup types
+ _user := testUser()
+ _user.SetID(1)
+ _user.SetName("foo")
+ _user.SetToken("bar")
+ _user.SetHash("baz")
+ _user.SetFavorites([]string{})
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "name", "refresh_token", "token", "hash", "favorites", "active", "admin"}).
+ AddRow(1, "foo", "", "bar", "baz", "{}", false, false)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "users" WHERE name = $1 LIMIT 1`).WithArgs("foo").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateUser(_user)
+ if err != nil {
+ t.Errorf("unable to create test user for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.User
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _user,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _user,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetUserForName("foo")
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetUserForName for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetUserForName for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetUserForName for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/user/get_test.go b/database/user/get_test.go
new file mode 100644
index 000000000..4593ce48f
--- /dev/null
+++ b/database/user/get_test.go
@@ -0,0 +1,86 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestUser_Engine_GetUser(t *testing.T) {
+ // setup types
+ _user := testUser()
+ _user.SetID(1)
+ _user.SetName("foo")
+ _user.SetToken("bar")
+ _user.SetHash("baz")
+ _user.SetFavorites([]string{})
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "name", "refresh_token", "token", "hash", "favorites", "active", "admin"}).
+ AddRow(1, "foo", "", "bar", "baz", "{}", false, false)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "users" WHERE id = $1 LIMIT 1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateUser(_user)
+ if err != nil {
+ t.Errorf("unable to create test user for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.User
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _user,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _user,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetUser(1)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetUser for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetUser for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetUser for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/user/index.go b/database/user/index.go
new file mode 100644
index 000000000..5445e963e
--- /dev/null
+++ b/database/user/index.go
@@ -0,0 +1,24 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+const (
+ // CreateUserRefreshIndex represents a query to create an
+ // index on the users table for the refresh_token column.
+ CreateUserRefreshIndex = `
+CREATE INDEX
+IF NOT EXISTS
+users_refresh
+ON users (refresh_token);
+`
+)
+
+// CreateUserIndexes creates the indexes for the users table in the database.
+func (e *engine) CreateUserIndexes() error {
+ e.logger.Tracef("creating indexes for users table in the database")
+
+ // create the refresh_token column index for the users table
+ return e.client.Exec(CreateUserRefreshIndex).Error
+}
diff --git a/database/user/index_test.go b/database/user/index_test.go
new file mode 100644
index 000000000..55728b77a
--- /dev/null
+++ b/database/user/index_test.go
@@ -0,0 +1,59 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestUser_Engine_CreateUserIndexes(t *testing.T) {
+ // setup types
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ _mock.ExpectExec(CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := test.database.CreateUserIndexes()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateUserIndexes for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateUserIndexes for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/user/interface.go b/database/user/interface.go
new file mode 100644
index 000000000..ad5986fad
--- /dev/null
+++ b/database/user/interface.go
@@ -0,0 +1,45 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "github.com/go-vela/types/library"
+)
+
+// UserInterface represents the Vela interface for user
+// functions with the supported Database backends.
+//
+//nolint:revive // ignore name stutter
+type UserInterface interface {
+ // User Data Definition Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_definition_language
+
+ // CreateUserIndexes defines a function that creates the indexes for the users table.
+ CreateUserIndexes() error
+ // CreateUserTable defines a function that creates the users table.
+ CreateUserTable(string) error
+
+ // User Data Manipulation Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_manipulation_language
+
+ // CountUsers defines a function that gets the count of all users.
+ CountUsers() (int64, error)
+ // CreateUser defines a function that creates a new user.
+ CreateUser(*library.User) error
+ // DeleteUser defines a function that deletes an existing user.
+ DeleteUser(*library.User) error
+ // GetUser defines a function that gets a user by ID.
+ GetUser(int64) (*library.User, error)
+ // GetUserForName defines a function that gets a user by name.
+ GetUserForName(string) (*library.User, error)
+ // ListUsers defines a function that gets a list of all users.
+ ListUsers() ([]*library.User, error)
+ // ListLiteUsers defines a function that gets a lite list of users.
+ ListLiteUsers(int, int) ([]*library.User, int64, error)
+ // UpdateUser defines a function that updates an existing user.
+ UpdateUser(*library.User) error
+}
diff --git a/database/user/list.go b/database/user/list.go
new file mode 100644
index 000000000..4bc730f27
--- /dev/null
+++ b/database/user/list.go
@@ -0,0 +1,67 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// ListUsers gets a list of all users from the database.
+func (e *engine) ListUsers() ([]*library.User, error) {
+ e.logger.Trace("listing all users from the database")
+
+ // variables to store query results and return value
+ count := int64(0)
+ u := new([]database.User)
+ users := []*library.User{}
+
+ // count the results
+ count, err := e.CountUsers()
+ if err != nil {
+ return nil, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return users, nil
+ }
+
+ // send query to the database and store result in variable
+ err = e.client.
+ Table(constants.TableUser).
+ Find(&u).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // iterate through all query results
+ for _, user := range *u {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := user
+
+ // decrypt the fields for the user
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#User.Decrypt
+ err = tmp.Decrypt(e.config.EncryptionKey)
+ if err != nil {
+ // TODO: remove backwards compatibility before 1.x.x release
+ //
+ // ensures that the change is backwards compatible
+ // by logging the error instead of returning it
+ // which allows us to fetch unencrypted users
+ e.logger.Errorf("unable to decrypt user %d: %v", tmp.ID.Int64, err)
+ }
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#User.ToLibrary
+ users = append(users, tmp.ToLibrary())
+ }
+
+ return users, nil
+}
diff --git a/database/user/list_lite.go b/database/user/list_lite.go
new file mode 100644
index 000000000..ee90bca3b
--- /dev/null
+++ b/database/user/list_lite.go
@@ -0,0 +1,61 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// ListLiteUsers gets a lite (only: id, name) list of users from the database.
+//
+//nolint:lll // ignore long line length due to variable names
+func (e *engine) ListLiteUsers(page, perPage int) ([]*library.User, int64, error) {
+ e.logger.Trace("listing lite users from the database")
+
+ // variables to store query results and return values
+ count := int64(0)
+ u := new([]database.User)
+ users := []*library.User{}
+
+ // count the results
+ count, err := e.CountUsers()
+ if err != nil {
+ return users, 0, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return users, 0, nil
+ }
+
+ // calculate offset for pagination through results
+ offset := perPage * (page - 1)
+
+ err = e.client.
+ Table(constants.TableUser).
+ Select("id", "name").
+ Limit(perPage).
+ Offset(offset).
+ Find(&u).
+ Error
+ if err != nil {
+ return nil, count, err
+ }
+
+ // iterate through all query results
+ for _, user := range *u {
+ // https://golang.org/doc/faq#closures_and_goroutines
+ tmp := user
+
+ // convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#User.ToLibrary
+ users = append(users, tmp.ToLibrary())
+ }
+
+ return users, count, nil
+}
diff --git a/database/user/list_lite_test.go b/database/user/list_lite_test.go
new file mode 100644
index 000000000..4c44bc20b
--- /dev/null
+++ b/database/user/list_lite_test.go
@@ -0,0 +1,116 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestUser_Engine_ListLiteUsers(t *testing.T) {
+ // setup types
+ _userOne := testUser()
+ _userOne.SetID(1)
+ _userOne.SetName("foo")
+ _userOne.SetToken("bar")
+ _userOne.SetHash("baz")
+ _userOne.SetFavorites([]string{})
+
+ _userTwo := testUser()
+ _userTwo.SetID(2)
+ _userTwo.SetName("baz")
+ _userTwo.SetToken("bar")
+ _userTwo.SetHash("foo")
+ _userTwo.SetFavorites([]string{})
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(1)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT count(*) FROM "users"`).WillReturnRows(_rows)
+
+ // create expected result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "name"}).
+ AddRow(1, "foo").
+ AddRow(2, "baz")
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT "id","name" FROM "users" LIMIT 10`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateUser(_userOne)
+ if err != nil {
+ t.Errorf("unable to create test user for sqlite: %v", err)
+ }
+
+ err = _sqlite.CreateUser(_userTwo)
+ if err != nil {
+ t.Errorf("unable to create test user for sqlite: %v", err)
+ }
+
+ // empty fields not returned by query
+ _userOne.RefreshToken = new(string)
+ _userOne.Token = new(string)
+ _userOne.Hash = new(string)
+ _userOne.Favorites = new([]string)
+
+ _userTwo.RefreshToken = new(string)
+ _userTwo.Token = new(string)
+ _userTwo.Hash = new(string)
+ _userTwo.Favorites = new([]string)
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.User
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.User{_userOne, _userTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.User{_userTwo, _userOne},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, _, err := test.database.ListLiteUsers(1, 10)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListLiteUsers for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListLiteUsers for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListLiteUsers for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/user/list_test.go b/database/user/list_test.go
new file mode 100644
index 000000000..61293d44c
--- /dev/null
+++ b/database/user/list_test.go
@@ -0,0 +1,105 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestUser_Engine_ListUsers(t *testing.T) {
+ // setup types
+ _userOne := testUser()
+ _userOne.SetID(1)
+ _userOne.SetName("foo")
+ _userOne.SetToken("bar")
+ _userOne.SetHash("baz")
+ _userOne.SetFavorites([]string{})
+
+ _userTwo := testUser()
+ _userTwo.SetID(2)
+ _userTwo.SetName("baz")
+ _userTwo.SetToken("bar")
+ _userTwo.SetHash("foo")
+ _userTwo.SetFavorites([]string{})
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT count(*) FROM "users"`).WillReturnRows(_rows)
+
+ // create expected result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "name", "refresh_token", "token", "hash", "favorites", "active", "admin"}).
+ AddRow(1, "foo", "", "bar", "baz", "{}", false, false).
+ AddRow(2, "baz", "", "bar", "foo", "{}", false, false)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "users"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateUser(_userOne)
+ if err != nil {
+ t.Errorf("unable to create test user for sqlite: %v", err)
+ }
+
+ err = _sqlite.CreateUser(_userTwo)
+ if err != nil {
+ t.Errorf("unable to create test user for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.User
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.User{_userOne, _userTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.User{_userOne, _userTwo},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.ListUsers()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListUsers for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListUsers for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListUsers for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/user/opts.go b/database/user/opts.go
new file mode 100644
index 000000000..58780c317
--- /dev/null
+++ b/database/user/opts.go
@@ -0,0 +1,54 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+// EngineOpt represents a configuration option to initialize the database engine for Users.
+type EngineOpt func(*engine) error
+
+// WithClient sets the gorm.io/gorm client in the database engine for Users.
+func WithClient(client *gorm.DB) EngineOpt {
+ return func(e *engine) error {
+ // set the gorm.io/gorm client in the user engine
+ e.client = client
+
+ return nil
+ }
+}
+
+// WithEncryptionKey sets the encryption key in the database engine for Users.
+func WithEncryptionKey(key string) EngineOpt {
+ return func(e *engine) error {
+ // set the encryption key in the user engine
+ e.config.EncryptionKey = key
+
+ return nil
+ }
+}
+
+// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for Users.
+func WithLogger(logger *logrus.Entry) EngineOpt {
+ return func(e *engine) error {
+ // set the github.com/sirupsen/logrus logger in the user engine
+ e.logger = logger
+
+ return nil
+ }
+}
+
+// WithSkipCreation sets the skip creation logic in the database engine for Users.
+func WithSkipCreation(skipCreation bool) EngineOpt {
+ return func(e *engine) error {
+ // set to skip creating tables and indexes in the user engine
+ e.config.SkipCreation = skipCreation
+
+ return nil
+ }
+}
diff --git a/database/user/opts_test.go b/database/user/opts_test.go
new file mode 100644
index 000000000..77fb9ae23
--- /dev/null
+++ b/database/user/opts_test.go
@@ -0,0 +1,210 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+func TestUser_EngineOpt_WithClient(t *testing.T) {
+ // setup types
+ e := &engine{client: new(gorm.DB)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ client *gorm.DB
+ want *gorm.DB
+ }{
+ {
+ failure: false,
+ name: "client set to new database",
+ client: new(gorm.DB),
+ want: new(gorm.DB),
+ },
+ {
+ failure: false,
+ name: "client set to nil",
+ client: nil,
+ want: nil,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithClient(test.client)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithClient for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithClient returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.client, test.want) {
+ t.Errorf("WithClient is %v, want %v", e.client, test.want)
+ }
+ })
+ }
+}
+
+func TestUser_EngineOpt_WithEncryptionKey(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ key string
+ want string
+ }{
+ {
+ failure: false,
+ name: "encryption key set",
+ key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ want: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ },
+ {
+ failure: false,
+ name: "encryption key not set",
+ key: "",
+ want: "",
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithEncryptionKey(test.key)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithEncryptionKey for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithEncryptionKey returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.config.EncryptionKey, test.want) {
+ t.Errorf("WithEncryptionKey is %v, want %v", e.config.EncryptionKey, test.want)
+ }
+ })
+ }
+}
+
+func TestUser_EngineOpt_WithLogger(t *testing.T) {
+ // setup types
+ e := &engine{logger: new(logrus.Entry)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ logger *logrus.Entry
+ want *logrus.Entry
+ }{
+ {
+ failure: false,
+ name: "logger set to new entry",
+ logger: new(logrus.Entry),
+ want: new(logrus.Entry),
+ },
+ {
+ failure: false,
+ name: "logger set to nil",
+ logger: nil,
+ want: nil,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithLogger(test.logger)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithLogger for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithLogger returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.logger, test.want) {
+ t.Errorf("WithLogger is %v, want %v", e.logger, test.want)
+ }
+ })
+ }
+}
+
+func TestUser_EngineOpt_WithSkipCreation(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ skipCreation bool
+ want bool
+ }{
+ {
+ failure: false,
+ name: "skip creation set to true",
+ skipCreation: true,
+ want: true,
+ },
+ {
+ failure: false,
+ name: "skip creation set to false",
+ skipCreation: false,
+ want: false,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithSkipCreation(test.skipCreation)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithSkipCreation for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithSkipCreation returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.config.SkipCreation, test.want) {
+ t.Errorf("WithSkipCreation is %v, want %v", e.config.SkipCreation, test.want)
+ }
+ })
+ }
+}
diff --git a/database/user/table.go b/database/user/table.go
new file mode 100644
index 000000000..456853770
--- /dev/null
+++ b/database/user/table.go
@@ -0,0 +1,62 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "github.com/go-vela/types/constants"
+)
+
+const (
+ // CreatePostgresTable represents a query to create the Postgres users table.
+ CreatePostgresTable = `
+CREATE TABLE
+IF NOT EXISTS
+users (
+ id SERIAL PRIMARY KEY,
+ name VARCHAR(250),
+ refresh_token VARCHAR(500),
+ token VARCHAR(500),
+ hash VARCHAR(500),
+ favorites VARCHAR(5000),
+ active BOOLEAN,
+ admin BOOLEAN,
+ UNIQUE(name)
+);
+`
+
+ // CreateSqliteTable represents a query to create the Sqlite users table.
+ CreateSqliteTable = `
+CREATE TABLE
+IF NOT EXISTS
+users (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT,
+ refresh_token TEXT,
+ token TEXT,
+ hash TEXT,
+ favorites TEXT,
+ active BOOLEAN,
+ admin BOOLEAN,
+ UNIQUE(name)
+);
+`
+)
+
+// CreateUserTable creates the users table in the database.
+func (e *engine) CreateUserTable(driver string) error {
+ e.logger.Tracef("creating users table in the database")
+
+ // handle the driver provided to create the table
+ switch driver {
+ case constants.DriverPostgres:
+ // create the users table for Postgres
+ return e.client.Exec(CreatePostgresTable).Error
+ case constants.DriverSqlite:
+ fallthrough
+ default:
+ // create the users table for Sqlite
+ return e.client.Exec(CreateSqliteTable).Error
+ }
+}
diff --git a/database/user/table_test.go b/database/user/table_test.go
new file mode 100644
index 000000000..95a2d4c00
--- /dev/null
+++ b/database/user/table_test.go
@@ -0,0 +1,59 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestUser_Engine_CreateUserTable(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.CreateUserTable(test.name)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateUserTable for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateUserTable for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/user/update.go b/database/user/update.go
new file mode 100644
index 000000000..c7efc5e7f
--- /dev/null
+++ b/database/user/update.go
@@ -0,0 +1,49 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+//nolint:dupl // ignore similar code in create.go
+package user
+
+import (
+ "fmt"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// UpdateUser updates an existing user in the database.
+func (e *engine) UpdateUser(u *library.User) error {
+ e.logger.WithFields(logrus.Fields{
+ "user": u.GetName(),
+ }).Tracef("updating user %s in the database", u.GetName())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#UserFromLibrary
+ user := database.UserFromLibrary(u)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#User.Validate
+ err := user.Validate()
+ if err != nil {
+ return err
+ }
+
+ // encrypt the fields for the user
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#User.Encrypt
+ err = user.Encrypt(e.config.EncryptionKey)
+ if err != nil {
+ return fmt.Errorf("unable to encrypt user %s: %w", u.GetName(), err)
+ }
+
+ // send query to the database
+ return e.client.
+ Table(constants.TableUser).
+ Save(user).
+ Error
+}
diff --git a/database/user/update_test.go b/database/user/update_test.go
new file mode 100644
index 000000000..4253ac435
--- /dev/null
+++ b/database/user/update_test.go
@@ -0,0 +1,75 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestUser_Engine_UpdateUser(t *testing.T) {
+ // setup types
+ _user := testUser()
+ _user.SetID(1)
+ _user.SetName("foo")
+ _user.SetToken("bar")
+ _user.SetHash("baz")
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`UPDATE "users"
+SET "name"=$1,"refresh_token"=$2,"token"=$3,"hash"=$4,"favorites"=$5,"active"=$6,"admin"=$7
+WHERE "id" = $8`).
+ WithArgs("foo", AnyArgument{}, AnyArgument{}, AnyArgument{}, nil, false, false, 1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateUser(_user)
+ if err != nil {
+ t.Errorf("unable to create test user for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err = test.database.UpdateUser(_user)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("UpdateUser for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("UpdateUser for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/user/user.go b/database/user/user.go
new file mode 100644
index 000000000..99e1f9701
--- /dev/null
+++ b/database/user/user.go
@@ -0,0 +1,82 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "fmt"
+
+ "github.com/go-vela/types/constants"
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+type (
+ // config represents the settings required to create the engine that implements the UserInterface interface.
+ config struct {
+ // specifies the encryption key to use for the User engine
+ EncryptionKey string
+ // specifies to skip creating tables and indexes for the User engine
+ SkipCreation bool
+ }
+
+ // engine represents the user functionality that implements the UserInterface interface.
+ engine struct {
+ // engine configuration settings used in user functions
+ config *config
+
+ // gorm.io/gorm database client used in user functions
+ //
+ // https://pkg.go.dev/gorm.io/gorm#DB
+ client *gorm.DB
+
+ // sirupsen/logrus logger used in user functions
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus#Entry
+ logger *logrus.Entry
+ }
+)
+
+// New creates and returns a Vela service for integrating with users in the database.
+//
+//nolint:revive // ignore returning unexported engine
+func New(opts ...EngineOpt) (*engine, error) {
+ // create new User engine
+ e := new(engine)
+
+ // create new fields
+ e.client = new(gorm.DB)
+ e.config = new(config)
+ e.logger = new(logrus.Entry)
+
+ // apply all provided configuration options
+ for _, opt := range opts {
+ err := opt(e)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // check if we should skip creating user database objects
+ if e.config.SkipCreation {
+ e.logger.Warning("skipping creation of users table and indexes in the database")
+
+ return e, nil
+ }
+
+ // create the users table
+ err := e.CreateUserTable(e.client.Config.Dialector.Name())
+ if err != nil {
+ return nil, fmt.Errorf("unable to create %s table: %w", constants.TableUser, err)
+ }
+
+ // create the indexes for the users table
+ err = e.CreateUserIndexes()
+ if err != nil {
+ return nil, fmt.Errorf("unable to create indexes for %s table: %w", constants.TableUser, err)
+ }
+
+ return e, nil
+}
diff --git a/database/user/user_test.go b/database/user/user_test.go
new file mode 100644
index 000000000..1586103a7
--- /dev/null
+++ b/database/user/user_test.go
@@ -0,0 +1,200 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package user
+
+import (
+ "database/sql/driver"
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/driver/postgres"
+ "gorm.io/driver/sqlite"
+ "gorm.io/gorm"
+)
+
+func TestUser_New(t *testing.T) {
+ // setup types
+ logger := logrus.NewEntry(logrus.StandardLogger())
+
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+ defer _sql.Close()
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _config := &gorm.Config{SkipDefaultTransaction: true}
+
+ _postgres, err := gorm.Open(postgres.New(postgres.Config{Conn: _sql}), _config)
+ if err != nil {
+ t.Errorf("unable to create new postgres database: %v", err)
+ }
+
+ _sqlite, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), _config)
+ if err != nil {
+ t.Errorf("unable to create new sqlite database: %v", err)
+ }
+
+ defer func() { _sql, _ := _sqlite.DB(); _sql.Close() }()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ client *gorm.DB
+ key string
+ logger *logrus.Entry
+ skipCreation bool
+ want *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ client: _postgres,
+ key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ client: _postgres,
+ config: &config{EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", SkipCreation: false},
+ logger: logger,
+ },
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ client: _sqlite,
+ key: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ client: _sqlite,
+ config: &config{EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW", SkipCreation: false},
+ logger: logger,
+ },
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := New(
+ WithClient(test.client),
+ WithEncryptionKey(test.key),
+ WithLogger(test.logger),
+ WithSkipCreation(test.skipCreation),
+ )
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("New for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("New for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("New for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
+
+// testPostgres is a helper function to create a Postgres engine for testing.
+func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) {
+ // create the new mock sql database
+ //
+ // https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#New
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateUserRefreshIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+
+ // create the new mock Postgres database client
+ //
+ // https://pkg.go.dev/gorm.io/gorm#Open
+ _postgres, err := gorm.Open(
+ postgres.New(postgres.Config{Conn: _sql}),
+ &gorm.Config{SkipDefaultTransaction: true},
+ )
+ if err != nil {
+ t.Errorf("unable to create new postgres database: %v", err)
+ }
+
+ _engine, err := New(
+ WithClient(_postgres),
+ WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"),
+ WithLogger(logrus.NewEntry(logrus.StandardLogger())),
+ WithSkipCreation(false),
+ )
+ if err != nil {
+ t.Errorf("unable to create new postgres user engine: %v", err)
+ }
+
+ return _engine, _mock
+}
+
+// testSqlite is a helper function to create a Sqlite engine for testing.
+func testSqlite(t *testing.T) *engine {
+ _sqlite, err := gorm.Open(
+ sqlite.Open("file::memory:?cache=shared"),
+ &gorm.Config{SkipDefaultTransaction: true},
+ )
+ if err != nil {
+ t.Errorf("unable to create new sqlite database: %v", err)
+ }
+
+ _engine, err := New(
+ WithClient(_sqlite),
+ WithEncryptionKey("A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW"),
+ WithLogger(logrus.NewEntry(logrus.StandardLogger())),
+ WithSkipCreation(false),
+ )
+ if err != nil {
+ t.Errorf("unable to create new sqlite user engine: %v", err)
+ }
+
+ return _engine
+}
+
+// testUser is a test helper function to create a library
+// User type with all fields set to their zero values.
+func testUser() *library.User {
+ return &library.User{
+ ID: new(int64),
+ Name: new(string),
+ RefreshToken: new(string),
+ Token: new(string),
+ Hash: new(string),
+ Favorites: new([]string),
+ Active: new(bool),
+ Admin: new(bool),
+ }
+}
+
+// This will be used with the github.com/DATA-DOG/go-sqlmock library to compare values
+// that are otherwise not easily compared. These typically would be values generated
+// before adding or updating them in the database.
+//
+// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime
+type AnyArgument struct{}
+
+// Match satisfies sqlmock.Argument interface.
+func (a AnyArgument) Match(v driver.Value) bool {
+ return true
+}
diff --git a/database/validate.go b/database/validate.go
new file mode 100644
index 000000000..18f07cb84
--- /dev/null
+++ b/database/validate.go
@@ -0,0 +1,75 @@
+// Copyright (c) 2023 Target Brands, Ine. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package database
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/go-vela/types/constants"
+ "github.com/sirupsen/logrus"
+)
+
+// Validate verifies the required fields from the provided configuration are populated correctly.
+func (c *config) Validate() error {
+ logrus.Trace("validating database configuration for engine")
+
+ // verify a database driver was provided
+ if len(c.Driver) == 0 {
+ return fmt.Errorf("no database driver provided")
+ }
+
+ // verify a database address was provided
+ if len(c.Address) == 0 {
+ return fmt.Errorf("no database address provided")
+ }
+
+ // check if the database address has a trailing slash
+ if strings.HasSuffix(c.Address, "/") {
+ return fmt.Errorf("invalid database address provided: address must not have trailing slash")
+ }
+
+ // verify a database encryption key was provided
+ if len(c.EncryptionKey) == 0 {
+ return fmt.Errorf("no database encryption key provided")
+ }
+
+ // check the database encryption key length - enforce AES-256 by forcing 32 characters in the key
+ if len(c.EncryptionKey) != 32 {
+ return fmt.Errorf("invalid database encryption key provided: key length (%d) must be 32 characters", len(c.EncryptionKey))
+ }
+
+ // verify the database compression level is valid
+ switch c.CompressionLevel {
+ case constants.CompressionNegOne:
+ fallthrough
+ case constants.CompressionZero:
+ fallthrough
+ case constants.CompressionOne:
+ fallthrough
+ case constants.CompressionTwo:
+ fallthrough
+ case constants.CompressionThree:
+ fallthrough
+ case constants.CompressionFour:
+ fallthrough
+ case constants.CompressionFive:
+ fallthrough
+ case constants.CompressionSix:
+ fallthrough
+ case constants.CompressionSeven:
+ fallthrough
+ case constants.CompressionEight:
+ fallthrough
+ case constants.CompressionNine:
+ break
+ default:
+ return fmt.Errorf("invalid database compression level provided: level (%d) must be between %d and %d",
+ c.CompressionLevel, constants.CompressionNegOne, constants.CompressionNine,
+ )
+ }
+
+ return nil
+}
diff --git a/database/setup_test.go b/database/validate_test.go
similarity index 59%
rename from database/setup_test.go
rename to database/validate_test.go
index 4ec2c958c..60761e215 100644
--- a/database/setup_test.go
+++ b/database/validate_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Ine. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
@@ -9,107 +9,17 @@ import (
"time"
)
-func TestDatabase_Setup_Postgres(t *testing.T) {
- // setup types
- _setup := &Setup{
- Driver: "postgres",
- Address: "postgres://foo:bar@localhost:5432/vela",
- CompressionLevel: 3,
- ConnectionLife: 10 * time.Second,
- ConnectionIdle: 5,
- ConnectionOpen: 20,
- EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
- SkipCreation: false,
- }
-
+func TestDatabase_Config_Validate(t *testing.T) {
// setup tests
tests := []struct {
failure bool
- setup *Setup
- }{
- {
- failure: true,
- setup: _setup,
- },
- {
- failure: true,
- setup: &Setup{Driver: "postgres"},
- },
- }
-
- // run tests
- for _, test := range tests {
- _, err := test.setup.Postgres()
-
- if test.failure {
- if err == nil {
- t.Errorf("Postgres should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("Postgres returned err: %v", err)
- }
- }
-}
-
-func TestDatabase_Setup_Sqlite(t *testing.T) {
- // setup types
- _setup := &Setup{
- Driver: "sqlite3",
- Address: "file::memory:?cache=shared",
- CompressionLevel: 3,
- ConnectionLife: 10 * time.Second,
- ConnectionIdle: 5,
- ConnectionOpen: 20,
- EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
- SkipCreation: false,
- }
-
- // setup tests
- tests := []struct {
- failure bool
- setup *Setup
+ name string
+ config *config
}{
{
+ name: "success with postgres",
failure: false,
- setup: _setup,
- },
- {
- failure: true,
- setup: &Setup{Driver: "sqlite3"},
- },
- }
-
- // run tests
- for _, test := range tests {
- _, err := test.setup.Sqlite()
-
- if test.failure {
- if err == nil {
- t.Errorf("Sqlite should have returned err")
- }
-
- continue
- }
-
- if err != nil {
- t.Errorf("Sqlite returned err: %v", err)
- }
- }
-}
-
-func TestDatabase_Setup_Validate(t *testing.T) {
- // setup tests
- tests := []struct {
- failure bool
- setup *Setup
- }{
- {
- failure: false,
- setup: &Setup{
+ config: &config{
Driver: "postgres",
Address: "postgres://foo:bar@localhost:5432/vela",
CompressionLevel: 3,
@@ -121,8 +31,9 @@ func TestDatabase_Setup_Validate(t *testing.T) {
},
},
{
+ name: "success with sqlite3",
failure: false,
- setup: &Setup{
+ config: &config{
Driver: "sqlite3",
Address: "file::memory:?cache=shared",
CompressionLevel: 3,
@@ -134,8 +45,9 @@ func TestDatabase_Setup_Validate(t *testing.T) {
},
},
{
+ name: "success with negative compression level",
failure: false,
- setup: &Setup{
+ config: &config{
Driver: "postgres",
Address: "postgres://foo:bar@localhost:5432/vela",
CompressionLevel: -1,
@@ -147,10 +59,11 @@ func TestDatabase_Setup_Validate(t *testing.T) {
},
},
{
+ name: "failure with empty driver",
failure: true,
- setup: &Setup{
- Driver: "postgres",
- Address: "postgres://foo:bar@localhost:5432/vela/",
+ config: &config{
+ Driver: "",
+ Address: "postgres://foo:bar@localhost:5432/vela",
CompressionLevel: 3,
ConnectionLife: 10 * time.Second,
ConnectionIdle: 5,
@@ -160,10 +73,11 @@ func TestDatabase_Setup_Validate(t *testing.T) {
},
},
{
+ name: "failure with empty address",
failure: true,
- setup: &Setup{
- Driver: "",
- Address: "postgres://foo:bar@localhost:5432/vela",
+ config: &config{
+ Driver: "postgres",
+ Address: "",
CompressionLevel: 3,
ConnectionLife: 10 * time.Second,
ConnectionIdle: 5,
@@ -173,10 +87,11 @@ func TestDatabase_Setup_Validate(t *testing.T) {
},
},
{
+ name: "failure with invalid address",
failure: true,
- setup: &Setup{
+ config: &config{
Driver: "postgres",
- Address: "",
+ Address: "postgres://foo:bar@localhost:5432/vela/",
CompressionLevel: 3,
ConnectionLife: 10 * time.Second,
ConnectionIdle: 5,
@@ -186,41 +101,44 @@ func TestDatabase_Setup_Validate(t *testing.T) {
},
},
{
+ name: "failure with invalid compression level",
failure: true,
- setup: &Setup{
+ config: &config{
Driver: "postgres",
Address: "postgres://foo:bar@localhost:5432/vela",
- CompressionLevel: 3,
+ CompressionLevel: 10,
ConnectionLife: 10 * time.Second,
ConnectionIdle: 5,
ConnectionOpen: 20,
- EncryptionKey: "",
+ EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
SkipCreation: false,
},
},
{
+ name: "failure with empty encryption key",
failure: true,
- setup: &Setup{
+ config: &config{
Driver: "postgres",
Address: "postgres://foo:bar@localhost:5432/vela",
CompressionLevel: 3,
ConnectionLife: 10 * time.Second,
ConnectionIdle: 5,
ConnectionOpen: 20,
- EncryptionKey: "A1B2C3D4E5G6H7I8J9K0",
+ EncryptionKey: "",
SkipCreation: false,
},
},
{
+ name: "failure with invalid encryption key",
failure: true,
- setup: &Setup{
+ config: &config{
Driver: "postgres",
Address: "postgres://foo:bar@localhost:5432/vela",
- CompressionLevel: 10,
+ CompressionLevel: 3,
ConnectionLife: 10 * time.Second,
ConnectionIdle: 5,
ConnectionOpen: 20,
- EncryptionKey: "A1B2C3D4E5G6H7I8J9K0LMNOPQRSTUVW",
+ EncryptionKey: "A1B2C3D4E5G6H7I8J9K0",
SkipCreation: false,
},
},
@@ -228,18 +146,20 @@ func TestDatabase_Setup_Validate(t *testing.T) {
// run tests
for _, test := range tests {
- err := test.setup.Validate()
+ t.Run(test.name, func(t *testing.T) {
+ err := test.config.Validate()
- if test.failure {
- if err == nil {
- t.Errorf("Validate should have returned err")
- }
+ if test.failure {
+ if err == nil {
+ t.Errorf("Validate for %s should have returned err", test.name)
+ }
- continue
- }
+ return
+ }
- if err != nil {
- t.Errorf("Validate returned err: %v", err)
- }
+ if err != nil {
+ t.Errorf("Validate for %s returned err: %v", test.name, err)
+ }
+ })
}
}
diff --git a/database/sqlite/worker_count.go b/database/worker/count.go
similarity index 53%
rename from database/sqlite/worker_count.go
rename to database/worker/count.go
index 38b2cf7b9..8ac0f3eb5 100644
--- a/database/sqlite/worker_count.go
+++ b/database/worker/count.go
@@ -2,25 +2,24 @@
//
// Use of this source code is governed by the LICENSE file in this repository.
-package sqlite
+package worker
import (
- "github.com/go-vela/server/database/sqlite/dml"
"github.com/go-vela/types/constants"
)
-// GetWorkerCount gets a count of all workers from the database.
-func (c *client) GetWorkerCount() (int64, error) {
- c.Logger.Trace("getting count of workers from the database")
+// CountWorkers gets the count of all workers from the database.
+func (e *engine) CountWorkers() (int64, error) {
+ e.logger.Tracef("getting count of all workers from the database")
// variable to store query results
var w int64
// send query to the database and store result in variable
- err := c.Sqlite.
+ err := e.client.
Table(constants.TableWorker).
- Raw(dml.SelectWorkersCount).
- Pluck("count", &w).Error
+ Count(&w).
+ Error
return w, err
}
diff --git a/database/worker/count_test.go b/database/worker/count_test.go
new file mode 100644
index 000000000..bd9d4c4ac
--- /dev/null
+++ b/database/worker/count_test.go
@@ -0,0 +1,93 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestWorker_Engine_CountWorkers(t *testing.T) {
+ // setup types
+ _workerOne := testWorker()
+ _workerOne.SetID(1)
+ _workerOne.SetHostname("worker_0")
+ _workerOne.SetAddress("localhost")
+ _workerOne.SetActive(true)
+
+ _workerTwo := testWorker()
+ _workerTwo.SetID(2)
+ _workerTwo.SetHostname("worker_1")
+ _workerTwo.SetAddress("localhost")
+ _workerTwo.SetActive(true)
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT count(*) FROM "workers"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateWorker(_workerOne)
+ if err != nil {
+ t.Errorf("unable to create test worker for sqlite: %v", err)
+ }
+
+ err = _sqlite.CreateWorker(_workerTwo)
+ if err != nil {
+ t.Errorf("unable to create test worker for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want int64
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: 2,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: 2,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.CountWorkers()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CountWorkers for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CountWorkers for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("CountWorkers for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/worker/create.go b/database/worker/create.go
new file mode 100644
index 000000000..6c62b30b6
--- /dev/null
+++ b/database/worker/create.go
@@ -0,0 +1,38 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// CreateWorker creates a new worker in the database.
+func (e *engine) CreateWorker(w *library.Worker) error {
+ e.logger.WithFields(logrus.Fields{
+ "worker": w.GetHostname(),
+ }).Tracef("creating worker %s in the database", w.GetHostname())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#WorkerFromLibrary
+ worker := database.WorkerFromLibrary(w)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Worker.Validate
+ err := worker.Validate()
+ if err != nil {
+ return err
+ }
+
+ // send query to the database
+ return e.client.
+ Table(constants.TableWorker).
+ Create(worker).
+ Error
+}
diff --git a/database/worker/create_test.go b/database/worker/create_test.go
new file mode 100644
index 000000000..e4c4dc9cb
--- /dev/null
+++ b/database/worker/create_test.go
@@ -0,0 +1,73 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestWorker_Engine_CreateWorker(t *testing.T) {
+ // setup types
+ _worker := testWorker()
+ _worker.SetID(1)
+ _worker.SetHostname("worker_0")
+ _worker.SetAddress("localhost")
+ _worker.SetActive(true)
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows([]string{"id"}).AddRow(1)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`INSERT INTO "workers"
+("hostname","address","routes","active","status","last_status_update_at","running_build_ids","last_build_started_at","last_build_finished_at","last_checked_in","build_limit","id")
+VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) RETURNING "id"`).
+ WithArgs("worker_0", "localhost", nil, true, 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.CreateWorker(_worker)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateWorker for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateWorker for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/worker/delete.go b/database/worker/delete.go
new file mode 100644
index 000000000..a04ebb13e
--- /dev/null
+++ b/database/worker/delete.go
@@ -0,0 +1,30 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// DeleteWorker deletes an existing worker from the database.
+func (e *engine) DeleteWorker(w *library.Worker) error {
+ e.logger.WithFields(logrus.Fields{
+ "worker": w.GetHostname(),
+ }).Tracef("deleting worker %s from the database", w.GetHostname())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#WorkerFromLibrary
+ worker := database.WorkerFromLibrary(w)
+
+ // send query to the database
+ return e.client.
+ Table(constants.TableWorker).
+ Delete(worker).
+ Error
+}
diff --git a/database/worker/delete_test.go b/database/worker/delete_test.go
new file mode 100644
index 000000000..c8a9bd1be
--- /dev/null
+++ b/database/worker/delete_test.go
@@ -0,0 +1,73 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestWorker_Engine_DeleteWorker(t *testing.T) {
+ // setup types
+ _worker := testWorker()
+ _worker.SetID(1)
+ _worker.SetHostname("worker_0")
+ _worker.SetAddress("localhost")
+ _worker.SetActive(true)
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`DELETE FROM "workers" WHERE "workers"."id" = $1`).
+ WithArgs(1).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateWorker(_worker)
+ if err != nil {
+ t.Errorf("unable to create test worker for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err = test.database.DeleteWorker(_worker)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("DeleteWorker for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("DeleteWorker for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/worker/get.go b/database/worker/get.go
new file mode 100644
index 000000000..dd2b07ecc
--- /dev/null
+++ b/database/worker/get.go
@@ -0,0 +1,34 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+)
+
+// GetWorker gets a worker by ID from the database.
+func (e *engine) GetWorker(id int64) (*library.Worker, error) {
+ e.logger.Tracef("getting worker %d from the database", id)
+
+ // variable to store query results
+ w := new(database.Worker)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableWorker).
+ Where("id = ?", id).
+ Take(w).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // return the worker
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Worker.ToLibrary
+ return w.ToLibrary(), nil
+}
diff --git a/database/worker/get_hostname.go b/database/worker/get_hostname.go
new file mode 100644
index 000000000..6bcf42a2b
--- /dev/null
+++ b/database/worker/get_hostname.go
@@ -0,0 +1,37 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// GetWorkerForHostname gets a worker by hostname from the database.
+func (e *engine) GetWorkerForHostname(hostname string) (*library.Worker, error) {
+ e.logger.WithFields(logrus.Fields{
+ "worker": hostname,
+ }).Tracef("getting worker %s from the database", hostname)
+
+ // variable to store query results
+ w := new(database.Worker)
+
+ // send query to the database and store result in variable
+ err := e.client.
+ Table(constants.TableWorker).
+ Where("hostname = ?", hostname).
+ Take(w).
+ Error
+ if err != nil {
+ return nil, err
+ }
+
+ // return the worker
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Worker.ToLibrary
+ return w.ToLibrary(), nil
+}
diff --git a/database/worker/get_hostname_test.go b/database/worker/get_hostname_test.go
new file mode 100644
index 000000000..3dd1d4fe6
--- /dev/null
+++ b/database/worker/get_hostname_test.go
@@ -0,0 +1,85 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestWorker_Engine_GetWorkerForName(t *testing.T) {
+ // setup types
+ _worker := testWorker()
+ _worker.SetID(1)
+ _worker.SetHostname("worker_0")
+ _worker.SetAddress("localhost")
+ _worker.SetActive(true)
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "hostname", "address", "routes", "active", "status", "last_status_update_at", "running_build_ids", "last_build_started_at", "last_build_finished_at", "last_checked_in", "build_limit"}).
+ AddRow(1, "worker_0", "localhost", nil, true, nil, 0, nil, 0, 0, 0, 0)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "workers" WHERE hostname = $1 LIMIT 1`).WithArgs("worker_0").WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateWorker(_worker)
+ if err != nil {
+ t.Errorf("unable to create test worker for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Worker
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _worker,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _worker,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetWorkerForHostname("worker_0")
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetWorkerForHostname for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetWorkerForHostname for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetWorkerForHostname for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/worker/get_test.go b/database/worker/get_test.go
new file mode 100644
index 000000000..17fd03739
--- /dev/null
+++ b/database/worker/get_test.go
@@ -0,0 +1,85 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestWorker_Engine_GetWorker(t *testing.T) {
+ // setup types
+ _worker := testWorker()
+ _worker.SetID(1)
+ _worker.SetHostname("worker_0")
+ _worker.SetAddress("localhost")
+ _worker.SetActive(true)
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows(
+ []string{"id", "hostname", "address", "routes", "active", "last_checked_in", "build_limit"}).
+ AddRow(1, "worker_0", "localhost", nil, true, 0, 0)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "workers" WHERE id = $1 LIMIT 1`).WithArgs(1).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateWorker(_worker)
+ if err != nil {
+ t.Errorf("unable to create test worker for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want *library.Worker
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: _worker,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: _worker,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.GetWorker(1)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("GetWorker for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("GetWorker for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("GetWorker for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/worker/index.go b/database/worker/index.go
new file mode 100644
index 000000000..f8f01a4b6
--- /dev/null
+++ b/database/worker/index.go
@@ -0,0 +1,24 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+const (
+ // CreateHostnameAddressIndex represents a query to create an
+ // index on the workers table for the hostname and address columns.
+ CreateHostnameAddressIndex = `
+CREATE INDEX
+IF NOT EXISTS
+workers_hostname_address
+ON workers (hostname, address);
+`
+)
+
+// CreateWorkerIndexes creates the indexes for the workers table in the database.
+func (e *engine) CreateWorkerIndexes() error {
+ e.logger.Tracef("creating indexes for workers table in the database")
+
+ // create the hostname and address columns index for the workers table
+ return e.client.Exec(CreateHostnameAddressIndex).Error
+}
diff --git a/database/worker/index_test.go b/database/worker/index_test.go
new file mode 100644
index 000000000..ead204e5c
--- /dev/null
+++ b/database/worker/index_test.go
@@ -0,0 +1,59 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestWorker_Engine_CreateWorkerIndexes(t *testing.T) {
+ // setup types
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ _mock.ExpectExec(CreateHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := test.database.CreateWorkerIndexes()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateWorkerIndexes for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateWorkerIndexes for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/worker/interface.go b/database/worker/interface.go
new file mode 100644
index 000000000..9e7fe1169
--- /dev/null
+++ b/database/worker/interface.go
@@ -0,0 +1,43 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "github.com/go-vela/types/library"
+)
+
+// WorkerInterface represents the Vela interface for worker
+// functions with the supported Database backends.
+//
+//nolint:revive // ignore name stutter
+type WorkerInterface interface {
+ // Worker Data Definition Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_definition_language
+
+ // CreateWorkerIndexes defines a function that creates the indexes for the workers table.
+ CreateWorkerIndexes() error
+ // CreateWorkerTable defines a function that creates the workers table.
+ CreateWorkerTable(string) error
+
+ // Worker Data Manipulation Language Functions
+ //
+ // https://en.wikipedia.org/wiki/Data_manipulation_language
+
+ // CountWorkers defines a function that gets the count of all workers.
+ CountWorkers() (int64, error)
+ // CreateWorker defines a function that creates a new worker.
+ CreateWorker(*library.Worker) error
+ // DeleteWorker defines a function that deletes an existing worker.
+ DeleteWorker(*library.Worker) error
+ // GetWorker defines a function that gets a worker by ID.
+ GetWorker(int64) (*library.Worker, error)
+ // GetWorkerForHostname defines a function that gets a worker by hostname.
+ GetWorkerForHostname(string) (*library.Worker, error)
+ // ListWorkers defines a function that gets a list of all workers.
+ ListWorkers() ([]*library.Worker, error)
+ // UpdateWorker defines a function that updates an existing worker.
+ UpdateWorker(*library.Worker) error
+}
diff --git a/database/postgres/worker_list.go b/database/worker/list.go
similarity index 51%
rename from database/postgres/worker_list.go
rename to database/worker/list.go
index 87bc66e7b..4ec11ef3d 100644
--- a/database/postgres/worker_list.go
+++ b/database/worker/list.go
@@ -2,38 +2,53 @@
//
// Use of this source code is governed by the LICENSE file in this repository.
-package postgres
+package worker
import (
- "github.com/go-vela/server/database/postgres/dml"
"github.com/go-vela/types/constants"
"github.com/go-vela/types/database"
"github.com/go-vela/types/library"
)
-// GetWorkerList gets a list of all workers from the database.
-func (c *client) GetWorkerList() ([]*library.Worker, error) {
- c.Logger.Trace("listing workers from the database")
+// ListWorkers gets a list of all workers from the database.
+func (e *engine) ListWorkers() ([]*library.Worker, error) {
+ e.logger.Trace("listing all workers from the database")
- // variable to store query results
+ // variables to store query results and return value
+ count := int64(0)
w := new([]database.Worker)
+ workers := []*library.Worker{}
+
+ // count the results
+ count, err := e.CountWorkers()
+ if err != nil {
+ return nil, err
+ }
+
+ // short-circuit if there are no results
+ if count == 0 {
+ return workers, nil
+ }
// send query to the database and store result in variable
- err := c.Postgres.
+ err = e.client.
Table(constants.TableWorker).
- Raw(dml.ListWorkers).
- Scan(w).Error
+ Find(&w).
+ Error
+ if err != nil {
+ return nil, err
+ }
- // variable we want to return
- workers := []*library.Worker{}
// iterate through all query results
for _, worker := range *w {
// https://golang.org/doc/faq#closures_and_goroutines
tmp := worker
// convert query result to library type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Worker.ToLibrary
workers = append(workers, tmp.ToLibrary())
}
- return workers, err
+ return workers, nil
}
diff --git a/database/worker/list_test.go b/database/worker/list_test.go
new file mode 100644
index 000000000..5eed3f94f
--- /dev/null
+++ b/database/worker/list_test.go
@@ -0,0 +1,103 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+)
+
+func TestWorker_Engine_ListWorkers(t *testing.T) {
+ // setup types
+ _workerOne := testWorker()
+ _workerOne.SetID(1)
+ _workerOne.SetHostname("worker_0")
+ _workerOne.SetAddress("localhost")
+ _workerOne.SetActive(true)
+
+ _workerTwo := testWorker()
+ _workerTwo.SetID(2)
+ _workerTwo.SetHostname("worker_1")
+ _workerTwo.SetAddress("localhost")
+ _workerTwo.SetActive(true)
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // create expected result in mock
+ _rows := sqlmock.NewRows([]string{"count"}).AddRow(2)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT count(*) FROM "workers"`).WillReturnRows(_rows)
+
+ // create expected result in mock
+ _rows = sqlmock.NewRows(
+ []string{"id", "hostname", "address", "routes", "active", "status", "last_status_update_at", "running_build_ids", "last_build_started_at", "last_build_finished_at", "last_checked_in", "build_limit"}).
+ AddRow(1, "worker_0", "localhost", nil, true, nil, 0, nil, 0, 0, 0, 0).
+ AddRow(2, "worker_1", "localhost", nil, true, nil, 0, nil, 0, 0, 0, 0)
+
+ // ensure the mock expects the query
+ _mock.ExpectQuery(`SELECT * FROM "workers"`).WillReturnRows(_rows)
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ err := _sqlite.CreateWorker(_workerOne)
+ if err != nil {
+ t.Errorf("unable to create test worker for sqlite: %v", err)
+ }
+
+ err = _sqlite.CreateWorker(_workerTwo)
+ if err != nil {
+ t.Errorf("unable to create test worker for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ want []*library.Worker
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ want: []*library.Worker{_workerOne, _workerTwo},
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ want: []*library.Worker{_workerOne, _workerTwo},
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := test.database.ListWorkers()
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("ListWorkers for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("ListWorkers for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ListWorkers for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/database/worker/opts.go b/database/worker/opts.go
new file mode 100644
index 000000000..c9891ba94
--- /dev/null
+++ b/database/worker/opts.go
@@ -0,0 +1,44 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+// EngineOpt represents a configuration option to initialize the database engine for Workers.
+type EngineOpt func(*engine) error
+
+// WithClient sets the gorm.io/gorm client in the database engine for Workers.
+func WithClient(client *gorm.DB) EngineOpt {
+ return func(e *engine) error {
+ // set the gorm.io/gorm client in the worker engine
+ e.client = client
+
+ return nil
+ }
+}
+
+// WithLogger sets the github.com/sirupsen/logrus logger in the database engine for Workers.
+func WithLogger(logger *logrus.Entry) EngineOpt {
+ return func(e *engine) error {
+ // set the github.com/sirupsen/logrus logger in the worker engine
+ e.logger = logger
+
+ return nil
+ }
+}
+
+// WithSkipCreation sets the skip creation logic in the database engine for Workers.
+func WithSkipCreation(skipCreation bool) EngineOpt {
+ return func(e *engine) error {
+ // set to skip creating tables and indexes in the worker engine
+ e.config.SkipCreation = skipCreation
+
+ return nil
+ }
+}
diff --git a/database/worker/opts_test.go b/database/worker/opts_test.go
new file mode 100644
index 000000000..a0ebf6aa5
--- /dev/null
+++ b/database/worker/opts_test.go
@@ -0,0 +1,161 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+func TestWorker_EngineOpt_WithClient(t *testing.T) {
+ // setup types
+ e := &engine{client: new(gorm.DB)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ client *gorm.DB
+ want *gorm.DB
+ }{
+ {
+ failure: false,
+ name: "client set to new database",
+ client: new(gorm.DB),
+ want: new(gorm.DB),
+ },
+ {
+ failure: false,
+ name: "client set to nil",
+ client: nil,
+ want: nil,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithClient(test.client)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithClient for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithClient returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.client, test.want) {
+ t.Errorf("WithClient is %v, want %v", e.client, test.want)
+ }
+ })
+ }
+}
+
+func TestWorker_EngineOpt_WithLogger(t *testing.T) {
+ // setup types
+ e := &engine{logger: new(logrus.Entry)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ logger *logrus.Entry
+ want *logrus.Entry
+ }{
+ {
+ failure: false,
+ name: "logger set to new entry",
+ logger: new(logrus.Entry),
+ want: new(logrus.Entry),
+ },
+ {
+ failure: false,
+ name: "logger set to nil",
+ logger: nil,
+ want: nil,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithLogger(test.logger)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithLogger for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithLogger returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.logger, test.want) {
+ t.Errorf("WithLogger is %v, want %v", e.logger, test.want)
+ }
+ })
+ }
+}
+
+func TestWorker_EngineOpt_WithSkipCreation(t *testing.T) {
+ // setup types
+ e := &engine{config: new(config)}
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ skipCreation bool
+ want bool
+ }{
+ {
+ failure: false,
+ name: "skip creation set to true",
+ skipCreation: true,
+ want: true,
+ },
+ {
+ failure: false,
+ name: "skip creation set to false",
+ skipCreation: false,
+ want: false,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := WithSkipCreation(test.skipCreation)(e)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithSkipCreation for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("WithSkipCreation returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(e.config.SkipCreation, test.want) {
+ t.Errorf("WithSkipCreation is %v, want %v", e.config.SkipCreation, test.want)
+ }
+ })
+ }
+}
diff --git a/database/worker/table.go b/database/worker/table.go
new file mode 100644
index 000000000..1d704674a
--- /dev/null
+++ b/database/worker/table.go
@@ -0,0 +1,69 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "github.com/go-vela/types/constants"
+)
+
+const (
+ // CreatePostgresTable represents a query to create the Postgres workers table.
+ CreatePostgresTable = `
+CREATE TABLE
+IF NOT EXISTS
+workers (
+ id SERIAL PRIMARY KEY,
+ hostname VARCHAR(250),
+ address VARCHAR(250),
+ routes VARCHAR(1000),
+ active BOOLEAN,
+ status VARCHAR(50),
+ last_status_update_at INTEGER,
+ running_build_ids VARCHAR(500),
+ last_build_started_at INTEGER,
+ last_build_finished_at INTEGER,
+ last_checked_in INTEGER,
+ build_limit INTEGER,
+ UNIQUE(hostname)
+);
+`
+ // CreateSqliteTable represents a query to create the Sqlite workers table.
+ CreateSqliteTable = `
+CREATE TABLE
+IF NOT EXISTS
+workers (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ hostname TEXT,
+ address TEXT,
+ routes TEXT,
+ active BOOLEAN,
+ status VARCHAR(50),
+ last_status_update_at INTEGER,
+ running_build_ids VARCHAR(500),
+ last_build_started_at INTEGER,
+ last_build_finished_at INTEGER,
+ last_checked_in INTEGER,
+ build_limit INTEGER,
+ UNIQUE(hostname)
+);
+`
+)
+
+// CreateWorkerTable creates the workers table in the database.
+func (e *engine) CreateWorkerTable(driver string) error {
+ e.logger.Tracef("creating workers table in the database")
+
+ // handle the driver provided to create the table
+ switch driver {
+ case constants.DriverPostgres:
+ // create the workers table for Postgres
+ return e.client.Exec(CreatePostgresTable).Error
+ case constants.DriverSqlite:
+ fallthrough
+ default:
+ // create the workers table for Sqlite
+ return e.client.Exec(CreateSqliteTable).Error
+ }
+}
diff --git a/database/worker/table_test.go b/database/worker/table_test.go
new file mode 100644
index 000000000..681a267f2
--- /dev/null
+++ b/database/worker/table_test.go
@@ -0,0 +1,59 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestWorker_Engine_CreateWorkerTable(t *testing.T) {
+ // setup types
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _sqlite := testSqlite(t)
+ defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := test.database.CreateWorkerTable(test.name)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("CreateWorkerTable for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("CreateWorkerTable for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/worker/update.go b/database/worker/update.go
new file mode 100644
index 000000000..b0e475273
--- /dev/null
+++ b/database/worker/update.go
@@ -0,0 +1,38 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/database"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// UpdateWorker updates an existing worker in the database.
+func (e *engine) UpdateWorker(w *library.Worker) error {
+ e.logger.WithFields(logrus.Fields{
+ "worker": w.GetHostname(),
+ }).Tracef("updating worker %s in the database", w.GetHostname())
+
+ // cast the library type to database type
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#WorkerFromLibrary
+ worker := database.WorkerFromLibrary(w)
+
+ // validate the necessary fields are populated
+ //
+ // https://pkg.go.dev/github.com/go-vela/types/database#Worker.Validate
+ err := worker.Validate()
+ if err != nil {
+ return err
+ }
+
+ // send query to the database
+ return e.client.
+ Table(constants.TableWorker).
+ Save(worker).
+ Error
+}
diff --git a/database/worker/update_test.go b/database/worker/update_test.go
new file mode 100644
index 000000000..0beeafa47
--- /dev/null
+++ b/database/worker/update_test.go
@@ -0,0 +1,75 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+)
+
+func TestWorker_Engine_UpdateWorker(t *testing.T) {
+ // setup types
+ _worker := testWorker()
+ _worker.SetID(1)
+ _worker.SetHostname("worker_0")
+ _worker.SetAddress("localhost")
+ _worker.SetActive(true)
+
+ _postgres, _mock := testPostgres(t)
+ defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()
+
+ // ensure the mock expects the query
+ _mock.ExpectExec(`UPDATE "workers"
+SET "hostname"=$1,"address"=$2,"routes"=$3,"active"=$4,"status"=$5,"last_status_update_at"=$6,"running_build_ids"=$7,"last_build_started_at"=$8,"last_build_finished_at"=$9,"last_checked_in"=$10,"build_limit"=$11
+WHERE "id" = $12`).
+ WithArgs("worker_0", "localhost", nil, true, 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.CreateWorker(_worker)
+ if err != nil {
+ t.Errorf("unable to create test worker for sqlite: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ database *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ database: _postgres,
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ database: _sqlite,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err = test.database.UpdateWorker(_worker)
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("UpdateWorker for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("UpdateWorker for %s returned err: %v", test.name, err)
+ }
+ })
+ }
+}
diff --git a/database/worker/worker.go b/database/worker/worker.go
new file mode 100644
index 000000000..d18aa6408
--- /dev/null
+++ b/database/worker/worker.go
@@ -0,0 +1,80 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "fmt"
+
+ "github.com/go-vela/types/constants"
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/gorm"
+)
+
+type (
+ // config represents the settings required to create the engine that implements the WorkerInterface interface.
+ config struct {
+ // specifies to skip creating tables and indexes for the Worker engine
+ SkipCreation bool
+ }
+
+ // engine represents the worker functionality that implements the WorkerInterface interface.
+ engine struct {
+ // engine configuration settings used in worker functions
+ config *config
+
+ // gorm.io/gorm database client used in worker functions
+ //
+ // https://pkg.go.dev/gorm.io/gorm#DB
+ client *gorm.DB
+
+ // sirupsen/logrus logger used in worker functions
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus#Entry
+ logger *logrus.Entry
+ }
+)
+
+// New creates and returns a Vela service for integrating with workers in the database.
+//
+//nolint:revive // ignore returning unexported engine
+func New(opts ...EngineOpt) (*engine, error) {
+ // create new Worker engine
+ e := new(engine)
+
+ // create new fields
+ e.client = new(gorm.DB)
+ e.config = new(config)
+ e.logger = new(logrus.Entry)
+
+ // apply all provided configuration options
+ for _, opt := range opts {
+ err := opt(e)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // check if we should skip creating worker database objects
+ if e.config.SkipCreation {
+ e.logger.Warning("skipping creation of workers table and indexes in the database")
+
+ return e, nil
+ }
+
+ // create the workers table
+ err := e.CreateWorkerTable(e.client.Config.Dialector.Name())
+ if err != nil {
+ return nil, fmt.Errorf("unable to create %s table: %w", constants.TableWorker, err)
+ }
+
+ // create the indexes for the workers table
+ err = e.CreateWorkerIndexes()
+ if err != nil {
+ return nil, fmt.Errorf("unable to create indexes for %s table: %w", constants.TableWorker, err)
+ }
+
+ return e, nil
+}
diff --git a/database/worker/worker_test.go b/database/worker/worker_test.go
new file mode 100644
index 000000000..00096c2a9
--- /dev/null
+++ b/database/worker/worker_test.go
@@ -0,0 +1,186 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package worker
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+
+ "gorm.io/driver/postgres"
+ "gorm.io/driver/sqlite"
+ "gorm.io/gorm"
+)
+
+func TestWorker_New(t *testing.T) {
+ // setup types
+ logger := logrus.NewEntry(logrus.StandardLogger())
+
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+ defer _sql.Close()
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+
+ _config := &gorm.Config{SkipDefaultTransaction: true}
+
+ _postgres, err := gorm.Open(postgres.New(postgres.Config{Conn: _sql}), _config)
+ if err != nil {
+ t.Errorf("unable to create new postgres database: %v", err)
+ }
+
+ _sqlite, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), _config)
+ if err != nil {
+ t.Errorf("unable to create new sqlite database: %v", err)
+ }
+
+ defer func() { _sql, _ := _sqlite.DB(); _sql.Close() }()
+
+ // setup tests
+ tests := []struct {
+ failure bool
+ name string
+ client *gorm.DB
+ key string
+ logger *logrus.Entry
+ skipCreation bool
+ want *engine
+ }{
+ {
+ failure: false,
+ name: "postgres",
+ client: _postgres,
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ client: _postgres,
+ config: &config{SkipCreation: false},
+ logger: logger,
+ },
+ },
+ {
+ failure: false,
+ name: "sqlite3",
+ client: _sqlite,
+ logger: logger,
+ skipCreation: false,
+ want: &engine{
+ client: _sqlite,
+ config: &config{SkipCreation: false},
+ logger: logger,
+ },
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := New(
+ WithClient(test.client),
+ WithLogger(test.logger),
+ WithSkipCreation(test.skipCreation),
+ )
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("New for %s should have returned err", test.name)
+ }
+
+ return
+ }
+
+ if err != nil {
+ t.Errorf("New for %s returned err: %v", test.name, err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("New for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
+
+// testPostgres is a helper function to create a Postgres engine for testing.
+func testPostgres(t *testing.T) (*engine, sqlmock.Sqlmock) {
+ // create the new mock sql database
+ //
+ // https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock#New
+ _sql, _mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
+ if err != nil {
+ t.Errorf("unable to create new SQL mock: %v", err)
+ }
+
+ _mock.ExpectExec(CreatePostgresTable).WillReturnResult(sqlmock.NewResult(1, 1))
+ _mock.ExpectExec(CreateHostnameAddressIndex).WillReturnResult(sqlmock.NewResult(1, 1))
+
+ // create the new mock Postgres database client
+ //
+ // https://pkg.go.dev/gorm.io/gorm#Open
+ _postgres, err := gorm.Open(
+ postgres.New(postgres.Config{Conn: _sql}),
+ &gorm.Config{SkipDefaultTransaction: true},
+ )
+ if err != nil {
+ t.Errorf("unable to create new postgres database: %v", err)
+ }
+
+ _engine, err := New(
+ WithClient(_postgres),
+ WithLogger(logrus.NewEntry(logrus.StandardLogger())),
+ WithSkipCreation(false),
+ )
+ if err != nil {
+ t.Errorf("unable to create new postgres worker engine: %v", err)
+ }
+
+ return _engine, _mock
+}
+
+// testSqlite is a helper function to create a Sqlite engine for testing.
+func testSqlite(t *testing.T) *engine {
+ _sqlite, err := gorm.Open(
+ sqlite.Open("file::memory:?cache=shared"),
+ &gorm.Config{SkipDefaultTransaction: true},
+ )
+ if err != nil {
+ t.Errorf("unable to create new sqlite database: %v", err)
+ }
+
+ _engine, err := New(
+ WithClient(_sqlite),
+ WithLogger(logrus.NewEntry(logrus.StandardLogger())),
+ WithSkipCreation(false),
+ )
+ if err != nil {
+ t.Errorf("unable to create new sqlite worker engine: %v", err)
+ }
+
+ return _engine
+}
+
+// testWorker is a test helper function to create a library
+// Worker type with all fields set to their zero values.
+func testWorker() *library.Worker {
+ return &library.Worker{
+ ID: new(int64),
+ Hostname: new(string),
+ Address: new(string),
+ Routes: new([]string),
+ Active: new(bool),
+ Status: new(string),
+ LastStatusUpdateAt: new(int64),
+ RunningBuildIDs: new([]string),
+ LastBuildStartedAt: new(int64),
+ LastBuildFinishedAt: new(int64),
+ LastCheckedIn: new(int64),
+ BuildLimit: new(int64),
+ }
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index 7d34df72f..6a596e45e 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -12,7 +12,7 @@ services:
# managing resources in the database and publishing
# builds to the FIFO queue.
#
- # https://go-vela.github.io/docs/concepts/infrastructure/server/
+ # https://go-vela.github.io/docs/administration/server/
server:
build:
context: .
@@ -28,6 +28,7 @@ services:
DATABASE_ENCRYPTION_KEY: 'C639A572E14D5075C526FDDD43E4ECF6'
QUEUE_DRIVER: redis
QUEUE_ADDR: 'redis://redis:6379'
+ QUEUE_PRIVATE_KEY: 'tCIevHOBq6DdN5SSBtteXUusjjd0fOqzk2eyi0DMq04NewmShNKQeUbbp3vkvIckb4pCxc+vxUo+mYf/vzOaSg=='
SCM_DRIVER: github
SCM_CONTEXT: 'continuous-integration/vela'
SECRET_VAULT: 'true'
@@ -37,10 +38,13 @@ services:
VELA_WEBUI_ADDR: 'http://localhost:8888'
VELA_LOG_LEVEL: trace
VELA_SECRET: 'zB7mrKDTZqNeNTD8z47yG4DHywspAh'
- VELA_REFRESH_TOKEN_DURATION: 90m
- VELA_ACCESS_TOKEN_DURATION: 60m
+ VELA_SERVER_PRIVATE_KEY: 'F534FF2A080E45F38E05DC70752E6787'
+ VELA_USER_REFRESH_TOKEN_DURATION: 90m
+ VELA_USER_ACCESS_TOKEN_DURATION: 60m
VELA_DISABLE_WEBHOOK_VALIDATION: 'true'
VELA_ENABLE_SECURE_COOKIE: 'false'
+ VELA_REPO_ALLOWLIST: '*'
+ VELA_SCHEDULE_ALLOWLIST: '*'
env_file:
- .env
restart: always
@@ -56,23 +60,26 @@ services:
# This component is used for pulling builds from the FIFO
# queue and executing them based off their configuration.
#
- # https://go-vela.github.io/docs/concepts/infrastructure/worker/
+ # https://go-vela.github.io/docs/administration/worker/
worker:
container_name: worker
image: target/vela-worker:latest
networks:
- vela
environment:
+ EXECUTOR_DRIVER: linux
QUEUE_DRIVER: redis
QUEUE_ADDR: 'redis://redis:6379'
+ QUEUE_PUBLIC_KEY: 'DXsJkoTSkHlG26d75LyHJG+KQsXPr8VKPpmH/78zmko='
VELA_BUILD_LIMIT: 1
VELA_BUILD_TIMEOUT: 30m
VELA_LOG_LEVEL: trace
VELA_RUNTIME_DRIVER: docker
+ VELA_RUNTIME_PRIVILEGED_IMAGES: 'target/vela-docker'
VELA_SERVER_ADDR: 'http://server:8080'
VELA_SERVER_SECRET: 'zB7mrKDTZqNeNTD8z47yG4DHywspAh'
WORKER_ADDR: 'http://worker:8080'
- WORKER_CHECK_IN: 15m
+ WORKER_CHECK_IN: 5m
restart: always
ports:
- '8081:8080'
@@ -86,7 +93,7 @@ services:
# This component is used for providing a user-friendly
# interface for triggering actions in the Vela system.
#
- # https://go-vela.github.io/docs/concepts/infrastructure/ui/
+ # https://go-vela.github.io/docs/administration/ui/
ui:
container_name: ui
image: target/vela-ui:latest
@@ -109,7 +116,7 @@ services:
# https://redis.io/
redis:
container_name: redis
- image: redis:6-alpine
+ image: redis:7-alpine
networks:
- vela
ports:
@@ -122,7 +129,7 @@ services:
# https://www.postgresql.org/
postgres:
container_name: postgres
- image: postgres:14-alpine
+ image: postgres:15-alpine
networks:
- vela
environment:
@@ -138,7 +145,7 @@ services:
#
# https://www.vaultproject.io/
vault:
- image: vault:latest
+ image: hashicorp/vault:latest
container_name: vault
command: server -dev
networks:
@@ -152,4 +159,4 @@ services:
- IPC_LOCK
networks:
- vela:
+ vela:
\ No newline at end of file
diff --git a/go.mod b/go.mod
index cced08cac..788f5cdb0 100644
--- a/go.mod
+++ b/go.mod
@@ -1,138 +1,130 @@
module github.com/go-vela/server
-go 1.17
+go 1.19
require (
github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb
github.com/DATA-DOG/go-sqlmock v1.5.0
- github.com/Masterminds/semver/v3 v3.1.1
- github.com/Masterminds/sprig/v3 v3.2.2
- github.com/alicebob/miniredis/v2 v2.18.0
- github.com/aws/aws-sdk-go v1.42.27
+ github.com/Masterminds/semver/v3 v3.2.1
+ github.com/Masterminds/sprig/v3 v3.2.3
+ github.com/adhocore/gronx v1.6.4
+ github.com/alicebob/miniredis/v2 v2.30.4
+ github.com/aws/aws-sdk-go v1.44.309
github.com/buildkite/yaml v0.0.0-20181016232759-0caa5f0796e3
github.com/drone/envsubst v1.0.3
- github.com/gin-gonic/gin v1.7.7
- github.com/go-playground/assert/v2 v2.0.1
- github.com/go-redis/redis/v8 v8.11.4
- github.com/go-vela/types v0.12.0-rc1
- github.com/golang-jwt/jwt/v4 v4.2.0
- github.com/google/go-cmp v0.5.6
- github.com/google/go-github/v42 v42.0.0
+ github.com/gin-gonic/gin v1.9.1
+ github.com/go-playground/assert/v2 v2.2.0
+ github.com/go-vela/types v0.20.2-0.20230822144153-14b37585731d
+ github.com/golang-jwt/jwt/v5 v5.0.0
+ github.com/google/go-cmp v0.5.9
+ github.com/google/go-github/v53 v53.2.0
github.com/google/uuid v1.3.0
- github.com/goware/urlx v0.3.1
+ github.com/goware/urlx v0.3.2
github.com/hashicorp/go-cleanhttp v0.5.2
- github.com/hashicorp/go-retryablehttp v0.7.0
- github.com/hashicorp/vault/api v1.3.1
- github.com/joho/godotenv v1.4.0
+ github.com/hashicorp/go-multierror v1.1.1
+ github.com/hashicorp/go-retryablehttp v0.7.4
+ github.com/hashicorp/vault/api v1.9.2
+ github.com/joho/godotenv v1.5.1
github.com/pkg/errors v0.9.1
- github.com/prometheus/client_golang v1.12.0
- github.com/sirupsen/logrus v1.8.1
- github.com/spf13/afero v1.8.0
- github.com/urfave/cli/v2 v2.3.0
- go.starlark.net v0.0.0-20211203141949-70c0e40ae128
- golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
+ github.com/prometheus/client_golang v1.16.0
+ github.com/redis/go-redis/v9 v9.0.5
+ github.com/sirupsen/logrus v1.9.3
+ github.com/spf13/afero v1.9.5
+ github.com/urfave/cli/v2 v2.25.7
+ go.starlark.net v0.0.0-20230725161458-0d7263928a74
+ golang.org/x/crypto v0.11.0
+ golang.org/x/oauth2 v0.9.0
+ golang.org/x/sync v0.3.0
gopkg.in/square/go-jose.v2 v2.6.0
- gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
- gorm.io/driver/postgres v1.2.3
- gorm.io/driver/sqlite v1.2.6
- gorm.io/gorm v1.22.4
- k8s.io/apimachinery v0.23.1
+ gorm.io/driver/postgres v1.5.2
+ gorm.io/driver/sqlite v1.5.2
+ gorm.io/gorm v1.25.2
+ k8s.io/apimachinery v0.27.4
)
require (
github.com/Masterminds/goutils v1.1.1 // indirect
+ github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
- github.com/armon/go-metrics v0.3.9 // indirect
- github.com/armon/go-radix v1.0.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
+ github.com/bytedance/sonic v1.9.1 // indirect
github.com/cenkalti/backoff/v3 v3.0.0 // indirect
- github.com/cespare/xxhash/v2 v2.1.2 // indirect
- github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
+ github.com/cloudflare/circl v1.3.3 // indirect
+ github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fatih/color v1.10.0 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
- github.com/go-logr/logr v1.2.0 // indirect
- github.com/go-playground/locales v0.13.0 // indirect
- github.com/go-playground/universal-translator v0.17.0 // indirect
- github.com/go-playground/validator/v10 v10.4.1 // indirect
+ github.com/go-jose/go-jose/v3 v3.0.0 // indirect
+ github.com/go-logr/logr v1.2.3 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/go-playground/validator/v10 v10.14.0 // indirect
+ github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
- github.com/golang/protobuf v1.5.2 // indirect
- github.com/golang/snappy v0.0.4 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
- github.com/hashicorp/go-hclog v0.16.2 // indirect
- github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
- github.com/hashicorp/go-multierror v1.1.1 // indirect
- github.com/hashicorp/go-plugin v1.4.3 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
- github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect
- github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 // indirect
- github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 // indirect
+ github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect
+ github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
- github.com/hashicorp/go-uuid v1.0.2 // indirect
- github.com/hashicorp/go-version v1.2.0 // indirect
- github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
- github.com/hashicorp/vault/sdk v0.3.0 // indirect
- github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect
- github.com/huandu/xstrings v1.3.2 // indirect
+ github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.11 // indirect
- github.com/jackc/chunkreader/v2 v2.0.1 // indirect
- github.com/jackc/pgconn v1.10.1 // indirect
- github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
- github.com/jackc/pgproto3/v2 v2.2.0 // indirect
- github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
- github.com/jackc/pgtype v1.9.0 // indirect
- github.com/jackc/pgx/v4 v4.14.0 // indirect
+ github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
+ github.com/jackc/pgx/v5 v5.3.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
- github.com/jinzhu/now v1.1.3 // indirect
+ github.com/jinzhu/now v1.1.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
- github.com/kr/pretty v0.3.0 // indirect
- github.com/leodido/go-urn v1.2.0 // indirect
- github.com/lib/pq v1.10.4 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.4 // indirect
+ github.com/kr/text v0.2.0 // indirect
+ github.com/leodido/go-urn v1.2.4 // indirect
+ github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
- github.com/mattn/go-isatty v0.0.12 // indirect
- github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
- github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
- github.com/microcosm-cc/bluemonday v1.0.17 // indirect
+ github.com/mattn/go-isatty v0.0.19 // indirect
+ github.com/mattn/go-sqlite3 v1.14.17 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/microcosm-cc/bluemonday v1.0.25 // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
- github.com/mitchellh/go-testing-interface v1.0.0 // indirect
- github.com/mitchellh/mapstructure v1.4.2 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
- github.com/oklog/run v1.0.0 // indirect
- github.com/pierrec/lz4 v2.5.2+incompatible // indirect
- github.com/prometheus/client_model v0.2.0 // indirect
- github.com/prometheus/common v0.32.1 // indirect
- github.com/prometheus/procfs v0.7.3 // indirect
- github.com/russross/blackfriday/v2 v2.0.1 // indirect
+ github.com/pelletier/go-toml/v2 v2.0.8 // indirect
+ github.com/prometheus/client_model v0.3.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/rogpeppe/go-internal v1.9.0 // indirect
+ github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
- github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
- github.com/ugorji/go/codec v1.1.11 // indirect
- github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da // indirect
- go.uber.org/atomic v1.9.0 // indirect
- golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
- golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
- golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
- golang.org/x/text v0.3.7 // indirect
- golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/ugorji/go/codec v1.2.11 // indirect
+ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
+ github.com/yuin/gopher-lua v1.1.0 // indirect
+ golang.org/x/arch v0.3.0 // indirect
+ golang.org/x/net v0.12.0 // indirect
+ golang.org/x/sys v0.10.0 // indirect
+ golang.org/x/text v0.11.0 // indirect
+ golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
google.golang.org/appengine v1.6.7 // indirect
- google.golang.org/genproto v0.0.0-20210226172003-ab064af71705 // indirect
- google.golang.org/grpc v1.41.0 // indirect
- google.golang.org/protobuf v1.27.1 // indirect
+ google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
- k8s.io/klog/v2 v2.30.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+ k8s.io/klog/v2 v2.90.1 // indirect
+ k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect
)
diff --git a/go.sum b/go.sum
index 771fa64d8..f109e9873 100644
--- a/go.sum
+++ b/go.sum
@@ -42,155 +42,115 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
-github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/FZambia/sentinel v1.0.0 h1:KJ0ryjKTZk5WMp0dXvSdNqp3lFaW1fNFuEYfrkLOYIc=
github.com/FZambia/sentinel v1.0.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
-github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
-github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
-github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
-github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
-github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
+github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
+github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
+github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
+github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
+github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
+github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA=
+github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/adhocore/gronx v1.6.4 h1:Bx5cNRVQsGquOOUJL3+2M5vlz1KCCMHrCECwb5UghNU=
+github.com/adhocore/gronx v1.6.4/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.11.1/go.mod h1:UA48pmi7aSazcGAvcdKcBB49z521IC9VjTTRz2nIaJE=
-github.com/alicebob/miniredis/v2 v2.18.0 h1:EPUGD69ou4Uw4c81t9NLh0+dSou46k4tFEvf498FJ0g=
-github.com/alicebob/miniredis/v2 v2.18.0/go.mod h1:gquAfGbzn92jvtrSC69+6zZnwSODVXVpYDRaGhWaL6I=
-github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
-github.com/armon/go-metrics v0.3.9 h1:O2sNqxBdvq8Eq5xmzljcYzAORli6RWCvEym4cJf9m18=
-github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
+github.com/alicebob/miniredis/v2 v2.30.4 h1:8S4/o1/KoUArAGbGwPxcwf0krlzceva2XVOSchFS7Eo=
+github.com/alicebob/miniredis/v2 v2.30.4/go.mod h1:b25qWj4fCEsBeAAR2mlb0ufImGC6uH3VlUfb/HS5zKg=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
-github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
-github.com/aws/aws-sdk-go v1.42.27 h1:kxsBXQg3ee6LLbqjp5/oUeDgG7TENFrWYDmEVnd7spU=
-github.com/aws/aws-sdk-go v1.42.27/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc=
+github.com/aws/aws-sdk-go v1.44.309 h1:IPJOFBzXekakxmEpDwd4RTKmmBR6LIAiXgNsM51bWbU=
+github.com/aws/aws-sdk-go v1.44.309/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
-github.com/bradleyfalzon/ghinstallation/v2 v2.0.3/go.mod h1:tlgi+JWCXnKFx/Y4WtnDbZEINo31N5bcvnCoqieefmk=
+github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
+github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
github.com/buildkite/yaml v0.0.0-20181016232759-0caa5f0796e3 h1:q+sMKdA6L8LyGVudTkpGoC73h6ak2iWSPFiFo/pFOU8=
github.com/buildkite/yaml v0.0.0-20181016232759-0caa5f0796e3/go.mod h1:5hCug3EZaHXU3FdCA3gJm0YTNi+V+ooA2qNTiVpky4A=
+github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
+github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
+github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
+github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c=
github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
-github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
-github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
+github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
+github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
-github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
-github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
+github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
-github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
-github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
-github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
-github.com/evanphx/json-patch/v5 v5.5.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
-github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
-github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
-github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
-github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk=
-github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
-github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
+github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
+github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
-github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
-github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
-github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
+github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
+github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
-github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
-github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
-github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
-github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE=
+github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
+github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
-github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
-github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
-github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
-github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
-github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
-github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
-github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
-github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
-github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
-github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
-github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
-github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
+github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
+github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
-github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
-github.com/go-vela/types v0.12.0-rc1 h1:/qXnZ10AAlJ7l4Rr/FkAfhGFz8G9ww1VkedAXJatHu8=
-github.com/go-vela/types v0.12.0-rc1/go.mod h1:nMZJ/0tb0HO8/AVaJXHuR5slG9UPuP9or+CnkuyFcL4=
-github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
-github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/go-vela/types v0.20.2-0.20230822144153-14b37585731d h1:ag6trc3Ev+7hzifeWy0M9rHHjrO9nFCYgW8dlKdZ4j4=
+github.com/go-vela/types v0.20.2-0.20230822144153-14b37585731d/go.mod h1:AXO4oQSygOBQ02fPapsKjQHkx2aQO3zTu7clpvVbXBY=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
-github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
-github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
+github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
+github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -217,10 +177,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
-github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
-github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
@@ -236,15 +194,13 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
-github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-github/v39 v39.0.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE=
-github.com/google/go-github/v42 v42.0.0 h1:YNT0FwjPrEysRkLIiKuEfSvBPCGKphW5aS5PxwaoLec=
-github.com/google/go-github/v42 v42.0.0/go.mod h1:jgg/jvyI0YlDOM1/ps6XYh04HNQ3vKf0CVko62/EhRg=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-github/v53 v53.2.0 h1:wvz3FyF53v4BK+AsnvCmeNhf8AkTaeh2SoYu/XUvTtI=
+github.com/google/go-github/v53 v53.2.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
@@ -267,383 +223,202 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
-github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
-github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
-github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/goware/urlx v0.3.1 h1:BbvKl8oiXtJAzOzMqAQ0GfIhf96fKeNEZfm9ocNSUBI=
-github.com/goware/urlx v0.3.1/go.mod h1:h8uwbJy68o+tQXCGZNa9D73WN8n0r9OBae5bUnLcgjw=
-github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/goware/urlx v0.3.2 h1:gdoo4kBHlkqZNaf6XlQ12LGtQOmpKJrR04Rc3RnpJEo=
+github.com/goware/urlx v0.3.2/go.mod h1:h8uwbJy68o+tQXCGZNa9D73WN8n0r9OBae5bUnLcgjw=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
-github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
-github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs=
-github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
-github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
-github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
-github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
-github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
-github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM=
-github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
-github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
-github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
-github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4=
-github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
+github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA=
+github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
-github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
-github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 h1:cCRo8gK7oq6A2L6LICkUZ+/a5rLiRXFMf1Qd4xSwxTc=
-github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I=
-github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 h1:78ki3QBevHwYrVxnyVeaEz+7WtifHhauYF23es/0KlI=
-github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
-github.com/hashicorp/go-secure-stdlib/password v0.1.1/go.mod h1:9hH302QllNwu1o2TGYtSk8I8kTAN0ca1EHpwhm5Mmzo=
-github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 h1:nd0HIW15E6FG1MsnArYaHfuw9C2zgzM8LxkG5Ty/788=
+github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ=
+github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
-github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs=
+github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
+github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
-github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
-github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
-github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
-github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hashicorp/vault/api v1.3.1 h1:pkDkcgTh47PRjY1NEFeofqR4W/HkNUi9qIakESO2aRM=
-github.com/hashicorp/vault/api v1.3.1/go.mod h1:QeJoWxMFt+MsuWcYhmwRLwKEXrjwAFFywzhptMsTIUw=
-github.com/hashicorp/vault/sdk v0.3.0 h1:kR3dpxNkhh/wr6ycaJYqp6AFT/i2xaftbfnwZduTKEY=
-github.com/hashicorp/vault/sdk v0.3.0/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0=
-github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
-github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
-github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
-github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
-github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/hashicorp/vault/api v1.9.2 h1:YjkZLJ7K3inKgMZ0wzCU9OHqc+UqMQyXsPXnf3Cl2as=
+github.com/hashicorp/vault/api v1.9.2/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8=
+github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
+github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
-github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
-github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
-github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
-github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
-github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
-github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
-github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
-github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
-github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
-github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
-github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
-github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8=
-github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
-github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
-github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
-github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
-github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
-github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
-github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
-github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
-github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
-github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
-github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
-github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
-github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
-github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns=
-github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
-github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
-github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
-github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
-github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
-github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
-github.com/jackc/pgtype v1.9.0 h1:/SH1RxEtltvJgsDqp3TbiTFApD3mey3iygpuEGeuBXk=
-github.com/jackc/pgtype v1.9.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
-github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
-github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
-github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
-github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
-github.com/jackc/pgx/v4 v4.14.0 h1:TgdrmgnM7VY72EuSQzBbBd4JA1RLqJolrw9nQVZABVc=
-github.com/jackc/pgx/v4 v4.14.0/go.mod h1:jT3ibf/A0ZVCp89rtCIN0zCJxcE74ypROmHEZYsG/j8=
-github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
-github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
-github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
+github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
+github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
+github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
-github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
-github.com/jinzhu/now v1.1.3 h1:PlHq1bSCSZL9K0wUhbm2pGLoTWs2GwVhsP6emvGV/ZI=
-github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
-github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
-github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
-github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
-github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
+github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
-github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
-github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
-github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
-github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
+github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
+github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
-github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
-github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
-github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
-github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
-github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
-github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
-github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/microcosm-cc/bluemonday v1.0.17 h1:Z1a//hgsQ4yjC+8zEkV8IWySkXnsxmdSY642CTFQb5Y=
-github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
+github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
+github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg=
+github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
-github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
-github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
-github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
-github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
-github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
-github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
-github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
-github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
-github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
-github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
-github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
-github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
-github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
-github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
-github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
-github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
-github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
-github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
-github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
+github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
-github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg=
-github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
-github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
-github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
-github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
-github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
-github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
-github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
+github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o=
+github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
-github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
-github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
-github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
-github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
-github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
-github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
-github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
-github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
-github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
-github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
-github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
-github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
-github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
-github.com/spf13/afero v1.8.0 h1:5MmtuhAgYeU6qpa7w7bP0dv6MBYuup0vekhSpSkoq60=
-github.com/spf13/afero v1.8.0/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
+github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
-github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
-github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
-github.com/ugorji/go v1.1.11 h1:O5AKWOf+CnfWi6L1WtdBtZpA+YNjoQd2YfbtkowsMrs=
-github.com/ugorji/go v1.1.11/go.mod h1:kbRrdMyHY64ADdazOwkrQP9btxt35Z26OJueD3Tq0/4=
-github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
-github.com/ugorji/go/codec v1.1.11 h1:GaQDxjNe1J3vCZvlVaDjUIHIbFuUByFXY7rMqnhB5ck=
-github.com/ugorji/go/codec v1.1.11/go.mod h1:svMFxxx5FVQJPnJ9vbpAgscNufuiXDyldvzApI86qQo=
-github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
-github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
+github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
+github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
+github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v0.0.0-20191213034115-f46add6fdb5c/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
-github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5ukMK0AMWEhFaL/lrEOaephfuoiARg=
-github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
-github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
+github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE=
+github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
-go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
-go.starlark.net v0.0.0-20211203141949-70c0e40ae128 h1:bxH+EXOo87zEOwKDdZ8Tevgi6irRbqheRm/fr293c58=
-go.starlark.net v0.0.0-20211203141949-70c0e40ae128/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0=
-go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
-go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
-go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
-go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
-go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
-go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
-go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
-go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+go.starlark.net v0.0.0-20230725161458-0d7263928a74 h1:EL8MuNFlzO8vvpHgZxDGPaehP0ozoJ1j1zA768zKXUQ=
+go.starlark.net v0.0.0-20230725161458-0d7263928a74/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
+golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
-golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
+golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
+golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -677,12 +452,9 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -690,12 +462,9 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -706,7 +475,6 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
@@ -716,13 +484,12 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
-golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
-golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
+golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
+golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -732,9 +499,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
-golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs=
+golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -745,34 +511,23 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -785,48 +540,49 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
-golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
+golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
+golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
-golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
+golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -834,18 +590,14 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -853,7 +605,6 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@@ -866,7 +617,6 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -878,18 +628,14 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
-golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@@ -918,7 +664,6 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -942,7 +687,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
@@ -950,15 +694,12 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210226172003-ab064af71705 h1:PYBmACG+YEv8uQPW0r1kJj8tR+gkF0UWq7iFdUezwEw=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -972,13 +713,9 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E=
-google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -991,45 +728,28 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
-google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
+google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
-gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
-gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs=
-gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To=
-gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs=
-gorm.io/driver/sqlite v1.2.6 h1:SStaH/b+280M7C8vXeZLz/zo9cLQmIGwwj3cSj7p6l4=
-gorm.io/driver/sqlite v1.2.6/go.mod h1:gyoX0vHiiwi0g49tv+x2E7l8ksauLK0U/gShcdUsjWY=
-gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
-gorm.io/gorm v1.22.4 h1:8aPcyEJhY0MAt8aY6Dc524Pn+pO29K+ydu+e/cXSpQM=
-gorm.io/gorm v1.22.4/go.mod h1:1aeVC+pe9ZmvKZban/gW4QPra7PRoTEssyc922qCAkk=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0=
+gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8=
+gorm.io/driver/sqlite v1.5.2 h1:TpQ+/dqCY4uCigCFyrfnrJnrW9zjpelWVoEVNy5qJkc=
+gorm.io/driver/sqlite v1.5.2/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
+gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
+gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1037,21 +757,14 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-k8s.io/apimachinery v0.23.1 h1:sfBjlDFwj2onG0Ijx5C+SrAoeUscPrmghm7wHP+uXlo=
-k8s.io/apimachinery v0.23.1/go.mod h1:SADt2Kl8/sttJ62RRsi9MIV4o8f5S3coArm0Iu3fBno=
-k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
-k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
-k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
-k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw=
-k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
-k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk=
-k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
-k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
+k8s.io/apimachinery v0.27.4 h1:CdxflD4AF61yewuid0fLl6bM4a3q04jWel0IlP+aYjs=
+k8s.io/apimachinery v0.27.4/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E=
+k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
+k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
+k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY=
+k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs=
-sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
-sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
-sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
-sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
+sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
diff --git a/internal/token/compose.go b/internal/token/compose.go
new file mode 100644
index 000000000..61bea7a32
--- /dev/null
+++ b/internal/token/compose.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 token
+
+import (
+ "net/http"
+ "net/url"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/types"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+)
+
+// Compose generates a refresh and access token pair unique
+// to the provided user and sets a secure cookie.
+// It uses the user's hash to sign the token. to
+// guarantee the signature is unique per token. The refresh
+// token is returned to store with the user
+// in the database.
+func (tm *Manager) Compose(c *gin.Context, u *library.User) (string, string, error) {
+ // grab the metadata from the context to pull in provided
+ // cookie duration information
+ m := c.MustGet("metadata").(*types.Metadata)
+
+ // mint token options for refresh token
+ rmto := MintTokenOpts{
+ User: u,
+ TokenType: constants.UserRefreshTokenType,
+ TokenDuration: tm.UserRefreshTokenDuration,
+ }
+
+ // create a refresh token with the provided options
+ refreshToken, err := tm.MintToken(&rmto)
+ if err != nil {
+ return "", "", err
+ }
+
+ // mint token options for access token
+ amto := MintTokenOpts{
+ User: u,
+ TokenType: constants.UserAccessTokenType,
+ TokenDuration: tm.UserAccessTokenDuration,
+ }
+
+ // create an access token with the provided options
+ accessToken, err := tm.MintToken(&amto)
+ if err != nil {
+ return "", "", err
+ }
+
+ // parse the address for the backend server
+ // so we can set it for the cookie domain
+ addr, err := url.Parse(m.Vela.Address)
+ if err != nil {
+ return "", "", err
+ }
+
+ refreshExpiry := int(tm.UserRefreshTokenDuration.Seconds())
+
+ // set the SameSite value for the cookie
+ // https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#samesite-attribute
+ // We set to Lax because we will have links from source provider web UI.
+ // Setting this to Strict would force a login when navigating via source provider web UI links.
+ c.SetSameSite(http.SameSiteLaxMode)
+ // set the cookie with the refresh token as a HttpOnly, Secure cookie
+ // https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#httponly-attribute
+ // https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#secure-attribute
+ c.SetCookie(constants.RefreshTokenName, refreshToken, refreshExpiry, "/", addr.Hostname(), c.Value("securecookie").(bool), true)
+
+ // return the refresh and access tokens
+ return refreshToken, accessToken, nil
+}
diff --git a/internal/token/compose_test.go b/internal/token/compose_test.go
new file mode 100644
index 000000000..ff01b543d
--- /dev/null
+++ b/internal/token/compose_test.go
@@ -0,0 +1,80 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package token
+
+import (
+ "net/http/httptest"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/types"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+
+ jwt "github.com/golang-jwt/jwt/v5"
+)
+
+func TestToken_Compose(t *testing.T) {
+ // setup types
+ u := new(library.User)
+ u.SetID(1)
+ u.SetName("foo")
+ u.SetToken("bar")
+ u.SetHash("baz")
+
+ tm := &Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ d := time.Minute * 5
+ now := time.Now()
+ exp := now.Add(d)
+
+ claims := &Claims{
+ IsActive: u.GetActive(),
+ IsAdmin: u.GetAdmin(),
+ TokenType: constants.UserAccessTokenType,
+ RegisteredClaims: jwt.RegisteredClaims{
+ Subject: u.GetName(),
+ IssuedAt: jwt.NewNumericDate(now),
+ ExpiresAt: jwt.NewNumericDate(exp),
+ },
+ }
+
+ tkn := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+
+ want, err := tkn.SignedString([]byte(tm.PrivateKey))
+ if err != nil {
+ t.Errorf("Unable to create test token: %v", err)
+ }
+
+ m := &types.Metadata{
+ Vela: &types.Vela{
+ AccessTokenDuration: d,
+ },
+ }
+
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, _ := gin.CreateTestContext(resp)
+ context.Set("metadata", m)
+ context.Set("securecookie", false)
+
+ // run test
+ _, got, err := tm.Compose(context, u)
+ if err != nil {
+ t.Errorf("Compose returned err: %v", err)
+ }
+
+ if !strings.EqualFold(got, want) {
+ t.Errorf("Compose is %v, want %v", got, want)
+ }
+}
diff --git a/internal/token/manager.go b/internal/token/manager.go
new file mode 100644
index 000000000..22daf3636
--- /dev/null
+++ b/internal/token/manager.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 token
+
+import (
+ "time"
+
+ "github.com/golang-jwt/jwt/v5"
+)
+
+type Manager struct {
+ // PrivateKey key used to sign tokens
+ PrivateKey string
+
+ // SignMethod method to sign tokens
+ SignMethod jwt.SigningMethod
+
+ // UserAccessTokenDuration specifies the token duration to use for users
+ UserAccessTokenDuration time.Duration
+
+ // UserRefreshTokenDuration specifies the token duration for user refresh
+ UserRefreshTokenDuration time.Duration
+
+ // BuildTokenBufferDuration specifies the additional token duration of build tokens beyond repo timeout
+ BuildTokenBufferDuration time.Duration
+
+ // WorkerAuthTokenDuration specifies the token duration for worker auth (check in)
+ WorkerAuthTokenDuration time.Duration
+
+ // WorkerRegisterTokenDuration specifies the token duration for worker register
+ WorkerRegisterTokenDuration time.Duration
+}
diff --git a/internal/token/mint.go b/internal/token/mint.go
new file mode 100644
index 000000000..7d2ff8f64
--- /dev/null
+++ b/internal/token/mint.go
@@ -0,0 +1,96 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package token
+
+import (
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/golang-jwt/jwt/v5"
+)
+
+// Claims struct is an extension of the JWT standard claims. It
+// includes information about the user.
+type Claims struct {
+ BuildID int64 `json:"build_id"`
+ IsActive bool `json:"is_active"`
+ IsAdmin bool `json:"is_admin"`
+ Repo string `json:"repo"`
+ TokenType string `json:"token_type"`
+ jwt.RegisteredClaims
+}
+
+// MintTokenOpts is a type to inform the token minter how to construct
+// the token.
+type MintTokenOpts struct {
+ BuildID int64
+ Hostname string
+ Repo string
+ TokenDuration time.Duration
+ TokenType string
+ User *library.User
+}
+
+// MintToken mints a Vela JWT Token given a set of options.
+func (tm *Manager) MintToken(mto *MintTokenOpts) (string, error) {
+ // initialize claims struct
+ var claims = new(Claims)
+
+ // apply claims based on token type
+ switch mto.TokenType {
+ case constants.UserAccessTokenType, constants.UserRefreshTokenType:
+ if mto.User == nil {
+ return "", fmt.Errorf("no user provided for user access token")
+ }
+
+ claims.IsActive = mto.User.GetActive()
+ claims.IsAdmin = mto.User.GetAdmin()
+ claims.Subject = mto.User.GetName()
+
+ case constants.WorkerBuildTokenType:
+ if mto.BuildID == 0 {
+ return "", errors.New("missing build id for build token")
+ }
+
+ if len(mto.Repo) == 0 {
+ return "", errors.New("missing repo for build token")
+ }
+
+ if len(mto.Hostname) == 0 {
+ return "", errors.New("missing host name for build token")
+ }
+
+ claims.BuildID = mto.BuildID
+ claims.Repo = mto.Repo
+ claims.Subject = mto.Hostname
+
+ case constants.WorkerAuthTokenType, constants.WorkerRegisterTokenType:
+ if len(mto.Hostname) == 0 {
+ return "", fmt.Errorf("missing host name for %s token", mto.TokenType)
+ }
+
+ claims.Subject = mto.Hostname
+
+ default:
+ return "", errors.New("invalid token type")
+ }
+
+ claims.IssuedAt = jwt.NewNumericDate(time.Now())
+ claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(mto.TokenDuration))
+ claims.TokenType = mto.TokenType
+
+ tk := jwt.NewWithClaims(tm.SignMethod, claims)
+
+ //sign token with configured private signing key
+ token, err := tk.SignedString([]byte(tm.PrivateKey))
+ if err != nil {
+ return "", fmt.Errorf("unable to sign token: %w", err)
+ }
+
+ return token, nil
+}
diff --git a/internal/token/parse.go b/internal/token/parse.go
new file mode 100644
index 000000000..ad5423ee6
--- /dev/null
+++ b/internal/token/parse.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 token
+
+import (
+ "errors"
+
+ "github.com/golang-jwt/jwt/v5"
+)
+
+// ParseToken scans the signed JWT token as a string and extracts
+// the user login from the claims to be looked up in the database.
+// This function will return an error for a few different reasons:
+//
+// * the token signature doesn't match what is expected
+// * the token signing method doesn't match what is expected
+// * the token is invalid (potentially expired or improper).
+func (tm *Manager) ParseToken(token string) (*Claims, error) {
+ var claims = new(Claims)
+
+ // create a new JWT parser
+ p := jwt.NewParser(jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Name}))
+
+ // parse and validate given token
+ tkn, err := p.ParseWithClaims(token, claims, func(t *jwt.Token) (interface{}, error) {
+ var err error
+
+ // extract the claims from the token
+ claims = t.Claims.(*Claims)
+ name := claims.Subject
+
+ // check if subject has a value in claims;
+ // we can save a db lookup attempt
+ if len(name) == 0 {
+ return nil, errors.New("no subject defined")
+ }
+
+ // ParseWithClaims will skip expiration check
+ // if expiration has default value;
+ // forcing a check and exiting if not set
+ if claims.ExpiresAt == nil {
+ return nil, errors.New("token has no expiration")
+ }
+
+ return []byte(tm.PrivateKey), err
+ })
+
+ if err != nil {
+ return nil, errors.New("failed parsing: " + err.Error())
+ }
+
+ if !tkn.Valid {
+ return nil, errors.New("invalid token")
+ }
+
+ return claims, nil
+}
diff --git a/internal/token/parse_test.go b/internal/token/parse_test.go
new file mode 100644
index 000000000..c7dc2422f
--- /dev/null
+++ b/internal/token/parse_test.go
@@ -0,0 +1,299 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package token
+
+import (
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+
+ jwt "github.com/golang-jwt/jwt/v5"
+)
+
+func TestTokenManager_ParseToken(t *testing.T) {
+ // setup types
+ u := new(library.User)
+ u.SetID(1)
+ u.SetName("foo")
+ u.SetToken("bar")
+ u.SetHash("baz")
+
+ tm := &Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ now := time.Now()
+
+ tests := []struct {
+ TokenType string
+ Mto *MintTokenOpts
+ Want *Claims
+ }{
+ {
+ TokenType: constants.UserAccessTokenType,
+ Mto: &MintTokenOpts{
+ User: u,
+ TokenType: constants.UserAccessTokenType,
+ TokenDuration: tm.UserAccessTokenDuration,
+ },
+ Want: &Claims{
+ IsActive: u.GetActive(),
+ IsAdmin: u.GetAdmin(),
+ TokenType: constants.UserAccessTokenType,
+ RegisteredClaims: jwt.RegisteredClaims{
+ Subject: u.GetName(),
+ IssuedAt: jwt.NewNumericDate(now),
+ ExpiresAt: jwt.NewNumericDate(now.Add(time.Minute * 5)),
+ },
+ },
+ },
+ {
+ TokenType: constants.UserRefreshTokenType,
+ Mto: &MintTokenOpts{
+ User: u,
+ TokenType: constants.UserRefreshTokenType,
+ TokenDuration: tm.UserRefreshTokenDuration,
+ },
+ Want: &Claims{
+ IsActive: u.GetActive(),
+ IsAdmin: u.GetAdmin(),
+ TokenType: constants.UserRefreshTokenType,
+ RegisteredClaims: jwt.RegisteredClaims{
+ Subject: u.GetName(),
+ IssuedAt: jwt.NewNumericDate(now),
+ ExpiresAt: jwt.NewNumericDate(now.Add(time.Minute * 30)),
+ },
+ },
+ },
+ {
+ TokenType: constants.WorkerBuildTokenType,
+ Mto: &MintTokenOpts{
+ BuildID: 1,
+ Repo: "foo/bar",
+ Hostname: "worker",
+ TokenType: constants.WorkerBuildTokenType,
+ TokenDuration: time.Minute * 90,
+ },
+ Want: &Claims{
+ BuildID: 1,
+ Repo: "foo/bar",
+ TokenType: constants.WorkerBuildTokenType,
+ RegisteredClaims: jwt.RegisteredClaims{
+ Subject: "worker",
+ IssuedAt: jwt.NewNumericDate(now),
+ ExpiresAt: jwt.NewNumericDate(now.Add(time.Minute * 90)),
+ },
+ },
+ },
+ }
+
+ gin.SetMode(gin.TestMode)
+
+ for _, tt := range tests {
+ t.Run(tt.TokenType, func(t *testing.T) {
+ tkn, err := tm.MintToken(tt.Mto)
+ if err != nil {
+ t.Errorf("Unable to create token: %v", err)
+ }
+ // run test
+ got, err := tm.ParseToken(tkn)
+ if err != nil {
+ t.Errorf("Parse returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(got, tt.Want) {
+ t.Errorf("Parse is %v, want %v", got, tt.Want)
+ }
+ })
+ }
+}
+
+func TestTokenManager_ParseToken_Error_NoParse(t *testing.T) {
+ // setup types
+ u := new(library.User)
+ u.SetID(1)
+ u.SetName("foo")
+ u.SetToken("bar")
+ u.SetHash("baz")
+
+ tm := &Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ // run test
+ got, err := tm.ParseToken("!@#$%^&*()")
+ if err == nil {
+ t.Errorf("Parse should have returned err")
+ }
+
+ if got != nil {
+ t.Errorf("Parse is %v, want nil", got)
+ }
+}
+
+func TestTokenManager_ParseToken_Expired(t *testing.T) {
+ // setup types
+ u := new(library.User)
+ u.SetID(1)
+ u.SetName("foo")
+ u.SetToken("bar")
+ u.SetHash("baz")
+
+ tm := &Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ mto := &MintTokenOpts{
+ User: u,
+ TokenType: constants.UserAccessTokenType,
+ TokenDuration: time.Minute * -1,
+ }
+
+ tkn, err := tm.MintToken(mto)
+ if err != nil {
+ t.Errorf("Unable to create token: %v", err)
+ }
+
+ // run test
+ _, err = tm.ParseToken(tkn)
+ if err == nil {
+ t.Errorf("Parse should return error due to expiration")
+ }
+}
+
+func TestTokenManager_ParseToken_NoSubject(t *testing.T) {
+ // setup types
+ u := new(library.User)
+ u.SetID(1)
+ u.SetName("foo")
+ u.SetToken("bar")
+ u.SetHash("baz")
+
+ tm := &Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ claims := &Claims{
+ IsActive: u.GetActive(),
+ IsAdmin: u.GetAdmin(),
+ TokenType: constants.UserRefreshTokenType,
+ RegisteredClaims: jwt.RegisteredClaims{
+ ExpiresAt: jwt.NewNumericDate(time.Now()),
+ },
+ }
+ tkn := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+
+ token, err := tkn.SignedString([]byte(tm.PrivateKey))
+ if err != nil {
+ t.Errorf("Unable to create test token: %v", err)
+ }
+
+ // run test
+ got, err := tm.ParseToken(token)
+ if err == nil {
+ t.Errorf("Parse should have returned err")
+ }
+
+ if got != nil {
+ t.Errorf("Parse is %v, want nil", got)
+ }
+}
+
+func TestTokenManager_ParseToken_Error_InvalidSignature(t *testing.T) {
+ // setup types
+ u := new(library.User)
+ u.SetID(1)
+ u.SetName("foo")
+ u.SetToken("bar")
+ u.SetHash("baz")
+
+ tm := &Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ claims := &Claims{
+ IsActive: u.GetActive(),
+ IsAdmin: u.GetAdmin(),
+ TokenType: constants.UserAccessTokenType,
+ RegisteredClaims: jwt.RegisteredClaims{
+ Subject: u.GetName(),
+ ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 1)),
+ },
+ }
+ tkn := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
+
+ token, err := tkn.SignedString([]byte(tm.PrivateKey))
+ if err != nil {
+ t.Errorf("Unable to create test token: %v", err)
+ }
+
+ // run test
+ got, err := tm.ParseToken(token)
+ if err == nil {
+ t.Errorf("Parse should have returned err")
+ }
+
+ if got != nil {
+ t.Errorf("Parse is %v, want nil", got)
+ }
+}
+
+func TestToken_Parse_AccessToken_NoExpiration(t *testing.T) {
+ // setup types
+ u := new(library.User)
+ u.SetID(1)
+ u.SetName("foo")
+ u.SetToken("bar")
+ u.SetHash("baz")
+
+ tm := &Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ claims := &Claims{
+ TokenType: constants.UserAccessTokenType,
+ RegisteredClaims: jwt.RegisteredClaims{
+ Subject: "user",
+ },
+ }
+ tkn := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+
+ token, err := tkn.SignedString([]byte(u.GetHash()))
+ if err != nil {
+ t.Errorf("Unable to create test token: %v", err)
+ }
+
+ // run test
+ got, err := tm.ParseToken(token)
+ if err == nil {
+ t.Errorf("Parse should have returned err")
+ }
+
+ if got != nil {
+ t.Errorf("Parse is %v, want nil", got)
+ }
+}
diff --git a/internal/token/refresh.go b/internal/token/refresh.go
new file mode 100644
index 000000000..8cb69f374
--- /dev/null
+++ b/internal/token/refresh.go
@@ -0,0 +1,43 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package token
+
+import (
+ "fmt"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/types/constants"
+)
+
+// Refresh returns a new access token, if the provided refreshToken is valid.
+func (tm *Manager) Refresh(c *gin.Context, refreshToken string) (string, error) {
+ // retrieve claims from token
+ claims, err := tm.ParseToken(refreshToken)
+ if err != nil {
+ return "", err
+ }
+
+ // look up user in database given claims subject
+ u, err := database.FromContext(c).GetUserForName(claims.Subject)
+ if err != nil {
+ return "", fmt.Errorf("unable to retrieve user %s from database from claims subject: %w", claims.Subject, err)
+ }
+
+ // options for user access token minting
+ amto := &MintTokenOpts{
+ User: u,
+ TokenType: constants.UserAccessTokenType,
+ TokenDuration: tm.UserAccessTokenDuration,
+ }
+
+ // create a new access token
+ at, err := tm.MintToken(amto)
+ if err != nil {
+ return "", err
+ }
+
+ return at, nil
+}
diff --git a/internal/token/refresh_test.go b/internal/token/refresh_test.go
new file mode 100644
index 000000000..e306f9ed6
--- /dev/null
+++ b/internal/token/refresh_test.go
@@ -0,0 +1,131 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package token
+
+import (
+ "net/http/httptest"
+ "testing"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/golang-jwt/jwt/v5"
+)
+
+func TestTokenManager_Refresh(t *testing.T) {
+ // setup types
+ u := new(library.User)
+ u.SetID(1)
+ u.SetName("foo")
+ u.SetToken("bar")
+ u.SetHash("baz")
+
+ tm := &Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ mto := &MintTokenOpts{
+ User: u,
+ TokenType: constants.UserRefreshTokenType,
+ TokenDuration: tm.UserRefreshTokenDuration,
+ }
+
+ rt, err := tm.MintToken(mto)
+ if err != nil {
+ t.Errorf("unable to create refresh token")
+ }
+
+ u.SetRefreshToken(rt)
+
+ // setup database
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+
+ defer func() {
+ db.DeleteUser(u)
+ db.Close()
+ }()
+
+ _ = db.CreateUser(u)
+
+ // set up context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, _ := gin.CreateTestContext(resp)
+ context.Set("database", db)
+
+ // run tests
+ got, err := tm.Refresh(context, rt)
+ if err != nil {
+ t.Error("Refresh should not error")
+ }
+
+ if len(got) == 0 {
+ t.Errorf("Refresh should have returned an access token")
+ }
+}
+
+func TestTokenManager_Refresh_Expired(t *testing.T) {
+ // setup types
+ u := new(library.User)
+ u.SetID(1)
+ u.SetName("foo")
+ u.SetToken("bar")
+ u.SetHash("baz")
+
+ tm := &Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ mto := &MintTokenOpts{
+ User: u,
+ TokenType: constants.UserRefreshTokenType,
+ TokenDuration: time.Minute * -1,
+ }
+
+ rt, err := tm.MintToken(mto)
+ if err != nil {
+ t.Errorf("unable to create refresh token")
+ }
+
+ u.SetRefreshToken(rt)
+
+ // setup database
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+
+ defer func() {
+ db.DeleteUser(u)
+ db.Close()
+ }()
+
+ _ = db.CreateUser(u)
+
+ // set up context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, _ := gin.CreateTestContext(resp)
+ context.Set("database", db)
+
+ // run tests
+ _, err = tm.Refresh(context, rt)
+ if err == nil {
+ t.Error("Refresh with expired token should error")
+ }
+}
diff --git a/mock/server/authentication.go b/mock/server/authentication.go
index 2d100f0d4..ea7bb76a1 100644
--- a/mock/server/authentication.go
+++ b/mock/server/authentication.go
@@ -16,7 +16,7 @@ import (
const (
// TokenRefreshResp represents a JSON return for a token refresh.
- // nolint:gosec // not a hardcoded credential
+ //nolint:gosec // not a hardcoded credential
TokenRefreshResp = `{
"token": "header.payload.signature"
}`
@@ -26,7 +26,7 @@ const (
func getTokenRefresh(c *gin.Context) {
data := []byte(TokenRefreshResp)
- var body library.Login
+ var body library.Token
_ = json.Unmarshal(data, &body)
c.JSON(http.StatusOK, body)
@@ -48,7 +48,7 @@ func getAuthenticate(c *gin.Context) {
return
}
- var body library.Login
+ var body library.Token
_ = json.Unmarshal(data, &body)
c.SetCookie(constants.RefreshTokenName, "refresh", 2, "/", "", true, true)
@@ -68,8 +68,22 @@ func getAuthenticateFromToken(c *gin.Context) {
c.AbortWithStatusJSON(http.StatusUnauthorized, types.Error{Message: &err})
}
- var body library.Login
+ var body library.Token
_ = json.Unmarshal(data, &body)
c.JSON(http.StatusOK, body)
}
+
+// validateToken returns mock response for a http GET.
+//
+// Don't pass "Authorization" in header to receive an unauthorized error message.
+func validateToken(c *gin.Context) {
+ err := "error"
+
+ token := c.Request.Header.Get("Authorization")
+ if len(token) == 0 {
+ c.AbortWithStatusJSON(http.StatusUnauthorized, types.Error{Message: &err})
+ }
+
+ c.JSON(http.StatusOK, "vela-server")
+}
diff --git a/mock/server/build.go b/mock/server/build.go
index 19f12a950..cc24af60c 100644
--- a/mock/server/build.go
+++ b/mock/server/build.go
@@ -142,6 +142,23 @@ const (
"full_name": "github/octocat"
}
]`
+
+ // BuildTokenResp represents a JSON return for requesting a build token
+ //
+ //nolint:gosec // not actual credentials
+ BuildTokenResp = `{
+ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJidWlsZF9pZCI6MSwicmVwbyI6ImZvby9iYXIiLCJzdWIiOiJPY3RvY2F0IiwiaWF0IjoxNTE2MjM5MDIyfQ.hD7gXpaf9acnLBdOBa4GOEa5KZxdzd0ZvK6fGwaN4bc"
+ }`
+
+ // BuildExecutableResp represents a JSON return for requesting a build executable.
+ BuildExecutableResp = `{
+ "id": 1
+ "build_id": 1,
+ "data": "eyAKICAgICJpZCI6ICJzdGVwX25hbWUiLAogICAgInZlcnNpb24iOiAiMSIsCiAgICAibWV0YWRhdGEiOnsKICAgICAgICAiY2xvbmUiOnRydWUsCiAgICAgICAgImVudmlyb25tZW50IjpbInN0ZXBzIiwic2VydmljZXMiLCJzZWNyZXRzIl19LAogICAgIndvcmtlciI6e30sCiAgICAic3RlcHMiOlsKICAgICAgICB7CiAgICAgICAgICAgICJpZCI6InN0ZXBfZ2l0aHViX29jdG9jYXRfMV9pbml0IiwKICAgICAgICAgICAgImRpcmVjdG9yeSI6Ii92ZWxhL3NyYy9naXRodWIuY29tL2dpdGh1Yi9vY3RvY2F0IiwKICAgICAgICAgICAgImVudmlyb25tZW50IjogeyJCVUlMRF9BVVRIT1IiOiJPY3RvY2F0In0KICAgICAgICB9CiAgICBdCn0KCg=="
+ }`
+
+ // CleanResourcesResp represents a string return for cleaning resources as an admin.
+ CleanResourcesResp = "42 builds cleaned. 42 services cleaned. 42 steps cleaned."
)
// getBuilds returns mock JSON for a http GET.
@@ -305,3 +322,72 @@ func buildQueue(c *gin.Context) {
c.JSON(http.StatusOK, body)
}
+
+// buildToken has a param :build returns mock JSON for a http GET.
+//
+// Pass "0" to :build to test receiving a http 404 response. Pass "2"
+// to :build to test receiving a http 400 response.
+func buildToken(c *gin.Context) {
+ b := c.Param("build")
+
+ if strings.EqualFold(b, "0") {
+ c.AbortWithStatusJSON(http.StatusNotFound, "")
+
+ return
+ }
+
+ if strings.EqualFold(b, "2") {
+ c.AbortWithStatusJSON(http.StatusBadRequest, "")
+
+ return
+ }
+
+ data := []byte(BuildTokenResp)
+
+ var body library.Token
+ _ = json.Unmarshal(data, &body)
+
+ c.JSON(http.StatusOK, body)
+}
+
+// buildExecutable has a param :build returns mock JSON for a http GET.
+//
+// Pass "0" to :build to test receiving a http 500 response.
+func buildExecutable(c *gin.Context) {
+ b := c.Param("build")
+
+ if strings.EqualFold(b, "0") {
+ msg := fmt.Sprintf("unable to get build executable for build %s", b)
+
+ c.AbortWithStatusJSON(http.StatusInternalServerError, types.Error{Message: &msg})
+
+ return
+ }
+
+ data := []byte(BuildExecutableResp)
+
+ var body library.BuildExecutable
+ _ = json.Unmarshal(data, &body)
+
+ c.JSON(http.StatusOK, body)
+}
+
+// cleanResources has a query param :before returns mock JSON for a http PUT
+//
+// Pass "1" to :before to test receiving a http 500 response. Pass "2" to :before
+// to test receiving a http 401 response.
+func cleanResoures(c *gin.Context) {
+ before := c.Query("before")
+
+ if strings.EqualFold(before, "1") {
+ c.AbortWithStatusJSON(http.StatusInternalServerError, "")
+
+ return
+ }
+
+ if strings.EqualFold(before, "2") {
+ c.AbortWithStatusJSON(http.StatusUnauthorized, "")
+ }
+
+ c.JSON(http.StatusOK, CleanResourcesResp)
+}
diff --git a/mock/server/hook.go b/mock/server/hook.go
index 0c356c3c5..d2c21da46 100644
--- a/mock/server/hook.go
+++ b/mock/server/hook.go
@@ -2,6 +2,7 @@
//
// Use of this source code is governed by the LICENSE file in this repository.
+//nolint:dupl // ignore duplicate with user code
package server
import (
diff --git a/mock/server/log.go b/mock/server/log.go
index 8a101c4d2..3becfff38 100644
--- a/mock/server/log.go
+++ b/mock/server/log.go
@@ -51,12 +51,7 @@ func getServiceLog(c *gin.Context) {
// addServiceLog returns mock JSON for a http GET.
func addServiceLog(c *gin.Context) {
- data := []byte(LogResp)
-
- var body library.Log
- _ = json.Unmarshal(data, &body)
-
- c.JSON(http.StatusCreated, body)
+ c.JSON(http.StatusCreated, nil)
}
// updateServiceLog has a param :service returns mock JSON for a http PUT.
@@ -73,12 +68,7 @@ func updateServiceLog(c *gin.Context) {
return
}
- data := []byte(LogResp)
-
- var body library.Log
- _ = json.Unmarshal(data, &body)
-
- c.JSON(http.StatusOK, body)
+ c.JSON(http.StatusOK, nil)
}
// removeServiceLog has a param :service returns mock JSON for a http DELETE.
@@ -122,12 +112,7 @@ func getStepLog(c *gin.Context) {
// addStepLog returns mock JSON for a http GET.
func addStepLog(c *gin.Context) {
- data := []byte(LogResp)
-
- var body library.Log
- _ = json.Unmarshal(data, &body)
-
- c.JSON(http.StatusCreated, body)
+ c.JSON(http.StatusCreated, nil)
}
// updateStepLog has a param :step returns mock JSON for a http PUT.
@@ -144,12 +129,7 @@ func updateStepLog(c *gin.Context) {
return
}
- data := []byte(LogResp)
-
- var body library.Log
- _ = json.Unmarshal(data, &body)
-
- c.JSON(http.StatusOK, body)
+ c.JSON(http.StatusOK, nil)
}
// removeStepLog has a param :step returns mock JSON for a http DELETE.
diff --git a/mock/server/pipeline.go b/mock/server/pipeline.go
index 6d886e0e7..021960ea3 100644
--- a/mock/server/pipeline.go
+++ b/mock/server/pipeline.go
@@ -5,10 +5,13 @@
package server
import (
+ "encoding/json"
"fmt"
"net/http"
"strings"
+ "github.com/go-vela/types/library"
+
"github.com/gin-gonic/gin"
"github.com/go-vela/types"
"github.com/go-vela/types/yaml"
@@ -102,37 +105,62 @@ templates:
source: github.com/go-vela/vela-tutorials/templates/sample.yml
type: github
`
-
- // PipelineResp represents a YAML return for a single pipeline.
- PipelineResp = `---
-version: "1"
-
-secrets:
- - name: docker_username
- key: go-vela/docker/username
- engine: native
- type: org
-
- - name: docker_password
- key: go-vela/docker/password
- engine: native
- type: org
-
-steps:
- - name: go
- template:
- name: sample
-
- - name: non-template-echo
- image: golang:latest
- commands:
- - echo hello
-
-templates:
- - name: sample
- source: github.com/go-vela/vela-tutorials/templates/sample.yml
- type: github
-`
+ // PipelineResp represents a JSON return for a single pipeline.
+ PipelineResp = `{
+ "id": 1,
+ "repo_id": 1,
+ "commit": "48afb5bdc41ad69bf22588491333f7cf71135163",
+ "flavor": "",
+ "platform": "",
+ "ref": "refs/heads/master",
+ "type": "yaml",
+ "version": "1",
+ "external_secrets": false,
+ "internal_secrets": false,
+ "services": false,
+ "stages": false,
+ "steps": true,
+ "templates": false,
+ "data": "LS0tCnZlcnNpb246ICIxIgoKc3RlcHM6CiAgLSBuYW1lOiBlY2hvCiAgICBpbWFnZTogYWxwaW5lOmxhdGVzdAogICAgY29tbWFuZHM6IFtlY2hvIGZvb10="
+}`
+
+ // PipelinesResp represents a JSON return for one to many hooks.
+ PipelinesResp = `[
+ {
+ "id": 2
+ "repo_id": 1,
+ "commit": "a49aaf4afae6431a79239c95247a2b169fd9f067",
+ "flavor": "",
+ "platform": "",
+ "ref": "refs/heads/master",
+ "type": "yaml",
+ "version": "1",
+ "external_secrets": false,
+ "internal_secrets": false,
+ "services": false,
+ "stages": false,
+ "steps": true,
+ "templates": false,
+ "data": "LS0tCnZlcnNpb246ICIxIgoKc3RlcHM6CiAgLSBuYW1lOiBlY2hvCiAgICBpbWFnZTogYWxwaW5lOmxhdGVzdAogICAgY29tbWFuZHM6IFtlY2hvIGZvb10="
+ },
+ {
+ "id": 1,
+ "repo_id": 1,
+ "commit": "48afb5bdc41ad69bf22588491333f7cf71135163",
+ "flavor": "",
+ "platform": "",
+ "ref": "refs/heads/master",
+ "type": "yaml",
+ "version": "1",
+ "external_secrets": false,
+ "internal_secrets": false,
+ "services": false,
+ "stages": false,
+ "steps": true,
+ "templates": false,
+ "data": "LS0tCnZlcnNpb246ICIxIgoKc3RlcHM6CiAgLSBuYW1lOiBlY2hvCiAgICBpbWFnZTogYWxwaW5lOmxhdGVzdAogICAgY29tbWFuZHM6IFtlY2hvIGZvb10="
+ }
+]`
// TemplateResp represents a YAML return for templates in a pipeline.
TemplateResp = `---
@@ -143,14 +171,24 @@ sample:
`
)
-// getPipeline has a param :repo returns mock YAML for a http GET.
+// getPipelines returns mock JSON for a http GET.
+func getPipelines(c *gin.Context) {
+ data := []byte(PipelinesResp)
+
+ var body []library.Pipeline
+ _ = json.Unmarshal(data, &body)
+
+ c.JSON(http.StatusOK, body)
+}
+
+// getPipeline has a param :pipeline returns mock YAML for a http GET.
//
-// Pass "not-found" to :repo to test receiving a http 404 response.
+// Pass "0" to :pipeline to test receiving a http 404 response.
func getPipeline(c *gin.Context) {
- r := c.Param("repo")
+ p := c.Param("pipeline")
- if strings.Contains(r, "not-found") {
- msg := fmt.Sprintf("Repo %s does not exist", r)
+ if strings.EqualFold(p, "0") {
+ msg := fmt.Sprintf("Pipeline %s does not exist", p)
c.AbortWithStatusJSON(http.StatusNotFound, types.Error{Message: &msg})
@@ -159,20 +197,71 @@ func getPipeline(c *gin.Context) {
data := []byte(PipelineResp)
- var body yaml.Build
- _ = yml.Unmarshal(data, &body)
+ var body library.Pipeline
+ _ = json.Unmarshal(data, &body)
- c.YAML(http.StatusOK, body)
+ c.JSON(http.StatusOK, body)
+}
+
+// addPipeline returns mock JSON for a http POST.
+func addPipeline(c *gin.Context) {
+ data := []byte(PipelineResp)
+
+ var body library.Pipeline
+ _ = json.Unmarshal(data, &body)
+
+ c.JSON(http.StatusCreated, body)
+}
+
+// updatePipeline has a param :pipeline returns mock JSON for a http PUT.
+//
+// Pass "0" to :pipeline to test receiving a http 404 response.
+func updatePipeline(c *gin.Context) {
+ if !strings.Contains(c.FullPath(), "admin") {
+ p := c.Param("pipeline")
+
+ if strings.EqualFold(p, "0") {
+ msg := fmt.Sprintf("Pipeline %s does not exist", p)
+
+ c.AbortWithStatusJSON(http.StatusNotFound, types.Error{Message: &msg})
+
+ return
+ }
+ }
+
+ data := []byte(PipelineResp)
+
+ var body library.Pipeline
+ _ = json.Unmarshal(data, &body)
+
+ c.JSON(http.StatusOK, body)
+}
+
+// removePipeline has a param :pipeline returns mock JSON for a http DELETE.
+//
+// Pass "0" to :pipeline to test receiving a http 404 response.
+func removePipeline(c *gin.Context) {
+ p := c.Param("pipeline")
+
+ if strings.EqualFold(p, "0") {
+ msg := fmt.Sprintf("Pipeline %s does not exist", p)
+
+ c.AbortWithStatusJSON(http.StatusNotFound, types.Error{Message: &msg})
+
+ return
+ }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("Pipeline %s removed", p))
}
-// compilePipeline has a param :repo returns mock YAML for a http GET.
+// compilePipeline has a param :pipeline returns mock YAML for a http GET.
//
-// Pass "not-found" to :repo to test receiving a http 404 response.
+// Pass "0" to :pipeline to test receiving a http 404 response.
func compilePipeline(c *gin.Context) {
- r := c.Param("repo")
+ p := c.Param("pipeline")
- if strings.Contains(r, "not-found") {
- msg := fmt.Sprintf("Repo %s does not exist", r)
+ if strings.EqualFold(p, "0") {
+ msg := fmt.Sprintf("Pipeline %s does not exist", p)
c.AbortWithStatusJSON(http.StatusNotFound, types.Error{Message: &msg})
@@ -187,14 +276,14 @@ func compilePipeline(c *gin.Context) {
c.YAML(http.StatusOK, body)
}
-// expandPipeline has a param :repo returns mock YAML for a http GET.
+// expandPipeline has a param :pipeline returns mock YAML for a http GET.
//
-// Pass "not-found" to :repo to test receiving a http 404 response.
+// Pass "0" to :pipeline to test receiving a http 404 response.
func expandPipeline(c *gin.Context) {
- r := c.Param("repo")
+ p := c.Param("pipeline")
- if strings.Contains(r, "not-found") {
- msg := fmt.Sprintf("Repo %s does not exist", r)
+ if strings.EqualFold(p, "0") {
+ msg := fmt.Sprintf("Pipeline %s does not exist", p)
c.AbortWithStatusJSON(http.StatusNotFound, types.Error{Message: &msg})
@@ -209,14 +298,14 @@ func expandPipeline(c *gin.Context) {
c.YAML(http.StatusOK, body)
}
-// getTemplates has a param :repo returns mock YAML for a http GET.
+// getTemplates has a param :pipeline returns mock YAML for a http GET.
//
-// Pass "not-found" to :repo to test receiving a http 404 response.
+// Pass "0" to :pipeline to test receiving a http 404 response.
func getTemplates(c *gin.Context) {
- r := c.Param("repo")
+ p := c.Param("pipeline")
- if strings.Contains(r, "not-found") {
- msg := fmt.Sprintf("Repo %s does not exist", r)
+ if strings.EqualFold(p, "0") {
+ msg := fmt.Sprintf("Pipeline %s does not exist", p)
c.AbortWithStatusJSON(http.StatusNotFound, types.Error{Message: &msg})
@@ -231,14 +320,14 @@ func getTemplates(c *gin.Context) {
c.YAML(http.StatusOK, body)
}
-// validatePipeline has a param :repo returns mock YAML for a http GET.
+// validatePipeline has a param :pipeline returns mock YAML for a http GET.
//
-// Pass "not-found" to :repo to test receiving a http 404 response.
+// Pass "0" to :pipeline to test receiving a http 404 response.
func validatePipeline(c *gin.Context) {
- r := c.Param("repo")
+ p := c.Param("pipeline")
- if strings.Contains(r, "not-found") {
- msg := fmt.Sprintf("Repo %s does not exist", r)
+ if strings.EqualFold(p, "0") {
+ msg := fmt.Sprintf("Pipeline %s does not exist", p)
c.AbortWithStatusJSON(http.StatusNotFound, types.Error{Message: &msg})
diff --git a/mock/server/schedule.go b/mock/server/schedule.go
new file mode 100644
index 000000000..a1acc54f7
--- /dev/null
+++ b/mock/server/schedule.go
@@ -0,0 +1,213 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package server
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/types"
+ "github.com/go-vela/types/library"
+)
+
+const (
+ // ScheduleResp represents a JSON return for a single schedule.
+ ScheduleResp = `{
+ "id": 2,
+ "active": true,
+ "name": "foo",
+ "entry": "@weekly",
+ "created_at": 1683154980,
+ "created_by": "octocat",
+ "updated_at": 1683154980,
+ "updated_by": "octocat",
+ "scheduled_at": 0,
+ "repo": {
+ "id": 1,
+ "user_id": 1,
+ "org": "github",
+ "name": "octocat",
+ "full_name": "github/octocat",
+ "link": "https://github.com/github/octocat",
+ "clone": "https://github.com/github/octocat.git",
+ "branch": "main",
+ "topics": [],
+ "build_limit": 10,
+ "timeout": 30,
+ "counter": 0,
+ "visibility": "public",
+ "private": false,
+ "trusted": false,
+ "active": true,
+ "allow_pull": false,
+ "allow_push": true,
+ "allow_deploy": false,
+ "allow_tag": false,
+ "allow_comment": false,
+ "pipeline_type": "yaml",
+ "previous_name": ""
+ }
+}`
+ SchedulesResp = `[
+ {
+ "id": 2,
+ "active": true,
+ "name": "foo",
+ "entry": "@weekly",
+ "created_at": 1683154980,
+ "created_by": "octocat",
+ "updated_at": 1683154980,
+ "updated_by": "octocat",
+ "scheduled_at": 0,
+ "repo": {
+ "id": 1,
+ "user_id": 1,
+ "org": "github",
+ "name": "octokitty",
+ "full_name": "github/octokitty",
+ "link": "https://github.com/github/octokitty",
+ "clone": "https://github.com/github/octokitty.git",
+ "branch": "main",
+ "topics": [],
+ "build_limit": 10,
+ "timeout": 30,
+ "counter": 0,
+ "visibility": "public",
+ "private": false,
+ "trusted": false,
+ "active": true,
+ "allow_pull": false,
+ "allow_push": true,
+ "allow_deploy": false,
+ "allow_tag": false,
+ "allow_comment": false,
+ "pipeline_type": "yaml",
+ "previous_name": ""
+ }
+ },
+ {
+ "id": 1,
+ "active": true,
+ "name": "bar",
+ "entry": "@weekly",
+ "created_at": 1683154974,
+ "created_by": "octocat",
+ "updated_at": 1683154974,
+ "updated_by": "octocat",
+ "scheduled_at": 0,
+ "repo": {
+ "id": 1,
+ "user_id": 1,
+ "org": "github",
+ "name": "octokitty",
+ "full_name": "github/octokitty",
+ "link": "https://github.com/github/octokitty",
+ "clone": "https://github.com/github/octokitty.git",
+ "branch": "main",
+ "topics": [],
+ "build_limit": 10,
+ "timeout": 30,
+ "counter": 0,
+ "visibility": "public",
+ "private": false,
+ "trusted": false,
+ "active": true,
+ "allow_pull": false,
+ "allow_push": true,
+ "allow_deploy": false,
+ "allow_tag": false,
+ "allow_comment": false,
+ "pipeline_type": "yaml",
+ "previous_name": ""
+ }
+ }
+]`
+)
+
+// getSchedules returns mock JSON for a http GET.
+func getSchedules(c *gin.Context) {
+ data := []byte(SchedulesResp)
+
+ var body []library.Schedule
+ _ = json.Unmarshal(data, &body)
+
+ c.JSON(http.StatusOK, body)
+}
+
+// getSchedule has a param :schedule returns mock JSON for a http GET.
+//
+// Pass "not-found" to :schedule to test receiving a http 404 response.
+func getSchedule(c *gin.Context) {
+ s := c.Param("schedule")
+
+ if strings.Contains(s, "not-found") {
+ msg := fmt.Sprintf("Schedule %s does not exist", s)
+
+ c.AbortWithStatusJSON(http.StatusNotFound, types.Error{Message: &msg})
+
+ return
+ }
+
+ data := []byte(ScheduleResp)
+
+ var body library.Schedule
+ _ = json.Unmarshal(data, &body)
+
+ c.JSON(http.StatusOK, body)
+}
+
+// addSchedule returns mock JSON for a http POST.
+func addSchedule(c *gin.Context) {
+ data := []byte(ScheduleResp)
+
+ var body library.Schedule
+ _ = json.Unmarshal(data, &body)
+
+ c.JSON(http.StatusCreated, body)
+}
+
+// updateSchedule has a param :schedule returns mock JSON for a http PUT.
+//
+// Pass "not-found" to :schedule to test receiving a http 404 response.
+func updateSchedule(c *gin.Context) {
+ if !strings.Contains(c.FullPath(), "admin") {
+ s := c.Param("schedule")
+
+ if strings.Contains(s, "not-found") {
+ msg := fmt.Sprintf("Schedule %s does not exist", s)
+
+ c.AbortWithStatusJSON(http.StatusNotFound, types.Error{Message: &msg})
+
+ return
+ }
+ }
+
+ data := []byte(ScheduleResp)
+
+ var body library.Schedule
+ _ = json.Unmarshal(data, &body)
+
+ c.JSON(http.StatusOK, body)
+}
+
+// removeSchedule has a param :schedule returns mock JSON for a http DELETE.
+//
+// Pass "not-found" to :schedule to test receiving a http 404 response.
+func removeSchedule(c *gin.Context) {
+ s := c.Param("schedule")
+
+ if strings.Contains(s, "not-found") {
+ msg := fmt.Sprintf("Schedule %s does not exist", s)
+
+ c.AbortWithStatusJSON(http.StatusNotFound, types.Error{Message: &msg})
+
+ return
+ }
+
+ c.JSON(http.StatusOK, fmt.Sprintf("schedule %s deleted", s))
+}
diff --git a/mock/server/secret.go b/mock/server/secret.go
index 3ef924d67..eb1bcd72a 100644
--- a/mock/server/secret.go
+++ b/mock/server/secret.go
@@ -2,6 +2,7 @@
//
// Use of this source code is governed by the LICENSE file in this repository.
+//nolint:dupl // ignore duplicate with user code
package server
import (
@@ -15,7 +16,7 @@ import (
"github.com/go-vela/types/library"
)
-// nolint:gosec // these are mock responses
+//nolint:gosec // these are mock responses
const (
// SecretResp represents a JSON return for a single secret.
SecretResp = `{
diff --git a/mock/server/server.go b/mock/server/server.go
index e36b1076e..7bc668719 100644
--- a/mock/server/server.go
+++ b/mock/server/server.go
@@ -12,30 +12,25 @@ import (
// FakeHandler returns an http.Handler that is capable of handling
// Vela API requests and returning mock responses.
-// nolint:funlen // number of endpoints is causing linter warning
+//
+//nolint:funlen // number of endpoints is causing linter warning
func FakeHandler() http.Handler {
gin.SetMode(gin.TestMode)
e := gin.New()
// mock endpoints for admin calls
- e.GET("/api/v1/admin/builds", getBuilds)
e.PUT("/api/v1/admin/build", updateBuild)
e.GET("/api/v1/admin/builds/queue", buildQueue)
- e.GET("/api/v1/admin/deployments", getDeployments)
e.PUT("/api/v1/admin/deployment", updateDeployment)
- e.GET("/api/v1/admin/hooks", getHooks)
e.PUT("/api/v1/admin/hook", updateHook)
- e.GET("/api/v1/admin/repos", getRepos)
e.PUT("/api/v1/admin/repo", updateRepo)
- e.GET("/api/v1/admin/secrets", getSecrets)
e.PUT("/api/v1/admin/secret", updateSecret)
- e.GET("/api/v1/admin/services", getServices)
e.PUT("/api/v1/admin/service", updateService)
- e.GET("/api/v1/admin/steps", getSteps)
e.PUT("/api/v1/admin/step", updateStep)
- e.GET("/api/v1/admin/users", getUsers)
e.PUT("/api/v1/admin/user", updateUser)
+ e.POST("/api/v1/admin/workers/:worker/register-token", registerToken)
+ e.PUT("api/v1/admin/clean", cleanResoures)
// mock endpoints for build calls
e.GET("/api/v1/repos/:org/:repo/builds/:build", getBuild)
@@ -46,6 +41,8 @@ func FakeHandler() http.Handler {
e.POST("/api/v1/repos/:org/:repo/builds", addBuild)
e.PUT("/api/v1/repos/:org/:repo/builds/:build", updateBuild)
e.DELETE("/api/v1/repos/:org/:repo/builds/:build", removeBuild)
+ e.GET("/api/v1/repos/:org/:repo/builds/:build/token", buildToken)
+ e.GET("/api/v1/repos/:org/:repo/builds/:build/executable", buildExecutable)
// mock endpoints for deployment calls
e.GET("/api/v1/deployments/:org/:repo", getDeployments)
@@ -70,11 +67,15 @@ func FakeHandler() http.Handler {
e.DELETE("/api/v1/repos/:org/:repo/builds/:build/steps/:step/logs", removeStepLog)
// mock endpoints for pipeline calls
- e.GET("/api/v1/pipelines/:org/:repo", getPipeline)
- e.POST("/api/v1/pipelines/:org/:repo/compile", compilePipeline)
- e.POST("/api/v1/pipelines/:org/:repo/expand", expandPipeline)
- e.GET("/api/v1/pipelines/:org/:repo/templates", getTemplates)
- e.POST("/api/v1/pipelines/:org/:repo/validate", validatePipeline)
+ e.POST("/api/v1/pipelines/:org/:repo", addPipeline)
+ e.GET("/api/v1/pipelines/:org/:repo", getPipelines)
+ e.GET("/api/v1/pipelines/:org/:repo/:pipeline", getPipeline)
+ e.PUT("/api/v1/pipelines/:org/:repo/:pipeline", updatePipeline)
+ e.DELETE("/api/v1/pipelines/:org/:repo/:pipeline", removePipeline)
+ e.POST("/api/v1/pipelines/:org/:repo/:pipeline/compile", compilePipeline)
+ e.POST("/api/v1/pipelines/:org/:repo/:pipeline/expand", expandPipeline)
+ e.GET("/api/v1/pipelines/:org/:repo/:pipeline/templates", getTemplates)
+ e.POST("/api/v1/pipelines/:org/:repo/:pipeline/validate", validatePipeline)
// mock endpoints for repo calls
e.GET("/api/v1/repos/:org/:repo", getRepo)
@@ -100,7 +101,6 @@ func FakeHandler() http.Handler {
e.POST("/api/v1/repos/:org/:repo/builds/:build/steps", addStep)
e.PUT("/api/v1/repos/:org/:repo/builds/:build/steps/:step", updateStep)
e.DELETE("/api/v1/repos/:org/:repo/builds/:build/steps/:step", removeStep)
- e.POST("/api/v1/repos/:org/:repo/builds/:build/steps/:step/stream", postStepStream)
// mock endpoints for service calls
e.GET("/api/v1/repos/:org/:repo/builds/:build/services/:service", getService)
@@ -108,7 +108,6 @@ func FakeHandler() http.Handler {
e.POST("/api/v1/repos/:org/:repo/builds/:build/services", addService)
e.PUT("/api/v1/repos/:org/:repo/builds/:build/services/:service", updateService)
e.DELETE("/api/v1/repos/:org/:repo/builds/:build/services/:service", removeService)
- e.POST("/api/v1/repos/:org/:repo/builds/:build/services/:service/stream", postServiceStream)
// mock endpoints for user calls
e.GET("/api/v1/users/:user", getUser)
@@ -122,12 +121,21 @@ func FakeHandler() http.Handler {
e.GET("/api/v1/workers/:worker", getWorker)
e.POST("/api/v1/workers", addWorker)
e.PUT("/api/v1/workers/:worker", updateWorker)
+ e.POST("/api/v1/workers/:worker/refresh", refreshWorkerAuth)
e.DELETE("/api/v1/workers/:worker", removeWorker)
+ // mock endpoints for schedule calls
+ e.GET("/api/v1/schedules/:org/:repo", getSchedules)
+ e.GET("/api/v1/schedules/:org/:repo/:schedule", getSchedule)
+ e.POST("/api/v1/schedules/:org/:repo", addSchedule)
+ e.PUT("/api/v1/schedules/:org/:repo/:schedule", updateSchedule)
+ e.DELETE("/api/v1/schedules/:org/:repo/:schedule", removeSchedule)
+
// mock endpoints for authentication calls
e.GET("/token-refresh", getTokenRefresh)
e.GET("/authenticate", getAuthenticate)
e.POST("/authenticate/token", getAuthenticateFromToken)
+ e.GET("/validate-token", validateToken)
return e
}
diff --git a/mock/server/service.go b/mock/server/service.go
index b461bde65..0d4ce36f1 100644
--- a/mock/server/service.go
+++ b/mock/server/service.go
@@ -2,6 +2,7 @@
//
// Use of this source code is governed by the LICENSE file in this repository.
+//nolint:dupl // ignore duplicate with user code
package server
import (
diff --git a/mock/server/step.go b/mock/server/step.go
index e0826d45a..c7111aded 100644
--- a/mock/server/step.go
+++ b/mock/server/step.go
@@ -2,6 +2,7 @@
//
// Use of this source code is governed by the LICENSE file in this repository.
+//nolint:dupl // ignore duplicate with user code
package server
import (
diff --git a/mock/server/stream.go b/mock/server/stream.go
deleted file mode 100644
index 05209fb1a..000000000
--- a/mock/server/stream.go
+++ /dev/null
@@ -1,21 +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 server
-
-import (
- "net/http"
-
- "github.com/gin-gonic/gin"
-)
-
-// postServiceStream returns a nock response for an http POST.
-func postServiceStream(c *gin.Context) {
- c.JSON(http.StatusNoContent, nil)
-}
-
-// postStepStream returns a nock response for an http POST.
-func postStepStream(c *gin.Context) {
- c.JSON(http.StatusNoContent, nil)
-}
diff --git a/mock/server/user.go b/mock/server/user.go
index 3d883274d..85029962b 100644
--- a/mock/server/user.go
+++ b/mock/server/user.go
@@ -2,6 +2,7 @@
//
// Use of this source code is governed by the LICENSE file in this repository.
+//nolint:dupl // ignore duplicate with user code
package server
import (
diff --git a/mock/server/worker.go b/mock/server/worker.go
index ed79507bb..51aedfa80 100644
--- a/mock/server/worker.go
+++ b/mock/server/worker.go
@@ -58,6 +58,23 @@ const (
"last_checked_in": 1602612590
}
]`
+
+ // AddWorkerResp represents a JSON return for adding a worker.
+ AddWorkerResp = `{
+ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3b3JrZXIiLCJpYXQiOjE1MTYyMzkwMjIsInRva2VuX3R5cGUiOiJXb3JrZXJBdXRoIn0.qeULIimCJlrwsE0JykNpzBmMaHUbvfk0vkyAz2eEo38"
+ }`
+
+ // RefreshWorkerAuthResp represents a JSON return for refreshing a worker's authentication.
+ RefreshWorkerAuthResp = `{
+ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3b3JrZXIiLCJpYXQiOjE1MTYyMzkwMjIsInRva2VuX3R5cGUiOiJXb3JrZXJBdXRoIn0.qeULIimCJlrwsE0JykNpzBmMaHUbvfk0vkyAz2eEo38"
+ }`
+
+ // RegisterTokenResp represents a JSON return for an admin requesting a registration token.
+ //
+ //nolint:gosec // not actual credentials
+ RegisterTokenResp = `{
+ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ3b3JrZXIiLCJpYXQiOjE1MTYyMzkwMjIsInRva2VuX3R5cGUiOiJXb3JrZXJSZWdpc3RlciJ9.gEzKaZB-sDd_gFCVF5uGo2mcf3iy9CrXDTLPZ6PTsTc"
+ }`
)
// getWorkers returns mock JSON for a http GET.
@@ -92,9 +109,9 @@ func getWorker(c *gin.Context) {
// addWorker returns mock JSON for a http POST.
func addWorker(c *gin.Context) {
- data := []byte(WorkerResp)
+ data := []byte(AddWorkerResp)
- var body library.Worker
+ var body library.Token
_ = json.Unmarshal(data, &body)
c.JSON(http.StatusCreated, body)
@@ -122,6 +139,28 @@ func updateWorker(c *gin.Context) {
c.JSON(http.StatusOK, body)
}
+// refreshWorkerAuth has a param :worker returns mock JSON for a http PUT.
+//
+// Pass "0" to :worker to test receiving a http 404 response.
+func refreshWorkerAuth(c *gin.Context) {
+ w := c.Param("worker")
+
+ if strings.EqualFold(w, "0") {
+ msg := fmt.Sprintf("Worker %s does not exist", w)
+
+ c.AbortWithStatusJSON(http.StatusNotFound, types.Error{Message: &msg})
+
+ return
+ }
+
+ data := []byte(RefreshWorkerAuthResp)
+
+ var body library.Token
+ _ = json.Unmarshal(data, &body)
+
+ c.JSON(http.StatusOK, body)
+}
+
// removeWorker has a param :worker returns mock JSON for a http DELETE.
//
// Pass "0" to :worker to test receiving a http 404 response.
@@ -138,3 +177,25 @@ func removeWorker(c *gin.Context) {
c.JSON(http.StatusOK, fmt.Sprintf("Worker %s removed", w))
}
+
+// registerToken has a param :worker returns mock JSON for a http POST.
+//
+// Pass "0" to :worker to test receiving a http 401 response.
+func registerToken(c *gin.Context) {
+ w := c.Param("worker")
+
+ if strings.EqualFold(w, "0") {
+ msg := fmt.Sprintf("user %s is not a platform admin", w)
+
+ c.AbortWithStatusJSON(http.StatusUnauthorized, types.Error{Message: &msg})
+
+ return
+ }
+
+ data := []byte(RegisterTokenResp)
+
+ var body library.Token
+ _ = json.Unmarshal(data, &body)
+
+ c.JSON(http.StatusCreated, body)
+}
diff --git a/queue/context.go b/queue/context.go
index ea9c2d0fe..e5bf48c78 100644
--- a/queue/context.go
+++ b/queue/context.go
@@ -56,7 +56,7 @@ func WithContext(c context.Context, s Service) context.Context {
//
// https://pkg.go.dev/context?tab=doc#WithValue
//
- // nolint: golint,staticcheck // ignore using string with context value
+ //nolint:staticcheck,revive // ignore using string with context value
return context.WithValue(c, key, s)
}
diff --git a/queue/context_test.go b/queue/context_test.go
index 4a6ba4c90..92218a1f4 100644
--- a/queue/context_test.go
+++ b/queue/context_test.go
@@ -22,7 +22,7 @@ func TestExecutor_FromContext(t *testing.T) {
want Service
}{
{
- // nolint: golint,staticcheck // ignore using string with context value
+ //nolint:staticcheck,revive // ignore using string with context value
context: context.WithValue(context.Background(), key, _service),
want: _service,
},
@@ -31,7 +31,7 @@ func TestExecutor_FromContext(t *testing.T) {
want: nil,
},
{
- // nolint: golint,staticcheck // ignore using string with context value
+ //nolint:staticcheck,revive // ignore using string with context value
context: context.WithValue(context.Background(), key, "foo"),
want: nil,
},
@@ -92,7 +92,7 @@ func TestExecutor_WithContext(t *testing.T) {
// setup types
_service, _ := New(&Setup{})
- // nolint: golint,staticcheck // ignore using string with context value
+ //nolint:staticcheck,revive // ignore using string with context value
want := context.WithValue(context.Background(), key, _service)
// run test
diff --git a/queue/doc.go b/queue/doc.go
index ca03c8c84..5c08b1b0d 100644
--- a/queue/doc.go
+++ b/queue/doc.go
@@ -7,5 +7,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/queue"
+// import "github.com/go-vela/server/queue"
package queue
diff --git a/queue/flags.go b/queue/flags.go
index 0fd1e23a6..b5ede4733 100644
--- a/queue/flags.go
+++ b/queue/flags.go
@@ -50,4 +50,16 @@ var Flags = []cli.Flag{
Usage: "timeout for requests that pop items off the queue",
Value: 60 * time.Second,
},
+ &cli.StringFlag{
+ EnvVars: []string{"QUEUE_PRIVATE_KEY"},
+ FilePath: "/vela/signing.key",
+ Name: "queue.private-key",
+ Usage: "set value of base64 encoded queue signing private key",
+ },
+ &cli.StringFlag{
+ EnvVars: []string{"QUEUE_PUBLIC_KEY"},
+ FilePath: "/vela/signing.pub",
+ Name: "queue.public-key",
+ Usage: "set value of base64 encoded queue signing public key",
+ },
}
diff --git a/queue/queue.go b/queue/queue.go
index f665c74ba..fca90b8a2 100644
--- a/queue/queue.go
+++ b/queue/queue.go
@@ -11,13 +11,12 @@ import (
"github.com/sirupsen/logrus"
)
-// nolint: godot // ignore period at end for comment ending in a list
-//
// New creates and returns a Vela service capable of
// integrating with the configured queue environment.
// Currently, the following queues are supported:
//
// * redis
+// .
func New(s *Setup) (Service, error) {
// validate the setup being provided
//
diff --git a/queue/queue_test.go b/queue/queue_test.go
index 8e690920c..61bcfd527 100644
--- a/queue/queue_test.go
+++ b/queue/queue_test.go
@@ -13,7 +13,6 @@ import (
func TestQueue_New(t *testing.T) {
// setup types
-
// create a local fake redis instance
//
// https://pkg.go.dev/github.com/alicebob/miniredis/v2#Run
diff --git a/queue/redis/doc.go b/queue/redis/doc.go
index ad6afe54c..d75e65027 100644
--- a/queue/redis/doc.go
+++ b/queue/redis/doc.go
@@ -7,5 +7,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/queue/redis"
+// import "github.com/go-vela/server/queue/redis"
package redis
diff --git a/queue/redis/driver_test.go b/queue/redis/driver_test.go
index 85582775c..2e5d4bd96 100644
--- a/queue/redis/driver_test.go
+++ b/queue/redis/driver_test.go
@@ -16,7 +16,6 @@ import (
func TestRedis_Driver(t *testing.T) {
// setup types
-
// create a local fake redis instance
//
// https://pkg.go.dev/github.com/alicebob/miniredis/v2#Run
diff --git a/queue/redis/length.go b/queue/redis/length.go
new file mode 100644
index 000000000..7ed4ecd8b
--- /dev/null
+++ b/queue/redis/length.go
@@ -0,0 +1,27 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package redis
+
+import (
+ "context"
+)
+
+// Length tallies all items present in the configured channels in the queue.
+func (c *client) Length(ctx context.Context) (int64, error) {
+ c.Logger.Tracef("reading length of all configured channels in queue")
+
+ total := int64(0)
+
+ for _, channel := range c.config.Channels {
+ items, err := c.Redis.LLen(ctx, channel).Result()
+ if err != nil {
+ return 0, err
+ }
+
+ total += items
+ }
+
+ return total, nil
+}
diff --git a/queue/redis/length_test.go b/queue/redis/length_test.go
new file mode 100644
index 000000000..d9622594c
--- /dev/null
+++ b/queue/redis/length_test.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 redis
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ "github.com/go-vela/types"
+ "gopkg.in/square/go-jose.v2/json"
+)
+
+func TestRedis_Length(t *testing.T) {
+ // setup types
+ // use global variables in redis_test.go
+ _item := &types.Item{
+ Build: _build,
+ Repo: _repo,
+ User: _user,
+ }
+
+ // setup queue item
+ bytes, err := json.Marshal(_item)
+ if err != nil {
+ t.Errorf("unable to marshal queue item: %v", err)
+ }
+
+ // setup redis mock
+ _redis, err := NewTest(_signingPrivateKey, _signingPublicKey, "vela", "vela:second", "vela:third")
+ if err != nil {
+ t.Errorf("unable to create queue service: %v", err)
+ }
+
+ // setup tests
+ tests := []struct {
+ channels []string
+ want int64
+ }{
+ {
+ channels: []string{"vela"},
+ want: 1,
+ },
+ {
+ channels: []string{"vela", "vela:second", "vela:third"},
+ want: 4,
+ },
+ {
+ channels: []string{"vela", "vela:second", "phony"},
+ want: 6,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ for _, channel := range test.channels {
+ err := _redis.Push(context.Background(), channel, bytes)
+ if err != nil {
+ t.Errorf("unable to push item to queue: %v", err)
+ }
+ }
+ got, err := _redis.Length(context.Background())
+
+ if err != nil {
+ t.Errorf("Length returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("Length is %v, want %v", got, test.want)
+ }
+ }
+}
diff --git a/queue/redis/opts.go b/queue/redis/opts.go
index aabcfea05..5589eeb92 100644
--- a/queue/redis/opts.go
+++ b/queue/redis/opts.go
@@ -5,6 +5,8 @@
package redis
import (
+ "encoding/base64"
+ "errors"
"fmt"
"time"
)
@@ -69,3 +71,75 @@ func WithTimeout(timeout time.Duration) ClientOpt {
return nil
}
}
+
+// WithPrivateKey sets the private key in the queue client for Redis.
+//
+//nolint:dupl // ignore similar code
+func WithPrivateKey(key string) ClientOpt {
+ return func(c *client) error {
+ c.Logger.Trace("configuring private key in redis queue client")
+
+ if len(key) == 0 {
+ c.Logger.Warn("unable to base64 decode private key, provided key is empty. queue service will be unable to sign items")
+ return nil
+ }
+
+ decoded, err := base64.StdEncoding.DecodeString(key)
+ if err != nil {
+ return err
+ }
+
+ if len(decoded) == 0 {
+ return errors.New("unable to base64 decode private key, decoded key is empty")
+ }
+
+ c.config.PrivateKey = new([64]byte)
+ copy(c.config.PrivateKey[:], decoded)
+
+ if c.config.PrivateKey == nil {
+ return errors.New("unable to copy decoded queue signing private key, copied key is nil")
+ }
+
+ if len(c.config.PrivateKey) == 0 {
+ return errors.New("unable to copy decoded queue signing private key, copied key is empty")
+ }
+
+ return nil
+ }
+}
+
+// WithPublicKey sets the public key in the queue client for Redis.
+//
+//nolint:dupl // ignore similar code
+func WithPublicKey(key string) ClientOpt {
+ return func(c *client) error {
+ c.Logger.Tracef("configuring public key in redis queue client")
+
+ if len(key) == 0 {
+ c.Logger.Warn("unable to base64 decode public key, provided key is empty. queue service will be unable to open items")
+ return nil
+ }
+
+ decoded, err := base64.StdEncoding.DecodeString(key)
+ if err != nil {
+ return err
+ }
+
+ if len(decoded) == 0 {
+ return errors.New("unable to base64 decode public key, decoded key is empty")
+ }
+
+ c.config.PublicKey = new([32]byte)
+ copy(c.config.PublicKey[:], decoded)
+
+ if c.config.PublicKey == nil {
+ return errors.New("unable to copy decoded queue signing public key, copied key is nil")
+ }
+
+ if len(c.config.PublicKey) == 0 {
+ return errors.New("unable to copy decoded queue signing public key, copied key is empty")
+ }
+
+ return nil
+ }
+}
diff --git a/queue/redis/opts_test.go b/queue/redis/opts_test.go
index 72c91c5eb..3f99e3857 100644
--- a/queue/redis/opts_test.go
+++ b/queue/redis/opts_test.go
@@ -5,6 +5,7 @@
package redis
import (
+ "encoding/base64"
"fmt"
"reflect"
"testing"
@@ -15,7 +16,6 @@ import (
func TestRedis_ClientOpt_WithAddress(t *testing.T) {
// setup tests
-
// create a local fake redis instance
//
// https://pkg.go.dev/github.com/alicebob/miniredis/v2#Run
@@ -68,7 +68,6 @@ func TestRedis_ClientOpt_WithAddress(t *testing.T) {
func TestRedis_ClientOpt_WithChannels(t *testing.T) {
// setup tests
-
// create a local fake redis instance
//
// https://pkg.go.dev/github.com/alicebob/miniredis/v2#Run
@@ -122,7 +121,6 @@ func TestRedis_ClientOpt_WithChannels(t *testing.T) {
func TestRedis_ClientOpt_WithCluster(t *testing.T) {
// setup tests
-
// create a local fake redis instance
//
// https://pkg.go.dev/github.com/alicebob/miniredis/v2#Run
@@ -183,3 +181,139 @@ func TestRedis_ClientOpt_WithCluster(t *testing.T) {
}
}
}
+
+func TestRedis_ClientOpt_WithSigningPrivateKey(t *testing.T) {
+ // setup tests
+ // create a local fake redis instance
+ //
+ // https://pkg.go.dev/github.com/alicebob/miniredis/v2#Run
+ _redis, err := miniredis.Run()
+ if err != nil {
+ t.Errorf("unable to create miniredis instance: %v", err)
+ }
+ defer _redis.Close()
+
+ tests := []struct {
+ failure bool
+ privKey string
+ want string
+ }{
+ { //valid key input
+ failure: false,
+ privKey: "tCIevHOBq6DdN5SSBtteXUusjjd0fOqzk2eyi0DMq04NewmShNKQeUbbp3vkvIckb4pCxc+vxUo+mYf/vzOaSg==",
+ want: "tCIevHOBq6DdN5SSBtteXUusjjd0fOqzk2eyi0DMq04NewmShNKQeUbbp3vkvIckb4pCxc+vxUo+mYf/vzOaSg==",
+ },
+ { //empty key input
+ failure: false,
+ privKey: "",
+ want: "",
+ },
+ { //invalid base64 encoded input
+ failure: true,
+ privKey: "abc123",
+ want: "",
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ _service, err := New(
+ WithAddress(fmt.Sprintf("redis://%s", _redis.Addr())),
+ WithPrivateKey(test.privKey),
+ )
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithPrivateKey should have returned err")
+ }
+
+ continue
+ }
+
+ if err != nil {
+ t.Errorf("WithPrivateKey returned err: %v", err)
+ }
+
+ got := ""
+ if _service.config.PrivateKey != nil {
+ got = fmt.Sprintf("%s", *_service.config.PrivateKey)
+ } else {
+ got = ""
+ }
+
+ w, _ := base64.StdEncoding.DecodeString(test.want)
+
+ want := string(w)
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("WithPrivateKey is %v, want %v", got, want)
+ }
+ }
+}
+
+func TestRedis_ClientOpt_WithSigningPublicKey(t *testing.T) {
+ // setup tests
+ // create a local fake redis instance
+ //
+ // https://pkg.go.dev/github.com/alicebob/miniredis/v2#Run
+ _redis, err := miniredis.Run()
+ if err != nil {
+ t.Errorf("unable to create miniredis instance: %v", err)
+ }
+ defer _redis.Close()
+
+ tests := []struct {
+ failure bool
+ pubKey string
+ want string
+ }{
+ { //valid key input
+ failure: false,
+ pubKey: "DXsJkoTSkHlG26d75LyHJG+KQsXPr8VKPpmH/78zmko=",
+ want: "DXsJkoTSkHlG26d75LyHJG+KQsXPr8VKPpmH/78zmko=",
+ },
+ { //empty key input
+ failure: false,
+ pubKey: "",
+ want: "",
+ },
+ { //invalid base64 encoded input
+ failure: true,
+ pubKey: "abc123",
+ want: "",
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ _service, err := New(
+ WithAddress(fmt.Sprintf("redis://%s", _redis.Addr())),
+ WithPublicKey(test.pubKey),
+ )
+
+ if test.failure {
+ if err == nil {
+ t.Errorf("WithPublicKey should have returned err")
+ }
+
+ continue
+ }
+
+ if err != nil {
+ t.Errorf("WithPublicKey returned err: %v", err)
+ }
+
+ got := ""
+ if _service.config.PublicKey != nil {
+ got = fmt.Sprintf("%s", *_service.config.PublicKey)
+ } else {
+ got = ""
+ }
+
+ w, _ := base64.StdEncoding.DecodeString(test.want)
+
+ want := string(w)
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("SigningPublicKey is %v, want %v", got, want)
+ }
+ }
+}
diff --git a/queue/redis/pop.go b/queue/redis/pop.go
index e55b3e253..732092c32 100644
--- a/queue/redis/pop.go
+++ b/queue/redis/pop.go
@@ -7,9 +7,11 @@ package redis
import (
"context"
"encoding/json"
+ "errors"
- "github.com/go-redis/redis/v8"
"github.com/go-vela/types"
+ "github.com/redis/go-redis/v9"
+ "golang.org/x/crypto/nacl/sign"
)
// Pop grabs an item from the specified channel off the queue.
@@ -26,18 +28,36 @@ func (c *client) Pop(ctx context.Context) (*types.Item, error) {
// https://pkg.go.dev/github.com/go-redis/redis?tab=doc#StringSliceCmd.Result
result, err := popCmd.Result()
if err != nil {
- switch err {
- case redis.Nil: // BLPOP timeout
+ // BLPOP timeout
+ if errors.Is(err, redis.Nil) {
return nil, nil
- default:
- return nil, err
}
+
+ return nil, err
}
- item := new(types.Item)
+ // this should already be validated on startup
+ if c.config.PublicKey == nil || len(*c.config.PublicKey) != 32 {
+ return nil, errors.New("no valid signing public key provided")
+ }
+
+ // extract signed item from pop results
+ signed := []byte(result[1])
+
+ var opened, out []byte
+
+ // open the item using the public key generated using sign
+ //
+ // https://pkg.go.dev/golang.org/x/crypto@v0.1.0/nacl/sign
+ opened, ok := sign.Open(out, signed, c.config.PublicKey)
+ if !ok {
+ return nil, errors.New("unable to open signed item")
+ }
// unmarshal result into queue item
- err = json.Unmarshal([]byte(result[1]), item)
+ item := new(types.Item)
+
+ err = json.Unmarshal(opened, item)
if err != nil {
return nil, err
}
diff --git a/queue/redis/pop_test.go b/queue/redis/pop_test.go
index cacda6557..1f19bf2c3 100644
--- a/queue/redis/pop_test.go
+++ b/queue/redis/pop_test.go
@@ -11,20 +11,22 @@ import (
"time"
"github.com/go-vela/types"
+ "golang.org/x/crypto/nacl/sign"
"gopkg.in/square/go-jose.v2/json"
)
func TestRedis_Pop(t *testing.T) {
// setup types
-
// use global variables in redis_test.go
_item := &types.Item{
- Build: _build,
- Pipeline: _steps,
- Repo: _repo,
- User: _user,
+ Build: _build,
+ Repo: _repo,
+ User: _user,
}
+ var signed []byte
+ var out []byte
+
// setup queue item
bytes, err := json.Marshal(_item)
if err != nil {
@@ -32,19 +34,21 @@ func TestRedis_Pop(t *testing.T) {
}
// setup redis mock
- _redis, err := NewTest("vela")
+ _redis, err := NewTest(_signingPrivateKey, _signingPublicKey, "vela")
if err != nil {
t.Errorf("unable to create queue service: %v", err)
}
+ signed = sign.Sign(out, bytes, _redis.config.PrivateKey)
+
// push item to queue
- err = _redis.Redis.RPush(context.Background(), "vela", bytes).Err()
+ err = _redis.Redis.RPush(context.Background(), "vela", signed).Err()
if err != nil {
t.Errorf("unable to push item to queue: %v", err)
}
// setup timeout redis mock
- timeout, err := NewTest("vela")
+ timeout, err := NewTest(_signingPrivateKey, _signingPublicKey, "vela")
if err != nil {
t.Errorf("unable to create queue service: %v", err)
}
@@ -52,27 +56,17 @@ func TestRedis_Pop(t *testing.T) {
timeout.config.Timeout = 1 * time.Second
// setup badChannel redis mock
- badChannel, err := NewTest("vela")
+ badChannel, err := NewTest(_signingPrivateKey, _signingPublicKey, "vela")
if err != nil {
t.Errorf("unable to create queue service: %v", err)
}
// overwrite channel to be invalid
badChannel.config.Channels = nil
- // push nothing to queue
- err = badChannel.Redis.RPush(context.Background(), "vela", nil).Err()
- if err != nil {
- t.Errorf("unable to push item to queue: %v", err)
- }
-
- // setup badItem redis mock
- badItem, err := NewTest("vela")
- if err != nil {
- t.Errorf("unable to create queue service: %v", err)
- }
+ signed = sign.Sign(out, bytes, badChannel.config.PrivateKey)
- // push nothing to queue
- err = badItem.Redis.RPush(context.Background(), "vela", nil).Err()
+ // push something to badChannel queue
+ err = badChannel.Redis.RPush(context.Background(), "vela", signed).Err()
if err != nil {
t.Errorf("unable to push item to queue: %v", err)
}
@@ -98,11 +92,6 @@ func TestRedis_Pop(t *testing.T) {
redis: badChannel,
want: nil,
},
- {
- failure: true,
- redis: badItem,
- want: nil,
- },
}
// run tests
diff --git a/queue/redis/push.go b/queue/redis/push.go
index 620e1174a..66c8a386a 100644
--- a/queue/redis/push.go
+++ b/queue/redis/push.go
@@ -6,16 +6,43 @@ package redis
import (
"context"
+ "errors"
+
+ "golang.org/x/crypto/nacl/sign"
)
// Push inserts an item to the specified channel in the queue.
func (c *client) Push(ctx context.Context, channel string, item []byte) error {
c.Logger.Tracef("pushing item to queue %s", channel)
+ // ensure the item to be pushed is valid
+ // go-redis RPush does not support nil as of v9.0.2
+ //
+ // https://github.com/redis/go-redis/pull/1960
+ if item == nil {
+ return errors.New("item is nil")
+ }
+
+ var signed []byte
+
+ var out []byte
+
+ // this should already be validated on startup
+ if c.config.PrivateKey == nil || len(*c.config.PrivateKey) != 64 {
+ return errors.New("no valid signing private key provided")
+ }
+
+ c.Logger.Tracef("signing item for queue %s", channel)
+
+ // sign the item using the private key generated using sign
+ //
+ // https://pkg.go.dev/golang.org/x/crypto@v0.1.0/nacl/sign
+ signed = sign.Sign(out, item, c.config.PrivateKey)
+
// build a redis queue command to push an item to queue
//
// https://pkg.go.dev/github.com/go-redis/redis?tab=doc#Client.RPush
- pushCmd := c.Redis.RPush(ctx, channel, item)
+ pushCmd := c.Redis.RPush(ctx, channel, signed)
// blocking call to push an item to queue and return err
//
diff --git a/queue/redis/push_test.go b/queue/redis/push_test.go
index 97ed47a7a..e61792ea9 100644
--- a/queue/redis/push_test.go
+++ b/queue/redis/push_test.go
@@ -14,23 +14,27 @@ import (
func TestRedis_Push(t *testing.T) {
// setup types
-
// use global variables in redis_test.go
_item := &types.Item{
- Build: _build,
- Pipeline: _steps,
- Repo: _repo,
- User: _user,
+ Build: _build,
+ Repo: _repo,
+ User: _user,
}
// setup queue item
- bytes, err := json.Marshal(_item)
+ _bytes, err := json.Marshal(_item)
if err != nil {
t.Errorf("unable to marshal queue item: %v", err)
}
// setup redis mock
- _redis, err := NewTest("vela")
+ _redis, err := NewTest(_signingPrivateKey, _signingPublicKey, "vela")
+ if err != nil {
+ t.Errorf("unable to create queue service: %v", err)
+ }
+
+ // setup redis mock
+ badItem, err := NewTest(_signingPrivateKey, _signingPublicKey, "vela")
if err != nil {
t.Errorf("unable to create queue service: %v", err)
}
@@ -39,16 +43,23 @@ func TestRedis_Push(t *testing.T) {
tests := []struct {
failure bool
redis *client
+ bytes []byte
}{
{
failure: false,
redis: _redis,
+ bytes: _bytes,
+ },
+ {
+ failure: true,
+ redis: badItem,
+ bytes: nil,
},
}
// run tests
for _, test := range tests {
- err := _redis.Push(context.Background(), "vela", bytes)
+ err := test.redis.Push(context.Background(), "vela", test.bytes)
if test.failure {
if err == nil {
diff --git a/queue/redis/redis.go b/queue/redis/redis.go
index 1d2c1467b..4e4f68aea 100644
--- a/queue/redis/redis.go
+++ b/queue/redis/redis.go
@@ -12,7 +12,7 @@ import (
"time"
"github.com/alicebob/miniredis/v2"
- "github.com/go-redis/redis/v8"
+ "github.com/redis/go-redis/v9"
"github.com/sirupsen/logrus"
)
@@ -25,6 +25,10 @@ type config struct {
Cluster bool
// specifies the timeout to use for the Redis client
Timeout time.Duration
+ // key for signing items pushed to the Redis client
+ PrivateKey *[64]byte
+ // key for opening items popped from the Redis client
+ PublicKey *[32]byte
}
type client struct {
@@ -38,7 +42,7 @@ type client struct {
// New returns a Queue implementation that
// integrates with a Redis queue instance.
//
-// nolint: revive // ignore returning unexported client
+//nolint:revive // ignore returning unexported client
func New(opts ...ClientOpt) (*client, error) {
// create new Redis client
c := new(client)
@@ -97,22 +101,22 @@ func New(opts ...ClientOpt) (*client, error) {
// the failover options from the parse options.
func failoverFromOptions(source *redis.Options) *redis.FailoverOptions {
target := &redis.FailoverOptions{
- OnConnect: source.OnConnect,
- Password: source.Password,
- DB: source.DB,
- MaxRetries: source.MaxRetries,
- MinRetryBackoff: source.MinRetryBackoff,
- MaxRetryBackoff: source.MaxRetryBackoff,
- DialTimeout: source.DialTimeout,
- ReadTimeout: source.ReadTimeout,
- WriteTimeout: source.WriteTimeout,
- PoolSize: source.PoolSize,
- MinIdleConns: source.MinIdleConns,
- MaxConnAge: source.MaxConnAge,
- PoolTimeout: source.PoolTimeout,
- IdleTimeout: source.IdleTimeout,
- IdleCheckFrequency: source.IdleCheckFrequency,
- TLSConfig: source.TLSConfig,
+ OnConnect: source.OnConnect,
+ Password: source.Password,
+ DB: source.DB,
+ MaxRetries: source.MaxRetries,
+ MinRetryBackoff: source.MinRetryBackoff,
+ MaxRetryBackoff: source.MaxRetryBackoff,
+ DialTimeout: source.DialTimeout,
+ ReadTimeout: source.ReadTimeout,
+ WriteTimeout: source.WriteTimeout,
+ PoolSize: source.PoolSize,
+ MinIdleConns: source.MinIdleConns,
+ MaxIdleConns: source.MaxIdleConns,
+ ConnMaxLifetime: source.ConnMaxLifetime,
+ PoolTimeout: source.PoolTimeout,
+ ConnMaxIdleTime: source.ConnMaxIdleTime,
+ TLSConfig: source.TLSConfig,
}
// trim auto appended :6379 from address
@@ -172,8 +176,8 @@ func pingQueue(c *client) error {
//
// This function is intended for running tests only.
//
-// nolint: revive // ignore returning unexported client
-func NewTest(channels ...string) (*client, error) {
+//nolint:revive // ignore returning unexported client
+func NewTest(signingPrivateKey, signingPublicKey string, channels ...string) (*client, error) {
// create a local fake redis instance
//
// https://pkg.go.dev/github.com/alicebob/miniredis/v2#Run
@@ -186,5 +190,7 @@ func NewTest(channels ...string) (*client, error) {
WithAddress(fmt.Sprintf("redis://%s", _redis.Addr())),
WithChannels(channels...),
WithCluster(false),
+ WithPrivateKey(signingPrivateKey),
+ WithPublicKey(signingPublicKey),
)
}
diff --git a/queue/redis/redis_test.go b/queue/redis/redis_test.go
index 1e607b856..6935a676d 100644
--- a/queue/redis/redis_test.go
+++ b/queue/redis/redis_test.go
@@ -15,7 +15,7 @@ import (
)
// The following functions were taken from
-// https://github.com/go-vela/sdk-go/blob/master/vela/go
+// https://github.com/go-vela/sdk-go/blob/main/vela/go
// which is the only reason go-vela/sdk-go is
// a dependency for go-vela/server
// TODO: consider moving to go-vela/types?
@@ -46,7 +46,9 @@ func Strings(v []string) *[]string { return &v }
// setup global variables used for testing.
var (
- _build = &library.Build{
+ _signingPrivateKey = "tCIevHOBq6DdN5SSBtteXUusjjd0fOqzk2eyi0DMq04NewmShNKQeUbbp3vkvIckb4pCxc+vxUo+mYf/vzOaSg=="
+ _signingPublicKey = "DXsJkoTSkHlG26d75LyHJG+KQsXPr8VKPpmH/78zmko="
+ _build = &library.Build{
ID: Int64(1),
Number: Int(1),
Parent: Int(1),
@@ -121,7 +123,7 @@ var (
ID: "step_github_octocat_1_clone",
Directory: "/home/github/octocat",
Environment: map[string]string{"FOO": "bar"},
- Image: "target/vela-git:v0.3.0",
+ Image: "target/vela-git:v0.5.1",
Name: "clone",
Number: 2,
Pull: "always",
@@ -151,7 +153,6 @@ var (
func TestRedis_New(t *testing.T) {
// setup types
-
// create a local fake redis instance
//
// https://pkg.go.dev/github.com/alicebob/miniredis/v2#Run
diff --git a/queue/redis/route.go b/queue/redis/route.go
index fa1a3b4f3..1da4b19db 100644
--- a/queue/redis/route.go
+++ b/queue/redis/route.go
@@ -22,7 +22,7 @@ func (c *client) Route(w *pipeline.Worker) (string, error) {
// if pipline does not specify route information return default
//
- // https://github.com/go-vela/types/blob/master/constants/queue.go#L10
+ // https://github.com/go-vela/types/blob/main/constants/queue.go#L10
if w.Empty() {
return constants.DefaultRoute, nil
}
@@ -37,5 +37,13 @@ func (c *client) Route(w *pipeline.Worker) (string, error) {
buf.WriteString(fmt.Sprintf(":%s", w.Platform))
}
- return strings.TrimLeft(buf.String(), ":"), nil
+ route := strings.TrimLeft(buf.String(), ":")
+
+ for _, r := range c.config.Channels {
+ if strings.EqualFold(route, r) {
+ return route, nil
+ }
+ }
+
+ return "", fmt.Errorf("invalid route %s provided", route)
}
diff --git a/queue/redis/route_test.go b/queue/redis/route_test.go
index 9956ca02d..23328df43 100644
--- a/queue/redis/route_test.go
+++ b/queue/redis/route_test.go
@@ -14,32 +14,48 @@ import (
func TestRedis_Client_Route(t *testing.T) {
// setup
- client, _ := NewTest("vela")
+ client, _ := NewTest(_signingPrivateKey, _signingPublicKey, "vela", "16cpu8gb", "16cpu8gb:gcp", "gcp")
tests := []struct {
- want string
- worker pipeline.Worker
+ success bool
+ want string
+ worker pipeline.Worker
}{
// pipeline with not worker passed
{
- want: constants.DefaultRoute,
- worker: pipeline.Worker{},
+ success: true,
+ want: constants.DefaultRoute,
+ worker: pipeline.Worker{},
},
{
- want: "vela",
- worker: pipeline.Worker{},
+ success: true,
+ want: "vela",
+ worker: pipeline.Worker{},
},
{
- want: "16cpu8gb",
- worker: pipeline.Worker{Flavor: "16cpu8gb"},
+ success: true,
+ want: "16cpu8gb",
+ worker: pipeline.Worker{Flavor: "16cpu8gb"},
},
{
- want: "16cpu8gb:gcp",
- worker: pipeline.Worker{Flavor: "16cpu8gb", Platform: "gcp"},
+ success: true,
+ want: "16cpu8gb:gcp",
+ worker: pipeline.Worker{Flavor: "16cpu8gb", Platform: "gcp"},
},
{
- want: "gcp",
- worker: pipeline.Worker{Platform: "gcp"},
+ success: true,
+ want: "gcp",
+ worker: pipeline.Worker{Platform: "gcp"},
+ },
+ {
+ success: false,
+ want: "",
+ worker: pipeline.Worker{Flavor: "bad", Platform: "route"},
+ },
+ {
+ success: false,
+ want: "",
+ worker: pipeline.Worker{Flavor: "bad"},
},
}
@@ -47,10 +63,14 @@ func TestRedis_Client_Route(t *testing.T) {
for _, test := range tests {
got, err := client.Route(&test.worker)
- if err != nil {
+ if test.success && err != nil {
t.Errorf("Route returned err: %v", err)
}
+ if !test.success && err == nil {
+ t.Errorf("Route returned %s, want err", got)
+ }
+
if !strings.EqualFold(got, test.want) {
t.Errorf("Route is %v, want %v", got, test.want)
}
diff --git a/queue/service.go b/queue/service.go
index 3974b76a3..418b3a919 100644
--- a/queue/service.go
+++ b/queue/service.go
@@ -20,6 +20,10 @@ type Service interface {
// the configured queue driver.
Driver() string
+ // Length defines a function that outputs
+ // the length of a queue channel
+ Length(context.Context) (int64, error)
+
// Pop defines a function that grabs an
// item off the queue.
Pop(context.Context) (*types.Item, error)
diff --git a/queue/setup.go b/queue/setup.go
index ae69a1429..ed0f131c8 100644
--- a/queue/setup.go
+++ b/queue/setup.go
@@ -30,6 +30,10 @@ type Setup struct {
Routes []string
// specifies the timeout for pop requests for the queue client
Timeout time.Duration
+ // private key in base64 used for signing items pushed to the queue
+ PrivateKey string
+ // public key in base64 used for opening items popped from the queue
+ PublicKey string
}
// Redis creates and returns a Vela service capable
@@ -45,6 +49,8 @@ func (s *Setup) Redis() (Service, error) {
redis.WithChannels(s.Routes...),
redis.WithCluster(s.Cluster),
redis.WithTimeout(s.Timeout),
+ redis.WithPrivateKey(s.PrivateKey),
+ redis.WithPublicKey(s.PublicKey),
)
}
diff --git a/queue/setup_test.go b/queue/setup_test.go
index 317753b91..4b2e35ede 100644
--- a/queue/setup_test.go
+++ b/queue/setup_test.go
@@ -13,7 +13,6 @@ import (
func TestQueue_Setup_Redis(t *testing.T) {
// setup types
-
// create a local fake redis instance
//
// https://pkg.go.dev/github.com/alicebob/miniredis/v2#Run
diff --git a/random/random.go b/random/random.go
index 96f324cd3..0a5953ed0 100644
--- a/random/random.go
+++ b/random/random.go
@@ -1,3 +1,7 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
package random
import (
diff --git a/router/admin.go b/router/admin.go
index f8c259795..96764617f 100644
--- a/router/admin.go
+++ b/router/admin.go
@@ -13,58 +13,52 @@ import (
// AdminHandlers is a function that extends the provided base router group
// with the API handlers for admin functionality.
//
-// GET /api/v1/admin/builds
// GET /api/v1/admin/builds/queue
+// GET /api/v1/admin/build/:id
// PUT /api/v1/admin/build
-// GET /api/v1/admin/deployments
+// PUT /api/v1/admin/clean
// PUT /api/v1/admin/deployment
-// GET /api/v1/admin/hooks
// PUT /api/v1/admin/hook
-// GET /api/v1/admin/repos
// PUT /api/v1/admin/repo
-// GET /api/v1/admin/secrets
// PUT /api/v1/admin/secret
-// GET /api/v1/admin/services
// PUT /api/v1/admin/service
-// GET /api/v1/admin/steps
// PUT /api/v1/admin/step
-// GET /api/v1/admin/users
// PUT /api/v1/admin/user.
func AdminHandlers(base *gin.RouterGroup) {
// Admin endpoints
_admin := base.Group("/admin", perm.MustPlatformAdmin())
{
- // Admin build endpoints
- _admin.GET("/builds", admin.AllBuilds)
+ // Admin build queue endpoint
_admin.GET("/builds/queue", admin.AllBuildsQueue)
+
+ // Admin build endpoint
_admin.PUT("/build", admin.UpdateBuild)
- // Admin deployment endpoints
- _admin.GET("/deployments", admin.AllDeployments)
+ // Admin clean endpoint
+ _admin.PUT("/clean", admin.CleanResources)
+
+ // Admin deployment endpoint
_admin.PUT("/deployment", admin.UpdateDeployment)
- // Admin hook endpoints
- _admin.GET("/hooks", admin.AllHooks)
+ // Admin hook endpoint
_admin.PUT("/hook", admin.UpdateHook)
- // Admin repo endpoints
- _admin.GET("/repos", admin.AllRepos)
+ // Admin repo endpoint
_admin.PUT("/repo", admin.UpdateRepo)
- // Admin secret endpoints
- _admin.GET("/secrets", admin.AllSecrets)
+ // Admin secret endpoint
_admin.PUT("/secret", admin.UpdateSecret)
- // Admin service endpoints
- _admin.GET("/services", admin.AllServices)
+ // Admin service endpoint
_admin.PUT("/service", admin.UpdateService)
- // Admin step endpoints
- _admin.GET("/steps", admin.AllSteps)
+ // Admin step endpoint
_admin.PUT("/step", admin.UpdateStep)
- // Admin user endpoints
- _admin.GET("/users", admin.AllUsers)
+ // Admin user endpoint
_admin.PUT("/user", admin.UpdateUser)
+
+ // Admin worker endpoint
+ _admin.POST("/workers/:worker/register-token", admin.RegisterToken)
} // end of admin endpoints
}
diff --git a/router/build.go b/router/build.go
index 153f61ad5..c8cc0d688 100644
--- a/router/build.go
+++ b/router/build.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
@@ -6,9 +6,10 @@ package router
import (
"github.com/gin-gonic/gin"
- "github.com/go-vela/server/api"
+ "github.com/go-vela/server/api/build"
+ "github.com/go-vela/server/api/log"
"github.com/go-vela/server/router/middleware"
- "github.com/go-vela/server/router/middleware/build"
+ bmiddleware "github.com/go-vela/server/router/middleware/build"
"github.com/go-vela/server/router/middleware/executors"
"github.com/go-vela/server/router/middleware/perm"
)
@@ -24,6 +25,8 @@ import (
// DELETE /api/v1/repos/:org/:repo/builds/:build
// DELETE /api/v1/repos/:org/:repo/builds/:build/cancel
// GET /api/v1/repos/:org/:repo/builds/:build/logs
+// GET /api/v1/repos/:org/:repo/builds/:build/token
+// GET /api/v1/repos/:org/:repo/builds/:build/executable
// POST /api/v1/repos/:org/:repo/builds/:build/services
// GET /api/v1/repos/:org/:repo/builds/:build/services
// GET /api/v1/repos/:org/:repo/builds/:build/services/:service
@@ -46,26 +49,28 @@ func BuildHandlers(base *gin.RouterGroup) {
// Builds endpoints
builds := base.Group("/builds")
{
- builds.POST("", perm.MustAdmin(), middleware.Payload(), api.CreateBuild)
- builds.GET("", perm.MustRead(), api.GetBuilds)
+ builds.POST("", perm.MustAdmin(), middleware.Payload(), build.CreateBuild)
+ builds.GET("", perm.MustRead(), build.ListBuildsForRepo)
// Build endpoints
- build := builds.Group("/:build", build.Establish())
+ b := builds.Group("/:build", bmiddleware.Establish())
{
- build.POST("", perm.MustWrite(), api.RestartBuild)
- build.GET("", perm.MustRead(), api.GetBuild)
- build.PUT("", perm.MustWrite(), middleware.Payload(), api.UpdateBuild)
- build.DELETE("", perm.MustPlatformAdmin(), api.DeleteBuild)
- build.DELETE("/cancel", executors.Establish(), perm.MustWrite(), api.CancelBuild)
- build.GET("/logs", perm.MustRead(), api.GetBuildLogs)
+ b.POST("", perm.MustWrite(), build.RestartBuild)
+ b.GET("", perm.MustRead(), build.GetBuild)
+ b.PUT("", perm.MustBuildAccess(), middleware.Payload(), build.UpdateBuild)
+ b.DELETE("", perm.MustPlatformAdmin(), build.DeleteBuild)
+ b.DELETE("/cancel", executors.Establish(), perm.MustWrite(), build.CancelBuild)
+ b.GET("/logs", perm.MustRead(), log.ListLogsForBuild)
+ b.GET("/token", perm.MustWorkerAuthToken(), build.GetBuildToken)
+ b.GET("/executable", perm.MustBuildAccess(), build.GetBuildExecutable)
// Service endpoints
// * Log endpoints
- ServiceHandlers(build)
+ ServiceHandlers(b)
// Step endpoints
// * Log endpoints
- StepHandlers(build)
+ StepHandlers(b)
} // end of build endpoints
} // end of builds endpoints
}
diff --git a/router/deployment.go b/router/deployment.go
index 9a5b15e27..eabbc48c6 100644
--- a/router/deployment.go
+++ b/router/deployment.go
@@ -8,7 +8,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/go-vela/server/router/middleware/org"
- "github.com/go-vela/server/api"
+ "github.com/go-vela/server/api/deployment"
"github.com/go-vela/server/router/middleware/perm"
"github.com/go-vela/server/router/middleware/repo"
)
@@ -23,8 +23,8 @@ func DeploymentHandlers(base *gin.RouterGroup) {
// Deployments endpoints
deployments := base.Group("/deployments/:org/:repo", org.Establish(), repo.Establish())
{
- deployments.POST("", perm.MustWrite(), api.CreateDeployment)
- deployments.GET("", perm.MustRead(), api.GetDeployments)
- deployments.GET("/:deployment", perm.MustRead(), api.GetDeployment)
+ deployments.POST("", perm.MustWrite(), deployment.CreateDeployment)
+ deployments.GET("", perm.MustRead(), deployment.ListDeployments)
+ deployments.GET("/:deployment", perm.MustRead(), deployment.GetDeployment)
} // end of deployments endpoints
}
diff --git a/router/doc.go b/router/doc.go
index 41ce9fb80..17052a2b5 100644
--- a/router/doc.go
+++ b/router/doc.go
@@ -7,5 +7,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/router"
+// import "github.com/go-vela/server/router"
package router
diff --git a/router/hook.go b/router/hook.go
index 97c7d53f3..f5e1aa1db 100644
--- a/router/hook.go
+++ b/router/hook.go
@@ -6,7 +6,7 @@ package router
import (
"github.com/gin-gonic/gin"
- "github.com/go-vela/server/api"
+ "github.com/go-vela/server/api/hook"
"github.com/go-vela/server/router/middleware/org"
"github.com/go-vela/server/router/middleware/perm"
"github.com/go-vela/server/router/middleware/repo"
@@ -19,15 +19,17 @@ import (
// GET /api/v1/hooks/:org/:repo
// GET /api/v1/hooks/:org/:repo/:hook
// PUT /api/v1/hooks/:org/:repo/:hook
-// DELETE /api/v1/hooks/:org/:repo/:hook .
+// DELETE /api/v1/hooks/:org/:repo/:hook
+// POST /api/v1/hooks/:org/:repo/:hook/redeliver .
func HookHandlers(base *gin.RouterGroup) {
// Hooks endpoints
- hooks := base.Group("/hooks/:org/:repo", org.Establish(), repo.Establish())
+ _hooks := base.Group("/hooks/:org/:repo", org.Establish(), repo.Establish())
{
- hooks.POST("", perm.MustPlatformAdmin(), api.CreateHook)
- hooks.GET("", perm.MustRead(), api.GetHooks)
- hooks.GET("/:hook", perm.MustRead(), api.GetHook)
- hooks.PUT("/:hook", perm.MustPlatformAdmin(), api.UpdateHook)
- hooks.DELETE("/:hook", perm.MustPlatformAdmin(), api.DeleteHook)
+ _hooks.POST("", perm.MustPlatformAdmin(), hook.CreateHook)
+ _hooks.GET("", perm.MustRead(), hook.ListHooks)
+ _hooks.GET("/:hook", perm.MustRead(), hook.GetHook)
+ _hooks.PUT("/:hook", perm.MustPlatformAdmin(), hook.UpdateHook)
+ _hooks.DELETE("/:hook", perm.MustPlatformAdmin(), hook.DeleteHook)
+ _hooks.POST("/:hook/redeliver", perm.MustWrite(), hook.RedeliverHook)
} // end of hooks endpoints
}
diff --git a/router/log.go b/router/log.go
index d7e8dc8db..71c6674c8 100644
--- a/router/log.go
+++ b/router/log.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
@@ -6,7 +6,7 @@ package router
import (
"github.com/gin-gonic/gin"
- "github.com/go-vela/server/api"
+ "github.com/go-vela/server/api/log"
"github.com/go-vela/server/router/middleware/perm"
)
@@ -21,10 +21,10 @@ func LogServiceHandlers(base *gin.RouterGroup) {
// Logs endpoints
logs := base.Group("/logs")
{
- logs.POST("", perm.MustAdmin(), api.CreateServiceLog)
- logs.GET("", perm.MustRead(), api.GetServiceLog)
- logs.PUT("", perm.MustWrite(), api.UpdateServiceLog)
- logs.DELETE("", perm.MustPlatformAdmin(), api.DeleteServiceLog)
+ logs.POST("", perm.MustAdmin(), log.CreateServiceLog)
+ logs.GET("", perm.MustRead(), log.GetServiceLog)
+ logs.PUT("", perm.MustBuildAccess(), log.UpdateServiceLog)
+ logs.DELETE("", perm.MustPlatformAdmin(), log.DeleteServiceLog)
} // end of logs endpoints
}
@@ -39,9 +39,9 @@ func LogStepHandlers(base *gin.RouterGroup) {
// Logs endpoints
logs := base.Group("/logs")
{
- logs.POST("", perm.MustAdmin(), api.CreateStepLog)
- logs.GET("", perm.MustRead(), api.GetStepLog)
- logs.PUT("", perm.MustWrite(), api.UpdateStepLog)
- logs.DELETE("", perm.MustPlatformAdmin(), api.DeleteStepLog)
+ logs.POST("", perm.MustAdmin(), log.CreateStepLog)
+ logs.GET("", perm.MustRead(), log.GetStepLog)
+ logs.PUT("", perm.MustBuildAccess(), log.UpdateStepLog)
+ logs.DELETE("", perm.MustPlatformAdmin(), log.DeleteStepLog)
} // end of logs endpoints
}
diff --git a/router/middleware/allowlist_schedule.go b/router/middleware/allowlist_schedule.go
new file mode 100644
index 000000000..8872d0361
--- /dev/null
+++ b/router/middleware/allowlist_schedule.go
@@ -0,0 +1,18 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package middleware
+
+import (
+ "github.com/gin-gonic/gin"
+)
+
+// AllowlistSchedule is a middleware function that attaches the allowlistschedule used
+// to limit which repos can utilize the schedule feature within the system.
+func AllowlistSchedule(allowlistschedule []string) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ c.Set("allowlistschedule", allowlistschedule)
+ c.Next()
+ }
+}
diff --git a/router/middleware/allowlist_schedule_test.go b/router/middleware/allowlist_schedule_test.go
new file mode 100644
index 000000000..a9b03ae28
--- /dev/null
+++ b/router/middleware/allowlist_schedule_test.go
@@ -0,0 +1,46 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package middleware
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "testing"
+
+ "github.com/gin-gonic/gin"
+)
+
+func TestMiddleware_AllowlistSchedule(t *testing.T) {
+ // setup types
+ got := []string{""}
+ want := []string{"foobar"}
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+ context.Request, _ = http.NewRequest(http.MethodGet, "/health", nil)
+
+ // setup mock server
+ engine.Use(AllowlistSchedule(want))
+ engine.GET("/health", func(c *gin.Context) {
+ got = c.Value("allowlistschedule").([]string)
+
+ c.Status(http.StatusOK)
+ })
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("AllowlistSchedule returned %v, want %v", resp.Code, http.StatusOK)
+ }
+
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("AllowlistSchedule is %v, want %v", got, want)
+ }
+}
diff --git a/router/middleware/auth/auth.go b/router/middleware/auth/auth.go
new file mode 100644
index 000000000..968ceabfb
--- /dev/null
+++ b/router/middleware/auth/auth.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 auth
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/go-vela/types/constants"
+
+ "github.com/golang-jwt/jwt/v5/request"
+)
+
+// RetrieveAccessToken gets the passed in access token from the header in the request.
+func RetrieveAccessToken(r *http.Request) (accessToken string, err error) {
+ return request.AuthorizationHeaderExtractor.ExtractToken(r)
+}
+
+// RetrieveRefreshToken gets the refresh token sent along with the request as a cookie.
+func RetrieveRefreshToken(r *http.Request) (string, error) {
+ refreshToken, err := r.Cookie(constants.RefreshTokenName)
+
+ if refreshToken == nil || len(refreshToken.Value) == 0 {
+ // cookie will not be sent if it has expired
+ return "", fmt.Errorf("refresh token expired or not provided")
+ }
+
+ return refreshToken.Value, err
+}
diff --git a/router/middleware/auth/auth_test.go b/router/middleware/auth/auth_test.go
new file mode 100644
index 000000000..850309c04
--- /dev/null
+++ b/router/middleware/auth/auth_test.go
@@ -0,0 +1,85 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package auth
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+ "testing"
+
+ "github.com/go-vela/types/constants"
+)
+
+func TestToken_Retrieve_Refresh(t *testing.T) {
+ // setup types
+ want := "fresh"
+
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", nil)
+ request.AddCookie(&http.Cookie{
+ Name: constants.RefreshTokenName,
+ Value: want,
+ })
+
+ // run test
+ got, err := RetrieveRefreshToken(request)
+ if err != nil {
+ t.Errorf("Retrieve returned err: %v", err)
+ }
+
+ if !strings.EqualFold(got, want) {
+ t.Errorf("Retrieve is %v, want %v", got, want)
+ }
+}
+
+func TestToken_Retrieve_Access(t *testing.T) {
+ // setup types
+ want := "foobar"
+
+ header := fmt.Sprintf("Bearer %s", want)
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", nil)
+ request.Header.Set("Authorization", header)
+
+ // run test
+ got, err := RetrieveAccessToken(request)
+ if err != nil {
+ t.Errorf("Retrieve returned err: %v", err)
+ }
+
+ if !strings.EqualFold(got, want) {
+ t.Errorf("Retrieve is %v, want %v", got, want)
+ }
+}
+
+func TestToken_Retrieve_Access_Error(t *testing.T) {
+ // setup types
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", nil)
+
+ // run test
+ got, err := RetrieveAccessToken(request)
+ if err == nil {
+ t.Errorf("Retrieve should have returned err")
+ }
+
+ if len(got) > 0 {
+ t.Errorf("Retrieve is %v, want \"\"", got)
+ }
+}
+
+func TestToken_Retrieve_Refresh_Error(t *testing.T) {
+ // setup types
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", nil)
+
+ // run test
+ got, err := RetrieveRefreshToken(request)
+ if err == nil {
+ t.Errorf("Retrieve should have returned err")
+ }
+
+ if len(got) > 0 {
+ t.Errorf("Retrieve is %v, want \"\"", got)
+ }
+}
diff --git a/router/middleware/token/doc.go b/router/middleware/auth/doc.go
similarity index 80%
rename from router/middleware/token/doc.go
rename to router/middleware/auth/doc.go
index e673c8f5e..e50ae3a1a 100644
--- a/router/middleware/token/doc.go
+++ b/router/middleware/auth/doc.go
@@ -8,5 +8,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/router/middleware/token"
-package token
+// import "github.com/go-vela/server/router/middleware/auth"
+package auth
diff --git a/router/middleware/build/build.go b/router/middleware/build/build.go
index ff4bcd4ca..3b0b4f0d8 100644
--- a/router/middleware/build/build.go
+++ b/router/middleware/build/build.go
@@ -9,15 +9,13 @@ import (
"net/http"
"strconv"
- "github.com/go-vela/server/router/middleware/org"
- "github.com/go-vela/server/router/middleware/user"
-
+ "github.com/gin-gonic/gin"
"github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
"github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
"github.com/go-vela/server/util"
"github.com/go-vela/types/library"
-
- "github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
@@ -32,17 +30,20 @@ func Establish() gin.HandlerFunc {
o := org.Retrieve(c)
r := repo.Retrieve(c)
u := user.Retrieve(c)
+ ctx := c.Request.Context()
if r == nil {
- retErr := fmt.Errorf("repo %s/%s not found", c.Param("org"), c.Param("repo"))
+ retErr := fmt.Errorf("repo %s/%s not found", util.PathParameter(c, "org"), util.PathParameter(c, "repo"))
util.HandleError(c, http.StatusNotFound, retErr)
+
return
}
- bParam := c.Param("build")
+ bParam := util.PathParameter(c, "build")
if len(bParam) == 0 {
retErr := fmt.Errorf("no build parameter provided")
util.HandleError(c, http.StatusBadRequest, retErr)
+
return
}
@@ -50,6 +51,7 @@ func Establish() gin.HandlerFunc {
if err != nil {
retErr := fmt.Errorf("invalid build parameter provided: %s", bParam)
util.HandleError(c, http.StatusBadRequest, retErr)
+
return
}
@@ -63,10 +65,11 @@ func Establish() gin.HandlerFunc {
"user": u.GetName(),
}).Debugf("reading build %s/%d", r.GetFullName(), number)
- b, err := database.FromContext(c).GetBuild(number, r)
+ b, err := database.FromContext(c).GetBuildForRepo(ctx, r, number)
if err != nil {
- retErr := fmt.Errorf("unable to read build %s/%d: %v", r.GetFullName(), number, err)
+ retErr := fmt.Errorf("unable to read build %s/%d: %w", r.GetFullName(), number, err)
util.HandleError(c, http.StatusNotFound, retErr)
+
return
}
diff --git a/router/middleware/build/build_test.go b/router/middleware/build/build_test.go
index 2940c17d7..40e52f52f 100644
--- a/router/middleware/build/build_test.go
+++ b/router/middleware/build/build_test.go
@@ -5,16 +5,15 @@
package build
import (
+ "context"
"net/http"
"net/http/httptest"
"reflect"
"testing"
- "github.com/go-vela/server/router/middleware/org"
-
"github.com/gin-gonic/gin"
"github.com/go-vela/server/database"
- "github.com/go-vela/server/database/sqlite"
+ "github.com/go-vela/server/router/middleware/org"
"github.com/go-vela/server/router/middleware/repo"
"github.com/go-vela/types/library"
)
@@ -52,9 +51,11 @@ func TestBuild_Establish(t *testing.T) {
want := new(library.Build)
want.SetID(1)
want.SetRepoID(1)
+ want.SetPipelineID(0)
want.SetNumber(1)
want.SetParent(1)
want.SetEvent("")
+ want.SetEventAction("")
want.SetStatus("")
want.SetError("")
want.SetEnqueued(0)
@@ -83,17 +84,19 @@ func TestBuild_Establish(t *testing.T) {
got := new(library.Build)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from builds;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteBuild(context.TODO(), want)
+ db.DeleteRepo(context.TODO(), r)
+ db.Close()
}()
- _ = db.CreateRepo(r)
- _ = db.CreateBuild(want)
+ _, _ = db.CreateRepo(context.TODO(), r)
+ _, _ = db.CreateBuild(context.TODO(), want)
// setup context
gin.SetMode(gin.TestMode)
@@ -127,8 +130,11 @@ func TestBuild_Establish(t *testing.T) {
func TestBuild_Establish_NoRepo(t *testing.T) {
// setup database
- db, _ := sqlite.NewTest()
- defer func() { _sql, _ := db.Sqlite.DB(); _sql.Close() }()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
// setup context
gin.SetMode(gin.TestMode)
@@ -161,15 +167,17 @@ func TestBuild_Establish_NoBuildParameter(t *testing.T) {
r.SetVisibility("public")
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(context.TODO(), r)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(context.TODO(), r)
// setup context
gin.SetMode(gin.TestMode)
@@ -207,15 +215,17 @@ func TestBuild_Establish_InvalidBuildParameter(t *testing.T) {
r.SetVisibility("public")
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(context.TODO(), r)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(context.TODO(), r)
// setup context
gin.SetMode(gin.TestMode)
@@ -253,15 +263,17 @@ func TestBuild_Establish_NoBuild(t *testing.T) {
r.SetVisibility("public")
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(context.TODO(), r)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(context.TODO(), r)
// setup context
gin.SetMode(gin.TestMode)
diff --git a/router/middleware/build/doc.go b/router/middleware/build/doc.go
index 662549772..bbe90cf70 100644
--- a/router/middleware/build/doc.go
+++ b/router/middleware/build/doc.go
@@ -8,5 +8,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/router/middleware/build"
+// import "github.com/go-vela/server/router/middleware/build"
package build
diff --git a/router/middleware/claims/claims.go b/router/middleware/claims/claims.go
new file mode 100644
index 000000000..5ba2784f6
--- /dev/null
+++ b/router/middleware/claims/claims.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 claims
+
+import (
+ "net/http"
+ "strings"
+
+ "github.com/go-vela/server/internal/token"
+ "github.com/go-vela/server/router/middleware/auth"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
+
+ "github.com/gin-gonic/gin"
+)
+
+// Retrieve gets the claims in the given context.
+func Retrieve(c *gin.Context) *token.Claims {
+ return FromContext(c)
+}
+
+// Establish sets the claims in the given context.
+func Establish() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ tm := c.MustGet("token-manager").(*token.Manager)
+ // get the access token from the request
+ at, err := auth.RetrieveAccessToken(c.Request)
+ if err != nil {
+ util.HandleError(c, http.StatusUnauthorized, err)
+ return
+ }
+
+ claims := new(token.Claims)
+
+ // special handling for workers if symmetric token is provided
+ if secret, ok := c.Value("secret").(string); ok {
+ if strings.EqualFold(at, secret) {
+ claims.Subject = "vela-worker"
+ claims.TokenType = constants.ServerWorkerTokenType
+ ToContext(c, claims)
+ c.Next()
+
+ return
+ }
+ }
+
+ // parse and validate the token and return the associated the user
+ claims, err = tm.ParseToken(at)
+ if err != nil {
+ util.HandleError(c, http.StatusUnauthorized, err)
+ return
+ }
+
+ ToContext(c, claims)
+ c.Next()
+ }
+}
diff --git a/router/middleware/claims/claims_test.go b/router/middleware/claims/claims_test.go
new file mode 100644
index 000000000..da28eb319
--- /dev/null
+++ b/router/middleware/claims/claims_test.go
@@ -0,0 +1,304 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package claims
+
+import (
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/internal/token"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/golang-jwt/jwt/v5"
+)
+
+func TestClaims_Retrieve(t *testing.T) {
+ // setup types
+ now := time.Now()
+ want := &token.Claims{
+ TokenType: constants.UserAccessTokenType,
+ IsAdmin: false,
+ IsActive: true,
+ RegisteredClaims: jwt.RegisteredClaims{
+ Subject: "octocat",
+ IssuedAt: jwt.NewNumericDate(now),
+ ExpiresAt: jwt.NewNumericDate(now.Add(time.Minute * 1)),
+ },
+ }
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ context, _ := gin.CreateTestContext(nil)
+ ToContext(context, want)
+
+ // run test
+ got := Retrieve(context)
+
+ if got != want {
+ t.Errorf("Retrieve is %v, want %v", got, want)
+ }
+}
+
+func TestClaims_Establish(t *testing.T) {
+ // setup types
+ user := new(library.User)
+ user.SetID(1)
+ user.SetName("foo")
+ user.SetRefreshToken("fresh")
+ user.SetToken("bar")
+ user.SetHash("baz")
+ user.SetActive(true)
+ user.SetAdmin(false)
+ user.SetFavorites([]string{})
+
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ WorkerAuthTokenDuration: time.Minute * 20,
+ WorkerRegisterTokenDuration: time.Minute * 1,
+ }
+
+ now := time.Now()
+
+ tests := []struct {
+ TokenType string
+ WantClaims *token.Claims
+ Mto *token.MintTokenOpts
+ CtxRequest string
+ Endpoint string
+ }{
+ {
+ TokenType: constants.UserAccessTokenType,
+ WantClaims: &token.Claims{
+ TokenType: constants.UserAccessTokenType,
+ IsAdmin: false,
+ IsActive: true,
+ RegisteredClaims: jwt.RegisteredClaims{
+ Subject: "foo",
+ IssuedAt: jwt.NewNumericDate(now),
+ ExpiresAt: jwt.NewNumericDate(now.Add(time.Minute * 5)),
+ },
+ },
+ Mto: &token.MintTokenOpts{
+ User: user,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ },
+ CtxRequest: "/repos/foo/bar/builds/1",
+ Endpoint: "repos/:org/:repo/builds/:build",
+ },
+ {
+ TokenType: constants.WorkerBuildTokenType,
+ WantClaims: &token.Claims{
+ TokenType: constants.WorkerBuildTokenType,
+ BuildID: 1,
+ Repo: "foo/bar",
+ RegisteredClaims: jwt.RegisteredClaims{
+ Subject: "host",
+ IssuedAt: jwt.NewNumericDate(now),
+ ExpiresAt: jwt.NewNumericDate(now.Add(time.Minute * 35)),
+ },
+ },
+ Mto: &token.MintTokenOpts{
+ Hostname: "host",
+ BuildID: 1,
+ Repo: "foo/bar",
+ TokenDuration: time.Minute * 35,
+ TokenType: constants.WorkerBuildTokenType,
+ },
+ CtxRequest: "/repos/foo/bar/builds/1",
+ Endpoint: "repos/:org/:repo/builds/:build",
+ },
+ {
+ TokenType: constants.WorkerAuthTokenType,
+ WantClaims: &token.Claims{
+ TokenType: constants.WorkerAuthTokenType,
+ RegisteredClaims: jwt.RegisteredClaims{
+ Subject: "host",
+ IssuedAt: jwt.NewNumericDate(now),
+ ExpiresAt: jwt.NewNumericDate(now.Add(tm.WorkerAuthTokenDuration)),
+ },
+ },
+ Mto: &token.MintTokenOpts{
+ Hostname: "host",
+ TokenDuration: tm.WorkerAuthTokenDuration,
+ TokenType: constants.WorkerAuthTokenType,
+ },
+ CtxRequest: "/workers/host",
+ Endpoint: "/workers/:hostname",
+ },
+ {
+ TokenType: constants.WorkerRegisterTokenType,
+ WantClaims: &token.Claims{
+ TokenType: constants.WorkerRegisterTokenType,
+ RegisteredClaims: jwt.RegisteredClaims{
+ Subject: "host",
+ IssuedAt: jwt.NewNumericDate(now),
+ ExpiresAt: jwt.NewNumericDate(now.Add(tm.WorkerRegisterTokenDuration)),
+ },
+ },
+ Mto: &token.MintTokenOpts{
+ Hostname: "host",
+ TokenDuration: tm.WorkerRegisterTokenDuration,
+ TokenType: constants.WorkerRegisterTokenType,
+ },
+ CtxRequest: "/workers/host/register",
+ Endpoint: "workers/:hostname/register",
+ },
+ {
+ TokenType: constants.ServerWorkerTokenType,
+ WantClaims: &token.Claims{
+ TokenType: constants.ServerWorkerTokenType,
+ RegisteredClaims: jwt.RegisteredClaims{
+ Subject: "vela-worker",
+ },
+ },
+ CtxRequest: "/repos/foo/bar/builds/1",
+ Endpoint: "repos/:org/:repo/builds/:build",
+ },
+ }
+
+ got := new(token.Claims)
+
+ gin.SetMode(gin.TestMode)
+
+ for _, tt := range tests {
+ t.Run(tt.TokenType, func(t *testing.T) {
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+ context.Request, _ = http.NewRequest(http.MethodPut, tt.CtxRequest, nil)
+
+ var tkn string
+
+ if strings.EqualFold(tt.TokenType, constants.ServerWorkerTokenType) {
+ tkn = "very-secret"
+ engine.Use(func(c *gin.Context) { c.Set("secret", "very-secret") })
+ } else {
+ tkn, _ = tm.MintToken(tt.Mto)
+ }
+
+ context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tkn))
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ // setup vela mock server
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(Establish())
+ engine.PUT(tt.Endpoint, func(c *gin.Context) {
+ got = Retrieve(c)
+
+ c.Status(http.StatusOK)
+ })
+
+ s1 := httptest.NewServer(engine)
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusOK)
+ }
+
+ if !reflect.DeepEqual(got, tt.WantClaims) {
+ t.Errorf("Establish is %v, want %v", got, tt.WantClaims)
+ }
+
+ s1.Close()
+ })
+ }
+}
+
+func TestClaims_Establish_NoToken(t *testing.T) {
+ // setup types
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+ context.Request, _ = http.NewRequest(http.MethodGet, "/workers/host", nil)
+
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(Establish())
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusUnauthorized {
+ t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusUnauthorized)
+ }
+}
+
+func TestClaims_Establish_BadToken(t *testing.T) {
+ // setup types
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+ context.Request, _ = http.NewRequest(http.MethodGet, "/workers/host", nil)
+
+ u := new(library.User)
+ u.SetID(1)
+ u.SetName("octocat")
+ u.SetHash("abc")
+
+ // setup database
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+
+ defer func() {
+ db.DeleteUser(u)
+ db.Close()
+ }()
+
+ _ = db.CreateUser(u)
+
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: time.Minute * -1,
+ TokenType: constants.UserRefreshTokenType,
+ }
+
+ tkn, _ := tm.MintToken(mto)
+
+ context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tkn))
+
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(func(c *gin.Context) { c.Set("secret", "very-secret") })
+ engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
+ engine.Use(Establish())
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusUnauthorized {
+ t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusUnauthorized)
+ }
+}
diff --git a/router/middleware/claims/context.go b/router/middleware/claims/context.go
new file mode 100644
index 000000000..5e51b2b4f
--- /dev/null
+++ b/router/middleware/claims/context.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 claims
+
+import (
+ "context"
+
+ "github.com/go-vela/server/internal/token"
+)
+
+const key = "claims"
+
+// Setter defines a context that enables setting values.
+type Setter interface {
+ Set(string, interface{})
+}
+
+// FromContext returns the Claims associated with this context.
+func FromContext(c context.Context) *token.Claims {
+ value := c.Value(key)
+ if value == nil {
+ return nil
+ }
+
+ cl, ok := value.(*token.Claims)
+ if !ok {
+ return nil
+ }
+
+ return cl
+}
+
+// ToContext adds the Claims to this context if it supports
+// the Setter interface.
+func ToContext(c Setter, cl *token.Claims) {
+ c.Set(key, cl)
+}
diff --git a/router/middleware/claims/context_test.go b/router/middleware/claims/context_test.go
new file mode 100644
index 000000000..b29fd739c
--- /dev/null
+++ b/router/middleware/claims/context_test.go
@@ -0,0 +1,110 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package claims
+
+import (
+ "testing"
+ "time"
+
+ "github.com/go-vela/server/internal/token"
+ "github.com/go-vela/types/constants"
+ "github.com/golang-jwt/jwt/v5"
+
+ "github.com/gin-gonic/gin"
+)
+
+func TestClaims_FromContext(t *testing.T) {
+ now := time.Now()
+ want := &token.Claims{
+ TokenType: constants.UserAccessTokenType,
+ IsAdmin: false,
+ IsActive: true,
+ RegisteredClaims: jwt.RegisteredClaims{
+ Subject: "octocat",
+ IssuedAt: jwt.NewNumericDate(now),
+ ExpiresAt: jwt.NewNumericDate(now.Add(time.Minute * 1)),
+ },
+ }
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+ context, _ := gin.CreateTestContext(nil)
+ context.Set(key, want)
+
+ // run test
+ got := FromContext(context)
+
+ if got != want {
+ t.Errorf("FromContext is %v, want %v", got, want)
+ }
+}
+
+func TestClaims_FromContext_Bad(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+ context, _ := gin.CreateTestContext(nil)
+ context.Set(key, nil)
+
+ // run test
+ got := FromContext(context)
+
+ if got != nil {
+ t.Errorf("FromContext is %v, want nil", got)
+ }
+}
+
+func TestClaims_FromContext_WrongType(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+ context, _ := gin.CreateTestContext(nil)
+ context.Set(key, 1)
+
+ // run test
+ got := FromContext(context)
+
+ if got != nil {
+ t.Errorf("FromContext is %v, want nil", got)
+ }
+}
+
+func TestClaims_FromContext_Empty(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+ context, _ := gin.CreateTestContext(nil)
+
+ // run test
+ got := FromContext(context)
+
+ if got != nil {
+ t.Errorf("FromContext is %v, want nil", got)
+ }
+}
+
+func TestClaims_ToContext(t *testing.T) {
+ // setup types
+ now := time.Now()
+ want := &token.Claims{
+ TokenType: constants.UserAccessTokenType,
+ IsAdmin: false,
+ IsActive: true,
+ RegisteredClaims: jwt.RegisteredClaims{
+ Subject: "octocat",
+ IssuedAt: jwt.NewNumericDate(now),
+ ExpiresAt: jwt.NewNumericDate(now.Add(time.Minute * 1)),
+ },
+ }
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+ context, _ := gin.CreateTestContext(nil)
+ ToContext(context, want)
+
+ // run test
+ got := context.Value(key)
+
+ if got != want {
+ t.Errorf("ToContext is %v, want %v", got, want)
+ }
+}
diff --git a/router/middleware/claims/doc.go b/router/middleware/claims/doc.go
new file mode 100644
index 000000000..f91ec309c
--- /dev/null
+++ b/router/middleware/claims/doc.go
@@ -0,0 +1,12 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+// Package claims provides the ability for inserting
+// token claims resources into or extracting token claims
+// resources from the middleware chain for the API.
+//
+// Usage:
+//
+// import "github.com/go-vela/server/router/middleware/claims"
+package claims
diff --git a/router/middleware/database.go b/router/middleware/database.go
index f4037b7df..fbbf91eea 100644
--- a/router/middleware/database.go
+++ b/router/middleware/database.go
@@ -11,7 +11,7 @@ import (
// Database is a middleware function that initializes the database and
// attaches to the context of every http.Request.
-func Database(d database.Service) gin.HandlerFunc {
+func Database(d database.Interface) gin.HandlerFunc {
return func(c *gin.Context) {
database.ToContext(c, d)
c.Next()
diff --git a/router/middleware/database_test.go b/router/middleware/database_test.go
index e8f6496bb..b0ba77b04 100644
--- a/router/middleware/database_test.go
+++ b/router/middleware/database_test.go
@@ -12,15 +12,17 @@ import (
"github.com/gin-gonic/gin"
"github.com/go-vela/server/database"
- "github.com/go-vela/server/database/sqlite"
)
func TestMiddleware_Database(t *testing.T) {
// setup types
- var got database.Service
+ var got database.Interface
- want, _ := sqlite.NewTest()
- defer func() { _sql, _ := want.Sqlite.DB(); _sql.Close() }()
+ want, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer want.Close()
// setup context
gin.SetMode(gin.TestMode)
diff --git a/router/middleware/default_build_limit_test.go b/router/middleware/default_build_limit_test.go
index 8c0ec2923..a0ed48c2e 100644
--- a/router/middleware/default_build_limit_test.go
+++ b/router/middleware/default_build_limit_test.go
@@ -16,6 +16,7 @@ import (
func TestMiddleware_DefaultBuildLimit(t *testing.T) {
// setup types
var got int64
+
want := int64(10)
// setup context
diff --git a/router/middleware/default_repo_events.go b/router/middleware/default_repo_events.go
new file mode 100644
index 000000000..65e5ed5b0
--- /dev/null
+++ b/router/middleware/default_repo_events.go
@@ -0,0 +1,18 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package middleware
+
+import (
+ "github.com/gin-gonic/gin"
+)
+
+// DefaultRepoEvents is a middleware function that attaches the defaultRepoEvents
+// to enable the server to override the default repo event.
+func DefaultRepoEvents(defaultRepoEvents []string) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ c.Set("defaultRepoEvents", defaultRepoEvents)
+ c.Next()
+ }
+}
diff --git a/router/middleware/default_repo_events_test.go b/router/middleware/default_repo_events_test.go
new file mode 100644
index 000000000..83cfc1703
--- /dev/null
+++ b/router/middleware/default_repo_events_test.go
@@ -0,0 +1,49 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package middleware
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "testing"
+
+ "github.com/go-vela/types/constants"
+
+ "github.com/gin-gonic/gin"
+)
+
+func TestMiddleware_DefaultRepoEvents(t *testing.T) {
+ // setup types
+ var got []string
+
+ want := []string{constants.EventPush}
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+ context.Request, _ = http.NewRequest(http.MethodGet, "/health", nil)
+
+ // setup mock server
+ engine.Use(DefaultRepoEvents(want))
+ engine.GET("/health", func(c *gin.Context) {
+ got = c.Value("defaultRepoEvents").([]string)
+
+ c.Status(http.StatusOK)
+ })
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("DefaultRepoEvents returned %v, want %v", resp.Code, http.StatusOK)
+ }
+
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("DefaultRepoEvents is %v, want %v", got, want)
+ }
+}
diff --git a/router/middleware/default_timeout_test.go b/router/middleware/default_timeout_test.go
index 25be3bce8..2c80a9dc2 100644
--- a/router/middleware/default_timeout_test.go
+++ b/router/middleware/default_timeout_test.go
@@ -16,6 +16,7 @@ import (
func TestMiddleware_DefaultTimeout(t *testing.T) {
// setup types
var got int64
+
want := int64(60)
// setup context
diff --git a/router/middleware/doc.go b/router/middleware/doc.go
index fc9e8ae67..36ab883b0 100644
--- a/router/middleware/doc.go
+++ b/router/middleware/doc.go
@@ -7,5 +7,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/router/middleware"
+// import "github.com/go-vela/server/router/middleware"
package middleware
diff --git a/router/middleware/executors/doc.go b/router/middleware/executors/doc.go
index faddc1d94..5a75b0cf3 100644
--- a/router/middleware/executors/doc.go
+++ b/router/middleware/executors/doc.go
@@ -8,5 +8,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/router/middleware/executor"
+// import "github.com/go-vela/server/router/middleware/executor"
package executors
diff --git a/router/middleware/executors/executors.go b/router/middleware/executors/executors.go
index 20061022b..777da7c37 100644
--- a/router/middleware/executors/executors.go
+++ b/router/middleware/executors/executors.go
@@ -5,20 +5,20 @@
package executors
import (
+ "context"
"encoding/json"
- "io/ioutil"
+ "fmt"
+ "io"
+ "net/http"
"time"
- "github.com/go-vela/types/library"
-
+ "github.com/gin-gonic/gin"
"github.com/go-vela/server/database"
+ "github.com/go-vela/server/internal/token"
"github.com/go-vela/server/router/middleware/build"
"github.com/go-vela/server/util"
-
- "fmt"
- "net/http"
-
- "github.com/gin-gonic/gin"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
)
// Retrieve gets the executors in the given context.
@@ -31,11 +31,21 @@ func Establish() gin.HandlerFunc {
return func(c *gin.Context) {
e := new([]library.Executor)
b := build.Retrieve(c)
+
+ // if build has no host, we cannot establish executors
+ if len(b.GetHost()) == 0 {
+ ToContext(c, *e)
+ c.Next()
+
+ return
+ }
+
// retrieve the worker
- w, err := database.FromContext(c).GetWorker(b.GetHost())
+ w, err := database.FromContext(c).GetWorkerForHostname(b.GetHost())
if err != nil {
retErr := fmt.Errorf("unable to get worker: %w", err)
util.HandleError(c, http.StatusNotFound, retErr)
+
return
}
@@ -43,15 +53,35 @@ func Establish() gin.HandlerFunc {
client := http.DefaultClient
client.Timeout = 30 * time.Second
endpoint := fmt.Sprintf("%s/api/v1/executors", w.GetAddress())
- req, err := http.NewRequest("GET", endpoint, nil)
+
+ req, err := http.NewRequestWithContext(context.Background(), "GET", endpoint, nil)
if err != nil {
retErr := fmt.Errorf("unable to form request to %s: %w", endpoint, err)
util.HandleError(c, http.StatusBadRequest, retErr)
+
return
}
- // add the token to authenticate to the worker as a header
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.MustGet("secret").(string)))
+ tm := c.MustGet("token-manager").(*token.Manager)
+
+ // set mint token options
+ mto := &token.MintTokenOpts{
+ Hostname: "vela-server",
+ TokenType: constants.WorkerAuthTokenType,
+ TokenDuration: time.Minute * 1,
+ }
+
+ // mint token
+ tkn, err := tm.MintToken(mto)
+ if err != nil {
+ retErr := fmt.Errorf("unable to generate auth token: %w", err)
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+
+ // add the token to authenticate to the worker
+ req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tkn))
// make the request to the worker and check the response
resp, err := client.Do(req)
@@ -64,10 +94,11 @@ func Establish() gin.HandlerFunc {
defer resp.Body.Close()
// Read Response Body
- respBody, err := ioutil.ReadAll(resp.Body)
+ respBody, err := io.ReadAll(resp.Body)
if err != nil {
retErr := fmt.Errorf("unable to read response from %s: %w", endpoint, err)
util.HandleError(c, http.StatusBadRequest, retErr)
+
return
}
@@ -76,6 +107,7 @@ func Establish() gin.HandlerFunc {
if err != nil {
retErr := fmt.Errorf("unable to parse response from %s: %w", endpoint, err)
util.HandleError(c, http.StatusBadRequest, retErr)
+
return
}
diff --git a/router/middleware/header.go b/router/middleware/header.go
index 9eea7c22a..39ae9b68c 100644
--- a/router/middleware/header.go
+++ b/router/middleware/header.go
@@ -51,13 +51,9 @@ func Secure(c *gin.Context) {
c.Header("X-Frame-Options", "DENY")
c.Header("X-Content-Type-Options", "nosniff")
c.Header("X-XSS-Protection", "1; mode=block")
-
- if c.Request.TLS != nil {
- c.Header("Strict-Transport-Security", "max-age=31536000")
- }
-
- // Also consider adding Content-Security-Policy headers
+ // TODO: consider adding Content-Security-Policy headers
// c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com")
+ c.Header("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload")
}
// Cors is a middleware function that appends headers for
@@ -67,10 +63,12 @@ func Cors(c *gin.Context) {
m := c.MustGet("metadata").(*types.Metadata)
c.Header("Access-Control-Allow-Origin", "*")
+
if len(m.Vela.WebAddress) > 0 {
c.Header("Access-Control-Allow-Origin", m.Vela.WebAddress)
c.Header("Access-Control-Allow-Credentials", "true")
}
+
c.Header("Access-Control-Expose-Headers", "link, x-total-count")
}
diff --git a/router/middleware/header_test.go b/router/middleware/header_test.go
index e17e24c18..86f79d41a 100644
--- a/router/middleware/header_test.go
+++ b/router/middleware/header_test.go
@@ -267,7 +267,7 @@ func TestMiddleware_Secure_TLS(t *testing.T) {
wantFrameOptions := "DENY"
wantContentTypeOptions := "nosniff"
wantProtection := "1; mode=block"
- wantSecurity := "max-age=31536000"
+ wantSecurity := "max-age=63072000; includeSubDomains; preload"
// setup context
gin.SetMode(gin.TestMode)
diff --git a/router/middleware/logger.go b/router/middleware/logger.go
index 40f2e7d7b..a6cb7f835 100644
--- a/router/middleware/logger.go
+++ b/router/middleware/logger.go
@@ -7,15 +7,16 @@ package middleware
import (
"time"
- "github.com/go-vela/server/router/middleware/org"
-
"github.com/gin-gonic/gin"
"github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
"github.com/go-vela/server/router/middleware/repo"
"github.com/go-vela/server/router/middleware/service"
"github.com/go-vela/server/router/middleware/step"
"github.com/go-vela/server/router/middleware/user"
"github.com/go-vela/server/router/middleware/worker"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
"github.com/sirupsen/logrus"
)
@@ -25,36 +26,34 @@ import (
// Requests without errors are logged using logrus.Info().
//
// It receives:
-// 1. A time package format string (e.g. time.RFC3339).
-// 2. A boolean stating whether to use UTC time zone or local.
-func Logger(logger *logrus.Logger, timeFormat string, utc bool) gin.HandlerFunc {
+// 1. A time package format string (e.g. time.RFC3339).
+func Logger(logger *logrus.Logger, timeFormat string) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
// some evil middlewares modify this values
- path := c.Request.URL.Path
+ path := util.EscapeValue(c.Request.URL.Path)
c.Next()
end := time.Now()
+
latency := end.Sub(start)
- if utc {
- end = end.UTC()
- }
// prevent us from logging the health endpoint
if c.Request.URL.Path != "/health" {
fields := logrus.Fields{
- "ip": c.ClientIP(),
+ "ip": util.EscapeValue(c.ClientIP()),
"latency": latency,
"method": c.Request.Method,
"path": path,
"status": c.Writer.Status(),
- "user-agent": c.Request.UserAgent(),
- "version": c.GetHeader("X-Vela-Version"),
+ "user-agent": util.EscapeValue(c.Request.UserAgent()),
+ "version": util.EscapeValue(c.GetHeader("X-Vela-Version")),
}
body := c.Value("payload")
if body != nil {
+ body = sanitize(body)
fields["body"] = body
}
@@ -104,3 +103,14 @@ func Logger(logger *logrus.Logger, timeFormat string, utc bool) gin.HandlerFunc
}
}
}
+
+func sanitize(body interface{}) interface{} {
+ if m, ok := body.(map[string]interface{}); ok {
+ if _, ok = m["email"]; ok {
+ m["email"] = constants.SecretMask
+ body = m
+ }
+ }
+
+ return body
+}
diff --git a/router/middleware/logger_test.go b/router/middleware/logger_test.go
index c4ef3a1e3..5204763e7 100644
--- a/router/middleware/logger_test.go
+++ b/router/middleware/logger_test.go
@@ -11,6 +11,7 @@ import (
"net/http"
"net/http/httptest"
"reflect"
+ "strings"
"testing"
"time"
@@ -86,7 +87,7 @@ func TestMiddleware_Logger(t *testing.T) {
engine.Use(func(c *gin.Context) { user.ToContext(c, u) })
engine.Use(func(c *gin.Context) { worker.ToContext(c, w) })
engine.Use(Payload())
- engine.Use(Logger(logger, time.RFC3339, true))
+ engine.Use(Logger(logger, time.RFC3339))
engine.POST("/foobar", func(c *gin.Context) {
c.Status(http.StatusOK)
})
@@ -126,9 +127,9 @@ func TestMiddleware_Logger_Error(t *testing.T) {
context.Request, _ = http.NewRequest(http.MethodGet, "/foobar", nil)
// setup mock server
- engine.Use(Logger(logger, time.RFC3339, true))
+ engine.Use(Logger(logger, time.RFC3339))
engine.GET("/foobar", func(c *gin.Context) {
- // nolint: errcheck // ignore checking error
+ //nolint:errcheck // ignore checking error
c.Error(fmt.Errorf("test error"))
c.Status(http.StatusOK)
})
@@ -151,3 +152,68 @@ func TestMiddleware_Logger_Error(t *testing.T) {
t.Errorf("Logger Message is %v, want %v", gotMessage, wantMessage)
}
}
+
+func TestMiddleware_Logger_Sanitize(t *testing.T) {
+ var logBody, logWant map[string]interface{}
+
+ r := new(library.Repo)
+ r.SetID(1)
+ r.SetUserID(1)
+ r.SetOrg("foo")
+ r.SetName("bar")
+ r.SetFullName("foo/bar")
+ logRepo, _ := json.Marshal(r)
+
+ b := new(library.Build)
+ b.SetID(1)
+ b.SetRepoID(1)
+ b.SetNumber(1)
+ b.SetEmail("octocat@github.com")
+ logBuild, _ := json.Marshal(b)
+
+ sanitizeBuild := *b
+ sanitizeBuild.SetEmail("[secure]")
+ logSBuild, _ := json.Marshal(&sanitizeBuild)
+
+ tests := []struct {
+ dataType string
+ body []byte
+ want []byte
+ }{
+ {
+ dataType: "stringMap",
+ body: logRepo,
+ want: logRepo,
+ },
+ {
+ dataType: "stringMap",
+ body: logBuild,
+ want: logSBuild,
+ },
+ {
+ dataType: "string",
+ body: []byte("successfully updated step"),
+ want: []byte("successfully updated step"),
+ },
+ }
+
+ for _, test := range tests {
+ if strings.EqualFold(test.dataType, "stringMap") {
+ err := json.Unmarshal(test.body, &logBody)
+ if err != nil {
+ t.Errorf("unable to unmarshal log body data")
+ }
+
+ err = json.Unmarshal(test.want, &logWant)
+ if err != nil {
+ t.Errorf("unable to unmarshal log want data")
+ }
+ }
+
+ got := sanitize(logBody)
+
+ if !reflect.DeepEqual(got, logWant) {
+ t.Errorf("Logger returned %v, want %v", got, logWant)
+ }
+ }
+}
diff --git a/router/middleware/max_build_limit_test.go b/router/middleware/max_build_limit_test.go
index f22df3846..00e3516c9 100644
--- a/router/middleware/max_build_limit_test.go
+++ b/router/middleware/max_build_limit_test.go
@@ -16,6 +16,7 @@ import (
func TestMiddleware_MaxBuildLimit(t *testing.T) {
// setup types
var got int64
+
want := int64(30)
// setup context
diff --git a/router/middleware/org/doc.go b/router/middleware/org/doc.go
index e3ddee377..32c3d410a 100644
--- a/router/middleware/org/doc.go
+++ b/router/middleware/org/doc.go
@@ -8,5 +8,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/router/middleware/org"
+// import "github.com/go-vela/server/router/middleware/org"
package org
diff --git a/router/middleware/org/org.go b/router/middleware/org/org.go
index 6f5ab5b84..153807e4b 100644
--- a/router/middleware/org/org.go
+++ b/router/middleware/org/org.go
@@ -5,12 +5,11 @@
package org
import (
- "github.com/go-vela/server/util"
-
"fmt"
"net/http"
"github.com/gin-gonic/gin"
+ "github.com/go-vela/server/util"
)
// Retrieve gets the org in the given context.
@@ -21,14 +20,16 @@ func Retrieve(c *gin.Context) string {
// Establish used to check if org param is used only.
func Establish() gin.HandlerFunc {
return func(c *gin.Context) {
- oParam := c.Param("org")
+ oParam := util.PathParameter(c, "org")
if len(oParam) == 0 {
retErr := fmt.Errorf("no org parameter provided")
util.HandleError(c, http.StatusBadRequest, retErr)
+
return
}
ToContext(c, oParam)
+
c.Next()
}
}
diff --git a/router/middleware/org/org_test.go b/router/middleware/org/org_test.go
index bfecf125b..7e6007bc1 100644
--- a/router/middleware/org/org_test.go
+++ b/router/middleware/org/org_test.go
@@ -5,6 +5,7 @@
package org
import (
+ "context"
"net/http"
"net/http/httptest"
"reflect"
@@ -12,7 +13,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/go-vela/server/database"
- "github.com/go-vela/server/database/sqlite"
"github.com/go-vela/types/library"
)
@@ -35,7 +35,6 @@ func TestOrg_Retrieve(t *testing.T) {
func TestOrg_Establish(t *testing.T) {
// setup types
-
r := new(library.Repo)
r.SetID(1)
r.SetUserID(1)
@@ -61,15 +60,17 @@ func TestOrg_Establish(t *testing.T) {
got := ""
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(context.TODO(), r)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(context.TODO(), r)
// setup context
gin.SetMode(gin.TestMode)
@@ -101,8 +102,11 @@ func TestOrg_Establish(t *testing.T) {
func TestOrg_Establish_NoOrgParameter(t *testing.T) {
// setup database
- db, _ := sqlite.NewTest()
- defer func() { _sql, _ := db.Sqlite.DB(); _sql.Close() }()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
// setup context
gin.SetMode(gin.TestMode)
diff --git a/router/middleware/payload.go b/router/middleware/payload.go
index 2ef7e5e22..c2241fa9f 100644
--- a/router/middleware/payload.go
+++ b/router/middleware/payload.go
@@ -7,7 +7,7 @@ package middleware
import (
"bytes"
"encoding/json"
- "io/ioutil"
+ "io"
"github.com/gin-gonic/gin"
)
@@ -24,7 +24,7 @@ func Payload() gin.HandlerFunc {
c.Set("payload", payload)
- c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
+ c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
c.Next()
}
diff --git a/router/middleware/perm/doc.go b/router/middleware/perm/doc.go
index daf86955b..4cb5ad472 100644
--- a/router/middleware/perm/doc.go
+++ b/router/middleware/perm/doc.go
@@ -7,5 +7,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/router/middleware/perm"
+// import "github.com/go-vela/server/router/middleware/perm"
package perm
diff --git a/router/middleware/perm/perm.go b/router/middleware/perm/perm.go
index c5a80502a..8028275ae 100644
--- a/router/middleware/perm/perm.go
+++ b/router/middleware/perm/perm.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
@@ -9,38 +9,165 @@ import (
"net/http"
"strings"
+ "github.com/gin-gonic/gin"
"github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/claims"
"github.com/go-vela/server/router/middleware/org"
"github.com/go-vela/server/router/middleware/repo"
"github.com/go-vela/server/router/middleware/user"
"github.com/go-vela/server/scm"
"github.com/go-vela/server/util"
-
"github.com/go-vela/types/constants"
- "github.com/go-vela/types/library"
-
- "github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
// MustPlatformAdmin ensures the user has admin access to the platform.
func MustPlatformAdmin() gin.HandlerFunc {
return func(c *gin.Context) {
- u := user.Retrieve(c)
+ cl := claims.Retrieve(c)
// update engine logger with API metadata
//
// https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
logrus.WithFields(logrus.Fields{
- "user": u.GetName(),
- }).Debugf("verifying user %s is a platform admin", u.GetName())
+ "user": cl.Subject,
+ }).Debugf("verifying user %s is a platform admin", cl.Subject)
switch {
- case globalPerms(u):
+ case cl.IsAdmin:
+ return
+
+ default:
+ if strings.EqualFold(cl.TokenType, constants.WorkerBuildTokenType) {
+ logrus.WithFields(logrus.Fields{
+ "user": cl.Subject,
+ "repo": cl.Repo,
+ "build": cl.BuildID,
+ }).Warnf("attempted access of admin endpoint with build token from %s", cl.Subject)
+ }
+
+ retErr := fmt.Errorf("user %s is not a platform admin", cl.Subject)
+ util.HandleError(c, http.StatusUnauthorized, retErr)
+
+ return
+ }
+ }
+}
+
+// MustWorkerRegisterToken ensures the token is a registration token retrieved by a platform admin.
+func MustWorkerRegisterToken() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ cl := claims.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "user": cl.Subject,
+ }).Debugf("verifying user %s has a registration token for worker", cl.Subject)
+
+ switch cl.TokenType {
+ case constants.WorkerRegisterTokenType:
+ return
+ case constants.ServerWorkerTokenType:
+ if strings.EqualFold(cl.Subject, "vela-worker") {
+ return
+ }
+
+ retErr := fmt.Errorf("server-worker token provided but does not match configuration")
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ default:
+ retErr := fmt.Errorf("invalid token type: must provide a worker registration token")
+ util.HandleError(c, http.StatusUnauthorized, retErr)
+
+ return
+ }
+ }
+}
+
+// MustWorkerAuthToken ensures the token is a worker auth token.
+func MustWorkerAuthToken() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ cl := claims.Retrieve(c)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "worker": cl.Subject,
+ }).Debugf("verifying worker %s has a valid auth token", cl.Subject)
+
+ // global permissions bypass
+ if cl.IsAdmin {
+ logrus.WithFields(logrus.Fields{
+ "user": cl.Subject,
+ }).Debugf("user %s has platform admin permissions", cl.Subject)
+
+ return
+ }
+
+ switch cl.TokenType {
+ case constants.WorkerAuthTokenType, constants.WorkerRegisterTokenType:
+ return
+ case constants.ServerWorkerTokenType:
+ if strings.EqualFold(cl.Subject, "vela-worker") {
+ return
+ }
+
+ retErr := fmt.Errorf("server-worker token provided but does not match configuration")
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ default:
+ retErr := fmt.Errorf("invalid token type: must provide a worker auth token")
+ util.HandleError(c, http.StatusUnauthorized, retErr)
+
return
+ }
+ }
+}
+
+// MustBuildAccess ensures the token is a build token for the appropriate build.
+func MustBuildAccess() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ cl := claims.Retrieve(c)
+ b := build.Retrieve(c)
+
+ // global permissions bypass
+ if cl.IsAdmin {
+ logrus.WithFields(logrus.Fields{
+ "user": cl.Subject,
+ }).Debugf("user %s has platform admin permissions", cl.Subject)
+
+ return
+ }
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "worker": cl.Subject,
+ }).Debugf("verifying worker %s has a valid build token", cl.Subject)
+ // validate token type and match build id in request with build id in token claims
+ switch cl.TokenType {
+ case constants.WorkerBuildTokenType:
+ if b.GetID() == cl.BuildID {
+ return
+ }
+
+ logrus.WithFields(logrus.Fields{
+ "user": cl.Subject,
+ "repo": cl.Repo,
+ "build": cl.BuildID,
+ }).Warnf("build token for build %d attempted to be used for build %d by %s", cl.BuildID, b.GetID(), cl.Subject)
+
+ fallthrough
default:
- retErr := fmt.Errorf("user %s is not a platform admin", u.GetName())
+ retErr := fmt.Errorf("invalid token: must provide matching worker build token")
util.HandleError(c, http.StatusUnauthorized, retErr)
return
@@ -50,14 +177,16 @@ func MustPlatformAdmin() gin.HandlerFunc {
// MustSecretAdmin ensures the user has admin access to the org, repo or team.
//
-// nolint: funlen // ignore function length due to comments
+//nolint:funlen // ignore function length
func MustSecretAdmin() gin.HandlerFunc {
return func(c *gin.Context) {
+ cl := claims.Retrieve(c)
u := user.Retrieve(c)
- e := c.Param("engine")
- t := c.Param("type")
- o := c.Param("org")
- n := c.Param("name")
+ e := util.PathParameter(c, "engine")
+ t := util.PathParameter(c, "type")
+ o := util.PathParameter(c, "org")
+ n := util.PathParameter(c, "name")
+ s := util.PathParameter(c, "secret")
m := c.Request.Method
// create log fields from API metadata
@@ -86,10 +215,49 @@ func MustSecretAdmin() gin.HandlerFunc {
// https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
logger := logrus.WithFields(fields)
- if globalPerms(u) {
+ if u.GetAdmin() {
return
}
+ // if caller is worker with build token, verify it has access to requested secret
+ if strings.EqualFold(cl.TokenType, constants.WorkerBuildTokenType) {
+ org, repo := util.SplitFullName(cl.Repo)
+
+ switch t {
+ case constants.SecretShared:
+ return
+ case constants.SecretOrg:
+ logger.Debugf("verifying subject %s has token permissions for org %s", cl.Subject, o)
+
+ if strings.EqualFold(org, o) {
+ return
+ }
+
+ logger.Warnf("build token for build %s/%d attempted to be used for secret %s/%s by %s", cl.Repo, cl.BuildID, o, s, cl.Subject)
+
+ retErr := fmt.Errorf("subject %s does not have token permissions for the org %s", cl.Subject, o)
+
+ util.HandleError(c, http.StatusUnauthorized, retErr)
+
+ return
+
+ case constants.SecretRepo:
+ logger.Debugf("verifying subject %s has token permissions for repo %s/%s", cl.Subject, o, n)
+
+ if strings.EqualFold(org, o) && strings.EqualFold(repo, n) {
+ return
+ }
+
+ logger.Warnf("build token for build %s/%d attempted to be used for secret %s/%s/%s by %s", cl.Repo, cl.BuildID, o, n, s, cl.Subject)
+
+ retErr := fmt.Errorf("subject %s does not have token permissions for the repo %s/%s", cl.Subject, o, n)
+
+ util.HandleError(c, http.StatusUnauthorized, retErr)
+
+ return
+ }
+ }
+
switch t {
case constants.SecretOrg:
logger.Debugf("verifying user %s has 'admin' permissions for org %s", u.GetName(), o)
@@ -115,7 +283,6 @@ func MustSecretAdmin() gin.HandlerFunc {
}
if !strings.EqualFold(perm, "admin") {
- // nolint: lll // ignore long line length due to error message
retErr := fmt.Errorf("user %s does not have 'admin' permissions for the repo %s/%s", u.GetName(), o, n)
util.HandleError(c, http.StatusUnauthorized, retErr)
@@ -124,6 +291,16 @@ func MustSecretAdmin() gin.HandlerFunc {
}
case constants.SecretShared:
if n == "*" && m == "GET" {
+ // check if user is accessing shared secrets in personal org
+ if strings.EqualFold(o, u.GetName()) {
+ logger.WithFields(logrus.Fields{
+ "org": o,
+ "user": u.GetName(),
+ }).Debugf("skipping gathering teams for user %s with org %s", u.GetName(), o)
+
+ return
+ }
+
logger.Debugf("gathering teams user %s is a member of in the org %s", u.GetName(), o)
teams, err := scm.FromContext(c).ListUsersTeamsForOrg(u, o)
@@ -147,7 +324,6 @@ func MustSecretAdmin() gin.HandlerFunc {
}
if !strings.EqualFold(perm, "admin") {
- // nolint: lll // ignore long line length due to error message
retErr := fmt.Errorf("user %s does not have 'admin' permissions for the team %s/%s", u.GetName(), o, n)
util.HandleError(c, http.StatusUnauthorized, retErr)
@@ -182,10 +358,9 @@ func MustAdmin() gin.HandlerFunc {
"user": u.GetName(),
})
- // nolint: lll // ignore long line length due to parameters
logger.Debugf("verifying user %s has 'admin' permissions for repo %s", u.GetName(), r.GetFullName())
- if globalPerms(u) {
+ if u.GetAdmin() {
return
}
@@ -212,11 +387,10 @@ func MustAdmin() gin.HandlerFunc {
}
switch perm {
- // nolint: goconst // ignore making constant
+ //nolint:goconst // ignore making constant
case "admin":
return
default:
- // nolint: lll // ignore long line length due to error message
retErr := fmt.Errorf("user %s does not have 'admin' permissions for the repo %s", u.GetName(), r.GetFullName())
util.HandleError(c, http.StatusUnauthorized, retErr)
@@ -242,10 +416,9 @@ func MustWrite() gin.HandlerFunc {
"user": u.GetName(),
})
- // nolint: lll // ignore long line length due to log message
logger.Debugf("verifying user %s has 'write' permissions for repo %s", u.GetName(), r.GetFullName())
- if globalPerms(u) {
+ if u.GetAdmin() {
return
}
@@ -277,7 +450,6 @@ func MustWrite() gin.HandlerFunc {
case "write":
return
default:
- // nolint: lll // ignore long line length due to error message
retErr := fmt.Errorf("user %s does not have 'write' permissions for the repo %s", u.GetName(), r.GetFullName())
util.HandleError(c, http.StatusUnauthorized, retErr)
@@ -290,6 +462,7 @@ func MustWrite() gin.HandlerFunc {
// MustRead ensures the user has admin, write or read access to the repo.
func MustRead() gin.HandlerFunc {
return func(c *gin.Context) {
+ cl := claims.Retrieve(c)
o := org.Retrieve(c)
r := repo.Retrieve(c)
u := user.Retrieve(c)
@@ -305,16 +478,29 @@ func MustRead() gin.HandlerFunc {
// check if the repo visibility field is set to public
if strings.EqualFold(r.GetVisibility(), constants.VisibilityPublic) {
- // nolint: lll // ignore long line length due to log message
logger.Debugf("skipping 'read' check for repo %s with %s visibility for user %s", r.GetFullName(), r.GetVisibility(), u.GetName())
return
}
- // nolint: lll // ignore long line length due to log message
+ // return if request is from worker with build token access
+ if strings.EqualFold(cl.TokenType, constants.WorkerBuildTokenType) {
+ b := build.Retrieve(c)
+ if cl.BuildID == b.GetID() {
+ return
+ }
+
+ retErr := fmt.Errorf("subject %s does not have 'read' permissions for repo %s", cl.Subject, r.GetFullName())
+
+ util.HandleError(c, http.StatusUnauthorized, retErr)
+
+ return
+ }
+
logger.Debugf("verifying user %s has 'read' permissions for repo %s", u.GetName(), r.GetFullName())
- if globalPerms(u) {
+ // return if user is platform admin
+ if u.GetAdmin() {
return
}
@@ -348,7 +534,6 @@ func MustRead() gin.HandlerFunc {
case "read":
return
default:
- // nolint: lll // ignore long line length due to error message
retErr := fmt.Errorf("user %s does not have 'read' permissions for repo %s", u.GetName(), r.GetFullName())
util.HandleError(c, http.StatusUnauthorized, retErr)
@@ -357,17 +542,3 @@ func MustRead() gin.HandlerFunc {
}
}
}
-
-// helper function to check if the user is a platform admin.
-func globalPerms(user *library.User) bool {
- switch {
- // Agents have full access to endpoints
- case user.GetName() == "vela-worker":
- return true
- // platform admins have full access to endpoints
- case user.GetAdmin():
- return true
- }
-
- return false
-}
diff --git a/router/middleware/perm/perm_test.go b/router/middleware/perm/perm_test.go
index 1b560a63a..b1495ab19 100644
--- a/router/middleware/perm/perm_test.go
+++ b/router/middleware/perm/perm_test.go
@@ -1,54 +1,782 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
package perm
import (
+ _context "context"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/internal/token"
+ "github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/claims"
"github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/scm/github"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/golang-jwt/jwt/v5"
+)
+
+func TestPerm_MustPlatformAdmin(t *testing.T) {
+ // setup types
+ secret := "superSecret"
+
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ u := new(library.User)
+ u.SetID(1)
+ u.SetName("foob")
+ u.SetToken("bar")
+ u.SetHash("baz")
+ u.SetAdmin(true)
+
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
+
+ // setup database
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+
+ defer func() {
+ db.DeleteUser(u)
+ db.Close()
+ }()
+
+ _ = db.CreateUser(u)
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+
+ context.Request, _ = http.NewRequest(http.MethodGet, "/admin/users", nil)
+ context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok))
+
+ // setup github mock server
+ engine.GET("/api/v3/user", func(c *gin.Context) {
+ c.String(http.StatusOK, userPayload)
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup client
+ client, _ := github.NewTest(s.URL)
+
+ // setup vela mock server
+ engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
+ engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
+ engine.Use(user.Establish())
+ engine.Use(MustPlatformAdmin())
+ engine.GET("/admin/users", func(c *gin.Context) {
+ c.Status(http.StatusOK)
+ })
+
+ s1 := httptest.NewServer(engine)
+ defer s1.Close()
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("MustPlatAdmin returned %v, want %v", resp.Code, http.StatusOK)
+ }
+}
+
+func TestPerm_MustPlatformAdmin_NotAdmin(t *testing.T) {
+ // setup types
+ secret := "superSecret"
+
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ u := new(library.User)
+ u.SetID(1)
+ u.SetName("foob")
+ u.SetToken("bar")
+ u.SetHash("baz")
+ u.SetAdmin(false)
+
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+
+ // setup database
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+
+ defer func() {
+ db.DeleteUser(u)
+ db.Close()
+ }()
+
+ _ = db.CreateUser(u)
+
+ context.Request, _ = http.NewRequest(http.MethodGet, "/admin/users", nil)
+ context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok))
+
+ // setup github mock server
+ engine.GET("/api/v3/user", func(c *gin.Context) {
+ c.String(http.StatusOK, userPayload)
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup client
+ client, _ := github.NewTest(s.URL)
+
+ // setup vela mock server
+ engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
+ engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
+ engine.Use(user.Establish())
+ engine.Use(MustPlatformAdmin())
+ engine.GET("/admin/users", func(c *gin.Context) {
+ c.Status(http.StatusOK)
+ })
+
+ s1 := httptest.NewServer(engine)
+ defer s1.Close()
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusUnauthorized {
+ t.Errorf("MustPlatAdmin returned %v, want %v", resp.Code, http.StatusUnauthorized)
+ }
+}
+
+func TestPerm_MustWorkerRegisterToken(t *testing.T) {
+ // setup types
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ WorkerRegisterTokenDuration: time.Minute * 1,
+ WorkerAuthTokenDuration: time.Minute * 15,
+ }
+
+ mto := &token.MintTokenOpts{
+ Hostname: "worker",
+ TokenDuration: tm.WorkerRegisterTokenDuration,
+ TokenType: constants.WorkerRegisterTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+
+ context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil)
+ context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok))
+
+ // setup vela mock server
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(claims.Establish())
+ engine.Use(user.Establish())
+ engine.Use(MustWorkerRegisterToken())
+ engine.GET("/test/:org/:repo", func(c *gin.Context) {
+ c.Status(http.StatusOK)
+ })
+
+ s1 := httptest.NewServer(engine)
+ defer s1.Close()
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("MustWorkerRegisterToken returned %v, want %v", resp.Code, http.StatusOK)
+ }
+}
+
+func TestPerm_MustWorkerRegisterToken_PlatAdmin(t *testing.T) {
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ u := new(library.User)
+ u.SetID(1)
+ u.SetName("vela-worker")
+ u.SetToken("bar")
+ u.SetHash("baz")
+ u.SetAdmin(true)
+
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+
+ // setup database
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+
+ defer func() {
+ db.DeleteUser(u)
+ db.Close()
+ }()
+
+ _ = db.CreateUser(u)
+
+ context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil)
+ context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok))
+
+ // setup vela mock server
+ engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(claims.Establish())
+ engine.Use(user.Establish())
+ engine.Use(MustWorkerRegisterToken())
+ engine.GET("/test/:org/:repo", func(c *gin.Context) {
+ c.Status(http.StatusOK)
+ })
+
+ s1 := httptest.NewServer(engine)
+ defer s1.Close()
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusUnauthorized {
+ t.Errorf("MustWorkerRegisterToken returned %v, want %v", resp.Code, http.StatusUnauthorized)
+ }
+}
+
+func TestPerm_MustWorkerAuthToken(t *testing.T) {
+ // setup types
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ WorkerRegisterTokenDuration: time.Minute * 1,
+ WorkerAuthTokenDuration: time.Minute * 15,
+ }
+
+ mto := &token.MintTokenOpts{
+ Hostname: "worker",
+ TokenDuration: tm.WorkerAuthTokenDuration,
+ TokenType: constants.WorkerAuthTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+
+ context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil)
+ context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok))
+
+ // setup vela mock server
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(claims.Establish())
+ engine.Use(user.Establish())
+ engine.Use(MustWorkerAuthToken())
+ engine.GET("/test/:org/:repo", func(c *gin.Context) {
+ c.Status(http.StatusOK)
+ })
+
+ s1 := httptest.NewServer(engine)
+ defer s1.Close()
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("MustWorkerAuthToken returned %v, want %v", resp.Code, http.StatusOK)
+ }
+}
+
+func TestPerm_MustWorkerAuth_ServerWorkerToken(t *testing.T) {
+ // setup types
+ secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ WorkerRegisterTokenDuration: time.Minute * 1,
+ WorkerAuthTokenDuration: time.Minute * 15,
+ }
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+
+ context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil)
+ context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", secret))
+
+ // setup vela mock server
+ engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(claims.Establish())
+ engine.Use(user.Establish())
+ engine.Use(MustWorkerAuthToken())
+ engine.GET("/test/:org/:repo", func(c *gin.Context) {
+ c.Status(http.StatusOK)
+ })
+
+ s1 := httptest.NewServer(engine)
+ defer s1.Close()
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("MustWorkerAuthToken returned %v, want %v", resp.Code, http.StatusOK)
+ }
+}
+
+func TestPerm_MustBuildAccess(t *testing.T) {
+ // setup types
+ secret := "superSecret"
+
+ r := new(library.Repo)
+ r.SetID(1)
+ r.SetUserID(1)
+ r.SetHash("baz")
+ r.SetOrg("foo")
+ r.SetName("bar")
+ r.SetFullName("foo/bar")
+ r.SetVisibility("public")
+
+ b := new(library.Build)
+ b.SetID(1)
+ b.SetRepoID(1)
+ b.SetNumber(1)
+
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ mto := &token.MintTokenOpts{
+ Hostname: "worker",
+ BuildID: 1,
+ Repo: "foo/bar",
+ TokenDuration: time.Minute * 30,
+ TokenType: constants.WorkerBuildTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+
+ // setup database
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+
+ ctx := _context.TODO()
+
+ defer func() {
+ db.DeleteBuild(ctx, b)
+ db.DeleteRepo(_context.TODO(), r)
+ db.Close()
+ }()
+
+ _, _ = db.CreateRepo(_context.TODO(), r)
+ _, _ = db.CreateBuild(ctx, b)
+
+ context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar/builds/1", nil)
+ context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok))
+
+ // setup vela mock server
+ engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
+ engine.Use(claims.Establish())
+ engine.Use(user.Establish())
+ engine.Use(org.Establish())
+ engine.Use(repo.Establish())
+ engine.Use(build.Establish())
+ engine.Use(MustBuildAccess())
+ engine.GET("/test/:org/:repo/builds/:build", func(c *gin.Context) {
+ c.Status(http.StatusOK)
+ })
+
+ s1 := httptest.NewServer(engine)
+ defer s1.Close()
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("MustBuildAccess returned %v, want %v", resp.Code, http.StatusOK)
+ }
+}
+
+func TestPerm_MustBuildAccess_PlatAdmin(t *testing.T) {
+ // setup types
+ secret := "superSecret"
+
+ r := new(library.Repo)
+ r.SetID(1)
+ r.SetUserID(1)
+ r.SetHash("baz")
+ r.SetOrg("foo")
+ r.SetName("bar")
+ r.SetFullName("foo/bar")
+ r.SetVisibility("public")
+
+ b := new(library.Build)
+ b.SetID(1)
+ b.SetRepoID(1)
+ b.SetNumber(1)
+
+ u := new(library.User)
+ u.SetID(1)
+ u.SetName("admin")
+ u.SetToken("bar")
+ u.SetHash("baz")
+ u.SetAdmin(true)
+
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+
+ ctx := _context.TODO()
+
+ // setup database
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+
+ defer func() {
+ db.DeleteBuild(ctx, b)
+ db.DeleteRepo(_context.TODO(), r)
+ db.DeleteUser(u)
+ db.Close()
+ }()
+
+ _, _ = db.CreateRepo(_context.TODO(), r)
+ _, _ = db.CreateBuild(ctx, b)
+ _ = db.CreateUser(u)
+
+ context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar/builds/1", nil)
+ context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok))
+
+ // setup vela mock server
+ engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
+ engine.Use(claims.Establish())
+ engine.Use(user.Establish())
+ engine.Use(org.Establish())
+ engine.Use(repo.Establish())
+ engine.Use(build.Establish())
+ engine.Use(MustBuildAccess())
+ engine.GET("/test/:org/:repo/builds/:build", func(c *gin.Context) {
+ c.Status(http.StatusOK)
+ })
+
+ s1 := httptest.NewServer(engine)
+ defer s1.Close()
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("MustBuildAccess returned %v, want %v", resp.Code, http.StatusOK)
+ }
+}
+
+func TestPerm_MustBuildToken_WrongBuild(t *testing.T) {
+ // setup types
+ secret := "superSecret"
+
+ r := new(library.Repo)
+ r.SetID(1)
+ r.SetUserID(1)
+ r.SetHash("baz")
+ r.SetOrg("foo")
+ r.SetName("bar")
+ r.SetFullName("foo/bar")
+ r.SetVisibility("public")
+
+ b := new(library.Build)
+ b.SetID(1)
+ b.SetRepoID(1)
+ b.SetNumber(1)
+
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ mto := &token.MintTokenOpts{
+ Hostname: "worker",
+ BuildID: 2,
+ Repo: "foo/bar",
+ TokenDuration: time.Minute * 30,
+ TokenType: constants.WorkerBuildTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+
+ ctx := _context.TODO()
+
+ // setup database
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+
+ defer func() {
+ db.DeleteBuild(ctx, b)
+ db.DeleteRepo(_context.TODO(), r)
+ db.Close()
+ }()
+
+ _, _ = db.CreateRepo(_context.TODO(), r)
+ _, _ = db.CreateBuild(ctx, b)
+
+ context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar/builds/1", nil)
+ context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok))
+
+ // setup vela mock server
+ engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
+ engine.Use(claims.Establish())
+ engine.Use(user.Establish())
+ engine.Use(org.Establish())
+ engine.Use(repo.Establish())
+ engine.Use(build.Establish())
+ engine.Use(MustBuildAccess())
+ engine.GET("/test/:org/:repo/builds/:build", func(c *gin.Context) {
+ c.Status(http.StatusOK)
+ })
+
+ s1 := httptest.NewServer(engine)
+ defer s1.Close()
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusUnauthorized {
+ t.Errorf("MustBuildAccess returned %v, want %v", resp.Code, http.StatusOK)
+ }
+}
+
+func TestPerm_MustSecretAdmin_BuildToken_Repo(t *testing.T) {
+ // setup types
+ secret := "superSecret"
+
+ r := new(library.Repo)
+ r.SetID(1)
+ r.SetUserID(1)
+ r.SetHash("baz")
+ r.SetOrg("foo")
+ r.SetName("bar")
+ r.SetFullName("foo/bar")
+ r.SetVisibility("public")
+
+ b := new(library.Build)
+ b.SetID(1)
+ b.SetRepoID(1)
+ b.SetNumber(1)
+
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ mto := &token.MintTokenOpts{
+ Hostname: "worker",
+ BuildID: 1,
+ Repo: "foo/bar",
+ TokenDuration: time.Minute * 30,
+ TokenType: constants.WorkerBuildTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+
+ ctx := _context.TODO()
+
+ // setup database
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+
+ defer func() {
+ db.DeleteBuild(ctx, b)
+ db.DeleteRepo(_context.TODO(), r)
+ db.Close()
+ }()
+
+ _, _ = db.CreateRepo(_context.TODO(), r)
+ _, _ = db.CreateBuild(ctx, b)
+
+ context.Request, _ = http.NewRequest(http.MethodGet, "/test/native/repo/foo/bar/baz", nil)
+ context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok))
+
+ // setup vela mock server
+ engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
+ engine.Use(claims.Establish())
+ engine.Use(user.Establish())
+ engine.Use(MustSecretAdmin())
+ engine.GET("/test/:engine/:type/:org/:name/:secret", func(c *gin.Context) {
+ c.Status(http.StatusOK)
+ })
- "github.com/gin-gonic/gin"
- "github.com/go-vela/server/database"
- "github.com/go-vela/server/database/sqlite"
- "github.com/go-vela/server/router/middleware/repo"
- "github.com/go-vela/server/router/middleware/token"
- "github.com/go-vela/server/router/middleware/user"
- "github.com/go-vela/server/scm"
- "github.com/go-vela/server/scm/github"
- "github.com/go-vela/types/library"
-)
+ s1 := httptest.NewServer(engine)
+ defer s1.Close()
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
-const accessTokenDuration = time.Minute * 15
+ if resp.Code != http.StatusOK {
+ t.Errorf("MustBuildAccess returned %v, want %v", resp.Code, http.StatusOK)
+ }
+}
-func TestPerm_MustPlatformAdmin(t *testing.T) {
+func TestPerm_MustSecretAdmin_BuildToken_Org(t *testing.T) {
// setup types
secret := "superSecret"
- u := new(library.User)
- u.SetID(1)
- u.SetName("foo")
- u.SetToken("bar")
- u.SetHash("baz")
- u.SetAdmin(true)
+ r := new(library.Repo)
+ r.SetID(1)
+ r.SetUserID(1)
+ r.SetHash("baz")
+ r.SetOrg("foo")
+ r.SetName("bar")
+ r.SetFullName("foo/bar")
+ r.SetVisibility("public")
- tok, _ := token.CreateAccessToken(u, accessTokenDuration)
+ b := new(library.Build)
+ b.SetID(1)
+ b.SetRepoID(1)
+ b.SetNumber(1)
- // setup database
- db, _ := sqlite.NewTest()
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
- defer func() {
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
- }()
+ mto := &token.MintTokenOpts{
+ Hostname: "worker",
+ BuildID: 1,
+ Repo: "foo/bar",
+ TokenDuration: time.Minute * 30,
+ TokenType: constants.WorkerBuildTokenType,
+ }
- _ = db.CreateUser(u)
+ tok, _ := tm.MintToken(mto)
// setup context
gin.SetMode(gin.TestMode)
@@ -56,27 +784,34 @@ func TestPerm_MustPlatformAdmin(t *testing.T) {
resp := httptest.NewRecorder()
context, engine := gin.CreateTestContext(resp)
- context.Request, _ = http.NewRequest(http.MethodGet, "/admin/users", nil)
- context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok))
+ ctx := _context.TODO()
- // setup github mock server
- engine.GET("/api/v3/user", func(c *gin.Context) {
- c.String(http.StatusOK, userPayload)
- })
+ // setup database
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
- s := httptest.NewServer(engine)
- defer s.Close()
+ defer func() {
+ db.DeleteBuild(ctx, b)
+ db.DeleteRepo(_context.TODO(), r)
+ db.Close()
+ }()
- // setup client
- client, _ := github.NewTest(s.URL)
+ _, _ = db.CreateRepo(_context.TODO(), r)
+ _, _ = db.CreateBuild(ctx, b)
+
+ context.Request, _ = http.NewRequest(http.MethodGet, "/test/native/org/foo/*/baz", nil)
+ context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok))
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
- engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
engine.Use(user.Establish())
- engine.Use(MustPlatformAdmin())
- engine.GET("/admin/users", func(c *gin.Context) {
+ engine.Use(MustSecretAdmin())
+ engine.GET("/test/:engine/:type/:org/:name/:secret", func(c *gin.Context) {
c.Status(http.StatusOK)
})
@@ -87,22 +822,44 @@ func TestPerm_MustPlatformAdmin(t *testing.T) {
engine.ServeHTTP(context.Writer, context.Request)
if resp.Code != http.StatusOK {
- t.Errorf("MustPlatAdmin returned %v, want %v", resp.Code, http.StatusOK)
+ t.Errorf("MustSecretAdmin returned %v, want %v", resp.Code, http.StatusOK)
}
}
-func TestPerm_MustPlatformAdmin_NotAdmin(t *testing.T) {
+func TestPerm_MustSecretAdmin_BuildToken_Shared(t *testing.T) {
// setup types
secret := "superSecret"
- u := new(library.User)
- u.SetID(1)
- u.SetName("foo")
- u.SetToken("bar")
- u.SetHash("baz")
- u.SetAdmin(false)
+ r := new(library.Repo)
+ r.SetID(1)
+ r.SetUserID(1)
+ r.SetHash("baz")
+ r.SetOrg("foo")
+ r.SetName("bar")
+ r.SetFullName("foo/bar")
+ r.SetVisibility("public")
+
+ b := new(library.Build)
+ b.SetID(1)
+ b.SetRepoID(1)
+ b.SetNumber(1)
+
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ mto := &token.MintTokenOpts{
+ Hostname: "worker",
+ BuildID: 1,
+ Repo: "foo/bar",
+ TokenDuration: time.Minute * 30,
+ TokenType: constants.WorkerBuildTokenType,
+ }
- tok, _ := token.CreateAccessToken(u, accessTokenDuration)
+ tok, _ := tm.MintToken(mto)
// setup context
gin.SetMode(gin.TestMode)
@@ -110,38 +867,34 @@ func TestPerm_MustPlatformAdmin_NotAdmin(t *testing.T) {
resp := httptest.NewRecorder()
context, engine := gin.CreateTestContext(resp)
+ ctx := _context.TODO()
+
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteBuild(ctx, b)
+ db.DeleteRepo(_context.TODO(), r)
+ db.Close()
}()
- _ = db.CreateUser(u)
+ _, _ = db.CreateRepo(_context.TODO(), r)
+ _, _ = db.CreateBuild(ctx, b)
- context.Request, _ = http.NewRequest(http.MethodGet, "/admin/users", nil)
+ context.Request, _ = http.NewRequest(http.MethodGet, "/test/native/shared/foo/*/*", nil)
context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok))
- // setup github mock server
- engine.GET("/api/v3/user", func(c *gin.Context) {
- c.String(http.StatusOK, userPayload)
- })
-
- s := httptest.NewServer(engine)
- defer s.Close()
-
- // setup client
- client, _ := github.NewTest(s.URL)
-
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
- engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
engine.Use(user.Establish())
- engine.Use(MustPlatformAdmin())
- engine.GET("/admin/users", func(c *gin.Context) {
+ engine.Use(MustSecretAdmin())
+ engine.GET("/test/:engine/:type/:org/:name/:secret", func(c *gin.Context) {
c.Status(http.StatusOK)
})
@@ -151,8 +904,8 @@ func TestPerm_MustPlatformAdmin_NotAdmin(t *testing.T) {
// run test
engine.ServeHTTP(context.Writer, context.Request)
- if resp.Code != http.StatusUnauthorized {
- t.Errorf("MustPlatAdmin returned %v, want %v", resp.Code, http.StatusUnauthorized)
+ if resp.Code != http.StatusOK {
+ t.Errorf("MustSecretAdmin returned %v, want %v", resp.Code, http.StatusOK)
}
}
@@ -160,6 +913,13 @@ func TestPerm_MustAdmin(t *testing.T) {
// setup types
secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
r := new(library.Repo)
r.SetID(1)
r.SetUserID(1)
@@ -171,12 +931,18 @@ func TestPerm_MustAdmin(t *testing.T) {
u := new(library.User)
u.SetID(1)
- u.SetName("foo")
+ u.SetName("foob")
u.SetToken("bar")
u.SetHash("baz")
u.SetAdmin(false)
- tok, _ := token.CreateAccessToken(u, accessTokenDuration)
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
// setup context
gin.SetMode(gin.TestMode)
@@ -185,16 +951,18 @@ func TestPerm_MustAdmin(t *testing.T) {
context, engine := gin.CreateTestContext(resp)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(_context.TODO(), r)
+ db.DeleteUser(u)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(_context.TODO(), r)
_ = db.CreateUser(u)
context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil)
@@ -216,8 +984,10 @@ func TestPerm_MustAdmin(t *testing.T) {
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
engine.Use(user.Establish())
engine.Use(org.Establish())
engine.Use(repo.Establish())
@@ -241,6 +1011,13 @@ func TestPerm_MustAdmin_PlatAdmin(t *testing.T) {
// setup types
secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
r := new(library.Repo)
r.SetID(1)
r.SetUserID(1)
@@ -252,12 +1029,18 @@ func TestPerm_MustAdmin_PlatAdmin(t *testing.T) {
u := new(library.User)
u.SetID(1)
- u.SetName("foo")
+ u.SetName("foob")
u.SetToken("bar")
u.SetHash("baz")
u.SetAdmin(true)
- tok, _ := token.CreateAccessToken(u, accessTokenDuration)
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
// setup context
gin.SetMode(gin.TestMode)
@@ -266,16 +1049,18 @@ func TestPerm_MustAdmin_PlatAdmin(t *testing.T) {
context, engine := gin.CreateTestContext(resp)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(_context.TODO(), r)
+ db.DeleteUser(u)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(_context.TODO(), r)
_ = db.CreateUser(u)
context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil)
@@ -297,8 +1082,10 @@ func TestPerm_MustAdmin_PlatAdmin(t *testing.T) {
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
engine.Use(user.Establish())
engine.Use(org.Establish())
engine.Use(repo.Establish())
@@ -322,6 +1109,13 @@ func TestPerm_MustAdmin_NotAdmin(t *testing.T) {
// setup types
secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
r := new(library.Repo)
r.SetID(1)
r.SetUserID(1)
@@ -333,12 +1127,18 @@ func TestPerm_MustAdmin_NotAdmin(t *testing.T) {
u := new(library.User)
u.SetID(1)
- u.SetName("foo")
+ u.SetName("foob")
u.SetToken("bar")
u.SetHash("baz")
u.SetAdmin(false)
- tok, _ := token.CreateAccessToken(u, accessTokenDuration)
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
// setup context
gin.SetMode(gin.TestMode)
@@ -347,16 +1147,18 @@ func TestPerm_MustAdmin_NotAdmin(t *testing.T) {
context, engine := gin.CreateTestContext(resp)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(_context.TODO(), r)
+ db.DeleteUser(u)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(_context.TODO(), r)
_ = db.CreateUser(u)
context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil)
@@ -378,8 +1180,10 @@ func TestPerm_MustAdmin_NotAdmin(t *testing.T) {
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
engine.Use(user.Establish())
engine.Use(org.Establish())
engine.Use(repo.Establish())
@@ -403,6 +1207,13 @@ func TestPerm_MustWrite(t *testing.T) {
// setup types
secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
r := new(library.Repo)
r.SetID(1)
r.SetUserID(1)
@@ -414,12 +1225,18 @@ func TestPerm_MustWrite(t *testing.T) {
u := new(library.User)
u.SetID(1)
- u.SetName("foo")
+ u.SetName("foob")
u.SetToken("bar")
u.SetHash("baz")
u.SetAdmin(false)
- tok, _ := token.CreateAccessToken(u, accessTokenDuration)
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
// setup context
gin.SetMode(gin.TestMode)
@@ -428,16 +1245,18 @@ func TestPerm_MustWrite(t *testing.T) {
context, engine := gin.CreateTestContext(resp)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(_context.TODO(), r)
+ db.DeleteUser(u)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(_context.TODO(), r)
_ = db.CreateUser(u)
context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil)
@@ -459,8 +1278,10 @@ func TestPerm_MustWrite(t *testing.T) {
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
engine.Use(user.Establish())
engine.Use(org.Establish())
engine.Use(repo.Establish())
@@ -484,6 +1305,13 @@ func TestPerm_MustWrite_PlatAdmin(t *testing.T) {
// setup types
secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
r := new(library.Repo)
r.SetID(1)
r.SetUserID(1)
@@ -495,12 +1323,18 @@ func TestPerm_MustWrite_PlatAdmin(t *testing.T) {
u := new(library.User)
u.SetID(1)
- u.SetName("foo")
+ u.SetName("foob")
u.SetToken("bar")
u.SetHash("baz")
u.SetAdmin(true)
- tok, _ := token.CreateAccessToken(u, accessTokenDuration)
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
// setup context
gin.SetMode(gin.TestMode)
@@ -509,16 +1343,18 @@ func TestPerm_MustWrite_PlatAdmin(t *testing.T) {
context, engine := gin.CreateTestContext(resp)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(_context.TODO(), r)
+ db.DeleteUser(u)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(_context.TODO(), r)
_ = db.CreateUser(u)
context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil)
@@ -540,8 +1376,10 @@ func TestPerm_MustWrite_PlatAdmin(t *testing.T) {
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
engine.Use(user.Establish())
engine.Use(org.Establish())
engine.Use(repo.Establish())
@@ -565,6 +1403,13 @@ func TestPerm_MustWrite_RepoAdmin(t *testing.T) {
// setup types
secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
r := new(library.Repo)
r.SetID(1)
r.SetUserID(1)
@@ -576,12 +1421,18 @@ func TestPerm_MustWrite_RepoAdmin(t *testing.T) {
u := new(library.User)
u.SetID(1)
- u.SetName("foo")
+ u.SetName("foob")
u.SetToken("bar")
u.SetHash("baz")
u.SetAdmin(false)
- tok, _ := token.CreateAccessToken(u, accessTokenDuration)
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
// setup context
gin.SetMode(gin.TestMode)
@@ -590,16 +1441,18 @@ func TestPerm_MustWrite_RepoAdmin(t *testing.T) {
context, engine := gin.CreateTestContext(resp)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(_context.TODO(), r)
+ db.DeleteUser(u)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(_context.TODO(), r)
_ = db.CreateUser(u)
context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil)
@@ -621,8 +1474,10 @@ func TestPerm_MustWrite_RepoAdmin(t *testing.T) {
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
engine.Use(user.Establish())
engine.Use(org.Establish())
engine.Use(repo.Establish())
@@ -646,6 +1501,13 @@ func TestPerm_MustWrite_NotWrite(t *testing.T) {
// setup types
secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
r := new(library.Repo)
r.SetID(1)
r.SetUserID(1)
@@ -657,12 +1519,18 @@ func TestPerm_MustWrite_NotWrite(t *testing.T) {
u := new(library.User)
u.SetID(1)
- u.SetName("foo")
+ u.SetName("foob")
u.SetToken("bar")
u.SetHash("baz")
u.SetAdmin(false)
- tok, _ := token.CreateAccessToken(u, accessTokenDuration)
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
// setup context
gin.SetMode(gin.TestMode)
@@ -671,16 +1539,18 @@ func TestPerm_MustWrite_NotWrite(t *testing.T) {
context, engine := gin.CreateTestContext(resp)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(_context.TODO(), r)
+ db.DeleteUser(u)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(_context.TODO(), r)
_ = db.CreateUser(u)
context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil)
@@ -702,8 +1572,10 @@ func TestPerm_MustWrite_NotWrite(t *testing.T) {
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
engine.Use(user.Establish())
engine.Use(org.Establish())
engine.Use(repo.Establish())
@@ -727,6 +1599,13 @@ func TestPerm_MustRead(t *testing.T) {
// setup types
secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
r := new(library.Repo)
r.SetID(1)
r.SetUserID(1)
@@ -738,12 +1617,18 @@ func TestPerm_MustRead(t *testing.T) {
u := new(library.User)
u.SetID(1)
- u.SetName("foo")
+ u.SetName("foob")
u.SetToken("bar")
u.SetHash("baz")
u.SetAdmin(false)
- tok, _ := token.CreateAccessToken(u, accessTokenDuration)
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
// setup context
gin.SetMode(gin.TestMode)
@@ -752,16 +1637,18 @@ func TestPerm_MustRead(t *testing.T) {
context, engine := gin.CreateTestContext(resp)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(_context.TODO(), r)
+ db.DeleteUser(u)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(_context.TODO(), r)
_ = db.CreateUser(u)
context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil)
@@ -783,8 +1670,10 @@ func TestPerm_MustRead(t *testing.T) {
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
engine.Use(user.Establish())
engine.Use(org.Establish())
engine.Use(repo.Establish())
@@ -808,6 +1697,13 @@ func TestPerm_MustRead_PlatAdmin(t *testing.T) {
// setup types
secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
r := new(library.Repo)
r.SetID(1)
r.SetUserID(1)
@@ -819,12 +1715,18 @@ func TestPerm_MustRead_PlatAdmin(t *testing.T) {
u := new(library.User)
u.SetID(1)
- u.SetName("foo")
+ u.SetName("foob")
u.SetToken("bar")
u.SetHash("baz")
u.SetAdmin(true)
- tok, _ := token.CreateAccessToken(u, accessTokenDuration)
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
// setup context
gin.SetMode(gin.TestMode)
@@ -833,16 +1735,18 @@ func TestPerm_MustRead_PlatAdmin(t *testing.T) {
context, engine := gin.CreateTestContext(resp)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(_context.TODO(), r)
+ db.DeleteUser(u)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(_context.TODO(), r)
_ = db.CreateUser(u)
context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil)
@@ -864,8 +1768,10 @@ func TestPerm_MustRead_PlatAdmin(t *testing.T) {
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
engine.Use(user.Establish())
engine.Use(org.Establish())
engine.Use(repo.Establish())
@@ -885,10 +1791,103 @@ func TestPerm_MustRead_PlatAdmin(t *testing.T) {
}
}
+func TestPerm_MustRead_WorkerBuildToken(t *testing.T) {
+ // setup types
+ secret := "superSecret"
+
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ r := new(library.Repo)
+ r.SetID(1)
+ r.SetUserID(1)
+ r.SetHash("baz")
+ r.SetOrg("foo")
+ r.SetName("bar")
+ r.SetFullName("foo/bar")
+ r.SetVisibility("private")
+
+ b := new(library.Build)
+ b.SetID(1)
+ b.SetRepoID(1)
+ b.SetNumber(1)
+
+ mto := &token.MintTokenOpts{
+ Hostname: "worker",
+ TokenDuration: time.Minute * 35,
+ TokenType: constants.WorkerBuildTokenType,
+ BuildID: 1,
+ Repo: "foo/bar",
+ }
+
+ tok, _ := tm.MintToken(mto)
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+
+ ctx := _context.TODO()
+
+ // setup database
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+
+ defer func() {
+ db.DeleteBuild(ctx, b)
+ db.DeleteRepo(_context.TODO(), r)
+ db.Close()
+ }()
+
+ _, _ = db.CreateBuild(ctx, b)
+ _, _ = db.CreateRepo(_context.TODO(), r)
+
+ context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar/builds/1", nil)
+ context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tok))
+
+ // setup vela mock server
+ engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
+ engine.Use(claims.Establish())
+ engine.Use(user.Establish())
+ engine.Use(org.Establish())
+ engine.Use(repo.Establish())
+ engine.Use(build.Establish())
+ engine.Use(MustRead())
+ engine.GET("/test/:org/:repo/builds/:build", func(c *gin.Context) {
+ c.Status(http.StatusOK)
+ })
+
+ s1 := httptest.NewServer(engine)
+ defer s1.Close()
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("MustRead returned %v, want %v", resp.Code, http.StatusOK)
+ }
+}
+
func TestPerm_MustRead_RepoAdmin(t *testing.T) {
// setup types
secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
r := new(library.Repo)
r.SetID(1)
r.SetUserID(1)
@@ -900,12 +1899,18 @@ func TestPerm_MustRead_RepoAdmin(t *testing.T) {
u := new(library.User)
u.SetID(1)
- u.SetName("foo")
+ u.SetName("foob")
u.SetToken("bar")
u.SetHash("baz")
u.SetAdmin(false)
- tok, _ := token.CreateAccessToken(u, accessTokenDuration)
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
// setup context
gin.SetMode(gin.TestMode)
@@ -914,16 +1919,18 @@ func TestPerm_MustRead_RepoAdmin(t *testing.T) {
context, engine := gin.CreateTestContext(resp)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(_context.TODO(), r)
+ db.DeleteUser(u)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(_context.TODO(), r)
_ = db.CreateUser(u)
context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil)
@@ -945,8 +1952,10 @@ func TestPerm_MustRead_RepoAdmin(t *testing.T) {
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
engine.Use(user.Establish())
engine.Use(org.Establish())
engine.Use(repo.Establish())
@@ -970,6 +1979,13 @@ func TestPerm_MustRead_RepoWrite(t *testing.T) {
// setup types
secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
r := new(library.Repo)
r.SetID(1)
r.SetUserID(1)
@@ -981,12 +1997,18 @@ func TestPerm_MustRead_RepoWrite(t *testing.T) {
u := new(library.User)
u.SetID(1)
- u.SetName("foo")
+ u.SetName("foob")
u.SetToken("bar")
u.SetHash("baz")
u.SetAdmin(false)
- tok, _ := token.CreateAccessToken(u, accessTokenDuration)
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
// setup context
gin.SetMode(gin.TestMode)
@@ -995,16 +2017,18 @@ func TestPerm_MustRead_RepoWrite(t *testing.T) {
context, engine := gin.CreateTestContext(resp)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(_context.TODO(), r)
+ db.DeleteUser(u)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(_context.TODO(), r)
_ = db.CreateUser(u)
context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil)
@@ -1026,8 +2050,10 @@ func TestPerm_MustRead_RepoWrite(t *testing.T) {
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
engine.Use(user.Establish())
engine.Use(org.Establish())
engine.Use(repo.Establish())
@@ -1051,6 +2077,13 @@ func TestPerm_MustRead_RepoPublic(t *testing.T) {
// setup types
secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
r := new(library.Repo)
r.SetID(1)
r.SetUserID(1)
@@ -1062,12 +2095,18 @@ func TestPerm_MustRead_RepoPublic(t *testing.T) {
u := new(library.User)
u.SetID(1)
- u.SetName("foo")
+ u.SetName("foob")
u.SetToken("bar")
u.SetHash("baz")
u.SetAdmin(false)
- tok, _ := token.CreateAccessToken(u, accessTokenDuration)
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
// setup context
gin.SetMode(gin.TestMode)
@@ -1076,16 +2115,18 @@ func TestPerm_MustRead_RepoPublic(t *testing.T) {
context, engine := gin.CreateTestContext(resp)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(_context.TODO(), r)
+ db.DeleteUser(u)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(_context.TODO(), r)
_ = db.CreateUser(u)
context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil)
@@ -1107,8 +2148,10 @@ func TestPerm_MustRead_RepoPublic(t *testing.T) {
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
engine.Use(user.Establish())
engine.Use(org.Establish())
engine.Use(repo.Establish())
@@ -1132,6 +2175,13 @@ func TestPerm_MustRead_NotRead(t *testing.T) {
// setup types
secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
r := new(library.Repo)
r.SetID(1)
r.SetUserID(1)
@@ -1143,12 +2193,18 @@ func TestPerm_MustRead_NotRead(t *testing.T) {
u := new(library.User)
u.SetID(1)
- u.SetName("foo")
+ u.SetName("foob")
u.SetToken("bar")
u.SetHash("baz")
u.SetAdmin(false)
- tok, _ := token.CreateAccessToken(u, accessTokenDuration)
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ tok, _ := tm.MintToken(mto)
// setup context
gin.SetMode(gin.TestMode)
@@ -1157,16 +2213,18 @@ func TestPerm_MustRead_NotRead(t *testing.T) {
context, engine := gin.CreateTestContext(resp)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(_context.TODO(), r)
+ db.DeleteUser(u)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(_context.TODO(), r)
_ = db.CreateUser(u)
context.Request, _ = http.NewRequest(http.MethodGet, "/test/foo/bar", nil)
@@ -1188,8 +2246,10 @@ func TestPerm_MustRead_NotRead(t *testing.T) {
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
engine.Use(user.Establish())
engine.Use(org.Establish())
engine.Use(repo.Establish())
@@ -1209,57 +2269,6 @@ func TestPerm_MustRead_NotRead(t *testing.T) {
}
}
-func TestPerm_globalPerms(t *testing.T) {
- // setup types
- u := new(library.User)
- u.SetID(1)
- u.SetName("foo")
- u.SetToken("bar")
- u.SetHash("baz")
- u.SetAdmin(false)
-
- // run test
- got := globalPerms(u)
-
- if got {
- t.Errorf("globalPerms returned %v, want false", got)
- }
-}
-
-func TestPerm_globalPerms_Agent(t *testing.T) {
- // setup types
- u := new(library.User)
- u.SetID(1)
- u.SetName("vela-worker")
- u.SetToken("bar")
- u.SetHash("baz")
- u.SetAdmin(false)
-
- // run test
- got := globalPerms(u)
-
- if !got {
- t.Errorf("globalPerms returned %v, want true", got)
- }
-}
-
-func TestPerm_globalPerms_Admin(t *testing.T) {
- // setup types
- u := new(library.User)
- u.SetID(1)
- u.SetName("foo")
- u.SetToken("bar")
- u.SetHash("baz")
- u.SetAdmin(true)
-
- // run test
- got := globalPerms(u)
-
- if !got {
- t.Errorf("globalPerms returned %v, want true", got)
- }
-}
-
const permAdminPayload = `
{
"permission": "admin",
@@ -1366,7 +2375,7 @@ const permNonePayload = `
const userPayload = `
{
- "login": "foo",
+ "login": "foob",
"id": 1,
"node_id": "MDQ6VXNlcjE=",
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
diff --git a/router/middleware/pipeline/context.go b/router/middleware/pipeline/context.go
new file mode 100644
index 000000000..6aab4037e
--- /dev/null
+++ b/router/middleware/pipeline/context.go
@@ -0,0 +1,39 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+
+ "github.com/go-vela/types/library"
+)
+
+const key = "pipeline"
+
+// Setter defines a context that enables setting values.
+type Setter interface {
+ Set(string, interface{})
+}
+
+// FromContext returns the Pipeline associated with this context.
+func FromContext(c context.Context) *library.Pipeline {
+ value := c.Value(key)
+ if value == nil {
+ return nil
+ }
+
+ b, ok := value.(*library.Pipeline)
+ if !ok {
+ return nil
+ }
+
+ return b
+}
+
+// ToContext adds the Pipeline to this context if it supports
+// the Setter interface.
+func ToContext(c Setter, b *library.Pipeline) {
+ c.Set(key, b)
+}
diff --git a/router/middleware/pipeline/context_test.go b/router/middleware/pipeline/context_test.go
new file mode 100644
index 000000000..ddc5b8bbd
--- /dev/null
+++ b/router/middleware/pipeline/context_test.go
@@ -0,0 +1,103 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/types/library"
+)
+
+func TestPipeline_FromContext(t *testing.T) {
+ // setup types
+ _pipeline := new(library.Pipeline)
+
+ gin.SetMode(gin.TestMode)
+ _context, _ := gin.CreateTestContext(nil)
+ _context.Set(key, _pipeline)
+
+ _emptyContext, _ := gin.CreateTestContext(nil)
+
+ _nilContext, _ := gin.CreateTestContext(nil)
+ _nilContext.Set(key, nil)
+
+ _typeContext, _ := gin.CreateTestContext(nil)
+ _typeContext.Set(key, 1)
+
+ // setup tests
+ tests := []struct {
+ name string
+ context *gin.Context
+ want *library.Pipeline
+ }{
+ {
+ name: "context",
+ context: _context,
+ want: _pipeline,
+ },
+ {
+ name: "context with no value",
+ context: _emptyContext,
+ want: nil,
+ },
+ {
+ name: "context with nil value",
+ context: _nilContext,
+ want: nil,
+ },
+ {
+ name: "context with wrong value type",
+ context: _typeContext,
+ want: nil,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ got := FromContext(test.context)
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("FromContext for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
+
+func TestPipeline_ToContext(t *testing.T) {
+ // setup types
+ _pipeline := new(library.Pipeline)
+
+ gin.SetMode(gin.TestMode)
+ _context, _ := gin.CreateTestContext(nil)
+
+ // setup tests
+ tests := []struct {
+ name string
+ context *gin.Context
+ want *library.Pipeline
+ }{
+ {
+ name: "context",
+ context: _context,
+ want: _pipeline,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ ToContext(test.context, test.want)
+
+ got := test.context.Value(key)
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("ToContext for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
diff --git a/router/middleware/pipeline/doc.go b/router/middleware/pipeline/doc.go
new file mode 100644
index 000000000..e6f19504d
--- /dev/null
+++ b/router/middleware/pipeline/doc.go
@@ -0,0 +1,12 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+// Package pipeline provides the ability for inserting
+// Vela pipeline resources into or extracting Vela pipeline
+// resources from the middleware chain for the API.
+//
+// Usage:
+//
+// import "github.com/go-vela/server/router/middleware/pipeline"
+package pipeline
diff --git a/router/middleware/pipeline/pipeline.go b/router/middleware/pipeline/pipeline.go
new file mode 100644
index 000000000..b3f2e57af
--- /dev/null
+++ b/router/middleware/pipeline/pipeline.go
@@ -0,0 +1,99 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/compiler"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// Retrieve gets the pipeline in the given context.
+func Retrieve(c *gin.Context) *library.Pipeline {
+ return FromContext(c)
+}
+
+// Establish sets the pipeline in the given context.
+func Establish() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ o := org.Retrieve(c)
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ if r == nil {
+ retErr := fmt.Errorf("repo %s/%s not found", util.PathParameter(c, "org"), util.PathParameter(c, "repo"))
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ p := util.PathParameter(c, "pipeline")
+ if len(p) == 0 {
+ retErr := fmt.Errorf("no pipeline parameter provided")
+
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ entry := fmt.Sprintf("%s/%s", r.GetFullName(), p)
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": o,
+ "pipeline": p,
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Debugf("reading pipeline %s", entry)
+
+ pipeline, err := database.FromContext(c).GetPipelineForRepo(ctx, p, r)
+ if err != nil { // assume the pipeline doesn't exist in the database yet (before pipeline support was added)
+ // send API call to capture the pipeline configuration file
+ config, err := scm.FromContext(c).ConfigBackoff(u, r, p)
+ if err != nil {
+ retErr := fmt.Errorf("unable to get pipeline configuration for %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ // parse and compile the pipeline configuration file
+ _, pipeline, err = compiler.FromContext(c).
+ Duplicate().
+ WithCommit(p).
+ WithMetadata(c.MustGet("metadata").(*types.Metadata)).
+ WithRepo(r).
+ WithUser(u).
+ Compile(config)
+ if err != nil {
+ retErr := fmt.Errorf("unable to compile pipeline configuration for %s: %w", entry, err)
+
+ util.HandleError(c, http.StatusInternalServerError, retErr)
+
+ return
+ }
+ }
+
+ ToContext(c, pipeline)
+
+ c.Next()
+ }
+}
diff --git a/router/middleware/pipeline/pipeline_test.go b/router/middleware/pipeline/pipeline_test.go
new file mode 100644
index 000000000..0fc1e8b29
--- /dev/null
+++ b/router/middleware/pipeline/pipeline_test.go
@@ -0,0 +1,343 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package pipeline
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/compiler"
+ "github.com/go-vela/server/compiler/native"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/internal/token"
+ "github.com/go-vela/server/router/middleware/claims"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/scm"
+ "github.com/go-vela/server/scm/github"
+ "github.com/go-vela/types"
+ "github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/urfave/cli/v2"
+)
+
+func TestPipeline_Retrieve(t *testing.T) {
+ // setup types
+ _pipeline := new(library.Pipeline)
+
+ gin.SetMode(gin.TestMode)
+ _context, _ := gin.CreateTestContext(nil)
+
+ // setup tests
+ tests := []struct {
+ name string
+ context *gin.Context
+ want *library.Pipeline
+ }{
+ {
+ name: "context",
+ context: _context,
+ want: _pipeline,
+ },
+ }
+
+ // run tests
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ ToContext(test.context, test.want)
+
+ got := Retrieve(test.context)
+
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("Retrieve for %s is %v, want %v", test.name, got, test.want)
+ }
+ })
+ }
+}
+
+func TestPipeline_Establish(t *testing.T) {
+ // setup types
+ r := new(library.Repo)
+ r.SetID(1)
+ r.SetUserID(1)
+ r.SetHash("baz")
+ r.SetOrg("foo")
+ r.SetName("bar")
+ r.SetFullName("foo/bar")
+ r.SetVisibility("public")
+
+ want := new(library.Pipeline)
+ want.SetID(1)
+ want.SetRepoID(1)
+ want.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163")
+ want.SetFlavor("")
+ want.SetPlatform("")
+ want.SetRef("refs/heads/master")
+ want.SetType("yaml")
+ want.SetVersion("1")
+ want.SetExternalSecrets(false)
+ want.SetInternalSecrets(false)
+ want.SetServices(false)
+ want.SetStages(false)
+ want.SetSteps(false)
+ want.SetTemplates(false)
+ want.SetData([]byte{})
+
+ got := new(library.Pipeline)
+
+ // setup database
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+
+ defer func() {
+ db.DeletePipeline(context.TODO(), want)
+ db.DeleteRepo(context.TODO(), r)
+ db.Close()
+ }()
+
+ _, _ = db.CreateRepo(context.TODO(), r)
+ _, _ = db.CreatePipeline(context.TODO(), want)
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+ context.Request, _ = http.NewRequest(http.MethodGet, "/pipelines/foo/bar/48afb5bdc41ad69bf22588491333f7cf71135163", nil)
+
+ // setup mock server
+ engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
+ engine.Use(org.Establish())
+ engine.Use(repo.Establish())
+ engine.Use(Establish())
+ engine.GET("/pipelines/:org/:repo/:pipeline", func(c *gin.Context) {
+ got = Retrieve(c)
+
+ c.Status(http.StatusOK)
+ })
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusOK)
+ }
+
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("Establish is %v, want %v", got, want)
+ }
+}
+
+func TestPipeline_Establish_NoRepo(t *testing.T) {
+ // setup database
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+ context.Request, _ = http.NewRequest(http.MethodGet, "/pipelines/foo/bar/48afb5bdc41ad69bf22588491333f7cf71135163", nil)
+
+ // setup mock server
+ engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
+ engine.Use(Establish())
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusNotFound {
+ t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusNotFound)
+ }
+}
+
+func TestPipeline_Establish_NoPipelineParameter(t *testing.T) {
+ // setup types
+ r := new(library.Repo)
+ r.SetID(1)
+ r.SetUserID(1)
+ r.SetHash("baz")
+ r.SetOrg("foo")
+ r.SetName("bar")
+ r.SetFullName("foo/bar")
+ r.SetVisibility("public")
+
+ // setup database
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+
+ defer func() {
+ db.DeleteRepo(context.TODO(), r)
+ db.Close()
+ }()
+
+ _, _ = db.CreateRepo(context.TODO(), r)
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+ context.Request, _ = http.NewRequest(http.MethodGet, "/pipelines/foo/bar", nil)
+
+ // setup mock server
+ engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
+ engine.Use(org.Establish())
+ engine.Use(repo.Establish())
+ engine.Use(Establish())
+ engine.GET("/pipelines/:org/:repo", func(c *gin.Context) {
+ c.Status(http.StatusOK)
+ })
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusBadRequest {
+ t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusBadRequest)
+ }
+}
+
+func TestPipeline_Establish_NoPipeline(t *testing.T) {
+ // setup types
+ secret := "superSecret"
+
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ r := new(library.Repo)
+ r.SetID(1)
+ r.SetUserID(1)
+ r.SetHash("baz")
+ r.SetOrg("foo")
+ r.SetName("bar")
+ r.SetFullName("foo/bar")
+ r.SetVisibility("public")
+
+ u := new(library.User)
+ u.SetID(1)
+ u.SetName("foo")
+ u.SetToken("bar")
+ u.SetHash("baz")
+ u.SetAdmin(true)
+
+ m := &types.Metadata{
+ Database: &types.Database{
+ Driver: "foo",
+ Host: "foo",
+ },
+ Queue: &types.Queue{
+ Channel: "foo",
+ Driver: "foo",
+ Host: "foo",
+ },
+ Source: &types.Source{
+ Driver: "foo",
+ Host: "foo",
+ },
+ Vela: &types.Vela{
+ Address: "foo",
+ WebAddress: "foo",
+ },
+ }
+
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ at, err := tm.MintToken(mto)
+ if err != nil {
+ t.Errorf("unable to mint user access token: %v", err)
+ }
+
+ set := flag.NewFlagSet("test", 0)
+ set.String("clone-image", "target/vela-git:latest", "doc")
+
+ comp, err := native.New(cli.NewContext(nil, set, nil))
+ if err != nil {
+ t.Errorf("unable to create compiler: %v", err)
+ }
+
+ // setup database
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+
+ defer func() {
+ db.DeleteRepo(context.TODO(), r)
+ db.DeleteUser(u)
+ db.Close()
+ }()
+
+ _, _ = db.CreateRepo(context.TODO(), r)
+ _ = db.CreateUser(u)
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+ context.Request, _ = http.NewRequest(http.MethodGet, "/pipelines/foo/bar/148afb5bdc41ad69bf22588491333f7cf71135163", nil)
+ context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", at))
+
+ // setup github mock server
+ engine.GET("/api/v3/repos/:org/:repo/contents/:path", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/yml.json")
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup client
+ client, _ := github.NewTest(s.URL)
+
+ // setup vela mock server
+ engine.Use(func(c *gin.Context) { c.Set("metadata", m) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { compiler.WithGinContext(c, comp) })
+ engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
+ engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
+ engine.Use(org.Establish())
+ engine.Use(repo.Establish())
+ engine.Use(user.Establish())
+ engine.Use(Establish())
+ engine.GET("/pipelines/:org/:repo/:pipeline", func(c *gin.Context) {
+ c.Status(http.StatusOK)
+ })
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusOK)
+ }
+}
diff --git a/router/middleware/pipeline/testdata/yml.json b/router/middleware/pipeline/testdata/yml.json
new file mode 100644
index 000000000..458ecaa44
--- /dev/null
+++ b/router/middleware/pipeline/testdata/yml.json
@@ -0,0 +1,18 @@
+{
+ "type": "file",
+ "encoding": "base64",
+ "size": 5362,
+ "name": ".vela.yml",
+ "path": ".vela.yml",
+ "content": "LS0tCnZlcnNpb246ICIxIgoKbWV0YWRhdGE6CiAgb3M6IGxpbnV4CgpzdGVwczoKICAtIG5hbWU6IGJ1aWxkCiAgICBpbWFnZTogb3BlbmpkazpsYXRlc3QKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICBHUkFETEVfVVNFUl9IT01FOiAuZ3JhZGxlCiAgICAgIEdSQURMRV9PUFRTOiAtRG9yZy5ncmFkbGUuZGFlbW9uPWZhbHNlIC1Eb3JnLmdyYWRsZS53b3JrZXJzLm1heD0xIC1Eb3JnLmdyYWRsZS5wYXJhbGxlbD1mYWxzZQogICAgY29tbWFuZHM6CiAgICAgIC0gLi9ncmFkbGV3IGJ1aWxkIGRpc3RUYXIK",
+ "sha": "3d21ec53a331a6f037a91c368710b99387d012c1",
+ "url": "https://api.github.com/repos/octokit/octokit.rb/contents/.vela.yml",
+ "git_url": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1",
+ "html_url": "https://github.com/octokit/octokit.rb/blob/master/.vela.yml",
+ "download_url": "https://raw.githubusercontent.com/octokit/octokit.rb/master/.vela.yml",
+ "_links": {
+ "git": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1",
+ "self": "https://api.github.com/repos/octokit/octokit.rb/contents/.vela.yml",
+ "html": "https://github.com/octokit/octokit.rb/blob/master/.vela.yml"
+ }
+}
\ No newline at end of file
diff --git a/router/middleware/queue_test.go b/router/middleware/queue_test.go
index cfbd94010..80c22ce07 100644
--- a/router/middleware/queue_test.go
+++ b/router/middleware/queue_test.go
@@ -20,7 +20,8 @@ func TestMiddleware_Queue(t *testing.T) {
// setup types
var got queue.Service
- want, _ := redis.NewTest()
+ // signing keys are irrelevant here
+ want, _ := redis.NewTest("", "")
// setup context
gin.SetMode(gin.TestMode)
diff --git a/router/middleware/repo/doc.go b/router/middleware/repo/doc.go
index 6b6a33c97..028ebe70b 100644
--- a/router/middleware/repo/doc.go
+++ b/router/middleware/repo/doc.go
@@ -8,5 +8,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/router/middleware/repo"
+// import "github.com/go-vela/server/router/middleware/repo"
package repo
diff --git a/router/middleware/repo/repo.go b/router/middleware/repo/repo.go
index 8afa16813..fcc898f9e 100644
--- a/router/middleware/repo/repo.go
+++ b/router/middleware/repo/repo.go
@@ -5,18 +5,16 @@
package repo
import (
- "github.com/go-vela/server/router/middleware/org"
- "github.com/go-vela/server/router/middleware/user"
- "github.com/go-vela/types/library"
- "github.com/sirupsen/logrus"
-
- "github.com/go-vela/server/database"
- "github.com/go-vela/server/util"
-
"fmt"
"net/http"
"github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
)
// Retrieve gets the repo in the given context.
@@ -29,11 +27,13 @@ func Establish() gin.HandlerFunc {
return func(c *gin.Context) {
o := org.Retrieve(c)
u := user.Retrieve(c)
+ ctx := c.Request.Context()
- rParam := c.Param("repo")
+ rParam := util.PathParameter(c, "repo")
if len(rParam) == 0 {
retErr := fmt.Errorf("no repo parameter provided")
util.HandleError(c, http.StatusBadRequest, retErr)
+
return
}
@@ -46,10 +46,11 @@ func Establish() gin.HandlerFunc {
"user": u.GetName(),
}).Debugf("reading repo %s/%s", o, rParam)
- r, err := database.FromContext(c).GetRepo(o, rParam)
+ r, err := database.FromContext(c).GetRepoForOrg(ctx, o, rParam)
if err != nil {
- retErr := fmt.Errorf("unable to read repo %s/%s: %v", o, rParam, err)
+ retErr := fmt.Errorf("unable to read repo %s/%s: %w", o, rParam, err)
util.HandleError(c, http.StatusNotFound, retErr)
+
return
}
diff --git a/router/middleware/repo/repo_test.go b/router/middleware/repo/repo_test.go
index 63411640c..0274db0ec 100644
--- a/router/middleware/repo/repo_test.go
+++ b/router/middleware/repo/repo_test.go
@@ -5,6 +5,7 @@
package repo
import (
+ "context"
"net/http"
"net/http/httptest"
"reflect"
@@ -14,7 +15,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/go-vela/server/database"
- "github.com/go-vela/server/database/sqlite"
"github.com/go-vela/types/library"
)
@@ -48,6 +48,7 @@ func TestRepo_Establish(t *testing.T) {
want.SetLink("")
want.SetClone("")
want.SetBranch("")
+ want.SetTopics([]string{})
want.SetBuildLimit(0)
want.SetTimeout(0)
want.SetCounter(0)
@@ -66,15 +67,17 @@ func TestRepo_Establish(t *testing.T) {
got := new(library.Repo)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(context.TODO(), want)
+ db.Close()
}()
- _ = db.CreateRepo(want)
+ _, _ = db.CreateRepo(context.TODO(), want)
// setup context
gin.SetMode(gin.TestMode)
@@ -107,8 +110,11 @@ func TestRepo_Establish(t *testing.T) {
func TestRepo_Establish_NoOrgParameter(t *testing.T) {
// setup database
- db, _ := sqlite.NewTest()
- defer func() { _sql, _ := db.Sqlite.DB(); _sql.Close() }()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
// setup context
gin.SetMode(gin.TestMode)
@@ -134,8 +140,11 @@ func TestRepo_Establish_NoOrgParameter(t *testing.T) {
func TestRepo_Establish_NoRepoParameter(t *testing.T) {
// setup database
- db, _ := sqlite.NewTest()
- defer func() { _sql, _ := db.Sqlite.DB(); _sql.Close() }()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
// setup context
gin.SetMode(gin.TestMode)
@@ -161,8 +170,11 @@ func TestRepo_Establish_NoRepoParameter(t *testing.T) {
func TestRepo_Establish_NoRepo(t *testing.T) {
// setup database
- db, _ := sqlite.NewTest()
- defer func() { _sql, _ := db.Sqlite.DB(); _sql.Close() }()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
// setup context
gin.SetMode(gin.TestMode)
diff --git a/router/middleware/schedule/context.go b/router/middleware/schedule/context.go
new file mode 100644
index 000000000..7ce62871c
--- /dev/null
+++ b/router/middleware/schedule/context.go
@@ -0,0 +1,39 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "context"
+
+ "github.com/go-vela/types/library"
+)
+
+const key = "schedule"
+
+// Setter defines a context that enables setting values.
+type Setter interface {
+ Set(string, interface{})
+}
+
+// FromContext returns the Schedule associated with this context.
+func FromContext(c context.Context) *library.Schedule {
+ value := c.Value(key)
+ if value == nil {
+ return nil
+ }
+
+ s, ok := value.(*library.Schedule)
+ if !ok {
+ return nil
+ }
+
+ return s
+}
+
+// ToContext adds the Schedule to this context if it supports
+// the Setter interface.
+func ToContext(c Setter, s *library.Schedule) {
+ c.Set(key, s)
+}
diff --git a/router/middleware/schedule/context_test.go b/router/middleware/schedule/context_test.go
new file mode 100644
index 000000000..fb73d0b32
--- /dev/null
+++ b/router/middleware/schedule/context_test.go
@@ -0,0 +1,89 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "testing"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/types/library"
+)
+
+func TestSchedule_FromContext(t *testing.T) {
+ // setup types
+ num := int64(1)
+ want := &library.Schedule{ID: &num}
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+ context, _ := gin.CreateTestContext(nil)
+ context.Set(key, want)
+
+ // run test
+ got := FromContext(context)
+
+ if got != want {
+ t.Errorf("FromContext is %v, want %v", got, want)
+ }
+}
+
+func TestSchedule_FromContext_Bad(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+ context, _ := gin.CreateTestContext(nil)
+ context.Set(key, nil)
+
+ // run test
+ got := FromContext(context)
+
+ if got != nil {
+ t.Errorf("FromContext is %v, want nil", got)
+ }
+}
+
+func TestSchedule_FromContext_WrongType(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+ context, _ := gin.CreateTestContext(nil)
+ context.Set(key, 1)
+
+ // run test
+ got := FromContext(context)
+
+ if got != nil {
+ t.Errorf("FromContext is %v, want nil", got)
+ }
+}
+
+func TestSchedule_FromContext_Empty(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+ context, _ := gin.CreateTestContext(nil)
+
+ // run test
+ got := FromContext(context)
+
+ if got != nil {
+ t.Errorf("FromContext is %v, want nil", got)
+ }
+}
+
+func TestSchedule_ToContext(t *testing.T) {
+ // setup types
+ num := int64(1)
+ want := &library.Schedule{ID: &num}
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+ context, _ := gin.CreateTestContext(nil)
+ ToContext(context, want)
+
+ // run test
+ got := context.Value(key)
+
+ if got != want {
+ t.Errorf("ToContext is %v, want %v", got, want)
+ }
+}
diff --git a/router/middleware/schedule/schedule.go b/router/middleware/schedule/schedule.go
new file mode 100644
index 000000000..65f78c4e0
--- /dev/null
+++ b/router/middleware/schedule/schedule.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package schedule
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
+)
+
+// Retrieve gets the schedule in the given context.
+func Retrieve(c *gin.Context) *library.Schedule {
+ return FromContext(c)
+}
+
+// Establish sets the schedule in the given context.
+func Establish() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ r := repo.Retrieve(c)
+ u := user.Retrieve(c)
+ ctx := c.Request.Context()
+
+ sParam := util.PathParameter(c, "schedule")
+ if len(sParam) == 0 {
+ retErr := fmt.Errorf("no schedule parameter provided")
+ util.HandleError(c, http.StatusBadRequest, retErr)
+
+ return
+ }
+
+ // update engine logger with API metadata
+ //
+ // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
+ logrus.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Debugf("reading schedule %s for repo %s", sParam, r.GetFullName())
+
+ s, err := database.FromContext(c).GetScheduleForRepo(ctx, r, sParam)
+ if err != nil {
+ retErr := fmt.Errorf("unable to read schedule %s for repo %s: %w", sParam, r.GetFullName(), err)
+ util.HandleError(c, http.StatusNotFound, retErr)
+
+ return
+ }
+
+ ToContext(c, s)
+ c.Next()
+ }
+}
diff --git a/router/middleware/schedule_frequency.go b/router/middleware/schedule_frequency.go
new file mode 100644
index 000000000..243c2ad06
--- /dev/null
+++ b/router/middleware/schedule_frequency.go
@@ -0,0 +1,20 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package middleware
+
+import (
+ "time"
+
+ "github.com/gin-gonic/gin"
+)
+
+// ScheduleFrequency is a middleware function that attaches the scheduleminimumfrequency used
+// to limit the frequency which schedules can be run within the system.
+func ScheduleFrequency(scheduleFrequency time.Duration) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ c.Set("scheduleminimumfrequency", scheduleFrequency)
+ c.Next()
+ }
+}
diff --git a/router/middleware/schedule_frequency_test.go b/router/middleware/schedule_frequency_test.go
new file mode 100644
index 000000000..171f96ad3
--- /dev/null
+++ b/router/middleware/schedule_frequency_test.go
@@ -0,0 +1,47 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package middleware
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/gin-gonic/gin"
+)
+
+func TestMiddleware_ScheduleFrequency(t *testing.T) {
+ // setup types
+ var got time.Duration
+ want := 30 * time.Minute
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+ context.Request, _ = http.NewRequest(http.MethodGet, "/health", nil)
+
+ // setup mock server
+ engine.Use(ScheduleFrequency(want))
+ engine.GET("/health", func(c *gin.Context) {
+ got = c.Value("scheduleminimumfrequency").(time.Duration)
+
+ c.Status(http.StatusOK)
+ })
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("ScheduleFrequency returned %v, want %v", resp.Code, http.StatusOK)
+ }
+
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("ScheduleFrequency is %v, want %v", got, want)
+ }
+}
diff --git a/router/middleware/secret.go b/router/middleware/secret.go
index 90f497492..66f28af78 100644
--- a/router/middleware/secret.go
+++ b/router/middleware/secret.go
@@ -26,6 +26,7 @@ func Secrets(secrets map[string]secret.Service) gin.HandlerFunc {
for k, v := range secrets {
secret.ToContext(c, k, v)
}
+
c.Next()
}
}
diff --git a/router/middleware/secret_test.go b/router/middleware/secret_test.go
index ae6cb623b..9ca21fd24 100644
--- a/router/middleware/secret_test.go
+++ b/router/middleware/secret_test.go
@@ -10,7 +10,7 @@ import (
"reflect"
"testing"
- "github.com/go-vela/server/database/sqlite"
+ "github.com/go-vela/server/database"
"github.com/go-vela/server/secret"
"github.com/go-vela/server/secret/native"
@@ -51,13 +51,16 @@ func TestMiddleware_Secret(t *testing.T) {
func TestMiddleware_Secrets(t *testing.T) {
// setup types
- d, _ := sqlite.NewTest()
- defer func() { _sql, _ := d.Sqlite.DB(); _sql.Close() }()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
var got secret.Service
want, _ := native.New(
- native.WithDatabase(d),
+ native.WithDatabase(db),
)
s := map[string]secret.Service{"native": want}
diff --git a/router/middleware/secure_cookie_test.go b/router/middleware/secure_cookie_test.go
index 4681cb3ca..60fa73eb4 100644
--- a/router/middleware/secure_cookie_test.go
+++ b/router/middleware/secure_cookie_test.go
@@ -18,6 +18,7 @@ func TestCookie_SecureCookie(t *testing.T) {
type args struct {
secure bool
}
+
tests := []struct {
name string
args args
@@ -38,6 +39,7 @@ func TestCookie_SecureCookie(t *testing.T) {
want: true,
},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// setup context
diff --git a/router/middleware/service/doc.go b/router/middleware/service/doc.go
index 2d185d23e..698d41aaa 100644
--- a/router/middleware/service/doc.go
+++ b/router/middleware/service/doc.go
@@ -8,5 +8,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/router/middleware/service"
+// import "github.com/go-vela/server/router/middleware/service"
package service
diff --git a/router/middleware/service/service.go b/router/middleware/service/service.go
index 4e557345f..30abf92d0 100644
--- a/router/middleware/service/service.go
+++ b/router/middleware/service/service.go
@@ -9,16 +9,14 @@ import (
"net/http"
"strconv"
- "github.com/go-vela/server/router/middleware/org"
- "github.com/go-vela/server/router/middleware/user"
-
+ "github.com/gin-gonic/gin"
"github.com/go-vela/server/database"
"github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
"github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
"github.com/go-vela/server/util"
"github.com/go-vela/types/library"
-
- "github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
@@ -37,21 +35,24 @@ func Establish() gin.HandlerFunc {
u := user.Retrieve(c)
if r == nil {
- retErr := fmt.Errorf("repo %s/%s not found", o, c.Param("repo"))
+ retErr := fmt.Errorf("repo %s/%s not found", o, util.PathParameter(c, "repo"))
util.HandleError(c, http.StatusNotFound, retErr)
+
return
}
if b == nil {
- retErr := fmt.Errorf("build %s not found for repo %s", c.Param("build"), r.GetFullName())
+ retErr := fmt.Errorf("build %s not found for repo %s", util.PathParameter(c, "build"), r.GetFullName())
util.HandleError(c, http.StatusNotFound, retErr)
+
return
}
- sParam := c.Param("service")
+ sParam := util.PathParameter(c, "service")
if len(sParam) == 0 {
retErr := fmt.Errorf("no service parameter provided")
util.HandleError(c, http.StatusBadRequest, retErr)
+
return
}
@@ -59,6 +60,7 @@ func Establish() gin.HandlerFunc {
if err != nil {
retErr := fmt.Errorf("malformed service parameter provided: %s", sParam)
util.HandleError(c, http.StatusBadRequest, retErr)
+
return
}
@@ -73,11 +75,11 @@ func Establish() gin.HandlerFunc {
"user": u.GetName(),
}).Debugf("reading service %s/%d/%d", r.GetFullName(), b.GetNumber(), number)
- s, err := database.FromContext(c).GetService(number, b)
+ s, err := database.FromContext(c).GetServiceForBuild(b, number)
if err != nil {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to read service %s/%d/%d: %v", r.GetFullName(), b.GetNumber(), number, err)
+ retErr := fmt.Errorf("unable to read service %s/%d/%d: %w", r.GetFullName(), b.GetNumber(), number, err)
util.HandleError(c, http.StatusNotFound, retErr)
+
return
}
diff --git a/router/middleware/service/service_test.go b/router/middleware/service/service_test.go
index 3b324d9c4..70a8024c3 100644
--- a/router/middleware/service/service_test.go
+++ b/router/middleware/service/service_test.go
@@ -5,17 +5,16 @@
package service
import (
+ "context"
"net/http"
"net/http/httptest"
"reflect"
"testing"
- "github.com/go-vela/server/router/middleware/org"
-
"github.com/gin-gonic/gin"
"github.com/go-vela/server/database"
- "github.com/go-vela/server/database/sqlite"
"github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
"github.com/go-vela/server/router/middleware/repo"
"github.com/go-vela/types/library"
)
@@ -74,19 +73,21 @@ func TestService_Establish(t *testing.T) {
got := new(library.Service)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from builds;")
- db.Sqlite.Exec("delete from services;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteBuild(context.TODO(), b)
+ db.DeleteRepo(context.TODO(), r)
+ db.DeleteService(want)
+ db.Close()
}()
- _ = db.CreateRepo(r)
- _ = db.CreateBuild(b)
- _ = db.CreateService(want)
+ _, _ = db.CreateRepo(context.TODO(), r)
+ _, _ = db.CreateBuild(context.TODO(), b)
+ _, _ = db.CreateService(want)
// setup context
gin.SetMode(gin.TestMode)
@@ -121,8 +122,11 @@ func TestService_Establish(t *testing.T) {
func TestService_Establish_NoRepo(t *testing.T) {
// setup database
- db, _ := sqlite.NewTest()
- defer func() { _sql, _ := db.Sqlite.DB(); _sql.Close() }()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
// setup context
gin.SetMode(gin.TestMode)
@@ -158,15 +162,17 @@ func TestService_Establish_NoBuild(t *testing.T) {
r.SetVisibility("public")
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(context.TODO(), r)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(context.TODO(), r)
// setup context
gin.SetMode(gin.TestMode)
@@ -209,17 +215,19 @@ func TestService_Establish_NoServiceParameter(t *testing.T) {
b.SetNumber(1)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from builds;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteBuild(context.TODO(), b)
+ db.DeleteRepo(context.TODO(), r)
+ db.Close()
}()
- _ = db.CreateRepo(r)
- _ = db.CreateBuild(b)
+ _, _ = db.CreateRepo(context.TODO(), r)
+ _, _ = db.CreateBuild(context.TODO(), b)
// setup context
gin.SetMode(gin.TestMode)
@@ -263,17 +271,19 @@ func TestService_Establish_InvalidServiceParameter(t *testing.T) {
b.SetNumber(1)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from builds;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteBuild(context.TODO(), b)
+ db.DeleteRepo(context.TODO(), r)
+ db.Close()
}()
- _ = db.CreateRepo(r)
- _ = db.CreateBuild(b)
+ _, _ = db.CreateRepo(context.TODO(), r)
+ _, _ = db.CreateBuild(context.TODO(), b)
// setup context
gin.SetMode(gin.TestMode)
@@ -317,17 +327,19 @@ func TestService_Establish_NoService(t *testing.T) {
b.SetNumber(1)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from builds;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteBuild(context.TODO(), b)
+ db.DeleteRepo(context.TODO(), r)
+ db.Close()
}()
- _ = db.CreateRepo(r)
- _ = db.CreateBuild(b)
+ _, _ = db.CreateRepo(context.TODO(), r)
+ _, _ = db.CreateBuild(context.TODO(), b)
// setup context
gin.SetMode(gin.TestMode)
diff --git a/router/middleware/signing.go b/router/middleware/signing.go
new file mode 100644
index 000000000..05c63b8e5
--- /dev/null
+++ b/router/middleware/signing.go
@@ -0,0 +1,18 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package middleware
+
+import (
+ "github.com/gin-gonic/gin"
+)
+
+// QueueSigningPrivateKey is a middleware function that attaches the private key used
+// to sign items that are pushed to the queue.
+func QueueSigningPrivateKey(key string) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ c.Set("queue.private-key", key)
+ c.Next()
+ }
+}
diff --git a/router/middleware/step/doc.go b/router/middleware/step/doc.go
index e1e0b26ba..45ddd7e61 100644
--- a/router/middleware/step/doc.go
+++ b/router/middleware/step/doc.go
@@ -8,5 +8,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/router/middleware/step"
+// import "github.com/go-vela/server/router/middleware/step"
package step
diff --git a/router/middleware/step/step.go b/router/middleware/step/step.go
index 3ae6409ee..27d517efe 100644
--- a/router/middleware/step/step.go
+++ b/router/middleware/step/step.go
@@ -9,16 +9,14 @@ import (
"net/http"
"strconv"
- "github.com/go-vela/server/router/middleware/org"
- "github.com/go-vela/server/router/middleware/user"
-
+ "github.com/gin-gonic/gin"
"github.com/go-vela/server/database"
"github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
"github.com/go-vela/server/router/middleware/repo"
+ "github.com/go-vela/server/router/middleware/user"
"github.com/go-vela/server/util"
"github.com/go-vela/types/library"
-
- "github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
@@ -37,21 +35,24 @@ func Establish() gin.HandlerFunc {
u := user.Retrieve(c)
if r == nil {
- retErr := fmt.Errorf("repo %s/%s not found", o, c.Param("repo"))
+ retErr := fmt.Errorf("repo %s/%s not found", o, util.PathParameter(c, "repo"))
util.HandleError(c, http.StatusNotFound, retErr)
+
return
}
if b == nil {
- retErr := fmt.Errorf("build %s not found for repo %s", c.Param("build"), r.GetFullName())
+ retErr := fmt.Errorf("build %s not found for repo %s", util.PathParameter(c, "build"), r.GetFullName())
util.HandleError(c, http.StatusNotFound, retErr)
+
return
}
- sParam := c.Param("step")
+ sParam := util.PathParameter(c, "step")
if len(sParam) == 0 {
retErr := fmt.Errorf("no step parameter provided")
util.HandleError(c, http.StatusBadRequest, retErr)
+
return
}
@@ -59,6 +60,7 @@ func Establish() gin.HandlerFunc {
if err != nil {
retErr := fmt.Errorf("malformed step parameter provided: %s", sParam)
util.HandleError(c, http.StatusBadRequest, retErr)
+
return
}
@@ -73,11 +75,11 @@ 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 {
- // nolint: lll // ignore long line length due to error message
- retErr := fmt.Errorf("unable to read step %s/%d/%d: %v", r.GetFullName(), b.GetNumber(), number, err)
+ retErr := fmt.Errorf("unable to read step %s/%d/%d: %w", r.GetFullName(), b.GetNumber(), number, err)
util.HandleError(c, http.StatusNotFound, retErr)
+
return
}
diff --git a/router/middleware/step/step_test.go b/router/middleware/step/step_test.go
index e0367c887..9ca2c8faf 100644
--- a/router/middleware/step/step_test.go
+++ b/router/middleware/step/step_test.go
@@ -5,17 +5,16 @@
package step
import (
+ "context"
"net/http"
"net/http/httptest"
"reflect"
"testing"
- "github.com/go-vela/server/router/middleware/org"
-
"github.com/gin-gonic/gin"
"github.com/go-vela/server/database"
- "github.com/go-vela/server/database/sqlite"
"github.com/go-vela/server/router/middleware/build"
+ "github.com/go-vela/server/router/middleware/org"
"github.com/go-vela/server/router/middleware/repo"
"github.com/go-vela/types/library"
)
@@ -76,19 +75,21 @@ func TestStep_Establish(t *testing.T) {
got := new(library.Step)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from builds;")
- db.Sqlite.Exec("delete from steps;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteBuild(context.TODO(), b)
+ db.DeleteRepo(context.TODO(), r)
+ db.DeleteStep(want)
+ db.Close()
}()
- _ = db.CreateRepo(r)
- _ = db.CreateBuild(b)
- _ = db.CreateStep(want)
+ _, _ = db.CreateRepo(context.TODO(), r)
+ _, _ = db.CreateBuild(context.TODO(), b)
+ _, _ = db.CreateStep(want)
// setup context
gin.SetMode(gin.TestMode)
@@ -123,8 +124,11 @@ func TestStep_Establish(t *testing.T) {
func TestStep_Establish_NoRepo(t *testing.T) {
// setup database
- db, _ := sqlite.NewTest()
- defer func() { _sql, _ := db.Sqlite.DB(); _sql.Close() }()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
// setup context
gin.SetMode(gin.TestMode)
@@ -160,15 +164,17 @@ func TestStep_Establish_NoBuild(t *testing.T) {
r.SetVisibility("public")
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteRepo(context.TODO(), r)
+ db.Close()
}()
- _ = db.CreateRepo(r)
+ _, _ = db.CreateRepo(context.TODO(), r)
// setup context
gin.SetMode(gin.TestMode)
@@ -211,17 +217,19 @@ func TestStep_Establish_NoStepParameter(t *testing.T) {
b.SetNumber(1)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from builds;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteBuild(context.TODO(), b)
+ db.DeleteRepo(context.TODO(), r)
+ db.Close()
}()
- _ = db.CreateRepo(r)
- _ = db.CreateBuild(b)
+ _, _ = db.CreateRepo(context.TODO(), r)
+ _, _ = db.CreateBuild(context.TODO(), b)
// setup context
gin.SetMode(gin.TestMode)
@@ -265,17 +273,19 @@ func TestStep_Establish_InvalidStepParameter(t *testing.T) {
b.SetNumber(1)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from builds;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteBuild(context.TODO(), b)
+ db.DeleteRepo(context.TODO(), r)
+ db.Close()
}()
- _ = db.CreateRepo(r)
- _ = db.CreateBuild(b)
+ _, _ = db.CreateRepo(context.TODO(), r)
+ _, _ = db.CreateBuild(context.TODO(), b)
// setup context
gin.SetMode(gin.TestMode)
@@ -319,17 +329,19 @@ func TestStep_Establish_NoStep(t *testing.T) {
b.SetNumber(1)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from repos;")
- db.Sqlite.Exec("delete from builds;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteBuild(context.TODO(), b)
+ db.DeleteRepo(context.TODO(), r)
+ db.Close()
}()
- _ = db.CreateRepo(r)
- _ = db.CreateBuild(b)
+ _, _ = db.CreateRepo(context.TODO(), r)
+ _, _ = db.CreateBuild(context.TODO(), b)
// setup context
gin.SetMode(gin.TestMode)
diff --git a/router/middleware/token/token.go b/router/middleware/token/token.go
deleted file mode 100644
index 51e9a2e07..000000000
--- a/router/middleware/token/token.go
+++ /dev/null
@@ -1,211 +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 token
-
-import (
- "errors"
- "fmt"
- "net/http"
- "net/url"
- "time"
-
- "github.com/gin-gonic/gin"
- "github.com/go-vela/server/database"
- "github.com/go-vela/types"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/library"
-
- "github.com/golang-jwt/jwt/v4"
- "github.com/golang-jwt/jwt/v4/request"
- "github.com/sirupsen/logrus"
-)
-
-type Claims struct {
- IsAdmin bool `json:"is_admin"`
- IsActive bool `json:"is_active"`
- jwt.StandardClaims
-}
-
-// Compose generates an refresh and access token pair unique
-// to the provided user and sets a secure cookie.
-// It uses a secret hash, which is unique for every user.
-// The hash signs the token to guarantee the signature is unique
-// per token. The refresh token is returned to store with the user
-// in the database.
-// nolint:lll // reference links cause long lines
-func Compose(c *gin.Context, u *library.User) (string, string, error) {
- // grab the metadata from the context to pull in provided
- // cookie duration information
- m := c.MustGet("metadata").(*types.Metadata)
-
- // create a refresh with the provided duration
- refreshToken, refreshExpiry, err := CreateRefreshToken(u, m.Vela.RefreshTokenDuration)
- if err != nil {
- return "", "", err
- }
-
- // create an access token with the provided duration
- accessToken, err := CreateAccessToken(u, m.Vela.AccessTokenDuration)
- if err != nil {
- return "", "", err
- }
-
- // parse the address for the backend server
- // so we can set it for the cookie domain
- addr, err := url.Parse(m.Vela.Address)
- if err != nil {
- return "", "", err
- }
-
- // set the SameSite value for the cookie
- // https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#samesite-attribute
- // We set to Lax because we will have links from source provider web UI.
- // Setting this to Strict would force a login when navigating via source provider web UI links.
- c.SetSameSite(http.SameSiteLaxMode)
- // set the cookie with the refresh token as a HttpOnly, Secure cookie
- // https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#httponly-attribute
- // https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#secure-attribute
- c.SetCookie(constants.RefreshTokenName, refreshToken, refreshExpiry, "/", addr.Hostname(), c.Value("securecookie").(bool), true)
-
- // return the refresh and access tokens
- return refreshToken, accessToken, nil
-}
-
-// Parse scans the signed JWT token as a string and extracts
-// the user login from the claims to be looked up in the database.
-// This function will return an error for a few different reasons:
-//
-// * the token signature doesn't match what is expected
-// * the token signing method doesn't match what is expected
-// * the token is invalid (potentially expired or improper).
-func Parse(t string, db database.Service) (*library.User, error) {
- u := new(library.User)
-
- // create a new JWT parser
- p := &jwt.Parser{
- // explicitly only allow these signing methods
- ValidMethods: []string{jwt.SigningMethodHS256.Name},
- }
-
- // parse the signed JWT token string
- // parse also validates the claims and token by default.
- _, err := p.ParseWithClaims(t, &Claims{}, func(token *jwt.Token) (interface{}, error) {
- var err error
-
- // extract the claims from the token
- claims := token.Claims.(*Claims)
- name := claims.Subject
-
- // check if subject has a value in claims;
- // we can save a db lookup attempt
- if len(name) == 0 {
- return nil, errors.New("no subject defined")
- }
-
- // ParseWithClaims will skip expiration check
- // if expiration has default value;
- // forcing a check and exiting if not set
- if claims.ExpiresAt == 0 {
- return nil, errors.New("token has no expiration")
- }
-
- // lookup the user in the database
- logrus.WithField("user", name).Debugf("reading user %s", name)
- u, err = db.GetUserName(name)
- return []byte(u.GetHash()), err
- })
-
- // there will be an error if we're not able to parse
- // the token, eg. due to expiration, invalid signature, etc
- if err != nil {
- return nil, fmt.Errorf("invalid token provided for %s: %w", u.GetName(), err)
- }
-
- return u, nil
-}
-
-// RetrieveAccessToken gets the passed in access token from the header in the request.
-func RetrieveAccessToken(r *http.Request) (accessToken string, err error) {
- accessToken, err = request.AuthorizationHeaderExtractor.ExtractToken(r)
-
- return
-}
-
-// RetrieveRefreshToken gets the refresh token sent along with the request as a cookie.
-func RetrieveRefreshToken(r *http.Request) (string, error) {
- refreshToken, err := r.Cookie(constants.RefreshTokenName)
-
- if refreshToken == nil || len(refreshToken.Value) == 0 {
- // cookie will not be sent if it has expired
- return "", fmt.Errorf("refresh token expired or not provided")
- }
-
- return refreshToken.Value, err
-}
-
-// CreateAccessToken creates a new access token for the given user and duration.
-func CreateAccessToken(u *library.User, d time.Duration) (string, error) {
- now := time.Now()
- exp := now.Add(d)
-
- claims := &Claims{
- IsActive: u.GetActive(),
- IsAdmin: u.GetAdmin(),
- StandardClaims: jwt.StandardClaims{
- Subject: u.GetName(),
- IssuedAt: now.Unix(),
- ExpiresAt: exp.Unix(),
- },
- }
-
- t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
-
- token, err := t.SignedString([]byte(u.GetHash()))
- if err != nil {
- return "", err
- }
-
- return token, nil
-}
-
-// CreateCreateRefreshToken creates a new refresh token for the given user and duration.
-func CreateRefreshToken(u *library.User, d time.Duration) (string, int, error) {
- exp := time.Now().Add(d)
-
- claims := jwt.StandardClaims{}
- claims.Subject = u.GetName()
- claims.ExpiresAt = exp.Unix()
-
- t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
-
- refreshToken, err := t.SignedString([]byte(u.GetHash()))
- if err != nil {
- return "", 0, err
- }
-
- return refreshToken, int(d.Seconds()), nil
-}
-
-// Refresh returns a new access token, if the provided refreshToken is valid.
-func Refresh(c *gin.Context, refreshToken string) (string, error) {
- // get the metadata
- m := c.MustGet("metadata").(*types.Metadata)
- // get a reference to the database
- db := database.FromContext(c)
-
- // parse (which also validates) the token
- u, err := Parse(refreshToken, db)
- if err != nil {
- return "", err
- }
-
- // create a new access token
- at, err := CreateAccessToken(u, m.Vela.AccessTokenDuration)
- if err != nil {
- return "", err
- }
-
- return at, nil
-}
diff --git a/router/middleware/token/token_test.go b/router/middleware/token/token_test.go
deleted file mode 100644
index 6e22c8111..000000000
--- a/router/middleware/token/token_test.go
+++ /dev/null
@@ -1,539 +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 token
-
-import (
- "fmt"
- "net/http"
- "net/http/httptest"
- "reflect"
- "strings"
- "testing"
- "time"
-
- "github.com/gin-gonic/gin"
- "github.com/go-vela/server/database/sqlite"
- "github.com/go-vela/types"
- "github.com/go-vela/types/constants"
- "github.com/go-vela/types/library"
-
- jwt "github.com/golang-jwt/jwt/v4"
-)
-
-func TestToken_Compose(t *testing.T) {
- // setup types
- u := new(library.User)
- u.SetID(1)
- u.SetName("foo")
- u.SetToken("bar")
- u.SetHash("baz")
-
- d := time.Minute * 5
- now := time.Now()
- exp := now.Add(d)
-
- claims := &Claims{
- IsActive: u.GetActive(),
- IsAdmin: u.GetAdmin(),
- StandardClaims: jwt.StandardClaims{
- Subject: u.GetName(),
- IssuedAt: now.Unix(),
- ExpiresAt: exp.Unix(),
- },
- }
-
- tkn := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
-
- want, err := tkn.SignedString([]byte(u.GetHash()))
- if err != nil {
- t.Errorf("Unable to create test token: %v", err)
- }
-
- m := &types.Metadata{
- Vela: &types.Vela{
- AccessTokenDuration: d,
- },
- }
-
- gin.SetMode(gin.TestMode)
-
- resp := httptest.NewRecorder()
- context, _ := gin.CreateTestContext(resp)
- context.Set("metadata", m)
- context.Set("securecookie", false)
-
- // run test
- _, got, err := Compose(context, u)
- if err != nil {
- t.Errorf("Compose returned err: %v", err)
- }
-
- if !strings.EqualFold(got, want) {
- t.Errorf("Compose is %v, want %v", got, want)
- }
-}
-
-func TestToken_Parse(t *testing.T) {
- // setup types
- want := new(library.User)
- want.SetID(1)
- want.SetName("foo")
- want.SetRefreshToken("fresh")
- want.SetToken("bar")
- want.SetHash("baz")
- want.SetActive(false)
- want.SetAdmin(false)
- want.SetFavorites([]string{})
-
- m := &types.Metadata{
- Vela: &types.Vela{
- AccessTokenDuration: time.Minute * 5,
- },
- }
-
- gin.SetMode(gin.TestMode)
-
- resp := httptest.NewRecorder()
- context, _ := gin.CreateTestContext(resp)
- context.Set("metadata", m)
-
- tkn, err := CreateAccessToken(want, time.Minute*5)
- if err != nil {
- t.Errorf("Unable to create token: %v", err)
- }
-
- // setup database
- db, _ := sqlite.NewTest()
-
- defer func() {
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
- }()
-
- _ = db.CreateUser(want)
-
- // run test
- got, err := Parse(tkn, db)
- if err != nil {
- t.Errorf("Parse returned err: %v", err)
- }
-
- if !reflect.DeepEqual(got, want) {
- t.Errorf("Parse is %v, want %v", got, want)
- }
-}
-
-func TestToken_Parse_Error_NoParse(t *testing.T) {
- // setup types
- u := new(library.User)
- u.SetID(1)
- u.SetName("foo")
- u.SetToken("bar")
- u.SetHash("baz")
-
- // setup database
- db, _ := sqlite.NewTest()
-
- defer func() {
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
- }()
-
- _ = db.CreateUser(u)
-
- // run test
- got, err := Parse("!@#$%^&*()", db)
- if err == nil {
- t.Errorf("Parse should have returned err")
- }
-
- if got != nil {
- t.Errorf("Parse is %v, want nil", got)
- }
-}
-
-func TestToken_Parse_Error_InvalidSignature(t *testing.T) {
- // setup types
- u := new(library.User)
- u.SetID(1)
- u.SetName("foo")
- u.SetToken("bar")
- u.SetHash("baz")
-
- claims := &Claims{
- IsActive: u.GetActive(),
- IsAdmin: u.GetAdmin(),
- StandardClaims: jwt.StandardClaims{
- Subject: u.GetName(),
- },
- }
- tkn := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
-
- token, err := tkn.SignedString([]byte(u.GetHash()))
- if err != nil {
- t.Errorf("Unable to create test token: %v", err)
- }
-
- // setup database
- db, _ := sqlite.NewTest()
-
- defer func() {
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
- }()
-
- _ = db.CreateUser(u)
-
- // run test
- got, err := Parse(token, db)
- if err == nil {
- t.Errorf("Parse should have returned err")
- }
-
- if got != nil {
- t.Errorf("Parse is %v, want nil", got)
- }
-}
-
-func TestToken_Parse_AccessToken_Expired(t *testing.T) {
- // setup types
- u := new(library.User)
- u.SetID(1)
- u.SetName("foo")
- u.SetToken("bar")
- u.SetHash("baz")
-
- tkn, err := CreateAccessToken(u, time.Minute*-1)
- if err != nil {
- t.Errorf("Unable to create token: %v", err)
- }
-
- // setup database
- db, _ := sqlite.NewTest()
-
- defer func() {
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
- }()
-
- _ = db.CreateUser(u)
-
- // run test
- _, err = Parse(tkn, db)
- if err == nil {
- t.Errorf("Parse should return error due to expiration")
- }
-}
-
-func TestToken_Parse_AccessToken_NoSubject(t *testing.T) {
- // setup types
- u := new(library.User)
- u.SetID(1)
- u.SetName("foo")
- u.SetToken("bar")
- u.SetHash("baz")
-
- claims := &Claims{
- IsActive: u.GetActive(),
- IsAdmin: u.GetAdmin(),
- StandardClaims: jwt.StandardClaims{
- ExpiresAt: 42,
- },
- }
- tkn := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
-
- token, err := tkn.SignedString([]byte(u.GetHash()))
- if err != nil {
- t.Errorf("Unable to create test token: %v", err)
- }
-
- // setup database
- db, _ := sqlite.NewTest()
-
- defer func() {
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
- }()
-
- _ = db.CreateUser(u)
-
- // run test
- got, err := Parse(token, db)
- if err == nil {
- t.Errorf("Parse should have returned err")
- }
-
- if got != nil {
- t.Errorf("Parse is %v, want nil", got)
- }
-}
-
-func TestToken_Parse_AccessToken_NoExpiration(t *testing.T) {
- // setup types
- u := new(library.User)
- u.SetID(1)
- u.SetName("foo")
- u.SetToken("bar")
- u.SetHash("baz")
-
- claims := &Claims{
- IsActive: u.GetActive(),
- IsAdmin: u.GetAdmin(),
- StandardClaims: jwt.StandardClaims{
- Subject: u.GetName(),
- },
- }
- tkn := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
-
- token, err := tkn.SignedString([]byte(u.GetHash()))
- if err != nil {
- t.Errorf("Unable to create test token: %v", err)
- }
-
- // setup database
- db, _ := sqlite.NewTest()
-
- defer func() {
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
- }()
-
- _ = db.CreateUser(u)
-
- // run test
- got, err := Parse(token, db)
- if err == nil {
- t.Errorf("Parse should have returned err")
- }
-
- if got != nil {
- t.Errorf("Parse is %v, want nil", got)
- }
-}
-
-func TestToken_Refresh(t *testing.T) {
- // setup types
- u := new(library.User)
- u.SetID(1)
- u.SetName("foo")
- u.SetToken("bar")
- u.SetHash("baz")
-
- d := time.Minute * 5
-
- m := &types.Metadata{
- Vela: &types.Vela{
- AccessTokenDuration: d,
- },
- }
-
- rt, _, err := CreateRefreshToken(u, d)
- if err != nil {
- t.Errorf("unable to create refresh token")
- }
-
- u.SetRefreshToken(rt)
-
- // setup database
- db, _ := sqlite.NewTest()
-
- defer func() {
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
- }()
-
- _ = db.CreateUser(u)
-
- // set up context
- gin.SetMode(gin.TestMode)
-
- resp := httptest.NewRecorder()
- context, _ := gin.CreateTestContext(resp)
- context.Set("metadata", m)
- context.Set("database", db)
-
- // run tests
- got, err := Refresh(context, rt)
- if err != nil {
- t.Error("Refresh should not error")
- }
-
- if len(got) == 0 {
- t.Errorf("Refresh should have returned an access token")
- }
-}
-
-func TestToken_Refresh_Expired(t *testing.T) {
- // setup types
- u := new(library.User)
- u.SetID(1)
- u.SetName("foo")
- u.SetToken("bar")
- u.SetHash("baz")
-
- d := time.Minute * -1
-
- m := &types.Metadata{
- Vela: &types.Vela{
- AccessTokenDuration: d,
- },
- }
-
- rt, _, err := CreateRefreshToken(u, d)
- if err != nil {
- t.Errorf("unable to create refresh token")
- }
-
- u.SetRefreshToken(rt)
-
- // setup database
- db, _ := sqlite.NewTest()
-
- defer func() {
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
- }()
-
- _ = db.CreateUser(u)
-
- // set up context
- gin.SetMode(gin.TestMode)
-
- resp := httptest.NewRecorder()
- context, _ := gin.CreateTestContext(resp)
- context.Set("metadata", m)
- context.Set("database", db)
-
- // run tests
- _, err = Refresh(context, rt)
- if err == nil {
- t.Error("Refresh with expired token should error")
- }
-}
-
-func TestToken_Refresh_TokenMissing(t *testing.T) {
- // setup types
- u := new(library.User)
- u.SetID(1)
- u.SetName("foo")
- u.SetToken("bar")
- u.SetHash("baz")
-
- d := time.Minute * -1
-
- m := &types.Metadata{
- Vela: &types.Vela{
- AccessTokenDuration: d,
- },
- }
-
- rt, _, err := CreateRefreshToken(u, d)
- if err != nil {
- t.Errorf("unable to create refresh token")
- }
-
- // setup database
- db, _ := sqlite.NewTest()
-
- defer func() {
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
- }()
-
- _ = db.CreateUser(u)
-
- // set up context
- gin.SetMode(gin.TestMode)
-
- resp := httptest.NewRecorder()
- context, _ := gin.CreateTestContext(resp)
- context.Set("metadata", m)
- context.Set("database", db)
-
- // run tests
- _, err = Refresh(context, rt)
- if err == nil {
- t.Error("Refresh with token that doesn't exist in database should error")
- }
-}
-
-func TestToken_Retrieve_Refresh(t *testing.T) {
- // setup types
- want := "fresh"
-
- request, _ := http.NewRequest(http.MethodGet, "/test", nil)
- request.AddCookie(&http.Cookie{
- Name: constants.RefreshTokenName,
- Value: want,
- })
-
- // run test
- got, err := RetrieveRefreshToken(request)
- if err != nil {
- t.Errorf("Retrieve returned err: %v", err)
- }
-
- if !strings.EqualFold(got, want) {
- t.Errorf("Retrieve is %v, want %v", got, want)
- }
-}
-
-func TestToken_Retrieve_Access(t *testing.T) {
- // setup types
- want := "foobar"
-
- header := fmt.Sprintf("Bearer %s", want)
- request, _ := http.NewRequest(http.MethodGet, "/test", nil)
- request.Header.Set("Authorization", header)
-
- // run test
- got, err := RetrieveAccessToken(request)
- if err != nil {
- t.Errorf("Retrieve returned err: %v", err)
- }
-
- if !strings.EqualFold(got, want) {
- t.Errorf("Retrieve is %v, want %v", got, want)
- }
-}
-
-func TestToken_Retrieve_Access_Error(t *testing.T) {
- // setup types
- request, _ := http.NewRequest(http.MethodGet, "/test", nil)
-
- // run test
- got, err := RetrieveAccessToken(request)
- if err == nil {
- t.Errorf("Retrieve should have returned err")
- }
-
- if len(got) > 0 {
- t.Errorf("Retrieve is %v, want \"\"", got)
- }
-}
-
-func TestToken_Retrieve_Refresh_Error(t *testing.T) {
- // setup types
- request, _ := http.NewRequest(http.MethodGet, "/test", nil)
-
- // run test
- got, err := RetrieveRefreshToken(request)
- if err == nil {
- t.Errorf("Retrieve should have returned err")
- }
-
- if len(got) > 0 {
- t.Errorf("Retrieve is %v, want \"\"", got)
- }
-}
diff --git a/router/middleware/token_manager.go b/router/middleware/token_manager.go
new file mode 100644
index 000000000..0d8d78108
--- /dev/null
+++ b/router/middleware/token_manager.go
@@ -0,0 +1,20 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package middleware
+
+import (
+ "github.com/gin-gonic/gin"
+
+ "github.com/go-vela/server/internal/token"
+)
+
+// TokenManager is a middleware function that attaches the token manager
+// to the context of every http.Request.
+func TokenManager(m *token.Manager) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ c.Set("token-manager", m)
+ c.Next()
+ }
+}
diff --git a/router/middleware/token_manager_test.go b/router/middleware/token_manager_test.go
new file mode 100644
index 000000000..2ba6e23f2
--- /dev/null
+++ b/router/middleware/token_manager_test.go
@@ -0,0 +1,53 @@
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package middleware
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "testing"
+
+ "github.com/go-vela/server/internal/token"
+
+ "github.com/gin-gonic/gin"
+)
+
+func TestMiddleware_TokenManager(t *testing.T) {
+ // setup types
+ s := httptest.NewServer(http.NotFoundHandler())
+ defer s.Close()
+
+ var got *token.Manager
+
+ want := new(token.Manager)
+ want.PrivateKey = "123abc"
+
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ context, engine := gin.CreateTestContext(resp)
+ context.Request, _ = http.NewRequest(http.MethodGet, "/health", nil)
+
+ // setup mock server
+ engine.Use(TokenManager(want))
+ engine.GET("/health", func(c *gin.Context) {
+ got = c.MustGet("token-manager").(*token.Manager)
+
+ c.Status(http.StatusOK)
+ })
+
+ // run test
+ engine.ServeHTTP(context.Writer, context.Request)
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("TokenManager returned %v, want %v", resp.Code, http.StatusOK)
+ }
+
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("TokenManager is %v, want %v", got, want)
+ }
+}
diff --git a/router/middleware/user/doc.go b/router/middleware/user/doc.go
index d86024512..42181f07d 100644
--- a/router/middleware/user/doc.go
+++ b/router/middleware/user/doc.go
@@ -8,5 +8,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/router/middleware/user"
+// import "github.com/go-vela/server/router/middleware/user"
package user
diff --git a/router/middleware/user/user.go b/router/middleware/user/user.go
index 58d2cab87..ad94a5e0d 100644
--- a/router/middleware/user/user.go
+++ b/router/middleware/user/user.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
@@ -9,9 +9,10 @@ import (
"strings"
"github.com/go-vela/server/database"
- "github.com/go-vela/server/router/middleware/token"
+ "github.com/go-vela/server/router/middleware/claims"
"github.com/go-vela/server/util"
+ "github.com/go-vela/types/constants"
"github.com/go-vela/types/library"
"github.com/gin-gonic/gin"
@@ -26,20 +27,11 @@ func Retrieve(c *gin.Context) *library.User {
// Establish sets the user in the given context.
func Establish() gin.HandlerFunc {
return func(c *gin.Context) {
- // get the access token from the request
- at, err := token.RetrieveAccessToken(c.Request)
- if err != nil {
- util.HandleError(c, http.StatusUnauthorized, err)
- return
- }
+ cl := claims.Retrieve(c)
- // special handling for workers
- secret := c.MustGet("secret").(string)
- if strings.EqualFold(at, secret) {
+ // if token is not a user token or claims were not retrieved, establish empty user to better handle nil checks
+ if cl == nil || !strings.EqualFold(cl.TokenType, constants.UserAccessTokenType) {
u := new(library.User)
- u.SetName("vela-worker")
- u.SetActive(true)
- u.SetAdmin(true)
ToContext(c, u)
c.Next()
@@ -49,8 +41,8 @@ func Establish() gin.HandlerFunc {
logrus.Debugf("parsing user access token")
- // parse and validate the token and return the associated the user
- u, err := token.Parse(at, database.FromContext(c))
+ // lookup user in claims subject in the database
+ u, err := database.FromContext(c).GetUserForName(cl.Subject)
if err != nil {
util.HandleError(c, http.StatusUnauthorized, err)
return
diff --git a/router/middleware/user/user_test.go b/router/middleware/user/user_test.go
index 6aefc9e8a..0ea18dfcc 100644
--- a/router/middleware/user/user_test.go
+++ b/router/middleware/user/user_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
@@ -12,16 +12,15 @@ import (
"testing"
"time"
+ "github.com/gin-gonic/gin"
"github.com/go-vela/server/database"
- "github.com/go-vela/server/database/sqlite"
- "github.com/go-vela/server/router/middleware/token"
+ "github.com/go-vela/server/internal/token"
+ "github.com/go-vela/server/router/middleware/claims"
"github.com/go-vela/server/scm"
"github.com/go-vela/server/scm/github"
-
"github.com/go-vela/types/constants"
"github.com/go-vela/types/library"
-
- "github.com/gin-gonic/gin"
+ "github.com/golang-jwt/jwt/v5"
)
func TestUser_Retrieve(t *testing.T) {
@@ -47,6 +46,13 @@ func TestUser_Establish(t *testing.T) {
// setup types
secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
want := new(library.User)
want.SetID(1)
want.SetName("foo")
@@ -65,7 +71,13 @@ func TestUser_Establish(t *testing.T) {
context, engine := gin.CreateTestContext(resp)
context.Request, _ = http.NewRequest(http.MethodGet, "/users/foo", nil)
- at, _ := token.CreateAccessToken(want, time.Minute*5)
+ mto := &token.MintTokenOpts{
+ User: want,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
+
+ at, _ := tm.MintToken(mto)
context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", at))
context.Request.AddCookie(&http.Cookie{
@@ -74,12 +86,14 @@ func TestUser_Establish(t *testing.T) {
})
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from users;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteUser(want)
+ db.Close()
}()
_ = db.CreateUser(want)
@@ -100,8 +114,10 @@ func TestUser_Establish(t *testing.T) {
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(claims.Establish())
engine.Use(Establish())
engine.GET("/users/:user", func(c *gin.Context) {
got = Retrieve(c)
@@ -125,9 +141,20 @@ func TestUser_Establish(t *testing.T) {
}
func TestUser_Establish_NoToken(t *testing.T) {
+ // setup types
+ secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
// setup database
- db, _ := sqlite.NewTest()
- defer func() { _sql, _ := db.Sqlite.DB(); _sql.Close() }()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
// setup context
gin.SetMode(gin.TestMode)
@@ -137,7 +164,10 @@ func TestUser_Establish_NoToken(t *testing.T) {
context.Request, _ = http.NewRequest(http.MethodGet, "/users/foo", nil)
// setup mock server
+ engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(claims.Establish())
engine.Use(Establish())
// run test
@@ -148,14 +178,18 @@ func TestUser_Establish_NoToken(t *testing.T) {
}
}
-func TestUser_Establish_SecretValid(t *testing.T) {
+func TestUser_Establish_DiffTokenType(t *testing.T) {
// setup types
secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
want := new(library.User)
- want.SetName("vela-worker")
- want.SetActive(true)
- want.SetAdmin(true)
got := new(library.User)
@@ -169,6 +203,8 @@ func TestUser_Establish_SecretValid(t *testing.T) {
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(claims.Establish())
engine.Use(Establish())
engine.GET("/users/:user", func(c *gin.Context) {
got = Retrieve(c)
@@ -195,9 +231,19 @@ func TestUser_Establish_NoAuthorizeUser(t *testing.T) {
// setup database
secret := "superSecret"
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
// setup database
- db, _ := sqlite.NewTest()
- defer func() { _sql, _ := db.Sqlite.DB(); _sql.Close() }()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
// setup context
gin.SetMode(gin.TestMode)
@@ -213,6 +259,8 @@ func TestUser_Establish_NoAuthorizeUser(t *testing.T) {
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(claims.Establish())
engine.Use(Establish())
// run test
@@ -225,12 +273,26 @@ func TestUser_Establish_NoAuthorizeUser(t *testing.T) {
func TestUser_Establish_NoUser(t *testing.T) {
// setup types
+ tm := &token.Manager{
+ PrivateKey: "123abc",
+ SignMethod: jwt.SigningMethodHS256,
+ UserAccessTokenDuration: time.Minute * 5,
+ UserRefreshTokenDuration: time.Minute * 30,
+ }
+
+ u := new(library.User)
+ u.SetID(1)
+ u.SetName("foo")
+
+ // setup database
secret := "superSecret"
- got := new(library.User)
// setup database
- db, _ := sqlite.NewTest()
- defer func() { _sql, _ := db.Sqlite.DB(); _sql.Close() }()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
// setup context
gin.SetMode(gin.TestMode)
@@ -239,30 +301,30 @@ func TestUser_Establish_NoUser(t *testing.T) {
context, engine := gin.CreateTestContext(resp)
context.Request, _ = http.NewRequest(http.MethodGet, "/users/foo?access_token=bar", nil)
- // setup github mock server
- engine.GET("/api/v3/user", func(c *gin.Context) {
- c.String(http.StatusOK, userPayload)
- })
+ mto := &token.MintTokenOpts{
+ User: u,
+ TokenDuration: tm.UserAccessTokenDuration,
+ TokenType: constants.UserAccessTokenType,
+ }
- s := httptest.NewServer(engine)
- defer s.Close()
+ at, _ := tm.MintToken(mto)
+
+ context.Request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", at))
+ context.Request.AddCookie(&http.Cookie{
+ Name: constants.RefreshTokenName,
+ Value: "fresh",
+ })
// setup client
- client, _ := github.NewTest(s.URL)
+ client, _ := github.NewTest("")
// setup vela mock server
engine.Use(func(c *gin.Context) { c.Set("secret", secret) })
engine.Use(func(c *gin.Context) { database.ToContext(c, db) })
engine.Use(func(c *gin.Context) { scm.ToContext(c, client) })
+ engine.Use(func(c *gin.Context) { c.Set("token-manager", tm) })
+ engine.Use(claims.Establish())
engine.Use(Establish())
- engine.GET("/users/:user", func(c *gin.Context) {
- got = Retrieve(c)
-
- c.Status(http.StatusOK)
- })
-
- s1 := httptest.NewServer(engine)
- defer s1.Close()
// run test
engine.ServeHTTP(context.Writer, context.Request)
@@ -270,10 +332,6 @@ func TestUser_Establish_NoUser(t *testing.T) {
if resp.Code != http.StatusUnauthorized {
t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusUnauthorized)
}
-
- if got.GetID() != 0 {
- t.Errorf("Establish is %v, want 0", got)
- }
}
const userPayload = `
diff --git a/router/middleware/webhook_validation_test.go b/router/middleware/webhook_validation_test.go
index e7596ba9f..cb0f432fe 100644
--- a/router/middleware/webhook_validation_test.go
+++ b/router/middleware/webhook_validation_test.go
@@ -18,6 +18,7 @@ func TestWebhook_WebhookValidation(t *testing.T) {
type args struct {
validate bool
}
+
tests := []struct {
name string
args args
@@ -38,6 +39,7 @@ func TestWebhook_WebhookValidation(t *testing.T) {
want: true,
},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// setup context
diff --git a/router/middleware/worker/doc.go b/router/middleware/worker/doc.go
index 1070cbd2b..44eaaa675 100644
--- a/router/middleware/worker/doc.go
+++ b/router/middleware/worker/doc.go
@@ -8,5 +8,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/router/middleware/worker"
+// import "github.com/go-vela/server/router/middleware/worker"
package worker
diff --git a/router/middleware/worker/worker.go b/router/middleware/worker/worker.go
index c2eed2880..5afc8b3bf 100644
--- a/router/middleware/worker/worker.go
+++ b/router/middleware/worker/worker.go
@@ -5,15 +5,13 @@
package worker
import (
- "github.com/go-vela/server/database"
- "github.com/go-vela/types/library"
-
- "github.com/go-vela/server/util"
-
"fmt"
"net/http"
"github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/util"
+ "github.com/go-vela/types/library"
"github.com/sirupsen/logrus"
)
@@ -25,18 +23,21 @@ func Retrieve(c *gin.Context) *library.Worker {
// Establish sets the worker in the given context.
func Establish() gin.HandlerFunc {
return func(c *gin.Context) {
- wParam := c.Param("worker")
+ wParam := util.PathParameter(c, "worker")
if len(wParam) == 0 {
retErr := fmt.Errorf("no worker parameter provided")
util.HandleError(c, http.StatusBadRequest, retErr)
+
return
}
logrus.Debugf("Reading worker %s", wParam)
- w, err := database.FromContext(c).GetWorker(wParam)
+
+ w, err := database.FromContext(c).GetWorkerForHostname(wParam)
if err != nil {
- retErr := fmt.Errorf("unable to read worker %s: %v", wParam, err)
+ retErr := fmt.Errorf("unable to read worker %s: %w", wParam, err)
util.HandleError(c, http.StatusNotFound, retErr)
+
return
}
diff --git a/router/middleware/worker/worker_test.go b/router/middleware/worker/worker_test.go
index dd0b92ffa..58d090825 100644
--- a/router/middleware/worker/worker_test.go
+++ b/router/middleware/worker/worker_test.go
@@ -12,7 +12,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/go-vela/server/database"
- "github.com/go-vela/server/database/sqlite"
"github.com/go-vela/types/library"
)
@@ -42,18 +41,25 @@ func TestWorker_Establish(t *testing.T) {
want.SetAddress("localhost")
want.SetRoutes([]string{"foo", "bar", "baz"})
want.SetActive(true)
+ want.SetStatus("available")
+ want.SetLastStatusUpdateAt(12345)
+ want.SetRunningBuildIDs([]string{})
+ want.SetLastBuildStartedAt(12345)
+ want.SetLastBuildFinishedAt(12345)
want.SetLastCheckedIn(12345)
want.SetBuildLimit(0)
got := new(library.Worker)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from workers;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteWorker(want)
+ db.Close()
}()
_ = db.CreateWorker(want)
@@ -88,8 +94,11 @@ func TestWorker_Establish(t *testing.T) {
func TestWorker_Establish_NoWorkerParameter(t *testing.T) {
// setup database
- db, _ := sqlite.NewTest()
- defer func() { _sql, _ := db.Sqlite.DB(); _sql.Close() }()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
// setup context
gin.SetMode(gin.TestMode)
diff --git a/router/middleware/worker_test.go b/router/middleware/worker_test.go
index 2e1a941e4..5c141bdbb 100644
--- a/router/middleware/worker_test.go
+++ b/router/middleware/worker_test.go
@@ -17,6 +17,7 @@ import (
func TestMiddleware_Worker(t *testing.T) {
// setup types
var got time.Duration
+
want := 5 * time.Minute
// setup context
diff --git a/router/pipeline.go b/router/pipeline.go
index 9f2668b0e..b523f9495 100644
--- a/router/pipeline.go
+++ b/router/pipeline.go
@@ -6,27 +6,41 @@ package router
import (
"github.com/gin-gonic/gin"
- "github.com/go-vela/server/api"
+ "github.com/go-vela/server/api/pipeline"
"github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/perm"
+ pmiddleware "github.com/go-vela/server/router/middleware/pipeline"
"github.com/go-vela/server/router/middleware/repo"
)
// PipelineHandlers is a function that extends the provided base router group
// with the API handlers for pipeline functionality.
//
-// GET /api/v1/pipelines/:org/:repo
-// GET /api/v1/pipelines/:org/:repo/templates
-// POST /api/v1/pipelines/:org/:repo/expand
-// POST /api/v1/pipelines/:org/:repo/compile
-// POST /api/v1/pipelines/:org/:repo/validate .
+// POST /api/v1/pipelines/:org/:repo
+// GET /api/v1/pipelines/:org/:repo
+// GET /api/v1/pipelines/:org/:repo/:pipeline
+// PUT /api/v1/pipelines/:org/:repo/:pipeline
+// DELETE /api/v1/pipelines/:org/:repo/:pipeline
+// GET /api/v1/pipelines/:org/:repo/:pipeline/templates
+// POST /api/v1/pipelines/:org/:repo/:pipeline/expand
+// POST /api/v1/pipelines/:org/:repo/:pipeline/compile
+// POST /api/v1/pipelines/:org/:repo/:pipeline/validate .
func PipelineHandlers(base *gin.RouterGroup) {
// Pipelines endpoints
- pipelines := base.Group("pipelines/:org/:repo", org.Establish(), repo.Establish())
+ _pipelines := base.Group("pipelines/:org/:repo", org.Establish(), repo.Establish())
{
- pipelines.GET("", api.GetPipeline)
- pipelines.GET("/templates", api.GetTemplates)
- pipelines.POST("/expand", api.ExpandPipeline)
- pipelines.POST("/validate", api.ValidatePipeline)
- pipelines.POST("/compile", api.CompilePipeline)
+ _pipelines.POST("", perm.MustAdmin(), pipeline.CreatePipeline)
+ _pipelines.GET("", perm.MustRead(), pipeline.ListPipelines)
+
+ _pipeline := _pipelines.Group("/:pipeline", pmiddleware.Establish())
+ {
+ _pipeline.GET("", perm.MustRead(), pipeline.GetPipeline)
+ _pipeline.PUT("", perm.MustWrite(), pipeline.UpdatePipeline)
+ _pipeline.DELETE("", perm.MustPlatformAdmin(), pipeline.DeletePipeline)
+ _pipeline.GET("/templates", perm.MustRead(), pipeline.GetTemplates)
+ _pipeline.POST("/compile", perm.MustRead(), pipeline.CompilePipeline)
+ _pipeline.POST("/expand", perm.MustRead(), pipeline.ExpandPipeline)
+ _pipeline.POST("/validate", perm.MustRead(), pipeline.ValidatePipeline)
+ } // end of pipeline endpoints
} // end of pipelines endpoints
}
diff --git a/router/repo.go b/router/repo.go
index 0ebbb3522..7b7859f6a 100644
--- a/router/repo.go
+++ b/router/repo.go
@@ -6,11 +6,12 @@ package router
import (
"github.com/gin-gonic/gin"
- "github.com/go-vela/server/api"
+ "github.com/go-vela/server/api/build"
+ "github.com/go-vela/server/api/repo"
"github.com/go-vela/server/router/middleware"
"github.com/go-vela/server/router/middleware/org"
"github.com/go-vela/server/router/middleware/perm"
- "github.com/go-vela/server/router/middleware/repo"
+ rmiddleware "github.com/go-vela/server/router/middleware/repo"
)
// RepoHandlers is a function that extends the provided base router group
@@ -31,7 +32,9 @@ import (
// GET /api/v1/repos/:org/:repo/builds/:build
// PUT /api/v1/repos/:org/:repo/builds/:build
// DELETE /api/v1/repos/:org/:repo/builds/:build
+// DELETE /api/v1/repos/:org/:repo/builds/:build/cancel
// GET /api/v1/repos/:org/:repo/builds/:build/logs
+// GET /api/v1/repos/:org/:repo/builds/:build/token
// POST /api/v1/repos/:org/:repo/builds/:build/services
// GET /api/v1/repos/:org/:repo/builds/:build/services
// GET /api/v1/repos/:org/:repo/builds/:build/services/:service
@@ -52,32 +55,32 @@ import (
// DELETE /api/v1/repos/:org/:repo/builds/:build/steps/:step/logs .
func RepoHandlers(base *gin.RouterGroup) {
// Repos endpoints
- repos := base.Group("/repos")
+ _repos := base.Group("/repos")
{
- repos.POST("", middleware.Payload(), api.CreateRepo)
- repos.GET("", api.GetRepos)
+ _repos.POST("", middleware.Payload(), repo.CreateRepo)
+ _repos.GET("", repo.ListRepos)
// Org endpoints
- org := repos.Group("/:org", org.Establish())
+ org := _repos.Group("/:org", org.Establish())
{
- org.GET("", api.GetOrgRepos)
- org.GET("/builds", api.GetOrgBuilds)
+ org.GET("", repo.ListReposForOrg)
+ org.GET("/builds", build.ListBuildsForOrg)
// Repo endpoints
- repo := org.Group("/:repo", repo.Establish())
+ _repo := org.Group("/:repo", rmiddleware.Establish())
{
- repo.GET("", perm.MustRead(), api.GetRepo)
- repo.PUT("", perm.MustAdmin(), middleware.Payload(), api.UpdateRepo)
- repo.DELETE("", perm.MustAdmin(), api.DeleteRepo)
- repo.PATCH("/repair", perm.MustAdmin(), api.RepairRepo)
- repo.PATCH("/chown", perm.MustAdmin(), api.ChownRepo)
+ _repo.GET("", perm.MustRead(), repo.GetRepo)
+ _repo.PUT("", perm.MustAdmin(), middleware.Payload(), repo.UpdateRepo)
+ _repo.DELETE("", perm.MustAdmin(), repo.DeleteRepo)
+ _repo.PATCH("/repair", perm.MustAdmin(), repo.RepairRepo)
+ _repo.PATCH("/chown", perm.MustAdmin(), repo.ChownRepo)
// Build endpoints
// * Service endpoints
// * Log endpoints
// * Step endpoints
// * Log endpoints
- BuildHandlers(repo)
+ BuildHandlers(_repo)
} // end of repo endpoints
} // end of org endpoints
} // end of repos endpoints
diff --git a/router/router.go b/router/router.go
index 43870bb5d..f95c6bf4b 100644
--- a/router/router.go
+++ b/router/router.go
@@ -6,39 +6,41 @@
//
// API for the Vela server
//
-// Version: 0.0.0-dev
-// Schemes: http, https
-// Host: localhost
+// Version: 0.0.0-dev
+// Schemes: http, https
+// Host: localhost
//
-// Consumes:
-// - application/json
+// Consumes:
+// - application/json
//
-// Produces:
-// - application/json
+// Produces:
+// - application/json
//
-// SecurityDefinitions:
-// ApiKeyAuth:
-// description: Bearer token
-// type: apiKey
-// in: header
-// name: Authorization
-// CookieAuth:
-// description: Refresh token sent as cookie (swagger 2.0 doesn't support cookie auth)
-// type: apiKey
-// in: header
-// name: vela_refresh_token
+// SecurityDefinitions:
+// ApiKeyAuth:
+// description: Bearer token
+// type: apiKey
+// in: header
+// name: Authorization
+// CookieAuth:
+// description: Refresh token sent as cookie (swagger 2.0 doesn't support cookie auth)
+// type: apiKey
+// in: header
+// name: vela_refresh_token
//
// swagger:meta
package router
import (
+ "github.com/gin-gonic/gin"
"github.com/go-vela/server/api"
+ "github.com/go-vela/server/api/auth"
+ "github.com/go-vela/server/api/webhook"
"github.com/go-vela/server/router/middleware"
+ "github.com/go-vela/server/router/middleware/claims"
"github.com/go-vela/server/router/middleware/org"
"github.com/go-vela/server/router/middleware/repo"
"github.com/go-vela/server/router/middleware/user"
-
- "github.com/gin-gonic/gin"
)
const (
@@ -65,34 +67,37 @@ func Load(options ...gin.HandlerFunc) *gin.Engine {
r.GET("/health", api.Health)
// Login endpoint
- r.GET("/login", api.Login)
+ r.GET("/login", auth.Login)
// Logout endpoint
- r.GET("/logout", user.Establish(), api.Logout)
+ r.GET("/logout", claims.Establish(), user.Establish(), auth.Logout)
// Refresh Access Token endpoint
- r.GET("/token-refresh", api.RefreshAccessToken)
+ r.GET("/token-refresh", auth.RefreshAccessToken)
// Metric endpoint
r.GET("/metrics", api.CustomMetrics, gin.WrapH(api.BaseMetrics()))
+ // Validate Server Token endpoint
+ r.GET("/validate-token", claims.Establish(), auth.ValidateServerToken)
+
// Version endpoint
r.GET("/version", api.Version)
// Webhook endpoint
- r.POST("/webhook", api.PostWebhook)
+ r.POST("/webhook", webhook.PostWebhook)
// Authentication endpoints
authenticate := r.Group("/authenticate")
{
- authenticate.GET("", api.Authenticate)
- authenticate.GET("/:type", api.AuthenticateType)
- authenticate.GET("/:type/:port", api.AuthenticateType)
- authenticate.POST("/token", api.AuthenticateToken)
+ authenticate.GET("", auth.GetAuthToken)
+ authenticate.GET("/:type", auth.GetAuthRedirect)
+ authenticate.GET("/:type/:port", auth.GetAuthRedirect)
+ authenticate.POST("/token", auth.PostAuthToken)
}
// API endpoints
- baseAPI := r.Group(base, user.Establish())
+ baseAPI := r.Group(base, claims.Establish(), user.Establish())
{
// Admin endpoints
AdminHandlers(baseAPI)
@@ -111,9 +116,15 @@ func Load(options ...gin.HandlerFunc) *gin.Engine {
// * Log endpoints
RepoHandlers(baseAPI)
+ // Schedule endpoints
+ ScheduleHandler(baseAPI)
+
// Source code management endpoints
ScmHandlers(baseAPI)
+ // Search endpoints
+ SearchHandlers(baseAPI)
+
// Secret endpoints
SecretHandlers(baseAPI)
@@ -125,6 +136,7 @@ func Load(options ...gin.HandlerFunc) *gin.Engine {
// Pipeline endpoints
PipelineHandlers(baseAPI)
+
} // end of api
return r
diff --git a/router/schedule.go b/router/schedule.go
new file mode 100644
index 000000000..7c73e30c2
--- /dev/null
+++ b/router/schedule.go
@@ -0,0 +1,39 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package router
+
+import (
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/api/schedule"
+ "github.com/go-vela/server/router/middleware"
+ "github.com/go-vela/server/router/middleware/org"
+ "github.com/go-vela/server/router/middleware/perm"
+ "github.com/go-vela/server/router/middleware/repo"
+ sMiddleware "github.com/go-vela/server/router/middleware/schedule"
+)
+
+// ScheduleHandler is a function that extends the provided base router group
+// with the API handlers for schedule functionality.
+//
+// POST /api/v1/schedules/:org/:repo
+// GET /api/v1/schedules/:org/:repo
+// GET /api/v1/schedules/:org/:repo/:schedule
+// PUT /api/v1/schedules/:org/:repo/:schedule
+// DELETE /api/v1/schedules/:org/:repo/:schedule .
+func ScheduleHandler(base *gin.RouterGroup) {
+ // Schedules endpoints
+ _schedules := base.Group("/schedules/:org/:repo", org.Establish(), repo.Establish())
+ {
+ _schedules.POST("", perm.MustAdmin(), middleware.Payload(), schedule.CreateSchedule)
+ _schedules.GET("", perm.MustRead(), schedule.ListSchedules)
+
+ s := _schedules.Group("/:schedule", sMiddleware.Establish())
+ {
+ s.GET("", perm.MustRead(), schedule.GetSchedule)
+ s.PUT("", perm.MustAdmin(), middleware.Payload(), schedule.UpdateSchedule)
+ s.DELETE("", perm.MustAdmin(), schedule.DeleteSchedule)
+ }
+ } // end of schedules endpoints
+}
diff --git a/router/scm.go b/router/scm.go
index 85b2a0d9f..40d745087 100644
--- a/router/scm.go
+++ b/router/scm.go
@@ -6,7 +6,7 @@ package router
import (
"github.com/gin-gonic/gin"
- "github.com/go-vela/server/api"
+ "github.com/go-vela/server/api/scm"
"github.com/go-vela/server/router/middleware/org"
"github.com/go-vela/server/router/middleware/repo"
)
@@ -23,7 +23,7 @@ func ScmHandlers(base *gin.RouterGroup) {
// SCM org endpoints
org := orgs.Group("/:org", org.Establish())
{
- org.GET("/sync", api.SyncRepos)
+ org.GET("/sync", scm.SyncReposForOrg)
} // end of SCM org endpoints
} // end of SCM orgs endpoints
@@ -33,7 +33,7 @@ func ScmHandlers(base *gin.RouterGroup) {
// SCM repo endpoints
repo := repos.Group("/:org/:repo", org.Establish(), repo.Establish())
{
- repo.GET("/sync", api.SyncRepo)
+ repo.GET("/sync", scm.SyncRepo)
} // end of SCM repo endpoints
} // end of SCM repos endpoints
}
diff --git a/router/search.go b/router/search.go
new file mode 100644
index 000000000..62e3542db
--- /dev/null
+++ b/router/search.go
@@ -0,0 +1,26 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package router
+
+import (
+ "github.com/gin-gonic/gin"
+ "github.com/go-vela/server/api/build"
+)
+
+// SearchHandlers is a function that extends the provided base router group
+// with the API handlers for resource search functionality.
+//
+// GET /api/v1/search/builds/:id .
+func SearchHandlers(base *gin.RouterGroup) {
+ // Search endpoints
+ search := base.Group("/search")
+ {
+ // Build endpoint
+ b := search.Group("/builds")
+ {
+ b.GET("/:id", build.GetBuildByID)
+ }
+ } // end of search endpoints
+}
diff --git a/router/secret.go b/router/secret.go
index dbc18e6b6..0ce94d982 100644
--- a/router/secret.go
+++ b/router/secret.go
@@ -5,7 +5,7 @@
package router
import (
- "github.com/go-vela/server/api"
+ "github.com/go-vela/server/api/secret"
"github.com/go-vela/server/router/middleware/perm"
"github.com/gin-gonic/gin"
@@ -23,10 +23,10 @@ func SecretHandlers(base *gin.RouterGroup) {
// Secrets endpoints
secrets := base.Group("/secrets/:engine/:type/:org/:name", perm.MustSecretAdmin())
{
- secrets.POST("", api.CreateSecret)
- secrets.GET("", api.GetSecrets)
- secrets.GET("/*secret", api.GetSecret)
- secrets.PUT("/*secret", api.UpdateSecret)
- secrets.DELETE("/*secret", api.DeleteSecret)
+ secrets.POST("", secret.CreateSecret)
+ secrets.GET("", secret.ListSecrets)
+ secrets.GET("/*secret", secret.GetSecret)
+ secrets.PUT("/*secret", secret.UpdateSecret)
+ secrets.DELETE("/*secret", secret.DeleteSecret)
} // end of secrets endpoints
}
diff --git a/router/service.go b/router/service.go
index 647d301e1..7b81e7af7 100644
--- a/router/service.go
+++ b/router/service.go
@@ -1,16 +1,16 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
-// nolint: dupl // ignore similar code with step
+//nolint:dupl // ignore similar code with step
package router
import (
"github.com/gin-gonic/gin"
- "github.com/go-vela/server/api"
+ "github.com/go-vela/server/api/service"
"github.com/go-vela/server/router/middleware"
"github.com/go-vela/server/router/middleware/perm"
- "github.com/go-vela/server/router/middleware/service"
+ smiddleware "github.com/go-vela/server/router/middleware/service"
)
// ServiceHandlers is a function that extends the provided base router group
@@ -24,26 +24,23 @@ import (
// POST /api/v1/repos/:org/:repo/builds/:build/services/:service/logs
// GET /api/v1/repos/:org/:repo/builds/:build/services/:service/logs
// PUT /api/v1/repos/:org/:repo/builds/:build/services/:service/logs
-// DELETE /api/v1/repos/:org/:repo/builds/:build/services/:service/logs
-// POST /api/v1/repos/:org/:repo/builds/:build/services/:service/stream .
+// DELETE /api/v1/repos/:org/:repo/builds/:build/services/:service/logs .
func ServiceHandlers(base *gin.RouterGroup) {
// Services endpoints
services := base.Group("/services")
{
- services.POST("", perm.MustPlatformAdmin(), middleware.Payload(), api.CreateService)
- services.GET("", perm.MustRead(), api.GetServices)
+ services.POST("", perm.MustPlatformAdmin(), middleware.Payload(), service.CreateService)
+ services.GET("", perm.MustRead(), service.ListServices)
// Service endpoints
- service := services.Group("/:service", service.Establish())
+ s := services.Group("/:service", smiddleware.Establish())
{
- service.GET("", perm.MustRead(), api.GetService)
- service.PUT("", perm.MustPlatformAdmin(), middleware.Payload(), api.UpdateService)
- service.DELETE("", perm.MustPlatformAdmin(), api.DeleteService)
-
- service.POST("/stream", perm.MustPlatformAdmin(), api.PostServiceStream)
+ s.GET("", perm.MustRead(), service.GetService)
+ s.PUT("", perm.MustBuildAccess(), middleware.Payload(), service.UpdateService)
+ s.DELETE("", perm.MustPlatformAdmin(), service.DeleteService)
// Log endpoints
- LogServiceHandlers(service)
+ LogServiceHandlers(s)
} // end of service endpoints
} // end of services endpoints
}
diff --git a/router/step.go b/router/step.go
index ac73ca238..9a53e4e97 100644
--- a/router/step.go
+++ b/router/step.go
@@ -1,16 +1,16 @@
-// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
-// nolint: dupl // ignore similar code with service
+//nolint:dupl // ignore similar code with service
package router
import (
"github.com/gin-gonic/gin"
- "github.com/go-vela/server/api"
+ "github.com/go-vela/server/api/step"
"github.com/go-vela/server/router/middleware"
"github.com/go-vela/server/router/middleware/perm"
- "github.com/go-vela/server/router/middleware/step"
+ smiddleware "github.com/go-vela/server/router/middleware/step"
)
// StepHandlers is a function that extends the provided base router group
@@ -24,26 +24,23 @@ import (
// POST /api/v1/repos/:org/:repo/builds/:build/steps/:step/logs
// GET /api/v1/repos/:org/:repo/builds/:build/steps/:step/logs
// PUT /api/v1/repos/:org/:repo/builds/:build/steps/:step/logs
-// DELETE /api/v1/repos/:org/:repo/builds/:build/steps/:step/logs
-// POST /api/v1/repos/:org/:repo/builds/:build/steps/:step/stream .
+// DELETE /api/v1/repos/:org/:repo/builds/:build/steps/:step/logs .
func StepHandlers(base *gin.RouterGroup) {
// Steps endpoints
steps := base.Group("/steps")
{
- steps.POST("", perm.MustPlatformAdmin(), middleware.Payload(), api.CreateStep)
- steps.GET("", perm.MustRead(), api.GetSteps)
+ steps.POST("", perm.MustPlatformAdmin(), middleware.Payload(), step.CreateStep)
+ steps.GET("", perm.MustRead(), step.ListSteps)
// Step endpoints
- step := steps.Group("/:step", step.Establish())
+ s := steps.Group("/:step", smiddleware.Establish())
{
- step.GET("", perm.MustRead(), api.GetStep)
- step.PUT("", perm.MustPlatformAdmin(), middleware.Payload(), api.UpdateStep)
- step.DELETE("", perm.MustPlatformAdmin(), api.DeleteStep)
-
- step.POST("/stream", perm.MustPlatformAdmin(), api.PostStepStream)
+ s.GET("", perm.MustRead(), step.GetStep)
+ s.PUT("", perm.MustBuildAccess(), middleware.Payload(), step.UpdateStep)
+ s.DELETE("", perm.MustPlatformAdmin(), step.DeleteStep)
// Log endpoints
- LogStepHandlers(step)
+ LogStepHandlers(s)
} // end of step endpoints
} // end of steps endpoints
}
diff --git a/router/user.go b/router/user.go
index 1b91836b1..31abe2d92 100644
--- a/router/user.go
+++ b/router/user.go
@@ -6,7 +6,7 @@ package router
import (
"github.com/gin-gonic/gin"
- "github.com/go-vela/server/api"
+ "github.com/go-vela/server/api/user"
"github.com/go-vela/server/router/middleware/perm"
)
@@ -18,9 +18,6 @@ import (
// GET /api/v1/users/:user
// PUT /api/v1/users/:user
// DELETE /api/v1/users/:user
-// GET /api/v1/users/:user/source/repos
-// POST /api/v1/users/:user/token
-// DELETE /api/v1/users/:user/token
// GET /api/v1/user
// PUT /api/v1/user
// GET /api/v1/user/source/repos
@@ -28,22 +25,22 @@ import (
// DELETE /api/v1/user/token .
func UserHandlers(base *gin.RouterGroup) {
// Users endpoints
- users := base.Group("/users")
+ _users := base.Group("/users")
{
- users.POST("", perm.MustPlatformAdmin(), api.CreateUser)
- users.GET("", api.GetUsers)
- users.GET("/:user", perm.MustPlatformAdmin(), api.GetUser)
- users.PUT("/:user", perm.MustPlatformAdmin(), api.UpdateUser)
- users.DELETE("/:user", perm.MustPlatformAdmin(), api.DeleteUser)
+ _users.POST("", perm.MustPlatformAdmin(), user.CreateUser)
+ _users.GET("", user.ListUsers)
+ _users.GET("/:user", perm.MustPlatformAdmin(), user.GetUser)
+ _users.PUT("/:user", perm.MustPlatformAdmin(), user.UpdateUser)
+ _users.DELETE("/:user", perm.MustPlatformAdmin(), user.DeleteUser)
} // end of users endpoints
// User endpoints
- user := base.Group("/user")
+ _user := base.Group("/user")
{
- user.GET("", api.GetCurrentUser)
- user.PUT("", api.UpdateCurrentUser)
- user.GET("/source/repos", api.GetUserSourceRepos)
- user.POST("/token", api.CreateToken)
- user.DELETE("/token", api.DeleteToken)
+ _user.GET("", user.GetCurrentUser)
+ _user.PUT("", user.UpdateCurrentUser)
+ _user.GET("/source/repos", user.GetSourceRepos)
+ _user.POST("/token", user.CreateToken)
+ _user.DELETE("/token", user.DeleteToken)
} // end of user endpoints
}
diff --git a/router/worker.go b/router/worker.go
index 7f7df3a3b..f85a84c45 100644
--- a/router/worker.go
+++ b/router/worker.go
@@ -6,10 +6,10 @@ package router
import (
"github.com/gin-gonic/gin"
- "github.com/go-vela/server/api"
+ "github.com/go-vela/server/api/worker"
"github.com/go-vela/server/router/middleware"
"github.com/go-vela/server/router/middleware/perm"
- "github.com/go-vela/server/router/middleware/worker"
+ wmiddleware "github.com/go-vela/server/router/middleware/worker"
)
// WorkerHandlers is a function that extends the provided base router group
@@ -19,20 +19,22 @@ import (
// GET /api/v1/workers
// GET /api/v1/workers/:worker
// PUT /api/v1/workers/:worker
+// POST /api/v1/workers/:worker/refresh
// DELETE /api/v1/workers/:worker .
func WorkerHandlers(base *gin.RouterGroup) {
// Workers endpoints
- workers := base.Group("/workers")
+ _workers := base.Group("/workers")
{
- workers.POST("", perm.MustPlatformAdmin(), middleware.Payload(), api.CreateWorker)
- workers.GET("", api.GetWorkers)
+ _workers.POST("", perm.MustWorkerRegisterToken(), middleware.Payload(), worker.CreateWorker)
+ _workers.GET("", worker.ListWorkers)
// Worker endpoints
- w := workers.Group("/:worker")
+ _worker := _workers.Group("/:worker")
{
- w.GET("", worker.Establish(), api.GetWorker)
- w.PUT("", perm.MustPlatformAdmin(), worker.Establish(), api.UpdateWorker)
- w.DELETE("", perm.MustPlatformAdmin(), worker.Establish(), api.DeleteWorker)
+ _worker.GET("", wmiddleware.Establish(), worker.GetWorker)
+ _worker.PUT("", perm.MustWorkerAuthToken(), wmiddleware.Establish(), worker.UpdateWorker)
+ _worker.POST("/refresh", perm.MustWorkerAuthToken(), wmiddleware.Establish(), worker.Refresh)
+ _worker.DELETE("", perm.MustPlatformAdmin(), wmiddleware.Establish(), worker.DeleteWorker)
} // end of worker endpoints
} // end of workers endpoints
}
diff --git a/scm/doc.go b/scm/doc.go
index 1401e5a14..e30a7bcee 100644
--- a/scm/doc.go
+++ b/scm/doc.go
@@ -7,5 +7,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/scm"
+// import "github.com/go-vela/server/scm"
package scm
diff --git a/scm/flags.go b/scm/flags.go
index 9de700f67..3dc36d139 100644
--- a/scm/flags.go
+++ b/scm/flags.go
@@ -15,7 +15,6 @@ import (
// https://pkg.go.dev/github.com/urfave/cli?tab=doc#Flag
//
// TODO: in a future release remove the "source" vars in favor of the "scm" ones.
-// nolint:lll // these errors will go away when the TODO is completed
var Flags = []cli.Flag{
// SCM Flags
diff --git a/scm/github/access.go b/scm/github/access.go
index d8d7a8afd..ee47cf3d1 100644
--- a/scm/github/access.go
+++ b/scm/github/access.go
@@ -10,7 +10,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/go-vela/types/library"
- "github.com/google/go-github/v42/github"
+ "github.com/google/go-github/v53/github"
)
// OrgAccess captures the user's access level for an org.
@@ -20,9 +20,14 @@ func (c *client) OrgAccess(u *library.User, org string) (string, error) {
"user": u.GetName(),
}).Tracef("capturing %s access level to org %s", u.GetName(), org)
- // if user is accessing personal org
- if strings.EqualFold(org, *u.Name) {
- // nolint: goconst // ignore making constant
+ // check if user is accessing personal org
+ if strings.EqualFold(org, u.GetName()) {
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "user": u.GetName(),
+ }).Debugf("skipping access level check for user %s with org %s", u.GetName(), org)
+
+ //nolint:goconst // ignore making constant
return "admin", nil
}
@@ -51,6 +56,17 @@ func (c *client) RepoAccess(u *library.User, token, org, repo string) (string, e
"user": u.GetName(),
}).Tracef("capturing %s access level to repo %s/%s", u.GetName(), org, repo)
+ // check if user is accessing repo in personal org
+ if strings.EqualFold(org, u.GetName()) {
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "repo": repo,
+ "user": u.GetName(),
+ }).Debugf("skipping access level check for user %s with repo %s/%s", u.GetName(), org, repo)
+
+ return "admin", nil
+ }
+
// create github oauth client with the given token
client := c.newClientToken(token)
@@ -71,6 +87,17 @@ func (c *client) TeamAccess(u *library.User, org, team string) (string, error) {
"user": u.GetName(),
}).Tracef("capturing %s access level to team %s/%s", u.GetName(), org, team)
+ // check if user is accessing team in personal org
+ if strings.EqualFold(org, u.GetName()) {
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "team": team,
+ "user": u.GetName(),
+ }).Debugf("skipping access level check for user %s with team %s/%s", u.GetName(), org, team)
+
+ return "admin", nil
+ }
+
// create GitHub OAuth client with user's token
client := c.newClientToken(u.GetToken())
teams := []*github.Team{}
diff --git a/scm/github/authentication.go b/scm/github/authentication.go
index c2d27b14c..3b4ccf14b 100644
--- a/scm/github/authentication.go
+++ b/scm/github/authentication.go
@@ -14,7 +14,7 @@ import (
"github.com/go-vela/server/random"
"github.com/go-vela/types/library"
- "github.com/google/go-github/v42/github"
+ "github.com/google/go-github/v53/github"
)
// Authorize uses the given access token to authorize the user.
@@ -38,8 +38,6 @@ func (c *client) Login(w http.ResponseWriter, r *http.Request) (string, error) {
c.Logger.Trace("processing login request")
// generate a random string for creating the OAuth state
- //
- // nolint: gomnd // ignore magic number
oAuthState, err := random.GenerateRandomString(32)
if err != nil {
return "", err
@@ -59,8 +57,6 @@ func (c *client) Login(w http.ResponseWriter, r *http.Request) (string, error) {
// Authenticate completes the authentication workflow for the session
// and returns the remote user details.
-//
-// nolint: lll // ignore long line length due to variable names
func (c *client) Authenticate(w http.ResponseWriter, r *http.Request, oAuthState string) (*library.User, error) {
c.Logger.Trace("authenticating user")
@@ -136,7 +132,8 @@ func (c *client) AuthenticateToken(r *http.Request) (*library.User, error) {
// check if the provided token was created by Vela
_, resp, err := client.Authorizations.Check(context.Background(), c.config.ClientID, token)
// check if the error is of type ErrorResponse
- if gerr, ok := err.(*github.ErrorResponse); ok {
+ var gerr *github.ErrorResponse
+ if errors.As(err, &gerr) {
// check the status code
switch gerr.Response.StatusCode {
// 404 is expected when non vela token is used
diff --git a/scm/github/authentication_test.go b/scm/github/authentication_test.go
index 3e1c50942..d1b369a1f 100644
--- a/scm/github/authentication_test.go
+++ b/scm/github/authentication_test.go
@@ -411,6 +411,7 @@ func TestGithub_AuthenticateToken_Vela_OAuth(t *testing.T) {
// run test
_, err := client.AuthenticateToken(context.Request)
+
if resp.Code != http.StatusOK {
t.Errorf("AuthenticateToken returned %v, want %v", resp.Code, http.StatusOK)
}
diff --git a/scm/github/changeset.go b/scm/github/changeset.go
index 6a47edce0..da8ca221a 100644
--- a/scm/github/changeset.go
+++ b/scm/github/changeset.go
@@ -10,7 +10,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/go-vela/types/library"
- "github.com/google/go-github/v42/github"
+ "github.com/google/go-github/v53/github"
)
// Changeset captures the list of files changed for a commit.
@@ -31,7 +31,7 @@ func (c *client) Changeset(u *library.User, r *library.Repo, sha string) ([]stri
// send API call to capture the commit
commit, _, err := client.Repositories.GetCommit(ctx, r.GetOrg(), r.GetName(), sha, &opts)
if err != nil {
- return nil, fmt.Errorf("Repositories.GetCommit returned error: %v", err)
+ return nil, fmt.Errorf("Repositories.GetCommit returned error: %w", err)
}
// iterate through each file in the commit
@@ -62,7 +62,7 @@ func (c *client) ChangesetPR(u *library.User, r *library.Repo, number int) ([]st
// send API call to capture the files from the pull request
files, resp, err := client.PullRequests.ListFiles(ctx, r.GetOrg(), r.GetName(), number, &opts)
if err != nil {
- return nil, fmt.Errorf("PullRequests.ListFiles returned error: %v", err)
+ return nil, fmt.Errorf("PullRequests.ListFiles returned error: %w", err)
}
f = append(f, files...)
diff --git a/scm/github/deployment.go b/scm/github/deployment.go
index 799948a74..fea3f8d03 100644
--- a/scm/github/deployment.go
+++ b/scm/github/deployment.go
@@ -11,12 +11,10 @@ import (
"github.com/go-vela/types/library"
"github.com/go-vela/types/raw"
- "github.com/google/go-github/v42/github"
+ "github.com/google/go-github/v53/github"
)
// GetDeployment gets a deployment from the GitHub repo.
-//
-// nolint: lll // ignore long line length due to variable names
func (c *client) GetDeployment(u *library.User, r *library.Repo, id int64) (*library.Deployment, error) {
c.Logger.WithFields(logrus.Fields{
"org": r.GetOrg(),
@@ -34,6 +32,7 @@ func (c *client) GetDeployment(u *library.User, r *library.Repo, id int64) (*lib
}
var payload *raw.StringSliceMap
+
err = json.Unmarshal(deployment.Payload, &payload)
if err != nil {
c.Logger.Tracef("Unable to unmarshal payload for deployment id %v", deployment.ID)
@@ -96,8 +95,6 @@ func (c *client) GetDeploymentCount(u *library.User, r *library.Repo) (int64, er
}
// GetDeploymentList gets a list of deployments from the GitHub repo.
-//
-// nolint: lll // ignore long line length due to variable names
func (c *client) GetDeploymentList(u *library.User, r *library.Repo, page, perPage int) ([]*library.Deployment, error) {
c.Logger.WithFields(logrus.Fields{
"org": r.GetOrg(),
@@ -128,6 +125,7 @@ func (c *client) GetDeploymentList(u *library.User, r *library.Repo, page, perPa
// iterate through all API results
for _, deployment := range d {
var payload *raw.StringSliceMap
+
err := json.Unmarshal(deployment.Payload, &payload)
if err != nil {
c.Logger.Tracef("Unable to unmarshal payload for deployment id %v", deployment.ID)
diff --git a/scm/github/doc.go b/scm/github/doc.go
index 804a33b12..e47f97555 100644
--- a/scm/github/doc.go
+++ b/scm/github/doc.go
@@ -7,5 +7,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/scm/github"
+// import "github.com/go-vela/server/scm/github"
package github
diff --git a/scm/github/github.go b/scm/github/github.go
index f3d8e5665..ebae7ec76 100644
--- a/scm/github/github.go
+++ b/scm/github/github.go
@@ -9,7 +9,7 @@ import (
"fmt"
"net/url"
- "github.com/google/go-github/v42/github"
+ "github.com/google/go-github/v53/github"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
@@ -25,6 +25,7 @@ const (
eventDeployment = "deployment"
eventIssueComment = "issue_comment"
eventRepository = "repository"
+ eventInitialize = "initialize"
)
var ctx = context.Background()
@@ -61,7 +62,7 @@ type client struct {
// New returns a SCM implementation that integrates with
// a GitHub or a GitHub Enterprise instance.
//
-// nolint: revive // ignore returning unexported client
+//nolint:revive // ignore returning unexported client
func New(opts ...ClientOpt) (*client, error) {
// create new GitHub client
c := new(client)
@@ -120,7 +121,7 @@ func New(opts ...ClientOpt) (*client, error) {
//
// This function is intended for running tests only.
//
-// nolint: revive // ignore returning unexported client
+//nolint:revive // ignore returning unexported client
func NewTest(urls ...string) (*client, error) {
address := urls[0]
server := address
diff --git a/scm/github/github_test.go b/scm/github/github_test.go
index 059b6392d..1c10db60a 100644
--- a/scm/github/github_test.go
+++ b/scm/github/github_test.go
@@ -12,7 +12,7 @@ import (
"reflect"
"testing"
- "github.com/google/go-github/v42/github"
+ "github.com/google/go-github/v53/github"
"golang.org/x/oauth2"
)
@@ -76,12 +76,12 @@ func TestGithub_newClientToken(t *testing.T) {
// run test
got := client.newClientToken("foobar")
- // nolint: staticcheck // ignore false positive
+ //nolint:staticcheck // ignore false positive
if got == nil {
t.Errorf("newClientToken is nil, want %v", want)
}
- // nolint: staticcheck // ignore false positive
+ //nolint:staticcheck // ignore false positive
if !reflect.DeepEqual(got.BaseURL, want.BaseURL) {
t.Errorf("newClientToken BaseURL is %v, want %v", got.BaseURL, want.BaseURL)
}
diff --git a/scm/github/org.go b/scm/github/org.go
new file mode 100644
index 000000000..ef9e43ca4
--- /dev/null
+++ b/scm/github/org.go
@@ -0,0 +1,43 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package github
+
+import (
+ "net/http"
+
+ "github.com/sirupsen/logrus"
+
+ "github.com/go-vela/types/library"
+)
+
+// GetOrgName gets org name from Github.
+func (c *client) GetOrgName(u *library.User, o string) (string, error) {
+ c.Logger.WithFields(logrus.Fields{
+ "org": o,
+ "user": u.GetName(),
+ }).Tracef("retrieving org information for %s", o)
+
+ // create GitHub OAuth client with user's token
+ client := c.newClientToken(u.GetToken())
+
+ // send an API call to get the org info
+ orgInfo, resp, err := client.Organizations.Get(ctx, o)
+
+ orgName := orgInfo.GetLogin()
+
+ // if org is not found, return the personal org
+ if resp.StatusCode == http.StatusNotFound {
+ user, _, err := client.Users.Get(ctx, "")
+ if err != nil {
+ return "", err
+ }
+
+ orgName = user.GetLogin()
+ } else if err != nil {
+ return "", err
+ }
+
+ return orgName, nil
+}
diff --git a/scm/github/org_test.go b/scm/github/org_test.go
new file mode 100644
index 000000000..b0512f9b9
--- /dev/null
+++ b/scm/github/org_test.go
@@ -0,0 +1,131 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
+package github
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "testing"
+
+ "github.com/gin-gonic/gin"
+
+ "github.com/go-vela/types/library"
+)
+
+func TestGithub_GetOrgName(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ _, engine := gin.CreateTestContext(resp)
+
+ // setup mock server
+ engine.GET("/api/v3/orgs/:org", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/get_org.json")
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup types
+ u := new(library.User)
+ u.SetName("foo")
+ u.SetToken("bar")
+
+ want := "github"
+
+ client, _ := NewTest(s.URL)
+
+ // run test
+ got, err := client.GetOrgName(u, "github")
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("GetOrgName returned %v, want %v", resp.Code, http.StatusOK)
+ }
+
+ if err != nil {
+ t.Errorf("GetOrgName returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("GetOrgName is %v, want %v", got, want)
+ }
+}
+
+func TestGithub_GetOrgName_Personal(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ _, engine := gin.CreateTestContext(resp)
+
+ // setup mock server
+ engine.GET("/api/v3/user", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/user.json")
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup types
+ u := new(library.User)
+ u.SetName("foo")
+ u.SetToken("bar")
+
+ want := "octocat"
+
+ client, _ := NewTest(s.URL)
+
+ // run test
+ got, err := client.GetOrgName(u, "octocat")
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("GetOrgName returned %v, want %v", resp.Code, http.StatusOK)
+ }
+
+ if err != nil {
+ t.Errorf("GetOrgName returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("GetOrgName is %v, want %v", got, want)
+ }
+}
+
+func TestGithub_GetOrgName_Fail(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ _, engine := gin.CreateTestContext(resp)
+
+ // setup mock server
+ engine.GET("/api/v3/orgs/:org", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusNotFound)
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup types
+ u := new(library.User)
+ u.SetName("foo")
+ u.SetToken("bar")
+
+ client, _ := NewTest(s.URL)
+
+ // run test
+ _, err := client.GetOrgName(u, "octocat")
+
+ if err == nil {
+ t.Error("GetOrgName should return error")
+ }
+}
diff --git a/scm/github/repo.go b/scm/github/repo.go
index 9f45bfc7f..e69c7220c 100644
--- a/scm/github/repo.go
+++ b/scm/github/repo.go
@@ -15,17 +15,17 @@ import (
"github.com/go-vela/types/constants"
"github.com/go-vela/types/library"
- "github.com/google/go-github/v42/github"
+ "github.com/google/go-github/v53/github"
)
// ConfigBackoff is a wrapper for Config that will retry five times if the function
// fails to retrieve the yaml/yml file.
-// nolint: lll // ignore long line length due to input arguments
func (c *client) ConfigBackoff(u *library.User, r *library.Repo, ref string) (data []byte, err error) {
// number of times to retry
retryLimit := 5
for i := 0; i < retryLimit; i++ {
+ logrus.Debugf("Fetching config file - Attempt %d", i+1)
// attempt to fetch the config
data, err = c.Config(u, r, ref)
@@ -98,7 +98,7 @@ func (c *client) Disable(u *library.User, org, name string) error {
"org": org,
"repo": name,
"user": u.GetName(),
- }).Tracef("deleting repository webhook for %s/%s", org, name)
+ }).Tracef("deleting repository webhooks for %s/%s", org, name)
// create GitHub OAuth client with user's token
client := c.newClientToken(*u.Token)
@@ -132,6 +132,12 @@ func (c *client) Disable(u *library.User, org, name string) error {
// skip if we have no hook IDs
if len(ids) == 0 {
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "repo": name,
+ "user": u.GetName(),
+ }).Warnf("no repository webhooks matching %s/webhook found for %s/%s", c.config.ServerWebhookAddress, org, name)
+
return nil
}
@@ -145,47 +151,120 @@ func (c *client) Disable(u *library.User, org, name string) error {
}
// Enable activates a repo by creating the webhook.
-func (c *client) Enable(u *library.User, org, name, secret string) (string, error) {
+func (c *client) Enable(u *library.User, r *library.Repo, h *library.Hook) (*library.Hook, string, error) {
c.Logger.WithFields(logrus.Fields{
- "org": org,
- "repo": name,
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
"user": u.GetName(),
- }).Tracef("creating repository webhook for %s/%s", org, name)
+ }).Tracef("creating repository webhook for %s/%s", r.GetOrg(), r.GetName())
// create GitHub OAuth client with user's token
client := c.newClientToken(*u.Token)
+ // always listen to repository events in case of repo name change
+ events := []string{eventRepository}
+
+ if r.GetAllowComment() {
+ events = append(events, eventIssueComment)
+ }
+
+ if r.GetAllowDeploy() {
+ events = append(events, eventDeployment)
+ }
+
+ if r.GetAllowPull() {
+ events = append(events, eventPullRequest)
+ }
+
+ if r.GetAllowPush() || r.GetAllowTag() {
+ events = append(events, eventPush)
+ }
+
// create the hook object to make the API call
hook := &github.Hook{
- Events: []string{
- eventPush,
- eventPullRequest,
- eventDeployment,
- eventIssueComment,
- eventRepository,
- },
+ Events: events,
Config: map[string]interface{}{
"url": fmt.Sprintf("%s/webhook", c.config.ServerWebhookAddress),
"content_type": "form",
- "secret": secret,
+ "secret": r.GetHash(),
},
Active: github.Bool(true),
}
// send API call to create the webhook
- _, resp, err := client.Repositories.CreateHook(ctx, org, name, hook)
+ hookInfo, resp, err := client.Repositories.CreateHook(ctx, r.GetOrg(), r.GetName(), hook)
+
+ // create the first hook for the repo and record its ID from GitHub
+ webhook := new(library.Hook)
+ webhook.SetWebhookID(hookInfo.GetID())
+ webhook.SetSourceID(r.GetName() + "-" + eventInitialize)
+ webhook.SetCreated(hookInfo.GetCreatedAt().Unix())
+ webhook.SetEvent(eventInitialize)
+ webhook.SetNumber(h.GetNumber() + 1)
+ webhook.SetStatus(constants.StatusSuccess)
switch resp.StatusCode {
case http.StatusUnprocessableEntity:
- return "", fmt.Errorf("repo already enabled")
+ return nil, "", fmt.Errorf("repo already enabled")
case http.StatusNotFound:
- return "", fmt.Errorf("repo not found")
+ return nil, "", fmt.Errorf("repo not found")
}
// create the URL for the repo
- url := fmt.Sprintf("%s/%s/%s", c.config.Address, org, name)
+ url := fmt.Sprintf("%s/%s/%s", c.config.Address, r.GetOrg(), r.GetName())
+
+ return webhook, url, err
+}
+
+// Update edits a repo webhook.
+func (c *client) Update(u *library.User, r *library.Repo, hookID int64) error {
+ c.Logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Tracef("updating repository webhook for %s/%s", r.GetOrg(), r.GetName())
+
+ // create GitHub OAuth client with user's token
+ client := c.newClientToken(*u.Token)
+
+ // always listen to repository events in case of repo name change
+ events := []string{eventRepository}
+
+ if r.GetAllowComment() {
+ events = append(events, eventIssueComment)
+ }
+
+ if r.GetAllowDeploy() {
+ events = append(events, eventDeployment)
+ }
+
+ if r.GetAllowPull() {
+ events = append(events, eventPullRequest)
+ }
+
+ if r.GetAllowPush() || r.GetAllowTag() {
+ events = append(events, eventPush)
+ }
+
+ // create the hook object to make the API call
+ hook := &github.Hook{
+ Events: events,
+ Config: map[string]interface{}{
+ "url": fmt.Sprintf("%s/webhook", c.config.ServerWebhookAddress),
+ "content_type": "form",
+ "secret": r.GetHash(),
+ },
+ Active: github.Bool(true),
+ }
+
+ // send API call to update the webhook
+ _, _, err := client.Repositories.EditHook(ctx, r.GetOrg(), r.GetName(), hookID, hook)
- return url, err
+ if err != nil {
+ return err
+ }
+
+ return nil
}
// Status sends the commit status for the given SHA from the GitHub repo.
@@ -218,7 +297,7 @@ func (c *client) Status(u *library.User, b *library.Build, org, name string) err
state = "success"
description = "the build was successful"
case constants.StatusFailure:
- // nolint: goconst // ignore making constant
+ //nolint:goconst // ignore making constant
state = "failure"
description = "the build has failed"
case constants.StatusCanceled:
@@ -310,6 +389,26 @@ func (c *client) GetRepo(u *library.User, r *library.Repo) (*library.Repo, error
return toLibraryRepo(*repo), nil
}
+// GetOrgAndRepoName returns the name of the org and the repository in the SCM.
+func (c *client) GetOrgAndRepoName(u *library.User, o string, r string) (string, string, error) {
+ c.Logger.WithFields(logrus.Fields{
+ "org": o,
+ "repo": r,
+ "user": u.GetName(),
+ }).Tracef("retrieving repository information for %s/%s", o, r)
+
+ // create GitHub OAuth client with user's token
+ client := c.newClientToken(u.GetToken())
+
+ // send an API call to get the repo info
+ repo, _, err := client.Repositories.Get(ctx, o, r)
+ if err != nil {
+ return "", "", err
+ }
+
+ return repo.GetOwner().GetLogin(), repo.GetName(), nil
+}
+
// ListUserRepos returns a list of all repos the user has access to.
func (c *client) ListUserRepos(u *library.User) ([]*library.Repo, error) {
c.Logger.WithFields(logrus.Fields{
@@ -332,7 +431,7 @@ func (c *client) ListUserRepos(u *library.User) ([]*library.Repo, error) {
// send API call to capture the user's repos
repos, resp, err := client.Repositories.List(ctx, "", opts)
if err != nil {
- return nil, fmt.Errorf("unable to list user repos: %v", err)
+ return nil, fmt.Errorf("unable to list user repos: %w", err)
}
r = append(r, repos...)
@@ -347,6 +446,12 @@ func (c *client) ListUserRepos(u *library.User) ([]*library.Repo, error) {
// iterate through each repo for the user
for _, repo := range r {
+ // skip if the repo is void
+ // fixes an issue with GitHub replication being out of sync
+ if repo == nil {
+ continue
+ }
+
// skip if the repo is archived or disabled
if repo.GetArchived() || repo.GetDisabled() {
continue
@@ -360,20 +465,29 @@ func (c *client) ListUserRepos(u *library.User) ([]*library.Repo, error) {
// toLibraryRepo does a partial conversion of a github repo to a library repo.
func toLibraryRepo(gr github.Repository) *library.Repo {
+ // setting the visbility to match the SCM visbility
+ var visibility string
+ if *gr.Private {
+ visibility = constants.VisibilityPrivate
+ } else {
+ visibility = constants.VisibilityPublic
+ }
+
return &library.Repo{
- Org: gr.GetOwner().Login,
- Name: gr.Name,
- FullName: gr.FullName,
- Link: gr.HTMLURL,
- Clone: gr.CloneURL,
- Branch: gr.DefaultBranch,
- Private: gr.Private,
+ Org: gr.GetOwner().Login,
+ Name: gr.Name,
+ FullName: gr.FullName,
+ Link: gr.HTMLURL,
+ Clone: gr.CloneURL,
+ Branch: gr.DefaultBranch,
+ Topics: &gr.Topics,
+ Private: gr.Private,
+ Visibility: &visibility,
}
}
// GetPullRequest defines a function that retrieves
// a pull request for a repo.
-// nolint:lll // function signature is lengthy
func (c *client) GetPullRequest(u *library.User, r *library.Repo, number int) (string, string, string, string, error) {
c.Logger.WithFields(logrus.Fields{
"org": r.GetOrg(),
@@ -422,6 +536,7 @@ func (c *client) GetHTMLURL(u *library.User, org, repo, name, ref string) (strin
// data is not nil if the file exists
if data != nil {
URL := data.GetHTMLURL()
+
if err != nil {
return "", err
}
@@ -431,3 +546,22 @@ func (c *client) GetHTMLURL(u *library.User, org, repo, name, ref string) (strin
return "", fmt.Errorf("no valid repository contents found")
}
+
+// GetBranch defines a function that retrieves a branch for a repo.
+func (c *client) GetBranch(u *library.User, r *library.Repo, branch string) (string, string, error) {
+ c.Logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ "user": u.GetName(),
+ }).Tracef("retrieving branch %s for repo %s", branch, r.GetFullName())
+
+ // create GitHub OAuth client with user's token
+ client := c.newClientToken(u.GetToken())
+
+ data, _, err := client.Repositories.GetBranch(ctx, r.GetOrg(), r.GetName(), branch, true)
+ if err != nil {
+ return "", "", err
+ }
+
+ return data.GetName(), data.GetCommit().GetSHA(), nil
+}
diff --git a/scm/github/repo_test.go b/scm/github/repo_test.go
index 55c143fa6..858d60f18 100644
--- a/scm/github/repo_test.go
+++ b/scm/github/repo_test.go
@@ -6,9 +6,9 @@ package github
import (
"fmt"
- "io/ioutil"
"net/http"
"net/http/httptest"
+ "os"
"reflect"
"strings"
"testing"
@@ -41,7 +41,7 @@ func TestGithub_Config_YML(t *testing.T) {
s := httptest.NewServer(engine)
defer s.Close()
- want, err := ioutil.ReadFile("testdata/pipeline.yml")
+ want, err := os.ReadFile("testdata/pipeline.yml")
if err != nil {
t.Errorf("Config reading file returned err: %v", err)
}
@@ -95,7 +95,7 @@ func TestGithub_ConfigBackoff_YML(t *testing.T) {
s := httptest.NewServer(engine)
defer s.Close()
- want, err := ioutil.ReadFile("testdata/pipeline.yml")
+ want, err := os.ReadFile("testdata/pipeline.yml")
if err != nil {
t.Errorf("Config reading file returned err: %v", err)
}
@@ -155,6 +155,7 @@ func TestGithub_Config_YML_BadRequest(t *testing.T) {
// run test
got, err := client.Config(u, r, "")
+
if resp.Code != http.StatusOK {
t.Errorf("Config returned %v, want %v", resp.Code, http.StatusOK)
}
@@ -190,7 +191,7 @@ func TestGithub_Config_YAML(t *testing.T) {
s := httptest.NewServer(engine)
defer s.Close()
- want, err := ioutil.ReadFile("testdata/pipeline.yml")
+ want, err := os.ReadFile("testdata/pipeline.yml")
if err != nil {
t.Errorf("Config reading file returned err: %v", err)
}
@@ -244,7 +245,7 @@ func TestGithub_Config_Star(t *testing.T) {
s := httptest.NewServer(engine)
defer s.Close()
- want, err := ioutil.ReadFile("testdata/pipeline.yml")
+ want, err := os.ReadFile("testdata/pipeline.yml")
if err != nil {
t.Errorf("Config reading file returned err: %v", err)
}
@@ -299,7 +300,7 @@ func TestGithub_Config_Py(t *testing.T) {
s := httptest.NewServer(engine)
defer s.Close()
- want, err := ioutil.ReadFile("testdata/pipeline.yml")
+ want, err := os.ReadFile("testdata/pipeline.yml")
if err != nil {
t.Errorf("Config reading file returned err: %v", err)
}
@@ -601,10 +602,27 @@ func TestGithub_Enable(t *testing.T) {
u.SetName("foo")
u.SetToken("bar")
+ wantHook := new(library.Hook)
+ wantHook.SetWebhookID(1)
+ wantHook.SetSourceID("bar-initialize")
+ wantHook.SetCreated(1315329987)
+ wantHook.SetNumber(1)
+ wantHook.SetEvent("initialize")
+ wantHook.SetStatus("success")
+
+ r := new(library.Repo)
+ r.SetID(1)
+ r.SetName("bar")
+ r.SetOrg("foo")
+ r.SetHash("secret")
+ r.SetAllowPush(true)
+ r.SetAllowPull(true)
+ r.SetAllowDeploy(true)
+
client, _ := NewTest(s.URL)
// run test
- _, err := client.Enable(u, "foo", "bar", "secret")
+ got, _, err := client.Enable(u, r, new(library.Hook))
if resp.Code != http.StatusOK {
t.Errorf("Enable returned %v, want %v", resp.Code, http.StatusOK)
@@ -613,6 +631,57 @@ func TestGithub_Enable(t *testing.T) {
if err != nil {
t.Errorf("Enable returned err: %v", err)
}
+
+ if !reflect.DeepEqual(wantHook, got) {
+ t.Errorf("Enable returned hook %v, want %v", got, wantHook)
+ }
+}
+
+func TestGithub_Update(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ _, engine := gin.CreateTestContext(resp)
+
+ // setup mock server
+ engine.PATCH("/api/v3/repos/:org/:repo/hooks/:hook_id", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/hook.json")
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup types
+ u := new(library.User)
+ u.SetName("foo")
+ u.SetToken("bar")
+
+ r := new(library.Repo)
+ r.SetID(1)
+ r.SetName("bar")
+ r.SetOrg("foo")
+ r.SetHash("secret")
+ r.SetAllowPush(true)
+ r.SetAllowPull(true)
+ r.SetAllowDeploy(true)
+
+ hookID := int64(1)
+
+ client, _ := NewTest(s.URL)
+
+ // run test
+ err := client.Update(u, r, hookID)
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("Update returned %v, want %v", resp.Code, http.StatusOK)
+ }
+
+ if err != nil {
+ t.Errorf("Update returned err: %v", err)
+ }
}
func TestGithub_Status_Deployment(t *testing.T) {
@@ -958,6 +1027,8 @@ func TestGithub_GetRepo(t *testing.T) {
want.SetClone("https://github.com/octocat/Hello-World.git")
want.SetBranch("master")
want.SetPrivate(false)
+ want.SetTopics([]string{"octocat", "atom", "electron", "api"})
+ want.SetVisibility("public")
client, _ := NewTest(s.URL)
@@ -1012,6 +1083,84 @@ func TestGithub_GetRepo_Fail(t *testing.T) {
}
}
+func TestGithub_GetOrgAndRepoName(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ _, engine := gin.CreateTestContext(resp)
+
+ // setup mock server
+ engine.GET("/api/v3/repos/:owner/:repo", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/get_repo.json")
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup types
+ u := new(library.User)
+ u.SetName("foo")
+ u.SetToken("bar")
+
+ wantOrg := "octocat"
+ wantRepo := "Hello-World"
+
+ client, _ := NewTest(s.URL)
+
+ // run test
+ gotOrg, gotRepo, err := client.GetOrgAndRepoName(u, "octocat", "Hello-World")
+
+ if resp.Code != http.StatusOK {
+ t.Errorf("GetRepoName returned %v, want %v", resp.Code, http.StatusOK)
+ }
+
+ if err != nil {
+ t.Errorf("GetRepoName returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(gotOrg, wantOrg) {
+ t.Errorf("GetRepoName org is %v, want %v", gotOrg, wantOrg)
+ }
+
+ if !reflect.DeepEqual(gotRepo, wantRepo) {
+ t.Errorf("GetRepoName repo is %v, want %v", gotRepo, wantRepo)
+ }
+}
+
+func TestGithub_GetOrgAndRepoName_Fail(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ _, engine := gin.CreateTestContext(resp)
+
+ // setup mock server
+ engine.GET("/api/v3/repos/:owner/:repo", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusNotFound)
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup types
+ u := new(library.User)
+ u.SetName("foo")
+ u.SetToken("bar")
+
+ client, _ := NewTest(s.URL)
+
+ // run test
+ _, _, err := client.GetOrgAndRepoName(u, "octocat", "Hello-World")
+
+ if err == nil {
+ t.Error("GetRepoName should return error")
+ }
+}
+
func TestGithub_ListUserRepos(t *testing.T) {
// setup context
gin.SetMode(gin.TestMode)
@@ -1042,6 +1191,8 @@ func TestGithub_ListUserRepos(t *testing.T) {
r.SetClone("https://github.com/octocat/Hello-World.git")
r.SetBranch("master")
r.SetPrivate(false)
+ r.SetTopics([]string{"octocat", "atom", "electron", "api"})
+ r.SetVisibility("public")
want := []*library.Repo{r}
@@ -1153,3 +1304,52 @@ func TestGithub_GetPullRequest(t *testing.T) {
t.Errorf("HeadRef is %v, want %v", gotHeadRef, wantHeadRef)
}
}
+
+func TestGithub_GetBranch(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ _, engine := gin.CreateTestContext(resp)
+
+ // setup mock server
+ engine.GET("/api/v3/repos/:owner/:repo/branches/:branch", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/branch.json")
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup types
+ u := new(library.User)
+ u.SetName("foo")
+ u.SetToken("bar")
+
+ r := new(library.Repo)
+ r.SetOrg("octocat")
+ r.SetName("Hello-World")
+ r.SetFullName("octocat/Hello-World")
+ r.SetBranch("main")
+
+ wantBranch := "main"
+ wantCommit := "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d"
+
+ client, _ := NewTest(s.URL)
+
+ // run test
+ gotBranch, gotCommit, err := client.GetBranch(u, r, "main")
+
+ if err != nil {
+ t.Errorf("Status returned err: %v", err)
+ }
+
+ if !strings.EqualFold(gotBranch, wantBranch) {
+ t.Errorf("Branch is %v, want %v", gotBranch, wantBranch)
+ }
+
+ if !strings.EqualFold(gotCommit, wantCommit) {
+ t.Errorf("Commit is %v, want %v", gotCommit, wantCommit)
+ }
+}
diff --git a/scm/github/testdata/branch.json b/scm/github/testdata/branch.json
new file mode 100644
index 000000000..b133e7b38
--- /dev/null
+++ b/scm/github/testdata/branch.json
@@ -0,0 +1,101 @@
+{
+ "name": "main",
+ "commit": {
+ "sha": "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d",
+ "node_id": "MDY6Q29tbWl0MTI5NjI2OTo3ZmQxYTYwYjAxZjkxYjMxNGY1OTk1NWE0ZTRkNGU4MGQ4ZWRmMTFk",
+ "commit": {
+ "author": {
+ "name": "The Octocat",
+ "email": "octocat@nowhere.com",
+ "date": "2012-03-06T23:06:50Z"
+ },
+ "committer": {
+ "name": "The Octocat",
+ "email": "octocat@nowhere.com",
+ "date": "2012-03-06T23:06:50Z"
+ },
+ "message": "Merge pull request #6 from Spaceghost/patch-1\n\nNew line at end of file.",
+ "tree": {
+ "sha": "b4eecafa9be2f2006ce1b709d6857b07069b4608",
+ "url": "https://api.github.com/repos/octocat/Hello-World/git/trees/b4eecafa9be2f2006ce1b709d6857b07069b4608"
+ },
+ "url": "https://api.github.com/repos/octocat/Hello-World/git/commits/7fd1a60b01f91b314f59955a4e4d4e80d8edf11d",
+ "comment_count": 77,
+ "verification": {
+ "verified": false,
+ "reason": "unsigned",
+ "signature": null,
+ "payload": null
+ }
+ },
+ "url": "https://api.github.com/repos/octocat/Hello-World/commits/7fd1a60b01f91b314f59955a4e4d4e80d8edf11d",
+ "html_url": "https://github.com/octocat/Hello-World/commit/7fd1a60b01f91b314f59955a4e4d4e80d8edf11d",
+ "comments_url": "https://api.github.com/repos/octocat/Hello-World/commits/7fd1a60b01f91b314f59955a4e4d4e80d8edf11d/comments",
+ "author": {
+ "login": "octocat",
+ "id": 583231,
+ "node_id": "MDQ6VXNlcjU4MzIzMQ==",
+ "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/octocat",
+ "html_url": "https://github.com/octocat",
+ "followers_url": "https://api.github.com/users/octocat/followers",
+ "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+ "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+ "organizations_url": "https://api.github.com/users/octocat/orgs",
+ "repos_url": "https://api.github.com/users/octocat/repos",
+ "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/octocat/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "committer": {
+ "login": "octocat",
+ "id": 583231,
+ "node_id": "MDQ6VXNlcjU4MzIzMQ==",
+ "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/octocat",
+ "html_url": "https://github.com/octocat",
+ "followers_url": "https://api.github.com/users/octocat/followers",
+ "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+ "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+ "organizations_url": "https://api.github.com/users/octocat/orgs",
+ "repos_url": "https://api.github.com/users/octocat/repos",
+ "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/octocat/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "parents": [
+ {
+ "sha": "553c2077f0edc3d5dc5d17262f6aa498e69d6f8e",
+ "url": "https://api.github.com/repos/octocat/Hello-World/commits/553c2077f0edc3d5dc5d17262f6aa498e69d6f8e",
+ "html_url": "https://github.com/octocat/Hello-World/commit/553c2077f0edc3d5dc5d17262f6aa498e69d6f8e"
+ },
+ {
+ "sha": "762941318ee16e59dabbacb1b4049eec22f0d303",
+ "url": "https://api.github.com/repos/octocat/Hello-World/commits/762941318ee16e59dabbacb1b4049eec22f0d303",
+ "html_url": "https://github.com/octocat/Hello-World/commit/762941318ee16e59dabbacb1b4049eec22f0d303"
+ }
+ ]
+ },
+ "_links": {
+ "self": "https://api.github.com/repos/octocat/Hello-World/branches/main",
+ "html": "https://github.com/octocat/Hello-World/tree/main"
+ },
+ "protected": false,
+ "protection": {
+ "enabled": false,
+ "required_status_checks": {
+ "enforcement_level": "off",
+ "contexts": [],
+ "checks": []
+ }
+ },
+ "protection_url": "https://api.github.com/repos/octocat/Hello-World/branches/main/protection"
+}
\ No newline at end of file
diff --git a/scm/github/testdata/delivery_summaries.json b/scm/github/testdata/delivery_summaries.json
new file mode 100644
index 000000000..12d8e3ae5
--- /dev/null
+++ b/scm/github/testdata/delivery_summaries.json
@@ -0,0 +1,72 @@
+[
+ {
+ "id": 22948189250,
+ "guid": "b6552230-aee1-11ec-949c-91a5dc3f8159",
+ "delivered_at": "2022-03-28T21:54:59Z",
+ "redelivery": false,
+ "duration": 0.4,
+ "status": "Invalid HTTP Response: 404",
+ "status_code": 404,
+ "event": "pull_request",
+ "action": "edited",
+ "installation_id": null,
+ "repository_id": 219518422,
+ "url": ""
+ },
+ {
+ "id": 22948188373,
+ "guid": "b595f0e0-aee1-11ec-86cf-9418381395c4",
+ "delivered_at": "2022-03-28T21:54:58Z",
+ "redelivery": false,
+ "duration": 0.31,
+ "status": "Invalid HTTP Response: 404",
+ "status_code": 404,
+ "event": "pull_request",
+ "action": "synchronize",
+ "installation_id": null,
+ "repository_id": 219518422,
+ "url": ""
+ },
+ {
+ "id": 22948187625,
+ "guid": "b4f5b1a2-aee1-11ec-94e7-28efa21cef07",
+ "delivered_at": "2022-03-28T21:54:57Z",
+ "redelivery": false,
+ "duration": 0.28,
+ "status": "Invalid HTTP Response: 404",
+ "status_code": 404,
+ "event": "push",
+ "action": null,
+ "installation_id": null,
+ "repository_id": 219518422,
+ "url": ""
+ },
+ {
+ "id": 22939666537,
+ "guid": "5275f5b0-aec7-11ec-9778-f09445731fb3",
+ "delivered_at": "2022-03-28T18:46:05Z",
+ "redelivery": false,
+ "duration": 0.29,
+ "status": "Invalid HTTP Response: 404",
+ "status_code": 404,
+ "event": "pull_request",
+ "action": "synchronize",
+ "installation_id": null,
+ "repository_id": 219518422,
+ "url": ""
+ },
+ {
+ "id": 22939665716,
+ "guid": "51d8149e-aec7-11ec-8377-b51dc14c4d81",
+ "delivered_at": "2022-03-28T18:46:04Z",
+ "redelivery": false,
+ "duration": 0.3,
+ "status": "Invalid HTTP Response: 404",
+ "status_code": 404,
+ "event": "push",
+ "action": null,
+ "installation_id": null,
+ "repository_id": 219518422,
+ "url": ""
+ }
+]
\ No newline at end of file
diff --git a/scm/github/testdata/get_org.json b/scm/github/testdata/get_org.json
new file mode 100644
index 000000000..e43ba5655
--- /dev/null
+++ b/scm/github/testdata/get_org.json
@@ -0,0 +1,53 @@
+{
+ "login": "github",
+ "id": 1,
+ "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=",
+ "url": "https://api.github.com/orgs/github",
+ "repos_url": "https://api.github.com/orgs/github/repos",
+ "events_url": "https://api.github.com/orgs/github/events",
+ "hooks_url": "https://api.github.com/orgs/github/hooks",
+ "issues_url": "https://api.github.com/orgs/github/issues",
+ "members_url": "https://api.github.com/orgs/github/members{/member}",
+ "public_members_url": "https://api.github.com/orgs/github/public_members{/member}",
+ "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+ "description": "A great organization",
+ "name": "github",
+ "company": "GitHub",
+ "blog": "https://github.com/blog",
+ "location": "San Francisco",
+ "email": "octocat@github.com",
+ "twitter_username": "github",
+ "is_verified": true,
+ "has_organization_projects": true,
+ "has_repository_projects": true,
+ "public_repos": 2,
+ "public_gists": 1,
+ "followers": 20,
+ "following": 0,
+ "html_url": "https://github.com/octocat",
+ "created_at": "2008-01-14T04:33:35Z",
+ "updated_at": "2014-03-03T18:58:10Z",
+ "type": "Organization",
+ "total_private_repos": 100,
+ "owned_private_repos": 100,
+ "private_gists": 81,
+ "disk_usage": 10000,
+ "collaborators": 8,
+ "billing_email": "mona@github.com",
+ "plan": {
+ "name": "Medium",
+ "space": 400,
+ "private_repos": 20,
+ "filled_seats": 4,
+ "seats": 5
+ },
+ "default_repository_permission": "read",
+ "members_can_create_repositories": true,
+ "two_factor_requirement_enabled": true,
+ "members_allowed_repository_creation_type": "all",
+ "members_can_create_public_repositories": false,
+ "members_can_create_private_repositories": false,
+ "members_can_create_internal_repositories": false,
+ "members_can_create_pages": true,
+ "members_can_fork_private_repositories": false
+}
\ No newline at end of file
diff --git a/scm/github/testdata/hooks/push.json b/scm/github/testdata/hooks/push.json
index 9beb5bc86..7058efe6b 100644
--- a/scm/github/testdata/hooks/push.json
+++ b/scm/github/testdata/hooks/push.json
@@ -156,7 +156,11 @@
"watchers": 0,
"default_branch": "master",
"stargazers": 0,
- "master_branch": "master"
+ "master_branch": "master",
+ "topics": [
+ "go",
+ "vela"
+ ]
},
"pusher": {
"name": "Codertocat",
diff --git a/scm/github/testdata/hooks/push_no_sender.json b/scm/github/testdata/hooks/push_no_sender.json
index 79eac0888..f9bef26d3 100644
--- a/scm/github/testdata/hooks/push_no_sender.json
+++ b/scm/github/testdata/hooks/push_no_sender.json
@@ -156,7 +156,11 @@
"watchers": 0,
"default_branch": "master",
"stargazers": 0,
- "master_branch": "master"
+ "master_branch": "master",
+ "topics": [
+ "go",
+ "vela"
+ ]
},
"pusher": {
"name": "Codertocat",
diff --git a/scm/github/testdata/hooks/repository_archived.json b/scm/github/testdata/hooks/repository_archived.json
new file mode 100644
index 000000000..b9c0ff71b
--- /dev/null
+++ b/scm/github/testdata/hooks/repository_archived.json
@@ -0,0 +1,133 @@
+{
+ "action": "archived",
+ "repository": {
+ "id": 118,
+ "node_id": "MDEwOlJlcG9zaXRvcnkxMTg=",
+ "name": "Hello-World",
+ "full_name": "Codertocat/Hello-World",
+ "private": false,
+ "owner": {
+ "login": "Codertocat",
+ "id": 4,
+ "node_id": "MDQ6VXNlcjQ=",
+ "avatar_url": "https://octocoders.github.io/avatars/u/4?",
+ "gravatar_id": "",
+ "url": "https://octocoders.github.io/api/v3/users/Codertocat",
+ "html_url": "https://octocoders.github.io/Codertocat",
+ "followers_url": "https://octocoders.github.io/api/v3/users/Codertocat/followers",
+ "following_url": "https://octocoders.github.io/api/v3/users/Codertocat/following{/other_user}",
+ "gists_url": "https://octocoders.github.io/api/v3/users/Codertocat/gists{/gist_id}",
+ "starred_url": "https://octocoders.github.io/api/v3/users/Codertocat/starred{/owner}{/repo}",
+ "subscriptions_url": "https://octocoders.github.io/api/v3/users/Codertocat/subscriptions",
+ "organizations_url": "https://octocoders.github.io/api/v3/users/Codertocat/orgs",
+ "repos_url": "https://octocoders.github.io/api/v3/users/Codertocat/repos",
+ "events_url": "https://octocoders.github.io/api/v3/users/Codertocat/events{/privacy}",
+ "received_events_url": "https://octocoders.github.io/api/v3/users/Codertocat/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "html_url": "https://octocoders.github.io/Codertocat/Hello-World",
+ "description": null,
+ "fork": false,
+ "url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World",
+ "forks_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/forks",
+ "keys_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/keys{/key_id}",
+ "collaborators_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/collaborators{/collaborator}",
+ "teams_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/teams",
+ "hooks_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/hooks",
+ "issue_events_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/issues/events{/number}",
+ "events_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/events",
+ "assignees_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/assignees{/user}",
+ "branches_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/branches{/branch}",
+ "tags_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/tags",
+ "blobs_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/blobs{/sha}",
+ "git_tags_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/tags{/sha}",
+ "git_refs_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/refs{/sha}",
+ "trees_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/trees{/sha}",
+ "statuses_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/statuses/{sha}",
+ "languages_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/languages",
+ "stargazers_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/stargazers",
+ "contributors_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/contributors",
+ "subscribers_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/subscribers",
+ "subscription_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/subscription",
+ "commits_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/commits{/sha}",
+ "git_commits_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/commits{/sha}",
+ "comments_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/comments{/number}",
+ "issue_comment_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/issues/comments{/number}",
+ "contents_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/contents/{+path}",
+ "compare_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/compare/{base}...{head}",
+ "merges_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/merges",
+ "archive_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/{archive_format}{/ref}",
+ "downloads_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/downloads",
+ "issues_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/issues{/number}",
+ "pulls_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/pulls{/number}",
+ "milestones_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/milestones{/number}",
+ "notifications_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/notifications{?since,all,participating}",
+ "labels_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/labels{/name}",
+ "releases_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/releases{/id}",
+ "deployments_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/deployments",
+ "created_at": "2019-05-15T19:37:07Z",
+ "updated_at": "2019-05-15T19:38:25Z",
+ "pushed_at": "2019-05-15T19:38:23Z",
+ "git_url": "git://octocoders.github.io/Codertocat/Hello-World.git",
+ "ssh_url": "git@octocoders.github.io:Codertocat/Hello-World.git",
+ "clone_url": "https://octocoders.github.io/Codertocat/Hello-World.git",
+ "svn_url": "https://octocoders.github.io/Codertocat/Hello-World",
+ "homepage": null,
+ "size": 0,
+ "stargazers_count": 0,
+ "watchers_count": 0,
+ "language": "Ruby",
+ "has_issues": true,
+ "has_projects": true,
+ "has_downloads": true,
+ "has_wiki": true,
+ "has_pages": true,
+ "forks_count": 0,
+ "mirror_url": null,
+ "archived": true,
+ "disabled": false,
+ "open_issues_count": 2,
+ "license": null,
+ "forks": 0,
+ "open_issues": 2,
+ "watchers": 0,
+ "default_branch": "master"
+ },
+ "enterprise": {
+ "id": 1,
+ "slug": "github",
+ "name": "GitHub",
+ "node_id": "MDg6QnVzaW5lc3Mx",
+ "avatar_url": "https://octocoders.github.io/avatars/b/1?",
+ "description": null,
+ "website_url": null,
+ "html_url": "https://octocoders.github.io/businesses/github",
+ "created_at": "2019-05-14T19:31:12Z",
+ "updated_at": "2019-05-14T19:31:12Z"
+ },
+ "sender": {
+ "login": "Codertocat",
+ "id": 4,
+ "node_id": "MDQ6VXNlcjQ=",
+ "avatar_url": "https://octocoders.github.io/avatars/u/4?",
+ "gravatar_id": "",
+ "url": "https://octocoders.github.io/api/v3/users/Codertocat",
+ "html_url": "https://octocoders.github.io/Codertocat",
+ "followers_url": "https://octocoders.github.io/api/v3/users/Codertocat/followers",
+ "following_url": "https://octocoders.github.io/api/v3/users/Codertocat/following{/other_user}",
+ "gists_url": "https://octocoders.github.io/api/v3/users/Codertocat/gists{/gist_id}",
+ "starred_url": "https://octocoders.github.io/api/v3/users/Codertocat/starred{/owner}{/repo}",
+ "subscriptions_url": "https://octocoders.github.io/api/v3/users/Codertocat/subscriptions",
+ "organizations_url": "https://octocoders.github.io/api/v3/users/Codertocat/orgs",
+ "repos_url": "https://octocoders.github.io/api/v3/users/Codertocat/repos",
+ "events_url": "https://octocoders.github.io/api/v3/users/Codertocat/events{/privacy}",
+ "received_events_url": "https://octocoders.github.io/api/v3/users/Codertocat/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "installation": {
+ "id": 5,
+ "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNQ=="
+ }
+}
\ No newline at end of file
diff --git a/scm/github/testdata/hooks/repository_edited.json b/scm/github/testdata/hooks/repository_edited.json
new file mode 100644
index 000000000..c9a41a1a1
--- /dev/null
+++ b/scm/github/testdata/hooks/repository_edited.json
@@ -0,0 +1,142 @@
+{
+ "action": "edited",
+ "changes": {
+ "default_branch": {
+ "from": "not-main"
+ }
+ },
+ "repository": {
+ "id": 118,
+ "node_id": "MDEwOlJlcG9zaXRvcnkxMTg=",
+ "name": "Hello-World",
+ "full_name": "Codertocat/Hello-World",
+ "private": false,
+ "owner": {
+ "login": "Codertocat",
+ "id": 4,
+ "node_id": "MDQ6VXNlcjQ=",
+ "avatar_url": "https://octocoders.github.io/avatars/u/4?",
+ "gravatar_id": "",
+ "url": "https://octocoders.github.io/api/v3/users/Codertocat",
+ "html_url": "https://octocoders.github.io/Codertocat",
+ "followers_url": "https://octocoders.github.io/api/v3/users/Codertocat/followers",
+ "following_url": "https://octocoders.github.io/api/v3/users/Codertocat/following{/other_user}",
+ "gists_url": "https://octocoders.github.io/api/v3/users/Codertocat/gists{/gist_id}",
+ "starred_url": "https://octocoders.github.io/api/v3/users/Codertocat/starred{/owner}{/repo}",
+ "subscriptions_url": "https://octocoders.github.io/api/v3/users/Codertocat/subscriptions",
+ "organizations_url": "https://octocoders.github.io/api/v3/users/Codertocat/orgs",
+ "repos_url": "https://octocoders.github.io/api/v3/users/Codertocat/repos",
+ "events_url": "https://octocoders.github.io/api/v3/users/Codertocat/events{/privacy}",
+ "received_events_url": "https://octocoders.github.io/api/v3/users/Codertocat/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "html_url": "https://octocoders.github.io/Codertocat/Hello-World",
+ "description": null,
+ "fork": false,
+ "url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World",
+ "forks_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/forks",
+ "keys_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/keys{/key_id}",
+ "collaborators_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/collaborators{/collaborator}",
+ "teams_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/teams",
+ "hooks_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/hooks",
+ "issue_events_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/issues/events{/number}",
+ "events_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/events",
+ "assignees_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/assignees{/user}",
+ "branches_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/branches{/branch}",
+ "tags_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/tags",
+ "blobs_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/blobs{/sha}",
+ "git_tags_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/tags{/sha}",
+ "git_refs_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/refs{/sha}",
+ "trees_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/trees{/sha}",
+ "statuses_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/statuses/{sha}",
+ "languages_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/languages",
+ "stargazers_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/stargazers",
+ "contributors_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/contributors",
+ "subscribers_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/subscribers",
+ "subscription_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/subscription",
+ "commits_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/commits{/sha}",
+ "git_commits_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/commits{/sha}",
+ "comments_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/comments{/number}",
+ "issue_comment_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/issues/comments{/number}",
+ "contents_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/contents/{+path}",
+ "compare_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/compare/{base}...{head}",
+ "merges_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/merges",
+ "archive_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/{archive_format}{/ref}",
+ "downloads_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/downloads",
+ "issues_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/issues{/number}",
+ "pulls_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/pulls{/number}",
+ "milestones_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/milestones{/number}",
+ "notifications_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/notifications{?since,all,participating}",
+ "labels_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/labels{/name}",
+ "releases_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/releases{/id}",
+ "deployments_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/deployments",
+ "created_at": "2019-05-15T19:37:07Z",
+ "updated_at": "2019-05-15T19:38:25Z",
+ "pushed_at": "2019-05-15T19:38:23Z",
+ "git_url": "git://octocoders.github.io/Codertocat/Hello-World.git",
+ "ssh_url": "git@octocoders.github.io:Codertocat/Hello-World.git",
+ "clone_url": "https://octocoders.github.io/Codertocat/Hello-World.git",
+ "svn_url": "https://octocoders.github.io/Codertocat/Hello-World",
+ "homepage": null,
+ "size": 0,
+ "stargazers_count": 0,
+ "watchers_count": 0,
+ "language": "Ruby",
+ "has_issues": true,
+ "has_projects": true,
+ "has_downloads": true,
+ "has_wiki": true,
+ "has_pages": true,
+ "forks_count": 0,
+ "mirror_url": null,
+ "archived": false,
+ "disabled": false,
+ "open_issues_count": 2,
+ "license": null,
+ "forks": 0,
+ "open_issues": 2,
+ "watchers": 0,
+ "default_branch": "main",
+ "topics": [
+ "cloud",
+ "security"
+ ]
+ },
+ "enterprise": {
+ "id": 1,
+ "slug": "github",
+ "name": "GitHub",
+ "node_id": "MDg6QnVzaW5lc3Mx",
+ "avatar_url": "https://octocoders.github.io/avatars/b/1?",
+ "description": null,
+ "website_url": null,
+ "html_url": "https://octocoders.github.io/businesses/github",
+ "created_at": "2019-05-14T19:31:12Z",
+ "updated_at": "2019-05-14T19:31:12Z"
+ },
+ "sender": {
+ "login": "Codertocat",
+ "id": 4,
+ "node_id": "MDQ6VXNlcjQ=",
+ "avatar_url": "https://octocoders.github.io/avatars/u/4?",
+ "gravatar_id": "",
+ "url": "https://octocoders.github.io/api/v3/users/Codertocat",
+ "html_url": "https://octocoders.github.io/Codertocat",
+ "followers_url": "https://octocoders.github.io/api/v3/users/Codertocat/followers",
+ "following_url": "https://octocoders.github.io/api/v3/users/Codertocat/following{/other_user}",
+ "gists_url": "https://octocoders.github.io/api/v3/users/Codertocat/gists{/gist_id}",
+ "starred_url": "https://octocoders.github.io/api/v3/users/Codertocat/starred{/owner}{/repo}",
+ "subscriptions_url": "https://octocoders.github.io/api/v3/users/Codertocat/subscriptions",
+ "organizations_url": "https://octocoders.github.io/api/v3/users/Codertocat/orgs",
+ "repos_url": "https://octocoders.github.io/api/v3/users/Codertocat/repos",
+ "events_url": "https://octocoders.github.io/api/v3/users/Codertocat/events{/privacy}",
+ "received_events_url": "https://octocoders.github.io/api/v3/users/Codertocat/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "installation": {
+ "id": 5,
+ "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNQ=="
+ }
+}
\ No newline at end of file
diff --git a/scm/github/testdata/hooks/repository_transferred.json b/scm/github/testdata/hooks/repository_transferred.json
new file mode 100644
index 000000000..2fdea23f6
--- /dev/null
+++ b/scm/github/testdata/hooks/repository_transferred.json
@@ -0,0 +1,159 @@
+{
+ "action": "transferred",
+ "changes": {
+ "owner": {
+ "from": {
+ "user": {
+ "login": "Old-Codertocat",
+ "id": 4,
+ "node_id": "MDQ6VXNlcjQ=",
+ "avatar_url": "https://octocoders.github.io/avatars/u/4?",
+ "gravatar_id": "",
+ "url": "https://octocoders.github.io/api/v3/users/Codertocat",
+ "html_url": "https://octocoders.github.io/Codertocat",
+ "followers_url": "https://octocoders.github.io/api/v3/users/Codertocat/followers",
+ "following_url": "https://octocoders.github.io/api/v3/users/Codertocat/following{/other_user}",
+ "gists_url": "https://octocoders.github.io/api/v3/users/Codertocat/gists{/gist_id}",
+ "starred_url": "https://octocoders.github.io/api/v3/users/Codertocat/starred{/owner}{/repo}",
+ "subscriptions_url": "https://octocoders.github.io/api/v3/users/Codertocat/subscriptions",
+ "organizations_url": "https://octocoders.github.io/api/v3/users/Codertocat/orgs",
+ "repos_url": "https://octocoders.github.io/api/v3/users/Codertocat/repos",
+ "events_url": "https://octocoders.github.io/api/v3/users/Codertocat/events{/privacy}",
+ "received_events_url": "https://octocoders.github.io/api/v3/users/Codertocat/received_events",
+ "type": "User",
+ "site_admin": false
+ }
+ }
+ }
+ },
+ "repository": {
+ "id": 118,
+ "node_id": "MDEwOlJlcG9zaXRvcnkxMTg=",
+ "name": "Hello-World",
+ "full_name": "Codertocat/Hello-World",
+ "private": false,
+ "owner": {
+ "login": "Codertocat",
+ "id": 4,
+ "node_id": "MDQ6VXNlcjQ=",
+ "avatar_url": "https://octocoders.github.io/avatars/u/4?",
+ "gravatar_id": "",
+ "url": "https://octocoders.github.io/api/v3/users/Codertocat",
+ "html_url": "https://octocoders.github.io/Codertocat",
+ "followers_url": "https://octocoders.github.io/api/v3/users/Codertocat/followers",
+ "following_url": "https://octocoders.github.io/api/v3/users/Codertocat/following{/other_user}",
+ "gists_url": "https://octocoders.github.io/api/v3/users/Codertocat/gists{/gist_id}",
+ "starred_url": "https://octocoders.github.io/api/v3/users/Codertocat/starred{/owner}{/repo}",
+ "subscriptions_url": "https://octocoders.github.io/api/v3/users/Codertocat/subscriptions",
+ "organizations_url": "https://octocoders.github.io/api/v3/users/Codertocat/orgs",
+ "repos_url": "https://octocoders.github.io/api/v3/users/Codertocat/repos",
+ "events_url": "https://octocoders.github.io/api/v3/users/Codertocat/events{/privacy}",
+ "received_events_url": "https://octocoders.github.io/api/v3/users/Codertocat/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "html_url": "https://octocoders.github.io/Codertocat/Hello-World",
+ "description": null,
+ "fork": false,
+ "url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World",
+ "forks_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/forks",
+ "keys_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/keys{/key_id}",
+ "collaborators_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/collaborators{/collaborator}",
+ "teams_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/teams",
+ "hooks_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/hooks",
+ "issue_events_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/issues/events{/number}",
+ "events_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/events",
+ "assignees_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/assignees{/user}",
+ "branches_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/branches{/branch}",
+ "tags_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/tags",
+ "blobs_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/blobs{/sha}",
+ "git_tags_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/tags{/sha}",
+ "git_refs_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/refs{/sha}",
+ "trees_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/trees{/sha}",
+ "statuses_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/statuses/{sha}",
+ "languages_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/languages",
+ "stargazers_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/stargazers",
+ "contributors_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/contributors",
+ "subscribers_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/subscribers",
+ "subscription_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/subscription",
+ "commits_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/commits{/sha}",
+ "git_commits_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/git/commits{/sha}",
+ "comments_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/comments{/number}",
+ "issue_comment_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/issues/comments{/number}",
+ "contents_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/contents/{+path}",
+ "compare_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/compare/{base}...{head}",
+ "merges_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/merges",
+ "archive_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/{archive_format}{/ref}",
+ "downloads_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/downloads",
+ "issues_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/issues{/number}",
+ "pulls_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/pulls{/number}",
+ "milestones_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/milestones{/number}",
+ "notifications_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/notifications{?since,all,participating}",
+ "labels_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/labels{/name}",
+ "releases_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/releases{/id}",
+ "deployments_url": "https://octocoders.github.io/api/v3/repos/Codertocat/Hello-World/deployments",
+ "created_at": "2019-05-15T19:37:07Z",
+ "updated_at": "2019-05-15T19:38:25Z",
+ "pushed_at": "2019-05-15T19:38:23Z",
+ "git_url": "git://octocoders.github.io/Codertocat/Hello-World.git",
+ "ssh_url": "git@octocoders.github.io:Codertocat/Hello-World.git",
+ "clone_url": "https://octocoders.github.io/Codertocat/Hello-World.git",
+ "svn_url": "https://octocoders.github.io/Codertocat/Hello-World",
+ "homepage": null,
+ "size": 0,
+ "stargazers_count": 0,
+ "watchers_count": 0,
+ "language": "Ruby",
+ "has_issues": true,
+ "has_projects": true,
+ "has_downloads": true,
+ "has_wiki": true,
+ "has_pages": true,
+ "forks_count": 0,
+ "mirror_url": null,
+ "archived": false,
+ "disabled": false,
+ "open_issues_count": 2,
+ "license": null,
+ "forks": 0,
+ "open_issues": 2,
+ "watchers": 0,
+ "default_branch": "master"
+ },
+ "enterprise": {
+ "id": 1,
+ "slug": "github",
+ "name": "GitHub",
+ "node_id": "MDg6QnVzaW5lc3Mx",
+ "avatar_url": "https://octocoders.github.io/avatars/b/1?",
+ "description": null,
+ "website_url": null,
+ "html_url": "https://octocoders.github.io/businesses/github",
+ "created_at": "2019-05-14T19:31:12Z",
+ "updated_at": "2019-05-14T19:31:12Z"
+ },
+ "sender": {
+ "login": "Codertocat",
+ "id": 4,
+ "node_id": "MDQ6VXNlcjQ=",
+ "avatar_url": "https://octocoders.github.io/avatars/u/4?",
+ "gravatar_id": "",
+ "url": "https://octocoders.github.io/api/v3/users/Codertocat",
+ "html_url": "https://octocoders.github.io/Codertocat",
+ "followers_url": "https://octocoders.github.io/api/v3/users/Codertocat/followers",
+ "following_url": "https://octocoders.github.io/api/v3/users/Codertocat/following{/other_user}",
+ "gists_url": "https://octocoders.github.io/api/v3/users/Codertocat/gists{/gist_id}",
+ "starred_url": "https://octocoders.github.io/api/v3/users/Codertocat/starred{/owner}{/repo}",
+ "subscriptions_url": "https://octocoders.github.io/api/v3/users/Codertocat/subscriptions",
+ "organizations_url": "https://octocoders.github.io/api/v3/users/Codertocat/orgs",
+ "repos_url": "https://octocoders.github.io/api/v3/users/Codertocat/repos",
+ "events_url": "https://octocoders.github.io/api/v3/users/Codertocat/events{/privacy}",
+ "received_events_url": "https://octocoders.github.io/api/v3/users/Codertocat/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "installation": {
+ "id": 5,
+ "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNQ=="
+ }
+ }
\ No newline at end of file
diff --git a/scm/github/webhook.go b/scm/github/webhook.go
index 7db25edf2..43615e8cb 100644
--- a/scm/github/webhook.go
+++ b/scm/github/webhook.go
@@ -5,9 +5,13 @@
package github
import (
+ "context"
"encoding/json"
+ "errors"
"fmt"
+ "mime"
"net/http"
+ "strconv"
"strings"
"time"
@@ -16,16 +20,25 @@ import (
"github.com/go-vela/types"
"github.com/go-vela/types/constants"
"github.com/go-vela/types/library"
- "github.com/google/go-github/v42/github"
+ "github.com/google/go-github/v53/github"
)
// ProcessWebhook parses the webhook from a repo.
+//
+//nolint:nilerr // ignore webhook returning nil
func (c *client) ProcessWebhook(request *http.Request) (*types.Webhook, error) {
c.Logger.Tracef("processing GitHub webhook")
h := new(library.Hook)
h.SetNumber(1)
h.SetSourceID(request.Header.Get("X-GitHub-Delivery"))
+
+ hookID, err := strconv.Atoi(request.Header.Get("X-GitHub-Hook-ID"))
+ if err != nil {
+ return nil, fmt.Errorf("unable to convert hook id to int64: %w", err)
+ }
+
+ h.SetWebhookID(int64(hookID))
h.SetCreated(time.Now().UTC().Unix())
h.SetHost("github.com")
h.SetEvent(request.Header.Get("X-GitHub-Event"))
@@ -35,7 +48,13 @@ func (c *client) ProcessWebhook(request *http.Request) (*types.Webhook, error) {
h.SetHost(request.Header.Get("X-GitHub-Enterprise-Host"))
}
- payload, err := github.ValidatePayload(request, nil)
+ // get content type
+ contentType, _, err := mime.ParseMediaType(request.Header.Get("Content-Type"))
+ if err != nil {
+ return nil, err
+ }
+
+ payload, err := github.ValidatePayloadFromBody(contentType, request.Body, "", nil)
if err != nil {
return &types.Webhook{Hook: h}, nil
}
@@ -79,9 +98,36 @@ func (c *client) VerifyWebhook(request *http.Request, r *library.Repo) error {
return nil
}
+// RedeliverWebhook redelivers webhooks from GitHub.
+func (c *client) RedeliverWebhook(ctx context.Context, u *library.User, r *library.Repo, h *library.Hook) error {
+ // create GitHub OAuth client with user's token
+ //nolint:contextcheck // do not need to pass context in this instance
+ client := c.newClientToken(*u.Token)
+
+ // capture the delivery ID of the hook using GitHub API
+ deliveryID, err := c.getDeliveryID(ctx, client, r, h)
+ if err != nil {
+ return err
+ }
+
+ // redeliver the webhook
+ _, _, err = client.Repositories.RedeliverHookDelivery(ctx, r.GetOrg(), r.GetName(), h.GetWebhookID(), deliveryID)
+
+ if err != nil {
+ var acceptedError *github.AcceptedError
+ // Persist if the status received is a 202 Accepted. This
+ // means the job was added to the queue for GitHub.
+ if errors.As(err, &acceptedError) {
+ return nil
+ }
+
+ return err
+ }
+
+ return nil
+}
+
// processPushEvent is a helper function to process the push event.
-//
-// nolint: lll // ignore long line length due to variable names
func (c *client) processPushEvent(h *library.Hook, payload *github.PushEvent) (*types.Webhook, error) {
c.Logger.WithFields(logrus.Fields{
"org": payload.GetRepo().GetOwner().GetLogin(),
@@ -99,6 +145,7 @@ func (c *client) processPushEvent(h *library.Hook, payload *github.PushEvent) (*
r.SetClone(repo.GetCloneURL())
r.SetBranch(repo.GetDefaultBranch())
r.SetPrivate(repo.GetPrivate())
+ r.SetTopics(repo.Topics)
// convert payload to library build
b := new(library.Build)
@@ -159,8 +206,6 @@ func (c *client) processPushEvent(h *library.Hook, payload *github.PushEvent) (*
}
// processPREvent is a helper function to process the pull_request event.
-//
-// nolint: lll // ignore long line length due to variable names
func (c *client) processPREvent(h *library.Hook, payload *github.PullRequestEvent) (*types.Webhook, error) {
c.Logger.WithFields(logrus.Fields{
"org": payload.GetRepo().GetOwner().GetLogin(),
@@ -179,7 +224,7 @@ func (c *client) processPREvent(h *library.Hook, payload *github.PullRequestEven
return &types.Webhook{Hook: h}, nil
}
- // skip if the pull request action is not opened or synchronize
+ // skip if the pull request action is not opened, synchronize
if !strings.EqualFold(payload.GetAction(), "opened") &&
!strings.EqualFold(payload.GetAction(), "synchronize") {
return &types.Webhook{Hook: h}, nil
@@ -197,10 +242,12 @@ func (c *client) processPREvent(h *library.Hook, payload *github.PullRequestEven
r.SetClone(repo.GetCloneURL())
r.SetBranch(repo.GetDefaultBranch())
r.SetPrivate(repo.GetPrivate())
+ r.SetTopics(repo.Topics)
// convert payload to library build
b := new(library.Build)
b.SetEvent(constants.EventPull)
+ b.SetEventAction(payload.GetAction())
b.SetClone(repo.GetCloneURL())
b.SetSource(payload.GetPullRequest().GetHTMLURL())
b.SetTitle(fmt.Sprintf("%s received from %s", constants.EventPull, repo.GetHTMLURL()))
@@ -244,8 +291,6 @@ func (c *client) processPREvent(h *library.Hook, payload *github.PullRequestEven
}
// processDeploymentEvent is a helper function to process the deployment event.
-//
-// nolint: lll // ignore long line length due to variable names
func (c *client) processDeploymentEvent(h *library.Hook, payload *github.DeploymentEvent) (*types.Webhook, error) {
c.Logger.WithFields(logrus.Fields{
"org": payload.GetRepo().GetOwner().GetLogin(),
@@ -264,6 +309,7 @@ func (c *client) processDeploymentEvent(h *library.Hook, payload *github.Deploym
r.SetClone(repo.GetCloneURL())
r.SetBranch(repo.GetDefaultBranch())
r.SetPrivate(repo.GetPrivate())
+ r.SetTopics(repo.Topics)
// convert payload to library build
b := new(library.Build)
@@ -287,8 +333,6 @@ func (c *client) processDeploymentEvent(h *library.Hook, payload *github.Deploym
//
// sending an API request to GitHub with no
// payload provided yields a default of `{}`.
- //
- // nolint: gomnd // ignore magic number
if len(payload.GetDeployment().Payload) > 2 {
deployPayload := make(map[string]string)
// unmarshal the payload into the expected map[string]string format
@@ -334,8 +378,6 @@ func (c *client) processDeploymentEvent(h *library.Hook, payload *github.Deploym
}
// processIssueCommentEvent is a helper function to process the issue comment event.
-//
-// nolint: lll // ignore long line length due to variable names
func (c *client) processIssueCommentEvent(h *library.Hook, payload *github.IssueCommentEvent) (*types.Webhook, error) {
c.Logger.WithFields(logrus.Fields{
"org": payload.GetRepo().GetOwner().GetLogin(),
@@ -369,10 +411,12 @@ func (c *client) processIssueCommentEvent(h *library.Hook, payload *github.Issue
r.SetClone(repo.GetCloneURL())
r.SetBranch(repo.GetDefaultBranch())
r.SetPrivate(repo.GetPrivate())
+ r.SetTopics(repo.Topics)
// convert payload to library build
b := new(library.Build)
b.SetEvent(constants.EventComment)
+ b.SetEventAction(payload.GetAction())
b.SetClone(repo.GetCloneURL())
b.SetSource(payload.Issue.GetHTMLURL())
b.SetTitle(fmt.Sprintf("%s received from %s", constants.EventComment, repo.GetHTMLURL()))
@@ -402,7 +446,7 @@ func (c *client) processIssueCommentEvent(h *library.Hook, payload *github.Issue
}
// processRepositoryEvent is a helper function to process the repository event.
-// nolint: lll // ignore long line length due to error message
+
func (c *client) processRepositoryEvent(h *library.Hook, payload *github.RepositoryEvent) (*types.Webhook, error) {
logrus.Tracef("processing repository event GitHub webhook for %s", payload.GetRepo().GetFullName())
@@ -417,16 +461,27 @@ func (c *client) processRepositoryEvent(h *library.Hook, payload *github.Reposit
r.SetClone(repo.GetCloneURL())
r.SetBranch(repo.GetDefaultBranch())
r.SetPrivate(repo.GetPrivate())
+ r.SetActive(!repo.GetArchived())
+ r.SetTopics(repo.Topics)
// if action is renamed, then get the previous name from payload
- if payload.GetAction() == "renamed" {
- r.SetPreviousName(payload.GetChanges().GetRepo().GetName().GetFrom())
- // update hook object event type
- h.SetEvent(constants.EventRepositoryRename)
- } else {
- h.SetEvent(constants.EventRepository)
+ if payload.GetAction() == constants.ActionRenamed {
+ r.SetPreviousName(repo.GetOwner().GetLogin() + "/" + payload.GetChanges().GetRepo().GetName().GetFrom())
}
+ // if action is transferred, then get the previous owner from payload
+ // could be a user or an org, but both are User structs
+ if payload.GetAction() == constants.ActionTransferred {
+ org := payload.GetChanges().GetOwner().GetOwnerInfo().GetOrg()
+ if org == nil {
+ org = payload.GetChanges().GetOwner().GetOwnerInfo().GetUser()
+ }
+
+ r.SetPreviousName(org.GetLogin() + "/" + repo.GetName())
+ }
+
+ h.SetEvent(constants.EventRepository)
+ h.SetEventAction(payload.GetAction())
h.SetBranch(r.GetBranch())
h.SetLink(
fmt.Sprintf("https://%s/%s/settings/hooks", h.GetHost(), r.GetFullName()),
@@ -438,3 +493,40 @@ func (c *client) processRepositoryEvent(h *library.Hook, payload *github.Reposit
Repo: r,
}, nil
}
+
+// getDeliveryID gets the last 100 webhook deliveries for a repo and
+// finds the matching delivery id with the source id in the hook.
+func (c *client) getDeliveryID(ctx context.Context, ghClient *github.Client, r *library.Repo, h *library.Hook) (int64, error) {
+ c.Logger.WithFields(logrus.Fields{
+ "org": r.GetOrg(),
+ "repo": r.GetName(),
+ }).Tracef("searching for delivery id for hook: %s", h.GetSourceID())
+
+ // set per page to 100 to retrieve last 100 hook summaries
+ opt := &github.ListCursorOptions{PerPage: 100}
+
+ // send API call to capture delivery summaries that contain Delivery ID value
+ deliveries, resp, err := ghClient.Repositories.ListHookDeliveries(ctx, r.GetOrg(), r.GetName(), h.GetWebhookID(), opt)
+
+ // version check: if GitHub API is older than version 3.2, this call will not work
+ if resp.StatusCode == 415 {
+ err = fmt.Errorf("requires GitHub version 3.2 or later")
+ return 0, err
+ }
+
+ if err != nil {
+ return 0, err
+ }
+
+ // cycle through delivery summaries and match Source ID/GUID. Capture Delivery ID
+ for _, delivery := range deliveries {
+ if delivery.GetGUID() == h.GetSourceID() {
+ return delivery.GetID(), nil
+ }
+ }
+
+ // if not found, webhook was not recent enough for GitHub
+ err = fmt.Errorf("webhook no longer available to be redelivered")
+
+ return 0, err
+}
diff --git a/scm/github/webhook_test.go b/scm/github/webhook_test.go
index fff122e79..1bef4c0ca 100644
--- a/scm/github/webhook_test.go
+++ b/scm/github/webhook_test.go
@@ -5,6 +5,7 @@
package github
import (
+ "context"
"fmt"
"net/http"
"net/http/httptest"
@@ -13,6 +14,7 @@ import (
"testing"
"time"
+ "github.com/gin-gonic/gin"
"github.com/go-vela/types/raw"
"github.com/google/go-cmp/cmp"
@@ -34,10 +36,11 @@ func TestGithub_ProcessWebhook_Push(t *testing.T) {
defer body.Close()
- request, _ := http.NewRequest(http.MethodGet, "/test", body)
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
request.Header.Set("X-GitHub-Event", "push")
// setup client
@@ -47,6 +50,7 @@ func TestGithub_ProcessWebhook_Push(t *testing.T) {
wantHook := new(library.Hook)
wantHook.SetNumber(1)
wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ wantHook.SetWebhookID(123456)
wantHook.SetCreated(time.Now().UTC().Unix())
wantHook.SetHost("github.com")
wantHook.SetEvent("push")
@@ -62,6 +66,7 @@ func TestGithub_ProcessWebhook_Push(t *testing.T) {
wantRepo.SetClone("https://github.com/Codertocat/Hello-World.git")
wantRepo.SetBranch("master")
wantRepo.SetPrivate(false)
+ wantRepo.SetTopics([]string{"go", "vela"})
wantBuild := new(library.Build)
wantBuild.SetEvent("push")
@@ -108,10 +113,11 @@ func TestGithub_ProcessWebhook_Push_NoSender(t *testing.T) {
defer body.Close()
- request, _ := http.NewRequest(http.MethodGet, "/test", body)
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
request.Header.Set("X-GitHub-Host", "github.com")
request.Header.Set("X-GitHub-Version", "2.16.0")
request.Header.Set("X-GitHub-Event", "push")
@@ -123,6 +129,7 @@ func TestGithub_ProcessWebhook_Push_NoSender(t *testing.T) {
wantHook := new(library.Hook)
wantHook.SetNumber(1)
wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ wantHook.SetWebhookID(123456)
wantHook.SetCreated(time.Now().UTC().Unix())
wantHook.SetHost("github.com")
wantHook.SetEvent("push")
@@ -138,6 +145,7 @@ func TestGithub_ProcessWebhook_Push_NoSender(t *testing.T) {
wantRepo.SetClone("https://github.com/Codertocat/Hello-World.git")
wantRepo.SetBranch("master")
wantRepo.SetPrivate(false)
+ wantRepo.SetTopics([]string{"go", "vela"})
wantBuild := new(library.Build)
wantBuild.SetEvent("push")
@@ -184,10 +192,11 @@ func TestGithub_ProcessWebhook_PullRequest(t *testing.T) {
defer body.Close()
- request, _ := http.NewRequest(http.MethodGet, "/test", body)
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
request.Header.Set("X-GitHub-Host", "github.com")
request.Header.Set("X-GitHub-Version", "2.16.0")
request.Header.Set("X-GitHub-Event", "pull_request")
@@ -199,6 +208,7 @@ func TestGithub_ProcessWebhook_PullRequest(t *testing.T) {
wantHook := new(library.Hook)
wantHook.SetNumber(1)
wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ wantHook.SetWebhookID(123456)
wantHook.SetCreated(time.Now().UTC().Unix())
wantHook.SetHost("github.com")
wantHook.SetEvent("pull_request")
@@ -214,9 +224,11 @@ func TestGithub_ProcessWebhook_PullRequest(t *testing.T) {
wantRepo.SetClone("https://github.com/Codertocat/Hello-World.git")
wantRepo.SetBranch("master")
wantRepo.SetPrivate(false)
+ wantRepo.SetTopics(nil)
wantBuild := new(library.Build)
wantBuild.SetEvent("pull_request")
+ wantBuild.SetEventAction("opened")
wantBuild.SetClone("https://github.com/Codertocat/Hello-World.git")
wantBuild.SetSource("https://github.com/Codertocat/Hello-World/pull/1")
wantBuild.SetTitle("pull_request received from https://github.com/Codertocat/Hello-World")
@@ -262,10 +274,11 @@ func TestGithub_ProcessWebhook_PullRequest_ClosedAction(t *testing.T) {
defer body.Close()
- request, _ := http.NewRequest(http.MethodGet, "/test", body)
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
request.Header.Set("X-GitHub-Host", "github.com")
request.Header.Set("X-GitHub-Version", "2.16.0")
request.Header.Set("X-GitHub-Event", "pull_request")
@@ -277,6 +290,7 @@ func TestGithub_ProcessWebhook_PullRequest_ClosedAction(t *testing.T) {
wantHook := new(library.Hook)
wantHook.SetNumber(1)
wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ wantHook.SetWebhookID(123456)
wantHook.SetCreated(time.Now().UTC().Unix())
wantHook.SetHost("github.com")
wantHook.SetEvent("pull_request")
@@ -315,10 +329,11 @@ func TestGithub_ProcessWebhook_PullRequest_ClosedState(t *testing.T) {
defer body.Close()
- request, _ := http.NewRequest(http.MethodGet, "/test", body)
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
request.Header.Set("X-GitHub-Host", "github.com")
request.Header.Set("X-GitHub-Version", "2.16.0")
request.Header.Set("X-GitHub-Event", "pull_request")
@@ -330,6 +345,7 @@ func TestGithub_ProcessWebhook_PullRequest_ClosedState(t *testing.T) {
wantHook := new(library.Hook)
wantHook.SetNumber(1)
wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ wantHook.SetWebhookID(123456)
wantHook.SetCreated(time.Now().UTC().Unix())
wantHook.SetHost("github.com")
wantHook.SetEvent("pull_request")
@@ -363,6 +379,7 @@ func TestGithub_ProcessWebhook_Deployment(t *testing.T) {
wantHook := new(library.Hook)
wantHook.SetNumber(1)
wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ wantHook.SetWebhookID(123456)
wantHook.SetCreated(time.Now().UTC().Unix())
wantHook.SetBranch("master")
wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks")
@@ -378,6 +395,7 @@ func TestGithub_ProcessWebhook_Deployment(t *testing.T) {
wantRepo.SetClone("https://github.com/Codertocat/Hello-World.git")
wantRepo.SetBranch("master")
wantRepo.SetPrivate(false)
+ wantRepo.SetTopics(nil)
wantBuild := new(library.Build)
wantBuild.SetEvent("deployment")
@@ -400,6 +418,7 @@ func TestGithub_ProcessWebhook_Deployment(t *testing.T) {
build *library.Build
deploymentPayload raw.StringSliceMap
}
+
tests := []struct {
name string
args args
@@ -409,6 +428,7 @@ func TestGithub_ProcessWebhook_Deployment(t *testing.T) {
{"unexpected json payload", args{file: "deployment_unexpected_json_payload.json", deploymentPayload: raw.StringSliceMap{}}, true},
{"unexpected text payload", args{file: "deployment_unexpected_text_payload.json", deploymentPayload: raw.StringSliceMap{}}, true},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
body, err := os.Open(fmt.Sprintf("testdata/hooks/%s", tt.args.file))
@@ -418,10 +438,11 @@ func TestGithub_ProcessWebhook_Deployment(t *testing.T) {
defer body.Close()
- request, _ := http.NewRequest(http.MethodGet, "/test", body)
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
request.Header.Set("X-GitHub-Host", "github.com")
request.Header.Set("X-GitHub-Version", "2.16.0")
request.Header.Set("X-GitHub-Event", "deployment")
@@ -462,10 +483,11 @@ func TestGithub_ProcessWebhook_Deployment_Commit(t *testing.T) {
defer body.Close()
- request, _ := http.NewRequest(http.MethodGet, "/test", body)
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
request.Header.Set("X-GitHub-Host", "github.com")
request.Header.Set("X-GitHub-Version", "2.16.0")
request.Header.Set("X-GitHub-Event", "deployment")
@@ -477,6 +499,7 @@ func TestGithub_ProcessWebhook_Deployment_Commit(t *testing.T) {
wantHook := new(library.Hook)
wantHook.SetNumber(1)
wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ wantHook.SetWebhookID(123456)
wantHook.SetCreated(time.Now().UTC().Unix())
wantHook.SetBranch("master")
wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks")
@@ -492,6 +515,7 @@ func TestGithub_ProcessWebhook_Deployment_Commit(t *testing.T) {
wantRepo.SetClone("https://github.com/Codertocat/Hello-World.git")
wantRepo.SetBranch("master")
wantRepo.SetPrivate(false)
+ wantRepo.SetTopics(nil)
wantBuild := new(library.Build)
wantBuild.SetEvent("deployment")
@@ -538,10 +562,11 @@ func TestGithub_ProcessWebhook_BadGithubEvent(t *testing.T) {
defer body.Close()
- request, _ := http.NewRequest(http.MethodGet, "/test", body)
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
request.Header.Set("X-GitHub-Host", "github.com")
request.Header.Set("X-GitHub-Version", "2.16.0")
request.Header.Set("X-GitHub-Event", "foobar")
@@ -553,6 +578,7 @@ func TestGithub_ProcessWebhook_BadGithubEvent(t *testing.T) {
wantHook := new(library.Hook)
wantHook.SetNumber(1)
wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ wantHook.SetWebhookID(123456)
wantHook.SetCreated(time.Now().UTC().Unix())
wantHook.SetHost("github.com")
wantHook.SetEvent("foobar")
@@ -589,10 +615,11 @@ func TestGithub_ProcessWebhook_BadContentType(t *testing.T) {
defer body.Close()
- request, _ := http.NewRequest(http.MethodGet, "/test", body)
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
request.Header.Set("Content-Type", "foobar")
request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
request.Header.Set("X-GitHub-Host", "github.com")
request.Header.Set("X-GitHub-Version", "2.16.0")
request.Header.Set("X-GitHub-Event", "pull_request")
@@ -604,6 +631,7 @@ func TestGithub_ProcessWebhook_BadContentType(t *testing.T) {
wantHook := new(library.Hook)
wantHook.SetNumber(1)
wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ wantHook.SetWebhookID(123456)
wantHook.SetCreated(time.Now().UTC().Unix())
wantHook.SetHost("github.com")
wantHook.SetEvent("pull_request")
@@ -640,10 +668,11 @@ func TestGithub_VerifyWebhook_EmptyRepo(t *testing.T) {
defer body.Close()
- request, _ := http.NewRequest(http.MethodGet, "/test", body)
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
request.Header.Set("X-GitHub-Host", "github.com")
request.Header.Set("X-GitHub-Version", "2.16.0")
request.Header.Set("X-GitHub-Event", "deployment")
@@ -680,10 +709,11 @@ func TestGithub_VerifyWebhook_NoSecret(t *testing.T) {
defer body.Close()
- request, _ := http.NewRequest(http.MethodGet, "/test", body)
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
request.Header.Set("X-GitHub-Host", "github.com")
request.Header.Set("X-GitHub-Version", "2.16.0")
request.Header.Set("X-GitHub-Event", "push")
@@ -711,10 +741,11 @@ func TestGithub_ProcessWebhook_IssueComment_PR(t *testing.T) {
defer body.Close()
- request, _ := http.NewRequest(http.MethodGet, "/test", body)
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
request.Header.Set("X-GitHub-Host", "github.com")
request.Header.Set("X-GitHub-Version", "2.16.0")
request.Header.Set("X-GitHub-Event", "issue_comment")
@@ -726,6 +757,7 @@ func TestGithub_ProcessWebhook_IssueComment_PR(t *testing.T) {
wantHook := new(library.Hook)
wantHook.SetNumber(1)
wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ wantHook.SetWebhookID(123456)
wantHook.SetCreated(time.Now().UTC().Unix())
wantHook.SetHost("github.com")
wantHook.SetEvent("comment")
@@ -740,9 +772,11 @@ func TestGithub_ProcessWebhook_IssueComment_PR(t *testing.T) {
wantRepo.SetClone("https://github.com/Codertocat/Hello-World.git")
wantRepo.SetBranch("master")
wantRepo.SetPrivate(false)
+ wantRepo.SetTopics(nil)
wantBuild := new(library.Build)
wantBuild.SetEvent("comment")
+ wantBuild.SetEventAction("created")
wantBuild.SetClone("https://github.com/Codertocat/Hello-World.git")
wantBuild.SetSource("https://github.com/Codertocat/Hello-World/pull/1")
wantBuild.SetTitle("comment received from https://github.com/Codertocat/Hello-World")
@@ -784,10 +818,11 @@ func TestGithub_ProcessWebhook_IssueComment_Created(t *testing.T) {
defer body.Close()
- request, _ := http.NewRequest(http.MethodGet, "/test", body)
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
request.Header.Set("X-GitHub-Host", "github.com")
request.Header.Set("X-GitHub-Version", "2.16.0")
request.Header.Set("X-GitHub-Event", "issue_comment")
@@ -799,6 +834,7 @@ func TestGithub_ProcessWebhook_IssueComment_Created(t *testing.T) {
wantHook := new(library.Hook)
wantHook.SetNumber(1)
wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ wantHook.SetWebhookID(123456)
wantHook.SetCreated(time.Now().UTC().Unix())
wantHook.SetHost("github.com")
wantHook.SetEvent("comment")
@@ -813,9 +849,11 @@ func TestGithub_ProcessWebhook_IssueComment_Created(t *testing.T) {
wantRepo.SetClone("https://github.com/Codertocat/Hello-World.git")
wantRepo.SetBranch("master")
wantRepo.SetPrivate(false)
+ wantRepo.SetTopics(nil)
wantBuild := new(library.Build)
wantBuild.SetEvent("comment")
+ wantBuild.SetEventAction("created")
wantBuild.SetClone("https://github.com/Codertocat/Hello-World.git")
wantBuild.SetSource("https://github.com/Codertocat/Hello-World/issues/1")
wantBuild.SetTitle("comment received from https://github.com/Codertocat/Hello-World")
@@ -857,10 +895,11 @@ func TestGithub_ProcessWebhook_IssueComment_Deleted(t *testing.T) {
defer body.Close()
- request, _ := http.NewRequest(http.MethodGet, "/test", body)
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
request.Header.Set("X-GitHub-Host", "github.com")
request.Header.Set("X-GitHub-Version", "2.16.0")
request.Header.Set("X-GitHub-Event", "issue_comment")
@@ -872,6 +911,7 @@ func TestGithub_ProcessWebhook_IssueComment_Deleted(t *testing.T) {
wantHook := new(library.Hook)
wantHook.SetNumber(1)
wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ wantHook.SetWebhookID(123456)
wantHook.SetCreated(time.Now().UTC().Unix())
wantHook.SetHost("github.com")
wantHook.SetEvent("comment")
@@ -910,10 +950,141 @@ func TestGitHub_ProcessWebhook_RepositoryRename(t *testing.T) {
defer body.Close()
- request, _ := http.NewRequest(http.MethodGet, "/test", body)
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
+ request.Header.Set("Content-Type", "application/json")
+ request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
+ request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
+ request.Header.Set("X-GitHub-Event", "repository")
+
+ // setup client
+ client, _ := NewTest(s.URL)
+
+ // run test
+ wantHook := new(library.Hook)
+ wantHook.SetNumber(1)
+ wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ wantHook.SetWebhookID(123456)
+ wantHook.SetCreated(time.Now().UTC().Unix())
+ wantHook.SetHost("github.com")
+ wantHook.SetEvent(constants.EventRepository)
+ wantHook.SetEventAction(constants.ActionRenamed)
+ wantHook.SetBranch("master")
+ wantHook.SetStatus(constants.StatusSuccess)
+ wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks")
+
+ wantRepo := new(library.Repo)
+ wantRepo.SetActive(true)
+ wantRepo.SetOrg("Codertocat")
+ wantRepo.SetName("Hello-World")
+ wantRepo.SetFullName("Codertocat/Hello-World")
+ wantRepo.SetLink("https://octocoders.github.io/Codertocat/Hello-World")
+ wantRepo.SetClone("https://octocoders.github.io/Codertocat/Hello-World.git")
+ wantRepo.SetBranch("master")
+ wantRepo.SetPrivate(false)
+ wantRepo.SetPreviousName("Codertocat/Hello-Old-World")
+ wantRepo.SetTopics(nil)
+
+ want := &types.Webhook{
+ Comment: "",
+ Hook: wantHook,
+ Repo: wantRepo,
+ }
+
+ got, err := client.ProcessWebhook(request)
+
+ if err != nil {
+ t.Errorf("ProcessWebhook returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("ProcessWebhook is %v, want %v", got, want)
+ }
+}
+
+func TestGitHub_ProcessWebhook_RepositoryTransfer(t *testing.T) {
+ // setup router
+ s := httptest.NewServer(http.NotFoundHandler())
+ defer s.Close()
+
+ // setup request
+ body, err := os.Open("testdata/hooks/repository_transferred.json")
+ if err != nil {
+ t.Errorf("unable to open file: %v", err)
+ }
+
+ defer body.Close()
+
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
+ request.Header.Set("Content-Type", "application/json")
+ request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
+ request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
+ request.Header.Set("X-GitHub-Event", "repository")
+
+ // setup client
+ client, _ := NewTest(s.URL)
+
+ // run test
+ wantHook := new(library.Hook)
+ wantHook.SetNumber(1)
+ wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ wantHook.SetWebhookID(123456)
+ wantHook.SetCreated(time.Now().UTC().Unix())
+ wantHook.SetHost("github.com")
+ wantHook.SetEvent(constants.EventRepository)
+ wantHook.SetEventAction(constants.ActionTransferred)
+ wantHook.SetBranch("master")
+ wantHook.SetStatus(constants.StatusSuccess)
+ wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks")
+
+ wantRepo := new(library.Repo)
+ wantRepo.SetActive(true)
+ wantRepo.SetOrg("Codertocat")
+ wantRepo.SetName("Hello-World")
+ wantRepo.SetFullName("Codertocat/Hello-World")
+ wantRepo.SetLink("https://octocoders.github.io/Codertocat/Hello-World")
+ wantRepo.SetClone("https://octocoders.github.io/Codertocat/Hello-World.git")
+ wantRepo.SetBranch("master")
+ wantRepo.SetPrivate(false)
+ wantRepo.SetPreviousName("Old-Codertocat/Hello-World")
+ wantRepo.SetTopics(nil)
+
+ want := &types.Webhook{
+ Comment: "",
+ Hook: wantHook,
+ Repo: wantRepo,
+ }
+
+ got, err := client.ProcessWebhook(request)
+
+ if err != nil {
+ t.Errorf("ProcessWebhook returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("ProcessWebhook is %v, want %v", got, want)
+ }
+}
+
+func TestGitHub_ProcessWebhook_RepositoryArchived(t *testing.T) {
+ // setup router
+ s := httptest.NewServer(http.NotFoundHandler())
+ defer s.Close()
+
+ // setup request
+ body, err := os.Open("testdata/hooks/repository_archived.json")
+ if err != nil {
+ t.Errorf("unable to open file: %v", err)
+ }
+
+ defer body.Close()
+
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
request.Header.Set("X-GitHub-Event", "repository")
// setup client
@@ -923,14 +1094,17 @@ func TestGitHub_ProcessWebhook_RepositoryRename(t *testing.T) {
wantHook := new(library.Hook)
wantHook.SetNumber(1)
wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ wantHook.SetWebhookID(123456)
wantHook.SetCreated(time.Now().UTC().Unix())
wantHook.SetHost("github.com")
- wantHook.SetEvent("repositoryRename")
+ wantHook.SetEvent(constants.EventRepository)
+ wantHook.SetEventAction("archived")
wantHook.SetBranch("master")
wantHook.SetStatus(constants.StatusSuccess)
wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks")
wantRepo := new(library.Repo)
+ wantRepo.SetActive(false)
wantRepo.SetOrg("Codertocat")
wantRepo.SetName("Hello-World")
wantRepo.SetFullName("Codertocat/Hello-World")
@@ -938,7 +1112,71 @@ func TestGitHub_ProcessWebhook_RepositoryRename(t *testing.T) {
wantRepo.SetClone("https://octocoders.github.io/Codertocat/Hello-World.git")
wantRepo.SetBranch("master")
wantRepo.SetPrivate(false)
- wantRepo.SetPreviousName("Hello-Old-World")
+ wantRepo.SetTopics(nil)
+
+ want := &types.Webhook{
+ Comment: "",
+ Hook: wantHook,
+ Repo: wantRepo,
+ }
+
+ got, err := client.ProcessWebhook(request)
+
+ if err != nil {
+ t.Errorf("ProcessWebhook returned err: %v", err)
+ }
+
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("ProcessWebhook is %v, want %v", got, want)
+ }
+}
+
+func TestGitHub_ProcessWebhook_RepositoryEdited(t *testing.T) {
+ // setup router
+ s := httptest.NewServer(http.NotFoundHandler())
+ defer s.Close()
+
+ // setup request
+ body, err := os.Open("testdata/hooks/repository_edited.json")
+ if err != nil {
+ t.Errorf("unable to open file: %v", err)
+ }
+
+ defer body.Close()
+
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
+ request.Header.Set("Content-Type", "application/json")
+ request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
+ request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
+ request.Header.Set("X-GitHub-Event", "repository")
+
+ // setup client
+ client, _ := NewTest(s.URL)
+
+ // run test
+ wantHook := new(library.Hook)
+ wantHook.SetNumber(1)
+ wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ wantHook.SetWebhookID(123456)
+ wantHook.SetCreated(time.Now().UTC().Unix())
+ wantHook.SetHost("github.com")
+ wantHook.SetEvent(constants.EventRepository)
+ wantHook.SetEventAction(constants.ActionEdited)
+ wantHook.SetBranch("main")
+ wantHook.SetStatus(constants.StatusSuccess)
+ wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks")
+
+ wantRepo := new(library.Repo)
+ wantRepo.SetActive(true)
+ wantRepo.SetOrg("Codertocat")
+ wantRepo.SetName("Hello-World")
+ wantRepo.SetFullName("Codertocat/Hello-World")
+ wantRepo.SetLink("https://octocoders.github.io/Codertocat/Hello-World")
+ wantRepo.SetClone("https://octocoders.github.io/Codertocat/Hello-World.git")
+ wantRepo.SetBranch("main")
+ wantRepo.SetTopics([]string{"cloud", "security"})
+ wantRepo.SetPrivate(false)
want := &types.Webhook{
Comment: "",
@@ -970,10 +1208,11 @@ func TestGitHub_ProcessWebhook_Repository(t *testing.T) {
defer body.Close()
- request, _ := http.NewRequest(http.MethodGet, "/test", body)
+ request, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/test", body)
request.Header.Set("Content-Type", "application/json")
request.Header.Set("User-Agent", "GitHub-Hookshot/a22606a")
request.Header.Set("X-GitHub-Delivery", "7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ request.Header.Set("X-GitHub-Hook-ID", "123456")
request.Header.Set("X-GitHub-Event", "repository")
// setup client
@@ -983,14 +1222,17 @@ func TestGitHub_ProcessWebhook_Repository(t *testing.T) {
wantHook := new(library.Hook)
wantHook.SetNumber(1)
wantHook.SetSourceID("7bd477e4-4415-11e9-9359-0d41fdf9567e")
+ wantHook.SetWebhookID(123456)
wantHook.SetCreated(time.Now().UTC().Unix())
wantHook.SetHost("github.com")
- wantHook.SetEvent("repository")
+ wantHook.SetEvent(constants.EventRepository)
+ wantHook.SetEventAction("publicized")
wantHook.SetBranch("master")
wantHook.SetStatus(constants.StatusSuccess)
wantHook.SetLink("https://github.com/Codertocat/Hello-World/settings/hooks")
wantRepo := new(library.Repo)
+ wantRepo.SetActive(true)
wantRepo.SetOrg("Codertocat")
wantRepo.SetName("Hello-World")
wantRepo.SetFullName("Codertocat/Hello-World")
@@ -998,6 +1240,7 @@ func TestGitHub_ProcessWebhook_Repository(t *testing.T) {
wantRepo.SetClone("https://octocoders.github.io/Codertocat/Hello-World.git")
wantRepo.SetBranch("master")
wantRepo.SetPrivate(false)
+ wantRepo.SetTopics(nil)
want := &types.Webhook{
Comment: "",
@@ -1015,3 +1258,105 @@ func TestGitHub_ProcessWebhook_Repository(t *testing.T) {
t.Errorf("ProcessWebhook is %v, want %v", got, want)
}
}
+
+func TestGithub_Redeliver_Webhook(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ _, engine := gin.CreateTestContext(resp)
+
+ // setup mock server
+ engine.POST("/api/v3/repos/:org/:repo/hooks/:repo_id/deliveries/:delivery_id/attempts", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/hooks/push.json")
+ })
+ engine.GET("/api/v3/repos/:org/:repo/hooks/:hook_id/deliveries", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/delivery_summaries.json")
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup types
+ u := new(library.User)
+ u.SetName("octocat")
+ u.SetToken("foo")
+
+ _hook := new(library.Hook)
+ _hook.SetSourceID("b595f0e0-aee1-11ec-86cf-9418381395c4")
+ _hook.SetID(1)
+ _hook.SetRepoID(1)
+ _hook.SetBuildID(1)
+ _hook.SetNumber(1)
+ _hook.SetWebhookID(1234)
+
+ _repo := new(library.Repo)
+ _repo.SetID(1)
+ _repo.SetName("bar")
+ _repo.SetOrg("foo")
+
+ client, _ := NewTest(s.URL, "https://foo.bar.com")
+
+ // run test
+ err := client.RedeliverWebhook(ctx, u, _repo, _hook)
+
+ if err != nil {
+ t.Errorf("RedeliverWebhook returned err: %v", err)
+ }
+}
+
+func TestGithub_GetDeliveryID(t *testing.T) {
+ // setup context
+ gin.SetMode(gin.TestMode)
+
+ resp := httptest.NewRecorder()
+ _, engine := gin.CreateTestContext(resp)
+
+ engine.GET("/api/v3/repos/:org/:repo/hooks/:hook_id/deliveries", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/delivery_summaries.json")
+ })
+
+ s := httptest.NewServer(engine)
+ defer s.Close()
+
+ // setup types
+ u := new(library.User)
+ u.SetName("octocat")
+ u.SetToken("foo")
+
+ _hook := new(library.Hook)
+ _hook.SetSourceID("b595f0e0-aee1-11ec-86cf-9418381395c4")
+ _hook.SetID(1)
+ _hook.SetRepoID(1)
+ _hook.SetBuildID(1)
+ _hook.SetNumber(1)
+ _hook.SetWebhookID(1234)
+
+ _repo := new(library.Repo)
+ _repo.SetID(1)
+ _repo.SetName("bar")
+ _repo.SetOrg("foo")
+
+ want := int64(22948188373)
+
+ client, _ := NewTest(s.URL, "https://foo.bar.com")
+
+ ghClient := client.newClientToken(*u.Token)
+
+ // run test
+ got, err := client.getDeliveryID(ctx, ghClient, _repo, _hook)
+
+ if err != nil {
+ t.Errorf("RedeliverWebhook returned err: %v", err)
+ }
+
+ if got != want {
+ t.Errorf("getDeliveryID returned: %v; want: %v", got, want)
+ }
+}
diff --git a/scm/scm.go b/scm/scm.go
index faf2881a9..2bd5c8170 100644
--- a/scm/scm.go
+++ b/scm/scm.go
@@ -12,14 +12,13 @@ import (
"github.com/sirupsen/logrus"
)
-// nolint: godot // top level comment ends in a list
-//
// New creates and returns a Vela service capable of
// integrating with the configured scm provider.
//
// Currently the following scm providers are supported:
//
// * Github
+// .
func New(s *Setup) (Service, error) {
// validate the setup being provided
//
diff --git a/scm/service.go b/scm/service.go
index efcf6d8fb..4de42362f 100644
--- a/scm/service.go
+++ b/scm/service.go
@@ -5,6 +5,7 @@
package scm
import (
+ "context"
"net/http"
"github.com/go-vela/types"
@@ -97,19 +98,31 @@ type Service interface {
Disable(*library.User, string, string) error
// Enable defines a function that activates
// a repo by creating the webhook.
- Enable(*library.User, string, string, string) (string, error)
+ Enable(*library.User, *library.Repo, *library.Hook) (*library.Hook, string, error)
+ // Update defines a function that updates
+ // a webhook for a specified repo.
+ Update(*library.User, *library.Repo, int64) error
// Status defines a function that sends the
// commit status for the given SHA from a repo.
Status(*library.User, *library.Build, string, string) error
// ListUserRepos defines a function that retrieves
// all repos with admin rights for the user.
ListUserRepos(*library.User) ([]*library.Repo, error)
+ // GetBranch defines a function that retrieves
+ // a branch for a repo.
+ GetBranch(*library.User, *library.Repo, string) (string, string, error)
// GetPullRequest defines a function that retrieves
// a pull request for a repo.
GetPullRequest(*library.User, *library.Repo, int) (string, string, string, string, error)
// GetRepo defines a function that retrieves
// details for a repo.
GetRepo(*library.User, *library.Repo) (*library.Repo, error)
+ // GetOrgAndRepoName defines a function that retrieves
+ // the name of the org and repo in the SCM.
+ GetOrgAndRepoName(*library.User, string, string) (string, string, error)
+ // GetOrg defines a function that retrieves
+ // the name for an org in the SCM.
+ GetOrgName(*library.User, string) (string, error)
// GetHTMLURL defines a function that retrieves
// a repository file's html_url.
GetHTMLURL(*library.User, string, string, string, string) (string, error)
@@ -122,6 +135,9 @@ type Service interface {
// VerifyWebhook defines a function that
// verifies the webhook from a repo.
VerifyWebhook(*http.Request, *library.Repo) error
+ // RedeliverWebhook defines a function that
+ // redelivers the webhook from the SCM.
+ RedeliverWebhook(context.Context, *library.User, *library.Repo, *library.Hook) error
// TODO: Add convert functions to interface?
}
diff --git a/secret/context_test.go b/secret/context_test.go
index 803980664..87f6afe06 100644
--- a/secret/context_test.go
+++ b/secret/context_test.go
@@ -7,19 +7,21 @@ package secret
import (
"testing"
- "github.com/go-vela/server/database/sqlite"
- "github.com/go-vela/server/secret/native"
-
"github.com/gin-gonic/gin"
+ "github.com/go-vela/server/database"
+ "github.com/go-vela/server/secret/native"
)
func TestSecret_FromContext(t *testing.T) {
// setup types
- d, _ := sqlite.NewTest()
- defer func() { _sql, _ := d.Sqlite.DB(); _sql.Close() }()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
want, err := native.New(
- native.WithDatabase(d),
+ native.WithDatabase(db),
)
if err != nil {
t.Errorf("New returned err: %v", err)
@@ -81,11 +83,14 @@ func TestSecret_FromContext_Empty(t *testing.T) {
func TestSecret_ToContext(t *testing.T) {
// setup types
- d, _ := sqlite.NewTest()
- defer func() { _sql, _ := d.Sqlite.DB(); _sql.Close() }()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
want, err := native.New(
- native.WithDatabase(d),
+ native.WithDatabase(db),
)
if err != nil {
t.Errorf("New returned err: %v", err)
diff --git a/secret/doc.go b/secret/doc.go
index 555864d4e..b15a4b102 100644
--- a/secret/doc.go
+++ b/secret/doc.go
@@ -7,5 +7,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/secret"
+// import "github.com/go-vela/server/secret"
package secret
diff --git a/secret/native/count.go b/secret/native/count.go
index d534cb268..2460d998a 100644
--- a/secret/native/count.go
+++ b/secret/native/count.go
@@ -5,38 +5,62 @@
package native
import (
- "strings"
+ "fmt"
"github.com/go-vela/types/constants"
+ "github.com/go-vela/types/library"
"github.com/sirupsen/logrus"
)
// Count counts a list of secrets.
func (c *client) Count(sType, org, name string, teams []string) (int64, error) {
- // create log fields from secret metadata
- fields := logrus.Fields{
- "org": org,
- "repo": name,
- "type": sType,
- }
+ // handle the secret based off the type
+ switch sType {
+ case constants.SecretOrg:
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "type": sType,
+ }).Tracef("counting native %s secrets for %s", sType, org)
- // check if secret is a shared secret
- if strings.EqualFold(sType, constants.SecretShared) {
- // update log fields from secret metadata
- fields = logrus.Fields{
+ // capture the count of org secrets from the native service
+ return c.Database.CountSecretsForOrg(org, nil)
+ case constants.SecretRepo:
+ c.Logger.WithFields(logrus.Fields{
"org": org,
- "team": name,
+ "repo": name,
"type": sType,
+ }).Tracef("counting native %s secrets for %s/%s", sType, org, name)
+
+ // create the repo with the information available
+ r := new(library.Repo)
+ r.SetOrg(org)
+ r.SetName(name)
+ r.SetFullName(fmt.Sprintf("%s/%s", org, name))
+
+ // capture the count of repo secrets from the native service
+ return c.Database.CountSecretsForRepo(r, nil)
+ case constants.SecretShared:
+ // check if we should capture secrets for multiple teams
+ if name == "*" {
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "teams": teams,
+ "type": sType,
+ }).Tracef("counting native %s secrets for teams %s in org %s", sType, teams, org)
+
+ // capture the count of shared secrets for multiple teams from the native service
+ return c.Database.CountSecretsForTeams(org, teams, nil)
}
- }
- c.Logger.WithFields(fields).Tracef("counting native %s secrets for %s/%s", sType, org, name)
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "team": name,
+ "type": sType,
+ }).Tracef("counting native %s secrets for %s/%s", sType, org, name)
- // capture the count of secrets from the native service
- s, err := c.Database.GetTypeSecretCount(sType, org, name, teams)
- if err != nil {
- return 0, err
+ // capture the count of shared secrets from the native service
+ return c.Database.CountSecretsForTeam(org, name, nil)
+ default:
+ return 0, fmt.Errorf("invalid secret type: %s", sType)
}
-
- return s, nil
}
diff --git a/secret/native/count_test.go b/secret/native/count_test.go
index d9e7e94ab..42c71cc9b 100644
--- a/secret/native/count_test.go
+++ b/secret/native/count_test.go
@@ -7,7 +7,7 @@ package native
import (
"testing"
- "github.com/go-vela/server/database/sqlite"
+ "github.com/go-vela/server/database"
"github.com/go-vela/types/library"
)
@@ -22,19 +22,23 @@ func TestNative_Count(t *testing.T) {
sec.SetType("repo")
sec.SetImages([]string{"foo", "bar"})
sec.SetEvents([]string{"foo", "bar"})
+ sec.SetCreatedAt(1)
+ sec.SetUpdatedAt(1)
want := 1
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from secrets;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteSecret(sec)
+ db.Close()
}()
- _ = db.CreateSecret(sec)
+ _, _ = db.CreateSecret(sec)
// run test
s, err := New(
@@ -54,11 +58,13 @@ func TestNative_Count(t *testing.T) {
}
}
-func TestNative_Count_Invalid(t *testing.T) {
+func TestNative_Count_Empty(t *testing.T) {
// setup database
- db, _ := sqlite.NewTest()
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
// run test
s, err := New(
@@ -69,8 +75,8 @@ func TestNative_Count_Invalid(t *testing.T) {
}
got, err := s.Count("repo", "foo", "bar", []string{})
- if err == nil {
- t.Errorf("Count should have returned err")
+ if err != nil {
+ t.Errorf("Count returned err: %v", err)
}
if got != 0 {
diff --git a/secret/native/create.go b/secret/native/create.go
index b4bad22b1..b93869602 100644
--- a/secret/native/create.go
+++ b/secret/native/create.go
@@ -6,47 +6,46 @@ package native
import (
"fmt"
- "strings"
-
- "github.com/sirupsen/logrus"
"github.com/go-vela/types/constants"
"github.com/go-vela/types/library"
+ "github.com/sirupsen/logrus"
)
// Create creates a new secret.
-func (c *client) Create(sType, org, name string, s *library.Secret) error {
- // create log fields from secret metadata
- fields := logrus.Fields{
- "org": org,
- "repo": name,
- "secret": s.GetName(),
- "type": sType,
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(sType, constants.SecretShared) {
- // update log fields from secret metadata
- fields = logrus.Fields{
+func (c *client) Create(sType, org, name string, s *library.Secret) (*library.Secret, error) {
+ // handle the secret based off the type
+ switch sType {
+ case constants.SecretOrg:
+ c.Logger.WithFields(logrus.Fields{
"org": org,
- "team": name,
"secret": s.GetName(),
"type": sType,
- }
- }
+ }).Tracef("creating native %s secret %s for %s", sType, s.GetName(), org)
- // nolint: lll // ignore long line length due to parameters
- c.Logger.WithFields(fields).Tracef("creating native %s secret %s for %s/%s", sType, s.GetName(), org, name)
-
- // create the secret for the native service
- switch sType {
- case constants.SecretOrg:
- fallthrough
+ // create the org secret in the native service
+ return c.Database.CreateSecret(s)
case constants.SecretRepo:
- fallthrough
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "repo": name,
+ "secret": s.GetName(),
+ "type": sType,
+ }).Tracef("creating native %s secret %s for %s/%s", sType, s.GetName(), org, name)
+
+ // create the repo secret in the native service
+ return c.Database.CreateSecret(s)
case constants.SecretShared:
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "secret": s.GetName(),
+ "team": name,
+ "type": sType,
+ }).Tracef("creating native %s secret %s for %s/%s", sType, s.GetName(), org, name)
+
+ // create the shared secret in the native service
return c.Database.CreateSecret(s)
default:
- return fmt.Errorf("invalid secret type: %v", sType)
+ return nil, fmt.Errorf("invalid secret type: %s", sType)
}
}
diff --git a/secret/native/create_test.go b/secret/native/create_test.go
index 08fd4b18b..93c6898a4 100644
--- a/secret/native/create_test.go
+++ b/secret/native/create_test.go
@@ -8,7 +8,7 @@ import (
"reflect"
"testing"
- "github.com/go-vela/server/database/sqlite"
+ "github.com/go-vela/server/database"
"github.com/go-vela/types/library"
)
@@ -31,12 +31,14 @@ func TestNative_Create_Org(t *testing.T) {
want.SetUpdatedBy("user2")
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from secrets;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteSecret(want)
+ db.Close()
}()
// run test
@@ -47,13 +49,11 @@ func TestNative_Create_Org(t *testing.T) {
t.Errorf("New returned err: %v", err)
}
- err = s.Create("org", "foo", "*", want)
+ got, err := s.Create("org", "foo", "*", want)
if err != nil {
t.Errorf("Create returned err: %v", err)
}
- got, _ := s.Get("org", "foo", "*", "bar")
-
if !reflect.DeepEqual(got, want) {
t.Errorf("Create is %v, want %v", got, want)
}
@@ -78,12 +78,14 @@ func TestNative_Create_Repo(t *testing.T) {
want.SetUpdatedBy("user2")
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from secrets;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteSecret(want)
+ db.Close()
}()
// run test
@@ -94,13 +96,11 @@ func TestNative_Create_Repo(t *testing.T) {
t.Errorf("New returned err: %v", err)
}
- err = s.Create("repo", "foo", "bar", want)
+ got, err := s.Create("repo", "foo", "bar", want)
if err != nil {
t.Errorf("Create returned err: %v", err)
}
- got, _ := s.Get("repo", "foo", "bar", "baz")
-
if !reflect.DeepEqual(got, want) {
t.Errorf("Create is %v, want %v", got, want)
}
@@ -125,12 +125,14 @@ func TestNative_Create_Shared(t *testing.T) {
want.SetUpdatedBy("user2")
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from secrets;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteSecret(want)
+ db.Close()
}()
// run test
@@ -141,13 +143,11 @@ func TestNative_Create_Shared(t *testing.T) {
t.Errorf("New returned err: %v", err)
}
- err = s.Create("shared", "foo", "bar", want)
+ got, err := s.Create("shared", "foo", "bar", want)
if err != nil {
t.Errorf("Create returned err: %v", err)
}
- got, _ := s.Get("shared", "foo", "bar", "baz")
-
if !reflect.DeepEqual(got, want) {
t.Errorf("Create is %v, want %v", got, want)
}
@@ -172,12 +172,14 @@ func TestNative_Create_Invalid(t *testing.T) {
sec.SetUpdatedBy("user2")
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from secrets;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteSecret(sec)
+ db.Close()
}()
// run test
@@ -188,7 +190,7 @@ func TestNative_Create_Invalid(t *testing.T) {
t.Errorf("New returned err: %v", err)
}
- err = s.Create("invalid", "foo", "bar", sec)
+ _, err = s.Create("invalid", "foo", "bar", sec)
if err == nil {
t.Errorf("Create should have returned err")
}
diff --git a/secret/native/delete.go b/secret/native/delete.go
index 76cf3ef15..c414296ca 100644
--- a/secret/native/delete.go
+++ b/secret/native/delete.go
@@ -5,7 +5,7 @@
package native
import (
- "strings"
+ "fmt"
"github.com/go-vela/types/constants"
"github.com/sirupsen/logrus"
@@ -13,34 +13,44 @@ import (
// Delete deletes a secret.
func (c *client) Delete(sType, org, name, path string) error {
- // create log fields from secret metadata
- fields := logrus.Fields{
- "org": org,
- "repo": name,
- "secret": path,
- "type": sType,
+ // capture the secret from the native service
+ s, err := c.Get(sType, org, name, path)
+ if err != nil {
+ return err
}
- // check if secret is a shared secret
- if strings.EqualFold(sType, constants.SecretShared) {
- // update log fields from secret metadata
- fields = logrus.Fields{
+ // handle the secret based off the type
+ switch sType {
+ case constants.SecretOrg:
+ c.Logger.WithFields(logrus.Fields{
"org": org,
- "team": name,
"secret": path,
"type": sType,
- }
- }
+ }).Tracef("deleting native %s secret %s for %s", sType, path, org)
- // nolint: lll // ignore long line length due to parameters
- c.Logger.WithFields(fields).Tracef("deleting native %s secret %s for %s/%s", sType, path, org, name)
+ // delete the org secret from the native service
+ return c.Database.DeleteSecret(s)
+ case constants.SecretRepo:
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "repo": name,
+ "secret": path,
+ "type": sType,
+ }).Tracef("deleting native %s secret %s for %s/%s", sType, path, org, name)
- // capture the secret from the native service
- s, err := c.Database.GetSecret(sType, org, name, path)
- if err != nil {
- return err
- }
+ // delete the repo secret from the native service
+ return c.Database.DeleteSecret(s)
+ case constants.SecretShared:
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "secret": path,
+ "team": name,
+ "type": sType,
+ }).Tracef("deleting native %s secret %s for %s/%s", sType, path, org, name)
- // delete the secret from the native service
- return c.Database.DeleteSecret(s.GetID())
+ // delete the shared secret from the native service
+ return c.Database.DeleteSecret(s)
+ default:
+ return fmt.Errorf("invalid secret type: %s", sType)
+ }
}
diff --git a/secret/native/delete_test.go b/secret/native/delete_test.go
index 34f9e4229..e2e21a5f7 100644
--- a/secret/native/delete_test.go
+++ b/secret/native/delete_test.go
@@ -7,7 +7,7 @@ package native
import (
"testing"
- "github.com/go-vela/server/database/sqlite"
+ "github.com/go-vela/server/database"
"github.com/go-vela/types/library"
)
@@ -24,17 +24,21 @@ func TestNative_Delete(t *testing.T) {
sec.SetImages([]string{"foo", "bar"})
sec.SetEvents([]string{"foo", "bar"})
sec.SetAllowCommand(false)
+ sec.SetCreatedAt(1)
+ sec.SetUpdatedAt(1)
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from secrets;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteSecret(sec)
+ db.Close()
}()
- _ = db.CreateSecret(sec)
+ _, _ = db.CreateSecret(sec)
// run test
s, err := New(
@@ -52,8 +56,11 @@ func TestNative_Delete(t *testing.T) {
func TestNative_Delete_Invalid(t *testing.T) {
// setup database
- db, _ := sqlite.NewTest()
- defer func() { _sql, _ := db.Sqlite.DB(); _sql.Close() }()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
// run test
s, err := New(
diff --git a/secret/native/doc.go b/secret/native/doc.go
index ddf75d3de..7cd54caec 100644
--- a/secret/native/doc.go
+++ b/secret/native/doc.go
@@ -7,5 +7,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/secret/native"
+// import "github.com/go-vela/server/secret/native"
package native
diff --git a/secret/native/driver_test.go b/secret/native/driver_test.go
index 89e152f27..ec0038df5 100644
--- a/secret/native/driver_test.go
+++ b/secret/native/driver_test.go
@@ -8,17 +8,17 @@ import (
"reflect"
"testing"
- "github.com/go-vela/server/database/sqlite"
+ "github.com/go-vela/server/database"
"github.com/go-vela/types/constants"
)
func TestNative_Driver(t *testing.T) {
// setup types
- db, err := sqlite.NewTest()
+ db, err := database.NewTest()
if err != nil {
t.Errorf("unable to create database service: %v", err)
}
- defer func() { _sql, _ := db.Sqlite.DB(); _sql.Close() }()
+ defer db.Close()
want := constants.DriverNative
diff --git a/secret/native/get.go b/secret/native/get.go
index 087679a0d..9ed322355 100644
--- a/secret/native/get.go
+++ b/secret/native/get.go
@@ -5,7 +5,7 @@
package native
import (
- "strings"
+ "fmt"
"github.com/go-vela/types/constants"
"github.com/go-vela/types/library"
@@ -14,32 +14,44 @@ import (
// Get captures a secret.
func (c *client) Get(sType, org, name, path string) (*library.Secret, error) {
- // create log fields from secret metadata
- fields := logrus.Fields{
- "org": org,
- "repo": name,
- "secret": path,
- "type": sType,
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(sType, constants.SecretShared) {
- // update log fields from secret metadata
- fields = logrus.Fields{
+ // handle the secret based off the type
+ switch sType {
+ case constants.SecretOrg:
+ c.Logger.WithFields(logrus.Fields{
"org": org,
- "team": name,
"secret": path,
"type": sType,
- }
- }
+ }).Tracef("getting native %s secret %s for %s", sType, path, org)
- c.Logger.WithFields(fields).Tracef("getting native %s secret %s for %s/%s", sType, path, org, name)
+ // capture the org secret from the native service
+ return c.Database.GetSecretForOrg(org, path)
+ case constants.SecretRepo:
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "repo": name,
+ "secret": path,
+ "type": sType,
+ }).Tracef("getting native %s secret %s for %s/%s", sType, path, org, name)
+
+ // create the repo with the information available
+ r := new(library.Repo)
+ r.SetOrg(org)
+ r.SetName(name)
+ r.SetFullName(fmt.Sprintf("%s/%s", org, name))
+
+ // capture the repo secret from the native service
+ return c.Database.GetSecretForRepo(path, r)
+ case constants.SecretShared:
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "secret": path,
+ "team": name,
+ "type": sType,
+ }).Tracef("getting native %s secret %s for %s/%s", sType, path, org, name)
- // capture the secret from the native service
- s, err := c.Database.GetSecret(sType, org, name, path)
- if err != nil {
- return nil, err
+ // capture the shared secret from the native service
+ return c.Database.GetSecretForTeam(org, name, path)
+ default:
+ return nil, fmt.Errorf("invalid secret type: %s", sType)
}
-
- return s, nil
}
diff --git a/secret/native/get_test.go b/secret/native/get_test.go
index 44db6acaf..218236dc6 100644
--- a/secret/native/get_test.go
+++ b/secret/native/get_test.go
@@ -8,7 +8,7 @@ import (
"reflect"
"testing"
- "github.com/go-vela/server/database/sqlite"
+ "github.com/go-vela/server/database"
"github.com/go-vela/types/library"
)
@@ -31,12 +31,15 @@ func TestNative_Get(t *testing.T) {
want.SetUpdatedBy("user2")
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
defer func() {
- db.Sqlite.Exec("delete from secrets;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteSecret(want)
+ db.Close()
}()
// run test
@@ -47,7 +50,7 @@ func TestNative_Get(t *testing.T) {
t.Errorf("New returned err: %v", err)
}
- _ = s.Create("repo", "foo", "bar", want)
+ _, _ = s.Create("repo", "foo", "bar", want)
got, err := s.Get("repo", "foo", "bar", "baz")
if err != nil {
@@ -61,8 +64,11 @@ func TestNative_Get(t *testing.T) {
func TestNative_Get_Invalid(t *testing.T) {
// setup database
- db, _ := sqlite.NewTest()
- defer func() { _sql, _ := db.Sqlite.DB(); _sql.Close() }()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
// run test
s, err := New(
diff --git a/secret/native/list.go b/secret/native/list.go
index a2dc14a6e..0bd9ab303 100644
--- a/secret/native/list.go
+++ b/secret/native/list.go
@@ -5,7 +5,7 @@
package native
import (
- "strings"
+ "fmt"
"github.com/go-vela/types/constants"
"github.com/go-vela/types/library"
@@ -13,33 +13,74 @@ import (
)
// List captures a list of secrets.
-//
-// nolint: lll // ignore long line length
func (c *client) List(sType, org, name string, page, perPage int, teams []string) ([]*library.Secret, error) {
- // create log fields from secret metadata
- fields := logrus.Fields{
- "org": org,
- "repo": name,
- "type": sType,
- }
+ // handle the secret based off the type
+ switch sType {
+ case constants.SecretOrg:
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "type": sType,
+ }).Tracef("listing native %s secrets for %s", sType, org)
+
+ // capture the list of org secrets from the native service
+ secrets, _, err := c.Database.ListSecretsForOrg(org, nil, page, perPage)
+ if err != nil {
+ return nil, err
+ }
- // check if secret is a shared secret
- if strings.EqualFold(sType, constants.SecretShared) {
- // update log fields from secret metadata
- fields = logrus.Fields{
+ return secrets, nil
+ case constants.SecretRepo:
+ c.Logger.WithFields(logrus.Fields{
"org": org,
- "team": name,
+ "repo": name,
"type": sType,
+ }).Tracef("listing native %s secrets for %s/%s", sType, org, name)
+
+ // create the repo with the information available
+ r := new(library.Repo)
+ r.SetOrg(org)
+ r.SetName(name)
+ r.SetFullName(fmt.Sprintf("%s/%s", org, name))
+
+ // capture the list of repo secrets from the native service
+ secrets, _, err := c.Database.ListSecretsForRepo(r, nil, page, perPage)
+ if err != nil {
+ return nil, err
}
- }
- c.Logger.WithFields(fields).Tracef("listing native %s secrets for %s/%s", sType, org, name)
+ return secrets, nil
+ case constants.SecretShared:
+ // check if we should capture secrets for multiple teams
+ if name == "*" {
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "teams": teams,
+ "type": sType,
+ }).Tracef("listing native %s secrets for teams %s in org %s", sType, teams, org)
- // capture the list of secrets from the native service
- s, err := c.Database.GetTypeSecretList(sType, org, name, page, perPage, teams)
- if err != nil {
- return nil, err
- }
+ // capture the list of shared secrets for multiple teams from the native service
+ secrets, _, err := c.Database.ListSecretsForTeams(org, teams, nil, page, perPage)
+ if err != nil {
+ return nil, err
+ }
+
+ return secrets, nil
+ }
+
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "team": name,
+ "type": sType,
+ }).Tracef("listing native %s secrets for %s/%s", sType, org, name)
- return s, nil
+ // capture the list of shared secrets from the native service
+ secrets, _, err := c.Database.ListSecretsForTeam(org, name, nil, page, perPage)
+ if err != nil {
+ return nil, err
+ }
+
+ return secrets, nil
+ default:
+ return nil, fmt.Errorf("invalid secret type: %s", sType)
+ }
}
diff --git a/secret/native/list_test.go b/secret/native/list_test.go
index ac9fd1c67..2cec7b78e 100644
--- a/secret/native/list_test.go
+++ b/secret/native/list_test.go
@@ -8,7 +8,7 @@ import (
"reflect"
"testing"
- "github.com/go-vela/server/database/sqlite"
+ "github.com/go-vela/server/database"
"github.com/go-vela/types/library"
)
@@ -49,12 +49,15 @@ func TestNative_List(t *testing.T) {
want := []*library.Secret{sTwo, sOne}
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from secrets;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteSecret(sOne)
+ db.DeleteSecret(sTwo)
+ db.Close()
}()
// run test
@@ -65,9 +68,9 @@ func TestNative_List(t *testing.T) {
t.Errorf("New returned err: %v", err)
}
- _ = s.Create("repo", "foo", "bar", sOne)
+ _, _ = s.Create("repo", "foo", "bar", sOne)
- _ = s.Create("repo", "foo", "bar", sTwo)
+ _, _ = s.Create("repo", "foo", "bar", sTwo)
got, err := s.List("repo", "foo", "bar", 1, 10, []string{})
if err != nil {
@@ -79,11 +82,13 @@ func TestNative_List(t *testing.T) {
}
}
-func TestNative_List_Invalid(t *testing.T) {
+func TestNative_List_Empty(t *testing.T) {
// setup database
- db, _ := sqlite.NewTest()
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
// run test
s, err := New(
@@ -94,11 +99,11 @@ func TestNative_List_Invalid(t *testing.T) {
}
got, err := s.List("repo", "foo", "bar", 1, 10, []string{})
- if err == nil {
- t.Errorf("List should have returned err")
+ if err != nil {
+ t.Errorf("List returned err: %v", err)
}
- if got != nil {
- t.Errorf("List is %v, want nil", got)
+ if len(got) > 0 {
+ t.Errorf("List is %v, want []", got)
}
}
diff --git a/secret/native/native.go b/secret/native/native.go
index fbc53cb5b..99b015715 100644
--- a/secret/native/native.go
+++ b/secret/native/native.go
@@ -12,20 +12,20 @@ import (
// client represents a struct to hold native secret setup.
type client struct {
// client to interact with database for secret operations
- Database database.Service
+ Database database.Interface
// https://pkg.go.dev/github.com/sirupsen/logrus#Entry
Logger *logrus.Entry
}
// New returns a Secret implementation that integrates with a Native secrets engine.
//
-// nolint: revive // ignore returning unexported client
+//nolint:revive // ignore returning unexported client
func New(opts ...ClientOpt) (*client, error) {
// create new native client
c := new(client)
// create new fields
- c.Database = *new(database.Service)
+ c.Database = *new(database.Interface)
// create new logger for the client
//
diff --git a/secret/native/native_test.go b/secret/native/native_test.go
index bc1ec1aa1..0a5e3ee49 100644
--- a/secret/native/native_test.go
+++ b/secret/native/native_test.go
@@ -8,22 +8,21 @@ import (
"testing"
"github.com/go-vela/server/database"
- "github.com/go-vela/server/database/sqlite"
)
func TestNative_New(t *testing.T) {
// setup types
- db, err := sqlite.NewTest()
+ db, err := database.NewTest()
if err != nil {
- t.Errorf("unable to create database service: %v", err)
+ t.Errorf("unable to create test database engine: %v", err)
}
- defer func() { _sql, _ := db.Sqlite.DB(); _sql.Close() }()
+ defer db.Close()
// setup tests
tests := []struct {
failure bool
- database database.Service
- want database.Service
+ database database.Interface
+ want database.Interface
}{
{
failure: false,
diff --git a/secret/native/opts.go b/secret/native/opts.go
index 160b554c6..0c4396348 100644
--- a/secret/native/opts.go
+++ b/secret/native/opts.go
@@ -14,7 +14,7 @@ import (
type ClientOpt func(*client) error
// WithDatabase sets the Vela database service in the secret client for Native.
-func WithDatabase(d database.Service) ClientOpt {
+func WithDatabase(d database.Interface) ClientOpt {
return func(c *client) error {
c.Logger.Trace("configuring database service in native secret client")
diff --git a/secret/native/opts_test.go b/secret/native/opts_test.go
index 2199a9f4d..0d9d968df 100644
--- a/secret/native/opts_test.go
+++ b/secret/native/opts_test.go
@@ -9,22 +9,21 @@ import (
"testing"
"github.com/go-vela/server/database"
- "github.com/go-vela/server/database/sqlite"
)
func TestNative_ClientOpt_WithDatabase(t *testing.T) {
// setup types
- db, err := sqlite.NewTest()
+ db, err := database.NewTest()
if err != nil {
- t.Errorf("unable to create database service: %v", err)
+ t.Errorf("unable to create test database engine: %v", err)
}
- defer func() { _sql, _ := db.Sqlite.DB(); _sql.Close() }()
+ defer db.Close()
// setup tests
tests := []struct {
failure bool
- database database.Service
- want database.Service
+ database database.Interface
+ want database.Interface
}{
{
failure: false,
diff --git a/secret/native/update.go b/secret/native/update.go
index 6cefa3f94..045b340c2 100644
--- a/secret/native/update.go
+++ b/secret/native/update.go
@@ -5,7 +5,7 @@
package native
import (
- "strings"
+ "fmt"
"github.com/go-vela/types/constants"
"github.com/go-vela/types/library"
@@ -13,54 +13,71 @@ import (
)
// Update updates an existing secret.
-func (c *client) Update(sType, org, name string, s *library.Secret) error {
- // create log fields from secret metadata
- fields := logrus.Fields{
- "org": org,
- "repo": name,
- "secret": s.GetName(),
- "type": sType,
- }
-
- // check if secret is a shared secret
- if strings.EqualFold(sType, constants.SecretShared) {
- // update log fields from secret metadata
- fields = logrus.Fields{
- "org": org,
- "team": name,
- "secret": s.GetName(),
- "type": sType,
- }
- }
-
- // nolint: lll // ignore long line length due to parameters
- c.Logger.WithFields(fields).Tracef("updating native %s secret %s for %s/%s", sType, s.GetName(), org, name)
-
+func (c *client) Update(sType, org, name string, s *library.Secret) (*library.Secret, error) {
// capture the secret from the native service
- sec, err := c.Database.GetSecret(sType, org, name, s.GetName())
+ secret, err := c.Get(sType, org, name, s.GetName())
if err != nil {
- return err
+ return nil, err
}
// update the events if set
if len(s.GetEvents()) > 0 {
- sec.SetEvents(s.GetEvents())
+ secret.SetEvents(s.GetEvents())
}
// update the images if set
if s.Images != nil {
- sec.SetImages(s.GetImages())
+ secret.SetImages(s.GetImages())
}
// update the value if set
if len(s.GetValue()) > 0 {
- sec.SetValue(s.GetValue())
+ secret.SetValue(s.GetValue())
}
// update allow_command if set
if s.AllowCommand != nil {
- sec.SetAllowCommand(s.GetAllowCommand())
+ secret.SetAllowCommand(s.GetAllowCommand())
}
- return c.Database.UpdateSecret(sec)
+ // update updated_at if set
+ secret.SetUpdatedAt(s.GetUpdatedAt())
+
+ // update updated_by if set
+ secret.SetUpdatedBy(s.GetUpdatedBy())
+
+ // handle the secret based off the type
+ switch sType {
+ case constants.SecretOrg:
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "secret": s.GetName(),
+ "type": sType,
+ }).Tracef("updating native %s secret %s for %s", sType, s.GetName(), org)
+
+ // update the org secret in the native service
+ return c.Database.UpdateSecret(secret)
+ case constants.SecretRepo:
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "repo": name,
+ "secret": s.GetName(),
+ "type": sType,
+ }).Tracef("updating native %s secret %s for %s/%s", sType, s.GetName(), org, name)
+
+ // update the repo secret in the native service
+ return c.Database.UpdateSecret(secret)
+ case constants.SecretShared:
+ c.Logger.WithFields(logrus.Fields{
+ "org": org,
+ "team": name,
+ "secret": s.GetName(),
+ "type": sType,
+ }).Tracef("updating native %s secret %s for %s/%s", sType, s.GetName(), org, name)
+
+ // update the shared secret in the native service
+ return c.Database.UpdateSecret(secret)
+ default:
+ return nil, fmt.Errorf("invalid secret type: %s", sType)
+ }
}
diff --git a/secret/native/update_test.go b/secret/native/update_test.go
index 941df171f..66e637ad3 100644
--- a/secret/native/update_test.go
+++ b/secret/native/update_test.go
@@ -9,12 +9,28 @@ import (
"testing"
"time"
- "github.com/go-vela/server/database/sqlite"
+ "github.com/go-vela/server/database"
"github.com/go-vela/types/library"
)
func TestNative_Update(t *testing.T) {
// setup types
+ original := new(library.Secret)
+ original.SetID(1)
+ original.SetOrg("foo")
+ original.SetRepo("bar")
+ original.SetTeam("")
+ original.SetName("baz")
+ original.SetValue("secretValue")
+ original.SetType("repo")
+ original.SetImages([]string{"foo", "baz"})
+ original.SetEvents([]string{"foob", "bar"})
+ original.SetAllowCommand(true)
+ original.SetCreatedAt(1)
+ original.SetCreatedBy("user")
+ original.SetUpdatedAt(time.Now().UTC().Unix())
+ original.SetUpdatedBy("user")
+
want := new(library.Secret)
want.SetID(1)
want.SetOrg("foo")
@@ -32,15 +48,17 @@ func TestNative_Update(t *testing.T) {
want.SetUpdatedBy("user2")
// setup database
- db, _ := sqlite.NewTest()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
defer func() {
- db.Sqlite.Exec("delete from secrets;")
- _sql, _ := db.Sqlite.DB()
- _sql.Close()
+ db.DeleteSecret(original)
+ db.Close()
}()
- _ = db.CreateSecret(want)
+ _, _ = db.CreateSecret(original)
// run test
s, err := New(
@@ -50,13 +68,11 @@ func TestNative_Update(t *testing.T) {
t.Errorf("New returned err: %v", err)
}
- err = s.Update("repo", "foo", "bar", want)
+ got, err := s.Update("repo", "foo", "bar", want)
if err != nil {
t.Errorf("Update returned err: %v", err)
}
- got, _ := s.Get("repo", "foo", "bar", "baz")
-
if !reflect.DeepEqual(got, want) {
t.Errorf("Update is %v, want %v", got, want)
}
@@ -69,8 +85,11 @@ func TestNative_Update_Invalid(t *testing.T) {
sec.SetValue("foob")
// setup database
- db, _ := sqlite.NewTest()
- defer func() { _sql, _ := db.Sqlite.DB(); _sql.Close() }()
+ db, err := database.NewTest()
+ if err != nil {
+ t.Errorf("unable to create test database engine: %v", err)
+ }
+ defer db.Close()
// run test
s, err := New(
@@ -80,7 +99,7 @@ func TestNative_Update_Invalid(t *testing.T) {
t.Errorf("New returned err: %v", err)
}
- err = s.Update("repo", "foo", "bar", sec)
+ _, err = s.Update("repo", "foo", "bar", sec)
if err == nil {
t.Errorf("Update should have returned err")
}
diff --git a/secret/secret.go b/secret/secret.go
index eb39b07b1..edeec6c93 100644
--- a/secret/secret.go
+++ b/secret/secret.go
@@ -12,8 +12,6 @@ import (
"github.com/sirupsen/logrus"
)
-// nolint: godot // top level comment ends in a list
-//
// New creates and returns a Vela service capable of
// integrating with the configured secret provider.
//
@@ -21,6 +19,7 @@ import (
//
// * Native
// * Vault
+// .
func New(s *Setup) (Service, error) {
// validate the setup being provided
//
diff --git a/secret/secret_test.go b/secret/secret_test.go
index c6f806871..21ab33ee4 100644
--- a/secret/secret_test.go
+++ b/secret/secret_test.go
@@ -7,16 +7,16 @@ package secret
import (
"testing"
- "github.com/go-vela/server/database/sqlite"
+ "github.com/go-vela/server/database"
)
func TestSecret_New(t *testing.T) {
// setup types
- _database, err := sqlite.NewTest()
+ db, err := database.NewTest()
if err != nil {
- t.Errorf("unable to create database service: %v", err)
+ t.Errorf("unable to create test database engine: %v", err)
}
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
+ defer db.Close()
// setup tests
tests := []struct {
@@ -27,7 +27,7 @@ func TestSecret_New(t *testing.T) {
failure: false,
setup: &Setup{
Driver: "native",
- Database: _database,
+ Database: db,
},
},
{
diff --git a/secret/service.go b/secret/service.go
index 855ae1863..aacb9a0d3 100644
--- a/secret/service.go
+++ b/secret/service.go
@@ -22,9 +22,9 @@ type Service interface {
// Count defines a function that counts a list of secrets.
Count(string, string, string, []string) (int64, error)
// Create defines a function that creates a new secret.
- Create(string, string, string, *library.Secret) error
+ Create(string, string, string, *library.Secret) (*library.Secret, error)
// Update defines a function that updates an existing secret.
- Update(string, string, string, *library.Secret) error
+ Update(string, string, string, *library.Secret) (*library.Secret, error)
// Delete defines a function that deletes a secret.
Delete(string, string, string, string) error
diff --git a/secret/setup.go b/secret/setup.go
index cdebe406b..e0a28a8a8 100644
--- a/secret/setup.go
+++ b/secret/setup.go
@@ -13,7 +13,6 @@ import (
"github.com/go-vela/server/secret/native"
"github.com/go-vela/server/secret/vault"
"github.com/go-vela/types/constants"
-
"github.com/sirupsen/logrus"
)
@@ -27,7 +26,7 @@ type Setup struct {
Driver string
// specifies the database service to use for the secret client
- Database database.Service
+ Database database.Interface
// specifies the address to use for the secret client
Address string
diff --git a/secret/setup_test.go b/secret/setup_test.go
index 904e19059..ef4492681 100644
--- a/secret/setup_test.go
+++ b/secret/setup_test.go
@@ -8,20 +8,20 @@ import (
"reflect"
"testing"
- "github.com/go-vela/server/database/sqlite"
+ "github.com/go-vela/server/database"
)
func TestSecret_Setup_Native(t *testing.T) {
// setup types
- _database, err := sqlite.NewTest()
+ db, err := database.NewTest()
if err != nil {
- t.Errorf("unable to create database service: %v", err)
+ t.Errorf("unable to create test database engine: %v", err)
}
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
+ defer db.Close()
_setup := &Setup{
Driver: "native",
- Database: _database,
+ Database: db,
}
_native, err := _setup.Native()
@@ -125,11 +125,11 @@ func TestSecret_Setup_Vault(t *testing.T) {
func TestSecret_Setup_Validate(t *testing.T) {
// setup types
- _database, err := sqlite.NewTest()
+ db, err := database.NewTest()
if err != nil {
- t.Errorf("unable to create database service: %v", err)
+ t.Errorf("unable to create test database engine: %v", err)
}
- defer func() { _sql, _ := _database.Sqlite.DB(); _sql.Close() }()
+ defer db.Close()
// setup tests
tests := []struct {
@@ -140,7 +140,7 @@ func TestSecret_Setup_Validate(t *testing.T) {
failure: false,
setup: &Setup{
Driver: "native",
- Database: _database,
+ Database: db,
},
},
{
diff --git a/secret/vault/count.go b/secret/vault/count.go
index 21373d861..44dbf2335 100644
--- a/secret/vault/count.go
+++ b/secret/vault/count.go
@@ -35,7 +35,7 @@ func (c *client) Count(sType, org, name string, _ []string) (i int64, err error)
c.Logger.WithFields(fields).Tracef("counting vault %s secrets for %s/%s", sType, org, name)
- // nolint: staticcheck // ignore false positive
+ //nolint:staticcheck // ignore false positive
vault := new(api.Secret)
count := 0
diff --git a/secret/vault/count_test.go b/secret/vault/count_test.go
index b57d4ea1a..f5219b3e0 100644
--- a/secret/vault/count_test.go
+++ b/secret/vault/count_test.go
@@ -63,6 +63,7 @@ func TestVault_Count_Org(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -71,6 +72,7 @@ func TestVault_Count_Org(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -154,6 +156,7 @@ func TestVault_Count_Repo(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -162,6 +165,7 @@ func TestVault_Count_Repo(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -245,6 +249,7 @@ func TestVault_Count_Shared(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -253,6 +258,7 @@ func TestVault_Count_Shared(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -294,6 +300,7 @@ func TestVault_Count_InvalidType(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -302,6 +309,7 @@ func TestVault_Count_InvalidType(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -338,6 +346,7 @@ func TestVault_Count_ClosedServer(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -346,6 +355,7 @@ func TestVault_Count_ClosedServer(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -406,6 +416,7 @@ func TestVault_Count_EmptyList(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -414,6 +425,7 @@ func TestVault_Count_EmptyList(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -479,6 +491,7 @@ func TestVault_Count_InvalidList(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -487,6 +500,7 @@ func TestVault_Count_InvalidList(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
diff --git a/secret/vault/create.go b/secret/vault/create.go
index 552a6fa55..ba34883ef 100644
--- a/secret/vault/create.go
+++ b/secret/vault/create.go
@@ -16,7 +16,7 @@ import (
)
// Create creates a new secret.
-func (c *client) Create(sType, org, name string, s *library.Secret) error {
+func (c *client) Create(sType, org, name string, s *library.Secret) (*library.Secret, error) {
// create log fields from secret metadata
fields := logrus.Fields{
"org": org,
@@ -36,13 +36,12 @@ func (c *client) Create(sType, org, name string, s *library.Secret) error {
}
}
- // nolint: lll // ignore long line length due to parameters
c.Logger.WithFields(fields).Tracef("creating vault %s secret %s for %s/%s", sType, s.GetName(), org, name)
// validate the secret
err := database.SecretFromLibrary(s).Validate()
if err != nil {
- return err
+ return nil, err
}
// convert our secret to a Vault secret
@@ -57,31 +56,31 @@ func (c *client) Create(sType, org, name string, s *library.Secret) error {
case constants.SecretShared:
return c.createShared(org, name, s.GetName(), vault.Data)
default:
- return fmt.Errorf("invalid secret type: %v", sType)
+ return nil, fmt.Errorf("invalid secret type: %v", sType)
}
}
// createOrg is a helper function to create
// the org secret for the provided path.
-func (c *client) createOrg(org, path string, data map[string]interface{}) error {
+func (c *client) createOrg(org, path string, data map[string]interface{}) (*library.Secret, error) {
return c.create(fmt.Sprintf("%s/org/%s/%s", c.config.Prefix, org, path), data)
}
// createRepo is a helper function to create
// the repo secret for the provided path.
-func (c *client) createRepo(org, repo, path string, data map[string]interface{}) error {
+func (c *client) createRepo(org, repo, path string, data map[string]interface{}) (*library.Secret, error) {
return c.create(fmt.Sprintf("%s/repo/%s/%s/%s", c.config.Prefix, org, repo, path), data)
}
// createShared is a helper function to create
// the shared secret for the provided path.
-func (c *client) createShared(org, team, path string, data map[string]interface{}) error {
+func (c *client) createShared(org, team, path string, data map[string]interface{}) (*library.Secret, error) {
return c.create(fmt.Sprintf("%s/shared/%s/%s/%s", c.config.Prefix, org, team, path), data)
}
// create is a helper function to create
// the secret for the provided path.
-func (c *client) create(path string, data map[string]interface{}) error {
+func (c *client) create(path string, data map[string]interface{}) (*library.Secret, error) {
if strings.HasPrefix("secret/data", c.config.Prefix) {
data = map[string]interface{}{
"data": data,
@@ -89,10 +88,10 @@ func (c *client) create(path string, data map[string]interface{}) error {
}
// send API call to create the secret
- _, err := c.Vault.Logical().Write(path, data)
+ s, err := c.Vault.Logical().Write(path, data)
if err != nil {
- return err
+ return nil, err
}
- return nil
+ return secretFromVault(s), nil
}
diff --git a/secret/vault/create_test.go b/secret/vault/create_test.go
index 8ed0471b0..3213925f1 100644
--- a/secret/vault/create_test.go
+++ b/secret/vault/create_test.go
@@ -7,6 +7,7 @@ package vault
import (
"net/http"
"net/http/httptest"
+ "reflect"
"testing"
"github.com/go-vela/types/library"
@@ -23,15 +24,21 @@ func TestVault_Create_Org(t *testing.T) {
// setup mock server
engine.PUT("/v1/secret/org/foo/bar", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v1/org.json")
})
engine.PUT("/v1/secret/data/org/foo/bar", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v2/org.json")
})
engine.PUT("/v1/secret/data/prefix/org/foo/bar", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v2/org.json")
})
fake := httptest.NewServer(engine)
@@ -41,18 +48,17 @@ func TestVault_Create_Org(t *testing.T) {
sec := new(library.Secret)
sec.SetOrg("foo")
sec.SetRepo("*")
- sec.SetTeam("")
sec.SetName("bar")
sec.SetValue("baz")
sec.SetType("org")
sec.SetImages([]string{"foo", "bar"})
sec.SetEvents([]string{"foo", "bar"})
- sec.SetAllowCommand(false)
type args struct {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -61,6 +67,7 @@ func TestVault_Create_Org(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -75,7 +82,7 @@ func TestVault_Create_Org(t *testing.T) {
if err != nil {
t.Errorf("New returned err: %v", err)
}
- err = s.Create("org", "foo", "*", sec)
+ got, err := s.Create("org", "foo", "*", sec)
if resp.Code != http.StatusOK {
t.Errorf("Create returned %v, want %v", resp.Code, http.StatusOK)
@@ -84,6 +91,10 @@ func TestVault_Create_Org(t *testing.T) {
if err != nil {
t.Errorf("Create returned err: %v", err)
}
+
+ if !reflect.DeepEqual(got, sec) {
+ t.Errorf("Create returned %s, want %s", got, sec)
+ }
})
}
}
@@ -97,15 +108,21 @@ func TestVault_Create_Repo(t *testing.T) {
// setup mock server
engine.PUT("/v1/secret/repo/foo/bar/baz", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v1/repo.json")
})
engine.PUT("/v1/secret/data/repo/foo/bar/baz", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v2/repo.json")
})
engine.PUT("/v1/secret/data/prefix/repo/foo/bar/baz", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v2/repo.json")
})
fake := httptest.NewServer(engine)
@@ -115,18 +132,17 @@ func TestVault_Create_Repo(t *testing.T) {
sec := new(library.Secret)
sec.SetOrg("foo")
sec.SetRepo("bar")
- sec.SetTeam("")
sec.SetName("baz")
sec.SetValue("foob")
sec.SetType("repo")
sec.SetImages([]string{"foo", "bar"})
sec.SetEvents([]string{"foo", "bar"})
- sec.SetAllowCommand(false)
type args struct {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -135,6 +151,7 @@ func TestVault_Create_Repo(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -149,7 +166,8 @@ func TestVault_Create_Repo(t *testing.T) {
if err != nil {
t.Errorf("New returned err: %v", err)
}
- err = s.Create("repo", "foo", "bar", sec)
+
+ got, err := s.Create("repo", "foo", "bar", sec)
if resp.Code != http.StatusOK {
t.Errorf("Create returned %v, want %v", resp.Code, http.StatusOK)
@@ -158,6 +176,10 @@ func TestVault_Create_Repo(t *testing.T) {
if err != nil {
t.Errorf("Create returned err: %v", err)
}
+
+ if !reflect.DeepEqual(got, sec) {
+ t.Errorf("Create returned %s, want %s", got, sec)
+ }
})
}
}
@@ -171,13 +193,21 @@ func TestVault_Create_Shared(t *testing.T) {
// setup mock server
engine.PUT("/v1/secret/shared/foo/bar/baz", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v1/shared.json")
})
+
engine.PUT("/v1/secret/data/shared/foo/bar/baz", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v2/shared.json")
})
+
engine.PUT("/v1/secret/data/prefix/shared/foo/bar/baz", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v2/shared.json")
})
fake := httptest.NewServer(engine)
@@ -186,19 +216,18 @@ func TestVault_Create_Shared(t *testing.T) {
// setup types
sec := new(library.Secret)
sec.SetOrg("foo")
- sec.SetRepo("")
sec.SetTeam("bar")
sec.SetName("baz")
sec.SetValue("foob")
sec.SetType("shared")
sec.SetImages([]string{"foo", "bar"})
sec.SetEvents([]string{"foo", "bar"})
- sec.SetAllowCommand(false)
type args struct {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -207,6 +236,7 @@ func TestVault_Create_Shared(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -221,7 +251,8 @@ func TestVault_Create_Shared(t *testing.T) {
if err != nil {
t.Errorf("New returned err: %v", err)
}
- err = s.Create("shared", "foo", "bar", sec)
+
+ got, err := s.Create("shared", "foo", "bar", sec)
if resp.Code != http.StatusOK {
t.Errorf("Create returned %v, want %v", resp.Code, http.StatusOK)
@@ -230,6 +261,10 @@ func TestVault_Create_Shared(t *testing.T) {
if err != nil {
t.Errorf("Create returned err: %v", err)
}
+
+ if !reflect.DeepEqual(got, sec) {
+ t.Errorf("Create returned %s, want %s", got, sec)
+ }
})
}
}
@@ -273,6 +308,7 @@ func TestVault_Create_InvalidSecret(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -281,6 +317,7 @@ func TestVault_Create_InvalidSecret(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -295,7 +332,8 @@ func TestVault_Create_InvalidSecret(t *testing.T) {
if err != nil {
t.Errorf("New returned err: %v", err)
}
- err = s.Create("repo", "foo", "bar", sec)
+
+ _, err = s.Create("repo", "foo", "bar", sec)
if resp.Code != http.StatusOK {
t.Errorf("Create returned %v, want %v", resp.Code, http.StatusOK)
@@ -329,6 +367,7 @@ func TestVault_Create_InvalidType(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -337,6 +376,7 @@ func TestVault_Create_InvalidType(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -352,7 +392,7 @@ func TestVault_Create_InvalidType(t *testing.T) {
t.Errorf("New returned err: %v", err)
}
- err = s.Create("invalid", "foo", "bar", sec)
+ _, err = s.Create("invalid", "foo", "bar", sec)
if err == nil {
t.Errorf("Create should have returned err")
}
@@ -381,6 +421,7 @@ func TestVault_Create_ClosedServer(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -389,6 +430,7 @@ func TestVault_Create_ClosedServer(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -404,7 +446,7 @@ func TestVault_Create_ClosedServer(t *testing.T) {
t.Errorf("New returned err: %v", err)
}
- err = s.Create("repo", "foo", "bar", sec)
+ _, err = s.Create("repo", "foo", "bar", sec)
if err == nil {
t.Errorf("Create should have returned err")
}
diff --git a/secret/vault/delete_test.go b/secret/vault/delete_test.go
index bdccaf416..57338820c 100644
--- a/secret/vault/delete_test.go
+++ b/secret/vault/delete_test.go
@@ -33,12 +33,14 @@ func TestVault_Delete_Org(t *testing.T) {
})
fake := httptest.NewServer(engine)
+
defer fake.Close()
type args struct {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -47,6 +49,7 @@ func TestVault_Delete_Org(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -102,6 +105,7 @@ func TestVault_Delete_Repo(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -110,6 +114,7 @@ func TestVault_Delete_Repo(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -165,6 +170,7 @@ func TestVault_Delete_Shared(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -173,6 +179,7 @@ func TestVault_Delete_Shared(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -210,6 +217,7 @@ func TestVault_Delete_InvalidType(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -218,6 +226,7 @@ func TestVault_Delete_InvalidType(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -250,6 +259,7 @@ func TestVault_Delete_ClosedServer(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -258,6 +268,7 @@ func TestVault_Delete_ClosedServer(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
diff --git a/secret/vault/doc.go b/secret/vault/doc.go
index 1fe491a49..12b1d8332 100644
--- a/secret/vault/doc.go
+++ b/secret/vault/doc.go
@@ -7,5 +7,5 @@
//
// Usage:
//
-// import "github.com/go-vela/server/secret/vault"
+// import "github.com/go-vela/server/secret/vault"
package vault
diff --git a/secret/vault/driver_test.go b/secret/vault/driver_test.go
index 43e83e321..9e4589fe8 100644
--- a/secret/vault/driver_test.go
+++ b/secret/vault/driver_test.go
@@ -25,6 +25,7 @@ func TestVault_Driver(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
diff --git a/secret/vault/get.go b/secret/vault/get.go
index 486dec6fc..3612176a5 100644
--- a/secret/vault/get.go
+++ b/secret/vault/get.go
@@ -38,7 +38,7 @@ func (c *client) Get(sType, org, name, path string) (s *library.Secret, err erro
c.Logger.WithFields(fields).Tracef("getting vault %s secret %s for %s/%s", sType, path, org, name)
- // nolint: ineffassign,staticcheck // ignore false positive
+ //nolint:ineffassign,staticcheck // ignore false positive
vault := new(api.Secret)
// capture the secret from the Vault service
@@ -75,7 +75,6 @@ func (c *client) getRepo(org, repo, path string) (*api.Secret, error) {
// getShared is a helper function to capture
// the shared secret for the provided path.
func (c *client) getShared(org, team, path string) (*api.Secret, error) {
- // nolint: lll // ignore long line length due to parameters
return c.get(fmt.Sprintf("%s/%s/%s/%s/%s", c.config.Prefix, constants.SecretShared, org, team, path))
}
diff --git a/secret/vault/get_test.go b/secret/vault/get_test.go
index 024644990..624cbb07a 100644
--- a/secret/vault/get_test.go
+++ b/secret/vault/get_test.go
@@ -58,6 +58,7 @@ func TestVault_Get_Org(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -66,6 +67,7 @@ func TestVault_Get_Org(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -140,6 +142,7 @@ func TestVault_Get_Repo(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -148,6 +151,7 @@ func TestVault_Get_Repo(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -222,6 +226,7 @@ func TestVault_Get_Shared(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -230,6 +235,7 @@ func TestVault_Get_Shared(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -270,6 +276,7 @@ func TestVault_Get_InvalidType(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -278,6 +285,7 @@ func TestVault_Get_InvalidType(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -313,6 +321,7 @@ func TestVault_Get_ClosedServer(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -321,6 +330,7 @@ func TestVault_Get_ClosedServer(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
diff --git a/secret/vault/list.go b/secret/vault/list.go
index 45172f548..00b64db29 100644
--- a/secret/vault/list.go
+++ b/secret/vault/list.go
@@ -43,7 +43,7 @@ func (c *client) List(sType, org, name string, _, _ int, _ []string) ([]*library
var err error
s := []*library.Secret{}
- // nolint: staticcheck // ignore false positive
+ //nolint:staticcheck // ignore false positive
vault := new(api.Secret)
// capture the list of secrets from the Vault service
diff --git a/secret/vault/list_test.go b/secret/vault/list_test.go
index 3323948f8..bb7f538a4 100644
--- a/secret/vault/list_test.go
+++ b/secret/vault/list_test.go
@@ -75,6 +75,7 @@ func TestVault_List_Org(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -83,6 +84,7 @@ func TestVault_List_Org(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -204,6 +206,7 @@ func TestVault_List_Repo(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -212,6 +215,7 @@ func TestVault_List_Repo(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -318,6 +322,7 @@ func TestVault_List_Shared(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -326,6 +331,7 @@ func TestVault_List_Shared(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -366,6 +372,7 @@ func TestVault_List_InvalidType(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -374,6 +381,7 @@ func TestVault_List_InvalidType(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -463,6 +471,7 @@ func TestVault_List_EmptyList(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -471,6 +480,7 @@ func TestVault_List_EmptyList(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -536,6 +546,7 @@ func TestVault_List_InvalidList(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -544,6 +555,7 @@ func TestVault_List_InvalidList(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -618,6 +630,7 @@ func TestVault_List_NoRead(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -626,6 +639,7 @@ func TestVault_List_NoRead(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
diff --git a/secret/vault/refresh.go b/secret/vault/refresh.go
index 330255a78..461d49fe8 100644
--- a/secret/vault/refresh.go
+++ b/secret/vault/refresh.go
@@ -1,10 +1,14 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
package vault
import (
"encoding/base64"
"encoding/json"
"fmt"
- "io/ioutil"
+ "io"
"time"
"github.com/aws/aws-sdk-go/aws"
@@ -64,6 +68,7 @@ func (c *client) getAwsToken() (string, time.Duration, error) {
}
c.Logger.Trace("getting AWS token from vault")
+
secret, err := c.Vault.Logical().Write("auth/aws/login", headers)
if err != nil {
return "", 0, err
@@ -97,14 +102,14 @@ func (c *client) generateAwsAuthHeader() (map[string]interface{}, error) {
}
// read the STS request body
- requestBody, err := ioutil.ReadAll(req.Body)
+ requestBody, err := io.ReadAll(req.Body)
if err != nil {
return nil, err
}
// construct the vault STS auth header
//
- // nolint: lll // ignore long line length due to variable names
+
loginData := map[string]interface{}{
"role": c.AWS.Role,
"iam_http_request_method": req.HTTPRequest.Method,
diff --git a/secret/vault/refresh_test.go b/secret/vault/refresh_test.go
index 60b5b4dca..33b41fa35 100644
--- a/secret/vault/refresh_test.go
+++ b/secret/vault/refresh_test.go
@@ -1,11 +1,15 @@
+// Copyright (c) 2022 Target Brands, Inc. All rights reserved.
+//
+// Use of this source code is governed by the LICENSE file in this repository.
+
package vault
import (
"fmt"
- "io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
+ "os"
"strings"
"testing"
"time"
@@ -41,7 +45,7 @@ func Test_client_initialize(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- data, err := ioutil.ReadFile(fmt.Sprintf("testdata/refresh/%s", tt.responseFile))
+ data, err := os.ReadFile(fmt.Sprintf("testdata/refresh/%s", tt.responseFile))
if err != nil {
t.Error(err)
}
@@ -197,7 +201,7 @@ func Test_client_getAwsToken(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(*testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- data, err := ioutil.ReadFile(fmt.Sprintf("testdata/refresh/%s", tt.responseFile))
+ data, err := os.ReadFile(fmt.Sprintf("testdata/refresh/%s", tt.responseFile))
if err != nil {
t.Error(err)
}
diff --git a/secret/vault/update.go b/secret/vault/update.go
index ce2d53b57..453d15e3e 100644
--- a/secret/vault/update.go
+++ b/secret/vault/update.go
@@ -16,7 +16,7 @@ import (
)
// Update updates a secret.
-func (c *client) Update(sType, org, name string, s *library.Secret) error {
+func (c *client) Update(sType, org, name string, s *library.Secret) (*library.Secret, error) {
// create log fields from secret metadata
fields := logrus.Fields{
"org": org,
@@ -36,13 +36,12 @@ func (c *client) Update(sType, org, name string, s *library.Secret) error {
}
}
- // nolint: lll // ignore long line length due to parameters
c.Logger.WithFields(fields).Tracef("updating vault %s secret %s for %s/%s", sType, s.GetName(), org, name)
// capture the secret from the Vault service
sec, err := c.Get(sType, org, name, s.GetName())
if err != nil {
- return err
+ return nil, err
}
// convert the Vault secret our secret
@@ -66,7 +65,7 @@ func (c *client) Update(sType, org, name string, s *library.Secret) error {
// validate the secret
err = database.SecretFromLibrary(secretFromVault(vault)).Validate()
if err != nil {
- return err
+ return nil, err
}
// update the secret for the Vault service
@@ -84,37 +83,35 @@ func (c *client) Update(sType, org, name string, s *library.Secret) error {
// updateOrg is a helper function to update
// the org secret for the provided path.
-func (c *client) updateOrg(org, path string, data map[string]interface{}) error {
+func (c *client) updateOrg(org, path string, data map[string]interface{}) (*library.Secret, error) {
return c.update(fmt.Sprintf("%s/%s/%s/%s", c.config.Prefix, constants.SecretOrg, org, path), data)
}
// updateRepo is a helper function to update
// the repo secret for the provided path.
-func (c *client) updateRepo(org, repo, path string, data map[string]interface{}) error {
- // nolint: lll // ignore long line length due to variable names
+func (c *client) updateRepo(org, repo, path string, data map[string]interface{}) (*library.Secret, error) {
return c.update(fmt.Sprintf("%s/%s/%s/%s/%s", c.config.Prefix, constants.SecretRepo, org, repo, path), data)
}
// updateShared is a helper function to update
// the shared secret for the provided path.
-func (c *client) updateShared(org, team, path string, data map[string]interface{}) error {
- // nolint: lll // ignore long line length due to variable names
+func (c *client) updateShared(org, team, path string, data map[string]interface{}) (*library.Secret, error) {
return c.update(fmt.Sprintf("%s/%s/%s/%s/%s", c.config.Prefix, constants.SecretShared, org, team, path), data)
}
// update is a helper function to update
// the secret for the provided path.
-func (c *client) update(path string, data map[string]interface{}) error {
+func (c *client) update(path string, data map[string]interface{}) (*library.Secret, error) {
if strings.HasPrefix("secret/data", c.config.Prefix) {
data = map[string]interface{}{
"data": data,
}
}
- _, err := c.Vault.Logical().Write(path, data)
+ s, err := c.Vault.Logical().Write(path, data)
if err != nil {
- return err
+ return nil, err
}
- return nil
+ return secretFromVault(s), nil
}
diff --git a/secret/vault/update_test.go b/secret/vault/update_test.go
index 785c87dd2..49e04a1b2 100644
--- a/secret/vault/update_test.go
+++ b/secret/vault/update_test.go
@@ -7,6 +7,7 @@ package vault
import (
"net/http"
"net/http/httptest"
+ "reflect"
"testing"
"github.com/go-vela/types/library"
@@ -22,31 +23,37 @@ func TestVault_Update_Org(t *testing.T) {
_, engine := gin.CreateTestContext(resp)
// setup mock server
- engine.GET("/v1/secret/org/foo/bar", func(c *gin.Context) {
+ engine.PUT("/v1/secret/org/foo/bar", func(c *gin.Context) {
c.Header("Content-Type", "application/json")
c.Status(http.StatusOK)
c.File("testdata/v1/org.json")
})
- engine.PUT("/v1/secret/org/foo/bar", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ engine.GET("/v1/secret/org/foo/bar", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v1/org.json")
})
- engine.GET("/v1/secret/data/org/foo/bar", func(c *gin.Context) {
+ engine.PUT("/v1/secret/data/org/foo/bar", func(c *gin.Context) {
c.Header("Content-Type", "application/json")
c.Status(http.StatusOK)
c.File("testdata/v2/org.json")
})
- engine.PUT("/v1/secret/data/org/foo/bar", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ engine.GET("/v1/secret/data/org/foo/bar", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v2/org.json")
})
- engine.GET("/v1/secret/data/prefix/org/foo/bar", func(c *gin.Context) {
+ engine.PUT("/v1/secret/data/prefix/org/foo/bar", func(c *gin.Context) {
c.Header("Content-Type", "application/json")
c.Status(http.StatusOK)
c.File("testdata/v2/org.json")
})
- engine.PUT("/v1/secret/data/prefix/org/foo/bar", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ engine.GET("/v1/secret/data/prefix/org/foo/bar", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v2/org.json")
})
fake := httptest.NewServer(engine)
@@ -56,18 +63,17 @@ func TestVault_Update_Org(t *testing.T) {
sec := new(library.Secret)
sec.SetOrg("foo")
sec.SetRepo("*")
- sec.SetTeam("")
sec.SetName("bar")
sec.SetValue("baz")
sec.SetType("org")
sec.SetImages([]string{"foo", "bar"})
sec.SetEvents([]string{"foo", "bar"})
- sec.SetAllowCommand(false)
type args struct {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -76,6 +82,7 @@ func TestVault_Update_Org(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -91,7 +98,7 @@ func TestVault_Update_Org(t *testing.T) {
t.Errorf("New returned err: %v", err)
}
- err = s.Update("org", "foo", "*", sec)
+ got, err := s.Update("org", "foo", "*", sec)
if resp.Code != http.StatusOK {
t.Errorf("Update returned %v, want %v", resp.Code, http.StatusOK)
@@ -100,6 +107,10 @@ func TestVault_Update_Org(t *testing.T) {
if err != nil {
t.Errorf("Update returned err: %v", err)
}
+
+ if !reflect.DeepEqual(got, sec) {
+ t.Errorf("Update returned %s, want %s", got, sec)
+ }
})
}
}
@@ -112,31 +123,37 @@ func TestVault_Update_Repo(t *testing.T) {
_, engine := gin.CreateTestContext(resp)
// setup mock server
- engine.GET("/v1/secret/repo/foo/bar/baz", func(c *gin.Context) {
+ engine.PUT("/v1/secret/repo/foo/bar/baz", func(c *gin.Context) {
c.Header("Content-Type", "application/json")
c.Status(http.StatusOK)
c.File("testdata/v1/repo.json")
})
- engine.PUT("/v1/secret/repo/foo/bar/baz", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ engine.GET("/v1/secret/repo/foo/bar/baz", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v1/repo.json")
})
- engine.GET("/v1/secret/data/repo/foo/bar/baz", func(c *gin.Context) {
+ engine.PUT("/v1/secret/data/repo/foo/bar/baz", func(c *gin.Context) {
c.Header("Content-Type", "application/json")
c.Status(http.StatusOK)
c.File("testdata/v2/repo.json")
})
- engine.PUT("/v1/secret/data/repo/foo/bar/baz", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ engine.GET("/v1/secret/data/repo/foo/bar/baz", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v2/repo.json")
})
- engine.GET("/v1/secret/data/prefix/repo/foo/bar/baz", func(c *gin.Context) {
+ engine.PUT("/v1/secret/data/prefix/repo/foo/bar/baz", func(c *gin.Context) {
c.Header("Content-Type", "application/json")
c.Status(http.StatusOK)
c.File("testdata/v2/repo.json")
})
- engine.PUT("/v1/secret/data/prefix/repo/foo/bar/baz", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ engine.GET("/v1/secret/data/prefix/repo/foo/bar/baz", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v2/repo.json")
})
fake := httptest.NewServer(engine)
@@ -151,12 +168,12 @@ func TestVault_Update_Repo(t *testing.T) {
sec.SetType("repo")
sec.SetImages([]string{"foo", "bar"})
sec.SetEvents([]string{"foo", "bar"})
- sec.SetAllowCommand(false)
type args struct {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -165,6 +182,7 @@ func TestVault_Update_Repo(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -180,7 +198,7 @@ func TestVault_Update_Repo(t *testing.T) {
t.Errorf("New returned err: %v", err)
}
- err = s.Update("repo", "foo", "bar", sec)
+ got, err := s.Update("repo", "foo", "bar", sec)
if resp.Code != http.StatusOK {
t.Errorf("Update returned %v, want %v", resp.Code, http.StatusOK)
@@ -189,6 +207,10 @@ func TestVault_Update_Repo(t *testing.T) {
if err != nil {
t.Errorf("Update returned err: %v", err)
}
+
+ if !reflect.DeepEqual(got, sec) {
+ t.Errorf("Update returned %s, want %s", got, sec)
+ }
})
}
}
@@ -201,31 +223,37 @@ func TestVault_Update_Shared(t *testing.T) {
_, engine := gin.CreateTestContext(resp)
// setup mock server
- engine.GET("/v1/secret/shared/foo/bar/baz", func(c *gin.Context) {
+ engine.PUT("/v1/secret/shared/foo/bar/baz", func(c *gin.Context) {
c.Header("Content-Type", "application/json")
c.Status(http.StatusOK)
c.File("testdata/v1/shared.json")
})
- engine.PUT("/v1/secret/shared/foo/bar/baz", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ engine.GET("/v1/secret/shared/foo/bar/baz", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v1/shared.json")
})
- engine.GET("/v1/secret/data/shared/foo/bar/baz", func(c *gin.Context) {
+ engine.PUT("/v1/secret/data/shared/foo/bar/baz", func(c *gin.Context) {
c.Header("Content-Type", "application/json")
c.Status(http.StatusOK)
c.File("testdata/v2/shared.json")
})
- engine.PUT("/v1/secret/data/shared/foo/bar/baz", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ engine.GET("/v1/secret/data/shared/foo/bar/baz", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v2/shared.json")
})
- engine.GET("/v1/secret/data/prefix/shared/foo/bar/baz", func(c *gin.Context) {
+ engine.PUT("/v1/secret/data/prefix/shared/foo/bar/baz", func(c *gin.Context) {
c.Header("Content-Type", "application/json")
c.Status(http.StatusOK)
c.File("testdata/v2/shared.json")
})
- engine.PUT("/v1/secret/data/prefix/shared/foo/bar/baz", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
+ engine.GET("/v1/secret/data/prefix/shared/foo/bar/baz", func(c *gin.Context) {
+ c.Header("Content-Type", "application/json")
+ c.Status(http.StatusOK)
+ c.File("testdata/v2/shared.json")
})
fake := httptest.NewServer(engine)
@@ -240,12 +268,12 @@ func TestVault_Update_Shared(t *testing.T) {
sec.SetType("shared")
sec.SetImages([]string{"foo", "bar"})
sec.SetEvents([]string{"foo", "bar"})
- sec.SetAllowCommand(false)
type args struct {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -254,6 +282,7 @@ func TestVault_Update_Shared(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -269,7 +298,7 @@ func TestVault_Update_Shared(t *testing.T) {
t.Errorf("New returned err: %v", err)
}
- err = s.Update("shared", "foo", "bar", sec)
+ got, err := s.Update("shared", "foo", "bar", sec)
if resp.Code != http.StatusOK {
t.Errorf("Update returned %v, want %v", resp.Code, http.StatusOK)
@@ -278,6 +307,10 @@ func TestVault_Update_Shared(t *testing.T) {
if err != nil {
t.Errorf("Update returned err: %v", err)
}
+
+ if !reflect.DeepEqual(got, sec) {
+ t.Errorf("Update returned %s, want %s", got, sec)
+ }
})
}
}
@@ -290,32 +323,23 @@ func TestVault_Update_InvalidSecret(t *testing.T) {
_, engine := gin.CreateTestContext(resp)
// setup mock server
- engine.GET("/v1/secret/repo/foo/bar/baz", func(c *gin.Context) {
+ engine.PUT("/v1/secret/repo/foo/bar/baz", func(c *gin.Context) {
c.Header("Content-Type", "application/json")
c.Status(http.StatusOK)
c.File("testdata/v1/invalid_repo.json")
})
- engine.PUT("/v1/secret/repo/foo/bar/baz", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
- })
- engine.GET("/v1/secret/data/repo/foo/bar/baz", func(c *gin.Context) {
+ engine.PUT("/v1/secret/data/repo/foo/bar/baz", func(c *gin.Context) {
c.Header("Content-Type", "application/json")
c.Status(http.StatusOK)
c.File("testdata/v2/invalid_repo.json")
})
- engine.PUT("/v1/secret/data/repo/foo/bar/baz", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
- })
- engine.GET("/v1/secret/data/prefix/repo/foo/bar/baz", func(c *gin.Context) {
+ engine.PUT("/v1/secret/data/prefix/repo/foo/bar/baz", func(c *gin.Context) {
c.Header("Content-Type", "application/json")
c.Status(http.StatusOK)
c.File("testdata/v2/invalid_repo.json")
})
- engine.PUT("/v1/secret/data/prefix/repo/foo/bar/baz", func(c *gin.Context) {
- c.String(http.StatusNoContent, "")
- })
fake := httptest.NewServer(engine)
defer fake.Close()
@@ -335,6 +359,7 @@ func TestVault_Update_InvalidSecret(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -343,6 +368,7 @@ func TestVault_Update_InvalidSecret(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -358,7 +384,7 @@ func TestVault_Update_InvalidSecret(t *testing.T) {
t.Errorf("New returned err: %v", err)
}
- err = s.Update("repo", "foo", "bar", sec)
+ _, err = s.Update("repo", "foo", "bar", sec)
if resp.Code != http.StatusOK {
t.Errorf("Update returned %v, want %v", resp.Code, http.StatusOK)
@@ -390,6 +416,7 @@ func TestVault_Update_InvalidType(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -398,6 +425,7 @@ func TestVault_Update_InvalidType(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -413,7 +441,7 @@ func TestVault_Update_InvalidType(t *testing.T) {
t.Errorf("New returned err: %v", err)
}
- err = s.Update("invalid", "foo", "bar", sec)
+ _, err = s.Update("invalid", "foo", "bar", sec)
if err == nil {
t.Errorf("Update should have returned err")
}
@@ -440,6 +468,7 @@ func TestVault_Update_ClosedServer(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -448,6 +477,7 @@ func TestVault_Update_ClosedServer(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -463,7 +493,7 @@ func TestVault_Update_ClosedServer(t *testing.T) {
t.Errorf("New returned err: %v", err)
}
- err = s.Update("repo", "foo", "bar", sec)
+ _, err = s.Update("repo", "foo", "bar", sec)
if err == nil {
t.Errorf("Update should have returned err")
}
@@ -479,29 +509,14 @@ func TestVault_Update_NoWrite(t *testing.T) {
_, engine := gin.CreateTestContext(resp)
// setup mock server
- engine.GET("/v1/secret/repo/foo/bar/baz", func(c *gin.Context) {
- c.Header("Content-Type", "application/json")
- c.Status(http.StatusOK)
- c.File("testdata/v1/repo.json")
- })
engine.PUT("/v1/secret/repo/foo/bar/baz", func(c *gin.Context) {
c.Status(http.StatusNotFound)
})
- engine.GET("/v1/secret/data/repo/foo/bar/baz", func(c *gin.Context) {
- c.Header("Content-Type", "application/json")
- c.Status(http.StatusOK)
- c.File("testdata/v2/repo.json")
- })
engine.PUT("/v1/secret/data/repo/foo/bar/baz", func(c *gin.Context) {
c.Status(http.StatusNotFound)
})
- engine.GET("/v1/secret/data/prefix/repo/foo/bar/baz", func(c *gin.Context) {
- c.Header("Content-Type", "application/json")
- c.Status(http.StatusOK)
- c.File("testdata/v2/repo.json")
- })
engine.PUT("/v1/secret/data/prefix/repo/foo/bar/baz", func(c *gin.Context) {
c.Status(http.StatusNotFound)
})
@@ -523,6 +538,7 @@ func TestVault_Update_NoWrite(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -531,6 +547,7 @@ func TestVault_Update_NoWrite(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -546,7 +563,7 @@ func TestVault_Update_NoWrite(t *testing.T) {
t.Errorf("New returned err: %v", err)
}
- err = s.Update("repo", "foo", "bar", sec)
+ _, err = s.Update("repo", "foo", "bar", sec)
if resp.Code != http.StatusOK {
t.Errorf("Update returned %v, want %v", resp.Code, http.StatusOK)
diff --git a/secret/vault/vault.go b/secret/vault/vault.go
index f49755737..997ff46f5 100644
--- a/secret/vault/vault.go
+++ b/secret/vault/vault.go
@@ -58,7 +58,7 @@ type (
// New returns a Secret implementation that integrates with a Vault secrets engine.
//
-// nolint: revive // ignore returning unexported client
+//nolint:revive // ignore returning unexported client
func New(opts ...ClientOpt) (*client, error) {
// create new Vault client
c := new(client)
@@ -132,7 +132,7 @@ func New(opts ...ClientOpt) (*client, error) {
// secretFromVault is a helper function to convert a HashiCorp Vault secret to a Vela secret.
//
-// nolint: gocyclo,funlen // ignore cyclomatic complexity and function length due to conditionals
+//nolint:gocyclo,funlen // ignore cyclomatic complexity and function length due to conditionals
func secretFromVault(vault *api.Secret) *library.Secret {
s := new(library.Secret)
diff --git a/secret/vault/vault_test.go b/secret/vault/vault_test.go
index d7d62e6ac..7ba0ac254 100644
--- a/secret/vault/vault_test.go
+++ b/secret/vault/vault_test.go
@@ -23,6 +23,7 @@ func TestVault_New(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -31,6 +32,7 @@ func TestVault_New(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -58,6 +60,7 @@ func TestVault_New_Error(t *testing.T) {
version string
prefix string
}
+
tests := []struct {
name string
args args
@@ -66,6 +69,7 @@ func TestVault_New_Error(t *testing.T) {
{"v2", args{version: "2", prefix: ""}},
{"v2 with prefix", args{version: "2", prefix: "prefix"}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := New(
@@ -146,6 +150,7 @@ func TestVault_secretFromVault(t *testing.T) {
type args struct {
secret *api.Secret
}
+
tests := []struct {
name string
args args
@@ -153,6 +158,7 @@ func TestVault_secretFromVault(t *testing.T) {
{"v1", args{secret: inputV1}},
{"v2", args{secret: inputV2}},
}
+
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := secretFromVault(tt.args.secret)
diff --git a/util/util.go b/util/util.go
index 6e7813807..31cdd1104 100644
--- a/util/util.go
+++ b/util/util.go
@@ -5,15 +5,19 @@
package util
import (
- "github.com/gin-gonic/gin"
+ "html"
+ "strings"
+
+ "github.com/go-vela/types/library"
+ "github.com/gin-gonic/gin"
"github.com/go-vela/types"
)
// HandleError appends the error to the handler chain for logging and outputs it.
func HandleError(c *gin.Context, status int, err error) {
msg := err.Error()
- // nolint: errcheck // ignore checking error
+ //nolint:errcheck // ignore checking error
c.Error(err)
c.AbortWithStatusJSON(status, types.Error{Message: &msg})
}
@@ -41,3 +45,88 @@ func MinInt(a, b int) int {
return b
}
+
+// FormParameter safely captures a form parameter from the context
+// by removing any new lines and HTML escaping the value.
+func FormParameter(c *gin.Context, parameter string) string {
+ return EscapeValue(c.Request.FormValue(parameter))
+}
+
+// QueryParameter safely captures a query parameter from the context
+// by removing any new lines and HTML escaping the value.
+func QueryParameter(c *gin.Context, parameter, value string) string {
+ return EscapeValue(c.DefaultQuery(parameter, value))
+}
+
+// PathParameter safely captures a path parameter from the context
+// by removing any new lines and HTML escaping the value.
+func PathParameter(c *gin.Context, parameter string) string {
+ return EscapeValue(c.Param(parameter))
+}
+
+// SplitFullName safely splits the repo.FullName field into an org and name.
+func SplitFullName(value string) (string, string) {
+ // split repo full name into org and repo
+ repoSlice := strings.Split(value, "/")
+ if len(repoSlice) != 2 {
+ return "", ""
+ }
+
+ org := repoSlice[0]
+ repo := repoSlice[1]
+
+ return org, repo
+}
+
+// EscapeValue safely escapes any string by removing any new lines and HTML escaping it.
+func EscapeValue(value string) string {
+ // replace all new lines in the value
+ escaped := strings.Replace(strings.Replace(value, "\n", "", -1), "\r", "", -1)
+
+ // HTML escape the new line escaped value
+ return html.EscapeString(escaped)
+}
+
+// Unique is a helper function that takes a slice and
+// validates that there are no duplicate entries.
+func Unique(stringSlice []string) []string {
+ keys := make(map[string]bool)
+ list := []string{}
+
+ for _, entry := range stringSlice {
+ if _, value := keys[entry]; !value {
+ keys[entry] = true
+
+ list = append(list, entry)
+ }
+ }
+
+ return list
+}
+
+// CheckAllowlist is a helper function to ensure only repos in the
+// allowlist are specified.
+//
+// a single entry of '*' allows any repo to be enabled.
+func CheckAllowlist(r *library.Repo, allowlist []string) bool {
+ // check if all repos are allowed to be enabled
+ if len(allowlist) == 1 && allowlist[0] == "*" {
+ return true
+ }
+
+ for _, repo := range allowlist {
+ // allow all repos in org
+ if strings.Contains(repo, "/*") {
+ if strings.HasPrefix(repo, r.GetOrg()) {
+ return true
+ }
+ }
+
+ // allow specific repo within org
+ if repo == r.GetFullName() {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/version/version.go b/version/version.go
index 3368f7a64..30312ee24 100644
--- a/version/version.go
+++ b/version/version.go
@@ -34,7 +34,7 @@ var (
func New() *version.Version {
// check if a semantic tag was provided
if len(Tag) == 0 {
- logrus.Warningf("no semantic tag provided - defaulting to v0.0.0")
+ logrus.Warning("no semantic tag provided - defaulting to v0.0.0")
// set a fallback default for the tag
Tag = "v0.0.0"
@@ -42,7 +42,7 @@ func New() *version.Version {
v, err := semver.NewVersion(Tag)
if err != nil {
- fmt.Println(fmt.Errorf("unable to parse semantic version for %s: %v", Tag, err))
+ fmt.Println(fmt.Errorf("unable to parse semantic version for %s: %w", Tag, err))
}
return &version.Version{