add pipelines and executions handlers to gitness

This commit is contained in:
Vistaar Juneja 2023-08-03 15:31:32 +01:00
parent b5a15b5ce1
commit 8cdcecb56f
31 changed files with 1280 additions and 3 deletions

View File

@ -16,7 +16,9 @@ import (
gitrpcserver "github.com/harness/gitness/gitrpc/server"
gitrpccron "github.com/harness/gitness/gitrpc/server/cron"
checkcontroller "github.com/harness/gitness/internal/api/controller/check"
"github.com/harness/gitness/internal/api/controller/execution"
"github.com/harness/gitness/internal/api/controller/githook"
"github.com/harness/gitness/internal/api/controller/pipeline"
"github.com/harness/gitness/internal/api/controller/principal"
"github.com/harness/gitness/internal/api/controller/pullreq"
"github.com/harness/gitness/internal/api/controller/repo"
@ -90,6 +92,8 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e
codecomments.WireSet,
gitrpccron.WireSet,
checkcontroller.WireSet,
execution.WireSet,
pipeline.WireSet,
)
return &cliserver.System{}, nil
}

View File

@ -14,7 +14,9 @@ import (
server3 "github.com/harness/gitness/gitrpc/server"
"github.com/harness/gitness/gitrpc/server/cron"
check2 "github.com/harness/gitness/internal/api/controller/check"
"github.com/harness/gitness/internal/api/controller/execution"
"github.com/harness/gitness/internal/api/controller/githook"
"github.com/harness/gitness/internal/api/controller/pipeline"
"github.com/harness/gitness/internal/api/controller/principal"
"github.com/harness/gitness/internal/api/controller/pullreq"
"github.com/harness/gitness/internal/api/controller/repo"
@ -84,7 +86,11 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
return nil, err
}
repoController := repo.ProvideController(config, db, provider, pathUID, authorizer, pathStore, repoStore, spaceStore, principalStore, gitrpcInterface)
executionStore := database.ProvideExecutionStore(db)
executionController := execution.ProvideController(db, authorizer, executionStore, repoStore, spaceStore)
spaceController := space.ProvideController(db, provider, pathUID, authorizer, pathStore, spaceStore, repoStore, principalStore, repoController, membershipStore)
pipelineStore := database.ProvidePipelineStore(db)
pipelineController := pipeline.ProvideController(db, pathUID, pathStore, repoStore, authorizer, pipelineStore, spaceStore)
pullReqStore := database.ProvidePullReqStore(db, principalInfoCache)
pullReqActivityStore := database.ProvidePullReqActivityStore(db, principalInfoCache)
codeCommentView := database.ProvideCodeCommentView(db)
@ -138,7 +144,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
principalController := principal.ProvideController(principalStore)
checkStore := database.ProvideCheckStore(db, principalInfoCache)
checkController := check2.ProvideController(db, authorizer, repoStore, checkStore, gitrpcInterface)
apiHandler := router.ProvideAPIHandler(config, authenticator, repoController, spaceController, pullreqController, webhookController, githookController, serviceaccountController, controller, principalController, checkController)
apiHandler := router.ProvideAPIHandler(config, authenticator, repoController, executionController, spaceController, pipelineController, pullreqController, webhookController, githookController, serviceaccountController, controller, principalController, checkController)
gitHandler := router.ProvideGitHandler(config, provider, repoStore, authenticator, authorizer, gitrpcInterface)
webHandler := router.ProvideWebHandler(config)
routerRouter := router.ProvideRouter(config, apiHandler, gitHandler, webHandler)

View File

@ -0,0 +1,31 @@
package execution
import (
"github.com/harness/gitness/internal/auth/authz"
"github.com/harness/gitness/internal/store"
"github.com/jmoiron/sqlx"
)
type Controller struct {
db *sqlx.DB
authorizer authz.Authorizer
executionStore store.ExecutionStore
repoStore store.RepoStore
spaceStore store.SpaceStore
}
func NewController(
db *sqlx.DB,
authorizer authz.Authorizer,
executionStore store.ExecutionStore,
repoStore store.RepoStore,
spaceStore store.SpaceStore,
) *Controller {
return &Controller{
db: db,
authorizer: authorizer,
executionStore: executionStore,
repoStore: repoStore,
spaceStore: spaceStore,
}
}

View File

@ -0,0 +1,24 @@
package execution
import (
"github.com/google/wire"
"github.com/harness/gitness/internal/auth/authz"
"github.com/harness/gitness/internal/store"
"github.com/jmoiron/sqlx"
)
// WireSet provides a wire set for this package.
var WireSet = wire.NewSet(
ProvideController,
)
func ProvideController(db *sqlx.DB,
authorizer authz.Authorizer,
executionStore store.ExecutionStore,
repoStore store.RepoStore,
spaceStore store.SpaceStore,
) *Controller {
return NewController(db, authorizer, executionStore,
repoStore,
spaceStore)
}

View File

@ -0,0 +1,39 @@
package pipeline
import (
"github.com/harness/gitness/internal/auth/authz"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types/check"
"github.com/jmoiron/sqlx"
)
type Controller struct {
defaultBranch string
db *sqlx.DB
uidCheck check.PathUID
pathStore store.PathStore
repoStore store.RepoStore
authorizer authz.Authorizer
pipelineStore store.PipelineStore
spaceStore store.SpaceStore
}
func NewController(
db *sqlx.DB,
uidCheck check.PathUID,
authorizer authz.Authorizer,
pathStore store.PathStore,
repoStore store.RepoStore,
pipelineStore store.PipelineStore,
spaceStore store.SpaceStore,
) *Controller {
return &Controller{
db: db,
uidCheck: uidCheck,
pathStore: pathStore,
repoStore: repoStore,
authorizer: authorizer,
pipelineStore: pipelineStore,
spaceStore: spaceStore,
}
}

View File

@ -0,0 +1,113 @@
package pipeline
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"github.com/harness/gitness/internal/api/usererror"
"github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
)
var (
// errRepositoryRequiresParent if the user tries to create a repo without a parent space.
errPipelineRequiresParent = usererror.BadRequest(
"Parent space required - standalone pipelines are not supported.")
)
type CreateInput struct {
Description string `json:"description"`
ParentRef string `json:"parent_ref"` // Ref of the parent space
UID string `json:"uid"`
RepoRef string `json:"repo_ref"` // null if repo_type != gitness
RepoType enum.ScmType `json:"repo_type"`
DefaultBranch string `json:"default_branch"`
ConfigPath string `json:"config_path"`
}
// Create creates a new pipeline
func (c *Controller) Create(ctx context.Context, session *auth.Session, in *CreateInput) (*types.Pipeline, error) {
// TODO: Add auth
// parentSpace, err := c.getSpaceCheckAuthRepoCreation(ctx, session, in.ParentRef)
// if err != nil {
// return nil, err
// }
parentSpace, err := c.spaceStore.FindByRef(ctx, in.ParentRef)
if err != nil {
return nil, fmt.Errorf("could not find parent by ref: %w", err)
}
var repoID int64
if in.RepoType == enum.ScmTypeGitness {
repo, err := c.repoStore.FindByRef(ctx, in.RepoRef)
if err != nil {
return nil, fmt.Errorf("could not find repo by ref: %w", err)
}
repoID = repo.ID
}
if err := c.sanitizeCreateInput(in); err != nil {
return nil, fmt.Errorf("failed to sanitize input: %w", err)
}
var pipeline *types.Pipeline
err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) error {
// lock parent space path to ensure it doesn't get updated while we setup new pipeline
_, err := c.pathStore.FindPrimaryWithLock(ctx, enum.PathTargetTypeSpace, parentSpace.ID)
if err != nil {
return usererror.BadRequest("Parent not found")
}
now := time.Now().UnixMilli()
pipeline = &types.Pipeline{
Description: in.Description,
ParentID: parentSpace.ID,
UID: in.UID,
Seq: 0,
RepoID: repoID,
RepoType: in.RepoType,
DefaultBranch: in.DefaultBranch,
ConfigPath: in.ConfigPath,
Created: now,
Updated: now,
Version: 0,
}
err = c.pipelineStore.Create(ctx, pipeline)
if err != nil {
return fmt.Errorf("pipeline creation failed: %w", err)
}
return nil
})
return pipeline, nil
}
func (c *Controller) sanitizeCreateInput(in *CreateInput) error {
parentRefAsID, err := strconv.ParseInt(in.ParentRef, 10, 64)
if (err == nil && parentRefAsID <= 0) || (len(strings.TrimSpace(in.ParentRef)) == 0) {
return errPipelineRequiresParent
}
if err := c.uidCheck(in.UID, false); err != nil {
return err
}
in.Description = strings.TrimSpace(in.Description)
if err := check.Description(in.Description); err != nil {
return err
}
if in.DefaultBranch == "" {
in.DefaultBranch = c.defaultBranch
}
return nil
}

View File

@ -0,0 +1,34 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipeline
import (
"context"
"fmt"
"github.com/harness/gitness/internal/auth"
)
// Delete deletes a pipeline.
func (c *Controller) Delete(ctx context.Context, session *auth.Session, spaceRef string, uid string) error {
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return err
}
// TODO: Add auth
// if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceDelete, false); err != nil {
// return err
// }
// TODO: uncomment when soft delete is implemented
pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, uid)
if err != nil {
return err
}
err = c.pipelineStore.Delete(ctx, pipeline.ID)
if err != nil {
return fmt.Errorf("could not delete pipeline: %w", err)
}
return nil
}

View File

@ -0,0 +1,26 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipeline
import (
"context"
"github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types"
)
// Find finds a repo.
func (c *Controller) Find(ctx context.Context, session *auth.Session, spaceRef string, uid string) (*types.Pipeline, error) {
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, err
}
// TODO: Add auth
// if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceDelete, false); err != nil {
// return err
// }
// TODO: uncomment when soft delete is implemented
return c.pipelineStore.FindByUID(ctx, space.ID, uid)
}

View File

@ -0,0 +1,50 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipeline
import (
"context"
"github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types"
)
// UpdateInput is used for updating a repo.
type UpdateInput struct {
Description string `json:"description"`
UID string `json:"uid"`
ConfigPath string `json:"config_path"`
}
// Update updates a repository.
func (c *Controller) Update(ctx context.Context, session *auth.Session,
spaceRef string, uid string, in *UpdateInput) (*types.Pipeline, error) {
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, err
}
pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, uid)
if err != nil {
return nil, err
}
if in.Description != "" {
pipeline.Description = in.Description
}
if in.UID != "" {
pipeline.UID = in.UID
}
if in.ConfigPath != "" {
pipeline.ConfigPath = in.ConfigPath
}
// TODO: Add auth
// if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoEdit, false); err != nil {
// return nil, err
// }
return c.pipelineStore.Update(ctx, pipeline)
}

View File

@ -0,0 +1,25 @@
package pipeline
import (
"github.com/google/wire"
"github.com/harness/gitness/internal/auth/authz"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types/check"
"github.com/jmoiron/sqlx"
)
// WireSet provides a wire set for this package.
var WireSet = wire.NewSet(
ProvideController,
)
func ProvideController(db *sqlx.DB,
uidCheck check.PathUID,
pathStore store.PathStore,
repoStore store.RepoStore,
authorizer authz.Authorizer,
pipelineStore store.PipelineStore,
spaceStore store.SpaceStore,
) *Controller {
return NewController(db, uidCheck, authorizer, pathStore, repoStore, pipelineStore, spaceStore)
}

View File

@ -0,0 +1,33 @@
package pipeline
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/internal/api/controller/pipeline"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
)
// HandleCreate returns a http.HandlerFunc that creates a new pipelinesitory.
func HandleCreate(pipelineCtrl *pipeline.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
in := new(pipeline.CreateInput)
err := json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(w, "Invalid Request Body: %s.", err)
return
}
pipeline, err := pipelineCtrl.Create(ctx, session, in)
if err != nil {
render.TranslatedUserError(w, err)
return
}
render.JSON(w, http.StatusCreated, pipeline)
}
}

View File

@ -0,0 +1,40 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipeline
import (
"net/http"
"github.com/harness/gitness/internal/api/controller/pipeline"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
)
/*
* Deletes a pipeline.
*/
func HandleDelete(pipelineCtrl *pipeline.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
pipelineRef, err := request.GetPipelinePathRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return
}
spaceRef, pipelineUID, err := SplitRef(pipelineRef)
if err != nil {
render.TranslatedUserError(w, err)
}
err = pipelineCtrl.Delete(ctx, session, spaceRef, pipelineUID)
if err != nil {
render.TranslatedUserError(w, err)
return
}
render.DeleteSuccessful(w)
}
}

View File

@ -0,0 +1,49 @@
package pipeline
import (
"errors"
"net/http"
"strings"
"github.com/harness/gitness/internal/api/controller/pipeline"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
)
// HandleFind writes json-encoded repository information to the http response body.
func HandleFind(pipelineCtrl *pipeline.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
pipelineRef, err := request.GetPipelinePathRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return
}
spaceRef, pipelineUID, err := SplitRef(pipelineRef)
if err != nil {
render.TranslatedUserError(w, err)
}
pipeline, err := pipelineCtrl.Find(ctx, session, spaceRef, pipelineUID)
if err != nil {
render.TranslatedUserError(w, err)
return
}
render.JSON(w, http.StatusOK, pipeline)
}
}
func SplitRef(ref string) (string, string, error) {
lastIndex := strings.LastIndex(ref, "/")
if lastIndex == -1 {
// The input string does not contain a "/".
return "", "", errors.New("could not split ref")
}
spaceRef := ref[:lastIndex]
uid := ref[lastIndex+1:]
return spaceRef, uid, nil
}

View File

@ -0,0 +1,45 @@
package pipeline
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/internal/api/controller/pipeline"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
)
/*
* Updates an existing pipeline.
*/
func HandleUpdate(pipelineCtrl *pipeline.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
in := new(pipeline.UpdateInput)
err := json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(w, "Invalid Request Body: %s.", err)
return
}
pipelineRef, err := request.GetPipelinePathRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return
}
spaceRef, pipelineUID, err := SplitRef(pipelineRef)
if err != nil {
render.TranslatedUserError(w, err)
}
pipeline, err := pipelineCtrl.Update(ctx, session, spaceRef, pipelineUID, in)
if err != nil {
render.TranslatedUserError(w, err)
return
}
render.JSON(w, http.StatusOK, pipeline)
}
}

View File

@ -41,6 +41,7 @@ func Generate() *openapi3.Spec {
buildPrincipals(&reflector)
spaceOperations(&reflector)
repoOperations(&reflector)
pipelineOperations(&reflector)
resourceOperations(&reflector)
pullReqOperations(&reflector)
webhookOperations(&reflector)

View File

@ -0,0 +1,81 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this pipelinesitory.
package openapi
import (
"net/http"
"github.com/harness/gitness/internal/api/controller/pipeline"
"github.com/harness/gitness/internal/api/usererror"
"github.com/harness/gitness/types"
"github.com/swaggest/openapi-go/openapi3"
)
type createPipelineRequest struct {
pipeline.CreateInput
}
type pipelineRequest struct {
Ref string `path:"pipeline_ref"`
}
type updatePipelineRequest struct {
pipelineRequest
pipeline.UpdateInput
}
type scmType string
type pipelineGetResponse struct {
types.Pipeline
}
func pipelineOperations(reflector *openapi3.Reflector) {
opCreate := openapi3.Operation{}
opCreate.WithTags("pipeline")
opCreate.WithMapOfAnything(map[string]interface{}{"operationId": "createPipeline"})
_ = reflector.SetRequest(&opCreate, new(createPipelineRequest), http.MethodPost)
_ = reflector.SetJSONResponse(&opCreate, new(types.Pipeline), http.StatusCreated)
_ = reflector.SetJSONResponse(&opCreate, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opCreate, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opCreate, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opCreate, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodPost, "/pipelines", opCreate)
opFind := openapi3.Operation{}
opFind.WithTags("pipeline")
opFind.WithMapOfAnything(map[string]interface{}{"operationId": "findPipeline"})
_ = reflector.SetRequest(&opFind, new(pipelineRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opFind, new(pipelineGetResponse), http.StatusOK)
_ = reflector.SetJSONResponse(&opFind, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opFind, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opFind, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opFind, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet, "/pipelines/{pipeline_ref}", opFind)
opDelete := openapi3.Operation{}
opDelete.WithTags("pipeline")
opDelete.WithMapOfAnything(map[string]interface{}{"operationId": "deletePipeline"})
_ = reflector.SetRequest(&opDelete, new(pipelineRequest), http.MethodDelete)
_ = reflector.SetJSONResponse(&opDelete, nil, http.StatusNoContent)
_ = reflector.SetJSONResponse(&opDelete, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opDelete, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opDelete, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opDelete, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodDelete, "/pipelines/{pipeline_ref}", opDelete)
opUpdate := openapi3.Operation{}
opUpdate.WithTags("pipeline")
opUpdate.WithMapOfAnything(map[string]interface{}{"operationId": "updatePipeline"})
_ = reflector.SetRequest(&opUpdate, new(updatePipelineRequest), http.MethodPatch)
_ = reflector.SetJSONResponse(&opUpdate, new(types.Pipeline), http.StatusOK)
_ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPatch, "/pipelines/{pipeline_ref}", opUpdate)
}

View File

@ -0,0 +1,54 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package request
import (
"net/http"
"net/url"
)
const (
PipelinePathRef = "pipeline_ref"
PipelineUID = "pipeline_uid"
)
func GetPipelinePathRefFromPath(r *http.Request) (string, error) {
rawRef, err := PathParamOrError(r, PipelinePathRef)
if err != nil {
return "", err
}
// paths are unescaped
return url.PathUnescape(rawRef)
}
func GetPipelineUIDFromPath(r *http.Request) (string, error) {
rawRef, err := PathParamOrError(r, PipelineUID)
if err != nil {
return "", err
}
// paths are unescaped
return url.PathUnescape(rawRef)
}
// TODO: Add list filters
// // ParseSortRepo extracts the repo sort parameter from the url.
// func ParseSortRepo(r *http.Request) enum.RepoAttr {
// return enum.ParseRepoAtrr(
// r.URL.Query().Get(QueryParamSort),
// )
// }
// // ParseRepoFilter extracts the repository filter from the url.
// func ParseRepoFilter(r *http.Request) *types.RepoFilter {
// return &types.RepoFilter{
// Query: ParseQuery(r),
// Order: ParseOrder(r),
// Page: ParsePage(r),
// Sort: ParseSortRepo(r),
// Size: ParseLimit(r),
// }
// }

View File

@ -10,7 +10,9 @@ import (
"github.com/harness/gitness/githook"
"github.com/harness/gitness/internal/api/controller/check"
"github.com/harness/gitness/internal/api/controller/execution"
controllergithook "github.com/harness/gitness/internal/api/controller/githook"
"github.com/harness/gitness/internal/api/controller/pipeline"
"github.com/harness/gitness/internal/api/controller/principal"
"github.com/harness/gitness/internal/api/controller/pullreq"
"github.com/harness/gitness/internal/api/controller/repo"
@ -21,6 +23,7 @@ import (
"github.com/harness/gitness/internal/api/handler/account"
handlercheck "github.com/harness/gitness/internal/api/handler/check"
handlergithook "github.com/harness/gitness/internal/api/handler/githook"
handlerpipeline "github.com/harness/gitness/internal/api/handler/pipeline"
handlerprincipal "github.com/harness/gitness/internal/api/handler/principal"
handlerpullreq "github.com/harness/gitness/internal/api/handler/pullreq"
handlerrepo "github.com/harness/gitness/internal/api/handler/repo"
@ -62,7 +65,9 @@ func NewAPIHandler(
config *types.Config,
authenticator authn.Authenticator,
repoCtrl *repo.Controller,
executionCtrl *execution.Controller,
spaceCtrl *space.Controller,
pipelineCtrl *pipeline.Controller,
pullreqCtrl *pullreq.Controller,
webhookCtrl *webhook.Controller,
githookCtrl *controllergithook.Controller,
@ -92,7 +97,7 @@ func NewAPIHandler(
r.Use(middlewareauthn.Attempt(authenticator, authn.SourceRouterAPI))
r.Route("/v1", func(r chi.Router) {
setupRoutesV1(r, repoCtrl, spaceCtrl, pullreqCtrl, webhookCtrl, githookCtrl,
setupRoutesV1(r, config, repoCtrl, executionCtrl, pipelineCtrl, spaceCtrl, pullreqCtrl, webhookCtrl, githookCtrl,
saCtrl, userCtrl, principalCtrl, checkCtrl)
})
@ -114,7 +119,10 @@ func corsHandler(config *types.Config) func(http.Handler) http.Handler {
}
func setupRoutesV1(r chi.Router,
config *types.Config,
repoCtrl *repo.Controller,
executionCtrl *execution.Controller,
pipelineCtrl *pipeline.Controller,
spaceCtrl *space.Controller,
pullreqCtrl *pullreq.Controller,
webhookCtrl *webhook.Controller,
@ -126,6 +134,7 @@ func setupRoutesV1(r chi.Router,
) {
setupSpaces(r, spaceCtrl)
setupRepos(r, repoCtrl, pullreqCtrl, webhookCtrl, checkCtrl)
setupPipelines(r, pipelineCtrl, executionCtrl)
setupUser(r, userCtrl)
setupServiceAccounts(r, saCtrl)
setupPrincipals(r, principalCtrl)
@ -266,6 +275,20 @@ func setupRepos(r chi.Router,
})
}
func setupPipelines(r chi.Router, pipelineCtrl *pipeline.Controller, executionCtrl *execution.Controller) {
r.Route("/pipelines", func(r chi.Router) {
// Create takes path and parentId via body, not uri
r.Post("/", handlerpipeline.HandleCreate(pipelineCtrl))
r.Route(fmt.Sprintf("/{%s}", request.PipelinePathRef), func(r chi.Router) {
r.Get("/", handlerpipeline.HandleFind(pipelineCtrl))
r.Patch("/", handlerpipeline.HandleUpdate(pipelineCtrl))
r.Delete("/", handlerpipeline.HandleDelete(pipelineCtrl))
// TODO: setup executions here
// SetupExecutions(r, executionCtrl)
})
})
}
func setupInternal(r chi.Router, githookCtrl *controllergithook.Controller) {
r.Route("/internal", func(r chi.Router) {
SetupGitHooks(r, githookCtrl)

View File

@ -7,7 +7,9 @@ package router
import (
"github.com/harness/gitness/gitrpc"
"github.com/harness/gitness/internal/api/controller/check"
"github.com/harness/gitness/internal/api/controller/execution"
"github.com/harness/gitness/internal/api/controller/githook"
"github.com/harness/gitness/internal/api/controller/pipeline"
"github.com/harness/gitness/internal/api/controller/principal"
"github.com/harness/gitness/internal/api/controller/pullreq"
"github.com/harness/gitness/internal/api/controller/repo"
@ -57,7 +59,9 @@ func ProvideAPIHandler(
config *types.Config,
authenticator authn.Authenticator,
repoCtrl *repo.Controller,
executionCtrl *execution.Controller,
spaceCtrl *space.Controller,
pipelineCtrl *pipeline.Controller,
pullreqCtrl *pullreq.Controller,
webhookCtrl *webhook.Controller,
githookCtrl *githook.Controller,
@ -66,7 +70,7 @@ func ProvideAPIHandler(
principalCtrl principal.Controller,
checkCtrl *check.Controller,
) APIHandler {
return NewAPIHandler(config, authenticator, repoCtrl, spaceCtrl, pullreqCtrl,
return NewAPIHandler(config, authenticator, repoCtrl, executionCtrl, spaceCtrl, pipelineCtrl, pullreqCtrl,
webhookCtrl, githookCtrl, saCtrl, userCtrl, principalCtrl, checkCtrl)
}

View File

@ -439,4 +439,85 @@ type (
// Delete removes a required status checks for a repo.
Delete(ctx context.Context, repoID, reqCheckID int64) error
}
PipelineStore interface {
// Find returns a pipeline given a pipeline ID from the datastore.
Find(context.Context, int64) (*types.Pipeline, error)
// FindByUID returns a pipeline with a given UID in a space
FindByUID(context.Context, int64, string) (*types.Pipeline, error)
// Create creates a new pipeline in the datastore.
Create(context.Context, *types.Pipeline) error
// Update tries to update a pipeline in the datastore with optimistic locking.
Update(context.Context, *types.Pipeline) (*types.Pipeline, error)
// List lists the pipelines present in a parent space ID in the datastore.
List(context.Context, int64, *types.PipelineFilter) ([]*types.Pipeline, error)
// Delete deletes a pipeline ID from the datastore.
Delete(context.Context, int64) error
}
// TODO: Implement the execution store interface
ExecutionStore interface {
// Find returns a build from the datastore.
// Find(context.Context, int64) (*types.Execution, error)
// FindNumber returns a build from the datastore by build number.
// FindNumber(context.Context, int64, int64) (*types.Execution, error)
// FindLast returns the last build from the datastore by ref.
// FindRef(context.Context, int64, string) (*types.Execution, error)
// List returns a list of builds from the datastore by repository id.
// List(context.Context, int64, int, int) ([]*types.Execution, error)
// ListRef returns a list of builds from the datastore by ref.
// ListRef(context.Context, int64, string, int, int) ([]*types.Execution, error)
// LatestBranches returns the latest builds from the
// datastore by branch.
// LatestBranches(context.Context, int64) ([]*types.Execution, error)
// LatestPulls returns the latest builds from the
// datastore by pull request.
// LatestPulls(context.Context, int64) ([]*types.Execution, error)
// LatestDeploys returns the latest builds from the
// datastore by deployment target.
// LatestDeploys(context.Context, int64) ([]*types.Execution, error)
// Pending returns a list of pending builds from the
// datastore by repository id (DEPRECATED).
// Pending(context.Context) ([]*types.Execution, error)
// Running returns a list of running builds from the
// datastore by repository id (DEPRECATED).
// Running(context.Context) ([]*types.Execution, error)
// Create persists a build to the datastore.
// Create(context.Context, *types.Execution, []*Stage) error
// Update updates a build in the datastore.
// Update(context.Context, *types.Execution) error
// // Delete deletes a build from the datastore.
// Delete(context.Context, *types.Execution) error
// // DeletePull deletes a pull request index from the datastore.
// DeletePull(context.Context, int64, int) error
// // DeleteBranch deletes a branch index from the datastore.
// DeleteBranch(context.Context, int64, string) error
// // DeleteDeploy deletes a deploy index from the datastore.
// DeleteDeploy(context.Context, int64, string) error
// // Purge deletes builds from the database where the build number is less than n.
// Purge(context.Context, int64, int64) error
// // Count returns a count of builds.
// Count(context.Context) (int64, error)
}
)

View File

@ -0,0 +1,59 @@
package database
import (
"github.com/harness/gitness/internal/store"
"github.com/jmoiron/sqlx"
)
var _ store.ExecutionStore = (*executionStore)(nil)
// NewSpaceStore returns a new PathStore.
func NewExecutionStore(db *sqlx.DB) *executionStore {
return &executionStore{
db: db,
}
}
type executionStore struct {
db *sqlx.DB
}
const (
executionColumns = `
execution_id
,execution_scm_type
,execution_repo_id
,execution_trigger
,execution_number
,execution_parent
,execution_status
,execution_error
,execution_event
,execution_action
,execution_link
,execution_timestamp
,execution_title
,execution_message
,execution_before
,execution_after
,execution_ref
,execution_source_repo
,execution_source
,execution_target
,execution_author
,execution_author_name
,execution_author_email
,execution_author_avatar
,execution_sender
,execution_params
,execution_cron
,execution_deploy
,execution_deploy_id
,execution_debug
,execution_started
,execution_finished
,execution_created
,execution_updated
,execution_version
`
)

View File

@ -0,0 +1,48 @@
CREATE TABLE IF NOT EXISTS executions (
execution_id INTEGER PRIMARY KEY AUTOINCREMENT,
execution_pipeline_id INTEGER NOT NULL,
execution_repo_id INTEGER,
execution_repo_type TEXT,
execution_repo_name TEXT,
execution_trigger TEXT,
execution_number INTEGER NOT NULL,
execution_parent INTEGER,
execution_status TEXT,
execution_error TEXT,
execution_event TEXT,
execution_action TEXT,
execution_link TEXT,
execution_timestamp INTEGER,
execution_title TEXT,
execution_message TEXT,
execution_before TEXT,
execution_after TEXT,
execution_ref TEXT,
execution_source_repo TEXT,
execution_source TEXT,
execution_target TEXT,
execution_author TEXT,
execution_author_name TEXT,
execution_author_email TEXT,
execution_author_avatar TEXT,
execution_sender TEXT,
execution_params TEXT,
execution_cron TEXT,
execution_deploy TEXT,
execution_deploy_id INTEGER,
execution_debug BOOLEAN NOT NULL DEFAULT 0,
execution_started INTEGER,
execution_finished INTEGER,
execution_created INTEGER,
execution_updated INTEGER,
execution_version INTEGER,
-- Ensure unique combination of pipeline ID and number
UNIQUE (execution_pipeline_id, execution_number),
-- Foreign key to pipelines table
CONSTRAINT fk_execution_pipeline_id FOREIGN KEY (execution_pipeline_id)
REFERENCES pipelines (pipeline_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
);

View File

@ -0,0 +1 @@
DROP TABLE pipelines;

View File

@ -0,0 +1,30 @@
CREATE TABLE IF NOT EXISTS pipelines (
pipeline_id INTEGER PRIMARY KEY AUTOINCREMENT,
pipeline_description TEXT,
pipeline_parent_id INTEGER NOT NULL,
pipeline_uid TEXT NOT NULL,
pipeline_seq INTEGER NOT NULL DEFAULT 0,
pipeline_repo_id INTEGER,
pipeline_repo_type TEXT NOT NULL,
pipeline_repo_name TEXT,
pipeline_default_branch TEXT,
pipeline_config_path TEXT,
pipeline_created INTEGER,
pipeline_updated INTEGER,
pipeline_version INTEGER,
-- Ensure unique combination of UID and ParentID
UNIQUE (pipeline_parent_id, pipeline_uid),
-- Foreign key to spaces table
CONSTRAINT fk_pipeline_parent_id FOREIGN KEY (pipeline_parent_id)
REFERENCES spaces (space_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
-- Foreign key to repositories table
CONSTRAINT fk_pipeline_repo_id FOREIGN KEY (pipeline_repo_id)
REFERENCES repositories (repo_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
);

View File

@ -0,0 +1 @@
DROP TABLE executions;

View File

@ -0,0 +1,46 @@
CREATE TABLE IF NOT EXISTS executions (
execution_id INTEGER PRIMARY KEY AUTOINCREMENT,
execution_pipeline_id INTEGER NOT NULL,
execution_repo_id INTEGER,
execution_trigger TEXT,
execution_number INTEGER NOT NULL,
execution_parent INTEGER,
execution_status TEXT,
execution_error TEXT,
execution_event TEXT,
execution_action TEXT,
execution_link TEXT,
execution_timestamp INTEGER,
execution_title TEXT,
execution_message TEXT,
execution_before TEXT,
execution_after TEXT,
execution_ref TEXT,
execution_source_repo TEXT,
execution_source TEXT,
execution_target TEXT,
execution_author TEXT,
execution_author_name TEXT,
execution_author_email TEXT,
execution_author_avatar TEXT,
execution_sender TEXT,
execution_params TEXT,
execution_cron TEXT,
execution_deploy TEXT,
execution_deploy_id INTEGER,
execution_debug BOOLEAN NOT NULL DEFAULT 0,
execution_started INTEGER,
execution_finished INTEGER,
execution_created INTEGER,
execution_updated INTEGER,
execution_version INTEGER,
-- Ensure unique combination of pipeline ID and number
UNIQUE (execution_pipeline_id, execution_number),
-- Foreign key to pipelines table
CONSTRAINT fk_execution_pipeline_id FOREIGN KEY (execution_pipeline_id)
REFERENCES pipelines (pipeline_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
);

View File

@ -0,0 +1,228 @@
package database
import (
"context"
"fmt"
"strings"
"time"
"github.com/harness/gitness/internal/store"
gitness_store "github.com/harness/gitness/store"
"github.com/harness/gitness/store/database"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)
var _ store.PipelineStore = (*pipelineStore)(nil)
const (
pipelineQueryBase = `
SELECT
pipeline_id,
pipeline_description,
pipeline_parent_id,
pipeline_uid,
pipeline_seq,
pipeline_repo_id,
pipeline_repo_type,
pipeline_repo_name,
pipeline_default_branch,
pipeline_config_path,
pipeline_created,
pipeline_updated,
pipeline_version
FROM pipelines
`
pipelineColumns = `
pipeline_id,
pipeline_description,
pipeline_parent_id,
pipeline_uid,
pipeline_seq,
pipeline_repo_id,
pipeline_repo_type,
pipeline_repo_name,
pipeline_default_branch,
pipeline_config_path,
pipeline_created,
pipeline_updated,
pipeline_version
`
pipelineInsertStmt = `
INSERT INTO pipelines (
pipeline_description,
pipeline_parent_id,
pipeline_uid,
pipeline_seq,
pipeline_repo_id,
pipeline_repo_type,
pipeline_repo_name,
pipeline_default_branch,
pipeline_config_path,
pipeline_created,
pipeline_updated,
pipeline_version
) VALUES (
:pipeline_description,
:pipeline_parent_id,
:pipeline_uid,
:pipeline_seq,
:pipeline_repo_id,
:pipeline_repo_type,
:pipeline_repo_name,
:pipeline_default_branch,
:pipeline_config_path,
:pipeline_created,
:pipeline_updated,
:pipeline_version
) RETURNING pipeline_id`
pipelineUpdateStmt = `
UPDATE pipelines
SET
pipeline_description = :pipeline_description,
pipeline_parent_id = :pipeline_parent_id,
pipeline_uid = :pipeline_uid,
pipeline_seq = :pipeline_seq,
pipeline_repo_id = :pipeline_repo_id,
pipeline_repo_type = :pipeline_repo_type,
pipeline_repo_name = :pipeline_repo_name,
pipeline_default_branch = :pipeline_default_branch,
pipeline_config_path = :pipeline_config_path,
pipeline_created = :pipeline_created,
pipeline_updated = :pipeline_updated,
pipeline_version = :pipeline_version
WHERE pipeline_id = :pipeline_id AND pipeline_version = :pipeline_version - 1`
)
// NewPipelineStore returns a new PipelineStore.
func NewPipelineStore(db *sqlx.DB) *pipelineStore {
return &pipelineStore{
db: db,
}
}
type pipelineStore struct {
db *sqlx.DB
}
// Find returns a pipeline given a pipeline ID
func (s *pipelineStore) Find(ctx context.Context, id int64) (*types.Pipeline, error) {
const findQueryStmt = pipelineQueryBase + `
WHERE pipeline_id = $1`
db := dbtx.GetAccessor(ctx, s.db)
dst := new(types.Pipeline)
if err := db.GetContext(ctx, dst, findQueryStmt, id); err != nil {
return nil, database.ProcessSQLErrorf(err, "Failed to find pipeline")
}
return dst, nil
}
// FindByUID returns a pipeline in a given space with a given UID
func (s *pipelineStore) FindByUID(ctx context.Context, spaceID int64, uid string) (*types.Pipeline, error) {
const findQueryStmt = pipelineQueryBase + `
WHERE pipeline_parent_id = $1 AND pipeline_uid = $2`
db := dbtx.GetAccessor(ctx, s.db)
dst := new(types.Pipeline)
if err := db.GetContext(ctx, dst, findQueryStmt, spaceID, uid); err != nil {
return nil, database.ProcessSQLErrorf(err, "Failed to find pipeline")
}
return dst, nil
}
// Create creates a pipeline
func (s *pipelineStore) Create(ctx context.Context, pipeline *types.Pipeline) error {
db := dbtx.GetAccessor(ctx, s.db)
query, arg, err := db.BindNamed(pipelineInsertStmt, pipeline)
if err != nil {
return database.ProcessSQLErrorf(err, "Failed to bind pipeline object")
}
if err = db.QueryRowContext(ctx, query, arg...).Scan(&pipeline.ID); err != nil {
return database.ProcessSQLErrorf(err, "Pipeline query failed")
}
return nil
}
func (s *pipelineStore) Update(ctx context.Context, pipeline *types.Pipeline) (*types.Pipeline, error) {
updatedAt := time.Now()
pipeline.Version++
pipeline.Updated = updatedAt.UnixMilli()
db := dbtx.GetAccessor(ctx, s.db)
query, arg, err := db.BindNamed(pipelineUpdateStmt, pipeline)
if err != nil {
return nil, database.ProcessSQLErrorf(err, "Failed to bind pipeline object")
}
result, err := db.ExecContext(ctx, query, arg...)
if err != nil {
return nil, database.ProcessSQLErrorf(err, "Failed to update pipeline")
}
count, err := result.RowsAffected()
if err != nil {
return nil, database.ProcessSQLErrorf(err, "Failed to get number of updated rows")
}
if count == 0 {
return nil, gitness_store.ErrVersionConflict
}
return s.Find(ctx, pipeline.ID)
}
// List lists all the pipelines present in a space
func (s *pipelineStore) List(ctx context.Context, parentID int64, opts *types.PipelineFilter) ([]*types.Pipeline, error) {
stmt := database.Builder.
Select(pipelineColumns).
From("pipelines").
Where("pipeline_parent_id = ?", fmt.Sprint(parentID))
if opts.Query != "" {
stmt = stmt.Where("LOWER(pipeline_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(opts.Query)))
}
stmt = stmt.Limit(database.Limit(opts.Size))
stmt = stmt.Offset(database.Offset(opts.Page, opts.Size))
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, s.db)
dst := []*types.Pipeline{}
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(err, "Failed executing custom list query")
}
return dst, nil
}
// Delete deletes a pipeline given a pipeline ID
func (s *pipelineStore) Delete(ctx context.Context, id int64) error {
const pipelineDeleteStmt = `
DELETE FROM pipelines
WHERE pipeline_id = $1`
db := dbtx.GetAccessor(ctx, s.db)
if _, err := db.ExecContext(ctx, pipelineDeleteStmt, id); err != nil {
return database.ProcessSQLErrorf(err, "Could not delete pipeline")
}
return nil
}

View File

@ -23,6 +23,8 @@ var WireSet = wire.NewSet(
ProvidePathStore,
ProvideSpaceStore,
ProvideRepoStore,
ProvideExecutionStore,
ProvidePipelineStore,
ProvideRepoGitInfoView,
ProvideMembershipStore,
ProvideTokenStore,
@ -78,6 +80,16 @@ func ProvideRepoStore(db *sqlx.DB, pathCache store.PathCache) store.RepoStore {
return NewRepoStore(db, pathCache)
}
// ProvidePipelineStore provides a pipeline store.
func ProvidePipelineStore(db *sqlx.DB) store.PipelineStore {
return NewPipelineStore(db)
}
// ProvideExecutionStore provides a build store
func ProvideExecutionStore(db *sqlx.DB) store.ExecutionStore {
return NewExecutionStore(db)
}
// ProvideRepoGitInfoView provides a repo git UID view.
func ProvideRepoGitInfoView(db *sqlx.DB) store.RepoGitInfoView {
return NewRepoGitInfoView(db)

20
types/enum/scm.go Normal file
View File

@ -0,0 +1,20 @@
package enum
// ScmType defines the different types of principal types at harness.
type ScmType string
func (ScmType) Enum() []interface{} { return toInterfaceSlice(scmTypes) }
var scmTypes = ([]ScmType{
ScmTypeGitness,
ScmTypeGithub,
ScmTypeGitlab,
ScmTypeUnknown,
})
const (
ScmTypeUnknown ScmType = "UNKNOWN"
ScmTypeGitness ScmType = "GITNESS"
ScmTypeGithub ScmType = "GITHUB"
ScmTypeGitlab ScmType = "GITLAB"
)

42
types/execution.go Normal file
View File

@ -0,0 +1,42 @@
package types
// Execution represents an instance of a pipeline execution
type Execution struct {
ID int64 `db:"execution_id" json:"id"`
PipelineID int64 `db:"execution_pipeline_id" json:"pipeline_id"`
RepoID int64 `db:"execution_repo_id" json:"repo_id"`
Trigger string `db:"execution_trigger" json:"trigger"`
Number int64 `db:"execution_number" json:"number"`
Parent int64 `db:"execution_parent" json:"parent,omitempty"`
Status string `db:"execution_status" json:"status"`
Error string `db:"execution_error" json:"error,omitempty"`
Event string `db:"execution_event" json:"event"`
Action string `db:"execution_action" json:"action"`
Link string `db:"execution_link" json:"link"`
Timestamp int64 `db:"execution_timestamp" json:"timestamp"`
Title string `db:"execution_title" json:"title,omitempty"`
Message string `db:"execution_message" json:"message"`
Before string `db:"execution_before" json:"before"`
After string `db:"execution_after" json:"after"`
Ref string `db:"execution_ref" json:"ref"`
Fork string `db:"execution_source_repo" json:"source_repo"`
Source string `db:"execution_source" json:"source"`
Target string `db:"execution_target" json:"target"`
Author string `db:"execution_author" json:"author_login"`
AuthorName string `db:"execution_author_name" json:"author_name"`
AuthorEmail string `db:"execution_author_email" json:"author_email"`
AuthorAvatar string `db:"execution_author_avatar" json:"author_avatar"`
Sender string `db:"execution_sender" json:"sender"`
Params map[string]string `db:"execution_params" json:"params,omitempty"`
Cron string `db:"execution_cron" json:"cron,omitempty"`
Deploy string `db:"execution_deploy" json:"deploy_to,omitempty"`
DeployID int64 `db:"execution_deploy_id" json:"deploy_id,omitempty"`
Debug bool `db:"execution_debug" json:"debug,omitempty"`
Started int64 `db:"execution_started" json:"started"`
Finished int64 `db:"execution_finished" json:"finished"`
Created int64 `db:"execution_created" json:"created"`
Updated int64 `db:"execution_updated" json:"updated"`
Version int64 `db:"execution_version" json:"version"`
// TODO: (Vistaar) Add stages
// Stages []*Stage `db:"-" json:"stages,omitempty"`
}

27
types/pipeline.go Normal file
View File

@ -0,0 +1,27 @@
package types
import "github.com/harness/gitness/types/enum"
type Pipeline struct {
ID int64 `db:"pipeline_id" json:"id"`
Description string `db:"pipeline_description" json:"description"`
ParentID int64 `db:"pipeline_parent_id" json:"parent_id"` // ID of the parent space
UID string `db:"pipeline_uid" json:"uid"`
Seq int64 `db:"pipeline_seq" json:"seq"` // last execution number for this pipeline
RepoID int64 `db:"pipeline_repo_id" json:"repo_id"` // null if repo_type != gitness
RepoType enum.ScmType `db:"pipeline_repo_type" json:"repo_type"`
RepoName string `db:"pipeline_repo_name" json:"repo_name"`
DefaultBranch string `db:"pipeline_default_branch" json:"default_branch"`
ConfigPath string `db:"pipeline_config_path" json:"config_path"`
Created int64 `db:"pipeline_created" json:"created"`
Updated int64 `db:"pipeline_updated" json:"updated"`
Version int64 `db:"pipeline_version" json:"version"`
}
// RepoFilter stores repo query parameters.
type PipelineFilter struct {
Page int `json:"page"`
Size int `json:"size"`
Query string `json:"query"`
Order enum.Order `json:"order"`
}