From 8cdcecb56f3eb9cbab04ff02c865f3dfb272d46e Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Thu, 3 Aug 2023 15:31:32 +0100 Subject: [PATCH 01/26] add pipelines and executions handlers to gitness --- cmd/gitness/wire.go | 4 + cmd/gitness/wire_gen.go | 8 +- .../api/controller/execution/controller.go | 31 +++ internal/api/controller/execution/wire.go | 24 ++ .../api/controller/pipeline/controller.go | 39 +++ internal/api/controller/pipeline/create.go | 113 +++++++++ internal/api/controller/pipeline/delete.go | 34 +++ internal/api/controller/pipeline/find.go | 26 ++ internal/api/controller/pipeline/update.go | 50 ++++ internal/api/controller/pipeline/wire.go | 25 ++ internal/api/handler/pipeline/create.go | 33 +++ internal/api/handler/pipeline/delete.go | 40 +++ internal/api/handler/pipeline/find.go | 49 ++++ internal/api/handler/pipeline/update.go | 45 ++++ internal/api/openapi/openapi.go | 1 + internal/api/openapi/pipeline.go | 81 +++++++ internal/api/request/pipeline.go | 54 +++++ internal/router/api.go | 25 +- internal/router/wire.go | 6 +- internal/store/database.go | 81 +++++++ internal/store/database/execution.go | 59 +++++ .../sqlite/0020_create_table_builds.up.sql | 48 ++++ .../0020_create_table_pipelines.down.sql | 1 + .../sqlite/0020_create_table_pipelines.up.sql | 30 +++ .../sqlite/0021_create_table_builds.down.sql | 1 + .../0021_create_table_executions.up.sql | 46 ++++ internal/store/database/pipeline.go | 228 ++++++++++++++++++ internal/store/database/wire.go | 12 + types/enum/scm.go | 20 ++ types/execution.go | 42 ++++ types/pipeline.go | 27 +++ 31 files changed, 1280 insertions(+), 3 deletions(-) create mode 100644 internal/api/controller/execution/controller.go create mode 100644 internal/api/controller/execution/wire.go create mode 100644 internal/api/controller/pipeline/controller.go create mode 100644 internal/api/controller/pipeline/create.go create mode 100644 internal/api/controller/pipeline/delete.go create mode 100644 internal/api/controller/pipeline/find.go create mode 100644 internal/api/controller/pipeline/update.go create mode 100644 internal/api/controller/pipeline/wire.go create mode 100644 internal/api/handler/pipeline/create.go create mode 100644 internal/api/handler/pipeline/delete.go create mode 100644 internal/api/handler/pipeline/find.go create mode 100644 internal/api/handler/pipeline/update.go create mode 100644 internal/api/openapi/pipeline.go create mode 100644 internal/api/request/pipeline.go create mode 100644 internal/store/database/execution.go create mode 100644 internal/store/database/migrate/sqlite/0020_create_table_builds.up.sql create mode 100644 internal/store/database/migrate/sqlite/0020_create_table_pipelines.down.sql create mode 100644 internal/store/database/migrate/sqlite/0020_create_table_pipelines.up.sql create mode 100644 internal/store/database/migrate/sqlite/0021_create_table_builds.down.sql create mode 100644 internal/store/database/migrate/sqlite/0021_create_table_executions.up.sql create mode 100644 internal/store/database/pipeline.go create mode 100644 types/enum/scm.go create mode 100644 types/execution.go create mode 100644 types/pipeline.go diff --git a/cmd/gitness/wire.go b/cmd/gitness/wire.go index ddd1bd0aa..6e9c05fd4 100644 --- a/cmd/gitness/wire.go +++ b/cmd/gitness/wire.go @@ -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 } diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index ee8870a0f..55ad410dd 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -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) diff --git a/internal/api/controller/execution/controller.go b/internal/api/controller/execution/controller.go new file mode 100644 index 000000000..0fcdb96c8 --- /dev/null +++ b/internal/api/controller/execution/controller.go @@ -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, + } +} diff --git a/internal/api/controller/execution/wire.go b/internal/api/controller/execution/wire.go new file mode 100644 index 000000000..919579f5e --- /dev/null +++ b/internal/api/controller/execution/wire.go @@ -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) +} diff --git a/internal/api/controller/pipeline/controller.go b/internal/api/controller/pipeline/controller.go new file mode 100644 index 000000000..54bb1c3bd --- /dev/null +++ b/internal/api/controller/pipeline/controller.go @@ -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, + } +} diff --git a/internal/api/controller/pipeline/create.go b/internal/api/controller/pipeline/create.go new file mode 100644 index 000000000..f2f2f00e3 --- /dev/null +++ b/internal/api/controller/pipeline/create.go @@ -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 +} diff --git a/internal/api/controller/pipeline/delete.go b/internal/api/controller/pipeline/delete.go new file mode 100644 index 000000000..4dc12c239 --- /dev/null +++ b/internal/api/controller/pipeline/delete.go @@ -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 +} diff --git a/internal/api/controller/pipeline/find.go b/internal/api/controller/pipeline/find.go new file mode 100644 index 000000000..c880dbb8e --- /dev/null +++ b/internal/api/controller/pipeline/find.go @@ -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) +} diff --git a/internal/api/controller/pipeline/update.go b/internal/api/controller/pipeline/update.go new file mode 100644 index 000000000..4430ec26a --- /dev/null +++ b/internal/api/controller/pipeline/update.go @@ -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) +} diff --git a/internal/api/controller/pipeline/wire.go b/internal/api/controller/pipeline/wire.go new file mode 100644 index 000000000..b66ce5dcd --- /dev/null +++ b/internal/api/controller/pipeline/wire.go @@ -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) +} diff --git a/internal/api/handler/pipeline/create.go b/internal/api/handler/pipeline/create.go new file mode 100644 index 000000000..161ade494 --- /dev/null +++ b/internal/api/handler/pipeline/create.go @@ -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) + } +} diff --git a/internal/api/handler/pipeline/delete.go b/internal/api/handler/pipeline/delete.go new file mode 100644 index 000000000..02673b3d0 --- /dev/null +++ b/internal/api/handler/pipeline/delete.go @@ -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) + } +} diff --git a/internal/api/handler/pipeline/find.go b/internal/api/handler/pipeline/find.go new file mode 100644 index 000000000..aeb69e02b --- /dev/null +++ b/internal/api/handler/pipeline/find.go @@ -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 +} diff --git a/internal/api/handler/pipeline/update.go b/internal/api/handler/pipeline/update.go new file mode 100644 index 000000000..80842a335 --- /dev/null +++ b/internal/api/handler/pipeline/update.go @@ -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) + } +} diff --git a/internal/api/openapi/openapi.go b/internal/api/openapi/openapi.go index 2e55f2e1d..5110b9377 100644 --- a/internal/api/openapi/openapi.go +++ b/internal/api/openapi/openapi.go @@ -41,6 +41,7 @@ func Generate() *openapi3.Spec { buildPrincipals(&reflector) spaceOperations(&reflector) repoOperations(&reflector) + pipelineOperations(&reflector) resourceOperations(&reflector) pullReqOperations(&reflector) webhookOperations(&reflector) diff --git a/internal/api/openapi/pipeline.go b/internal/api/openapi/pipeline.go new file mode 100644 index 000000000..61b7c9fb4 --- /dev/null +++ b/internal/api/openapi/pipeline.go @@ -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) +} diff --git a/internal/api/request/pipeline.go b/internal/api/request/pipeline.go new file mode 100644 index 000000000..fea17c598 --- /dev/null +++ b/internal/api/request/pipeline.go @@ -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), +// } +// } diff --git a/internal/router/api.go b/internal/router/api.go index 35dc4238d..80546dc8a 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -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) diff --git a/internal/router/wire.go b/internal/router/wire.go index 3118ca790..bd834b12e 100644 --- a/internal/router/wire.go +++ b/internal/router/wire.go @@ -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) } diff --git a/internal/store/database.go b/internal/store/database.go index 5dc84cf30..ab9c7ae0e 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -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) + } ) diff --git a/internal/store/database/execution.go b/internal/store/database/execution.go new file mode 100644 index 000000000..b851fb6c5 --- /dev/null +++ b/internal/store/database/execution.go @@ -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 + ` +) diff --git a/internal/store/database/migrate/sqlite/0020_create_table_builds.up.sql b/internal/store/database/migrate/sqlite/0020_create_table_builds.up.sql new file mode 100644 index 000000000..52bb5c590 --- /dev/null +++ b/internal/store/database/migrate/sqlite/0020_create_table_builds.up.sql @@ -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 +); \ No newline at end of file diff --git a/internal/store/database/migrate/sqlite/0020_create_table_pipelines.down.sql b/internal/store/database/migrate/sqlite/0020_create_table_pipelines.down.sql new file mode 100644 index 000000000..acbdf9bdb --- /dev/null +++ b/internal/store/database/migrate/sqlite/0020_create_table_pipelines.down.sql @@ -0,0 +1 @@ +DROP TABLE pipelines; \ No newline at end of file diff --git a/internal/store/database/migrate/sqlite/0020_create_table_pipelines.up.sql b/internal/store/database/migrate/sqlite/0020_create_table_pipelines.up.sql new file mode 100644 index 000000000..d5324e18a --- /dev/null +++ b/internal/store/database/migrate/sqlite/0020_create_table_pipelines.up.sql @@ -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 +); \ No newline at end of file diff --git a/internal/store/database/migrate/sqlite/0021_create_table_builds.down.sql b/internal/store/database/migrate/sqlite/0021_create_table_builds.down.sql new file mode 100644 index 000000000..cc476de4a --- /dev/null +++ b/internal/store/database/migrate/sqlite/0021_create_table_builds.down.sql @@ -0,0 +1 @@ +DROP TABLE executions; \ No newline at end of file diff --git a/internal/store/database/migrate/sqlite/0021_create_table_executions.up.sql b/internal/store/database/migrate/sqlite/0021_create_table_executions.up.sql new file mode 100644 index 000000000..466b193d3 --- /dev/null +++ b/internal/store/database/migrate/sqlite/0021_create_table_executions.up.sql @@ -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 +); \ No newline at end of file diff --git a/internal/store/database/pipeline.go b/internal/store/database/pipeline.go new file mode 100644 index 000000000..c6a942b2d --- /dev/null +++ b/internal/store/database/pipeline.go @@ -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 +} diff --git a/internal/store/database/wire.go b/internal/store/database/wire.go index ff6a66870..6863ddbea 100644 --- a/internal/store/database/wire.go +++ b/internal/store/database/wire.go @@ -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) diff --git a/types/enum/scm.go b/types/enum/scm.go new file mode 100644 index 000000000..2b28b8978 --- /dev/null +++ b/types/enum/scm.go @@ -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" +) diff --git a/types/execution.go b/types/execution.go new file mode 100644 index 000000000..c9b4aee95 --- /dev/null +++ b/types/execution.go @@ -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"` +} diff --git a/types/pipeline.go b/types/pipeline.go new file mode 100644 index 000000000..56ea321bb --- /dev/null +++ b/types/pipeline.go @@ -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"` +} From f04a695732027efc56c9b71db3babbbbfe225901 Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Fri, 4 Aug 2023 10:32:59 +0100 Subject: [PATCH 02/26] add copyright header to all files --- internal/api/controller/execution/controller.go | 4 ++++ internal/api/controller/execution/wire.go | 4 ++++ internal/api/controller/pipeline/controller.go | 4 ++++ internal/api/controller/pipeline/create.go | 4 ++++ internal/api/controller/pipeline/wire.go | 4 ++++ internal/api/handler/pipeline/create.go | 4 ++++ internal/api/handler/pipeline/find.go | 4 ++++ internal/api/handler/pipeline/update.go | 4 ++++ internal/api/openapi/pipeline.go | 2 +- internal/router/api.go | 8 +++++++- internal/store/database/execution.go | 4 ++++ internal/store/database/pipeline.go | 4 ++++ types/execution.go | 4 ++++ types/pipeline.go | 4 ++++ 14 files changed, 56 insertions(+), 2 deletions(-) diff --git a/internal/api/controller/execution/controller.go b/internal/api/controller/execution/controller.go index 0fcdb96c8..540557c48 100644 --- a/internal/api/controller/execution/controller.go +++ b/internal/api/controller/execution/controller.go @@ -1,3 +1,7 @@ +// 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 execution import ( diff --git a/internal/api/controller/execution/wire.go b/internal/api/controller/execution/wire.go index 919579f5e..6021a841e 100644 --- a/internal/api/controller/execution/wire.go +++ b/internal/api/controller/execution/wire.go @@ -1,3 +1,7 @@ +// 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 execution import ( diff --git a/internal/api/controller/pipeline/controller.go b/internal/api/controller/pipeline/controller.go index 54bb1c3bd..5c7f4ae44 100644 --- a/internal/api/controller/pipeline/controller.go +++ b/internal/api/controller/pipeline/controller.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/internal/api/controller/pipeline/create.go b/internal/api/controller/pipeline/create.go index f2f2f00e3..495742ea8 100644 --- a/internal/api/controller/pipeline/create.go +++ b/internal/api/controller/pipeline/create.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/internal/api/controller/pipeline/wire.go b/internal/api/controller/pipeline/wire.go index b66ce5dcd..9702ebdd3 100644 --- a/internal/api/controller/pipeline/wire.go +++ b/internal/api/controller/pipeline/wire.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/internal/api/handler/pipeline/create.go b/internal/api/handler/pipeline/create.go index 161ade494..ac1641f93 100644 --- a/internal/api/handler/pipeline/create.go +++ b/internal/api/handler/pipeline/create.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/internal/api/handler/pipeline/find.go b/internal/api/handler/pipeline/find.go index aeb69e02b..4be4b456b 100644 --- a/internal/api/handler/pipeline/find.go +++ b/internal/api/handler/pipeline/find.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/internal/api/handler/pipeline/update.go b/internal/api/handler/pipeline/update.go index 80842a335..42d402879 100644 --- a/internal/api/handler/pipeline/update.go +++ b/internal/api/handler/pipeline/update.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/internal/api/openapi/pipeline.go b/internal/api/openapi/pipeline.go index 61b7c9fb4..f7a63e8a6 100644 --- a/internal/api/openapi/pipeline.go +++ b/internal/api/openapi/pipeline.go @@ -1,6 +1,6 @@ // 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. +// that can be found in the LICENSE.md file for this repository. package openapi diff --git a/internal/router/api.go b/internal/router/api.go index 80546dc8a..e88d79777 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -284,11 +284,17 @@ func setupPipelines(r chi.Router, pipelineCtrl *pipeline.Controller, executionCt r.Patch("/", handlerpipeline.HandleUpdate(pipelineCtrl)) r.Delete("/", handlerpipeline.HandleDelete(pipelineCtrl)) // TODO: setup executions here - // SetupExecutions(r, executionCtrl) + setupExecutions(r, executionCtrl) }) }) } +func setupExecutions(r chi.Router, executionCtrl *execution.Controller) { + r.Route("/executions", func(r chi.Router) { + r.Post("/") + }) +} + func setupInternal(r chi.Router, githookCtrl *controllergithook.Controller) { r.Route("/internal", func(r chi.Router) { SetupGitHooks(r, githookCtrl) diff --git a/internal/store/database/execution.go b/internal/store/database/execution.go index b851fb6c5..089559459 100644 --- a/internal/store/database/execution.go +++ b/internal/store/database/execution.go @@ -1,3 +1,7 @@ +// 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 database import ( diff --git a/internal/store/database/pipeline.go b/internal/store/database/pipeline.go index c6a942b2d..7b0806e73 100644 --- a/internal/store/database/pipeline.go +++ b/internal/store/database/pipeline.go @@ -1,3 +1,7 @@ +// 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 database import ( diff --git a/types/execution.go b/types/execution.go index c9b4aee95..4128bfe40 100644 --- a/types/execution.go +++ b/types/execution.go @@ -1,3 +1,7 @@ +// 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 types // Execution represents an instance of a pipeline execution diff --git a/types/pipeline.go b/types/pipeline.go index 56ea321bb..06d1311b2 100644 --- a/types/pipeline.go +++ b/types/pipeline.go @@ -1,3 +1,7 @@ +// 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 types import "github.com/harness/gitness/types/enum" From 09d2fee06ebbd934e5c51f0ef5d9d6ce2ce5233a Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Fri, 4 Aug 2023 10:43:44 +0100 Subject: [PATCH 03/26] move db migrations to different folder --- internal/router/api.go | 3 +- .../ci_migrations.sql} | 31 +++++++++++++++++++ .../sqlite/0020_create_table_pipelines.up.sql | 30 ------------------ 3 files changed, 32 insertions(+), 32 deletions(-) rename internal/store/database/migrate/{sqlite/0021_create_table_executions.up.sql => ci/ci_migrations.sql} (58%) delete mode 100644 internal/store/database/migrate/sqlite/0020_create_table_pipelines.up.sql diff --git a/internal/router/api.go b/internal/router/api.go index e88d79777..41e455ab9 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -283,7 +283,6 @@ func setupPipelines(r chi.Router, pipelineCtrl *pipeline.Controller, executionCt r.Get("/", handlerpipeline.HandleFind(pipelineCtrl)) r.Patch("/", handlerpipeline.HandleUpdate(pipelineCtrl)) r.Delete("/", handlerpipeline.HandleDelete(pipelineCtrl)) - // TODO: setup executions here setupExecutions(r, executionCtrl) }) }) @@ -291,7 +290,7 @@ func setupPipelines(r chi.Router, pipelineCtrl *pipeline.Controller, executionCt func setupExecutions(r chi.Router, executionCtrl *execution.Controller) { r.Route("/executions", func(r chi.Router) { - r.Post("/") + }) } diff --git a/internal/store/database/migrate/sqlite/0021_create_table_executions.up.sql b/internal/store/database/migrate/ci/ci_migrations.sql similarity index 58% rename from internal/store/database/migrate/sqlite/0021_create_table_executions.up.sql rename to internal/store/database/migrate/ci/ci_migrations.sql index 466b193d3..a46c210f7 100644 --- a/internal/store/database/migrate/sqlite/0021_create_table_executions.up.sql +++ b/internal/store/database/migrate/ci/ci_migrations.sql @@ -1,3 +1,34 @@ +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 +); + CREATE TABLE IF NOT EXISTS executions ( execution_id INTEGER PRIMARY KEY AUTOINCREMENT, execution_pipeline_id INTEGER NOT NULL, diff --git a/internal/store/database/migrate/sqlite/0020_create_table_pipelines.up.sql b/internal/store/database/migrate/sqlite/0020_create_table_pipelines.up.sql deleted file mode 100644 index d5324e18a..000000000 --- a/internal/store/database/migrate/sqlite/0020_create_table_pipelines.up.sql +++ /dev/null @@ -1,30 +0,0 @@ -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 -); \ No newline at end of file From bf5a8f6ba8993a521a65f9541c19c039196c9445 Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Fri, 4 Aug 2023 15:52:46 +0100 Subject: [PATCH 04/26] remove migrations in migrate/ folder --- .../sqlite/0020_create_table_builds.up.sql | 48 ------------------- .../0020_create_table_pipelines.down.sql | 1 - .../sqlite/0021_create_table_builds.down.sql | 1 - 3 files changed, 50 deletions(-) delete mode 100644 internal/store/database/migrate/sqlite/0020_create_table_builds.up.sql delete mode 100644 internal/store/database/migrate/sqlite/0020_create_table_pipelines.down.sql delete mode 100644 internal/store/database/migrate/sqlite/0021_create_table_builds.down.sql diff --git a/internal/store/database/migrate/sqlite/0020_create_table_builds.up.sql b/internal/store/database/migrate/sqlite/0020_create_table_builds.up.sql deleted file mode 100644 index 52bb5c590..000000000 --- a/internal/store/database/migrate/sqlite/0020_create_table_builds.up.sql +++ /dev/null @@ -1,48 +0,0 @@ -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 -); \ No newline at end of file diff --git a/internal/store/database/migrate/sqlite/0020_create_table_pipelines.down.sql b/internal/store/database/migrate/sqlite/0020_create_table_pipelines.down.sql deleted file mode 100644 index acbdf9bdb..000000000 --- a/internal/store/database/migrate/sqlite/0020_create_table_pipelines.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE pipelines; \ No newline at end of file diff --git a/internal/store/database/migrate/sqlite/0021_create_table_builds.down.sql b/internal/store/database/migrate/sqlite/0021_create_table_builds.down.sql deleted file mode 100644 index cc476de4a..000000000 --- a/internal/store/database/migrate/sqlite/0021_create_table_builds.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE executions; \ No newline at end of file From c9eb9003cf0edbdd4f074b275137377e563dabac Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Fri, 4 Aug 2023 16:01:38 +0100 Subject: [PATCH 05/26] address comments --- internal/api/controller/pipeline/delete.go | 6 +----- internal/api/controller/pipeline/find.go | 2 +- internal/api/controller/pipeline/update.go | 10 +++++++--- internal/api/handler/pipeline/find.go | 2 +- internal/store/database.go | 5 ++++- internal/store/database/pipeline.go | 19 +++++++++++++++++-- 6 files changed, 31 insertions(+), 13 deletions(-) diff --git a/internal/api/controller/pipeline/delete.go b/internal/api/controller/pipeline/delete.go index 4dc12c239..29f93935c 100644 --- a/internal/api/controller/pipeline/delete.go +++ b/internal/api/controller/pipeline/delete.go @@ -22,11 +22,7 @@ func (c *Controller) Delete(ctx context.Context, session *auth.Session, spaceRef // 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) + err = c.pipelineStore.DeleteByUID(ctx, space.ID, uid) if err != nil { return fmt.Errorf("could not delete pipeline: %w", err) } diff --git a/internal/api/controller/pipeline/find.go b/internal/api/controller/pipeline/find.go index c880dbb8e..ad8604f58 100644 --- a/internal/api/controller/pipeline/find.go +++ b/internal/api/controller/pipeline/find.go @@ -11,7 +11,7 @@ import ( "github.com/harness/gitness/types" ) -// Find finds a repo. +// Find finds a pipeline. 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 { diff --git a/internal/api/controller/pipeline/update.go b/internal/api/controller/pipeline/update.go index 4430ec26a..d2f4b9453 100644 --- a/internal/api/controller/pipeline/update.go +++ b/internal/api/controller/pipeline/update.go @@ -18,9 +18,13 @@ type UpdateInput struct { 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) { +// Update updates a pipeline. +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 diff --git a/internal/api/handler/pipeline/find.go b/internal/api/handler/pipeline/find.go index 4be4b456b..11850b31c 100644 --- a/internal/api/handler/pipeline/find.go +++ b/internal/api/handler/pipeline/find.go @@ -14,7 +14,7 @@ import ( "github.com/harness/gitness/internal/api/request" ) -// HandleFind writes json-encoded repository information to the http response body. +// HandleFind finds a pipeline from the database. func HandleFind(pipelineCtrl *pipeline.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() diff --git a/internal/store/database.go b/internal/store/database.go index ab9c7ae0e..a62f38ea4 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -453,10 +453,13 @@ type ( 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) + List(context.Context, int64, *types.PipelineFilter) ([]types.Pipeline, error) // Delete deletes a pipeline ID from the datastore. Delete(context.Context, int64) error + + // DeleteByUID deletes a pipeline with a given UID in a space + DeleteByUID(context.Context, int64, string) error } // TODO: Implement the execution store interface diff --git a/internal/store/database/pipeline.go b/internal/store/database/pipeline.go index 7b0806e73..b22650309 100644 --- a/internal/store/database/pipeline.go +++ b/internal/store/database/pipeline.go @@ -188,7 +188,7 @@ func (s *pipelineStore) Update(ctx context.Context, pipeline *types.Pipeline) (* } // List lists all the pipelines present in a space -func (s *pipelineStore) List(ctx context.Context, parentID int64, opts *types.PipelineFilter) ([]*types.Pipeline, error) { +func (s *pipelineStore) List(ctx context.Context, parentID int64, opts *types.PipelineFilter) ([]types.Pipeline, error) { stmt := database.Builder. Select(pipelineColumns). From("pipelines"). @@ -208,7 +208,7 @@ func (s *pipelineStore) List(ctx context.Context, parentID int64, opts *types.Pi db := dbtx.GetAccessor(ctx, s.db) - dst := []*types.Pipeline{} + dst := []types.Pipeline{} if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { return nil, database.ProcessSQLErrorf(err, "Failed executing custom list query") } @@ -230,3 +230,18 @@ func (s *pipelineStore) Delete(ctx context.Context, id int64) error { return nil } + +// DeleteByUID deletes a pipeline with a given UID in a space +func (s *pipelineStore) DeleteByUID(ctx context.Context, spaceID int64, uid string) error { + const pipelineDeleteStmt = ` + DELETE FROM pipelines + WHERE pipeline_parent_id = $1 AND pipeline_uid = $1` + + db := dbtx.GetAccessor(ctx, s.db) + + if _, err := db.ExecContext(ctx, pipelineDeleteStmt, spaceID, uid); err != nil { + return database.ProcessSQLErrorf(err, "Could not delete pipeline") + } + + return nil +} From 21d666b4307fa7875baebe26eb30c8ab2ec5d3de Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Fri, 4 Aug 2023 16:08:12 +0100 Subject: [PATCH 06/26] add list API --- types/enum/scm.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/types/enum/scm.go b/types/enum/scm.go index 2b28b8978..88e1efdcb 100644 --- a/types/enum/scm.go +++ b/types/enum/scm.go @@ -1,6 +1,10 @@ +// 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 enum -// ScmType defines the different types of principal types at harness. +// ScmType defines the different SCM types supported for CI. type ScmType string func (ScmType) Enum() []interface{} { return toInterfaceSlice(scmTypes) } From 4390376acdf01d153ef2c61aa3727ae698de1791 Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Fri, 4 Aug 2023 16:44:51 +0100 Subject: [PATCH 07/26] add list, fix delete --- cmd/gitness/wire_gen.go | 2 +- internal/api/controller/space/controller.go | 4 +- .../api/controller/space/list_pipelines.go | 31 +++++++++++++ internal/api/controller/space/wire.go | 4 +- internal/api/handler/space/list_pipelines.go | 43 +++++++++++++++++++ internal/api/openapi/space.go | 13 ++++++ internal/router/api.go | 1 + internal/store/database/pipeline.go | 2 +- 8 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 internal/api/controller/space/list_pipelines.go create mode 100644 internal/api/handler/space/list_pipelines.go diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index 55ad410dd..3d536978d 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -88,8 +88,8 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro 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) + spaceController := space.ProvideController(db, provider, pathUID, authorizer, pathStore, pipelineStore, spaceStore, repoStore, principalStore, repoController, membershipStore) pipelineController := pipeline.ProvideController(db, pathUID, pathStore, repoStore, authorizer, pipelineStore, spaceStore) pullReqStore := database.ProvidePullReqStore(db, principalInfoCache) pullReqActivityStore := database.ProvidePullReqActivityStore(db, principalInfoCache) diff --git a/internal/api/controller/space/controller.go b/internal/api/controller/space/controller.go index 25f1b626f..5e7be791b 100644 --- a/internal/api/controller/space/controller.go +++ b/internal/api/controller/space/controller.go @@ -20,6 +20,7 @@ type Controller struct { uidCheck check.PathUID authorizer authz.Authorizer pathStore store.PathStore + pipelineStore store.PipelineStore spaceStore store.SpaceStore repoStore store.RepoStore principalStore store.PrincipalStore @@ -29,7 +30,7 @@ type Controller struct { func NewController(db *sqlx.DB, urlProvider *url.Provider, uidCheck check.PathUID, authorizer authz.Authorizer, - pathStore store.PathStore, spaceStore store.SpaceStore, + pathStore store.PathStore, pipelineStore store.PipelineStore, spaceStore store.SpaceStore, repoStore store.RepoStore, principalStore store.PrincipalStore, repoCtrl *repo.Controller, membershipStore store.MembershipStore, ) *Controller { @@ -39,6 +40,7 @@ func NewController(db *sqlx.DB, urlProvider *url.Provider, uidCheck: uidCheck, authorizer: authorizer, pathStore: pathStore, + pipelineStore: pipelineStore, spaceStore: spaceStore, repoStore: repoStore, principalStore: principalStore, diff --git a/internal/api/controller/space/list_pipelines.go b/internal/api/controller/space/list_pipelines.go new file mode 100644 index 000000000..b2af52a7a --- /dev/null +++ b/internal/api/controller/space/list_pipelines.go @@ -0,0 +1,31 @@ +// 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 space + +import ( + "context" + + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" +) + +// ListRepositories lists the repositories of a space. +// TODO: move to different file +func (c *Controller) ListPipelines(ctx context.Context, session *auth.Session, + spaceRef string, filter *types.PipelineFilter) ([]types.Pipeline, int, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, 0, err + } + + // TODO: Add auth + // if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionRepoView, true); err != nil { + // return nil, 0, err + // } + pipelines, err := c.pipelineStore.List(ctx, space.ID, filter) + if err != nil { + return nil, 0, err + } + return pipelines, len(pipelines), nil +} diff --git a/internal/api/controller/space/wire.go b/internal/api/controller/space/wire.go index d8b9cbbba..9162675bb 100644 --- a/internal/api/controller/space/wire.go +++ b/internal/api/controller/space/wire.go @@ -21,12 +21,12 @@ var WireSet = wire.NewSet( ) func ProvideController(db *sqlx.DB, urlProvider *url.Provider, uidCheck check.PathUID, authorizer authz.Authorizer, - pathStore store.PathStore, spaceStore store.SpaceStore, repoStore store.RepoStore, + pathStore store.PathStore, pipelineStore store.PipelineStore, spaceStore store.SpaceStore, repoStore store.RepoStore, principalStore store.PrincipalStore, repoCtrl *repo.Controller, membershipStore store.MembershipStore, ) *Controller { return NewController(db, urlProvider, uidCheck, authorizer, - pathStore, spaceStore, repoStore, + pathStore, pipelineStore, spaceStore, repoStore, principalStore, repoCtrl, membershipStore) } diff --git a/internal/api/handler/space/list_pipelines.go b/internal/api/handler/space/list_pipelines.go new file mode 100644 index 000000000..633c8f9d2 --- /dev/null +++ b/internal/api/handler/space/list_pipelines.go @@ -0,0 +1,43 @@ +// 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 space + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/space" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// HandleListRepos writes json-encoded list of repos in the request body. +func HandleListPipelines(spaceCtrl *space.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + spaceRef, err := request.GetSpaceRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + // TODO: Use pipeline filter + filter := request.ParseRepoFilter(r) + if filter.Order == enum.OrderDefault { + filter.Order = enum.OrderAsc + } + + repos, totalCount, err := spaceCtrl.ListPipelines(ctx, session, spaceRef, &types.PipelineFilter{}) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.Pagination(r, w, filter.Page, filter.Size, int(totalCount)) + render.JSON(w, http.StatusOK, repos) + } +} diff --git a/internal/api/openapi/space.go b/internal/api/openapi/space.go index 04569762c..e7509c12b 100644 --- a/internal/api/openapi/space.go +++ b/internal/api/openapi/space.go @@ -230,6 +230,19 @@ func spaceOperations(reflector *openapi3.Reflector) { _ = reflector.SetJSONResponse(&opRepos, new(usererror.Error), http.StatusNotFound) _ = reflector.Spec.AddOperation(http.MethodGet, "/spaces/{space_ref}/repos", opRepos) + opPipelines := openapi3.Operation{} + opPipelines.WithTags("space") + opPipelines.WithMapOfAnything(map[string]interface{}{"operationId": "listPipelines"}) + opPipelines.WithParameters(queryParameterQueryRepo, queryParameterSortRepo, queryParameterOrder, + queryParameterPage, queryParameterLimit) + _ = reflector.SetRequest(&opPipelines, new(spaceRequest), http.MethodGet) + _ = reflector.SetJSONResponse(&opPipelines, []types.Pipeline{}, http.StatusOK) + _ = reflector.SetJSONResponse(&opPipelines, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opPipelines, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opPipelines, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&opPipelines, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodGet, "/spaces/{space_ref}/pipelines", opPipelines) + opServiceAccounts := openapi3.Operation{} opServiceAccounts.WithTags("space") opServiceAccounts.WithMapOfAnything(map[string]interface{}{"operationId": "listServiceAccounts"}) diff --git a/internal/router/api.go b/internal/router/api.go index 41e455ab9..b3031ff6b 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -160,6 +160,7 @@ func setupSpaces(r chi.Router, spaceCtrl *space.Controller) { r.Get("/spaces", handlerspace.HandleListSpaces(spaceCtrl)) r.Get("/repos", handlerspace.HandleListRepos(spaceCtrl)) r.Get("/service-accounts", handlerspace.HandleListServiceAccounts(spaceCtrl)) + r.Get("/pipelines", handlerspace.HandleListPipelines(spaceCtrl)) // Child collections r.Route("/paths", func(r chi.Router) { diff --git a/internal/store/database/pipeline.go b/internal/store/database/pipeline.go index b22650309..07afe55f7 100644 --- a/internal/store/database/pipeline.go +++ b/internal/store/database/pipeline.go @@ -235,7 +235,7 @@ func (s *pipelineStore) Delete(ctx context.Context, id int64) error { func (s *pipelineStore) DeleteByUID(ctx context.Context, spaceID int64, uid string) error { const pipelineDeleteStmt = ` DELETE FROM pipelines - WHERE pipeline_parent_id = $1 AND pipeline_uid = $1` + WHERE pipeline_parent_id = $1 AND pipeline_uid = $2` db := dbtx.GetAccessor(ctx, s.db) From 2c5c781a3cb77f2afc7e01187c4c34016443f8e4 Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Sun, 6 Aug 2023 18:13:42 +0100 Subject: [PATCH 08/26] Add execution db interface --- internal/api/handler/pipeline/find.go | 2 + internal/store/database.go | 16 +- internal/store/database/execution.go | 335 ++++++++++++++++-- .../database/migrate/ci/ci_migrations.sql | 2 +- types/pipeline.go | 11 +- 5 files changed, 327 insertions(+), 39 deletions(-) diff --git a/internal/api/handler/pipeline/find.go b/internal/api/handler/pipeline/find.go index 11850b31c..65091e0fb 100644 --- a/internal/api/handler/pipeline/find.go +++ b/internal/api/handler/pipeline/find.go @@ -39,6 +39,8 @@ func HandleFind(pipelineCtrl *pipeline.Controller) http.HandlerFunc { } } +// SplitRef splits apart a ref into two parts, otherwise returns an error +// For example: path/to/space/uid will get split into path/to/space and uid func SplitRef(ref string) (string, string, error) { lastIndex := strings.LastIndex(ref, "/") if lastIndex == -1 { diff --git a/internal/store/database.go b/internal/store/database.go index a62f38ea4..bda11bb14 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -462,8 +462,22 @@ type ( DeleteByUID(context.Context, int64, string) error } - // TODO: Implement the execution store interface ExecutionStore interface { + // Find returns a execution given a pipeline and an execution number + Find(context.Context, int64, int64) (*types.Execution, error) + + // Create creates a new execution in the datastore. + Create(context.Context, *types.Execution) error + + // Update tries to update an execution in the datastore with optimistic locking. + Update(context.Context, *types.Execution) (*types.Execution, error) + + // List lists the executions for a given pipeline ID + List(context.Context, int64, *types.ExecutionFilter) ([]types.Execution, error) + + // Delete deletes an execution given a pipeline ID and an execution number + Delete(context.Context, int64, int64) error + // Find returns a build from the datastore. // Find(context.Context, int64) (*types.Execution, error) diff --git a/internal/store/database/execution.go b/internal/store/database/execution.go index 089559459..ca54710a1 100644 --- a/internal/store/database/execution.go +++ b/internal/store/database/execution.go @@ -5,8 +5,17 @@ package database import ( + "context" + "fmt" + "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.ExecutionStore = (*executionStore)(nil) @@ -23,41 +32,295 @@ type executionStore struct { } 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 + executionQueryBase = ` + SELECT + execution_id, + execution_pipeline_id, + 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 + FROM executions ` + + executionColumns = ` + execution_id, + execution_pipeline_id, + 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 + ` + + executionInsterStmt = ` + INSERT INTO executions ( + execution_id, + execution_pipeline_id, + 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 + ) VALUES ( + :execution_id, + :execution_pipeline_id, + :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 + ) RETURNING execution_id` + + executionUpdateStmt = ` + UPDATE executions + SET + execution_pipeline_id = :execution_pipeline_id, + execution_repo_id = :execution_repo_id, + execution_trigger = :execution_trigger, + execution_number = :execution_number, + execution_parent = :execution_parent, + execution_status = :execution_status, + execution_error = :execution_error, + execution_event = :execution_event, + execution_action = :execution_action, + execution_link = :execution_link, + execution_timestamp = :execution_timestamp, + execution_title = :execution_title, + execution_message = :execution_message, + execution_before = :execution_before, + execution_after = :execution_after, + execution_ref = :execution_ref, + execution_source_repo = :execution_source_repo, + execution_source = :execution_source, + execution_target = :execution_target, + execution_author = :execution_author, + execution_author_name = :execution_author_name, + execution_author_email = :execution_author_email, + execution_author_avatar = :execution_author_avatar, + execution_sender = :execution_sender, + execution_params = :execution_params, + execution_cron = :execution_cron, + execution_deploy = :execution_deploy, + execution_deploy_id = :execution_deploy_id, + execution_debug = :execution_debug, + execution_started = :execution_started, + execution_finished = :execution_finished, + execution_created = :execution_created, + execution_updated = :execution_updated, + execution_version = :execution_version + WHERE execution_id = :execution_id AND execution_version = :execution_version - 1` ) + +// Find returns an execution given a pipeline ID and an execution number +func (s *executionStore) Find(ctx context.Context, parentID int64, n int64) (*types.Execution, error) { + const findQueryStmt = executionQueryBase + ` + WHERE execution_pipeline_id = $1 AND execution_number = $2` + db := dbtx.GetAccessor(ctx, s.db) + + dst := new(types.Execution) + if err := db.GetContext(ctx, dst, findQueryStmt, parentID, n); err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed to find execution") + } + return dst, nil +} + +// Create creates a new execution in the datastore. +func (s *executionStore) Create(ctx context.Context, execution *types.Execution) error { + db := dbtx.GetAccessor(ctx, s.db) + + query, arg, err := db.BindNamed(executionInsterStmt, execution) + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to bind execution object") + } + + if err = db.QueryRowContext(ctx, query, arg...).Scan(&execution.ID); err != nil { + return database.ProcessSQLErrorf(err, "Execution query failed") + } + + return nil +} + +// Update tries to update an execution in the datastore with optimistic locking. +func (s *executionStore) Update(ctx context.Context, execution *types.Execution) (*types.Execution, error) { + updatedAt := time.Now() + + execution.Version++ + execution.Updated = updatedAt.UnixMilli() + + db := dbtx.GetAccessor(ctx, s.db) + + query, arg, err := db.BindNamed(executionUpdateStmt, execution) + if err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed to bind execution object") + } + + result, err := db.ExecContext(ctx, query, arg...) + if err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed to update execution") + } + + 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, execution.Parent, execution.Number) +} + +// List lists the executions for a given pipeline ID +func (s *executionStore) List(ctx context.Context, pipelineID int64, opts *types.ExecutionFilter) ([]types.Execution, error) { + stmt := database.Builder. + Select(executionColumns). + From("executions"). + Where("execution_pipeline_id = ?", fmt.Sprint(pipelineID)) + + 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.Execution{} + 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 an execution given a pipeline ID and an execution number +func (s *executionStore) Delete(ctx context.Context, pipelineID int64, n int64) error { + const executionDeleteStmt = ` + DELETE FROM executions + WHERE execution_pipeline_id = $1 AND execution_number = $2` + + db := dbtx.GetAccessor(ctx, s.db) + + if _, err := db.ExecContext(ctx, executionDeleteStmt, pipelineID, n); err != nil { + return database.ProcessSQLErrorf(err, "Could not delete execution") + } + + return nil +} diff --git a/internal/store/database/migrate/ci/ci_migrations.sql b/internal/store/database/migrate/ci/ci_migrations.sql index a46c210f7..1cd33a689 100644 --- a/internal/store/database/migrate/ci/ci_migrations.sql +++ b/internal/store/database/migrate/ci/ci_migrations.sql @@ -8,7 +8,7 @@ CREATE TABLE IF NOT EXISTS pipelines ( pipeline_repo_type TEXT NOT NULL, pipeline_repo_name TEXT, pipeline_default_branch TEXT, - pipeline_config_path TEXT, + pipeline_config_path TEXT NOT NULL, pipeline_created INTEGER, pipeline_updated INTEGER, pipeline_version INTEGER, diff --git a/types/pipeline.go b/types/pipeline.go index 06d1311b2..ce9cb4576 100644 --- a/types/pipeline.go +++ b/types/pipeline.go @@ -22,10 +22,19 @@ type Pipeline struct { Version int64 `db:"pipeline_version" json:"version"` } -// RepoFilter stores repo query parameters. +// PipelineFilter stores pipeline query parameters. type PipelineFilter struct { Page int `json:"page"` Size int `json:"size"` Query string `json:"query"` Order enum.Order `json:"order"` } + +// TODO: Move this into separate file +// ExecutionFilter stores execution query parameters. +type ExecutionFilter struct { + Page int `json:"page"` + Size int `json:"size"` + Query string `json:"query"` + Order enum.Order `json:"order"` +} From bc921ac1a23d47ab75e27771ce3b3242d93e4fbb Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Mon, 7 Aug 2023 02:19:41 +0100 Subject: [PATCH 09/26] add execution endpoints --- cmd/gitness/wire_gen.go | 2 +- .../api/controller/execution/controller.go | 3 + internal/api/controller/execution/create.go | 80 +++++++++++++++++ internal/api/controller/execution/delete.go | 34 ++++++++ internal/api/controller/execution/find.go | 31 +++++++ internal/api/controller/execution/list.go | 39 +++++++++ internal/api/controller/execution/update.go | 52 ++++++++++++ internal/api/controller/execution/wire.go | 4 +- internal/api/handler/execution/create.go | 47 ++++++++++ internal/api/handler/execution/delete.go | 46 ++++++++++ internal/api/handler/execution/find.go | 61 +++++++++++++ internal/api/handler/execution/list.go | 48 +++++++++++ internal/api/handler/execution/update.go | 54 ++++++++++++ internal/api/handler/pipeline/delete.go | 1 + internal/api/openapi/pipeline.go | 85 ++++++++++++++++--- internal/api/request/pipeline.go | 18 ++++ internal/router/api.go | 13 ++- internal/store/database.go | 3 + internal/store/database/execution.go | 8 +- internal/store/database/pipeline.go | 19 +++++ types/execution.go | 70 +++++++-------- 21 files changed, 662 insertions(+), 56 deletions(-) create mode 100644 internal/api/controller/execution/create.go create mode 100644 internal/api/controller/execution/delete.go create mode 100644 internal/api/controller/execution/find.go create mode 100644 internal/api/controller/execution/list.go create mode 100644 internal/api/controller/execution/update.go create mode 100644 internal/api/handler/execution/create.go create mode 100644 internal/api/handler/execution/delete.go create mode 100644 internal/api/handler/execution/find.go create mode 100644 internal/api/handler/execution/list.go create mode 100644 internal/api/handler/execution/update.go diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index 3d536978d..ca1da4ea1 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -87,8 +87,8 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro } 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) pipelineStore := database.ProvidePipelineStore(db) + executionController := execution.ProvideController(db, authorizer, executionStore, repoStore, pipelineStore, spaceStore) spaceController := space.ProvideController(db, provider, pathUID, authorizer, pathStore, pipelineStore, spaceStore, repoStore, principalStore, repoController, membershipStore) pipelineController := pipeline.ProvideController(db, pathUID, pathStore, repoStore, authorizer, pipelineStore, spaceStore) pullReqStore := database.ProvidePullReqStore(db, principalInfoCache) diff --git a/internal/api/controller/execution/controller.go b/internal/api/controller/execution/controller.go index 540557c48..92ac0096f 100644 --- a/internal/api/controller/execution/controller.go +++ b/internal/api/controller/execution/controller.go @@ -15,6 +15,7 @@ type Controller struct { authorizer authz.Authorizer executionStore store.ExecutionStore repoStore store.RepoStore + pipelineStore store.PipelineStore spaceStore store.SpaceStore } @@ -23,6 +24,7 @@ func NewController( authorizer authz.Authorizer, executionStore store.ExecutionStore, repoStore store.RepoStore, + pipelineStore store.PipelineStore, spaceStore store.SpaceStore, ) *Controller { return &Controller{ @@ -30,6 +32,7 @@ func NewController( authorizer: authorizer, executionStore: executionStore, repoStore: repoStore, + pipelineStore: pipelineStore, spaceStore: spaceStore, } } diff --git a/internal/api/controller/execution/create.go b/internal/api/controller/execution/create.go new file mode 100644 index 000000000..bc5191d59 --- /dev/null +++ b/internal/api/controller/execution/create.go @@ -0,0 +1,80 @@ +// 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 execution + +import ( + "context" + "fmt" + "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" +) + +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.") +) + +// TODO: Add more as needed +type CreateInput struct { + Status string `json:"status"` +} + +// Create creates a new execution +func (c *Controller) Create(ctx context.Context, session *auth.Session, spaceRef string, uid string, in *CreateInput) (*types.Execution, error) { + // TODO: Add auth + // parentSpace, err := c.getSpaceCheckAuthRepoCreation(ctx, session, in.ParentRef) + // if err != nil { + // return nil, err + // } + + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, fmt.Errorf("could not find space: %w", err) + } + + if err := c.sanitizeCreateInput(in); err != nil { + return nil, fmt.Errorf("failed to sanitize input: %w", err) + } + + var execution *types.Execution + err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) error { + now := time.Now().UnixMilli() + pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, uid) + if err != nil { + return err + } + fmt.Println("seq before: ", pipeline.Seq) + pipeline, err = c.pipelineStore.Increment(ctx, pipeline) + if err != nil { + return err + } + fmt.Println("seq after: ", pipeline.Seq) + execution = &types.Execution{ + Number: pipeline.Seq, + Status: in.Status, + RepoID: pipeline.RepoID, + PipelineID: pipeline.ID, + Created: now, + Updated: now, + Version: 0, + } + err = c.executionStore.Create(ctx, execution) + if err != nil { + return fmt.Errorf("execution creation failed: %w", err) + } + return nil + }) + + return execution, nil +} + +func (c *Controller) sanitizeCreateInput(in *CreateInput) error { + return nil +} diff --git a/internal/api/controller/execution/delete.go b/internal/api/controller/execution/delete.go new file mode 100644 index 000000000..8f308e8fd --- /dev/null +++ b/internal/api/controller/execution/delete.go @@ -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 execution + +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, n int64) 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.executionStore.Delete(ctx, pipeline.ID, n) + if err != nil { + return fmt.Errorf("could not delete execution: %w", err) + } + return nil +} diff --git a/internal/api/controller/execution/find.go b/internal/api/controller/execution/find.go new file mode 100644 index 000000000..670bb729e --- /dev/null +++ b/internal/api/controller/execution/find.go @@ -0,0 +1,31 @@ +// 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 execution + +import ( + "context" + + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" +) + +// Find finds a pipeline. +func (c *Controller) Find(ctx context.Context, session *auth.Session, spaceRef string, uid string, n int64) (*types.Execution, 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 + // } + + pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, uid) + if err != nil { + return nil, err + } + + return c.executionStore.Find(ctx, pipeline.ID, n) +} diff --git a/internal/api/controller/execution/list.go b/internal/api/controller/execution/list.go new file mode 100644 index 000000000..12907a8ab --- /dev/null +++ b/internal/api/controller/execution/list.go @@ -0,0 +1,39 @@ +// 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 execution + +import ( + "context" + + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" +) + +// ListRepositories lists the repositories of a space. +// TODO: move to different file +func (c *Controller) List( + ctx context.Context, + session *auth.Session, + spaceRef string, + pipelineUID string, + filter *types.ExecutionFilter) ([]types.Execution, int, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, 0, err + } + pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, pipelineUID) + if err != nil { + return nil, 0, err + } + + // TODO: Add auth + // if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionRepoView, true); err != nil { + // return nil, 0, err + // } + executions, err := c.executionStore.List(ctx, pipeline.ID, filter) + if err != nil { + return nil, 0, err + } + return executions, len(executions), nil +} diff --git a/internal/api/controller/execution/update.go b/internal/api/controller/execution/update.go new file mode 100644 index 000000000..2dfee2f42 --- /dev/null +++ b/internal/api/controller/execution/update.go @@ -0,0 +1,52 @@ +// 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 execution + +import ( + "context" + + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" +) + +// UpdateInput is used for updating a repo. +type UpdateInput struct { + Status string `json:"status"` +} + +// Update updates an execution. +func (c *Controller) Update( + ctx context.Context, + session *auth.Session, + spaceRef string, + uid string, + n int64, + in *UpdateInput) (*types.Execution, 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 + } + + execution, err := c.executionStore.Find(ctx, pipeline.ID, n) + if err != nil { + return nil, err + } + + if in.Status != "" { + execution.Status = in.Status + } + + // TODO: Add auth + // if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoEdit, false); err != nil { + // return nil, err + // } + + return c.executionStore.Update(ctx, execution) +} diff --git a/internal/api/controller/execution/wire.go b/internal/api/controller/execution/wire.go index 6021a841e..ba0a6d915 100644 --- a/internal/api/controller/execution/wire.go +++ b/internal/api/controller/execution/wire.go @@ -20,9 +20,9 @@ func ProvideController(db *sqlx.DB, authorizer authz.Authorizer, executionStore store.ExecutionStore, repoStore store.RepoStore, + pipelineStore store.PipelineStore, spaceStore store.SpaceStore, ) *Controller { return NewController(db, authorizer, executionStore, - repoStore, - spaceStore) + repoStore, pipelineStore, spaceStore) } diff --git a/internal/api/handler/execution/create.go b/internal/api/handler/execution/create.go new file mode 100644 index 000000000..a9d490b1d --- /dev/null +++ b/internal/api/handler/execution/create.go @@ -0,0 +1,47 @@ +// 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 execution + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/internal/api/controller/execution" + "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(executionCtrl *execution.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) + return + } + + in := new(execution.CreateInput) + err = json.NewDecoder(r.Body).Decode(in) + if err != nil { + render.BadRequestf(w, "Invalid Request Body: %s.", err) + return + } + + execution, err := executionCtrl.Create(ctx, session, spaceRef, pipelineUID, in) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusCreated, execution) + } +} diff --git a/internal/api/handler/execution/delete.go b/internal/api/handler/execution/delete.go new file mode 100644 index 000000000..218d6d31c --- /dev/null +++ b/internal/api/handler/execution/delete.go @@ -0,0 +1,46 @@ +// 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 execution + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/execution" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" +) + +/* + * Deletes an execution + */ +func HandleDelete(executionCtrl *execution.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) + return + } + n, err := request.GetExecutionNumberFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + err = executionCtrl.Delete(ctx, session, spaceRef, pipelineUID, n) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.DeleteSuccessful(w) + } +} diff --git a/internal/api/handler/execution/find.go b/internal/api/handler/execution/find.go new file mode 100644 index 000000000..fec7d2c26 --- /dev/null +++ b/internal/api/handler/execution/find.go @@ -0,0 +1,61 @@ +// 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 execution + +import ( + "errors" + "net/http" + "strings" + + "github.com/harness/gitness/internal/api/controller/execution" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" +) + +// HandleFind finds a pipeline from the database. +func HandleFind(executionCtrl *execution.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 + } + n, err := request.GetExecutionNumberFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + spaceRef, pipelineUID, err := SplitRef(pipelineRef) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + execution, err := executionCtrl.Find(ctx, session, spaceRef, pipelineUID, n) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusOK, execution) + } +} + +// SplitRef splits apart a ref into two parts, otherwise returns an error +// For example: path/to/space/uid will get split into path/to/space and uid +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 +} diff --git a/internal/api/handler/execution/list.go b/internal/api/handler/execution/list.go new file mode 100644 index 000000000..5c79b414a --- /dev/null +++ b/internal/api/handler/execution/list.go @@ -0,0 +1,48 @@ +// 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 execution + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/execution" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// HandleListRepos writes json-encoded list of repos in the request body. +func HandleList(executionCtrl *execution.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) + return + } + + // TODO: Use execution filter + filter := request.ParseRepoFilter(r) + if filter.Order == enum.OrderDefault { + filter.Order = enum.OrderAsc + } + + repos, totalCount, err := executionCtrl.List(ctx, session, spaceRef, pipelineUID, &types.ExecutionFilter{}) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.Pagination(r, w, filter.Page, filter.Size, int(totalCount)) + render.JSON(w, http.StatusOK, repos) + } +} diff --git a/internal/api/handler/execution/update.go b/internal/api/handler/execution/update.go new file mode 100644 index 000000000..994918a76 --- /dev/null +++ b/internal/api/handler/execution/update.go @@ -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 execution + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/internal/api/controller/execution" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" +) + +/* + * Updates an existing execution + */ +func HandleUpdate(executionCtrl *execution.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + + in := new(execution.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) + } + n, err := request.GetExecutionNumberFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + pipeline, err := executionCtrl.Update(ctx, session, spaceRef, pipelineUID, n, in) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusOK, pipeline) + } +} diff --git a/internal/api/handler/pipeline/delete.go b/internal/api/handler/pipeline/delete.go index 02673b3d0..057f6c0a5 100644 --- a/internal/api/handler/pipeline/delete.go +++ b/internal/api/handler/pipeline/delete.go @@ -27,6 +27,7 @@ func HandleDelete(pipelineCtrl *pipeline.Controller) http.HandlerFunc { spaceRef, pipelineUID, err := SplitRef(pipelineRef) if err != nil { render.TranslatedUserError(w, err) + return } err = pipelineCtrl.Delete(ctx, session, spaceRef, pipelineUID) diff --git a/internal/api/openapi/pipeline.go b/internal/api/openapi/pipeline.go index f7a63e8a6..346d0f76c 100644 --- a/internal/api/openapi/pipeline.go +++ b/internal/api/openapi/pipeline.go @@ -7,6 +7,7 @@ package openapi import ( "net/http" + "github.com/harness/gitness/internal/api/controller/execution" "github.com/harness/gitness/internal/api/controller/pipeline" "github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/types" @@ -14,8 +15,20 @@ import ( "github.com/swaggest/openapi-go/openapi3" ) -type createPipelineRequest struct { - pipeline.CreateInput +type createExecutionRequest struct { + Ref string `path:"pipeline_ref"` + execution.CreateInput +} + +type getExecutionRequest struct { + Ref string `path:"pipeline_ref"` + Number string `path:"execution_number"` +} + +type updateExecutionRequest struct { + Ref string `path:"pipeline_ref"` + Number string `path:"execution_number"` + execution.UpdateInput } type pipelineRequest struct { @@ -27,17 +40,11 @@ type updatePipelineRequest struct { 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.SetRequest(&opCreate, new(pipeline.CreateInput), 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) @@ -49,7 +56,7 @@ func pipelineOperations(reflector *openapi3.Reflector) { 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(types.Pipeline), 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) @@ -78,4 +85,62 @@ func pipelineOperations(reflector *openapi3.Reflector) { _ = 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) + + executionCreate := openapi3.Operation{} + executionCreate.WithTags("pipeline") + executionCreate.WithMapOfAnything(map[string]interface{}{"operationId": "createExecution"}) + _ = reflector.SetRequest(&executionCreate, new(createExecutionRequest), http.MethodPost) + _ = reflector.SetJSONResponse(&executionCreate, new(types.Execution), http.StatusCreated) + _ = reflector.SetJSONResponse(&executionCreate, new(usererror.Error), http.StatusBadRequest) + _ = reflector.SetJSONResponse(&executionCreate, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&executionCreate, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&executionCreate, new(usererror.Error), http.StatusForbidden) + _ = reflector.Spec.AddOperation(http.MethodPost, "/pipelines/{pipeline_ref}/executions", executionCreate) + + executionFind := openapi3.Operation{} + executionFind.WithTags("pipeline") + executionFind.WithMapOfAnything(map[string]interface{}{"operationId": "findExecution"}) + _ = reflector.SetRequest(&executionFind, new(getExecutionRequest), http.MethodGet) + _ = reflector.SetJSONResponse(&executionFind, new(types.Execution), http.StatusOK) + _ = reflector.SetJSONResponse(&executionFind, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&executionFind, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&executionFind, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&executionFind, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodGet, "/pipelines/{pipeline_ref}/executions/{execution_number}", executionFind) + + executionDelete := openapi3.Operation{} + executionDelete.WithTags("pipeline") + executionDelete.WithMapOfAnything(map[string]interface{}{"operationId": "deleteExecution"}) + _ = reflector.SetRequest(&executionDelete, new(getExecutionRequest), http.MethodDelete) + _ = reflector.SetJSONResponse(&executionDelete, nil, http.StatusNoContent) + _ = reflector.SetJSONResponse(&executionDelete, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&executionDelete, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&executionDelete, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&executionDelete, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodDelete, "/pipelines/{pipeline_ref}/executions/{execution_number}", executionDelete) + + executionUpdate := openapi3.Operation{} + executionUpdate.WithTags("pipeline") + executionUpdate.WithMapOfAnything(map[string]interface{}{"operationId": "updateExecution"}) + _ = reflector.SetRequest(&executionUpdate, new(updateExecutionRequest), http.MethodPatch) + _ = reflector.SetJSONResponse(&executionUpdate, new(types.Execution), http.StatusOK) + _ = reflector.SetJSONResponse(&executionUpdate, new(usererror.Error), http.StatusBadRequest) + _ = reflector.SetJSONResponse(&executionUpdate, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&executionUpdate, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&executionUpdate, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&executionUpdate, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodPatch, "/pipelines/{pipeline_ref}/executions/{execution_number}", executionUpdate) + + executionList := openapi3.Operation{} + executionList.WithTags("pipeline") + executionList.WithMapOfAnything(map[string]interface{}{"operationId": "listExecutions"}) + executionList.WithParameters(queryParameterQueryRepo, queryParameterSortRepo, queryParameterOrder, + queryParameterPage, queryParameterLimit) + _ = reflector.SetRequest(&executionList, new(pipelineRequest), http.MethodGet) + _ = reflector.SetJSONResponse(&executionList, []types.Execution{}, http.StatusOK) + _ = reflector.SetJSONResponse(&executionList, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&executionList, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&executionList, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&executionList, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodGet, "/pipelines/{pipeline_ref}/executions", executionList) } diff --git a/internal/api/request/pipeline.go b/internal/api/request/pipeline.go index fea17c598..524402ae6 100644 --- a/internal/api/request/pipeline.go +++ b/internal/api/request/pipeline.go @@ -7,11 +7,13 @@ package request import ( "net/http" "net/url" + "strconv" ) const ( PipelinePathRef = "pipeline_ref" PipelineUID = "pipeline_uid" + ExecutionNumber = "execution_number" ) func GetPipelinePathRefFromPath(r *http.Request) (string, error) { @@ -24,6 +26,22 @@ func GetPipelinePathRefFromPath(r *http.Request) (string, error) { return url.PathUnescape(rawRef) } +// TODO: Move into separate execution folder +func GetExecutionNumberFromPath(r *http.Request) (int64, error) { + rawRef, err := PathParamOrError(r, ExecutionNumber) + if err != nil { + return 0, err + } + + n, err := strconv.Atoi(rawRef) + if err != nil { + return 0, err + } + + // paths are unescaped + return int64(n), nil +} + func GetPipelineUIDFromPath(r *http.Request) (string, error) { rawRef, err := PathParamOrError(r, PipelineUID) if err != nil { diff --git a/internal/router/api.go b/internal/router/api.go index b3031ff6b..66d15fd1f 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -22,6 +22,7 @@ import ( "github.com/harness/gitness/internal/api/controller/webhook" "github.com/harness/gitness/internal/api/handler/account" handlercheck "github.com/harness/gitness/internal/api/handler/check" + handlerexecution "github.com/harness/gitness/internal/api/handler/execution" 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" @@ -284,14 +285,20 @@ func setupPipelines(r chi.Router, pipelineCtrl *pipeline.Controller, executionCt r.Get("/", handlerpipeline.HandleFind(pipelineCtrl)) r.Patch("/", handlerpipeline.HandleUpdate(pipelineCtrl)) r.Delete("/", handlerpipeline.HandleDelete(pipelineCtrl)) - setupExecutions(r, executionCtrl) + setupExecutions(r, pipelineCtrl, executionCtrl) }) }) } -func setupExecutions(r chi.Router, executionCtrl *execution.Controller) { +func setupExecutions(r chi.Router, pipelineCtrl *pipeline.Controller, executionCtrl *execution.Controller) { r.Route("/executions", func(r chi.Router) { - + r.Get("/", handlerexecution.HandleList(executionCtrl)) + r.Post("/", handlerexecution.HandleCreate(executionCtrl)) + r.Route(fmt.Sprintf("/{%s}", request.ExecutionNumber), func(r chi.Router) { + r.Get("/", handlerexecution.HandleFind(executionCtrl)) + r.Patch("/", handlerexecution.HandleUpdate(executionCtrl)) + r.Delete("/", handlerexecution.HandleDelete(executionCtrl)) + }) }) } diff --git a/internal/store/database.go b/internal/store/database.go index bda11bb14..7432fa4b1 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -460,6 +460,9 @@ type ( // DeleteByUID deletes a pipeline with a given UID in a space DeleteByUID(context.Context, int64, string) error + + // Incremenet increments the sequence number of the pipeline + Increment(context.Context, *types.Pipeline) (*types.Pipeline, error) } ExecutionStore interface { diff --git a/internal/store/database/execution.go b/internal/store/database/execution.go index ca54710a1..dcf23ffc0 100644 --- a/internal/store/database/execution.go +++ b/internal/store/database/execution.go @@ -110,9 +110,8 @@ const ( execution_version ` - executionInsterStmt = ` + executionInsertStmt = ` INSERT INTO executions ( - execution_id, execution_pipeline_id, execution_repo_id, execution_trigger, @@ -148,7 +147,6 @@ const ( execution_updated, execution_version ) VALUES ( - :execution_id, :execution_pipeline_id, :execution_repo_id, :execution_trigger, @@ -242,7 +240,7 @@ func (s *executionStore) Find(ctx context.Context, parentID int64, n int64) (*ty func (s *executionStore) Create(ctx context.Context, execution *types.Execution) error { db := dbtx.GetAccessor(ctx, s.db) - query, arg, err := db.BindNamed(executionInsterStmt, execution) + query, arg, err := db.BindNamed(executionInsertStmt, execution) if err != nil { return database.ProcessSQLErrorf(err, "Failed to bind execution object") } @@ -282,7 +280,7 @@ func (s *executionStore) Update(ctx context.Context, execution *types.Execution) return nil, gitness_store.ErrVersionConflict } - return s.Find(ctx, execution.Parent, execution.Number) + return s.Find(ctx, execution.PipelineID, execution.Number) } // List lists the executions for a given pipeline ID diff --git a/internal/store/database/pipeline.go b/internal/store/database/pipeline.go index 07afe55f7..c5581d231 100644 --- a/internal/store/database/pipeline.go +++ b/internal/store/database/pipeline.go @@ -245,3 +245,22 @@ func (s *pipelineStore) DeleteByUID(ctx context.Context, spaceID int64, uid stri return nil } + +// Increment increments the pipeline sequence number. It will keep retrying in case +// of optimistic lock errors. +func (s *pipelineStore) Increment(ctx context.Context, pipeline *types.Pipeline) (*types.Pipeline, error) { + for { + pipeline.Seq++ + pipeline, err := s.Update(ctx, pipeline) + if err == nil { + return pipeline, nil + } + if err != nil && err != gitness_store.ErrVersionConflict { + return pipeline, err + } + pipeline, err = s.Find(ctx, pipeline.ID) + if err != nil { + return nil, err + } + } +} diff --git a/types/execution.go b/types/execution.go index 4128bfe40..374b3e5dc 100644 --- a/types/execution.go +++ b/types/execution.go @@ -6,41 +6,41 @@ 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"` + 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 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"` } From 4bc8777a5d5a269d61a19c4c14e284cbd21ab683 Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Mon, 7 Aug 2023 13:30:26 +0100 Subject: [PATCH 10/26] remove unneeded transaction --- internal/api/controller/execution/create.go | 53 ++++++++++----------- internal/store/database/execution.go | 2 +- internal/store/database/pipeline.go | 2 +- 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/internal/api/controller/execution/create.go b/internal/api/controller/execution/create.go index bc5191d59..8e098b815 100644 --- a/internal/api/controller/execution/create.go +++ b/internal/api/controller/execution/create.go @@ -11,7 +11,6 @@ import ( "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" ) @@ -43,34 +42,30 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, spaceRef return nil, fmt.Errorf("failed to sanitize input: %w", err) } - var execution *types.Execution - err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) error { - now := time.Now().UnixMilli() - pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, uid) - if err != nil { - return err - } - fmt.Println("seq before: ", pipeline.Seq) - pipeline, err = c.pipelineStore.Increment(ctx, pipeline) - if err != nil { - return err - } - fmt.Println("seq after: ", pipeline.Seq) - execution = &types.Execution{ - Number: pipeline.Seq, - Status: in.Status, - RepoID: pipeline.RepoID, - PipelineID: pipeline.ID, - Created: now, - Updated: now, - Version: 0, - } - err = c.executionStore.Create(ctx, execution) - if err != nil { - return fmt.Errorf("execution creation failed: %w", err) - } - return nil - }) + pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, uid) + if err != nil { + return nil, err + } + + pipeline, err = c.pipelineStore.Increment(ctx, pipeline) + if err != nil { + return nil, err + } + + now := time.Now().UnixMilli() + execution := &types.Execution{ + Number: pipeline.Seq, + Status: in.Status, + RepoID: pipeline.RepoID, + PipelineID: pipeline.ID, + Created: now, + Updated: now, + Version: 0, + } + err = c.executionStore.Create(ctx, execution) + if err != nil { + return nil, fmt.Errorf("execution creation failed: %w", err) + } return execution, nil } diff --git a/internal/store/database/execution.go b/internal/store/database/execution.go index dcf23ffc0..3335d30f1 100644 --- a/internal/store/database/execution.go +++ b/internal/store/database/execution.go @@ -280,7 +280,7 @@ func (s *executionStore) Update(ctx context.Context, execution *types.Execution) return nil, gitness_store.ErrVersionConflict } - return s.Find(ctx, execution.PipelineID, execution.Number) + return execution, nil } // List lists the executions for a given pipeline ID diff --git a/internal/store/database/pipeline.go b/internal/store/database/pipeline.go index c5581d231..90addc1fb 100644 --- a/internal/store/database/pipeline.go +++ b/internal/store/database/pipeline.go @@ -183,7 +183,7 @@ func (s *pipelineStore) Update(ctx context.Context, pipeline *types.Pipeline) (* return nil, gitness_store.ErrVersionConflict } - return s.Find(ctx, pipeline.ID) + return pipeline, nil } From 0c184b5970b55d5934f4241a0bbd9e930012cec9 Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Mon, 7 Aug 2023 15:49:50 +0100 Subject: [PATCH 11/26] fix + termination --- internal/router/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/router/api.go b/internal/router/api.go index 66d15fd1f..d6ce808e0 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -58,7 +58,7 @@ type APIHandler interface { var ( // terminatedPathPrefixesAPI is the list of prefixes that will require resolving terminated paths. - terminatedPathPrefixesAPI = []string{"/v1/spaces/", "/v1/repos/"} + terminatedPathPrefixesAPI = []string{"/v1/spaces/", "/v1/repos/", "/v1/pipelines/"} ) // NewAPIHandler returns a new APIHandler. From c8ae92150f6a6ec563a2c4d409764dfb6ebe0c22 Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Mon, 7 Aug 2023 18:29:29 +0100 Subject: [PATCH 12/26] add pipelines auth --- internal/api/auth/pipeline.go | 29 ++++++++++++++ internal/api/controller/execution/create.go | 13 ++++--- internal/api/controller/execution/delete.go | 11 ++++-- internal/api/controller/execution/find.go | 11 ++++-- internal/api/controller/execution/list.go | 14 ++++--- internal/api/controller/execution/update.go | 12 +++--- internal/api/controller/pipeline/create.go | 38 ++++++++++++++++--- internal/api/controller/pipeline/delete.go | 12 +++--- internal/api/controller/pipeline/find.go | 11 +++--- internal/api/controller/pipeline/update.go | 12 +++--- .../api/controller/space/list_pipelines.go | 13 ++++--- internal/auth/authz/membership.go | 3 ++ .../database/migrate/ci/ci_migrations.sql | 7 +++- types/enum/membership_role.go | 6 +++ types/enum/permission.go | 11 ++++++ 15 files changed, 150 insertions(+), 53 deletions(-) create mode 100644 internal/api/auth/pipeline.go diff --git a/internal/api/auth/pipeline.go b/internal/api/auth/pipeline.go new file mode 100644 index 000000000..73d24fac8 --- /dev/null +++ b/internal/api/auth/pipeline.go @@ -0,0 +1,29 @@ +// 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 auth + +import ( + "context" + + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/internal/auth/authz" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// CheckRepo checks if a repo specific permission is granted for the current auth session +// in the scope of its parent. +// Returns nil if the permission is granted, otherwise returns an error. +// NotAuthenticated, NotAuthorized, or any underlying error. +func CheckPipeline(ctx context.Context, authorizer authz.Authorizer, session *auth.Session, + parentPath, uid string, permission enum.Permission) error { + scope := &types.Scope{SpacePath: parentPath} + resource := &types.Resource{ + Type: enum.ResourceTypeRepo, + Name: uid, + } + + return Check(ctx, authorizer, session, scope, resource, permission) +} diff --git a/internal/api/controller/execution/create.go b/internal/api/controller/execution/create.go index 8e098b815..62a8679ef 100644 --- a/internal/api/controller/execution/create.go +++ b/internal/api/controller/execution/create.go @@ -9,9 +9,11 @@ import ( "fmt" "time" + apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/internal/auth" "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" ) var ( @@ -27,12 +29,6 @@ type CreateInput struct { // Create creates a new execution func (c *Controller) Create(ctx context.Context, session *auth.Session, spaceRef string, uid string, in *CreateInput) (*types.Execution, error) { - // TODO: Add auth - // parentSpace, err := c.getSpaceCheckAuthRepoCreation(ctx, session, in.ParentRef) - // if err != nil { - // return nil, err - // } - space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { return nil, fmt.Errorf("could not find space: %w", err) @@ -47,6 +43,11 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, spaceRef return nil, err } + err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, pipeline.UID, enum.PermissionPipelineExecute) + if err != nil { + return nil, err + } + pipeline, err = c.pipelineStore.Increment(ctx, pipeline) if err != nil { return nil, err diff --git a/internal/api/controller/execution/delete.go b/internal/api/controller/execution/delete.go index 8f308e8fd..f60562c77 100644 --- a/internal/api/controller/execution/delete.go +++ b/internal/api/controller/execution/delete.go @@ -8,7 +8,9 @@ import ( "context" "fmt" + apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types/enum" ) // Delete deletes a pipeline. @@ -17,15 +19,16 @@ func (c *Controller) Delete(ctx context.Context, session *auth.Session, 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 = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, pipeline.UID, enum.PermissionPipelineDelete) + if err != nil { + return err + } err = c.executionStore.Delete(ctx, pipeline.ID, n) if err != nil { return fmt.Errorf("could not delete execution: %w", err) diff --git a/internal/api/controller/execution/find.go b/internal/api/controller/execution/find.go index 670bb729e..ce3c23be0 100644 --- a/internal/api/controller/execution/find.go +++ b/internal/api/controller/execution/find.go @@ -7,8 +7,10 @@ package execution import ( "context" + apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" ) // Find finds a pipeline. @@ -17,15 +19,16 @@ func (c *Controller) Find(ctx context.Context, session *auth.Session, spaceRef s 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 - // } pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, uid) if err != nil { return nil, err } + err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, pipeline.UID, enum.PermissionPipelineView) + if err != nil { + return nil, err + } + return c.executionStore.Find(ctx, pipeline.ID, n) } diff --git a/internal/api/controller/execution/list.go b/internal/api/controller/execution/list.go index 12907a8ab..3e4550c23 100644 --- a/internal/api/controller/execution/list.go +++ b/internal/api/controller/execution/list.go @@ -6,12 +6,13 @@ package execution import ( "context" + apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" ) -// ListRepositories lists the repositories of a space. -// TODO: move to different file +// List lists the executions in a pipeline. func (c *Controller) List( ctx context.Context, session *auth.Session, @@ -27,10 +28,11 @@ func (c *Controller) List( return nil, 0, err } - // TODO: Add auth - // if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionRepoView, true); err != nil { - // return nil, 0, err - // } + err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, pipeline.UID, enum.PermissionPipelineView) + if err != nil { + return nil, 0, err + } + executions, err := c.executionStore.List(ctx, pipeline.ID, filter) if err != nil { return nil, 0, err diff --git a/internal/api/controller/execution/update.go b/internal/api/controller/execution/update.go index 2dfee2f42..f18758771 100644 --- a/internal/api/controller/execution/update.go +++ b/internal/api/controller/execution/update.go @@ -7,8 +7,10 @@ package execution import ( "context" + apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" ) // UpdateInput is used for updating a repo. @@ -29,6 +31,11 @@ func (c *Controller) Update( return nil, err } + err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, uid, enum.PermissionPipelineEdit) + if err != nil { + return nil, err + } + pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, uid) if err != nil { return nil, err @@ -43,10 +50,5 @@ func (c *Controller) Update( execution.Status = in.Status } - // TODO: Add auth - // if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoEdit, false); err != nil { - // return nil, err - // } - return c.executionStore.Update(ctx, execution) } diff --git a/internal/api/controller/pipeline/create.go b/internal/api/controller/pipeline/create.go index 495742ea8..d90f9c7b4 100644 --- a/internal/api/controller/pipeline/create.go +++ b/internal/api/controller/pipeline/create.go @@ -11,6 +11,7 @@ import ( "strings" "time" + apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/internal/auth" "github.com/harness/gitness/store/database/dbtx" @@ -37,16 +38,16 @@ type CreateInput struct { // 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) } + + err = apiauth.CheckPipeline(ctx, c.authorizer, session, parentSpace.Path, in.UID, enum.PermissionPipelineEdit) + if err != nil { + return nil, err + } + var repoID int64 if in.RepoType == enum.ScmTypeGitness { @@ -93,6 +94,31 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea return pipeline, nil } +func (c *Controller) getSpaceCheckAuthRepoCreation( + ctx context.Context, + session *auth.Session, + parentRef string, +) (*types.Space, error) { + space, err := c.spaceStore.FindByRef(ctx, parentRef) + if err != nil { + return nil, fmt.Errorf("parent space not found: %w", err) + } + + // create is a special case - check permission without specific resource + scope := &types.Scope{SpacePath: space.Path} + resource := &types.Resource{ + Type: enum.ResourceTypeRepo, + Name: "", + } + + err = apiauth.Check(ctx, c.authorizer, session, scope, resource, enum.PermissionRepoEdit) + if err != nil { + return nil, fmt.Errorf("auth check failed: %w", err) + } + + return space, nil +} + func (c *Controller) sanitizeCreateInput(in *CreateInput) error { parentRefAsID, err := strconv.ParseInt(in.ParentRef, 10, 64) diff --git a/internal/api/controller/pipeline/delete.go b/internal/api/controller/pipeline/delete.go index 29f93935c..56668ceaf 100644 --- a/internal/api/controller/pipeline/delete.go +++ b/internal/api/controller/pipeline/delete.go @@ -8,7 +8,9 @@ import ( "context" "fmt" + apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types/enum" ) // Delete deletes a pipeline. @@ -17,11 +19,11 @@ func (c *Controller) Delete(ctx context.Context, session *auth.Session, 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 + + err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, uid, enum.PermissionPipelineDelete) + if err != nil { + return err + } err = c.pipelineStore.DeleteByUID(ctx, space.ID, uid) if err != nil { return fmt.Errorf("could not delete pipeline: %w", err) diff --git a/internal/api/controller/pipeline/find.go b/internal/api/controller/pipeline/find.go index ad8604f58..128dad3e3 100644 --- a/internal/api/controller/pipeline/find.go +++ b/internal/api/controller/pipeline/find.go @@ -7,8 +7,10 @@ package pipeline import ( "context" + apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" ) // Find finds a pipeline. @@ -17,10 +19,9 @@ func (c *Controller) Find(ctx context.Context, session *auth.Session, spaceRef s 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 + err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, uid, enum.PermissionPipelineView) + if err != nil { + return nil, err + } return c.pipelineStore.FindByUID(ctx, space.ID, uid) } diff --git a/internal/api/controller/pipeline/update.go b/internal/api/controller/pipeline/update.go index d2f4b9453..bd57a6ea0 100644 --- a/internal/api/controller/pipeline/update.go +++ b/internal/api/controller/pipeline/update.go @@ -7,8 +7,10 @@ package pipeline import ( "context" + apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" ) // UpdateInput is used for updating a repo. @@ -30,6 +32,11 @@ func (c *Controller) Update( return nil, err } + err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, uid, enum.PermissionPipelineEdit) + if err != nil { + return nil, err + } + pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, uid) if err != nil { return nil, err @@ -45,10 +52,5 @@ func (c *Controller) Update( 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) } diff --git a/internal/api/controller/space/list_pipelines.go b/internal/api/controller/space/list_pipelines.go index b2af52a7a..f2da69dac 100644 --- a/internal/api/controller/space/list_pipelines.go +++ b/internal/api/controller/space/list_pipelines.go @@ -6,12 +6,13 @@ package space import ( "context" + apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" ) -// ListRepositories lists the repositories of a space. -// TODO: move to different file +// ListPipelines lists the pipelines in a space. func (c *Controller) ListPipelines(ctx context.Context, session *auth.Session, spaceRef string, filter *types.PipelineFilter) ([]types.Pipeline, int, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) @@ -19,10 +20,10 @@ func (c *Controller) ListPipelines(ctx context.Context, session *auth.Session, return nil, 0, err } - // TODO: Add auth - // if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionRepoView, true); err != nil { - // return nil, 0, err - // } + err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView, true) + if err != nil { + return nil, 0, err + } pipelines, err := c.pipelineStore.List(ctx, space.ID, filter) if err != nil { return nil, 0, err diff --git a/internal/auth/authz/membership.go b/internal/auth/authz/membership.go index 0bcfc4f19..9610d1b5c 100644 --- a/internal/auth/authz/membership.go +++ b/internal/auth/authz/membership.go @@ -63,6 +63,9 @@ func (a *MembershipAuthorizer) Check( case enum.ResourceTypeServiceAccount: spaceRef = scope.SpacePath + case enum.ResourceTypePipeline: + spaceRef = scope.SpacePath + case enum.ResourceTypeUser: // a user is allowed to view / edit themselves if resource.Name == session.Principal.UID && diff --git a/internal/store/database/migrate/ci/ci_migrations.sql b/internal/store/database/migrate/ci/ci_migrations.sql index 1cd33a689..ac926c4ae 100644 --- a/internal/store/database/migrate/ci/ci_migrations.sql +++ b/internal/store/database/migrate/ci/ci_migrations.sql @@ -74,4 +74,9 @@ CREATE TABLE IF NOT EXISTS executions ( REFERENCES pipelines (pipeline_id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE -); \ No newline at end of file +); + +CREATE TABLE IF NOT EXISTS secrets ( + secret_id INTEGER PRIMARY KEY AUTOINCREMENT, + +) \ No newline at end of file diff --git a/types/enum/membership_role.go b/types/enum/membership_role.go index f7888d2c2..b957491cc 100644 --- a/types/enum/membership_role.go +++ b/types/enum/membership_role.go @@ -24,10 +24,12 @@ var membershipRoleReaderPermissions = slices.Clip(slices.Insert([]Permission{}, PermissionRepoView, PermissionSpaceView, PermissionServiceAccountView, + PermissionPipelineView, )) var membershipRoleExecutorPermissions = slices.Clip(slices.Insert(membershipRoleReaderPermissions, 0, PermissionCommitCheckReport, + PermissionPipelineExecute, )) var membershipRoleContributorPermissions = slices.Clip(slices.Insert(membershipRoleReaderPermissions, 0, @@ -47,6 +49,10 @@ var membershipRoleSpaceOwnerPermissions = slices.Clip(slices.Insert(membershipRo PermissionServiceAccountCreate, PermissionServiceAccountEdit, PermissionServiceAccountDelete, + + PermissionPipelineEdit, + PermissionPipelineExecute, + PermissionPipelineDelete, )) func init() { diff --git a/types/enum/permission.go b/types/enum/permission.go index 6824f035a..c917f0fed 100644 --- a/types/enum/permission.go +++ b/types/enum/permission.go @@ -13,6 +13,7 @@ const ( ResourceTypeUser ResourceType = "USER" ResourceTypeServiceAccount ResourceType = "SERVICEACCOUNT" ResourceTypeService ResourceType = "SERVICE" + ResourceTypePipeline ResourceType = "PIPELINE" // ResourceType_Branch ResourceType = "BRANCH" ) @@ -71,6 +72,16 @@ const ( PermissionServiceEditAdmin Permission = "service_editAdmin" ) +const ( + /* + ----- PIPELINE ----- + */ + PermissionPipelineView Permission = "pipeline_view" + PermissionPipelineEdit Permission = "pipeline_edit" + PermissionPipelineDelete Permission = "pipeline_delete" + PermissionPipelineExecute Permission = "pipeline_execute" +) + const ( /* ----- COMMIT CHECK ----- From 1e7e4c98ddadeed3a4bfb64a86581010402ebbe8 Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Tue, 8 Aug 2023 00:23:51 +0100 Subject: [PATCH 13/26] fix some TODOs --- internal/api/controller/execution/list.go | 1 + .../api/controller/space/list_pipelines.go | 1 + internal/api/handler/execution/list.go | 10 ++---- internal/api/handler/space/list_pipelines.go | 11 ++---- internal/api/request/pipeline.go | 35 +++++++++---------- .../database/migrate/ci/ci_migrations.sql | 7 +--- types/execution.go | 6 ++++ types/pipeline.go | 16 ++------- 8 files changed, 33 insertions(+), 54 deletions(-) diff --git a/internal/api/controller/execution/list.go b/internal/api/controller/execution/list.go index 3e4550c23..86d87b19d 100644 --- a/internal/api/controller/execution/list.go +++ b/internal/api/controller/execution/list.go @@ -37,5 +37,6 @@ func (c *Controller) List( if err != nil { return nil, 0, err } + // TODO: This should be total count, not returned count return executions, len(executions), nil } diff --git a/internal/api/controller/space/list_pipelines.go b/internal/api/controller/space/list_pipelines.go index f2da69dac..008d99d4e 100644 --- a/internal/api/controller/space/list_pipelines.go +++ b/internal/api/controller/space/list_pipelines.go @@ -28,5 +28,6 @@ func (c *Controller) ListPipelines(ctx context.Context, session *auth.Session, if err != nil { return nil, 0, err } + // TODO: This should be total count, not returned count return pipelines, len(pipelines), nil } diff --git a/internal/api/handler/execution/list.go b/internal/api/handler/execution/list.go index 5c79b414a..a3c7d377f 100644 --- a/internal/api/handler/execution/list.go +++ b/internal/api/handler/execution/list.go @@ -10,8 +10,6 @@ import ( "github.com/harness/gitness/internal/api/controller/execution" "github.com/harness/gitness/internal/api/render" "github.com/harness/gitness/internal/api/request" - "github.com/harness/gitness/types" - "github.com/harness/gitness/types/enum" ) // HandleListRepos writes json-encoded list of repos in the request body. @@ -30,13 +28,9 @@ func HandleList(executionCtrl *execution.Controller) http.HandlerFunc { return } - // TODO: Use execution filter - filter := request.ParseRepoFilter(r) - if filter.Order == enum.OrderDefault { - filter.Order = enum.OrderAsc - } + filter := request.ParseExecutionFilter(r) - repos, totalCount, err := executionCtrl.List(ctx, session, spaceRef, pipelineUID, &types.ExecutionFilter{}) + repos, totalCount, err := executionCtrl.List(ctx, session, spaceRef, pipelineUID, filter) if err != nil { render.TranslatedUserError(w, err) return diff --git a/internal/api/handler/space/list_pipelines.go b/internal/api/handler/space/list_pipelines.go index 633c8f9d2..432adb797 100644 --- a/internal/api/handler/space/list_pipelines.go +++ b/internal/api/handler/space/list_pipelines.go @@ -10,8 +10,6 @@ import ( "github.com/harness/gitness/internal/api/controller/space" "github.com/harness/gitness/internal/api/render" "github.com/harness/gitness/internal/api/request" - "github.com/harness/gitness/types" - "github.com/harness/gitness/types/enum" ) // HandleListRepos writes json-encoded list of repos in the request body. @@ -25,13 +23,8 @@ func HandleListPipelines(spaceCtrl *space.Controller) http.HandlerFunc { return } - // TODO: Use pipeline filter - filter := request.ParseRepoFilter(r) - if filter.Order == enum.OrderDefault { - filter.Order = enum.OrderAsc - } - - repos, totalCount, err := spaceCtrl.ListPipelines(ctx, session, spaceRef, &types.PipelineFilter{}) + filter := request.ParsePipelineFilter(r) + repos, totalCount, err := spaceCtrl.ListPipelines(ctx, session, spaceRef, filter) if err != nil { render.TranslatedUserError(w, err) return diff --git a/internal/api/request/pipeline.go b/internal/api/request/pipeline.go index 524402ae6..6e2f0698d 100644 --- a/internal/api/request/pipeline.go +++ b/internal/api/request/pipeline.go @@ -8,6 +8,8 @@ import ( "net/http" "net/url" "strconv" + + "github.com/harness/gitness/types" ) const ( @@ -26,7 +28,6 @@ func GetPipelinePathRefFromPath(r *http.Request) (string, error) { return url.PathUnescape(rawRef) } -// TODO: Move into separate execution folder func GetExecutionNumberFromPath(r *http.Request) (int64, error) { rawRef, err := PathParamOrError(r, ExecutionNumber) if err != nil { @@ -52,21 +53,19 @@ func GetPipelineUIDFromPath(r *http.Request) (string, error) { 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), -// ) -// } +// ParsePipelineFilter extracts the pipeline filter from the url. +func ParsePipelineFilter(r *http.Request) *types.PipelineFilter { + return &types.PipelineFilter{ + Query: ParseQuery(r), + Page: ParsePage(r), + Size: ParseLimit(r), + } +} -// // 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), -// } -// } +// ParseExecutionFilter extracts the execution filter from the url. +func ParseExecutionFilter(r *http.Request) *types.ExecutionFilter { + return &types.ExecutionFilter{ + Page: ParsePage(r), + Size: ParseLimit(r), + } +} diff --git a/internal/store/database/migrate/ci/ci_migrations.sql b/internal/store/database/migrate/ci/ci_migrations.sql index ac926c4ae..1cd33a689 100644 --- a/internal/store/database/migrate/ci/ci_migrations.sql +++ b/internal/store/database/migrate/ci/ci_migrations.sql @@ -74,9 +74,4 @@ CREATE TABLE IF NOT EXISTS executions ( REFERENCES pipelines (pipeline_id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE -); - -CREATE TABLE IF NOT EXISTS secrets ( - secret_id INTEGER PRIMARY KEY AUTOINCREMENT, - -) \ No newline at end of file +); \ No newline at end of file diff --git a/types/execution.go b/types/execution.go index 374b3e5dc..e29df4f8d 100644 --- a/types/execution.go +++ b/types/execution.go @@ -44,3 +44,9 @@ type Execution struct { // TODO: (Vistaar) Add stages // Stages []*Stage `db:"-" json:"stages,omitempty"` } + +// ExecutionFilter stores execution query parameters. +type ExecutionFilter struct { + Page int `json:"page"` + Size int `json:"size"` +} diff --git a/types/pipeline.go b/types/pipeline.go index ce9cb4576..eefc038fc 100644 --- a/types/pipeline.go +++ b/types/pipeline.go @@ -24,17 +24,7 @@ type Pipeline struct { // PipelineFilter stores pipeline query parameters. type PipelineFilter struct { - Page int `json:"page"` - Size int `json:"size"` - Query string `json:"query"` - Order enum.Order `json:"order"` -} - -// TODO: Move this into separate file -// ExecutionFilter stores execution query parameters. -type ExecutionFilter struct { - Page int `json:"page"` - Size int `json:"size"` - Query string `json:"query"` - Order enum.Order `json:"order"` + Page int `json:"page"` + Size int `json:"size"` + Query string `json:"query"` } From 80b7082a432a2a2bf193add9549c4ed93c92571f Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Tue, 8 Aug 2023 12:09:48 +0100 Subject: [PATCH 14/26] fix pagination --- internal/api/controller/execution/list.go | 12 ++++++--- .../api/controller/space/list_pipelines.go | 12 ++++++--- internal/store/database.go | 6 +++++ internal/store/database/execution.go | 22 ++++++++++++++++ internal/store/database/pipeline.go | 26 +++++++++++++++++++ 5 files changed, 72 insertions(+), 6 deletions(-) diff --git a/internal/api/controller/execution/list.go b/internal/api/controller/execution/list.go index 86d87b19d..879476f28 100644 --- a/internal/api/controller/execution/list.go +++ b/internal/api/controller/execution/list.go @@ -5,6 +5,7 @@ package execution import ( "context" + "fmt" apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" @@ -18,7 +19,7 @@ func (c *Controller) List( session *auth.Session, spaceRef string, pipelineUID string, - filter *types.ExecutionFilter) ([]types.Execution, int, error) { + filter *types.ExecutionFilter) ([]types.Execution, int64, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { return nil, 0, err @@ -33,10 +34,15 @@ func (c *Controller) List( return nil, 0, err } + count, err := c.executionStore.Count(ctx, pipeline.ID, filter) + if err != nil { + return nil, 0, fmt.Errorf("failed to count child executions: %w", err) + } + executions, err := c.executionStore.List(ctx, pipeline.ID, filter) if err != nil { return nil, 0, err } - // TODO: This should be total count, not returned count - return executions, len(executions), nil + + return executions, count, nil } diff --git a/internal/api/controller/space/list_pipelines.go b/internal/api/controller/space/list_pipelines.go index 008d99d4e..1d4d667f2 100644 --- a/internal/api/controller/space/list_pipelines.go +++ b/internal/api/controller/space/list_pipelines.go @@ -5,6 +5,7 @@ package space import ( "context" + "fmt" apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" @@ -14,12 +15,17 @@ import ( // ListPipelines lists the pipelines in a space. func (c *Controller) ListPipelines(ctx context.Context, session *auth.Session, - spaceRef string, filter *types.PipelineFilter) ([]types.Pipeline, int, error) { + spaceRef string, filter *types.PipelineFilter) ([]types.Pipeline, int64, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { return nil, 0, err } + count, err := c.pipelineStore.Count(ctx, space.ID, filter) + if err != nil { + return nil, 0, fmt.Errorf("failed to count child pipelnes: %w", err) + } + err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView, true) if err != nil { return nil, 0, err @@ -28,6 +34,6 @@ func (c *Controller) ListPipelines(ctx context.Context, session *auth.Session, if err != nil { return nil, 0, err } - // TODO: This should be total count, not returned count - return pipelines, len(pipelines), nil + + return pipelines, count, nil } diff --git a/internal/store/database.go b/internal/store/database.go index 7432fa4b1..fe2619cec 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -458,6 +458,9 @@ type ( // Delete deletes a pipeline ID from the datastore. Delete(context.Context, int64) error + // Count the number of pipelines in a space matching the given filter. + Count(ctx context.Context, parentID int64, opts *types.PipelineFilter) (int64, error) + // DeleteByUID deletes a pipeline with a given UID in a space DeleteByUID(context.Context, int64, string) error @@ -481,6 +484,9 @@ type ( // Delete deletes an execution given a pipeline ID and an execution number Delete(context.Context, int64, int64) error + // Count the number of executions in a space matching the given filter. + Count(ctx context.Context, parentID int64, opts *types.ExecutionFilter) (int64, error) + // Find returns a build from the datastore. // Find(context.Context, int64) (*types.Execution, error) diff --git a/internal/store/database/execution.go b/internal/store/database/execution.go index 3335d30f1..779aefe1a 100644 --- a/internal/store/database/execution.go +++ b/internal/store/database/execution.go @@ -308,6 +308,28 @@ func (s *executionStore) List(ctx context.Context, pipelineID int64, opts *types return dst, nil } +// Count of executions in a space. +func (s *executionStore) Count(ctx context.Context, pipelineID int64, opts *types.ExecutionFilter) (int64, error) { + stmt := database.Builder. + Select("count(*)"). + From("executions"). + Where("execution_pipeline_id = ?", pipelineID) + + sql, args, err := stmt.ToSql() + if err != nil { + return 0, errors.Wrap(err, "Failed to convert query to sql") + } + + db := dbtx.GetAccessor(ctx, s.db) + + var count int64 + err = db.QueryRowContext(ctx, sql, args...).Scan(&count) + if err != nil { + return 0, database.ProcessSQLErrorf(err, "Failed executing count query") + } + return count, nil +} + // Delete deletes an execution given a pipeline ID and an execution number func (s *executionStore) Delete(ctx context.Context, pipelineID int64, n int64) error { const executionDeleteStmt = ` diff --git a/internal/store/database/pipeline.go b/internal/store/database/pipeline.go index 90addc1fb..09f322b1c 100644 --- a/internal/store/database/pipeline.go +++ b/internal/store/database/pipeline.go @@ -216,6 +216,32 @@ func (s *pipelineStore) List(ctx context.Context, parentID int64, opts *types.Pi return dst, nil } +// Count of pipelines in a space. +func (s *pipelineStore) Count(ctx context.Context, parentID int64, opts *types.PipelineFilter) (int64, error) { + stmt := database.Builder. + Select("count(*)"). + From("pipelines"). + Where("pipeline_parent_id = ?", parentID) + + if opts.Query != "" { + stmt = stmt.Where("pipeline_uid LIKE ?", fmt.Sprintf("%%%s%%", opts.Query)) + } + + sql, args, err := stmt.ToSql() + if err != nil { + return 0, errors.Wrap(err, "Failed to convert query to sql") + } + + db := dbtx.GetAccessor(ctx, s.db) + + var count int64 + err = db.QueryRowContext(ctx, sql, args...).Scan(&count) + if err != nil { + return 0, database.ProcessSQLErrorf(err, "Failed executing count query") + } + return count, nil +} + // Delete deletes a pipeline given a pipeline ID func (s *pipelineStore) Delete(ctx context.Context, id int64) error { const pipelineDeleteStmt = ` From 5a47d789a4c8b7799874ceadda67f5538e682b31 Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Tue, 8 Aug 2023 13:32:50 +0100 Subject: [PATCH 15/26] add secret store and encryptor/decryptor --- encrypt/aesgcm.go | 70 +++++ encrypt/encrypt.go | 33 +++ encrypt/none.go | 19 ++ internal/store/database.go | 23 ++ .../database/migrate/ci/ci_migrations.sql | 22 +- internal/store/database/secret.go | 257 ++++++++++++++++++ types/secret.go | 36 +++ 7 files changed, 459 insertions(+), 1 deletion(-) create mode 100644 encrypt/aesgcm.go create mode 100644 encrypt/encrypt.go create mode 100644 encrypt/none.go create mode 100644 internal/store/database/secret.go create mode 100644 types/secret.go diff --git a/encrypt/aesgcm.go b/encrypt/aesgcm.go new file mode 100644 index 000000000..a7340d462 --- /dev/null +++ b/encrypt/aesgcm.go @@ -0,0 +1,70 @@ +// Copyright 2023 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 encrypt + +import ( + "crypto/cipher" + "crypto/rand" + "errors" + "io" +) + +// Aesgcm provides an encrypter that uses the aesgcm encryption +// algorithm. +type Aesgcm struct { + block cipher.Block + Compat bool +} + +// Encrypt encrypts the plaintext using aesgcm. +func (e *Aesgcm) Encrypt(plaintext string) ([]byte, error) { + gcm, err := cipher.NewGCM(e.block) + if err != nil { + return nil, err + } + + nonce := make([]byte, gcm.NonceSize()) + _, err = io.ReadFull(rand.Reader, nonce) + if err != nil { + return nil, err + } + + return gcm.Seal(nonce, nonce, []byte(plaintext), nil), nil +} + +// Decrypt decrypts the ciphertext using aesgcm. +func (e *Aesgcm) Decrypt(ciphertext []byte) (string, error) { + gcm, err := cipher.NewGCM(e.block) + if err != nil { + return "", err + } + + if len(ciphertext) < gcm.NonceSize() { + // if the decryption utility is running in compatibility + // mode, it will return the ciphertext as plain text if + // decryption fails. This should be used when running the + // database in mixed-mode, where there is a mix of encrypted + // and unencrypted content. + if e.Compat { + return string(ciphertext), nil + } + return "", errors.New("malformed ciphertext") + } + + plaintext, err := gcm.Open(nil, + ciphertext[:gcm.NonceSize()], + ciphertext[gcm.NonceSize():], + nil, + ) + // if the decryption utility is running in compatibility + // mode, it will return the ciphertext as plain text if + // decryption fails. This should be used when running the + // database in mixed-mode, where there is a mix of encrypted + // and unencrypted content. + if err != nil && e.Compat { + return string(ciphertext), nil + } + return string(plaintext), err +} diff --git a/encrypt/encrypt.go b/encrypt/encrypt.go new file mode 100644 index 000000000..5e365254a --- /dev/null +++ b/encrypt/encrypt.go @@ -0,0 +1,33 @@ +package encrypt + +import ( + "crypto/aes" + "errors" +) + +// indicates key size is too small. +var errKeySize = errors.New("encryption key must be 32 bytes") + +// Encrypter provides database field encryption and decryption. +// Encrypted values are currently limited to strings, which is +// reflected in the interface design. +type Encrypter interface { + Encrypt(plaintext string) ([]byte, error) + Decrypt(ciphertext []byte) (string, error) +} + +// New provides a new database field encrypter. +func New(key string) (Encrypter, error) { + if key == "" { + return &none{}, nil + } + if len(key) != 32 { + return nil, errKeySize + } + b := []byte(key) + block, err := aes.NewCipher(b) + if err != nil { + return nil, err + } + return &Aesgcm{block: block}, nil +} diff --git a/encrypt/none.go b/encrypt/none.go new file mode 100644 index 000000000..91897cc09 --- /dev/null +++ b/encrypt/none.go @@ -0,0 +1,19 @@ +// Copyright 2023 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 encrypt + +// none is an encryption strategy that stores secret +// values in plain text. This is the default strategy +// when no key is specified. +type none struct { +} + +func (*none) Encrypt(plaintext string) ([]byte, error) { + return []byte(plaintext), nil +} + +func (*none) Decrypt(ciphertext []byte) (string, error) { + return string(ciphertext), nil +} diff --git a/internal/store/database.go b/internal/store/database.go index fe2619cec..3dcf7f750 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -468,6 +468,29 @@ type ( Increment(context.Context, *types.Pipeline) (*types.Pipeline, error) } + SecretStore interface { + // Find returns a secret given an ID + Find(context.Context, int64) (*types.Secret, error) + + // FindByUID returns a secret given a space ID and a UID + FindByUID(context.Context, int64, string) (*types.Secret, error) + + // Create creates a new secret + Create(context.Context, *types.Secret) error + + // Update tries to update an execution in the datastore with optimistic locking. + Update(context.Context, *types.Secret) (*types.Secret, error) + + // Delete deletes an execution given a pipeline ID and an execution number + Delete(context.Context, int64) error + + // Delete deletes an execution given a pipeline ID and an execution number + DeleteByUID(context.Context, int64, string) error + + // List lists the executions for a given pipeline ID + List(context.Context, int64, *types.SecretFilter) ([]types.Secret, error) + } + ExecutionStore interface { // Find returns a execution given a pipeline and an execution number Find(context.Context, int64, int64) (*types.Execution, error) diff --git a/internal/store/database/migrate/ci/ci_migrations.sql b/internal/store/database/migrate/ci/ci_migrations.sql index 1cd33a689..a87a58055 100644 --- a/internal/store/database/migrate/ci/ci_migrations.sql +++ b/internal/store/database/migrate/ci/ci_migrations.sql @@ -29,7 +29,7 @@ CREATE TABLE IF NOT EXISTS pipelines ( ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS executions ( +CREATE TABLE IF NOT EXISTS secrets ( execution_id INTEGER PRIMARY KEY AUTOINCREMENT, execution_pipeline_id INTEGER NOT NULL, execution_repo_id INTEGER, @@ -74,4 +74,24 @@ CREATE TABLE IF NOT EXISTS executions ( REFERENCES pipelines (pipeline_id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS secrets ( + secret_id INTEGER PRIMARY KEY AUTOINCREMENT, + secret_uid TEXT NOT NULL, + secret_parent_id INTEGER NOT NULL, + secret_description TEXT NOT NULL, + secret_data BLOB NOT NULL, + secret_created INTEGER, + secret_updated INTEGER, + secret_version INTEGER, + + -- Ensure unique combination of space ID and UID + UNIQUE (secret_parent_id, secret_uid), + + -- Foreign key to spaces table + CONSTRAINT fk_secret_parent_id FOREIGN KEY (secret_parent_id) + REFERENCES spaces (space_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE ); \ No newline at end of file diff --git a/internal/store/database/secret.go b/internal/store/database/secret.go new file mode 100644 index 000000000..23e34ed89 --- /dev/null +++ b/internal/store/database/secret.go @@ -0,0 +1,257 @@ +// 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 database + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/harness/gitness/encrypt" + "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.SecretStore = (*secretStore)(nil) + +const ( + secretQueryBase = ` + SELECT + secret_id, + secret_description, + secret_parent_id, + secret_uid, + secret_data, + secret_created, + secret_updated, + secret_version + FROM secrets + ` + + secretColumns = ` + secret_id, + secret_description, + secret_parent_id, + secret_uid, + secret_data, + secret_created, + secret_updated, + secret_version + ` + + secretInsertStmt = ` + INSERT INTO secrets ( + secret_description, + secret_parent_id, + secret_uid, + secret_data, + secret_created, + secret_updated, + secret_version + ) VALUES ( + :secret_description, + :secret_parent_id, + :secret_uid, + :secret_data, + :secret_created, + :secret_updated, + :secret_version + ) RETURNING secret_id` + + secretUpdateStmt = ` + UPDATE secrets + SET + secret_description = :secret_description, + secret_parent_id = :secret_parent_id, + secret_uid = :secret_uid, + secret_data = :secret_data, + secret_created = :secret_created, + secret_updated = :secret_updated, + secret_version = :secret_version + WHERE secret_id = :secret_id AND secret_version = :secret_version - 1` +) + +// NewSecretStore returns a new SecretStore. +func NewSecretStore(db *sqlx.DB, enc encrypt.Encrypter) *secretStore { + return &secretStore{ + db: db, + enc: enc, + } +} + +type secretStore struct { + db *sqlx.DB + enc encrypt.Encrypter +} + +// Find returns a secret given a secret ID +func (s *secretStore) Find(ctx context.Context, id int64) (*types.Secret, error) { + const findQueryStmt = secretQueryBase + ` + WHERE secret_id = $1` + db := dbtx.GetAccessor(ctx, s.db) + + dst := new(types.Secret) + if err := db.GetContext(ctx, dst, findQueryStmt, id); err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed to find secret") + } + return dec(s.enc, dst) +} + +// FindByUID returns a secret in a given space with a given UID +func (s *secretStore) FindByUID(ctx context.Context, spaceID int64, uid string) (*types.Secret, error) { + const findQueryStmt = secretQueryBase + ` + WHERE secret_parent_id = $1 AND secret_uid = $2` + db := dbtx.GetAccessor(ctx, s.db) + + dst := new(types.Secret) + if err := db.GetContext(ctx, dst, findQueryStmt, spaceID, uid); err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed to find secret") + } + return dec(s.enc, dst) +} + +// Create creates a secret +func (s *secretStore) Create(ctx context.Context, secret *types.Secret) error { + db := dbtx.GetAccessor(ctx, s.db) + + secret, err := enc(s.enc, secret) + if err != nil { + return err + } + + query, arg, err := db.BindNamed(secretInsertStmt, secret) + if err != nil { + return database.ProcessSQLErrorf(err, "Failed to bind secret object") + } + + if err = db.QueryRowContext(ctx, query, arg...).Scan(&secret.ID); err != nil { + return database.ProcessSQLErrorf(err, "secret query failed") + } + + return nil +} + +func (s *secretStore) Update(ctx context.Context, secret *types.Secret) (*types.Secret, error) { + updatedAt := time.Now() + + secret.Version++ + secret.Updated = updatedAt.UnixMilli() + + db := dbtx.GetAccessor(ctx, s.db) + + secret, err := enc(s.enc, secret) + if err != nil { + return nil, err + } + + query, arg, err := db.BindNamed(secretUpdateStmt, secret) + if err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed to bind secret object") + } + + result, err := db.ExecContext(ctx, query, arg...) + if err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed to update secret") + } + + 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 secret, nil + +} + +// List lists all the secrets present in a space +func (s *secretStore) List(ctx context.Context, parentID int64, opts *types.SecretFilter) ([]types.Secret, error) { + stmt := database.Builder. + Select(secretColumns). + From("secrets"). + Where("secret_parent_id = ?", fmt.Sprint(parentID)) + + if opts.Query != "" { + stmt = stmt.Where("LOWER(secret_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.Secret{} + 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 secret given a secret ID +func (s *secretStore) Delete(ctx context.Context, id int64) error { + const secretDeleteStmt = ` + DELETE FROM secrets + WHERE secret_id = $1` + + db := dbtx.GetAccessor(ctx, s.db) + + if _, err := db.ExecContext(ctx, secretDeleteStmt, id); err != nil { + return database.ProcessSQLErrorf(err, "Could not delete secret") + } + + return nil +} + +// DeleteByUID deletes a secret with a given UID in a space +func (s *secretStore) DeleteByUID(ctx context.Context, spaceID int64, uid string) error { + const secretDeleteStmt = ` + DELETE FROM secrets + WHERE secret_parent_id = $1 AND secret_uid = $2` + + db := dbtx.GetAccessor(ctx, s.db) + + if _, err := db.ExecContext(ctx, secretDeleteStmt, spaceID, uid); err != nil { + return database.ProcessSQLErrorf(err, "Could not delete secret") + } + + return nil +} + +// helper function returns the same secret with encrypted data +func enc(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error) { + s := *secret + ciphertext, err := encrypt.Encrypt(secret.Data) + if err != nil { + return nil, err + } + s.Data = string(ciphertext) + return &s, nil +} + +// helper function returns the same secret with decrypted data +func dec(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error) { + s := *secret + plaintext, err := encrypt.Decrypt([]byte(secret.Data)) + if err != nil { + return nil, err + } + s.Data = string(plaintext) + return &s, nil +} diff --git a/types/secret.go b/types/secret.go new file mode 100644 index 000000000..0f24ea054 --- /dev/null +++ b/types/secret.go @@ -0,0 +1,36 @@ +// Copyright 2023 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 types + +type Secret struct { + ID int64 `db:"secret_id" json:"id"` + Description string `db:"secret_description" json:"description"` + ParentID int64 `db:"secret_parent_id" json:"parent_id"` // ID of the parent space + UID string `db:"secret_uid" json:"uid"` + Data string `db:"secret_data" json:"data"` + Created int64 `db:"secret_created" json:"created"` + Updated int64 `db:"secret_updated" json:"updated"` + Version int64 `db:"secret_version" json:"version"` +} + +// SecretFilter stores secret query parameters. +type SecretFilter struct { + Page int `json:"page"` + Size int `json:"size"` + Query string `json:"query"` +} + +// Copy makes a copy of the secret without the value. +func (s *Secret) Copy() *Secret { + return &Secret{ + ID: s.ID, + Description: s.Description, + UID: s.UID, + ParentID: s.ParentID, + Created: s.Created, + Updated: s.Updated, + Version: s.Version, + } +} From 4983a8b48c064b9f777022e6fd4c14909581c964 Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Tue, 8 Aug 2023 15:35:13 +0100 Subject: [PATCH 16/26] add pipeline request struct --- internal/api/openapi/pipeline.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/api/openapi/pipeline.go b/internal/api/openapi/pipeline.go index 346d0f76c..64324fc53 100644 --- a/internal/api/openapi/pipeline.go +++ b/internal/api/openapi/pipeline.go @@ -20,6 +20,11 @@ type createExecutionRequest struct { execution.CreateInput } +type createPipelineRequest struct { + Ref string `path:"pipeline_ref"` + pipeline.CreateInput +} + type getExecutionRequest struct { Ref string `path:"pipeline_ref"` Number string `path:"execution_number"` @@ -44,7 +49,7 @@ func pipelineOperations(reflector *openapi3.Reflector) { opCreate := openapi3.Operation{} opCreate.WithTags("pipeline") opCreate.WithMapOfAnything(map[string]interface{}{"operationId": "createPipeline"}) - _ = reflector.SetRequest(&opCreate, new(pipeline.CreateInput), http.MethodPost) + _ = 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) From 26d6651168b8e7761474ccddd2e5a74777545a5f Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Wed, 9 Aug 2023 15:02:48 +0100 Subject: [PATCH 17/26] address comments, complete secret CRUD --- cli/server/config.go | 6 +- cmd/gitness/wire.go | 2 + cmd/gitness/wire_gen.go | 11 +- internal/api/auth/pipeline.go | 4 +- internal/api/auth/secret.go | 29 +++ .../api/controller/execution/controller.go | 3 - internal/api/controller/execution/create.go | 21 +- internal/api/controller/execution/delete.go | 14 +- internal/api/controller/execution/find.go | 14 +- internal/api/controller/execution/list.go | 33 ++- internal/api/controller/execution/update.go | 11 +- internal/api/controller/execution/wire.go | 4 +- internal/api/controller/pipeline/create.go | 85 ++---- internal/api/controller/pipeline/delete.go | 4 +- internal/api/controller/pipeline/find.go | 5 +- internal/api/controller/pipeline/update.go | 12 +- internal/api/controller/pipeline/wire.go | 3 +- internal/api/controller/secret/controller.go | 39 +++ internal/api/controller/secret/create.go | 97 +++++++ internal/api/controller/secret/delete.go | 32 +++ internal/api/controller/secret/find.go | 27 ++ internal/api/controller/secret/update.go | 56 ++++ internal/api/controller/secret/wire.go | 28 ++ .../api/controller/space/list_pipelines.go | 31 ++- internal/api/handler/execution/create.go | 4 +- internal/api/handler/execution/delete.go | 6 +- internal/api/handler/execution/find.go | 21 +- internal/api/handler/execution/list.go | 4 +- internal/api/handler/execution/update.go | 7 +- internal/api/handler/pipeline/create.go | 1 - internal/api/handler/pipeline/delete.go | 6 +- internal/api/handler/pipeline/find.go | 22 +- internal/api/handler/pipeline/update.go | 7 +- internal/api/handler/secret/create.go | 37 +++ internal/api/handler/secret/delete.go | 42 +++ internal/api/handler/secret/find.go | 39 +++ internal/api/handler/secret/update.go | 50 ++++ internal/api/handler/space/list_pipelines.go | 1 - internal/api/openapi/openapi.go | 1 + internal/api/openapi/pipeline.go | 33 ++- internal/api/openapi/secret.go | 79 ++++++ internal/api/openapi/space.go | 3 +- internal/api/request/pipeline.go | 13 +- internal/api/request/secret.go | 34 +++ internal/api/request/util.go | 20 ++ internal/auth/authz/membership.go | 3 + internal/router/api.go | 22 +- internal/router/wire.go | 6 +- internal/store/database.go | 109 ++------ internal/store/database/execution.go | 246 ++++++++---------- .../database/migrate/ci/ci_migrations.sql | 44 ++-- internal/store/database/pipeline.go | 82 +++--- internal/store/database/secret.go | 18 +- internal/store/database/wire.go | 26 ++ store/database/config.go | 6 +- types/config.go | 3 + types/enum/membership_role.go | 8 + types/enum/permission.go | 11 + types/pipeline.go | 2 +- types/secret.go | 4 +- 60 files changed, 1052 insertions(+), 539 deletions(-) create mode 100644 internal/api/auth/secret.go create mode 100644 internal/api/controller/secret/controller.go create mode 100644 internal/api/controller/secret/create.go create mode 100644 internal/api/controller/secret/delete.go create mode 100644 internal/api/controller/secret/find.go create mode 100644 internal/api/controller/secret/update.go create mode 100644 internal/api/controller/secret/wire.go create mode 100644 internal/api/handler/secret/create.go create mode 100644 internal/api/handler/secret/delete.go create mode 100644 internal/api/handler/secret/find.go create mode 100644 internal/api/handler/secret/update.go create mode 100644 internal/api/openapi/secret.go create mode 100644 internal/api/request/secret.go diff --git a/cli/server/config.go b/cli/server/config.go index b1dea701f..414ebd2a9 100644 --- a/cli/server/config.go +++ b/cli/server/config.go @@ -90,8 +90,10 @@ func getSanitizedMachineName() (string, error) { // ProvideDatabaseConfig loads the database config from the main config. func ProvideDatabaseConfig(config *types.Config) database.Config { return database.Config{ - Driver: config.Database.Driver, - Datasource: config.Database.Datasource, + Driver: config.Database.Driver, + Datasource: config.Database.Datasource, + Secret: config.Database.Secret, + EncryptMixedContent: config.Database.EncryptMixedContent, } } diff --git a/cmd/gitness/wire.go b/cmd/gitness/wire.go index 6e9c05fd4..f610c1a4d 100644 --- a/cmd/gitness/wire.go +++ b/cmd/gitness/wire.go @@ -22,6 +22,7 @@ import ( "github.com/harness/gitness/internal/api/controller/principal" "github.com/harness/gitness/internal/api/controller/pullreq" "github.com/harness/gitness/internal/api/controller/repo" + "github.com/harness/gitness/internal/api/controller/secret" "github.com/harness/gitness/internal/api/controller/service" "github.com/harness/gitness/internal/api/controller/serviceaccount" "github.com/harness/gitness/internal/api/controller/space" @@ -94,6 +95,7 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e checkcontroller.WireSet, execution.WireSet, pipeline.WireSet, + secret.WireSet, ) return &cliserver.System{}, nil } diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index ca1da4ea1..b06d1260b 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -20,6 +20,7 @@ import ( "github.com/harness/gitness/internal/api/controller/principal" "github.com/harness/gitness/internal/api/controller/pullreq" "github.com/harness/gitness/internal/api/controller/repo" + "github.com/harness/gitness/internal/api/controller/secret" "github.com/harness/gitness/internal/api/controller/service" "github.com/harness/gitness/internal/api/controller/serviceaccount" "github.com/harness/gitness/internal/api/controller/space" @@ -88,9 +89,15 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro repoController := repo.ProvideController(config, db, provider, pathUID, authorizer, pathStore, repoStore, spaceStore, principalStore, gitrpcInterface) executionStore := database.ProvideExecutionStore(db) pipelineStore := database.ProvidePipelineStore(db) - executionController := execution.ProvideController(db, authorizer, executionStore, repoStore, pipelineStore, spaceStore) + executionController := execution.ProvideController(db, authorizer, executionStore, pipelineStore, spaceStore) spaceController := space.ProvideController(db, provider, pathUID, authorizer, pathStore, pipelineStore, spaceStore, repoStore, principalStore, repoController, membershipStore) pipelineController := pipeline.ProvideController(db, pathUID, pathStore, repoStore, authorizer, pipelineStore, spaceStore) + encrypter, err := database.ProvideEncryptor(databaseConfig) + if err != nil { + return nil, err + } + secretStore := database.ProvideSecretStore(encrypter, db) + secretController := secret.ProvideController(db, pathUID, pathStore, secretStore, authorizer, spaceStore) pullReqStore := database.ProvidePullReqStore(db, principalInfoCache) pullReqActivityStore := database.ProvidePullReqActivityStore(db, principalInfoCache) codeCommentView := database.ProvideCodeCommentView(db) @@ -144,7 +151,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, executionController, spaceController, pipelineController, pullreqController, webhookController, githookController, serviceaccountController, controller, principalController, checkController) + apiHandler := router.ProvideAPIHandler(config, authenticator, repoController, executionController, spaceController, pipelineController, secretController, 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) diff --git a/internal/api/auth/pipeline.go b/internal/api/auth/pipeline.go index 73d24fac8..8c5c199a6 100644 --- a/internal/api/auth/pipeline.go +++ b/internal/api/auth/pipeline.go @@ -13,7 +13,7 @@ import ( "github.com/harness/gitness/types/enum" ) -// CheckRepo checks if a repo specific permission is granted for the current auth session +// CheckPipeline checks if a repo specific permission is granted for the current auth session // in the scope of its parent. // Returns nil if the permission is granted, otherwise returns an error. // NotAuthenticated, NotAuthorized, or any underlying error. @@ -21,7 +21,7 @@ func CheckPipeline(ctx context.Context, authorizer authz.Authorizer, session *au parentPath, uid string, permission enum.Permission) error { scope := &types.Scope{SpacePath: parentPath} resource := &types.Resource{ - Type: enum.ResourceTypeRepo, + Type: enum.ResourceTypePipeline, Name: uid, } diff --git a/internal/api/auth/secret.go b/internal/api/auth/secret.go new file mode 100644 index 000000000..7804f8c09 --- /dev/null +++ b/internal/api/auth/secret.go @@ -0,0 +1,29 @@ +// 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 auth + +import ( + "context" + + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/internal/auth/authz" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// CheckSecret checks if a repo specific permission is granted for the current auth session +// in the scope of its parent. +// Returns nil if the permission is granted, otherwise returns an error. +// NotAuthenticated, NotAuthorized, or any underlying error. +func CheckSecret(ctx context.Context, authorizer authz.Authorizer, session *auth.Session, + parentPath, uid string, permission enum.Permission) error { + scope := &types.Scope{SpacePath: parentPath} + resource := &types.Resource{ + Type: enum.ResourceTypeSecret, + Name: uid, + } + + return Check(ctx, authorizer, session, scope, resource, permission) +} diff --git a/internal/api/controller/execution/controller.go b/internal/api/controller/execution/controller.go index 92ac0096f..652637035 100644 --- a/internal/api/controller/execution/controller.go +++ b/internal/api/controller/execution/controller.go @@ -14,7 +14,6 @@ type Controller struct { db *sqlx.DB authorizer authz.Authorizer executionStore store.ExecutionStore - repoStore store.RepoStore pipelineStore store.PipelineStore spaceStore store.SpaceStore } @@ -23,7 +22,6 @@ func NewController( db *sqlx.DB, authorizer authz.Authorizer, executionStore store.ExecutionStore, - repoStore store.RepoStore, pipelineStore store.PipelineStore, spaceStore store.SpaceStore, ) *Controller { @@ -31,7 +29,6 @@ func NewController( db: db, authorizer: authorizer, executionStore: executionStore, - repoStore: repoStore, pipelineStore: pipelineStore, spaceStore: spaceStore, } diff --git a/internal/api/controller/execution/create.go b/internal/api/controller/execution/create.go index 62a8679ef..427d0315c 100644 --- a/internal/api/controller/execution/create.go +++ b/internal/api/controller/execution/create.go @@ -10,18 +10,11 @@ import ( "time" apiauth "github.com/harness/gitness/internal/api/auth" - "github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/internal/auth" "github.com/harness/gitness/types" "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.") -) - // TODO: Add more as needed type CreateInput struct { Status string `json:"status"` @@ -34,23 +27,19 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, spaceRef return nil, fmt.Errorf("could not find space: %w", err) } - if err := c.sanitizeCreateInput(in); err != nil { - return nil, fmt.Errorf("failed to sanitize input: %w", err) - } - pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, uid) if err != nil { - return nil, err + return nil, fmt.Errorf("could not find pipeline: %w", err) } err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, pipeline.UID, enum.PermissionPipelineExecute) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to authorize: %w", err) } pipeline, err = c.pipelineStore.Increment(ctx, pipeline) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to increment sequence number: %w", err) } now := time.Now().UnixMilli() @@ -70,7 +59,3 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, spaceRef return execution, nil } - -func (c *Controller) sanitizeCreateInput(in *CreateInput) error { - return nil -} diff --git a/internal/api/controller/execution/delete.go b/internal/api/controller/execution/delete.go index f60562c77..357574092 100644 --- a/internal/api/controller/execution/delete.go +++ b/internal/api/controller/execution/delete.go @@ -13,23 +13,21 @@ import ( "github.com/harness/gitness/types/enum" ) -// Delete deletes a pipeline. -func (c *Controller) Delete(ctx context.Context, session *auth.Session, spaceRef string, uid string, n int64) error { +func (c *Controller) Delete(ctx context.Context, session *auth.Session, spaceRef string, pipelineUID string, executionNum int64) error { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { - return err + return fmt.Errorf("could not find parent space: %w", err) } - // TODO: uncomment when soft delete is implemented - pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, uid) + pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, pipelineUID) if err != nil { - return err + return fmt.Errorf("could not find pipeline: %w", err) } err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, pipeline.UID, enum.PermissionPipelineDelete) if err != nil { - return err + return fmt.Errorf("could not authorize: %w", err) } - err = c.executionStore.Delete(ctx, pipeline.ID, n) + err = c.executionStore.Delete(ctx, pipeline.ID, executionNum) if err != nil { return fmt.Errorf("could not delete execution: %w", err) } diff --git a/internal/api/controller/execution/find.go b/internal/api/controller/execution/find.go index ce3c23be0..81bf08ba0 100644 --- a/internal/api/controller/execution/find.go +++ b/internal/api/controller/execution/find.go @@ -6,6 +6,7 @@ package execution import ( "context" + "fmt" apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" @@ -13,22 +14,21 @@ import ( "github.com/harness/gitness/types/enum" ) -// Find finds a pipeline. -func (c *Controller) Find(ctx context.Context, session *auth.Session, spaceRef string, uid string, n int64) (*types.Execution, error) { +func (c *Controller) Find(ctx context.Context, session *auth.Session, spaceRef string, pipelineUID string, executionNum int64) (*types.Execution, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { - return nil, err + return nil, fmt.Errorf("could not find parent space: %w", err) } - pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, uid) + pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, pipelineUID) if err != nil { - return nil, err + return nil, fmt.Errorf("could not find pipeline: %w", err) } err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, pipeline.UID, enum.PermissionPipelineView) if err != nil { - return nil, err + return nil, fmt.Errorf("could not authorize: %w", err) } - return c.executionStore.Find(ctx, pipeline.ID, n) + return c.executionStore.Find(ctx, pipeline.ID, executionNum) } diff --git a/internal/api/controller/execution/list.go b/internal/api/controller/execution/list.go index 879476f28..5cbbde5d0 100644 --- a/internal/api/controller/execution/list.go +++ b/internal/api/controller/execution/list.go @@ -9,6 +9,7 @@ import ( apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/store/database/dbtx" "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" ) @@ -19,29 +20,41 @@ func (c *Controller) List( session *auth.Session, spaceRef string, pipelineUID string, - filter *types.ExecutionFilter) ([]types.Execution, int64, error) { + filter *types.ExecutionFilter, +) ([]types.Execution, int64, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { - return nil, 0, err + return nil, 0, fmt.Errorf("failed to find parent space: %w", err) } pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, pipelineUID) if err != nil { - return nil, 0, err + return nil, 0, fmt.Errorf("failed to find pipeline: %w", err) } err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, pipeline.UID, enum.PermissionPipelineView) if err != nil { - return nil, 0, err + return nil, 0, fmt.Errorf("failed to authorize: %w", err) } - count, err := c.executionStore.Count(ctx, pipeline.ID, filter) - if err != nil { - return nil, 0, fmt.Errorf("failed to count child executions: %w", err) - } + var count int64 + var executions []types.Execution - executions, err := c.executionStore.List(ctx, pipeline.ID, filter) + err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { + var dbErr error + count, dbErr = c.executionStore.Count(ctx, pipeline.ID, filter) + if dbErr != nil { + return fmt.Errorf("failed to count child executions: %w", err) + } + + executions, dbErr = c.executionStore.List(ctx, pipeline.ID, filter) + if dbErr != nil { + return fmt.Errorf("failed to list child executions: %w", err) + } + + return dbErr + }, dbtx.TxDefaultReadOnly) if err != nil { - return nil, 0, err + return executions, count, err } return executions, count, nil diff --git a/internal/api/controller/execution/update.go b/internal/api/controller/execution/update.go index f18758771..e62923cd4 100644 --- a/internal/api/controller/execution/update.go +++ b/internal/api/controller/execution/update.go @@ -6,6 +6,7 @@ package execution import ( "context" + "fmt" apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" @@ -13,12 +14,10 @@ import ( "github.com/harness/gitness/types/enum" ) -// UpdateInput is used for updating a repo. type UpdateInput struct { Status string `json:"status"` } -// Update updates an execution. func (c *Controller) Update( ctx context.Context, session *auth.Session, @@ -28,22 +27,22 @@ func (c *Controller) Update( in *UpdateInput) (*types.Execution, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { - return nil, err + return nil, fmt.Errorf("could not find space: %w", err) } err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, uid, enum.PermissionPipelineEdit) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to check auth: %w", err) } pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, uid) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to find pipeline: %w", err) } execution, err := c.executionStore.Find(ctx, pipeline.ID, n) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to find execution: %w", err) } if in.Status != "" { diff --git a/internal/api/controller/execution/wire.go b/internal/api/controller/execution/wire.go index ba0a6d915..b2836ae35 100644 --- a/internal/api/controller/execution/wire.go +++ b/internal/api/controller/execution/wire.go @@ -19,10 +19,8 @@ var WireSet = wire.NewSet( func ProvideController(db *sqlx.DB, authorizer authz.Authorizer, executionStore store.ExecutionStore, - repoStore store.RepoStore, pipelineStore store.PipelineStore, spaceStore store.SpaceStore, ) *Controller { - return NewController(db, authorizer, executionStore, - repoStore, pipelineStore, spaceStore) + return NewController(db, authorizer, executionStore, pipelineStore, spaceStore) } diff --git a/internal/api/controller/pipeline/create.go b/internal/api/controller/pipeline/create.go index d90f9c7b4..1c134295b 100644 --- a/internal/api/controller/pipeline/create.go +++ b/internal/api/controller/pipeline/create.go @@ -14,23 +14,22 @@ import ( apiauth "github.com/harness/gitness/internal/api/auth" "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 if the user tries to create a pipeline 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 + SpaceRef string `json:"space_ref"` UID string `json:"uid"` - RepoRef string `json:"repo_ref"` // null if repo_type != gitness + RepoRef string `json:"repo_ref"` // empty if repo_type != gitness RepoType enum.ScmType `json:"repo_type"` DefaultBranch string `json:"default_branch"` ConfigPath string `json:"config_path"` @@ -38,7 +37,7 @@ type CreateInput struct { // Create creates a new pipeline func (c *Controller) Create(ctx context.Context, session *auth.Session, in *CreateInput) (*types.Pipeline, error) { - parentSpace, err := c.spaceStore.FindByRef(ctx, in.ParentRef) + parentSpace, err := c.spaceStore.FindByRef(ctx, in.SpaceRef) if err != nil { return nil, fmt.Errorf("could not find parent by ref: %w", err) } @@ -62,67 +61,33 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea return nil, fmt.Errorf("failed to sanitize input: %w", err) } + fmt.Println("parent space: ", parentSpace.ID) 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 - }) + now := time.Now().UnixMilli() + pipeline = &types.Pipeline{ + Description: in.Description, + SpaceID: 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 nil, fmt.Errorf("pipeline creation failed: %w", err) + } return pipeline, nil } -func (c *Controller) getSpaceCheckAuthRepoCreation( - ctx context.Context, - session *auth.Session, - parentRef string, -) (*types.Space, error) { - space, err := c.spaceStore.FindByRef(ctx, parentRef) - if err != nil { - return nil, fmt.Errorf("parent space not found: %w", err) - } - - // create is a special case - check permission without specific resource - scope := &types.Scope{SpacePath: space.Path} - resource := &types.Resource{ - Type: enum.ResourceTypeRepo, - Name: "", - } - - err = apiauth.Check(ctx, c.authorizer, session, scope, resource, enum.PermissionRepoEdit) - if err != nil { - return nil, fmt.Errorf("auth check failed: %w", err) - } - - return space, 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) { + parentRefAsID, err := strconv.ParseInt(in.SpaceRef, 10, 64) + if (err == nil && parentRefAsID <= 0) || (len(strings.TrimSpace(in.SpaceRef)) == 0) { return errPipelineRequiresParent } diff --git a/internal/api/controller/pipeline/delete.go b/internal/api/controller/pipeline/delete.go index 56668ceaf..fd266a5e9 100644 --- a/internal/api/controller/pipeline/delete.go +++ b/internal/api/controller/pipeline/delete.go @@ -17,12 +17,12 @@ import ( 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 + return fmt.Errorf("could not find parent space: %w", err) } err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, uid, enum.PermissionPipelineDelete) if err != nil { - return err + return fmt.Errorf("could not authorize: %w", err) } err = c.pipelineStore.DeleteByUID(ctx, space.ID, uid) if err != nil { diff --git a/internal/api/controller/pipeline/find.go b/internal/api/controller/pipeline/find.go index 128dad3e3..22560502a 100644 --- a/internal/api/controller/pipeline/find.go +++ b/internal/api/controller/pipeline/find.go @@ -6,6 +6,7 @@ package pipeline import ( "context" + "fmt" apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" @@ -17,11 +18,11 @@ import ( 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 + return nil, fmt.Errorf("could not find parent space: %w", err) } err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, uid, enum.PermissionPipelineView) if err != nil { - return nil, err + return nil, fmt.Errorf("could not authorize: %w", err) } return c.pipelineStore.FindByUID(ctx, space.ID, uid) } diff --git a/internal/api/controller/pipeline/update.go b/internal/api/controller/pipeline/update.go index bd57a6ea0..5f97b3d9d 100644 --- a/internal/api/controller/pipeline/update.go +++ b/internal/api/controller/pipeline/update.go @@ -6,6 +6,7 @@ package pipeline import ( "context" + "fmt" apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" @@ -13,33 +14,32 @@ import ( "github.com/harness/gitness/types/enum" ) -// 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 pipeline. func (c *Controller) Update( ctx context.Context, session *auth.Session, spaceRef string, uid string, - in *UpdateInput) (*types.Pipeline, error) { + in *UpdateInput, +) (*types.Pipeline, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { - return nil, err + return nil, fmt.Errorf("could not find parent space: %w", err) } err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, uid, enum.PermissionPipelineEdit) if err != nil { - return nil, err + return nil, fmt.Errorf("could not authorize: %w", err) } pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, uid) if err != nil { - return nil, err + return nil, fmt.Errorf("could not find pipeline: %w", err) } if in.Description != "" { diff --git a/internal/api/controller/pipeline/wire.go b/internal/api/controller/pipeline/wire.go index 9702ebdd3..70bd3ff44 100644 --- a/internal/api/controller/pipeline/wire.go +++ b/internal/api/controller/pipeline/wire.go @@ -6,10 +6,11 @@ package pipeline import ( "github.com/google/wire" + "github.com/jmoiron/sqlx" + "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. diff --git a/internal/api/controller/secret/controller.go b/internal/api/controller/secret/controller.go new file mode 100644 index 000000000..8bbd2082d --- /dev/null +++ b/internal/api/controller/secret/controller.go @@ -0,0 +1,39 @@ +// 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 secret + +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 { + db *sqlx.DB + uidCheck check.PathUID + pathStore store.PathStore + secretStore store.SecretStore + authorizer authz.Authorizer + spaceStore store.SpaceStore +} + +func NewController( + db *sqlx.DB, + uidCheck check.PathUID, + authorizer authz.Authorizer, + pathStore store.PathStore, + secretStore store.SecretStore, + spaceStore store.SpaceStore, +) *Controller { + return &Controller{ + db: db, + uidCheck: uidCheck, + pathStore: pathStore, + secretStore: secretStore, + authorizer: authorizer, + spaceStore: spaceStore, + } +} diff --git a/internal/api/controller/secret/create.go b/internal/api/controller/secret/create.go new file mode 100644 index 000000000..d2573c08a --- /dev/null +++ b/internal/api/controller/secret/create.go @@ -0,0 +1,97 @@ +// 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 secret + +import ( + "context" + "fmt" + "strconv" + "strings" + "time" + + apiauth "github.com/harness/gitness/internal/api/auth" + "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 ( + // errSecretRequiresParent if the user tries to create a secret without a parent space. + errSecretRequiresParent = usererror.BadRequest( + "Parent space required - standalone secret are not supported.") +) + +type CreateInput struct { + Description string `json:"description"` + SpaceRef string `json:"space_ref"` // Ref of the parent space + UID string `json:"uid"` + Data string `json:"data"` +} + +// Create creates a new pipeline +func (c *Controller) Create(ctx context.Context, session *auth.Session, in *CreateInput) (*types.Secret, error) { + parentSpace, err := c.spaceStore.FindByRef(ctx, in.SpaceRef) + if err != nil { + return nil, fmt.Errorf("could not find parent by ref: %w", err) + } + + err = apiauth.CheckSecret(ctx, c.authorizer, session, parentSpace.Path, in.UID, enum.PermissionSecretEdit) + if err != nil { + return nil, err + } + + if err := c.sanitizeCreateInput(in); err != nil { + return nil, fmt.Errorf("failed to sanitize input: %w", err) + } + + var secret *types.Secret + 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() + secret = &types.Secret{ + Description: in.Description, + Data: in.Data, + SpaceID: parentSpace.ID, + UID: in.UID, + Created: now, + Updated: now, + Version: 0, + } + err = c.secretStore.Create(ctx, secret) + if err != nil { + return fmt.Errorf("secret creation failed: %w", err) + } + return nil + }) + + return secret, nil +} + +func (c *Controller) sanitizeCreateInput(in *CreateInput) error { + parentRefAsID, err := strconv.ParseInt(in.SpaceRef, 10, 64) + + if (err == nil && parentRefAsID <= 0) || (len(strings.TrimSpace(in.SpaceRef)) == 0) { + return errSecretRequiresParent + } + + 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 + } + + return nil +} diff --git a/internal/api/controller/secret/delete.go b/internal/api/controller/secret/delete.go new file mode 100644 index 000000000..baf76584a --- /dev/null +++ b/internal/api/controller/secret/delete.go @@ -0,0 +1,32 @@ +// 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 secret + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types/enum" +) + +// Delete deletes a secret. +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 + } + + err = apiauth.CheckSecret(ctx, c.authorizer, session, space.Path, uid, enum.PermissionSecretDelete) + if err != nil { + return err + } + err = c.secretStore.DeleteByUID(ctx, space.ID, uid) + if err != nil { + return fmt.Errorf("could not delete secret: %w", err) + } + return nil +} diff --git a/internal/api/controller/secret/find.go b/internal/api/controller/secret/find.go new file mode 100644 index 000000000..6f5278b65 --- /dev/null +++ b/internal/api/controller/secret/find.go @@ -0,0 +1,27 @@ +// 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 secret + +import ( + "context" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// Find finds a secret. +func (c *Controller) Find(ctx context.Context, session *auth.Session, spaceRef string, uid string) (*types.Secret, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, err + } + err = apiauth.CheckSecret(ctx, c.authorizer, session, space.Path, uid, enum.PermissionSecretView) + if err != nil { + return nil, err + } + return c.secretStore.FindByUID(ctx, space.ID, uid) +} diff --git a/internal/api/controller/secret/update.go b/internal/api/controller/secret/update.go new file mode 100644 index 000000000..c872dcd1a --- /dev/null +++ b/internal/api/controller/secret/update.go @@ -0,0 +1,56 @@ +// 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 secret + +import ( + "context" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// UpdateInput is used for updating a repo. +type UpdateInput struct { + Description string `json:"description"` + UID string `json:"uid"` + Data string `json:"data"` +} + +// Update updates a secret. +func (c *Controller) Update( + ctx context.Context, + session *auth.Session, + spaceRef string, + uid string, + in *UpdateInput) (*types.Secret, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, err + } + + err = apiauth.CheckSecret(ctx, c.authorizer, session, space.Path, uid, enum.PermissionSecretEdit) + if err != nil { + return nil, err + } + + secret, err := c.secretStore.FindByUID(ctx, space.ID, uid) + if err != nil { + return nil, err + } + + if in.Description != "" { + secret.Description = in.Description + } + if in.Data != "" { + secret.Data = in.Data // will get encrypted at db layer + } + if in.UID != "" { + secret.UID = in.UID + } + + return c.secretStore.Update(ctx, secret) +} diff --git a/internal/api/controller/secret/wire.go b/internal/api/controller/secret/wire.go new file mode 100644 index 000000000..09b46cc52 --- /dev/null +++ b/internal/api/controller/secret/wire.go @@ -0,0 +1,28 @@ +// 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 secret + +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, + secretStore store.SecretStore, + authorizer authz.Authorizer, + spaceStore store.SpaceStore, +) *Controller { + return NewController(db, uidCheck, authorizer, pathStore, secretStore, spaceStore) +} diff --git a/internal/api/controller/space/list_pipelines.go b/internal/api/controller/space/list_pipelines.go index 1d4d667f2..59e709e7a 100644 --- a/internal/api/controller/space/list_pipelines.go +++ b/internal/api/controller/space/list_pipelines.go @@ -9,6 +9,7 @@ import ( apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/store/database/dbtx" "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" ) @@ -18,21 +19,33 @@ func (c *Controller) ListPipelines(ctx context.Context, session *auth.Session, spaceRef string, filter *types.PipelineFilter) ([]types.Pipeline, int64, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { - return nil, 0, err - } - - count, err := c.pipelineStore.Count(ctx, space.ID, filter) - if err != nil { - return nil, 0, fmt.Errorf("failed to count child pipelnes: %w", err) + return nil, 0, fmt.Errorf("failed to find parent space: %w", err) } err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView, true) if err != nil { - return nil, 0, err + return nil, 0, fmt.Errorf("could not authorize: %w", err) } - pipelines, err := c.pipelineStore.List(ctx, space.ID, filter) + + var count int64 + var pipelines []types.Pipeline + + err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { + var dbErr error + count, dbErr = c.pipelineStore.Count(ctx, space.ID, filter) + if dbErr != nil { + return fmt.Errorf("failed to count child executions: %w", err) + } + + pipelines, dbErr = c.pipelineStore.List(ctx, space.ID, filter) + if dbErr != nil { + return fmt.Errorf("failed to list child executions: %w", err) + } + + return dbErr + }, dbtx.TxDefaultReadOnly) if err != nil { - return nil, 0, err + return pipelines, count, err } return pipelines, count, nil diff --git a/internal/api/handler/execution/create.go b/internal/api/handler/execution/create.go index a9d490b1d..0ba85da3f 100644 --- a/internal/api/handler/execution/create.go +++ b/internal/api/handler/execution/create.go @@ -11,9 +11,9 @@ import ( "github.com/harness/gitness/internal/api/controller/execution" "github.com/harness/gitness/internal/api/render" "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" ) -// HandleCreate returns a http.HandlerFunc that creates a new pipelinesitory. func HandleCreate(executionCtrl *execution.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -23,7 +23,7 @@ func HandleCreate(executionCtrl *execution.Controller) http.HandlerFunc { render.TranslatedUserError(w, err) return } - spaceRef, pipelineUID, err := SplitRef(pipelineRef) + spaceRef, pipelineUID, err := paths.DisectLeaf(pipelineRef) if err != nil { render.TranslatedUserError(w, err) return diff --git a/internal/api/handler/execution/delete.go b/internal/api/handler/execution/delete.go index 218d6d31c..f52f413f9 100644 --- a/internal/api/handler/execution/delete.go +++ b/internal/api/handler/execution/delete.go @@ -10,11 +10,9 @@ import ( "github.com/harness/gitness/internal/api/controller/execution" "github.com/harness/gitness/internal/api/render" "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" ) -/* - * Deletes an execution - */ func HandleDelete(executionCtrl *execution.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -24,7 +22,7 @@ func HandleDelete(executionCtrl *execution.Controller) http.HandlerFunc { render.TranslatedUserError(w, err) return } - spaceRef, pipelineUID, err := SplitRef(pipelineRef) + spaceRef, pipelineUID, err := paths.DisectLeaf(pipelineRef) if err != nil { render.TranslatedUserError(w, err) return diff --git a/internal/api/handler/execution/find.go b/internal/api/handler/execution/find.go index fec7d2c26..8df28e39f 100644 --- a/internal/api/handler/execution/find.go +++ b/internal/api/handler/execution/find.go @@ -5,16 +5,14 @@ package execution import ( - "errors" "net/http" - "strings" "github.com/harness/gitness/internal/api/controller/execution" "github.com/harness/gitness/internal/api/render" "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" ) -// HandleFind finds a pipeline from the database. func HandleFind(executionCtrl *execution.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -29,7 +27,7 @@ func HandleFind(executionCtrl *execution.Controller) http.HandlerFunc { render.TranslatedUserError(w, err) return } - spaceRef, pipelineUID, err := SplitRef(pipelineRef) + spaceRef, pipelineUID, err := paths.DisectLeaf(pipelineRef) if err != nil { render.TranslatedUserError(w, err) return @@ -44,18 +42,3 @@ func HandleFind(executionCtrl *execution.Controller) http.HandlerFunc { render.JSON(w, http.StatusOK, execution) } } - -// SplitRef splits apart a ref into two parts, otherwise returns an error -// For example: path/to/space/uid will get split into path/to/space and uid -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 -} diff --git a/internal/api/handler/execution/list.go b/internal/api/handler/execution/list.go index a3c7d377f..9a7540b8a 100644 --- a/internal/api/handler/execution/list.go +++ b/internal/api/handler/execution/list.go @@ -10,9 +10,9 @@ import ( "github.com/harness/gitness/internal/api/controller/execution" "github.com/harness/gitness/internal/api/render" "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" ) -// HandleListRepos writes json-encoded list of repos in the request body. func HandleList(executionCtrl *execution.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -22,7 +22,7 @@ func HandleList(executionCtrl *execution.Controller) http.HandlerFunc { render.TranslatedUserError(w, err) return } - spaceRef, pipelineUID, err := SplitRef(pipelineRef) + spaceRef, pipelineUID, err := paths.DisectLeaf(pipelineRef) if err != nil { render.TranslatedUserError(w, err) return diff --git a/internal/api/handler/execution/update.go b/internal/api/handler/execution/update.go index 994918a76..4bc61ad2f 100644 --- a/internal/api/handler/execution/update.go +++ b/internal/api/handler/execution/update.go @@ -11,11 +11,9 @@ import ( "github.com/harness/gitness/internal/api/controller/execution" "github.com/harness/gitness/internal/api/render" "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" ) -/* - * Updates an existing execution - */ func HandleUpdate(executionCtrl *execution.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -33,9 +31,10 @@ func HandleUpdate(executionCtrl *execution.Controller) http.HandlerFunc { render.TranslatedUserError(w, err) return } - spaceRef, pipelineUID, err := SplitRef(pipelineRef) + spaceRef, pipelineUID, err := paths.DisectLeaf(pipelineRef) if err != nil { render.TranslatedUserError(w, err) + return } n, err := request.GetExecutionNumberFromPath(r) if err != nil { diff --git a/internal/api/handler/pipeline/create.go b/internal/api/handler/pipeline/create.go index ac1641f93..df1f49a9c 100644 --- a/internal/api/handler/pipeline/create.go +++ b/internal/api/handler/pipeline/create.go @@ -13,7 +13,6 @@ import ( "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() diff --git a/internal/api/handler/pipeline/delete.go b/internal/api/handler/pipeline/delete.go index 057f6c0a5..5dbd30a29 100644 --- a/internal/api/handler/pipeline/delete.go +++ b/internal/api/handler/pipeline/delete.go @@ -10,11 +10,9 @@ import ( "github.com/harness/gitness/internal/api/controller/pipeline" "github.com/harness/gitness/internal/api/render" "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" ) -/* - * Deletes a pipeline. - */ func HandleDelete(pipelineCtrl *pipeline.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -24,7 +22,7 @@ func HandleDelete(pipelineCtrl *pipeline.Controller) http.HandlerFunc { render.TranslatedUserError(w, err) return } - spaceRef, pipelineUID, err := SplitRef(pipelineRef) + spaceRef, pipelineUID, err := paths.DisectLeaf(pipelineRef) if err != nil { render.TranslatedUserError(w, err) return diff --git a/internal/api/handler/pipeline/find.go b/internal/api/handler/pipeline/find.go index 65091e0fb..d0fc11d3a 100644 --- a/internal/api/handler/pipeline/find.go +++ b/internal/api/handler/pipeline/find.go @@ -5,16 +5,14 @@ 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" + "github.com/harness/gitness/internal/paths" ) -// HandleFind finds a pipeline from the database. func HandleFind(pipelineCtrl *pipeline.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -24,9 +22,10 @@ func HandleFind(pipelineCtrl *pipeline.Controller) http.HandlerFunc { render.TranslatedUserError(w, err) return } - spaceRef, pipelineUID, err := SplitRef(pipelineRef) + spaceRef, pipelineUID, err := paths.DisectLeaf(pipelineRef) if err != nil { render.TranslatedUserError(w, err) + return } pipeline, err := pipelineCtrl.Find(ctx, session, spaceRef, pipelineUID) @@ -38,18 +37,3 @@ func HandleFind(pipelineCtrl *pipeline.Controller) http.HandlerFunc { render.JSON(w, http.StatusOK, pipeline) } } - -// SplitRef splits apart a ref into two parts, otherwise returns an error -// For example: path/to/space/uid will get split into path/to/space and uid -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 -} diff --git a/internal/api/handler/pipeline/update.go b/internal/api/handler/pipeline/update.go index 42d402879..d90f5d9fa 100644 --- a/internal/api/handler/pipeline/update.go +++ b/internal/api/handler/pipeline/update.go @@ -11,11 +11,9 @@ import ( "github.com/harness/gitness/internal/api/controller/pipeline" "github.com/harness/gitness/internal/api/render" "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" ) -/* - * Updates an existing pipeline. - */ func HandleUpdate(pipelineCtrl *pipeline.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -33,9 +31,10 @@ func HandleUpdate(pipelineCtrl *pipeline.Controller) http.HandlerFunc { render.TranslatedUserError(w, err) return } - spaceRef, pipelineUID, err := SplitRef(pipelineRef) + spaceRef, pipelineUID, err := paths.DisectLeaf(pipelineRef) if err != nil { render.TranslatedUserError(w, err) + return } pipeline, err := pipelineCtrl.Update(ctx, session, spaceRef, pipelineUID, in) diff --git a/internal/api/handler/secret/create.go b/internal/api/handler/secret/create.go new file mode 100644 index 000000000..460b1cd8a --- /dev/null +++ b/internal/api/handler/secret/create.go @@ -0,0 +1,37 @@ +// 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 secret + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/internal/api/controller/secret" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" +) + +// HandleCreate returns a http.HandlerFunc that creates a new secretsitory. +func HandleCreate(secretCtrl *secret.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + + in := new(secret.CreateInput) + err := json.NewDecoder(r.Body).Decode(in) + if err != nil { + render.BadRequestf(w, "Invalid Request Body: %s.", err) + return + } + + secret, err := secretCtrl.Create(ctx, session, in) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusCreated, secret.Copy()) + } +} diff --git a/internal/api/handler/secret/delete.go b/internal/api/handler/secret/delete.go new file mode 100644 index 000000000..681f6bd34 --- /dev/null +++ b/internal/api/handler/secret/delete.go @@ -0,0 +1,42 @@ +// 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 secret + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/secret" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" +) + +/* + * Deletes a secret. + */ +func HandleDelete(secretCtrl *secret.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + secretRef, err := request.GetSecretRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + spaceRef, secretUID, err := paths.DisectLeaf(secretRef) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + err = secretCtrl.Delete(ctx, session, spaceRef, secretUID) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.DeleteSuccessful(w) + } +} diff --git a/internal/api/handler/secret/find.go b/internal/api/handler/secret/find.go new file mode 100644 index 000000000..37ce5bfb7 --- /dev/null +++ b/internal/api/handler/secret/find.go @@ -0,0 +1,39 @@ +// 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 secret + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/secret" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" +) + +// HandleFind finds a secret from the database. +func HandleFind(secretCtrl *secret.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + secretRef, err := request.GetSecretRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + spaceRef, secretUID, err := paths.DisectLeaf(secretRef) + if err != nil { + render.TranslatedUserError(w, err) + } + + secret, err := secretCtrl.Find(ctx, session, spaceRef, secretUID) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusOK, secret.Copy()) + } +} diff --git a/internal/api/handler/secret/update.go b/internal/api/handler/secret/update.go new file mode 100644 index 000000000..b6fbaaedd --- /dev/null +++ b/internal/api/handler/secret/update.go @@ -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 secret + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/internal/api/controller/secret" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/internal/paths" +) + +/* + * Updates an existing secret. + */ +func HandleUpdate(secretCtrl *secret.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + + in := new(secret.UpdateInput) + err := json.NewDecoder(r.Body).Decode(in) + if err != nil { + render.BadRequestf(w, "Invalid Request Body: %s.", err) + return + } + + secretRef, err := request.GetSecretRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + spaceRef, secretUID, err := paths.DisectLeaf(secretRef) + if err != nil { + render.TranslatedUserError(w, err) + } + + secret, err := secretCtrl.Update(ctx, session, spaceRef, secretUID, in) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusOK, secret.Copy()) + } +} diff --git a/internal/api/handler/space/list_pipelines.go b/internal/api/handler/space/list_pipelines.go index 432adb797..0bdd02633 100644 --- a/internal/api/handler/space/list_pipelines.go +++ b/internal/api/handler/space/list_pipelines.go @@ -12,7 +12,6 @@ import ( "github.com/harness/gitness/internal/api/request" ) -// HandleListRepos writes json-encoded list of repos in the request body. func HandleListPipelines(spaceCtrl *space.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() diff --git a/internal/api/openapi/openapi.go b/internal/api/openapi/openapi.go index 5110b9377..5ea384d0e 100644 --- a/internal/api/openapi/openapi.go +++ b/internal/api/openapi/openapi.go @@ -42,6 +42,7 @@ func Generate() *openapi3.Spec { spaceOperations(&reflector) repoOperations(&reflector) pipelineOperations(&reflector) + secretOperations(&reflector) resourceOperations(&reflector) pullReqOperations(&reflector) webhookOperations(&reflector) diff --git a/internal/api/openapi/pipeline.go b/internal/api/openapi/pipeline.go index 64324fc53..ece281796 100644 --- a/internal/api/openapi/pipeline.go +++ b/internal/api/openapi/pipeline.go @@ -15,31 +15,37 @@ import ( "github.com/swaggest/openapi-go/openapi3" ) -type createExecutionRequest struct { +type pipelineRequest struct { Ref string `path:"pipeline_ref"` +} + +type executionRequest struct { + pipelineRequest + Number string `path:"execution_number"` +} + +type createExecutionRequest struct { + pipelineRequest execution.CreateInput } type createPipelineRequest struct { - Ref string `path:"pipeline_ref"` pipeline.CreateInput } type getExecutionRequest struct { - Ref string `path:"pipeline_ref"` - Number string `path:"execution_number"` + executionRequest +} + +type getPipelineRequest struct { + pipelineRequest } type updateExecutionRequest struct { - Ref string `path:"pipeline_ref"` - Number string `path:"execution_number"` + executionRequest execution.UpdateInput } -type pipelineRequest struct { - Ref string `path:"pipeline_ref"` -} - type updatePipelineRequest struct { pipelineRequest pipeline.UpdateInput @@ -60,7 +66,7 @@ func pipelineOperations(reflector *openapi3.Reflector) { opFind := openapi3.Operation{} opFind.WithTags("pipeline") opFind.WithMapOfAnything(map[string]interface{}{"operationId": "findPipeline"}) - _ = reflector.SetRequest(&opFind, new(pipelineRequest), http.MethodGet) + _ = reflector.SetRequest(&opFind, new(getPipelineRequest), http.MethodGet) _ = reflector.SetJSONResponse(&opFind, new(types.Pipeline), http.StatusOK) _ = reflector.SetJSONResponse(&opFind, new(usererror.Error), http.StatusInternalServerError) _ = reflector.SetJSONResponse(&opFind, new(usererror.Error), http.StatusUnauthorized) @@ -71,7 +77,7 @@ func pipelineOperations(reflector *openapi3.Reflector) { opDelete := openapi3.Operation{} opDelete.WithTags("pipeline") opDelete.WithMapOfAnything(map[string]interface{}{"operationId": "deletePipeline"}) - _ = reflector.SetRequest(&opDelete, new(pipelineRequest), http.MethodDelete) + _ = reflector.SetRequest(&opDelete, new(getPipelineRequest), http.MethodDelete) _ = reflector.SetJSONResponse(&opDelete, nil, http.StatusNoContent) _ = reflector.SetJSONResponse(&opDelete, new(usererror.Error), http.StatusInternalServerError) _ = reflector.SetJSONResponse(&opDelete, new(usererror.Error), http.StatusUnauthorized) @@ -139,8 +145,7 @@ func pipelineOperations(reflector *openapi3.Reflector) { executionList := openapi3.Operation{} executionList.WithTags("pipeline") executionList.WithMapOfAnything(map[string]interface{}{"operationId": "listExecutions"}) - executionList.WithParameters(queryParameterQueryRepo, queryParameterSortRepo, queryParameterOrder, - queryParameterPage, queryParameterLimit) + executionList.WithParameters(queryParameterPage, queryParameterLimit) _ = reflector.SetRequest(&executionList, new(pipelineRequest), http.MethodGet) _ = reflector.SetJSONResponse(&executionList, []types.Execution{}, http.StatusOK) _ = reflector.SetJSONResponse(&executionList, new(usererror.Error), http.StatusInternalServerError) diff --git a/internal/api/openapi/secret.go b/internal/api/openapi/secret.go new file mode 100644 index 000000000..f12fb77ea --- /dev/null +++ b/internal/api/openapi/secret.go @@ -0,0 +1,79 @@ +// 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 openapi + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/secret" + "github.com/harness/gitness/internal/api/usererror" + "github.com/harness/gitness/types" + + "github.com/swaggest/openapi-go/openapi3" +) + +type createSecretRequest struct { + secret.CreateInput +} + +type secretRequest struct { + Ref string `path:"secret_ref"` +} + +type getSecretRequest struct { + secretRequest +} + +type updateSecretRequest struct { + secretRequest + secret.UpdateInput +} + +func secretOperations(reflector *openapi3.Reflector) { + opCreate := openapi3.Operation{} + opCreate.WithTags("secret") + opCreate.WithMapOfAnything(map[string]interface{}{"operationId": "createSecret"}) + _ = reflector.SetRequest(&opCreate, new(createSecretRequest), http.MethodPost) + _ = reflector.SetJSONResponse(&opCreate, new(types.Secret), 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, "/secrets", opCreate) + + opFind := openapi3.Operation{} + opFind.WithTags("secret") + opFind.WithMapOfAnything(map[string]interface{}{"operationId": "findSecret"}) + _ = reflector.SetRequest(&opFind, new(getSecretRequest), http.MethodGet) + _ = reflector.SetJSONResponse(&opFind, new(types.Secret), 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, "/secrets/{secret_ref}", opFind) + + opDelete := openapi3.Operation{} + opDelete.WithTags("secret") + opDelete.WithMapOfAnything(map[string]interface{}{"operationId": "deleteSecret"}) + _ = reflector.SetRequest(&opDelete, new(getSecretRequest), 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, "/secrets/{secret_ref}", opDelete) + + opUpdate := openapi3.Operation{} + opUpdate.WithTags("secret") + opUpdate.WithMapOfAnything(map[string]interface{}{"operationId": "updateSecret"}) + _ = reflector.SetRequest(&opUpdate, new(updateSecretRequest), http.MethodPatch) + _ = reflector.SetJSONResponse(&opUpdate, new(types.Secret), 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, "/secrets/{secret_ref}", opUpdate) +} diff --git a/internal/api/openapi/space.go b/internal/api/openapi/space.go index e7509c12b..0a043ae47 100644 --- a/internal/api/openapi/space.go +++ b/internal/api/openapi/space.go @@ -233,8 +233,7 @@ func spaceOperations(reflector *openapi3.Reflector) { opPipelines := openapi3.Operation{} opPipelines.WithTags("space") opPipelines.WithMapOfAnything(map[string]interface{}{"operationId": "listPipelines"}) - opPipelines.WithParameters(queryParameterQueryRepo, queryParameterSortRepo, queryParameterOrder, - queryParameterPage, queryParameterLimit) + opPipelines.WithParameters(queryParameterQueryRepo, queryParameterPage, queryParameterLimit) _ = reflector.SetRequest(&opPipelines, new(spaceRequest), http.MethodGet) _ = reflector.SetJSONResponse(&opPipelines, []types.Pipeline{}, http.StatusOK) _ = reflector.SetJSONResponse(&opPipelines, new(usererror.Error), http.StatusInternalServerError) diff --git a/internal/api/request/pipeline.go b/internal/api/request/pipeline.go index 6e2f0698d..812e14af4 100644 --- a/internal/api/request/pipeline.go +++ b/internal/api/request/pipeline.go @@ -7,7 +7,6 @@ package request import ( "net/http" "net/url" - "strconv" "github.com/harness/gitness/types" ) @@ -29,18 +28,8 @@ func GetPipelinePathRefFromPath(r *http.Request) (string, error) { } func GetExecutionNumberFromPath(r *http.Request) (int64, error) { - rawRef, err := PathParamOrError(r, ExecutionNumber) - if err != nil { - return 0, err - } + return PathParamAsPositiveInt64(r, ExecutionNumber) - n, err := strconv.Atoi(rawRef) - if err != nil { - return 0, err - } - - // paths are unescaped - return int64(n), nil } func GetPipelineUIDFromPath(r *http.Request) (string, error) { diff --git a/internal/api/request/secret.go b/internal/api/request/secret.go new file mode 100644 index 000000000..d47b140a5 --- /dev/null +++ b/internal/api/request/secret.go @@ -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 request + +import ( + "net/http" + "net/url" + + "github.com/harness/gitness/types" +) + +const ( + SecretRef = "secret_ref" +) + +func GetSecretRefFromPath(r *http.Request) (string, error) { + rawRef, err := PathParamOrError(r, SecretRef) + if err != nil { + return "", err + } + + // paths are unescaped + return url.PathUnescape(rawRef) +} + +// ParseExecutionFilter extracts the execution filter from the url. +func ParseSecretFilter(r *http.Request) *types.SecretFilter { + return &types.SecretFilter{ + Page: ParsePage(r), + Size: ParseLimit(r), + } +} diff --git a/internal/api/request/util.go b/internal/api/request/util.go index 8e3b47459..709253395 100644 --- a/internal/api/request/util.go +++ b/internal/api/request/util.go @@ -166,6 +166,26 @@ func GetRemainderFromPath(r *http.Request) (string, error) { return PathParamOrError(r, PathParamRemainder) } +// PipelineFilter stores pipeline query parameters. +type Pagination struct { + Page int `json:"page"` + Size int `json:"size"` + Query string `json:"query"` + Sort string `json:"sort"` + Order enum.Order `json:"order"` +} + +// ParseExecutionFilter extracts the execution filter from the url. +func ParseFilterFromRequest(r *http.Request) Pagination { + return Pagination{ + Page: ParsePage(r), + Size: ParseLimit(r), + Query: ParseQuery(r), + Sort: ParseSort(r), + Order: ParseOrder(r), + } +} + // ParseQuery extracts the query parameter from the url. func ParseQuery(r *http.Request) string { return r.URL.Query().Get(QueryParamQuery) diff --git a/internal/auth/authz/membership.go b/internal/auth/authz/membership.go index 9610d1b5c..65bd4e398 100644 --- a/internal/auth/authz/membership.go +++ b/internal/auth/authz/membership.go @@ -66,6 +66,9 @@ func (a *MembershipAuthorizer) Check( case enum.ResourceTypePipeline: spaceRef = scope.SpacePath + case enum.ResourceTypeSecret: + spaceRef = scope.SpacePath + case enum.ResourceTypeUser: // a user is allowed to view / edit themselves if resource.Name == session.Principal.UID && diff --git a/internal/router/api.go b/internal/router/api.go index d6ce808e0..882ef5a36 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -16,6 +16,7 @@ import ( "github.com/harness/gitness/internal/api/controller/principal" "github.com/harness/gitness/internal/api/controller/pullreq" "github.com/harness/gitness/internal/api/controller/repo" + "github.com/harness/gitness/internal/api/controller/secret" "github.com/harness/gitness/internal/api/controller/serviceaccount" "github.com/harness/gitness/internal/api/controller/space" "github.com/harness/gitness/internal/api/controller/user" @@ -29,6 +30,7 @@ import ( handlerpullreq "github.com/harness/gitness/internal/api/handler/pullreq" handlerrepo "github.com/harness/gitness/internal/api/handler/repo" "github.com/harness/gitness/internal/api/handler/resource" + handlersecret "github.com/harness/gitness/internal/api/handler/secret" handlerserviceaccount "github.com/harness/gitness/internal/api/handler/serviceaccount" handlerspace "github.com/harness/gitness/internal/api/handler/space" "github.com/harness/gitness/internal/api/handler/system" @@ -58,7 +60,7 @@ type APIHandler interface { var ( // terminatedPathPrefixesAPI is the list of prefixes that will require resolving terminated paths. - terminatedPathPrefixesAPI = []string{"/v1/spaces/", "/v1/repos/", "/v1/pipelines/"} + terminatedPathPrefixesAPI = []string{"/v1/spaces/", "/v1/repos/", "/v1/pipelines/", "/v1/secrets/"} ) // NewAPIHandler returns a new APIHandler. @@ -69,6 +71,7 @@ func NewAPIHandler( executionCtrl *execution.Controller, spaceCtrl *space.Controller, pipelineCtrl *pipeline.Controller, + secretCtrl *secret.Controller, pullreqCtrl *pullreq.Controller, webhookCtrl *webhook.Controller, githookCtrl *controllergithook.Controller, @@ -98,7 +101,7 @@ func NewAPIHandler( r.Use(middlewareauthn.Attempt(authenticator, authn.SourceRouterAPI)) r.Route("/v1", func(r chi.Router) { - setupRoutesV1(r, config, repoCtrl, executionCtrl, pipelineCtrl, spaceCtrl, pullreqCtrl, webhookCtrl, githookCtrl, + setupRoutesV1(r, repoCtrl, executionCtrl, pipelineCtrl, secretCtrl, spaceCtrl, pullreqCtrl, webhookCtrl, githookCtrl, saCtrl, userCtrl, principalCtrl, checkCtrl) }) @@ -120,10 +123,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, + secretCtrl *secret.Controller, spaceCtrl *space.Controller, pullreqCtrl *pullreq.Controller, webhookCtrl *webhook.Controller, @@ -136,6 +139,7 @@ func setupRoutesV1(r chi.Router, setupSpaces(r, spaceCtrl) setupRepos(r, repoCtrl, pullreqCtrl, webhookCtrl, checkCtrl) setupPipelines(r, pipelineCtrl, executionCtrl) + setupSecrets(r, secretCtrl) setupUser(r, userCtrl) setupServiceAccounts(r, saCtrl) setupPrincipals(r, principalCtrl) @@ -290,6 +294,18 @@ func setupPipelines(r chi.Router, pipelineCtrl *pipeline.Controller, executionCt }) } +func setupSecrets(r chi.Router, secretCtrl *secret.Controller) { + r.Route("/secrets", func(r chi.Router) { + // Create takes path and parentId via body, not uri + r.Post("/", handlersecret.HandleCreate(secretCtrl)) + r.Route(fmt.Sprintf("/{%s}", request.SecretRef), func(r chi.Router) { + r.Get("/", handlersecret.HandleFind(secretCtrl)) + r.Patch("/", handlersecret.HandleUpdate(secretCtrl)) + r.Delete("/", handlersecret.HandleDelete(secretCtrl)) + }) + }) +} + func setupExecutions(r chi.Router, pipelineCtrl *pipeline.Controller, executionCtrl *execution.Controller) { r.Route("/executions", func(r chi.Router) { r.Get("/", handlerexecution.HandleList(executionCtrl)) diff --git a/internal/router/wire.go b/internal/router/wire.go index bd834b12e..d01d8e6d3 100644 --- a/internal/router/wire.go +++ b/internal/router/wire.go @@ -13,6 +13,7 @@ import ( "github.com/harness/gitness/internal/api/controller/principal" "github.com/harness/gitness/internal/api/controller/pullreq" "github.com/harness/gitness/internal/api/controller/repo" + "github.com/harness/gitness/internal/api/controller/secret" "github.com/harness/gitness/internal/api/controller/serviceaccount" "github.com/harness/gitness/internal/api/controller/space" "github.com/harness/gitness/internal/api/controller/user" @@ -62,6 +63,7 @@ func ProvideAPIHandler( executionCtrl *execution.Controller, spaceCtrl *space.Controller, pipelineCtrl *pipeline.Controller, + secretCtrl *secret.Controller, pullreqCtrl *pullreq.Controller, webhookCtrl *webhook.Controller, githookCtrl *githook.Controller, @@ -70,8 +72,8 @@ func ProvideAPIHandler( principalCtrl principal.Controller, checkCtrl *check.Controller, ) APIHandler { - return NewAPIHandler(config, authenticator, repoCtrl, executionCtrl, spaceCtrl, pipelineCtrl, pullreqCtrl, - webhookCtrl, githookCtrl, saCtrl, userCtrl, principalCtrl, checkCtrl) + return NewAPIHandler(config, authenticator, repoCtrl, executionCtrl, spaceCtrl, pipelineCtrl, secretCtrl, + pullreqCtrl, webhookCtrl, githookCtrl, saCtrl, userCtrl, principalCtrl, checkCtrl) } func ProvideWebHandler(config *types.Config) WebHandler { diff --git a/internal/store/database.go b/internal/store/database.go index 3dcf7f750..951ee12b8 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -441,132 +441,73 @@ type ( } PipelineStore interface { // Find returns a pipeline given a pipeline ID from the datastore. - Find(context.Context, int64) (*types.Pipeline, error) + Find(ctx context.Context, id int64) (*types.Pipeline, error) // FindByUID returns a pipeline with a given UID in a space - FindByUID(context.Context, int64, string) (*types.Pipeline, error) + FindByUID(ctx context.Context, id int64, uid string) (*types.Pipeline, error) // Create creates a new pipeline in the datastore. - Create(context.Context, *types.Pipeline) error + Create(ctx context.Context, pipeline *types.Pipeline) error // Update tries to update a pipeline in the datastore with optimistic locking. - Update(context.Context, *types.Pipeline) (*types.Pipeline, error) + Update(ctx context.Context, pipeline *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) + List(ctx context.Context, spaceID int64, filter *types.PipelineFilter) ([]types.Pipeline, error) // Delete deletes a pipeline ID from the datastore. - Delete(context.Context, int64) error + Delete(ctx context.Context, id int64) error // Count the number of pipelines in a space matching the given filter. - Count(ctx context.Context, parentID int64, opts *types.PipelineFilter) (int64, error) + Count(ctx context.Context, spaceID int64, filter *types.PipelineFilter) (int64, error) // DeleteByUID deletes a pipeline with a given UID in a space - DeleteByUID(context.Context, int64, string) error + DeleteByUID(ctx context.Context, spaceID int64, uid string) error // Incremenet increments the sequence number of the pipeline - Increment(context.Context, *types.Pipeline) (*types.Pipeline, error) + Increment(ctx context.Context, pipeline *types.Pipeline) (*types.Pipeline, error) } SecretStore interface { // Find returns a secret given an ID - Find(context.Context, int64) (*types.Secret, error) + Find(ctx context.Context, id int64) (*types.Secret, error) // FindByUID returns a secret given a space ID and a UID - FindByUID(context.Context, int64, string) (*types.Secret, error) + FindByUID(ctx context.Context, spaceID int64, uid string) (*types.Secret, error) // Create creates a new secret - Create(context.Context, *types.Secret) error + Create(ctx context.Context, secret *types.Secret) error - // Update tries to update an execution in the datastore with optimistic locking. - Update(context.Context, *types.Secret) (*types.Secret, error) + // Update tries to update a secret. + Update(ctx context.Context, secret *types.Secret) (*types.Secret, error) - // Delete deletes an execution given a pipeline ID and an execution number - Delete(context.Context, int64) error + // Delete deletes a secret given an ID. + Delete(ctx context.Context, id int64) error - // Delete deletes an execution given a pipeline ID and an execution number - DeleteByUID(context.Context, int64, string) error + // DeleteByUID deletes a secret given a space ID and a uid + DeleteByUID(ctx context.Context, spaceID int64, uid string) error - // List lists the executions for a given pipeline ID - List(context.Context, int64, *types.SecretFilter) ([]types.Secret, error) + // List lists the secrets in a given space + List(ctx context.Context, spaceID int64, filter *types.SecretFilter) ([]types.Secret, error) } ExecutionStore interface { // Find returns a execution given a pipeline and an execution number - Find(context.Context, int64, int64) (*types.Execution, error) + Find(ctx context.Context, pipelineID int64, num int64) (*types.Execution, error) // Create creates a new execution in the datastore. - Create(context.Context, *types.Execution) error + Create(ctx context.Context, execution *types.Execution) error // Update tries to update an execution in the datastore with optimistic locking. - Update(context.Context, *types.Execution) (*types.Execution, error) + Update(ctx context.Context, execution *types.Execution) (*types.Execution, error) // List lists the executions for a given pipeline ID - List(context.Context, int64, *types.ExecutionFilter) ([]types.Execution, error) + List(ctx context.Context, pipelineID int64, filter *types.ExecutionFilter) ([]types.Execution, error) // Delete deletes an execution given a pipeline ID and an execution number - Delete(context.Context, int64, int64) error + Delete(ctx context.Context, pipelineID int64, num int64) error // Count the number of executions in a space matching the given filter. Count(ctx context.Context, parentID int64, opts *types.ExecutionFilter) (int64, error) - - // 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) } ) diff --git a/internal/store/database/execution.go b/internal/store/database/execution.go index 779aefe1a..8e0e53fdb 100644 --- a/internal/store/database/execution.go +++ b/internal/store/database/execution.go @@ -33,154 +33,118 @@ type executionStore struct { const ( executionQueryBase = ` - SELECT - execution_id, - execution_pipeline_id, - 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 - FROM executions - ` + SELECT` + executionColumns + ` + FROM executions` executionColumns = ` - execution_id, - execution_pipeline_id, - 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 + execution_id + ,execution_pipeline_id + ,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 ` executionInsertStmt = ` INSERT INTO executions ( - execution_pipeline_id, - 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 + execution_pipeline_id + ,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 ) VALUES ( - :execution_pipeline_id, - :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 + :execution_pipeline_id + ,: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 ) RETURNING execution_id` executionUpdateStmt = ` diff --git a/internal/store/database/migrate/ci/ci_migrations.sql b/internal/store/database/migrate/ci/ci_migrations.sql index a87a58055..57df6eaf5 100644 --- a/internal/store/database/migrate/ci/ci_migrations.sql +++ b/internal/store/database/migrate/ci/ci_migrations.sql @@ -1,7 +1,7 @@ CREATE TABLE IF NOT EXISTS pipelines ( pipeline_id INTEGER PRIMARY KEY AUTOINCREMENT, pipeline_description TEXT, - pipeline_parent_id INTEGER NOT NULL, + pipeline_space_id INTEGER NOT NULL, pipeline_uid TEXT NOT NULL, pipeline_seq INTEGER NOT NULL DEFAULT 0, pipeline_repo_id INTEGER, @@ -9,27 +9,27 @@ CREATE TABLE IF NOT EXISTS pipelines ( pipeline_repo_name TEXT, pipeline_default_branch TEXT, pipeline_config_path TEXT NOT NULL, - pipeline_created INTEGER, - pipeline_updated INTEGER, - pipeline_version INTEGER, + pipeline_created INTEGER NOT NULL, + pipeline_updated INTEGER NOT NULL, + pipeline_version INTEGER NOT NULL, -- Ensure unique combination of UID and ParentID - UNIQUE (pipeline_parent_id, pipeline_uid), + UNIQUE (pipeline_space_id, pipeline_uid), -- Foreign key to spaces table - CONSTRAINT fk_pipeline_parent_id FOREIGN KEY (pipeline_parent_id) + CONSTRAINT fk_pipeline_space_id FOREIGN KEY (pipeline_space_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) + CONSTRAINT fk_pipelines_repo_id FOREIGN KEY (pipeline_repo_id) REFERENCES repositories (repo_id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS secrets ( +CREATE TABLE IF NOT EXISTS executions ( execution_id INTEGER PRIMARY KEY AUTOINCREMENT, execution_pipeline_id INTEGER NOT NULL, execution_repo_id INTEGER, @@ -62,35 +62,41 @@ CREATE TABLE IF NOT EXISTS secrets ( execution_debug BOOLEAN NOT NULL DEFAULT 0, execution_started INTEGER, execution_finished INTEGER, - execution_created INTEGER, - execution_updated INTEGER, - execution_version INTEGER, + execution_created INTEGER NOT NULL, + execution_updated INTEGER NOT NULL, + execution_version INTEGER NOT NULL, -- 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) + CONSTRAINT fk_executions_pipeline_id FOREIGN KEY (execution_pipeline_id) REFERENCES pipelines (pipeline_id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE + + -- Foreign key to repositories table + CONSTRAINT fk_executions_repo_id FOREIGN KEY (execution_repo_id) + REFERENCES repositories (repo_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS secrets ( secret_id INTEGER PRIMARY KEY AUTOINCREMENT, secret_uid TEXT NOT NULL, - secret_parent_id INTEGER NOT NULL, - secret_description TEXT NOT NULL, + secret_space_id INTEGER NOT NULL, + secret_description TEXT, secret_data BLOB NOT NULL, - secret_created INTEGER, - secret_updated INTEGER, - secret_version INTEGER, + secret_created INTEGER NOT NULL, + secret_updated INTEGER NOT NULL, + secret_version INTEGER NOT NULL, -- Ensure unique combination of space ID and UID - UNIQUE (secret_parent_id, secret_uid), + UNIQUE (secret_space_id, secret_uid), -- Foreign key to spaces table - CONSTRAINT fk_secret_parent_id FOREIGN KEY (secret_parent_id) + CONSTRAINT fk_secrets_space_id FOREIGN KEY (secret_space_id) REFERENCES spaces (space_id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE diff --git a/internal/store/database/pipeline.go b/internal/store/database/pipeline.go index 09f322b1c..81a8f0b4f 100644 --- a/internal/store/database/pipeline.go +++ b/internal/store/database/pipeline.go @@ -15,6 +15,7 @@ import ( "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" ) @@ -23,56 +24,43 @@ 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 - ` + SELECT` + + pipelineColumns + ` + 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 + pipeline_id + ,pipeline_description + ,pipeline_space_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 + pipeline_description + ,pipeline_space_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_space_id, :pipeline_uid, :pipeline_seq, :pipeline_repo_id, @@ -89,7 +77,7 @@ const ( UPDATE pipelines SET pipeline_description = :pipeline_description, - pipeline_parent_id = :pipeline_parent_id, + pipeline_space_id = :pipeline_space_id, pipeline_uid = :pipeline_uid, pipeline_seq = :pipeline_seq, pipeline_repo_id = :pipeline_repo_id, @@ -130,7 +118,7 @@ func (s *pipelineStore) Find(ctx context.Context, id int64) (*types.Pipeline, er // 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` + WHERE pipeline_space_id = $1 AND pipeline_uid = $2` db := dbtx.GetAccessor(ctx, s.db) dst := new(types.Pipeline) @@ -192,7 +180,7 @@ func (s *pipelineStore) List(ctx context.Context, parentID int64, opts *types.Pi stmt := database.Builder. Select(pipelineColumns). From("pipelines"). - Where("pipeline_parent_id = ?", fmt.Sprint(parentID)) + Where("pipeline_space_id = ?", fmt.Sprint(parentID)) if opts.Query != "" { stmt = stmt.Where("LOWER(pipeline_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(opts.Query))) @@ -221,7 +209,7 @@ func (s *pipelineStore) Count(ctx context.Context, parentID int64, opts *types.P stmt := database.Builder. Select("count(*)"). From("pipelines"). - Where("pipeline_parent_id = ?", parentID) + Where("pipeline_space_id = ?", parentID) if opts.Query != "" { stmt = stmt.Where("pipeline_uid LIKE ?", fmt.Sprintf("%%%s%%", opts.Query)) @@ -261,7 +249,7 @@ func (s *pipelineStore) Delete(ctx context.Context, id int64) error { func (s *pipelineStore) DeleteByUID(ctx context.Context, spaceID int64, uid string) error { const pipelineDeleteStmt = ` DELETE FROM pipelines - WHERE pipeline_parent_id = $1 AND pipeline_uid = $2` + WHERE pipeline_space_id = $1 AND pipeline_uid = $2` db := dbtx.GetAccessor(ctx, s.db) diff --git a/internal/store/database/secret.go b/internal/store/database/secret.go index 23e34ed89..a1ec03bf8 100644 --- a/internal/store/database/secret.go +++ b/internal/store/database/secret.go @@ -27,7 +27,7 @@ const ( SELECT secret_id, secret_description, - secret_parent_id, + secret_space_id, secret_uid, secret_data, secret_created, @@ -39,7 +39,7 @@ const ( secretColumns = ` secret_id, secret_description, - secret_parent_id, + secret_space_id, secret_uid, secret_data, secret_created, @@ -50,7 +50,7 @@ const ( secretInsertStmt = ` INSERT INTO secrets ( secret_description, - secret_parent_id, + secret_space_id, secret_uid, secret_data, secret_created, @@ -58,7 +58,7 @@ const ( secret_version ) VALUES ( :secret_description, - :secret_parent_id, + :secret_space_id, :secret_uid, :secret_data, :secret_created, @@ -70,7 +70,7 @@ const ( UPDATE secrets SET secret_description = :secret_description, - secret_parent_id = :secret_parent_id, + secret_space_id = :secret_space_id, secret_uid = :secret_uid, secret_data = :secret_data, secret_created = :secret_created, @@ -80,7 +80,7 @@ const ( ) // NewSecretStore returns a new SecretStore. -func NewSecretStore(db *sqlx.DB, enc encrypt.Encrypter) *secretStore { +func NewSecretStore(enc encrypt.Encrypter, db *sqlx.DB) *secretStore { return &secretStore{ db: db, enc: enc, @@ -108,7 +108,7 @@ func (s *secretStore) Find(ctx context.Context, id int64) (*types.Secret, error) // FindByUID returns a secret in a given space with a given UID func (s *secretStore) FindByUID(ctx context.Context, spaceID int64, uid string) (*types.Secret, error) { const findQueryStmt = secretQueryBase + ` - WHERE secret_parent_id = $1 AND secret_uid = $2` + WHERE secret_space_id = $1 AND secret_uid = $2` db := dbtx.GetAccessor(ctx, s.db) dst := new(types.Secret) @@ -180,7 +180,7 @@ func (s *secretStore) List(ctx context.Context, parentID int64, opts *types.Secr stmt := database.Builder. Select(secretColumns). From("secrets"). - Where("secret_parent_id = ?", fmt.Sprint(parentID)) + Where("secret_space_id = ?", fmt.Sprint(parentID)) if opts.Query != "" { stmt = stmt.Where("LOWER(secret_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(opts.Query))) @@ -223,7 +223,7 @@ func (s *secretStore) Delete(ctx context.Context, id int64) error { func (s *secretStore) DeleteByUID(ctx context.Context, spaceID int64, uid string) error { const secretDeleteStmt = ` DELETE FROM secrets - WHERE secret_parent_id = $1 AND secret_uid = $2` + WHERE secret_space_id = $1 AND secret_uid = $2` db := dbtx.GetAccessor(ctx, s.db) diff --git a/internal/store/database/wire.go b/internal/store/database/wire.go index 6863ddbea..d0fba1660 100644 --- a/internal/store/database/wire.go +++ b/internal/store/database/wire.go @@ -6,7 +6,9 @@ package database import ( "context" + "fmt" + "github.com/harness/gitness/encrypt" "github.com/harness/gitness/internal/store" "github.com/harness/gitness/internal/store/database/migrate" "github.com/harness/gitness/store/database" @@ -25,6 +27,8 @@ var WireSet = wire.NewSet( ProvideRepoStore, ProvideExecutionStore, ProvidePipelineStore, + ProvideSecretStore, + ProvideEncryptor, ProvideRepoGitInfoView, ProvideMembershipStore, ProvideTokenStore, @@ -60,6 +64,23 @@ func ProvidePrincipalStore(db *sqlx.DB, uidTransformation store.PrincipalUIDTran return NewPrincipalStore(db, uidTransformation) } +// ProvideEncryptor provides an encryptor implementation +func ProvideEncryptor(config database.Config) (encrypt.Encrypter, error) { + fmt.Println("secret: ", config.Secret) + enc, err := encrypt.New(config.Secret) + // mixed-content mode should be set to true if the database + // originally had encryption disabled and therefore has + // plaintext entries. This prevents gitness from returning an + // error if decryption fails; on failure, the ciphertext is + // returned as-is and the error is ignored. + if aesgcm, ok := enc.(*encrypt.Aesgcm); ok { + if config.EncryptMixedContent { + aesgcm.Compat = true + } + } + return enc, err +} + // ProvidePrincipalInfoView provides a principal info store. func ProvidePrincipalInfoView(db *sqlx.DB) store.PrincipalInfoView { return NewPrincipalInfoView(db) @@ -85,6 +106,11 @@ func ProvidePipelineStore(db *sqlx.DB) store.PipelineStore { return NewPipelineStore(db) } +// ProvidePipelineStore provides a pipeline store. +func ProvideSecretStore(enc encrypt.Encrypter, db *sqlx.DB) store.SecretStore { + return NewSecretStore(enc, db) +} + // ProvideExecutionStore provides a build store func ProvideExecutionStore(db *sqlx.DB) store.ExecutionStore { return NewExecutionStore(db) diff --git a/store/database/config.go b/store/database/config.go index 399e8494d..1b180c787 100644 --- a/store/database/config.go +++ b/store/database/config.go @@ -6,6 +6,8 @@ package database // Config specifies the config for the database package. type Config struct { - Driver string - Datasource string + Driver string + Datasource string + Secret string + EncryptMixedContent bool } diff --git a/types/config.go b/types/config.go index ac4af71a1..9f4c10ec0 100644 --- a/types/config.go +++ b/types/config.go @@ -75,6 +75,9 @@ type Config struct { Database struct { Driver string `envconfig:"GITNESS_DATABASE_DRIVER" default:"sqlite3"` Datasource string `envconfig:"GITNESS_DATABASE_DATASOURCE" default:"database.sqlite3"` + // secret used for encryption/decryption in the DB + Secret string `envconfig:"GITNESS_DATABASE_SECRET"` + EncryptMixedContent bool `envconfig:"GITNESS_ENCRYPT_MIXED_CONTENT"` } // Token defines token configuration parameters. diff --git a/types/enum/membership_role.go b/types/enum/membership_role.go index b957491cc..fbe2b9d6a 100644 --- a/types/enum/membership_role.go +++ b/types/enum/membership_role.go @@ -25,11 +25,13 @@ var membershipRoleReaderPermissions = slices.Clip(slices.Insert([]Permission{}, PermissionSpaceView, PermissionServiceAccountView, PermissionPipelineView, + PermissionSecretView, )) var membershipRoleExecutorPermissions = slices.Clip(slices.Insert(membershipRoleReaderPermissions, 0, PermissionCommitCheckReport, PermissionPipelineExecute, + PermissionSecretAccess, )) var membershipRoleContributorPermissions = slices.Clip(slices.Insert(membershipRoleReaderPermissions, 0, @@ -53,6 +55,12 @@ var membershipRoleSpaceOwnerPermissions = slices.Clip(slices.Insert(membershipRo PermissionPipelineEdit, PermissionPipelineExecute, PermissionPipelineDelete, + PermissionPipelineView, + + PermissionSecretAccess, + PermissionSecretDelete, + PermissionSecretEdit, + PermissionSecretView, )) func init() { diff --git a/types/enum/permission.go b/types/enum/permission.go index c917f0fed..fe8d0fc6e 100644 --- a/types/enum/permission.go +++ b/types/enum/permission.go @@ -14,6 +14,7 @@ const ( ResourceTypeServiceAccount ResourceType = "SERVICEACCOUNT" ResourceTypeService ResourceType = "SERVICE" ResourceTypePipeline ResourceType = "PIPELINE" + ResourceTypeSecret ResourceType = "SECRET" // ResourceType_Branch ResourceType = "BRANCH" ) @@ -82,6 +83,16 @@ const ( PermissionPipelineExecute Permission = "pipeline_execute" ) +const ( + /* + ----- SECRET ----- + */ + PermissionSecretView Permission = "secret_view" + PermissionSecretEdit Permission = "secret_edit" + PermissionSecretDelete Permission = "secret_delete" + PermissionSecretAccess Permission = "secret_access" +) + const ( /* ----- COMMIT CHECK ----- diff --git a/types/pipeline.go b/types/pipeline.go index eefc038fc..b368dd5de 100644 --- a/types/pipeline.go +++ b/types/pipeline.go @@ -9,7 +9,7 @@ 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 + SpaceID int64 `db:"pipeline_space_id" json:"space_id"` 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 diff --git a/types/secret.go b/types/secret.go index 0f24ea054..270809909 100644 --- a/types/secret.go +++ b/types/secret.go @@ -7,7 +7,7 @@ package types type Secret struct { ID int64 `db:"secret_id" json:"id"` Description string `db:"secret_description" json:"description"` - ParentID int64 `db:"secret_parent_id" json:"parent_id"` // ID of the parent space + SpaceID int64 `db:"secret_space_id" json:"space_id"` UID string `db:"secret_uid" json:"uid"` Data string `db:"secret_data" json:"data"` Created int64 `db:"secret_created" json:"created"` @@ -28,7 +28,7 @@ func (s *Secret) Copy() *Secret { ID: s.ID, Description: s.Description, UID: s.UID, - ParentID: s.ParentID, + SpaceID: s.SpaceID, Created: s.Created, Updated: s.Updated, Version: s.Version, From f35d8fe03dd649fc287dd39bfae0c178437dbc4f Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Wed, 9 Aug 2023 17:19:26 +0100 Subject: [PATCH 18/26] add list secrets --- cmd/gitness/wire_gen.go | 4 +- internal/api/controller/space/controller.go | 8 +-- internal/api/controller/space/list_secrets.go | 52 +++++++++++++++++++ internal/api/controller/space/wire.go | 11 ++-- internal/api/handler/space/list_secrets.go | 42 +++++++++++++++ internal/api/openapi/space.go | 12 +++++ internal/router/api.go | 1 + internal/store/database.go | 3 ++ internal/store/database/secret.go | 26 ++++++++++ 9 files changed, 148 insertions(+), 11 deletions(-) create mode 100644 internal/api/controller/space/list_secrets.go create mode 100644 internal/api/handler/space/list_secrets.go diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index b06d1260b..08d04cb70 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -90,13 +90,13 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro executionStore := database.ProvideExecutionStore(db) pipelineStore := database.ProvidePipelineStore(db) executionController := execution.ProvideController(db, authorizer, executionStore, pipelineStore, spaceStore) - spaceController := space.ProvideController(db, provider, pathUID, authorizer, pathStore, pipelineStore, spaceStore, repoStore, principalStore, repoController, membershipStore) - pipelineController := pipeline.ProvideController(db, pathUID, pathStore, repoStore, authorizer, pipelineStore, spaceStore) encrypter, err := database.ProvideEncryptor(databaseConfig) if err != nil { return nil, err } secretStore := database.ProvideSecretStore(encrypter, db) + spaceController := space.ProvideController(db, provider, pathUID, authorizer, pathStore, pipelineStore, secretStore, spaceStore, repoStore, principalStore, repoController, membershipStore) + pipelineController := pipeline.ProvideController(db, pathUID, pathStore, repoStore, authorizer, pipelineStore, spaceStore) secretController := secret.ProvideController(db, pathUID, pathStore, secretStore, authorizer, spaceStore) pullReqStore := database.ProvidePullReqStore(db, principalInfoCache) pullReqActivityStore := database.ProvidePullReqActivityStore(db, principalInfoCache) diff --git a/internal/api/controller/space/controller.go b/internal/api/controller/space/controller.go index 5e7be791b..3249c82e1 100644 --- a/internal/api/controller/space/controller.go +++ b/internal/api/controller/space/controller.go @@ -21,6 +21,7 @@ type Controller struct { authorizer authz.Authorizer pathStore store.PathStore pipelineStore store.PipelineStore + secretStore store.SecretStore spaceStore store.SpaceStore repoStore store.RepoStore principalStore store.PrincipalStore @@ -30,9 +31,9 @@ type Controller struct { func NewController(db *sqlx.DB, urlProvider *url.Provider, uidCheck check.PathUID, authorizer authz.Authorizer, - pathStore store.PathStore, pipelineStore store.PipelineStore, spaceStore store.SpaceStore, - repoStore store.RepoStore, principalStore store.PrincipalStore, repoCtrl *repo.Controller, - membershipStore store.MembershipStore, + pathStore store.PathStore, pipelineStore store.PipelineStore, secretStore store.SecretStore, + spaceStore store.SpaceStore, repoStore store.RepoStore, principalStore store.PrincipalStore, + repoCtrl *repo.Controller, membershipStore store.MembershipStore, ) *Controller { return &Controller{ db: db, @@ -41,6 +42,7 @@ func NewController(db *sqlx.DB, urlProvider *url.Provider, authorizer: authorizer, pathStore: pathStore, pipelineStore: pipelineStore, + secretStore: secretStore, spaceStore: spaceStore, repoStore: repoStore, principalStore: principalStore, diff --git a/internal/api/controller/space/list_secrets.go b/internal/api/controller/space/list_secrets.go new file mode 100644 index 000000000..0e175d798 --- /dev/null +++ b/internal/api/controller/space/list_secrets.go @@ -0,0 +1,52 @@ +// 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 space + +import ( + "context" + "fmt" + + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/store/database/dbtx" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// ListSecrets lists the secrets in a space. +func (c *Controller) ListSecrets(ctx context.Context, session *auth.Session, + spaceRef string, filter *types.SecretFilter) ([]types.Secret, int64, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, 0, fmt.Errorf("failed to find parent space: %w", err) + } + + err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView, true) + if err != nil { + return nil, 0, fmt.Errorf("could not authorize: %w", err) + } + + var count int64 + var secrets []types.Secret + + err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { + var dbErr error + count, dbErr = c.secretStore.Count(ctx, space.ID, filter) + if dbErr != nil { + return fmt.Errorf("failed to count child executions: %w", err) + } + + secrets, dbErr = c.secretStore.List(ctx, space.ID, filter) + if dbErr != nil { + return fmt.Errorf("failed to list child executions: %w", err) + } + + return dbErr + }, dbtx.TxDefaultReadOnly) + if err != nil { + return secrets, count, err + } + + return secrets, count, nil +} diff --git a/internal/api/controller/space/wire.go b/internal/api/controller/space/wire.go index 9162675bb..9b126de43 100644 --- a/internal/api/controller/space/wire.go +++ b/internal/api/controller/space/wire.go @@ -21,12 +21,11 @@ var WireSet = wire.NewSet( ) func ProvideController(db *sqlx.DB, urlProvider *url.Provider, uidCheck check.PathUID, authorizer authz.Authorizer, - pathStore store.PathStore, pipelineStore store.PipelineStore, spaceStore store.SpaceStore, repoStore store.RepoStore, - principalStore store.PrincipalStore, repoCtrl *repo.Controller, - membershipStore store.MembershipStore, + pathStore store.PathStore, pipelineStore store.PipelineStore, secretStore store.SecretStore, + spaceStore store.SpaceStore, repoStore store.RepoStore, principalStore store.PrincipalStore, + repoCtrl *repo.Controller, membershipStore store.MembershipStore, ) *Controller { return NewController(db, urlProvider, uidCheck, authorizer, - pathStore, pipelineStore, spaceStore, repoStore, - principalStore, repoCtrl, - membershipStore) + pathStore, pipelineStore, secretStore, spaceStore, repoStore, + principalStore, repoCtrl, membershipStore) } diff --git a/internal/api/handler/space/list_secrets.go b/internal/api/handler/space/list_secrets.go new file mode 100644 index 000000000..d5e0f1eb2 --- /dev/null +++ b/internal/api/handler/space/list_secrets.go @@ -0,0 +1,42 @@ +// 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 space + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/space" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" + "github.com/harness/gitness/types" +) + +func HandleListSecrets(spaceCtrl *space.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + spaceRef, err := request.GetSpaceRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + filter := request.ParseSecretFilter(r) + ret, totalCount, err := spaceCtrl.ListSecrets(ctx, session, spaceRef, filter) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + // Strip out data in the returned value + secrets := []types.Secret{} + for _, s := range ret { + secrets = append(secrets, *s.Copy()) + } + + render.Pagination(r, w, filter.Page, filter.Size, int(totalCount)) + render.JSON(w, http.StatusOK, secrets) + } +} diff --git a/internal/api/openapi/space.go b/internal/api/openapi/space.go index 0a043ae47..b6e5a0573 100644 --- a/internal/api/openapi/space.go +++ b/internal/api/openapi/space.go @@ -242,6 +242,18 @@ func spaceOperations(reflector *openapi3.Reflector) { _ = reflector.SetJSONResponse(&opPipelines, new(usererror.Error), http.StatusNotFound) _ = reflector.Spec.AddOperation(http.MethodGet, "/spaces/{space_ref}/pipelines", opPipelines) + opSecrets := openapi3.Operation{} + opSecrets.WithTags("space") + opSecrets.WithMapOfAnything(map[string]interface{}{"operationId": "listSecrets"}) + opSecrets.WithParameters(queryParameterQueryRepo, queryParameterPage, queryParameterLimit) + _ = reflector.SetRequest(&opSecrets, new(spaceRequest), http.MethodGet) + _ = reflector.SetJSONResponse(&opSecrets, []types.Secret{}, http.StatusOK) + _ = reflector.SetJSONResponse(&opSecrets, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&opSecrets, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&opSecrets, new(usererror.Error), http.StatusForbidden) + _ = reflector.SetJSONResponse(&opSecrets, new(usererror.Error), http.StatusNotFound) + _ = reflector.Spec.AddOperation(http.MethodGet, "/spaces/{space_ref}/secrets", opSecrets) + opServiceAccounts := openapi3.Operation{} opServiceAccounts.WithTags("space") opServiceAccounts.WithMapOfAnything(map[string]interface{}{"operationId": "listServiceAccounts"}) diff --git a/internal/router/api.go b/internal/router/api.go index 882ef5a36..da2d3c949 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -166,6 +166,7 @@ func setupSpaces(r chi.Router, spaceCtrl *space.Controller) { r.Get("/repos", handlerspace.HandleListRepos(spaceCtrl)) r.Get("/service-accounts", handlerspace.HandleListServiceAccounts(spaceCtrl)) r.Get("/pipelines", handlerspace.HandleListPipelines(spaceCtrl)) + r.Get("/secrets", handlerspace.HandleListSecrets(spaceCtrl)) // Child collections r.Route("/paths", func(r chi.Router) { diff --git a/internal/store/database.go b/internal/store/database.go index 951ee12b8..d5d276600 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -478,6 +478,9 @@ type ( // Create creates a new secret Create(ctx context.Context, secret *types.Secret) error + // Count the number of secrets in a space matching the given filter. + Count(ctx context.Context, spaceID int64, filter *types.SecretFilter) (int64, error) + // Update tries to update a secret. Update(ctx context.Context, secret *types.Secret) (*types.Secret, error) diff --git a/internal/store/database/secret.go b/internal/store/database/secret.go index a1ec03bf8..96ef4b8ee 100644 --- a/internal/store/database/secret.go +++ b/internal/store/database/secret.go @@ -234,6 +234,32 @@ func (s *secretStore) DeleteByUID(ctx context.Context, spaceID int64, uid string return nil } +// Count of secrets in a space. +func (s *secretStore) Count(ctx context.Context, parentID int64, opts *types.SecretFilter) (int64, error) { + stmt := database.Builder. + Select("count(*)"). + From("secrets"). + Where("secret_space_id = ?", parentID) + + if opts.Query != "" { + stmt = stmt.Where("secret_uid LIKE ?", fmt.Sprintf("%%%s%%", opts.Query)) + } + + sql, args, err := stmt.ToSql() + if err != nil { + return 0, errors.Wrap(err, "Failed to convert query to sql") + } + + db := dbtx.GetAccessor(ctx, s.db) + + var count int64 + err = db.QueryRowContext(ctx, sql, args...).Scan(&count) + if err != nil { + return 0, database.ProcessSQLErrorf(err, "Failed executing count query") + } + return count, nil +} + // helper function returns the same secret with encrypted data func enc(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error) { s := *secret From 5574fcac47dd509d5fbbcd2ca9ab2eb2618aed71 Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Wed, 9 Aug 2023 22:25:13 +0100 Subject: [PATCH 19/26] address comments --- internal/api/controller/execution/create.go | 2 +- internal/api/controller/execution/update.go | 10 +++++----- internal/api/controller/pipeline/create.go | 1 - internal/api/handler/execution/create.go | 2 +- internal/api/handler/execution/delete.go | 2 +- internal/api/handler/execution/find.go | 2 +- internal/api/handler/execution/list.go | 2 +- internal/api/handler/execution/update.go | 2 +- internal/api/handler/pipeline/delete.go | 2 +- internal/api/handler/pipeline/find.go | 2 +- internal/api/handler/pipeline/update.go | 2 +- internal/api/handler/secret/create.go | 2 +- internal/api/handler/secret/find.go | 2 +- internal/api/handler/secret/update.go | 2 +- internal/api/handler/space/list_secrets.go | 2 +- internal/api/request/pipeline.go | 12 +----------- internal/store/database.go | 4 ++-- internal/store/database/execution.go | 14 +++++--------- internal/store/database/pipeline.go | 7 +++---- internal/store/database/secret.go | 14 ++------------ internal/store/database/wire.go | 2 -- types/secret.go | 2 +- 22 files changed, 32 insertions(+), 60 deletions(-) diff --git a/internal/api/controller/execution/create.go b/internal/api/controller/execution/create.go index 427d0315c..829ef6135 100644 --- a/internal/api/controller/execution/create.go +++ b/internal/api/controller/execution/create.go @@ -37,7 +37,7 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, spaceRef return nil, fmt.Errorf("failed to authorize: %w", err) } - pipeline, err = c.pipelineStore.Increment(ctx, pipeline) + pipeline, err = c.pipelineStore.IncrementSeqNum(ctx, pipeline) if err != nil { return nil, fmt.Errorf("failed to increment sequence number: %w", err) } diff --git a/internal/api/controller/execution/update.go b/internal/api/controller/execution/update.go index e62923cd4..820d97adb 100644 --- a/internal/api/controller/execution/update.go +++ b/internal/api/controller/execution/update.go @@ -22,25 +22,25 @@ func (c *Controller) Update( ctx context.Context, session *auth.Session, spaceRef string, - uid string, - n int64, + pipelineUID string, + executionNum int64, in *UpdateInput) (*types.Execution, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { return nil, fmt.Errorf("could not find space: %w", err) } - err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, uid, enum.PermissionPipelineEdit) + err = apiauth.CheckPipeline(ctx, c.authorizer, session, space.Path, pipelineUID, enum.PermissionPipelineEdit) if err != nil { return nil, fmt.Errorf("failed to check auth: %w", err) } - pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, uid) + pipeline, err := c.pipelineStore.FindByUID(ctx, space.ID, pipelineUID) if err != nil { return nil, fmt.Errorf("failed to find pipeline: %w", err) } - execution, err := c.executionStore.Find(ctx, pipeline.ID, n) + execution, err := c.executionStore.Find(ctx, pipeline.ID, executionNum) if err != nil { return nil, fmt.Errorf("failed to find execution: %w", err) } diff --git a/internal/api/controller/pipeline/create.go b/internal/api/controller/pipeline/create.go index 1c134295b..16eeeff13 100644 --- a/internal/api/controller/pipeline/create.go +++ b/internal/api/controller/pipeline/create.go @@ -61,7 +61,6 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea return nil, fmt.Errorf("failed to sanitize input: %w", err) } - fmt.Println("parent space: ", parentSpace.ID) var pipeline *types.Pipeline now := time.Now().UnixMilli() pipeline = &types.Pipeline{ diff --git a/internal/api/handler/execution/create.go b/internal/api/handler/execution/create.go index 0ba85da3f..97366148e 100644 --- a/internal/api/handler/execution/create.go +++ b/internal/api/handler/execution/create.go @@ -18,7 +18,7 @@ func HandleCreate(executionCtrl *execution.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() session, _ := request.AuthSessionFrom(ctx) - pipelineRef, err := request.GetPipelinePathRefFromPath(r) + pipelineRef, err := request.GetPipelineRefFromPath(r) if err != nil { render.TranslatedUserError(w, err) return diff --git a/internal/api/handler/execution/delete.go b/internal/api/handler/execution/delete.go index f52f413f9..77ee63e2f 100644 --- a/internal/api/handler/execution/delete.go +++ b/internal/api/handler/execution/delete.go @@ -17,7 +17,7 @@ func HandleDelete(executionCtrl *execution.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() session, _ := request.AuthSessionFrom(ctx) - pipelineRef, err := request.GetPipelinePathRefFromPath(r) + pipelineRef, err := request.GetPipelineRefFromPath(r) if err != nil { render.TranslatedUserError(w, err) return diff --git a/internal/api/handler/execution/find.go b/internal/api/handler/execution/find.go index 8df28e39f..9a4bc4c49 100644 --- a/internal/api/handler/execution/find.go +++ b/internal/api/handler/execution/find.go @@ -17,7 +17,7 @@ func HandleFind(executionCtrl *execution.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() session, _ := request.AuthSessionFrom(ctx) - pipelineRef, err := request.GetPipelinePathRefFromPath(r) + pipelineRef, err := request.GetPipelineRefFromPath(r) if err != nil { render.TranslatedUserError(w, err) return diff --git a/internal/api/handler/execution/list.go b/internal/api/handler/execution/list.go index 9a7540b8a..3ade46038 100644 --- a/internal/api/handler/execution/list.go +++ b/internal/api/handler/execution/list.go @@ -17,7 +17,7 @@ func HandleList(executionCtrl *execution.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() session, _ := request.AuthSessionFrom(ctx) - pipelineRef, err := request.GetPipelinePathRefFromPath(r) + pipelineRef, err := request.GetPipelineRefFromPath(r) if err != nil { render.TranslatedUserError(w, err) return diff --git a/internal/api/handler/execution/update.go b/internal/api/handler/execution/update.go index 4bc61ad2f..14fdf18f9 100644 --- a/internal/api/handler/execution/update.go +++ b/internal/api/handler/execution/update.go @@ -26,7 +26,7 @@ func HandleUpdate(executionCtrl *execution.Controller) http.HandlerFunc { return } - pipelineRef, err := request.GetPipelinePathRefFromPath(r) + pipelineRef, err := request.GetPipelineRefFromPath(r) if err != nil { render.TranslatedUserError(w, err) return diff --git a/internal/api/handler/pipeline/delete.go b/internal/api/handler/pipeline/delete.go index 5dbd30a29..1679df7a1 100644 --- a/internal/api/handler/pipeline/delete.go +++ b/internal/api/handler/pipeline/delete.go @@ -17,7 +17,7 @@ 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) + pipelineRef, err := request.GetPipelineRefFromPath(r) if err != nil { render.TranslatedUserError(w, err) return diff --git a/internal/api/handler/pipeline/find.go b/internal/api/handler/pipeline/find.go index d0fc11d3a..74ee6c77d 100644 --- a/internal/api/handler/pipeline/find.go +++ b/internal/api/handler/pipeline/find.go @@ -17,7 +17,7 @@ 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) + pipelineRef, err := request.GetPipelineRefFromPath(r) if err != nil { render.TranslatedUserError(w, err) return diff --git a/internal/api/handler/pipeline/update.go b/internal/api/handler/pipeline/update.go index d90f5d9fa..631ea4a77 100644 --- a/internal/api/handler/pipeline/update.go +++ b/internal/api/handler/pipeline/update.go @@ -26,7 +26,7 @@ func HandleUpdate(pipelineCtrl *pipeline.Controller) http.HandlerFunc { return } - pipelineRef, err := request.GetPipelinePathRefFromPath(r) + pipelineRef, err := request.GetPipelineRefFromPath(r) if err != nil { render.TranslatedUserError(w, err) return diff --git a/internal/api/handler/secret/create.go b/internal/api/handler/secret/create.go index 460b1cd8a..f0e2ac143 100644 --- a/internal/api/handler/secret/create.go +++ b/internal/api/handler/secret/create.go @@ -32,6 +32,6 @@ func HandleCreate(secretCtrl *secret.Controller) http.HandlerFunc { return } - render.JSON(w, http.StatusCreated, secret.Copy()) + render.JSON(w, http.StatusCreated, secret.CopyWithoutData()) } } diff --git a/internal/api/handler/secret/find.go b/internal/api/handler/secret/find.go index 37ce5bfb7..e77890a71 100644 --- a/internal/api/handler/secret/find.go +++ b/internal/api/handler/secret/find.go @@ -34,6 +34,6 @@ func HandleFind(secretCtrl *secret.Controller) http.HandlerFunc { return } - render.JSON(w, http.StatusOK, secret.Copy()) + render.JSON(w, http.StatusOK, secret.CopyWithoutData()) } } diff --git a/internal/api/handler/secret/update.go b/internal/api/handler/secret/update.go index b6fbaaedd..6722ab14c 100644 --- a/internal/api/handler/secret/update.go +++ b/internal/api/handler/secret/update.go @@ -45,6 +45,6 @@ func HandleUpdate(secretCtrl *secret.Controller) http.HandlerFunc { return } - render.JSON(w, http.StatusOK, secret.Copy()) + render.JSON(w, http.StatusOK, secret.CopyWithoutData()) } } diff --git a/internal/api/handler/space/list_secrets.go b/internal/api/handler/space/list_secrets.go index d5e0f1eb2..2209493b2 100644 --- a/internal/api/handler/space/list_secrets.go +++ b/internal/api/handler/space/list_secrets.go @@ -33,7 +33,7 @@ func HandleListSecrets(spaceCtrl *space.Controller) http.HandlerFunc { // Strip out data in the returned value secrets := []types.Secret{} for _, s := range ret { - secrets = append(secrets, *s.Copy()) + secrets = append(secrets, *s.CopyWithoutData()) } render.Pagination(r, w, filter.Page, filter.Size, int(totalCount)) diff --git a/internal/api/request/pipeline.go b/internal/api/request/pipeline.go index 812e14af4..a61f1b625 100644 --- a/internal/api/request/pipeline.go +++ b/internal/api/request/pipeline.go @@ -17,7 +17,7 @@ const ( ExecutionNumber = "execution_number" ) -func GetPipelinePathRefFromPath(r *http.Request) (string, error) { +func GetPipelineRefFromPath(r *http.Request) (string, error) { rawRef, err := PathParamOrError(r, PipelinePathRef) if err != nil { return "", err @@ -32,16 +32,6 @@ func GetExecutionNumberFromPath(r *http.Request) (int64, error) { } -func GetPipelineUIDFromPath(r *http.Request) (string, error) { - rawRef, err := PathParamOrError(r, PipelineUID) - if err != nil { - return "", err - } - - // paths are unescaped - return url.PathUnescape(rawRef) -} - // ParsePipelineFilter extracts the pipeline filter from the url. func ParsePipelineFilter(r *http.Request) *types.PipelineFilter { return &types.PipelineFilter{ diff --git a/internal/store/database.go b/internal/store/database.go index d5d276600..e9a006d01 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -464,8 +464,8 @@ type ( // DeleteByUID deletes a pipeline with a given UID in a space DeleteByUID(ctx context.Context, spaceID int64, uid string) error - // Incremenet increments the sequence number of the pipeline - Increment(ctx context.Context, pipeline *types.Pipeline) (*types.Pipeline, error) + // IncrementSeqNum increments the sequence number of the pipeline + IncrementSeqNum(ctx context.Context, pipeline *types.Pipeline) (*types.Pipeline, error) } SecretStore interface { diff --git a/internal/store/database/execution.go b/internal/store/database/execution.go index 8e0e53fdb..af0918d1c 100644 --- a/internal/store/database/execution.go +++ b/internal/store/database/execution.go @@ -20,7 +20,7 @@ import ( var _ store.ExecutionStore = (*executionStore)(nil) -// NewSpaceStore returns a new PathStore. +// NewExecutionStore returns a new ExecutionStore. func NewExecutionStore(db *sqlx.DB) *executionStore { return &executionStore{ db: db, @@ -150,10 +150,7 @@ const ( executionUpdateStmt = ` UPDATE executions SET - execution_pipeline_id = :execution_pipeline_id, - execution_repo_id = :execution_repo_id, execution_trigger = :execution_trigger, - execution_number = :execution_number, execution_parent = :execution_parent, execution_status = :execution_status, execution_error = :execution_error, @@ -181,20 +178,19 @@ const ( execution_debug = :execution_debug, execution_started = :execution_started, execution_finished = :execution_finished, - execution_created = :execution_created, execution_updated = :execution_updated, execution_version = :execution_version WHERE execution_id = :execution_id AND execution_version = :execution_version - 1` ) // Find returns an execution given a pipeline ID and an execution number -func (s *executionStore) Find(ctx context.Context, parentID int64, n int64) (*types.Execution, error) { +func (s *executionStore) Find(ctx context.Context, pipelineID int64, executionNum int64) (*types.Execution, error) { const findQueryStmt = executionQueryBase + ` WHERE execution_pipeline_id = $1 AND execution_number = $2` db := dbtx.GetAccessor(ctx, s.db) dst := new(types.Execution) - if err := db.GetContext(ctx, dst, findQueryStmt, parentID, n); err != nil { + if err := db.GetContext(ctx, dst, findQueryStmt, pipelineID, executionNum); err != nil { return nil, database.ProcessSQLErrorf(err, "Failed to find execution") } return dst, nil @@ -295,14 +291,14 @@ func (s *executionStore) Count(ctx context.Context, pipelineID int64, opts *type } // Delete deletes an execution given a pipeline ID and an execution number -func (s *executionStore) Delete(ctx context.Context, pipelineID int64, n int64) error { +func (s *executionStore) Delete(ctx context.Context, pipelineID int64, executionNum int64) error { const executionDeleteStmt = ` DELETE FROM executions WHERE execution_pipeline_id = $1 AND execution_number = $2` db := dbtx.GetAccessor(ctx, s.db) - if _, err := db.ExecContext(ctx, executionDeleteStmt, pipelineID, n); err != nil { + if _, err := db.ExecContext(ctx, executionDeleteStmt, pipelineID, executionNum); err != nil { return database.ProcessSQLErrorf(err, "Could not delete execution") } diff --git a/internal/store/database/pipeline.go b/internal/store/database/pipeline.go index 81a8f0b4f..d43fa7032 100644 --- a/internal/store/database/pipeline.go +++ b/internal/store/database/pipeline.go @@ -85,7 +85,6 @@ const ( 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` @@ -262,7 +261,7 @@ func (s *pipelineStore) DeleteByUID(ctx context.Context, spaceID int64, uid stri // Increment increments the pipeline sequence number. It will keep retrying in case // of optimistic lock errors. -func (s *pipelineStore) Increment(ctx context.Context, pipeline *types.Pipeline) (*types.Pipeline, error) { +func (s *pipelineStore) IncrementSeqNum(ctx context.Context, pipeline *types.Pipeline) (*types.Pipeline, error) { for { pipeline.Seq++ pipeline, err := s.Update(ctx, pipeline) @@ -270,11 +269,11 @@ func (s *pipelineStore) Increment(ctx context.Context, pipeline *types.Pipeline) return pipeline, nil } if err != nil && err != gitness_store.ErrVersionConflict { - return pipeline, err + return pipeline, errors.Wrap(err, "could not increment pipeline sequence number") } pipeline, err = s.Find(ctx, pipeline.ID) if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not increment pipeline sequence number") } } } diff --git a/internal/store/database/secret.go b/internal/store/database/secret.go index 96ef4b8ee..bb3be1b83 100644 --- a/internal/store/database/secret.go +++ b/internal/store/database/secret.go @@ -24,17 +24,8 @@ var _ store.SecretStore = (*secretStore)(nil) const ( secretQueryBase = ` - SELECT - secret_id, - secret_description, - secret_space_id, - secret_uid, - secret_data, - secret_created, - secret_updated, - secret_version - FROM secrets - ` + SELECT` + secretColumns + ` + FROM secrets` secretColumns = ` secret_id, @@ -73,7 +64,6 @@ const ( secret_space_id = :secret_space_id, secret_uid = :secret_uid, secret_data = :secret_data, - secret_created = :secret_created, secret_updated = :secret_updated, secret_version = :secret_version WHERE secret_id = :secret_id AND secret_version = :secret_version - 1` diff --git a/internal/store/database/wire.go b/internal/store/database/wire.go index d0fba1660..49cc72ba4 100644 --- a/internal/store/database/wire.go +++ b/internal/store/database/wire.go @@ -6,7 +6,6 @@ package database import ( "context" - "fmt" "github.com/harness/gitness/encrypt" "github.com/harness/gitness/internal/store" @@ -66,7 +65,6 @@ func ProvidePrincipalStore(db *sqlx.DB, uidTransformation store.PrincipalUIDTran // ProvideEncryptor provides an encryptor implementation func ProvideEncryptor(config database.Config) (encrypt.Encrypter, error) { - fmt.Println("secret: ", config.Secret) enc, err := encrypt.New(config.Secret) // mixed-content mode should be set to true if the database // originally had encryption disabled and therefore has diff --git a/types/secret.go b/types/secret.go index 270809909..6b20e7b5e 100644 --- a/types/secret.go +++ b/types/secret.go @@ -23,7 +23,7 @@ type SecretFilter struct { } // Copy makes a copy of the secret without the value. -func (s *Secret) Copy() *Secret { +func (s *Secret) CopyWithoutData() *Secret { return &Secret{ ID: s.ID, Description: s.Description, From e24eb858d562b0d52b5c6c0a877eca545c6a3ad1 Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Wed, 9 Aug 2023 23:00:33 +0100 Subject: [PATCH 20/26] run make format and fix lint errors --- cmd/gitness/wire_gen.go | 1 + encrypt/encrypt.go | 4 +++ .../api/controller/execution/controller.go | 1 + internal/api/controller/execution/create.go | 11 +++++--- internal/api/controller/execution/delete.go | 8 +++++- internal/api/controller/execution/find.go | 8 +++++- internal/api/controller/execution/list.go | 3 +-- internal/api/controller/execution/wire.go | 3 ++- .../api/controller/pipeline/controller.go | 1 + internal/api/controller/pipeline/create.go | 1 - internal/api/controller/pipeline/delete.go | 1 - internal/api/controller/pipeline/find.go | 8 ++++-- internal/api/controller/pipeline/wire.go | 6 ++--- internal/api/controller/secret/controller.go | 1 + internal/api/controller/secret/create.go | 4 ++- internal/api/controller/secret/delete.go | 1 - internal/api/controller/secret/find.go | 8 ++++-- internal/api/controller/secret/update.go | 1 - internal/api/controller/secret/wire.go | 3 ++- internal/api/openapi/pipeline.go | 18 ++++++++----- internal/api/request/pipeline.go | 1 - internal/store/database.go | 4 +-- internal/store/database/execution.go | 15 +++++++---- internal/store/database/pipeline.go | 26 +++++++++++-------- internal/store/database/secret.go | 20 +++++++------- internal/store/database/wire.go | 6 ++--- mocks/mock_client.go | 3 ++- mocks/mock_store.go | 3 ++- types/execution.go | 2 +- 29 files changed, 110 insertions(+), 62 deletions(-) diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index 08d04cb70..81aca6116 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -8,6 +8,7 @@ package main import ( "context" + "github.com/harness/gitness/cli/server" "github.com/harness/gitness/events" "github.com/harness/gitness/gitrpc" diff --git a/encrypt/encrypt.go b/encrypt/encrypt.go index 5e365254a..403c866d5 100644 --- a/encrypt/encrypt.go +++ b/encrypt/encrypt.go @@ -1,3 +1,7 @@ +// 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 encrypt import ( diff --git a/internal/api/controller/execution/controller.go b/internal/api/controller/execution/controller.go index 652637035..abfa572eb 100644 --- a/internal/api/controller/execution/controller.go +++ b/internal/api/controller/execution/controller.go @@ -7,6 +7,7 @@ package execution import ( "github.com/harness/gitness/internal/auth/authz" "github.com/harness/gitness/internal/store" + "github.com/jmoiron/sqlx" ) diff --git a/internal/api/controller/execution/create.go b/internal/api/controller/execution/create.go index 829ef6135..595def806 100644 --- a/internal/api/controller/execution/create.go +++ b/internal/api/controller/execution/create.go @@ -15,13 +15,18 @@ import ( "github.com/harness/gitness/types/enum" ) -// TODO: Add more as needed +// TODO: Add more as needed. type CreateInput struct { Status string `json:"status"` } -// Create creates a new execution -func (c *Controller) Create(ctx context.Context, session *auth.Session, spaceRef string, uid string, in *CreateInput) (*types.Execution, error) { +func (c *Controller) Create( + ctx context.Context, + session *auth.Session, + spaceRef string, + uid string, + in *CreateInput, +) (*types.Execution, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { return nil, fmt.Errorf("could not find space: %w", err) diff --git a/internal/api/controller/execution/delete.go b/internal/api/controller/execution/delete.go index 357574092..9b2a84aa9 100644 --- a/internal/api/controller/execution/delete.go +++ b/internal/api/controller/execution/delete.go @@ -13,7 +13,13 @@ import ( "github.com/harness/gitness/types/enum" ) -func (c *Controller) Delete(ctx context.Context, session *auth.Session, spaceRef string, pipelineUID string, executionNum int64) error { +func (c *Controller) Delete( + ctx context.Context, + session *auth.Session, + spaceRef string, + pipelineUID string, + executionNum int64, +) error { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { return fmt.Errorf("could not find parent space: %w", err) diff --git a/internal/api/controller/execution/find.go b/internal/api/controller/execution/find.go index 81bf08ba0..d597488fd 100644 --- a/internal/api/controller/execution/find.go +++ b/internal/api/controller/execution/find.go @@ -14,7 +14,13 @@ import ( "github.com/harness/gitness/types/enum" ) -func (c *Controller) Find(ctx context.Context, session *auth.Session, spaceRef string, pipelineUID string, executionNum int64) (*types.Execution, error) { +func (c *Controller) Find( + ctx context.Context, + session *auth.Session, + spaceRef string, + pipelineUID string, + executionNum int64, +) (*types.Execution, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { return nil, fmt.Errorf("could not find parent space: %w", err) diff --git a/internal/api/controller/execution/list.go b/internal/api/controller/execution/list.go index 5cbbde5d0..7d9c79488 100644 --- a/internal/api/controller/execution/list.go +++ b/internal/api/controller/execution/list.go @@ -14,7 +14,6 @@ import ( "github.com/harness/gitness/types/enum" ) -// List lists the executions in a pipeline. func (c *Controller) List( ctx context.Context, session *auth.Session, @@ -41,7 +40,7 @@ func (c *Controller) List( err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { var dbErr error - count, dbErr = c.executionStore.Count(ctx, pipeline.ID, filter) + count, dbErr = c.executionStore.Count(ctx, pipeline.ID) if dbErr != nil { return fmt.Errorf("failed to count child executions: %w", err) } diff --git a/internal/api/controller/execution/wire.go b/internal/api/controller/execution/wire.go index b2836ae35..c559831f9 100644 --- a/internal/api/controller/execution/wire.go +++ b/internal/api/controller/execution/wire.go @@ -5,9 +5,10 @@ package execution import ( - "github.com/google/wire" "github.com/harness/gitness/internal/auth/authz" "github.com/harness/gitness/internal/store" + + "github.com/google/wire" "github.com/jmoiron/sqlx" ) diff --git a/internal/api/controller/pipeline/controller.go b/internal/api/controller/pipeline/controller.go index 5c7f4ae44..61020c78a 100644 --- a/internal/api/controller/pipeline/controller.go +++ b/internal/api/controller/pipeline/controller.go @@ -8,6 +8,7 @@ import ( "github.com/harness/gitness/internal/auth/authz" "github.com/harness/gitness/internal/store" "github.com/harness/gitness/types/check" + "github.com/jmoiron/sqlx" ) diff --git a/internal/api/controller/pipeline/create.go b/internal/api/controller/pipeline/create.go index 16eeeff13..74cbedc8d 100644 --- a/internal/api/controller/pipeline/create.go +++ b/internal/api/controller/pipeline/create.go @@ -35,7 +35,6 @@ type CreateInput struct { 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) { parentSpace, err := c.spaceStore.FindByRef(ctx, in.SpaceRef) if err != nil { diff --git a/internal/api/controller/pipeline/delete.go b/internal/api/controller/pipeline/delete.go index fd266a5e9..cdae2a3cc 100644 --- a/internal/api/controller/pipeline/delete.go +++ b/internal/api/controller/pipeline/delete.go @@ -13,7 +13,6 @@ import ( "github.com/harness/gitness/types/enum" ) -// 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 { diff --git a/internal/api/controller/pipeline/find.go b/internal/api/controller/pipeline/find.go index 22560502a..b065bc404 100644 --- a/internal/api/controller/pipeline/find.go +++ b/internal/api/controller/pipeline/find.go @@ -14,8 +14,12 @@ import ( "github.com/harness/gitness/types/enum" ) -// Find finds a pipeline. -func (c *Controller) Find(ctx context.Context, session *auth.Session, spaceRef string, uid string) (*types.Pipeline, error) { +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, fmt.Errorf("could not find parent space: %w", err) diff --git a/internal/api/controller/pipeline/wire.go b/internal/api/controller/pipeline/wire.go index 70bd3ff44..d665a8ee6 100644 --- a/internal/api/controller/pipeline/wire.go +++ b/internal/api/controller/pipeline/wire.go @@ -5,12 +5,12 @@ package pipeline import ( - "github.com/google/wire" - "github.com/jmoiron/sqlx" - "github.com/harness/gitness/internal/auth/authz" "github.com/harness/gitness/internal/store" "github.com/harness/gitness/types/check" + + "github.com/google/wire" + "github.com/jmoiron/sqlx" ) // WireSet provides a wire set for this package. diff --git a/internal/api/controller/secret/controller.go b/internal/api/controller/secret/controller.go index 8bbd2082d..c979b6dcb 100644 --- a/internal/api/controller/secret/controller.go +++ b/internal/api/controller/secret/controller.go @@ -8,6 +8,7 @@ import ( "github.com/harness/gitness/internal/auth/authz" "github.com/harness/gitness/internal/store" "github.com/harness/gitness/types/check" + "github.com/jmoiron/sqlx" ) diff --git a/internal/api/controller/secret/create.go b/internal/api/controller/secret/create.go index d2573c08a..fe6da13f6 100644 --- a/internal/api/controller/secret/create.go +++ b/internal/api/controller/secret/create.go @@ -33,7 +33,6 @@ type CreateInput struct { Data string `json:"data"` } -// Create creates a new pipeline func (c *Controller) Create(ctx context.Context, session *auth.Session, in *CreateInput) (*types.Secret, error) { parentSpace, err := c.spaceStore.FindByRef(ctx, in.SpaceRef) if err != nil { @@ -73,6 +72,9 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea } return nil }) + if err != nil { + return nil, err + } return secret, nil } diff --git a/internal/api/controller/secret/delete.go b/internal/api/controller/secret/delete.go index baf76584a..8e42ede62 100644 --- a/internal/api/controller/secret/delete.go +++ b/internal/api/controller/secret/delete.go @@ -13,7 +13,6 @@ import ( "github.com/harness/gitness/types/enum" ) -// Delete deletes a secret. 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 { diff --git a/internal/api/controller/secret/find.go b/internal/api/controller/secret/find.go index 6f5278b65..91d2e492f 100644 --- a/internal/api/controller/secret/find.go +++ b/internal/api/controller/secret/find.go @@ -13,8 +13,12 @@ import ( "github.com/harness/gitness/types/enum" ) -// Find finds a secret. -func (c *Controller) Find(ctx context.Context, session *auth.Session, spaceRef string, uid string) (*types.Secret, error) { +func (c *Controller) Find( + ctx context.Context, + session *auth.Session, + spaceRef string, + uid string, +) (*types.Secret, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { return nil, err diff --git a/internal/api/controller/secret/update.go b/internal/api/controller/secret/update.go index c872dcd1a..d495dc082 100644 --- a/internal/api/controller/secret/update.go +++ b/internal/api/controller/secret/update.go @@ -20,7 +20,6 @@ type UpdateInput struct { Data string `json:"data"` } -// Update updates a secret. func (c *Controller) Update( ctx context.Context, session *auth.Session, diff --git a/internal/api/controller/secret/wire.go b/internal/api/controller/secret/wire.go index 09b46cc52..8922a5dc1 100644 --- a/internal/api/controller/secret/wire.go +++ b/internal/api/controller/secret/wire.go @@ -5,10 +5,11 @@ package secret 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/google/wire" "github.com/jmoiron/sqlx" ) diff --git a/internal/api/openapi/pipeline.go b/internal/api/openapi/pipeline.go index ece281796..533e29a09 100644 --- a/internal/api/openapi/pipeline.go +++ b/internal/api/openapi/pipeline.go @@ -95,7 +95,8 @@ func pipelineOperations(reflector *openapi3.Reflector) { _ = 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) + _ = reflector.Spec.AddOperation(http.MethodPatch, + "/pipelines/{pipeline_ref}", opUpdate) executionCreate := openapi3.Operation{} executionCreate.WithTags("pipeline") @@ -106,7 +107,8 @@ func pipelineOperations(reflector *openapi3.Reflector) { _ = reflector.SetJSONResponse(&executionCreate, new(usererror.Error), http.StatusInternalServerError) _ = reflector.SetJSONResponse(&executionCreate, new(usererror.Error), http.StatusUnauthorized) _ = reflector.SetJSONResponse(&executionCreate, new(usererror.Error), http.StatusForbidden) - _ = reflector.Spec.AddOperation(http.MethodPost, "/pipelines/{pipeline_ref}/executions", executionCreate) + _ = reflector.Spec.AddOperation(http.MethodPost, + "/pipelines/{pipeline_ref}/executions", executionCreate) executionFind := openapi3.Operation{} executionFind.WithTags("pipeline") @@ -117,7 +119,8 @@ func pipelineOperations(reflector *openapi3.Reflector) { _ = reflector.SetJSONResponse(&executionFind, new(usererror.Error), http.StatusUnauthorized) _ = reflector.SetJSONResponse(&executionFind, new(usererror.Error), http.StatusForbidden) _ = reflector.SetJSONResponse(&executionFind, new(usererror.Error), http.StatusNotFound) - _ = reflector.Spec.AddOperation(http.MethodGet, "/pipelines/{pipeline_ref}/executions/{execution_number}", executionFind) + _ = reflector.Spec.AddOperation(http.MethodGet, + "/pipelines/{pipeline_ref}/executions/{execution_number}", executionFind) executionDelete := openapi3.Operation{} executionDelete.WithTags("pipeline") @@ -128,7 +131,8 @@ func pipelineOperations(reflector *openapi3.Reflector) { _ = reflector.SetJSONResponse(&executionDelete, new(usererror.Error), http.StatusUnauthorized) _ = reflector.SetJSONResponse(&executionDelete, new(usererror.Error), http.StatusForbidden) _ = reflector.SetJSONResponse(&executionDelete, new(usererror.Error), http.StatusNotFound) - _ = reflector.Spec.AddOperation(http.MethodDelete, "/pipelines/{pipeline_ref}/executions/{execution_number}", executionDelete) + _ = reflector.Spec.AddOperation(http.MethodDelete, + "/pipelines/{pipeline_ref}/executions/{execution_number}", executionDelete) executionUpdate := openapi3.Operation{} executionUpdate.WithTags("pipeline") @@ -140,7 +144,8 @@ func pipelineOperations(reflector *openapi3.Reflector) { _ = reflector.SetJSONResponse(&executionUpdate, new(usererror.Error), http.StatusUnauthorized) _ = reflector.SetJSONResponse(&executionUpdate, new(usererror.Error), http.StatusForbidden) _ = reflector.SetJSONResponse(&executionUpdate, new(usererror.Error), http.StatusNotFound) - _ = reflector.Spec.AddOperation(http.MethodPatch, "/pipelines/{pipeline_ref}/executions/{execution_number}", executionUpdate) + _ = reflector.Spec.AddOperation(http.MethodPatch, + "/pipelines/{pipeline_ref}/executions/{execution_number}", executionUpdate) executionList := openapi3.Operation{} executionList.WithTags("pipeline") @@ -152,5 +157,6 @@ func pipelineOperations(reflector *openapi3.Reflector) { _ = reflector.SetJSONResponse(&executionList, new(usererror.Error), http.StatusUnauthorized) _ = reflector.SetJSONResponse(&executionList, new(usererror.Error), http.StatusForbidden) _ = reflector.SetJSONResponse(&executionList, new(usererror.Error), http.StatusNotFound) - _ = reflector.Spec.AddOperation(http.MethodGet, "/pipelines/{pipeline_ref}/executions", executionList) + _ = reflector.Spec.AddOperation(http.MethodGet, + "/pipelines/{pipeline_ref}/executions", executionList) } diff --git a/internal/api/request/pipeline.go b/internal/api/request/pipeline.go index a61f1b625..4dbb5abde 100644 --- a/internal/api/request/pipeline.go +++ b/internal/api/request/pipeline.go @@ -29,7 +29,6 @@ func GetPipelineRefFromPath(r *http.Request) (string, error) { func GetExecutionNumberFromPath(r *http.Request) (int64, error) { return PathParamAsPositiveInt64(r, ExecutionNumber) - } // ParsePipelineFilter extracts the pipeline filter from the url. diff --git a/internal/store/database.go b/internal/store/database.go index e9a006d01..87fe85cb9 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -510,7 +510,7 @@ type ( // Delete deletes an execution given a pipeline ID and an execution number Delete(ctx context.Context, pipelineID int64, num int64) error - // Count the number of executions in a space matching the given filter. - Count(ctx context.Context, parentID int64, opts *types.ExecutionFilter) (int64, error) + // Count the number of executions in a space + Count(ctx context.Context, parentID int64) (int64, error) } ) diff --git a/internal/store/database/execution.go b/internal/store/database/execution.go index af0918d1c..73e344beb 100644 --- a/internal/store/database/execution.go +++ b/internal/store/database/execution.go @@ -14,6 +14,7 @@ import ( "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" ) @@ -183,7 +184,7 @@ const ( WHERE execution_id = :execution_id AND execution_version = :execution_version - 1` ) -// Find returns an execution given a pipeline ID and an execution number +// Find returns an execution given a pipeline ID and an execution number. func (s *executionStore) Find(ctx context.Context, pipelineID int64, executionNum int64) (*types.Execution, error) { const findQueryStmt = executionQueryBase + ` WHERE execution_pipeline_id = $1 AND execution_number = $2` @@ -243,8 +244,12 @@ func (s *executionStore) Update(ctx context.Context, execution *types.Execution) return execution, nil } -// List lists the executions for a given pipeline ID -func (s *executionStore) List(ctx context.Context, pipelineID int64, opts *types.ExecutionFilter) ([]types.Execution, error) { +// List lists the executions for a given pipeline ID. +func (s *executionStore) List( + ctx context.Context, + pipelineID int64, + opts *types.ExecutionFilter, +) ([]types.Execution, error) { stmt := database.Builder. Select(executionColumns). From("executions"). @@ -269,7 +274,7 @@ func (s *executionStore) List(ctx context.Context, pipelineID int64, opts *types } // Count of executions in a space. -func (s *executionStore) Count(ctx context.Context, pipelineID int64, opts *types.ExecutionFilter) (int64, error) { +func (s *executionStore) Count(ctx context.Context, pipelineID int64) (int64, error) { stmt := database.Builder. Select("count(*)"). From("executions"). @@ -290,7 +295,7 @@ func (s *executionStore) Count(ctx context.Context, pipelineID int64, opts *type return count, nil } -// Delete deletes an execution given a pipeline ID and an execution number +// Delete deletes an execution given a pipeline ID and an execution number. func (s *executionStore) Delete(ctx context.Context, pipelineID int64, executionNum int64) error { const executionDeleteStmt = ` DELETE FROM executions diff --git a/internal/store/database/pipeline.go b/internal/store/database/pipeline.go index d43fa7032..1e36f9615 100644 --- a/internal/store/database/pipeline.go +++ b/internal/store/database/pipeline.go @@ -101,7 +101,7 @@ type pipelineStore struct { db *sqlx.DB } -// Find returns a pipeline given a pipeline ID +// 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` @@ -114,7 +114,7 @@ func (s *pipelineStore) Find(ctx context.Context, id int64) (*types.Pipeline, er return dst, nil } -// FindByUID returns a pipeline in a given space with a given UID +// 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_space_id = $1 AND pipeline_uid = $2` @@ -127,7 +127,7 @@ func (s *pipelineStore) FindByUID(ctx context.Context, spaceID int64, uid string return dst, nil } -// Create creates a pipeline +// Create creates a pipeline. func (s *pipelineStore) Create(ctx context.Context, pipeline *types.Pipeline) error { db := dbtx.GetAccessor(ctx, s.db) @@ -143,6 +143,7 @@ func (s *pipelineStore) Create(ctx context.Context, pipeline *types.Pipeline) er return nil } +// Update updates a pipeline. func (s *pipelineStore) Update(ctx context.Context, pipeline *types.Pipeline) (*types.Pipeline, error) { updatedAt := time.Now() @@ -171,11 +172,14 @@ func (s *pipelineStore) Update(ctx context.Context, pipeline *types.Pipeline) (* } return pipeline, nil - } -// List lists all the pipelines present in a space -func (s *pipelineStore) List(ctx context.Context, parentID int64, opts *types.PipelineFilter) ([]types.Pipeline, error) { +// 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"). @@ -229,7 +233,7 @@ func (s *pipelineStore) Count(ctx context.Context, parentID int64, opts *types.P return count, nil } -// Delete deletes a pipeline given a pipeline ID +// Delete deletes a pipeline given a pipeline ID. func (s *pipelineStore) Delete(ctx context.Context, id int64) error { const pipelineDeleteStmt = ` DELETE FROM pipelines @@ -244,7 +248,7 @@ func (s *pipelineStore) Delete(ctx context.Context, id int64) error { return nil } -// DeleteByUID deletes a pipeline with a given UID in a space +// DeleteByUID deletes a pipeline with a given UID in a space. func (s *pipelineStore) DeleteByUID(ctx context.Context, spaceID int64, uid string) error { const pipelineDeleteStmt = ` DELETE FROM pipelines @@ -263,12 +267,12 @@ func (s *pipelineStore) DeleteByUID(ctx context.Context, spaceID int64, uid stri // of optimistic lock errors. func (s *pipelineStore) IncrementSeqNum(ctx context.Context, pipeline *types.Pipeline) (*types.Pipeline, error) { for { + var err error pipeline.Seq++ - pipeline, err := s.Update(ctx, pipeline) + pipeline, err = s.Update(ctx, pipeline) if err == nil { return pipeline, nil - } - if err != nil && err != gitness_store.ErrVersionConflict { + } else if !errors.Is(err, gitness_store.ErrVersionConflict) { return pipeline, errors.Wrap(err, "could not increment pipeline sequence number") } pipeline, err = s.Find(ctx, pipeline.ID) diff --git a/internal/store/database/secret.go b/internal/store/database/secret.go index bb3be1b83..a53a10db7 100644 --- a/internal/store/database/secret.go +++ b/internal/store/database/secret.go @@ -16,6 +16,7 @@ import ( "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" ) @@ -82,7 +83,7 @@ type secretStore struct { enc encrypt.Encrypter } -// Find returns a secret given a secret ID +// Find returns a secret given a secret ID. func (s *secretStore) Find(ctx context.Context, id int64) (*types.Secret, error) { const findQueryStmt = secretQueryBase + ` WHERE secret_id = $1` @@ -95,7 +96,7 @@ func (s *secretStore) Find(ctx context.Context, id int64) (*types.Secret, error) return dec(s.enc, dst) } -// FindByUID returns a secret in a given space with a given UID +// FindByUID returns a secret in a given space with a given UID. func (s *secretStore) FindByUID(ctx context.Context, spaceID int64, uid string) (*types.Secret, error) { const findQueryStmt = secretQueryBase + ` WHERE secret_space_id = $1 AND secret_uid = $2` @@ -108,7 +109,7 @@ func (s *secretStore) FindByUID(ctx context.Context, spaceID int64, uid string) return dec(s.enc, dst) } -// Create creates a secret +// Create creates a secret. func (s *secretStore) Create(ctx context.Context, secret *types.Secret) error { db := dbtx.GetAccessor(ctx, s.db) @@ -162,10 +163,9 @@ func (s *secretStore) Update(ctx context.Context, secret *types.Secret) (*types. } return secret, nil - } -// List lists all the secrets present in a space +// List lists all the secrets present in a space. func (s *secretStore) List(ctx context.Context, parentID int64, opts *types.SecretFilter) ([]types.Secret, error) { stmt := database.Builder. Select(secretColumns). @@ -194,7 +194,7 @@ func (s *secretStore) List(ctx context.Context, parentID int64, opts *types.Secr return dst, nil } -// Delete deletes a secret given a secret ID +// Delete deletes a secret given a secret ID. func (s *secretStore) Delete(ctx context.Context, id int64) error { const secretDeleteStmt = ` DELETE FROM secrets @@ -209,7 +209,7 @@ func (s *secretStore) Delete(ctx context.Context, id int64) error { return nil } -// DeleteByUID deletes a secret with a given UID in a space +// DeleteByUID deletes a secret with a given UID in a space. func (s *secretStore) DeleteByUID(ctx context.Context, spaceID int64, uid string) error { const secretDeleteStmt = ` DELETE FROM secrets @@ -250,7 +250,7 @@ func (s *secretStore) Count(ctx context.Context, parentID int64, opts *types.Sec return count, nil } -// helper function returns the same secret with encrypted data +// helper function returns the same secret with encrypted data. func enc(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error) { s := *secret ciphertext, err := encrypt.Encrypt(secret.Data) @@ -261,13 +261,13 @@ func enc(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error) return &s, nil } -// helper function returns the same secret with decrypted data +// helper function returns the same secret with decrypted data. func dec(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error) { s := *secret plaintext, err := encrypt.Decrypt([]byte(secret.Data)) if err != nil { return nil, err } - s.Data = string(plaintext) + s.Data = plaintext return &s, nil } diff --git a/internal/store/database/wire.go b/internal/store/database/wire.go index 49cc72ba4..bd4b64cc1 100644 --- a/internal/store/database/wire.go +++ b/internal/store/database/wire.go @@ -63,7 +63,7 @@ func ProvidePrincipalStore(db *sqlx.DB, uidTransformation store.PrincipalUIDTran return NewPrincipalStore(db, uidTransformation) } -// ProvideEncryptor provides an encryptor implementation +// ProvideEncryptor provides an encryptor implementation. func ProvideEncryptor(config database.Config) (encrypt.Encrypter, error) { enc, err := encrypt.New(config.Secret) // mixed-content mode should be set to true if the database @@ -104,12 +104,12 @@ func ProvidePipelineStore(db *sqlx.DB) store.PipelineStore { return NewPipelineStore(db) } -// ProvidePipelineStore provides a pipeline store. +// ProvideSecretStore provides a secret store. func ProvideSecretStore(enc encrypt.Encrypter, db *sqlx.DB) store.SecretStore { return NewSecretStore(enc, db) } -// ProvideExecutionStore provides a build store +// ProvideExecutionStore provides an execution store. func ProvideExecutionStore(db *sqlx.DB) store.ExecutionStore { return NewExecutionStore(db) } diff --git a/mocks/mock_client.go b/mocks/mock_client.go index b16a6490e..3cb746055 100644 --- a/mocks/mock_client.go +++ b/mocks/mock_client.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" user "github.com/harness/gitness/internal/api/controller/user" types "github.com/harness/gitness/types" + + gomock "github.com/golang/mock/gomock" ) // MockClient is a mock of Client interface. diff --git a/mocks/mock_store.go b/mocks/mock_store.go index 0af0bbc34..9310f3729 100644 --- a/mocks/mock_store.go +++ b/mocks/mock_store.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" types "github.com/harness/gitness/types" enum "github.com/harness/gitness/types/enum" + + gomock "github.com/golang/mock/gomock" ) // MockPrincipalStore is a mock of PrincipalStore interface. diff --git a/types/execution.go b/types/execution.go index e29df4f8d..fde55504e 100644 --- a/types/execution.go +++ b/types/execution.go @@ -4,7 +4,7 @@ package types -// Execution represents an instance of a pipeline execution +// 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"` From 2bf73fbb5cef4643886c18dd38c62709806a6c70 Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Wed, 9 Aug 2023 23:56:47 +0100 Subject: [PATCH 21/26] add UpdateOptLock --- internal/api/controller/execution/list.go | 4 +- internal/api/controller/execution/update.go | 12 ++- internal/api/controller/pipeline/update.go | 22 +++-- internal/api/controller/secret/update.go | 22 +++-- .../api/controller/space/list_pipelines.go | 12 ++- internal/api/controller/space/list_secrets.go | 12 ++- internal/api/handler/execution/list.go | 6 +- internal/api/handler/space/list_pipelines.go | 6 +- internal/api/handler/space/list_secrets.go | 6 +- internal/api/request/pipeline.go | 19 ---- internal/api/request/secret.go | 10 -- internal/api/request/util.go | 39 +++----- internal/store/database.go | 26 +++-- internal/store/database/execution.go | 97 ++++++++++++------- internal/store/database/pipeline.go | 43 ++++++-- internal/store/database/secret.go | 49 ++++++++-- mocks/mock_client.go | 3 +- mocks/mock_store.go | 3 +- types/execution.go | 6 -- types/pagination.go | 12 +++ types/pipeline.go | 7 -- types/secret.go | 7 -- 22 files changed, 247 insertions(+), 176 deletions(-) create mode 100644 types/pagination.go diff --git a/internal/api/controller/execution/list.go b/internal/api/controller/execution/list.go index 7d9c79488..ef9ad1a0d 100644 --- a/internal/api/controller/execution/list.go +++ b/internal/api/controller/execution/list.go @@ -19,7 +19,7 @@ func (c *Controller) List( session *auth.Session, spaceRef string, pipelineUID string, - filter *types.ExecutionFilter, + pagination types.Pagination, ) ([]types.Execution, int64, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { @@ -45,7 +45,7 @@ func (c *Controller) List( return fmt.Errorf("failed to count child executions: %w", err) } - executions, dbErr = c.executionStore.List(ctx, pipeline.ID, filter) + executions, dbErr = c.executionStore.List(ctx, pipeline.ID, pagination) if dbErr != nil { return fmt.Errorf("failed to list child executions: %w", err) } diff --git a/internal/api/controller/execution/update.go b/internal/api/controller/execution/update.go index 820d97adb..0bd8a8959 100644 --- a/internal/api/controller/execution/update.go +++ b/internal/api/controller/execution/update.go @@ -45,9 +45,13 @@ func (c *Controller) Update( return nil, fmt.Errorf("failed to find execution: %w", err) } - if in.Status != "" { - execution.Status = in.Status - } + return c.executionStore.UpdateOptLock(ctx, + execution, func(original *types.Execution) error { + // update values only if provided + if in.Status != "" { + original.Status = in.Status + } - return c.executionStore.Update(ctx, execution) + return nil + }) } diff --git a/internal/api/controller/pipeline/update.go b/internal/api/controller/pipeline/update.go index 5f97b3d9d..7cc75cdc9 100644 --- a/internal/api/controller/pipeline/update.go +++ b/internal/api/controller/pipeline/update.go @@ -42,15 +42,17 @@ func (c *Controller) Update( return nil, fmt.Errorf("could not find pipeline: %w", err) } - if in.Description != "" { - pipeline.Description = in.Description - } - if in.UID != "" { - pipeline.UID = in.UID - } - if in.ConfigPath != "" { - pipeline.ConfigPath = in.ConfigPath - } + return c.pipelineStore.UpdateOptLock(ctx, pipeline, func(pipeline *types.Pipeline) error { + if in.Description != "" { + pipeline.Description = in.Description + } + if in.UID != "" { + pipeline.UID = in.UID + } + if in.ConfigPath != "" { + pipeline.ConfigPath = in.ConfigPath + } - return c.pipelineStore.Update(ctx, pipeline) + return nil + }) } diff --git a/internal/api/controller/secret/update.go b/internal/api/controller/secret/update.go index d495dc082..3d283b11d 100644 --- a/internal/api/controller/secret/update.go +++ b/internal/api/controller/secret/update.go @@ -41,15 +41,17 @@ func (c *Controller) Update( return nil, err } - if in.Description != "" { - secret.Description = in.Description - } - if in.Data != "" { - secret.Data = in.Data // will get encrypted at db layer - } - if in.UID != "" { - secret.UID = in.UID - } + return c.secretStore.UpdateOptLock(ctx, secret, func(original *types.Secret) error { + if in.Description != "" { + original.Description = in.Description + } + if in.Data != "" { + original.Data = in.Data // will get encrypted at db layer + } + if in.UID != "" { + original.UID = in.UID + } - return c.secretStore.Update(ctx, secret) + return nil + }) } diff --git a/internal/api/controller/space/list_pipelines.go b/internal/api/controller/space/list_pipelines.go index 59e709e7a..6a70162c9 100644 --- a/internal/api/controller/space/list_pipelines.go +++ b/internal/api/controller/space/list_pipelines.go @@ -15,8 +15,12 @@ import ( ) // ListPipelines lists the pipelines in a space. -func (c *Controller) ListPipelines(ctx context.Context, session *auth.Session, - spaceRef string, filter *types.PipelineFilter) ([]types.Pipeline, int64, error) { +func (c *Controller) ListPipelines( + ctx context.Context, + session *auth.Session, + spaceRef string, + pagination types.Pagination, +) ([]types.Pipeline, int64, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { return nil, 0, fmt.Errorf("failed to find parent space: %w", err) @@ -32,12 +36,12 @@ func (c *Controller) ListPipelines(ctx context.Context, session *auth.Session, err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { var dbErr error - count, dbErr = c.pipelineStore.Count(ctx, space.ID, filter) + count, dbErr = c.pipelineStore.Count(ctx, space.ID, pagination) if dbErr != nil { return fmt.Errorf("failed to count child executions: %w", err) } - pipelines, dbErr = c.pipelineStore.List(ctx, space.ID, filter) + pipelines, dbErr = c.pipelineStore.List(ctx, space.ID, pagination) if dbErr != nil { return fmt.Errorf("failed to list child executions: %w", err) } diff --git a/internal/api/controller/space/list_secrets.go b/internal/api/controller/space/list_secrets.go index 0e175d798..ff49b06d4 100644 --- a/internal/api/controller/space/list_secrets.go +++ b/internal/api/controller/space/list_secrets.go @@ -15,8 +15,12 @@ import ( ) // ListSecrets lists the secrets in a space. -func (c *Controller) ListSecrets(ctx context.Context, session *auth.Session, - spaceRef string, filter *types.SecretFilter) ([]types.Secret, int64, error) { +func (c *Controller) ListSecrets( + ctx context.Context, + session *auth.Session, + spaceRef string, + pagination types.Pagination, +) ([]types.Secret, int64, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { return nil, 0, fmt.Errorf("failed to find parent space: %w", err) @@ -32,12 +36,12 @@ func (c *Controller) ListSecrets(ctx context.Context, session *auth.Session, err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { var dbErr error - count, dbErr = c.secretStore.Count(ctx, space.ID, filter) + count, dbErr = c.secretStore.Count(ctx, space.ID, pagination) if dbErr != nil { return fmt.Errorf("failed to count child executions: %w", err) } - secrets, dbErr = c.secretStore.List(ctx, space.ID, filter) + secrets, dbErr = c.secretStore.List(ctx, space.ID, pagination) if dbErr != nil { return fmt.Errorf("failed to list child executions: %w", err) } diff --git a/internal/api/handler/execution/list.go b/internal/api/handler/execution/list.go index 3ade46038..eb590f239 100644 --- a/internal/api/handler/execution/list.go +++ b/internal/api/handler/execution/list.go @@ -28,15 +28,15 @@ func HandleList(executionCtrl *execution.Controller) http.HandlerFunc { return } - filter := request.ParseExecutionFilter(r) + pagination := request.ParsePaginationFromRequest(r) - repos, totalCount, err := executionCtrl.List(ctx, session, spaceRef, pipelineUID, filter) + repos, totalCount, err := executionCtrl.List(ctx, session, spaceRef, pipelineUID, pagination) if err != nil { render.TranslatedUserError(w, err) return } - render.Pagination(r, w, filter.Page, filter.Size, int(totalCount)) + render.Pagination(r, w, pagination.Page, pagination.Size, int(totalCount)) render.JSON(w, http.StatusOK, repos) } } diff --git a/internal/api/handler/space/list_pipelines.go b/internal/api/handler/space/list_pipelines.go index 0bdd02633..764145ac4 100644 --- a/internal/api/handler/space/list_pipelines.go +++ b/internal/api/handler/space/list_pipelines.go @@ -22,14 +22,14 @@ func HandleListPipelines(spaceCtrl *space.Controller) http.HandlerFunc { return } - filter := request.ParsePipelineFilter(r) - repos, totalCount, err := spaceCtrl.ListPipelines(ctx, session, spaceRef, filter) + pagination := request.ParsePaginationFromRequest(r) + repos, totalCount, err := spaceCtrl.ListPipelines(ctx, session, spaceRef, pagination) if err != nil { render.TranslatedUserError(w, err) return } - render.Pagination(r, w, filter.Page, filter.Size, int(totalCount)) + render.Pagination(r, w, pagination.Page, pagination.Page, int(totalCount)) render.JSON(w, http.StatusOK, repos) } } diff --git a/internal/api/handler/space/list_secrets.go b/internal/api/handler/space/list_secrets.go index 2209493b2..ab76ee74d 100644 --- a/internal/api/handler/space/list_secrets.go +++ b/internal/api/handler/space/list_secrets.go @@ -23,8 +23,8 @@ func HandleListSecrets(spaceCtrl *space.Controller) http.HandlerFunc { return } - filter := request.ParseSecretFilter(r) - ret, totalCount, err := spaceCtrl.ListSecrets(ctx, session, spaceRef, filter) + pagination := request.ParsePaginationFromRequest(r) + ret, totalCount, err := spaceCtrl.ListSecrets(ctx, session, spaceRef, pagination) if err != nil { render.TranslatedUserError(w, err) return @@ -36,7 +36,7 @@ func HandleListSecrets(spaceCtrl *space.Controller) http.HandlerFunc { secrets = append(secrets, *s.CopyWithoutData()) } - render.Pagination(r, w, filter.Page, filter.Size, int(totalCount)) + render.Pagination(r, w, pagination.Page, pagination.Size, int(totalCount)) render.JSON(w, http.StatusOK, secrets) } } diff --git a/internal/api/request/pipeline.go b/internal/api/request/pipeline.go index 4dbb5abde..d6259ae32 100644 --- a/internal/api/request/pipeline.go +++ b/internal/api/request/pipeline.go @@ -7,8 +7,6 @@ package request import ( "net/http" "net/url" - - "github.com/harness/gitness/types" ) const ( @@ -30,20 +28,3 @@ func GetPipelineRefFromPath(r *http.Request) (string, error) { func GetExecutionNumberFromPath(r *http.Request) (int64, error) { return PathParamAsPositiveInt64(r, ExecutionNumber) } - -// ParsePipelineFilter extracts the pipeline filter from the url. -func ParsePipelineFilter(r *http.Request) *types.PipelineFilter { - return &types.PipelineFilter{ - Query: ParseQuery(r), - Page: ParsePage(r), - Size: ParseLimit(r), - } -} - -// ParseExecutionFilter extracts the execution filter from the url. -func ParseExecutionFilter(r *http.Request) *types.ExecutionFilter { - return &types.ExecutionFilter{ - Page: ParsePage(r), - Size: ParseLimit(r), - } -} diff --git a/internal/api/request/secret.go b/internal/api/request/secret.go index d47b140a5..517ff642d 100644 --- a/internal/api/request/secret.go +++ b/internal/api/request/secret.go @@ -7,8 +7,6 @@ package request import ( "net/http" "net/url" - - "github.com/harness/gitness/types" ) const ( @@ -24,11 +22,3 @@ func GetSecretRefFromPath(r *http.Request) (string, error) { // paths are unescaped return url.PathUnescape(rawRef) } - -// ParseExecutionFilter extracts the execution filter from the url. -func ParseSecretFilter(r *http.Request) *types.SecretFilter { - return &types.SecretFilter{ - Page: ParsePage(r), - Size: ParseLimit(r), - } -} diff --git a/internal/api/request/util.go b/internal/api/request/util.go index 709253395..05fc3c81a 100644 --- a/internal/api/request/util.go +++ b/internal/api/request/util.go @@ -9,6 +9,7 @@ import ( "strconv" "github.com/harness/gitness/internal/api/usererror" + "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" "github.com/go-chi/chi" @@ -17,11 +18,10 @@ import ( const ( PathParamRemainder = "*" - QueryParamSort = "sort" - QueryParamOrder = "order" - QueryParamQuery = "query" - QueryParamCreatedBy = "created_by" + QueryParamSort = "sort" + QueryParamOrder = "order" + QueryParamQuery = "query" QueryParamState = "state" QueryParamKind = "kind" @@ -166,26 +166,6 @@ func GetRemainderFromPath(r *http.Request) (string, error) { return PathParamOrError(r, PathParamRemainder) } -// PipelineFilter stores pipeline query parameters. -type Pagination struct { - Page int `json:"page"` - Size int `json:"size"` - Query string `json:"query"` - Sort string `json:"sort"` - Order enum.Order `json:"order"` -} - -// ParseExecutionFilter extracts the execution filter from the url. -func ParseFilterFromRequest(r *http.Request) Pagination { - return Pagination{ - Page: ParsePage(r), - Size: ParseLimit(r), - Query: ParseQuery(r), - Sort: ParseSort(r), - Order: ParseOrder(r), - } -} - // ParseQuery extracts the query parameter from the url. func ParseQuery(r *http.Request) string { return r.URL.Query().Get(QueryParamQuery) @@ -224,3 +204,14 @@ func ParseOrder(r *http.Request) enum.Order { func ParseSort(r *http.Request) string { return r.URL.Query().Get(QueryParamSort) } + +// ParsePaginationFromRequest extracts the pagination info from the url. +func ParsePaginationFromRequest(r *http.Request) types.Pagination { + return types.Pagination{ + Page: ParsePage(r), + Size: ParseLimit(r), + Query: ParseQuery(r), + Sort: ParseSort(r), + Order: ParseOrder(r), + } +} diff --git a/internal/store/database.go b/internal/store/database.go index 87fe85cb9..3d066f3fa 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -449,17 +449,21 @@ type ( // Create creates a new pipeline in the datastore. Create(ctx context.Context, pipeline *types.Pipeline) error - // Update tries to update a pipeline in the datastore with optimistic locking. + // Update tries to update a pipeline in the datastore Update(ctx context.Context, pipeline *types.Pipeline) (*types.Pipeline, error) // List lists the pipelines present in a parent space ID in the datastore. - List(ctx context.Context, spaceID int64, filter *types.PipelineFilter) ([]types.Pipeline, error) + List(ctx context.Context, spaceID int64, pagination types.Pagination) ([]types.Pipeline, error) + + // UpdateOptLock updates the pipeline using the optimistic locking mechanism. + UpdateOptLock(ctx context.Context, pipeline *types.Pipeline, + mutateFn func(pipeline *types.Pipeline) error) (*types.Pipeline, error) // Delete deletes a pipeline ID from the datastore. Delete(ctx context.Context, id int64) error // Count the number of pipelines in a space matching the given filter. - Count(ctx context.Context, spaceID int64, filter *types.PipelineFilter) (int64, error) + Count(ctx context.Context, spaceID int64, filter types.Pagination) (int64, error) // DeleteByUID deletes a pipeline with a given UID in a space DeleteByUID(ctx context.Context, spaceID int64, uid string) error @@ -479,7 +483,11 @@ type ( Create(ctx context.Context, secret *types.Secret) error // Count the number of secrets in a space matching the given filter. - Count(ctx context.Context, spaceID int64, filter *types.SecretFilter) (int64, error) + Count(ctx context.Context, spaceID int64, pagination types.Pagination) (int64, error) + + // UpdateOptLock updates the secret using the optimistic locking mechanism. + UpdateOptLock(ctx context.Context, secret *types.Secret, + mutateFn func(secret *types.Secret) error) (*types.Secret, error) // Update tries to update a secret. Update(ctx context.Context, secret *types.Secret) (*types.Secret, error) @@ -491,7 +499,7 @@ type ( DeleteByUID(ctx context.Context, spaceID int64, uid string) error // List lists the secrets in a given space - List(ctx context.Context, spaceID int64, filter *types.SecretFilter) ([]types.Secret, error) + List(ctx context.Context, spaceID int64, filter types.Pagination) ([]types.Secret, error) } ExecutionStore interface { @@ -501,11 +509,15 @@ type ( // Create creates a new execution in the datastore. Create(ctx context.Context, execution *types.Execution) error - // Update tries to update an execution in the datastore with optimistic locking. + // Update tries to update an execution. Update(ctx context.Context, execution *types.Execution) (*types.Execution, error) + // UpdateOptLock updates the execution using the optimistic locking mechanism. + UpdateOptLock(ctx context.Context, exectuion *types.Execution, + mutateFn func(execution *types.Execution) error) (*types.Execution, error) + // List lists the executions for a given pipeline ID - List(ctx context.Context, pipelineID int64, filter *types.ExecutionFilter) ([]types.Execution, error) + List(ctx context.Context, pipelineID int64, pagination types.Pagination) ([]types.Execution, error) // Delete deletes an execution given a pipeline ID and an execution number Delete(ctx context.Context, pipelineID int64, num int64) error diff --git a/internal/store/database/execution.go b/internal/store/database/execution.go index 73e344beb..29c8f53bf 100644 --- a/internal/store/database/execution.go +++ b/internal/store/database/execution.go @@ -151,36 +151,36 @@ const ( executionUpdateStmt = ` UPDATE executions SET - execution_trigger = :execution_trigger, - execution_parent = :execution_parent, - execution_status = :execution_status, - execution_error = :execution_error, - execution_event = :execution_event, - execution_action = :execution_action, - execution_link = :execution_link, - execution_timestamp = :execution_timestamp, - execution_title = :execution_title, - execution_message = :execution_message, - execution_before = :execution_before, - execution_after = :execution_after, - execution_ref = :execution_ref, - execution_source_repo = :execution_source_repo, - execution_source = :execution_source, - execution_target = :execution_target, - execution_author = :execution_author, - execution_author_name = :execution_author_name, - execution_author_email = :execution_author_email, - execution_author_avatar = :execution_author_avatar, - execution_sender = :execution_sender, - execution_params = :execution_params, - execution_cron = :execution_cron, - execution_deploy = :execution_deploy, - execution_deploy_id = :execution_deploy_id, - execution_debug = :execution_debug, - execution_started = :execution_started, - execution_finished = :execution_finished, - execution_updated = :execution_updated, - execution_version = :execution_version + execution_trigger = :execution_trigger + ,execution_parent = :execution_parent + ,execution_status = :execution_status + ,execution_error = :execution_error + ,execution_event = :execution_event + ,execution_action = :execution_action + ,execution_link = :execution_link + ,execution_timestamp = :execution_timestamp + ,execution_title = :execution_title + ,execution_message = :execution_message + ,execution_before = :execution_before + ,execution_after = :execution_after + ,execution_ref = :execution_ref + ,execution_source_repo = :execution_source_repo + ,execution_source = :execution_source + ,execution_target = :execution_target + ,execution_author = :execution_author + ,execution_author_name = :execution_author_name + ,execution_author_email = :execution_author_email + ,execution_author_avatar = :execution_author_avatar + ,execution_sender = :execution_sender + ,execution_params = :execution_params + ,execution_cron = :execution_cron + ,execution_deploy = :execution_deploy + ,execution_deploy_id = :execution_deploy_id + ,execution_debug = :execution_debug + ,execution_started = :execution_started + ,execution_finished = :execution_finished + ,execution_updated = :execution_updated + ,execution_version = :execution_version WHERE execution_id = :execution_id AND execution_version = :execution_version - 1` ) @@ -244,19 +244,50 @@ func (s *executionStore) Update(ctx context.Context, execution *types.Execution) return execution, nil } +// UpdateOptLock updates the pipeline using the optimistic locking mechanism. +func (s *executionStore) UpdateOptLock(ctx context.Context, + execution *types.Execution, + mutateFn func(execution *types.Execution) error) (*types.Execution, error) { + for { + dup := *execution + + fmt.Println(dup.Status) + + err := mutateFn(&dup) + if err != nil { + return nil, err + } + + fmt.Println("dup.Status after: ", dup.Status) + + execution, err = s.Update(ctx, &dup) + if err == nil { + return &dup, nil + } + if !errors.Is(err, gitness_store.ErrVersionConflict) { + return nil, err + } + + execution, err = s.Find(ctx, execution.PipelineID, execution.Number) + if err != nil { + return nil, err + } + } +} + // List lists the executions for a given pipeline ID. func (s *executionStore) List( ctx context.Context, pipelineID int64, - opts *types.ExecutionFilter, + pagination types.Pagination, ) ([]types.Execution, error) { stmt := database.Builder. Select(executionColumns). From("executions"). Where("execution_pipeline_id = ?", fmt.Sprint(pipelineID)) - stmt = stmt.Limit(database.Limit(opts.Size)) - stmt = stmt.Offset(database.Offset(opts.Page, opts.Size)) + stmt = stmt.Limit(database.Limit(pagination.Size)) + stmt = stmt.Offset(database.Offset(pagination.Page, pagination.Size)) sql, args, err := stmt.ToSql() if err != nil { diff --git a/internal/store/database/pipeline.go b/internal/store/database/pipeline.go index 1e36f9615..a09320fdb 100644 --- a/internal/store/database/pipeline.go +++ b/internal/store/database/pipeline.go @@ -178,19 +178,19 @@ func (s *pipelineStore) Update(ctx context.Context, pipeline *types.Pipeline) (* func (s *pipelineStore) List( ctx context.Context, parentID int64, - opts *types.PipelineFilter, + pagination types.Pagination, ) ([]types.Pipeline, error) { stmt := database.Builder. Select(pipelineColumns). From("pipelines"). Where("pipeline_space_id = ?", fmt.Sprint(parentID)) - if opts.Query != "" { - stmt = stmt.Where("LOWER(pipeline_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(opts.Query))) + if pagination.Query != "" { + stmt = stmt.Where("LOWER(pipeline_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(pagination.Query))) } - stmt = stmt.Limit(database.Limit(opts.Size)) - stmt = stmt.Offset(database.Offset(opts.Page, opts.Size)) + stmt = stmt.Limit(database.Limit(pagination.Size)) + stmt = stmt.Offset(database.Offset(pagination.Page, pagination.Size)) sql, args, err := stmt.ToSql() if err != nil { @@ -207,15 +207,42 @@ func (s *pipelineStore) List( return dst, nil } +// UpdateOptLock updates the pipeline using the optimistic locking mechanism. +func (s *pipelineStore) UpdateOptLock(ctx context.Context, + pipeline *types.Pipeline, + mutateFn func(pipeline *types.Pipeline) error) (*types.Pipeline, error) { + for { + dup := *pipeline + + err := mutateFn(&dup) + if err != nil { + return nil, err + } + + pipeline, err = s.Update(ctx, &dup) + if err == nil { + return &dup, nil + } + if !errors.Is(err, gitness_store.ErrVersionConflict) { + return nil, err + } + + pipeline, err = s.Find(ctx, pipeline.ID) + if err != nil { + return nil, err + } + } +} + // Count of pipelines in a space. -func (s *pipelineStore) Count(ctx context.Context, parentID int64, opts *types.PipelineFilter) (int64, error) { +func (s *pipelineStore) Count(ctx context.Context, parentID int64, filter types.Pagination) (int64, error) { stmt := database.Builder. Select("count(*)"). From("pipelines"). Where("pipeline_space_id = ?", parentID) - if opts.Query != "" { - stmt = stmt.Where("pipeline_uid LIKE ?", fmt.Sprintf("%%%s%%", opts.Query)) + if filter.Query != "" { + stmt = stmt.Where("pipeline_uid LIKE ?", fmt.Sprintf("%%%s%%", filter.Query)) } sql, args, err := stmt.ToSql() diff --git a/internal/store/database/secret.go b/internal/store/database/secret.go index a53a10db7..2ce4f5bf9 100644 --- a/internal/store/database/secret.go +++ b/internal/store/database/secret.go @@ -165,19 +165,46 @@ func (s *secretStore) Update(ctx context.Context, secret *types.Secret) (*types. return secret, nil } +// UpdateOptLock updates the pipeline using the optimistic locking mechanism. +func (s *secretStore) UpdateOptLock(ctx context.Context, + secret *types.Secret, + mutateFn func(secret *types.Secret) error) (*types.Secret, error) { + for { + dup := *secret + + err := mutateFn(&dup) + if err != nil { + return nil, err + } + + secret, err = s.Update(ctx, &dup) + if err == nil { + return &dup, nil + } + if !errors.Is(err, gitness_store.ErrVersionConflict) { + return nil, err + } + + secret, err = s.Find(ctx, secret.ID) + if err != nil { + return nil, err + } + } +} + // List lists all the secrets present in a space. -func (s *secretStore) List(ctx context.Context, parentID int64, opts *types.SecretFilter) ([]types.Secret, error) { +func (s *secretStore) List(ctx context.Context, parentID int64, pagination types.Pagination) ([]types.Secret, error) { stmt := database.Builder. Select(secretColumns). From("secrets"). Where("secret_space_id = ?", fmt.Sprint(parentID)) - if opts.Query != "" { - stmt = stmt.Where("LOWER(secret_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(opts.Query))) + if pagination.Query != "" { + stmt = stmt.Where("LOWER(secret_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(pagination.Query))) } - stmt = stmt.Limit(database.Limit(opts.Size)) - stmt = stmt.Offset(database.Offset(opts.Page, opts.Size)) + stmt = stmt.Limit(database.Limit(pagination.Size)) + stmt = stmt.Offset(database.Offset(pagination.Page, pagination.Size)) sql, args, err := stmt.ToSql() if err != nil { @@ -225,14 +252,14 @@ func (s *secretStore) DeleteByUID(ctx context.Context, spaceID int64, uid string } // Count of secrets in a space. -func (s *secretStore) Count(ctx context.Context, parentID int64, opts *types.SecretFilter) (int64, error) { +func (s *secretStore) Count(ctx context.Context, parentID int64, filter types.Pagination) (int64, error) { stmt := database.Builder. Select("count(*)"). From("secrets"). Where("secret_space_id = ?", parentID) - if opts.Query != "" { - stmt = stmt.Where("secret_uid LIKE ?", fmt.Sprintf("%%%s%%", opts.Query)) + if filter.Query != "" { + stmt = stmt.Where("secret_uid LIKE ?", fmt.Sprintf("%%%s%%", filter.Query)) } sql, args, err := stmt.ToSql() @@ -252,6 +279,9 @@ func (s *secretStore) Count(ctx context.Context, parentID int64, opts *types.Sec // helper function returns the same secret with encrypted data. func enc(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error) { + if secret == nil { + return nil, fmt.Errorf("cannot encrypt a nil secret") + } s := *secret ciphertext, err := encrypt.Encrypt(secret.Data) if err != nil { @@ -263,6 +293,9 @@ func enc(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error) // helper function returns the same secret with decrypted data. func dec(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error) { + if secret == nil { + return nil, fmt.Errorf("cannot decrypt a nil secret") + } s := *secret plaintext, err := encrypt.Decrypt([]byte(secret.Data)) if err != nil { diff --git a/mocks/mock_client.go b/mocks/mock_client.go index 3cb746055..b16a6490e 100644 --- a/mocks/mock_client.go +++ b/mocks/mock_client.go @@ -8,10 +8,9 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" user "github.com/harness/gitness/internal/api/controller/user" types "github.com/harness/gitness/types" - - gomock "github.com/golang/mock/gomock" ) // MockClient is a mock of Client interface. diff --git a/mocks/mock_store.go b/mocks/mock_store.go index 9310f3729..0af0bbc34 100644 --- a/mocks/mock_store.go +++ b/mocks/mock_store.go @@ -8,10 +8,9 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" types "github.com/harness/gitness/types" enum "github.com/harness/gitness/types/enum" - - gomock "github.com/golang/mock/gomock" ) // MockPrincipalStore is a mock of PrincipalStore interface. diff --git a/types/execution.go b/types/execution.go index fde55504e..a66ff6680 100644 --- a/types/execution.go +++ b/types/execution.go @@ -44,9 +44,3 @@ type Execution struct { // TODO: (Vistaar) Add stages // Stages []*Stage `db:"-" json:"stages,omitempty"` } - -// ExecutionFilter stores execution query parameters. -type ExecutionFilter struct { - Page int `json:"page"` - Size int `json:"size"` -} diff --git a/types/pagination.go b/types/pagination.go new file mode 100644 index 000000000..28bd01971 --- /dev/null +++ b/types/pagination.go @@ -0,0 +1,12 @@ +package types + +import "github.com/harness/gitness/types/enum" + +// Pagination stores pagination related params +type Pagination struct { + Page int `json:"page"` + Size int `json:"size"` + Query string `json:"query"` + Sort string `json:"sort"` + Order enum.Order `json:"order"` +} diff --git a/types/pipeline.go b/types/pipeline.go index b368dd5de..019bf99a1 100644 --- a/types/pipeline.go +++ b/types/pipeline.go @@ -21,10 +21,3 @@ type Pipeline struct { Updated int64 `db:"pipeline_updated" json:"updated"` Version int64 `db:"pipeline_version" json:"version"` } - -// PipelineFilter stores pipeline query parameters. -type PipelineFilter struct { - Page int `json:"page"` - Size int `json:"size"` - Query string `json:"query"` -} diff --git a/types/secret.go b/types/secret.go index 6b20e7b5e..f4bfa5b88 100644 --- a/types/secret.go +++ b/types/secret.go @@ -15,13 +15,6 @@ type Secret struct { Version int64 `db:"secret_version" json:"version"` } -// SecretFilter stores secret query parameters. -type SecretFilter struct { - Page int `json:"page"` - Size int `json:"size"` - Query string `json:"query"` -} - // Copy makes a copy of the secret without the value. func (s *Secret) CopyWithoutData() *Secret { return &Secret{ From f4ea3d714e5d347fa297c5609746d2ae4cfffc65 Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Thu, 10 Aug 2023 00:02:46 +0100 Subject: [PATCH 22/26] wrap list error --- internal/api/controller/execution/list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/api/controller/execution/list.go b/internal/api/controller/execution/list.go index ef9ad1a0d..2d2285521 100644 --- a/internal/api/controller/execution/list.go +++ b/internal/api/controller/execution/list.go @@ -53,7 +53,7 @@ func (c *Controller) List( return dbErr }, dbtx.TxDefaultReadOnly) if err != nil { - return executions, count, err + return executions, count, fmt.Errorf("failed to fetch list: %w", err) } return executions, count, nil From 01fffd56a34d991a8d38aa9f25035df2573b20e9 Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Thu, 10 Aug 2023 14:33:02 +0100 Subject: [PATCH 23/26] address comments --- cmd/gitness/wire_gen.go | 8 +- internal/api/controller/secret/controller.go | 4 + internal/api/controller/secret/create.go | 33 ++++ internal/api/controller/secret/delete.go | 4 +- internal/api/controller/secret/find.go | 15 +- internal/api/controller/secret/update.go | 16 +- internal/api/controller/secret/wire.go | 4 +- .../api/controller/space/list_pipelines.go | 20 +-- internal/api/controller/space/list_secrets.go | 20 +-- internal/api/handler/secret/delete.go | 3 - internal/api/handler/secret/update.go | 3 - internal/store/database.go | 6 +- internal/store/database/execution.go | 105 ++++-------- .../database/migrate/ci/ci_migrations.sql | 116 ++++++------- internal/store/database/pipeline.go | 100 ++++++----- internal/store/database/secret.go | 158 +++++++----------- internal/store/database/wire.go | 4 +- mocks/mock_client.go | 3 +- mocks/mock_store.go | 3 +- 19 files changed, 297 insertions(+), 328 deletions(-) diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index 81aca6116..a9d129906 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -91,14 +91,14 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro executionStore := database.ProvideExecutionStore(db) pipelineStore := database.ProvidePipelineStore(db) executionController := execution.ProvideController(db, authorizer, executionStore, pipelineStore, spaceStore) + secretStore := database.ProvideSecretStore(db) + spaceController := space.ProvideController(db, provider, pathUID, authorizer, pathStore, pipelineStore, secretStore, spaceStore, repoStore, principalStore, repoController, membershipStore) + pipelineController := pipeline.ProvideController(db, pathUID, pathStore, repoStore, authorizer, pipelineStore, spaceStore) encrypter, err := database.ProvideEncryptor(databaseConfig) if err != nil { return nil, err } - secretStore := database.ProvideSecretStore(encrypter, db) - spaceController := space.ProvideController(db, provider, pathUID, authorizer, pathStore, pipelineStore, secretStore, spaceStore, repoStore, principalStore, repoController, membershipStore) - pipelineController := pipeline.ProvideController(db, pathUID, pathStore, repoStore, authorizer, pipelineStore, spaceStore) - secretController := secret.ProvideController(db, pathUID, pathStore, secretStore, authorizer, spaceStore) + secretController := secret.ProvideController(db, pathUID, pathStore, encrypter, secretStore, authorizer, spaceStore) pullReqStore := database.ProvidePullReqStore(db, principalInfoCache) pullReqActivityStore := database.ProvidePullReqActivityStore(db, principalInfoCache) codeCommentView := database.ProvideCodeCommentView(db) diff --git a/internal/api/controller/secret/controller.go b/internal/api/controller/secret/controller.go index c979b6dcb..5a62633b2 100644 --- a/internal/api/controller/secret/controller.go +++ b/internal/api/controller/secret/controller.go @@ -5,6 +5,7 @@ package secret import ( + "github.com/harness/gitness/encrypt" "github.com/harness/gitness/internal/auth/authz" "github.com/harness/gitness/internal/store" "github.com/harness/gitness/types/check" @@ -16,6 +17,7 @@ type Controller struct { db *sqlx.DB uidCheck check.PathUID pathStore store.PathStore + encrypter encrypt.Encrypter secretStore store.SecretStore authorizer authz.Authorizer spaceStore store.SpaceStore @@ -26,6 +28,7 @@ func NewController( uidCheck check.PathUID, authorizer authz.Authorizer, pathStore store.PathStore, + encrypter encrypt.Encrypter, secretStore store.SecretStore, spaceStore store.SpaceStore, ) *Controller { @@ -33,6 +36,7 @@ func NewController( db: db, uidCheck: uidCheck, pathStore: pathStore, + encrypter: encrypter, secretStore: secretStore, authorizer: authorizer, spaceStore: spaceStore, diff --git a/internal/api/controller/secret/create.go b/internal/api/controller/secret/create.go index fe6da13f6..63b4ee881 100644 --- a/internal/api/controller/secret/create.go +++ b/internal/api/controller/secret/create.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/harness/gitness/encrypt" apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/api/usererror" "github.com/harness/gitness/internal/auth" @@ -66,6 +67,10 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea Updated: now, Version: 0, } + secret, err = enc(c.encrypter, secret) + if err != nil { + return fmt.Errorf("could not encrypt secret: %w", err) + } err = c.secretStore.Create(ctx, secret) if err != nil { return fmt.Errorf("secret creation failed: %w", err) @@ -97,3 +102,31 @@ func (c *Controller) sanitizeCreateInput(in *CreateInput) error { return nil } + +// helper function returns the same secret with encrypted data. +func enc(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error) { + if secret == nil { + return nil, fmt.Errorf("cannot encrypt a nil secret") + } + s := *secret + ciphertext, err := encrypt.Encrypt(secret.Data) + if err != nil { + return nil, err + } + s.Data = string(ciphertext) + return &s, nil +} + +// helper function returns the same secret with decrypted data. +func dec(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error) { + if secret == nil { + return nil, fmt.Errorf("cannot decrypt a nil secret") + } + s := *secret + plaintext, err := encrypt.Decrypt([]byte(secret.Data)) + if err != nil { + return nil, err + } + s.Data = plaintext + return &s, nil +} diff --git a/internal/api/controller/secret/delete.go b/internal/api/controller/secret/delete.go index 8e42ede62..bcb31e1be 100644 --- a/internal/api/controller/secret/delete.go +++ b/internal/api/controller/secret/delete.go @@ -16,12 +16,12 @@ import ( 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 + return fmt.Errorf("could not find space: %w", err) } err = apiauth.CheckSecret(ctx, c.authorizer, session, space.Path, uid, enum.PermissionSecretDelete) if err != nil { - return err + return fmt.Errorf("failed to authorize: %w", err) } err = c.secretStore.DeleteByUID(ctx, space.ID, uid) if err != nil { diff --git a/internal/api/controller/secret/find.go b/internal/api/controller/secret/find.go index 91d2e492f..34dcc9f3d 100644 --- a/internal/api/controller/secret/find.go +++ b/internal/api/controller/secret/find.go @@ -6,6 +6,7 @@ package secret import ( "context" + "fmt" apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" @@ -21,11 +22,19 @@ func (c *Controller) Find( ) (*types.Secret, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { - return nil, err + return nil, fmt.Errorf("could not find space: %w", err) } err = apiauth.CheckSecret(ctx, c.authorizer, session, space.Path, uid, enum.PermissionSecretView) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to authorize: %w", err) } - return c.secretStore.FindByUID(ctx, space.ID, uid) + secret, err := c.secretStore.FindByUID(ctx, space.ID, uid) + if err != nil { + return nil, fmt.Errorf("could not find secret: %w", err) + } + secret, err = dec(c.encrypter, secret) + if err != nil { + return nil, fmt.Errorf("could not decrypt secret: %w", err) + } + return secret, nil } diff --git a/internal/api/controller/secret/update.go b/internal/api/controller/secret/update.go index 3d283b11d..2ec2de78b 100644 --- a/internal/api/controller/secret/update.go +++ b/internal/api/controller/secret/update.go @@ -6,6 +6,7 @@ package secret import ( "context" + "fmt" apiauth "github.com/harness/gitness/internal/api/auth" "github.com/harness/gitness/internal/auth" @@ -25,20 +26,21 @@ func (c *Controller) Update( session *auth.Session, spaceRef string, uid string, - in *UpdateInput) (*types.Secret, error) { + in *UpdateInput, +) (*types.Secret, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { - return nil, err + return nil, fmt.Errorf("could not find space: %w", err) } err = apiauth.CheckSecret(ctx, c.authorizer, session, space.Path, uid, enum.PermissionSecretEdit) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to authorize: %w", err) } secret, err := c.secretStore.FindByUID(ctx, space.ID, uid) if err != nil { - return nil, err + return nil, fmt.Errorf("could not find secret: %w", err) } return c.secretStore.UpdateOptLock(ctx, secret, func(original *types.Secret) error { @@ -46,7 +48,11 @@ func (c *Controller) Update( original.Description = in.Description } if in.Data != "" { - original.Data = in.Data // will get encrypted at db layer + data, err := c.encrypter.Encrypt(original.Data) + if err != nil { + return err + } + original.Data = string(data) } if in.UID != "" { original.UID = in.UID diff --git a/internal/api/controller/secret/wire.go b/internal/api/controller/secret/wire.go index 8922a5dc1..807cac0d8 100644 --- a/internal/api/controller/secret/wire.go +++ b/internal/api/controller/secret/wire.go @@ -5,6 +5,7 @@ package secret import ( + "github.com/harness/gitness/encrypt" "github.com/harness/gitness/internal/auth/authz" "github.com/harness/gitness/internal/store" "github.com/harness/gitness/types/check" @@ -21,9 +22,10 @@ var WireSet = wire.NewSet( func ProvideController(db *sqlx.DB, uidCheck check.PathUID, pathStore store.PathStore, + encrypter encrypt.Encrypter, secretStore store.SecretStore, authorizer authz.Authorizer, spaceStore store.SpaceStore, ) *Controller { - return NewController(db, uidCheck, authorizer, pathStore, secretStore, spaceStore) + return NewController(db, uidCheck, authorizer, pathStore, encrypter, secretStore, spaceStore) } diff --git a/internal/api/controller/space/list_pipelines.go b/internal/api/controller/space/list_pipelines.go index 6a70162c9..276106671 100644 --- a/internal/api/controller/space/list_pipelines.go +++ b/internal/api/controller/space/list_pipelines.go @@ -35,21 +35,21 @@ func (c *Controller) ListPipelines( var pipelines []types.Pipeline err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { - var dbErr error - count, dbErr = c.pipelineStore.Count(ctx, space.ID, pagination) - if dbErr != nil { - return fmt.Errorf("failed to count child executions: %w", err) + count, err = c.pipelineStore.Count(ctx, space.ID, pagination) + if err != nil { + err = fmt.Errorf("failed to count child executions: %w", err) + return } - pipelines, dbErr = c.pipelineStore.List(ctx, space.ID, pagination) - if dbErr != nil { - return fmt.Errorf("failed to list child executions: %w", err) + pipelines, err = c.pipelineStore.List(ctx, space.ID, pagination) + if err != nil { + err = fmt.Errorf("failed to count child executions: %w", err) + return } - - return dbErr + return }, dbtx.TxDefaultReadOnly) if err != nil { - return pipelines, count, err + return pipelines, count, fmt.Errorf("failed to list pipelines: %w", err) } return pipelines, count, nil diff --git a/internal/api/controller/space/list_secrets.go b/internal/api/controller/space/list_secrets.go index ff49b06d4..63db20f8e 100644 --- a/internal/api/controller/space/list_secrets.go +++ b/internal/api/controller/space/list_secrets.go @@ -35,21 +35,21 @@ func (c *Controller) ListSecrets( var secrets []types.Secret err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { - var dbErr error - count, dbErr = c.secretStore.Count(ctx, space.ID, pagination) - if dbErr != nil { - return fmt.Errorf("failed to count child executions: %w", err) + count, err = c.secretStore.Count(ctx, space.ID, pagination) + if err != nil { + err = fmt.Errorf("failed to count child executions: %w", err) + return } - secrets, dbErr = c.secretStore.List(ctx, space.ID, pagination) - if dbErr != nil { - return fmt.Errorf("failed to list child executions: %w", err) + secrets, err = c.secretStore.List(ctx, space.ID, pagination) + if err != nil { + err = fmt.Errorf("failed to list child executions: %w", err) + return } - - return dbErr + return }, dbtx.TxDefaultReadOnly) if err != nil { - return secrets, count, err + return secrets, count, fmt.Errorf("failed to list secrets: %w", err) } return secrets, count, nil diff --git a/internal/api/handler/secret/delete.go b/internal/api/handler/secret/delete.go index 681f6bd34..968a9d171 100644 --- a/internal/api/handler/secret/delete.go +++ b/internal/api/handler/secret/delete.go @@ -13,9 +13,6 @@ import ( "github.com/harness/gitness/internal/paths" ) -/* - * Deletes a secret. - */ func HandleDelete(secretCtrl *secret.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() diff --git a/internal/api/handler/secret/update.go b/internal/api/handler/secret/update.go index 6722ab14c..618319265 100644 --- a/internal/api/handler/secret/update.go +++ b/internal/api/handler/secret/update.go @@ -14,9 +14,6 @@ import ( "github.com/harness/gitness/internal/paths" ) -/* - * Updates an existing secret. - */ func HandleUpdate(secretCtrl *secret.Controller) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() diff --git a/internal/store/database.go b/internal/store/database.go index 3d066f3fa..3d4c6bd24 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -450,7 +450,7 @@ type ( Create(ctx context.Context, pipeline *types.Pipeline) error // Update tries to update a pipeline in the datastore - Update(ctx context.Context, pipeline *types.Pipeline) (*types.Pipeline, error) + Update(ctx context.Context, pipeline *types.Pipeline) error // List lists the pipelines present in a parent space ID in the datastore. List(ctx context.Context, spaceID int64, pagination types.Pagination) ([]types.Pipeline, error) @@ -490,7 +490,7 @@ type ( mutateFn func(secret *types.Secret) error) (*types.Secret, error) // Update tries to update a secret. - Update(ctx context.Context, secret *types.Secret) (*types.Secret, error) + Update(ctx context.Context, secret *types.Secret) error // Delete deletes a secret given an ID. Delete(ctx context.Context, id int64) error @@ -510,7 +510,7 @@ type ( Create(ctx context.Context, execution *types.Execution) error // Update tries to update an execution. - Update(ctx context.Context, execution *types.Execution) (*types.Execution, error) + Update(ctx context.Context, execution *types.Execution) error // UpdateOptLock updates the execution using the optimistic locking mechanism. UpdateOptLock(ctx context.Context, exectuion *types.Execution, diff --git a/internal/store/database/execution.go b/internal/store/database/execution.go index 29c8f53bf..8edc51338 100644 --- a/internal/store/database/execution.go +++ b/internal/store/database/execution.go @@ -33,10 +33,6 @@ type executionStore struct { } const ( - executionQueryBase = ` - SELECT` + executionColumns + ` - FROM executions` - executionColumns = ` execution_id ,execution_pipeline_id @@ -74,8 +70,26 @@ const ( ,execution_updated ,execution_version ` +) - executionInsertStmt = ` +// Find returns an execution given a pipeline ID and an execution number. +func (s *executionStore) Find(ctx context.Context, pipelineID int64, executionNum int64) (*types.Execution, error) { + const findQueryStmt = ` + SELECT` + executionColumns + ` + FROM executions + WHERE execution_pipeline_id = $1 AND execution_number = $2` + db := dbtx.GetAccessor(ctx, s.db) + + dst := new(types.Execution) + if err := db.GetContext(ctx, dst, findQueryStmt, pipelineID, executionNum); err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed to find execution") + } + return dst, nil +} + +// Create creates a new execution in the datastore. +func (s *executionStore) Create(ctx context.Context, execution *types.Execution) error { + const executionInsertStmt = ` INSERT INTO executions ( execution_pipeline_id ,execution_repo_id @@ -147,58 +161,6 @@ const ( ,:execution_updated ,:execution_version ) RETURNING execution_id` - - executionUpdateStmt = ` - UPDATE executions - SET - execution_trigger = :execution_trigger - ,execution_parent = :execution_parent - ,execution_status = :execution_status - ,execution_error = :execution_error - ,execution_event = :execution_event - ,execution_action = :execution_action - ,execution_link = :execution_link - ,execution_timestamp = :execution_timestamp - ,execution_title = :execution_title - ,execution_message = :execution_message - ,execution_before = :execution_before - ,execution_after = :execution_after - ,execution_ref = :execution_ref - ,execution_source_repo = :execution_source_repo - ,execution_source = :execution_source - ,execution_target = :execution_target - ,execution_author = :execution_author - ,execution_author_name = :execution_author_name - ,execution_author_email = :execution_author_email - ,execution_author_avatar = :execution_author_avatar - ,execution_sender = :execution_sender - ,execution_params = :execution_params - ,execution_cron = :execution_cron - ,execution_deploy = :execution_deploy - ,execution_deploy_id = :execution_deploy_id - ,execution_debug = :execution_debug - ,execution_started = :execution_started - ,execution_finished = :execution_finished - ,execution_updated = :execution_updated - ,execution_version = :execution_version - WHERE execution_id = :execution_id AND execution_version = :execution_version - 1` -) - -// Find returns an execution given a pipeline ID and an execution number. -func (s *executionStore) Find(ctx context.Context, pipelineID int64, executionNum int64) (*types.Execution, error) { - const findQueryStmt = executionQueryBase + ` - WHERE execution_pipeline_id = $1 AND execution_number = $2` - db := dbtx.GetAccessor(ctx, s.db) - - dst := new(types.Execution) - if err := db.GetContext(ctx, dst, findQueryStmt, pipelineID, executionNum); err != nil { - return nil, database.ProcessSQLErrorf(err, "Failed to find execution") - } - return dst, nil -} - -// Create creates a new execution in the datastore. -func (s *executionStore) Create(ctx context.Context, execution *types.Execution) error { db := dbtx.GetAccessor(ctx, s.db) query, arg, err := db.BindNamed(executionInsertStmt, execution) @@ -214,7 +176,18 @@ func (s *executionStore) Create(ctx context.Context, execution *types.Execution) } // Update tries to update an execution in the datastore with optimistic locking. -func (s *executionStore) Update(ctx context.Context, execution *types.Execution) (*types.Execution, error) { +func (s *executionStore) Update(ctx context.Context, execution *types.Execution) error { + const executionUpdateStmt = ` + UPDATE executions + SET + ,execution_status = :execution_status + ,execution_error = :execution_error + ,execution_event = :execution_event + ,execution_started = :execution_started + ,execution_finished = :execution_finished + ,execution_updated = :execution_updated + ,execution_version = :execution_version + WHERE execution_id = :execution_id AND execution_version = :execution_version - 1` updatedAt := time.Now() execution.Version++ @@ -224,24 +197,24 @@ func (s *executionStore) Update(ctx context.Context, execution *types.Execution) query, arg, err := db.BindNamed(executionUpdateStmt, execution) if err != nil { - return nil, database.ProcessSQLErrorf(err, "Failed to bind execution object") + return database.ProcessSQLErrorf(err, "Failed to bind execution object") } result, err := db.ExecContext(ctx, query, arg...) if err != nil { - return nil, database.ProcessSQLErrorf(err, "Failed to update execution") + return database.ProcessSQLErrorf(err, "Failed to update execution") } count, err := result.RowsAffected() if err != nil { - return nil, database.ProcessSQLErrorf(err, "Failed to get number of updated rows") + return database.ProcessSQLErrorf(err, "Failed to get number of updated rows") } if count == 0 { - return nil, gitness_store.ErrVersionConflict + return gitness_store.ErrVersionConflict } - return execution, nil + return nil } // UpdateOptLock updates the pipeline using the optimistic locking mechanism. @@ -251,16 +224,12 @@ func (s *executionStore) UpdateOptLock(ctx context.Context, for { dup := *execution - fmt.Println(dup.Status) - err := mutateFn(&dup) if err != nil { return nil, err } - fmt.Println("dup.Status after: ", dup.Status) - - execution, err = s.Update(ctx, &dup) + err = s.Update(ctx, &dup) if err == nil { return &dup, nil } diff --git a/internal/store/database/migrate/ci/ci_migrations.sql b/internal/store/database/migrate/ci/ci_migrations.sql index 57df6eaf5..89975c98a 100644 --- a/internal/store/database/migrate/ci/ci_migrations.sql +++ b/internal/store/database/migrate/ci/ci_migrations.sql @@ -1,17 +1,17 @@ CREATE TABLE IF NOT EXISTS pipelines ( - pipeline_id INTEGER PRIMARY KEY AUTOINCREMENT, - pipeline_description TEXT, - pipeline_space_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 NOT NULL, - pipeline_created INTEGER NOT NULL, - pipeline_updated INTEGER NOT NULL, - pipeline_version INTEGER NOT NULL, + pipeline_id INTEGER PRIMARY KEY AUTOINCREMENT + ,pipeline_description TEXT NOT NULL + ,pipeline_space_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 NOT NULL + ,pipeline_created INTEGER NOT NULL + ,pipeline_updated INTEGER NOT NULL + ,pipeline_version INTEGER NOT NULL -- Ensure unique combination of UID and ParentID UNIQUE (pipeline_space_id, pipeline_uid), @@ -30,67 +30,67 @@ CREATE TABLE IF NOT EXISTS pipelines ( ); 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 NOT NULL, - execution_updated INTEGER NOT NULL, - execution_version INTEGER NOT NULL, + 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 NOT NULL + ,execution_updated INTEGER NOT NULL + ,execution_version INTEGER NOT NULL -- Ensure unique combination of pipeline ID and number UNIQUE (execution_pipeline_id, execution_number), -- Foreign key to pipelines table - CONSTRAINT fk_executions_pipeline_id FOREIGN KEY (execution_pipeline_id) + CONSTRAINT fk_executions_pipeline_id FOREIGN KEY (,execution_pipeline_id) REFERENCES pipelines (pipeline_id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE -- Foreign key to repositories table - CONSTRAINT fk_executions_repo_id FOREIGN KEY (execution_repo_id) + CONSTRAINT fk_executions_repo_id FOREIGN KEY (,execution_repo_id) REFERENCES repositories (repo_id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS secrets ( - secret_id INTEGER PRIMARY KEY AUTOINCREMENT, - secret_uid TEXT NOT NULL, - secret_space_id INTEGER NOT NULL, - secret_description TEXT, - secret_data BLOB NOT NULL, - secret_created INTEGER NOT NULL, - secret_updated INTEGER NOT NULL, - secret_version INTEGER NOT NULL, + secret_id INTEGER PRIMARY KEY AUTOINCREMENT + ,secret_uid TEXT NOT NULL + ,secret_space_id INTEGER NOT NULL + ,secret_description TEXT NOT NULL + ,secret_data BLOB NOT NULL + ,secret_created INTEGER NOT NULL + ,secret_updated INTEGER NOT NULL + ,secret_version INTEGER NOT NULL -- Ensure unique combination of space ID and UID UNIQUE (secret_space_id, secret_uid), diff --git a/internal/store/database/pipeline.go b/internal/store/database/pipeline.go index a09320fdb..5b83426f8 100644 --- a/internal/store/database/pipeline.go +++ b/internal/store/database/pipeline.go @@ -43,51 +43,6 @@ const ( ,pipeline_updated ,pipeline_version ` - - pipelineInsertStmt = ` - INSERT INTO pipelines ( - pipeline_description - ,pipeline_space_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_space_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_space_id = :pipeline_space_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_updated = :pipeline_updated, - pipeline_version = :pipeline_version - WHERE pipeline_id = :pipeline_id AND pipeline_version = :pipeline_version - 1` ) // NewPipelineStore returns a new PipelineStore. @@ -129,6 +84,34 @@ func (s *pipelineStore) FindByUID(ctx context.Context, spaceID int64, uid string // Create creates a pipeline. func (s *pipelineStore) Create(ctx context.Context, pipeline *types.Pipeline) error { + const pipelineInsertStmt = ` + INSERT INTO pipelines ( + pipeline_description + ,pipeline_space_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_space_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` db := dbtx.GetAccessor(ctx, s.db) query, arg, err := db.BindNamed(pipelineInsertStmt, pipeline) @@ -144,7 +127,18 @@ func (s *pipelineStore) Create(ctx context.Context, pipeline *types.Pipeline) er } // Update updates a pipeline. -func (s *pipelineStore) Update(ctx context.Context, pipeline *types.Pipeline) (*types.Pipeline, error) { +func (s *pipelineStore) Update(ctx context.Context, pipeline *types.Pipeline) error { + const pipelineUpdateStmt = ` + UPDATE pipelines + SET + pipeline_description = :pipeline_description, + pipeline_uid = :pipeline_uid, + pipeline_seq = :pipeline_seq, + pipeline_default_branch = :pipeline_default_branch, + pipeline_config_path = :pipeline_config_path, + pipeline_updated = :pipeline_updated, + pipeline_version = :pipeline_version + WHERE pipeline_id = :pipeline_id AND pipeline_version = :pipeline_version - 1` updatedAt := time.Now() pipeline.Version++ @@ -154,24 +148,24 @@ func (s *pipelineStore) Update(ctx context.Context, pipeline *types.Pipeline) (* query, arg, err := db.BindNamed(pipelineUpdateStmt, pipeline) if err != nil { - return nil, database.ProcessSQLErrorf(err, "Failed to bind pipeline object") + return 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") + return 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") + return database.ProcessSQLErrorf(err, "Failed to get number of updated rows") } if count == 0 { - return nil, gitness_store.ErrVersionConflict + return gitness_store.ErrVersionConflict } - return pipeline, nil + return nil } // List lists all the pipelines present in a space. @@ -219,7 +213,7 @@ func (s *pipelineStore) UpdateOptLock(ctx context.Context, return nil, err } - pipeline, err = s.Update(ctx, &dup) + err = s.Update(ctx, &dup) if err == nil { return &dup, nil } @@ -296,7 +290,7 @@ func (s *pipelineStore) IncrementSeqNum(ctx context.Context, pipeline *types.Pip for { var err error pipeline.Seq++ - pipeline, err = s.Update(ctx, pipeline) + err = s.Update(ctx, pipeline) if err == nil { return pipeline, nil } else if !errors.Is(err, gitness_store.ErrVersionConflict) { diff --git a/internal/store/database/secret.go b/internal/store/database/secret.go index 2ce4f5bf9..5761a468b 100644 --- a/internal/store/database/secret.go +++ b/internal/store/database/secret.go @@ -10,7 +10,6 @@ import ( "strings" "time" - "github.com/harness/gitness/encrypt" "github.com/harness/gitness/internal/store" gitness_store "github.com/harness/gitness/store" "github.com/harness/gitness/store/database" @@ -38,8 +37,48 @@ const ( secret_updated, secret_version ` +) - secretInsertStmt = ` +// NewSecretStore returns a new SecretStore. +func NewSecretStore(db *sqlx.DB) *secretStore { + return &secretStore{ + db: db, + } +} + +type secretStore struct { + db *sqlx.DB +} + +// Find returns a secret given a secret ID. +func (s *secretStore) Find(ctx context.Context, id int64) (*types.Secret, error) { + const findQueryStmt = secretQueryBase + ` + WHERE secret_id = $1` + db := dbtx.GetAccessor(ctx, s.db) + + dst := new(types.Secret) + if err := db.GetContext(ctx, dst, findQueryStmt, id); err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed to find secret") + } + return dst, nil +} + +// FindByUID returns a secret in a given space with a given UID. +func (s *secretStore) FindByUID(ctx context.Context, spaceID int64, uid string) (*types.Secret, error) { + const findQueryStmt = secretQueryBase + ` + WHERE secret_space_id = $1 AND secret_uid = $2` + db := dbtx.GetAccessor(ctx, s.db) + + dst := new(types.Secret) + if err := db.GetContext(ctx, dst, findQueryStmt, spaceID, uid); err != nil { + return nil, database.ProcessSQLErrorf(err, "Failed to find secret") + } + return dst, nil +} + +// Create creates a secret. +func (s *secretStore) Create(ctx context.Context, secret *types.Secret) error { + const secretInsertStmt = ` INSERT INTO secrets ( secret_description, secret_space_id, @@ -57,67 +96,8 @@ const ( :secret_updated, :secret_version ) RETURNING secret_id` - - secretUpdateStmt = ` - UPDATE secrets - SET - secret_description = :secret_description, - secret_space_id = :secret_space_id, - secret_uid = :secret_uid, - secret_data = :secret_data, - secret_updated = :secret_updated, - secret_version = :secret_version - WHERE secret_id = :secret_id AND secret_version = :secret_version - 1` -) - -// NewSecretStore returns a new SecretStore. -func NewSecretStore(enc encrypt.Encrypter, db *sqlx.DB) *secretStore { - return &secretStore{ - db: db, - enc: enc, - } -} - -type secretStore struct { - db *sqlx.DB - enc encrypt.Encrypter -} - -// Find returns a secret given a secret ID. -func (s *secretStore) Find(ctx context.Context, id int64) (*types.Secret, error) { - const findQueryStmt = secretQueryBase + ` - WHERE secret_id = $1` db := dbtx.GetAccessor(ctx, s.db) - dst := new(types.Secret) - if err := db.GetContext(ctx, dst, findQueryStmt, id); err != nil { - return nil, database.ProcessSQLErrorf(err, "Failed to find secret") - } - return dec(s.enc, dst) -} - -// FindByUID returns a secret in a given space with a given UID. -func (s *secretStore) FindByUID(ctx context.Context, spaceID int64, uid string) (*types.Secret, error) { - const findQueryStmt = secretQueryBase + ` - WHERE secret_space_id = $1 AND secret_uid = $2` - db := dbtx.GetAccessor(ctx, s.db) - - dst := new(types.Secret) - if err := db.GetContext(ctx, dst, findQueryStmt, spaceID, uid); err != nil { - return nil, database.ProcessSQLErrorf(err, "Failed to find secret") - } - return dec(s.enc, dst) -} - -// Create creates a secret. -func (s *secretStore) Create(ctx context.Context, secret *types.Secret) error { - db := dbtx.GetAccessor(ctx, s.db) - - secret, err := enc(s.enc, secret) - if err != nil { - return err - } - query, arg, err := db.BindNamed(secretInsertStmt, secret) if err != nil { return database.ProcessSQLErrorf(err, "Failed to bind secret object") @@ -130,7 +110,16 @@ func (s *secretStore) Create(ctx context.Context, secret *types.Secret) error { return nil } -func (s *secretStore) Update(ctx context.Context, secret *types.Secret) (*types.Secret, error) { +func (s *secretStore) Update(ctx context.Context, secret *types.Secret) error { + const secretUpdateStmt = ` + UPDATE secrets + SET + secret_description = :secret_description, + secret_uid = :secret_uid, + secret_data = :secret_data, + secret_updated = :secret_updated, + secret_version = :secret_version + WHERE secret_id = :secret_id AND secret_version = :secret_version - 1` updatedAt := time.Now() secret.Version++ @@ -138,31 +127,26 @@ func (s *secretStore) Update(ctx context.Context, secret *types.Secret) (*types. db := dbtx.GetAccessor(ctx, s.db) - secret, err := enc(s.enc, secret) - if err != nil { - return nil, err - } - query, arg, err := db.BindNamed(secretUpdateStmt, secret) if err != nil { - return nil, database.ProcessSQLErrorf(err, "Failed to bind secret object") + return database.ProcessSQLErrorf(err, "Failed to bind secret object") } result, err := db.ExecContext(ctx, query, arg...) if err != nil { - return nil, database.ProcessSQLErrorf(err, "Failed to update secret") + return database.ProcessSQLErrorf(err, "Failed to update secret") } count, err := result.RowsAffected() if err != nil { - return nil, database.ProcessSQLErrorf(err, "Failed to get number of updated rows") + return database.ProcessSQLErrorf(err, "Failed to get number of updated rows") } if count == 0 { - return nil, gitness_store.ErrVersionConflict + return gitness_store.ErrVersionConflict } - return secret, nil + return nil } // UpdateOptLock updates the pipeline using the optimistic locking mechanism. @@ -177,7 +161,7 @@ func (s *secretStore) UpdateOptLock(ctx context.Context, return nil, err } - secret, err = s.Update(ctx, &dup) + err = s.Update(ctx, &dup) if err == nil { return &dup, nil } @@ -276,31 +260,3 @@ func (s *secretStore) Count(ctx context.Context, parentID int64, filter types.Pa } return count, nil } - -// helper function returns the same secret with encrypted data. -func enc(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error) { - if secret == nil { - return nil, fmt.Errorf("cannot encrypt a nil secret") - } - s := *secret - ciphertext, err := encrypt.Encrypt(secret.Data) - if err != nil { - return nil, err - } - s.Data = string(ciphertext) - return &s, nil -} - -// helper function returns the same secret with decrypted data. -func dec(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error) { - if secret == nil { - return nil, fmt.Errorf("cannot decrypt a nil secret") - } - s := *secret - plaintext, err := encrypt.Decrypt([]byte(secret.Data)) - if err != nil { - return nil, err - } - s.Data = plaintext - return &s, nil -} diff --git a/internal/store/database/wire.go b/internal/store/database/wire.go index bd4b64cc1..04179824a 100644 --- a/internal/store/database/wire.go +++ b/internal/store/database/wire.go @@ -105,8 +105,8 @@ func ProvidePipelineStore(db *sqlx.DB) store.PipelineStore { } // ProvideSecretStore provides a secret store. -func ProvideSecretStore(enc encrypt.Encrypter, db *sqlx.DB) store.SecretStore { - return NewSecretStore(enc, db) +func ProvideSecretStore(db *sqlx.DB) store.SecretStore { + return NewSecretStore(db) } // ProvideExecutionStore provides an execution store. diff --git a/mocks/mock_client.go b/mocks/mock_client.go index b16a6490e..3cb746055 100644 --- a/mocks/mock_client.go +++ b/mocks/mock_client.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" user "github.com/harness/gitness/internal/api/controller/user" types "github.com/harness/gitness/types" + + gomock "github.com/golang/mock/gomock" ) // MockClient is a mock of Client interface. diff --git a/mocks/mock_store.go b/mocks/mock_store.go index 0af0bbc34..9310f3729 100644 --- a/mocks/mock_store.go +++ b/mocks/mock_store.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" types "github.com/harness/gitness/types" enum "github.com/harness/gitness/types/enum" + + gomock "github.com/golang/mock/gomock" ) // MockPrincipalStore is a mock of PrincipalStore interface. From c1ebb1837a202f237315b1aa7927678511844d6c Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Thu, 10 Aug 2023 16:52:39 +0100 Subject: [PATCH 24/26] update return type of list to use pointer --- internal/api/controller/execution/list.go | 4 ++-- internal/api/controller/space/list_pipelines.go | 4 ++-- internal/api/controller/space/list_secrets.go | 4 ++-- internal/store/database.go | 6 +++--- internal/store/database/execution.go | 4 ++-- internal/store/database/pipeline.go | 4 ++-- internal/store/database/secret.go | 7 ++++--- mocks/mock_client.go | 3 +-- mocks/mock_store.go | 3 +-- 9 files changed, 19 insertions(+), 20 deletions(-) diff --git a/internal/api/controller/execution/list.go b/internal/api/controller/execution/list.go index 2d2285521..e0a6d6587 100644 --- a/internal/api/controller/execution/list.go +++ b/internal/api/controller/execution/list.go @@ -20,7 +20,7 @@ func (c *Controller) List( spaceRef string, pipelineUID string, pagination types.Pagination, -) ([]types.Execution, int64, error) { +) ([]*types.Execution, int64, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { return nil, 0, fmt.Errorf("failed to find parent space: %w", err) @@ -36,7 +36,7 @@ func (c *Controller) List( } var count int64 - var executions []types.Execution + var executions []*types.Execution err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { var dbErr error diff --git a/internal/api/controller/space/list_pipelines.go b/internal/api/controller/space/list_pipelines.go index 276106671..e80bc3586 100644 --- a/internal/api/controller/space/list_pipelines.go +++ b/internal/api/controller/space/list_pipelines.go @@ -20,7 +20,7 @@ func (c *Controller) ListPipelines( session *auth.Session, spaceRef string, pagination types.Pagination, -) ([]types.Pipeline, int64, error) { +) ([]*types.Pipeline, int64, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { return nil, 0, fmt.Errorf("failed to find parent space: %w", err) @@ -32,7 +32,7 @@ func (c *Controller) ListPipelines( } var count int64 - var pipelines []types.Pipeline + var pipelines []*types.Pipeline err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { count, err = c.pipelineStore.Count(ctx, space.ID, pagination) diff --git a/internal/api/controller/space/list_secrets.go b/internal/api/controller/space/list_secrets.go index 63db20f8e..7db2fda4f 100644 --- a/internal/api/controller/space/list_secrets.go +++ b/internal/api/controller/space/list_secrets.go @@ -20,7 +20,7 @@ func (c *Controller) ListSecrets( session *auth.Session, spaceRef string, pagination types.Pagination, -) ([]types.Secret, int64, error) { +) ([]*types.Secret, int64, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { return nil, 0, fmt.Errorf("failed to find parent space: %w", err) @@ -32,7 +32,7 @@ func (c *Controller) ListSecrets( } var count int64 - var secrets []types.Secret + var secrets []*types.Secret err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { count, err = c.secretStore.Count(ctx, space.ID, pagination) diff --git a/internal/store/database.go b/internal/store/database.go index 3d4c6bd24..28d9037d7 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -453,7 +453,7 @@ type ( Update(ctx context.Context, pipeline *types.Pipeline) error // List lists the pipelines present in a parent space ID in the datastore. - List(ctx context.Context, spaceID int64, pagination types.Pagination) ([]types.Pipeline, error) + List(ctx context.Context, spaceID int64, pagination types.Pagination) ([]*types.Pipeline, error) // UpdateOptLock updates the pipeline using the optimistic locking mechanism. UpdateOptLock(ctx context.Context, pipeline *types.Pipeline, @@ -499,7 +499,7 @@ type ( DeleteByUID(ctx context.Context, spaceID int64, uid string) error // List lists the secrets in a given space - List(ctx context.Context, spaceID int64, filter types.Pagination) ([]types.Secret, error) + List(ctx context.Context, spaceID int64, filter types.Pagination) ([]*types.Secret, error) } ExecutionStore interface { @@ -517,7 +517,7 @@ type ( mutateFn func(execution *types.Execution) error) (*types.Execution, error) // List lists the executions for a given pipeline ID - List(ctx context.Context, pipelineID int64, pagination types.Pagination) ([]types.Execution, error) + List(ctx context.Context, pipelineID int64, pagination types.Pagination) ([]*types.Execution, error) // Delete deletes an execution given a pipeline ID and an execution number Delete(ctx context.Context, pipelineID int64, num int64) error diff --git a/internal/store/database/execution.go b/internal/store/database/execution.go index 8edc51338..f78e5d6ce 100644 --- a/internal/store/database/execution.go +++ b/internal/store/database/execution.go @@ -249,7 +249,7 @@ func (s *executionStore) List( ctx context.Context, pipelineID int64, pagination types.Pagination, -) ([]types.Execution, error) { +) ([]*types.Execution, error) { stmt := database.Builder. Select(executionColumns). From("executions"). @@ -265,7 +265,7 @@ func (s *executionStore) List( db := dbtx.GetAccessor(ctx, s.db) - dst := []types.Execution{} + dst := []*types.Execution{} if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { return nil, database.ProcessSQLErrorf(err, "Failed executing custom list query") } diff --git a/internal/store/database/pipeline.go b/internal/store/database/pipeline.go index 5b83426f8..d341302d6 100644 --- a/internal/store/database/pipeline.go +++ b/internal/store/database/pipeline.go @@ -173,7 +173,7 @@ func (s *pipelineStore) List( ctx context.Context, parentID int64, pagination types.Pagination, -) ([]types.Pipeline, error) { +) ([]*types.Pipeline, error) { stmt := database.Builder. Select(pipelineColumns). From("pipelines"). @@ -193,7 +193,7 @@ func (s *pipelineStore) List( db := dbtx.GetAccessor(ctx, s.db) - dst := []types.Pipeline{} + dst := []*types.Pipeline{} if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { return nil, database.ProcessSQLErrorf(err, "Failed executing custom list query") } diff --git a/internal/store/database/secret.go b/internal/store/database/secret.go index 5761a468b..7f7e9386b 100644 --- a/internal/store/database/secret.go +++ b/internal/store/database/secret.go @@ -152,7 +152,8 @@ func (s *secretStore) Update(ctx context.Context, secret *types.Secret) error { // UpdateOptLock updates the pipeline using the optimistic locking mechanism. func (s *secretStore) UpdateOptLock(ctx context.Context, secret *types.Secret, - mutateFn func(secret *types.Secret) error) (*types.Secret, error) { + mutateFn func(secret *types.Secret) error, +) (*types.Secret, error) { for { dup := *secret @@ -177,7 +178,7 @@ func (s *secretStore) UpdateOptLock(ctx context.Context, } // List lists all the secrets present in a space. -func (s *secretStore) List(ctx context.Context, parentID int64, pagination types.Pagination) ([]types.Secret, error) { +func (s *secretStore) List(ctx context.Context, parentID int64, pagination types.Pagination) ([]*types.Secret, error) { stmt := database.Builder. Select(secretColumns). From("secrets"). @@ -197,7 +198,7 @@ func (s *secretStore) List(ctx context.Context, parentID int64, pagination types db := dbtx.GetAccessor(ctx, s.db) - dst := []types.Secret{} + dst := []*types.Secret{} if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { return nil, database.ProcessSQLErrorf(err, "Failed executing custom list query") } diff --git a/mocks/mock_client.go b/mocks/mock_client.go index 3cb746055..b16a6490e 100644 --- a/mocks/mock_client.go +++ b/mocks/mock_client.go @@ -8,10 +8,9 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" user "github.com/harness/gitness/internal/api/controller/user" types "github.com/harness/gitness/types" - - gomock "github.com/golang/mock/gomock" ) // MockClient is a mock of Client interface. diff --git a/mocks/mock_store.go b/mocks/mock_store.go index 9310f3729..0af0bbc34 100644 --- a/mocks/mock_store.go +++ b/mocks/mock_store.go @@ -8,10 +8,9 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" types "github.com/harness/gitness/types" enum "github.com/harness/gitness/types/enum" - - gomock "github.com/golang/mock/gomock" ) // MockPrincipalStore is a mock of PrincipalStore interface. From d1d50377fc5230220e467fa6c2e78c0d54ddea19 Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Fri, 11 Aug 2023 11:36:39 +0100 Subject: [PATCH 25/26] address comments --- cli/server/config.go | 6 +-- cmd/gitness/wire.go | 2 + cmd/gitness/wire_gen.go | 3 +- encrypt/aesgcm.go | 14 ++++++ encrypt/encrypt.go | 19 +------- encrypt/wire.go | 19 ++++++++ internal/api/controller/execution/list.go | 11 ++--- internal/api/controller/secret/create.go | 46 ++++++++----------- internal/api/controller/secret/update.go | 2 +- .../api/controller/space/list_pipelines.go | 14 +++--- internal/api/controller/space/list_secrets.go | 14 +++--- internal/api/handler/space/list_pipelines.go | 6 +-- internal/api/handler/space/list_secrets.go | 6 +-- internal/api/request/pipeline.go | 9 ++-- internal/api/request/secret.go | 4 +- internal/api/request/util.go | 17 ++++--- internal/router/api.go | 6 +-- internal/store/database.go | 8 ++-- internal/store/database/execution.go | 6 ++- .../database/migrate/ci/ci_migrations.sql | 16 +++---- internal/store/database/pipeline.go | 17 ++++--- internal/store/database/secret.go | 17 ++++--- internal/store/database/wire.go | 18 -------- mocks/mock_client.go | 3 +- mocks/mock_store.go | 3 +- store/database/config.go | 6 +-- types/config.go | 9 ++-- types/list_filters.go | 7 +++ types/pagination.go | 9 +--- types/secret.go | 2 +- 30 files changed, 161 insertions(+), 158 deletions(-) create mode 100644 encrypt/wire.go create mode 100644 types/list_filters.go diff --git a/cli/server/config.go b/cli/server/config.go index 414ebd2a9..b1dea701f 100644 --- a/cli/server/config.go +++ b/cli/server/config.go @@ -90,10 +90,8 @@ func getSanitizedMachineName() (string, error) { // ProvideDatabaseConfig loads the database config from the main config. func ProvideDatabaseConfig(config *types.Config) database.Config { return database.Config{ - Driver: config.Database.Driver, - Datasource: config.Database.Datasource, - Secret: config.Database.Secret, - EncryptMixedContent: config.Database.EncryptMixedContent, + Driver: config.Database.Driver, + Datasource: config.Database.Datasource, } } diff --git a/cmd/gitness/wire.go b/cmd/gitness/wire.go index f610c1a4d..ed157dbc2 100644 --- a/cmd/gitness/wire.go +++ b/cmd/gitness/wire.go @@ -11,6 +11,7 @@ import ( "context" cliserver "github.com/harness/gitness/cli/server" + "github.com/harness/gitness/encrypt" "github.com/harness/gitness/events" "github.com/harness/gitness/gitrpc" gitrpcserver "github.com/harness/gitness/gitrpc/server" @@ -82,6 +83,7 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e gitrpc.WireSet, store.WireSet, check.WireSet, + encrypt.WireSet, cliserver.ProvideEventsConfig, events.WireSet, cliserver.ProvideWebhookConfig, diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index a9d129906..4329e3130 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -10,6 +10,7 @@ import ( "context" "github.com/harness/gitness/cli/server" + "github.com/harness/gitness/encrypt" "github.com/harness/gitness/events" "github.com/harness/gitness/gitrpc" server3 "github.com/harness/gitness/gitrpc/server" @@ -94,7 +95,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro secretStore := database.ProvideSecretStore(db) spaceController := space.ProvideController(db, provider, pathUID, authorizer, pathStore, pipelineStore, secretStore, spaceStore, repoStore, principalStore, repoController, membershipStore) pipelineController := pipeline.ProvideController(db, pathUID, pathStore, repoStore, authorizer, pipelineStore, spaceStore) - encrypter, err := database.ProvideEncryptor(databaseConfig) + encrypter, err := encrypt.ProvideEncrypter(config) if err != nil { return nil, err } diff --git a/encrypt/aesgcm.go b/encrypt/aesgcm.go index a7340d462..4a01fbba2 100644 --- a/encrypt/aesgcm.go +++ b/encrypt/aesgcm.go @@ -5,6 +5,7 @@ package encrypt import ( + "crypto/aes" "crypto/cipher" "crypto/rand" "errors" @@ -68,3 +69,16 @@ func (e *Aesgcm) Decrypt(ciphertext []byte) (string, error) { } return string(plaintext), err } + +// New provides a new aesgcm encrypter +func New(key string, compat bool) (Encrypter, error) { + if len(key) != 32 { + return nil, errKeySize + } + b := []byte(key) + block, err := aes.NewCipher(b) + if err != nil { + return nil, err + } + return &Aesgcm{block: block, Compat: compat}, nil +} diff --git a/encrypt/encrypt.go b/encrypt/encrypt.go index 403c866d5..adfb42454 100644 --- a/encrypt/encrypt.go +++ b/encrypt/encrypt.go @@ -5,33 +5,16 @@ package encrypt import ( - "crypto/aes" "errors" ) // indicates key size is too small. var errKeySize = errors.New("encryption key must be 32 bytes") -// Encrypter provides database field encryption and decryption. +// Encrypter provides field encryption and decryption. // Encrypted values are currently limited to strings, which is // reflected in the interface design. type Encrypter interface { Encrypt(plaintext string) ([]byte, error) Decrypt(ciphertext []byte) (string, error) } - -// New provides a new database field encrypter. -func New(key string) (Encrypter, error) { - if key == "" { - return &none{}, nil - } - if len(key) != 32 { - return nil, errKeySize - } - b := []byte(key) - block, err := aes.NewCipher(b) - if err != nil { - return nil, err - } - return &Aesgcm{block: block}, nil -} diff --git a/encrypt/wire.go b/encrypt/wire.go new file mode 100644 index 000000000..61888a2e1 --- /dev/null +++ b/encrypt/wire.go @@ -0,0 +1,19 @@ +package encrypt + +import ( + "github.com/harness/gitness/types" + + "github.com/google/wire" +) + +// WireSet provides a wire set for this package. +var WireSet = wire.NewSet( + ProvideEncrypter, +) + +func ProvideEncrypter(config *types.Config) (Encrypter, error) { + if config.Encrypter.Secret == "" { + return &none{}, nil + } + return New(config.Encrypter.Secret, config.Encrypter.EncryptMixedContent) +} diff --git a/internal/api/controller/execution/list.go b/internal/api/controller/execution/list.go index e0a6d6587..a5ba72f6b 100644 --- a/internal/api/controller/execution/list.go +++ b/internal/api/controller/execution/list.go @@ -39,18 +39,17 @@ func (c *Controller) List( var executions []*types.Execution err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { - var dbErr error - count, dbErr = c.executionStore.Count(ctx, pipeline.ID) - if dbErr != nil { + count, err = c.executionStore.Count(ctx, pipeline.ID) + if err != nil { return fmt.Errorf("failed to count child executions: %w", err) } - executions, dbErr = c.executionStore.List(ctx, pipeline.ID, pagination) - if dbErr != nil { + executions, err = c.executionStore.List(ctx, pipeline.ID, pagination) + if err != nil { return fmt.Errorf("failed to list child executions: %w", err) } - return dbErr + return }, dbtx.TxDefaultReadOnly) if err != nil { return executions, count, fmt.Errorf("failed to fetch list: %w", err) diff --git a/internal/api/controller/secret/create.go b/internal/api/controller/secret/create.go index 63b4ee881..18b1f6288 100644 --- a/internal/api/controller/secret/create.go +++ b/internal/api/controller/secret/create.go @@ -15,7 +15,6 @@ import ( apiauth "github.com/harness/gitness/internal/api/auth" "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" @@ -50,33 +49,24 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea } var secret *types.Secret - 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() - secret = &types.Secret{ - Description: in.Description, - Data: in.Data, - SpaceID: parentSpace.ID, - UID: in.UID, - Created: now, - Updated: now, - Version: 0, - } - secret, err = enc(c.encrypter, secret) - if err != nil { - return fmt.Errorf("could not encrypt secret: %w", err) - } - err = c.secretStore.Create(ctx, secret) - if err != nil { - return fmt.Errorf("secret creation failed: %w", err) - } - return nil - }) + now := time.Now().UnixMilli() + secret = &types.Secret{ + Description: in.Description, + Data: in.Data, + SpaceID: parentSpace.ID, + UID: in.UID, + Created: now, + Updated: now, + Version: 0, + } + secret, err = enc(c.encrypter, secret) + if err != nil { + return nil, fmt.Errorf("could not encrypt secret: %w", err) + } + err = c.secretStore.Create(ctx, secret) + if err != nil { + return nil, fmt.Errorf("secret creation failed: %w", err) + } if err != nil { return nil, err } diff --git a/internal/api/controller/secret/update.go b/internal/api/controller/secret/update.go index 2ec2de78b..c3be74121 100644 --- a/internal/api/controller/secret/update.go +++ b/internal/api/controller/secret/update.go @@ -50,7 +50,7 @@ func (c *Controller) Update( if in.Data != "" { data, err := c.encrypter.Encrypt(original.Data) if err != nil { - return err + return fmt.Errorf("could not encrypt secret: %w", err) } original.Data = string(data) } diff --git a/internal/api/controller/space/list_pipelines.go b/internal/api/controller/space/list_pipelines.go index e80bc3586..87d999bb4 100644 --- a/internal/api/controller/space/list_pipelines.go +++ b/internal/api/controller/space/list_pipelines.go @@ -19,14 +19,14 @@ func (c *Controller) ListPipelines( ctx context.Context, session *auth.Session, spaceRef string, - pagination types.Pagination, + filter types.ListQueryFilter, ) ([]*types.Pipeline, int64, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { return nil, 0, fmt.Errorf("failed to find parent space: %w", err) } - err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView, true) + err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionPipelineView, false) if err != nil { return nil, 0, fmt.Errorf("could not authorize: %w", err) } @@ -35,16 +35,14 @@ func (c *Controller) ListPipelines( var pipelines []*types.Pipeline err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { - count, err = c.pipelineStore.Count(ctx, space.ID, pagination) + count, err = c.pipelineStore.Count(ctx, space.ID, filter) if err != nil { - err = fmt.Errorf("failed to count child executions: %w", err) - return + return fmt.Errorf("failed to count child executions: %w", err) } - pipelines, err = c.pipelineStore.List(ctx, space.ID, pagination) + pipelines, err = c.pipelineStore.List(ctx, space.ID, filter) if err != nil { - err = fmt.Errorf("failed to count child executions: %w", err) - return + return fmt.Errorf("failed to count child executions: %w", err) } return }, dbtx.TxDefaultReadOnly) diff --git a/internal/api/controller/space/list_secrets.go b/internal/api/controller/space/list_secrets.go index 7db2fda4f..1c091d639 100644 --- a/internal/api/controller/space/list_secrets.go +++ b/internal/api/controller/space/list_secrets.go @@ -19,14 +19,14 @@ func (c *Controller) ListSecrets( ctx context.Context, session *auth.Session, spaceRef string, - pagination types.Pagination, + filter types.ListQueryFilter, ) ([]*types.Secret, int64, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { return nil, 0, fmt.Errorf("failed to find parent space: %w", err) } - err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView, true) + err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSecretView, false) if err != nil { return nil, 0, fmt.Errorf("could not authorize: %w", err) } @@ -35,16 +35,14 @@ func (c *Controller) ListSecrets( var secrets []*types.Secret err = dbtx.New(c.db).WithTx(ctx, func(ctx context.Context) (err error) { - count, err = c.secretStore.Count(ctx, space.ID, pagination) + count, err = c.secretStore.Count(ctx, space.ID, filter) if err != nil { - err = fmt.Errorf("failed to count child executions: %w", err) - return + return fmt.Errorf("failed to count child executions: %w", err) } - secrets, err = c.secretStore.List(ctx, space.ID, pagination) + secrets, err = c.secretStore.List(ctx, space.ID, filter) if err != nil { - err = fmt.Errorf("failed to list child executions: %w", err) - return + return fmt.Errorf("failed to list child executions: %w", err) } return }, dbtx.TxDefaultReadOnly) diff --git a/internal/api/handler/space/list_pipelines.go b/internal/api/handler/space/list_pipelines.go index 764145ac4..30aba9b4b 100644 --- a/internal/api/handler/space/list_pipelines.go +++ b/internal/api/handler/space/list_pipelines.go @@ -22,14 +22,14 @@ func HandleListPipelines(spaceCtrl *space.Controller) http.HandlerFunc { return } - pagination := request.ParsePaginationFromRequest(r) - repos, totalCount, err := spaceCtrl.ListPipelines(ctx, session, spaceRef, pagination) + filter := request.ParseListQueryFilterFromRequest(r) + repos, totalCount, err := spaceCtrl.ListPipelines(ctx, session, spaceRef, filter) if err != nil { render.TranslatedUserError(w, err) return } - render.Pagination(r, w, pagination.Page, pagination.Page, int(totalCount)) + render.Pagination(r, w, filter.Page, filter.Size, int(totalCount)) render.JSON(w, http.StatusOK, repos) } } diff --git a/internal/api/handler/space/list_secrets.go b/internal/api/handler/space/list_secrets.go index ab76ee74d..40c0002aa 100644 --- a/internal/api/handler/space/list_secrets.go +++ b/internal/api/handler/space/list_secrets.go @@ -23,8 +23,8 @@ func HandleListSecrets(spaceCtrl *space.Controller) http.HandlerFunc { return } - pagination := request.ParsePaginationFromRequest(r) - ret, totalCount, err := spaceCtrl.ListSecrets(ctx, session, spaceRef, pagination) + filter := request.ParseListQueryFilterFromRequest(r) + ret, totalCount, err := spaceCtrl.ListSecrets(ctx, session, spaceRef, filter) if err != nil { render.TranslatedUserError(w, err) return @@ -36,7 +36,7 @@ func HandleListSecrets(spaceCtrl *space.Controller) http.HandlerFunc { secrets = append(secrets, *s.CopyWithoutData()) } - render.Pagination(r, w, pagination.Page, pagination.Size, int(totalCount)) + render.Pagination(r, w, filter.Page, filter.Size, int(totalCount)) render.JSON(w, http.StatusOK, secrets) } } diff --git a/internal/api/request/pipeline.go b/internal/api/request/pipeline.go index d6259ae32..8806102a3 100644 --- a/internal/api/request/pipeline.go +++ b/internal/api/request/pipeline.go @@ -10,13 +10,12 @@ import ( ) const ( - PipelinePathRef = "pipeline_ref" - PipelineUID = "pipeline_uid" - ExecutionNumber = "execution_number" + PathParamPipelineRef = "pipeline_ref" + PathParamExecutionNumber = "execution_number" ) func GetPipelineRefFromPath(r *http.Request) (string, error) { - rawRef, err := PathParamOrError(r, PipelinePathRef) + rawRef, err := PathParamOrError(r, PathParamPipelineRef) if err != nil { return "", err } @@ -26,5 +25,5 @@ func GetPipelineRefFromPath(r *http.Request) (string, error) { } func GetExecutionNumberFromPath(r *http.Request) (int64, error) { - return PathParamAsPositiveInt64(r, ExecutionNumber) + return PathParamAsPositiveInt64(r, PathParamExecutionNumber) } diff --git a/internal/api/request/secret.go b/internal/api/request/secret.go index 517ff642d..d039a6f5b 100644 --- a/internal/api/request/secret.go +++ b/internal/api/request/secret.go @@ -10,11 +10,11 @@ import ( ) const ( - SecretRef = "secret_ref" + PathParamSecretRef = "secret_ref" ) func GetSecretRefFromPath(r *http.Request) (string, error) { - rawRef, err := PathParamOrError(r, SecretRef) + rawRef, err := PathParamOrError(r, PathParamSecretRef) if err != nil { return "", err } diff --git a/internal/api/request/util.go b/internal/api/request/util.go index 05fc3c81a..cc0be0a28 100644 --- a/internal/api/request/util.go +++ b/internal/api/request/util.go @@ -205,13 +205,18 @@ func ParseSort(r *http.Request) string { return r.URL.Query().Get(QueryParamSort) } -// ParsePaginationFromRequest extracts the pagination info from the url. +// ParsePaginationFromRequest parses pagination related info from the url. func ParsePaginationFromRequest(r *http.Request) types.Pagination { return types.Pagination{ - Page: ParsePage(r), - Size: ParseLimit(r), - Query: ParseQuery(r), - Sort: ParseSort(r), - Order: ParseOrder(r), + Page: ParsePage(r), + Size: ParseLimit(r), + } +} + +// ParseListQueryFilterFromRequest parses pagination and query related info from the url. +func ParseListQueryFilterFromRequest(r *http.Request) types.ListQueryFilter { + return types.ListQueryFilter{ + Query: ParseQuery(r), + Pagination: ParsePaginationFromRequest(r), } } diff --git a/internal/router/api.go b/internal/router/api.go index da2d3c949..5fdaf09be 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -286,7 +286,7 @@ func setupPipelines(r chi.Router, pipelineCtrl *pipeline.Controller, executionCt 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.Route(fmt.Sprintf("/{%s}", request.PathParamPipelineRef), func(r chi.Router) { r.Get("/", handlerpipeline.HandleFind(pipelineCtrl)) r.Patch("/", handlerpipeline.HandleUpdate(pipelineCtrl)) r.Delete("/", handlerpipeline.HandleDelete(pipelineCtrl)) @@ -299,7 +299,7 @@ func setupSecrets(r chi.Router, secretCtrl *secret.Controller) { r.Route("/secrets", func(r chi.Router) { // Create takes path and parentId via body, not uri r.Post("/", handlersecret.HandleCreate(secretCtrl)) - r.Route(fmt.Sprintf("/{%s}", request.SecretRef), func(r chi.Router) { + r.Route(fmt.Sprintf("/{%s}", request.PathParamSecretRef), func(r chi.Router) { r.Get("/", handlersecret.HandleFind(secretCtrl)) r.Patch("/", handlersecret.HandleUpdate(secretCtrl)) r.Delete("/", handlersecret.HandleDelete(secretCtrl)) @@ -311,7 +311,7 @@ func setupExecutions(r chi.Router, pipelineCtrl *pipeline.Controller, executionC r.Route("/executions", func(r chi.Router) { r.Get("/", handlerexecution.HandleList(executionCtrl)) r.Post("/", handlerexecution.HandleCreate(executionCtrl)) - r.Route(fmt.Sprintf("/{%s}", request.ExecutionNumber), func(r chi.Router) { + r.Route(fmt.Sprintf("/{%s}", request.PathParamExecutionNumber), func(r chi.Router) { r.Get("/", handlerexecution.HandleFind(executionCtrl)) r.Patch("/", handlerexecution.HandleUpdate(executionCtrl)) r.Delete("/", handlerexecution.HandleDelete(executionCtrl)) diff --git a/internal/store/database.go b/internal/store/database.go index 28d9037d7..f716f10ab 100644 --- a/internal/store/database.go +++ b/internal/store/database.go @@ -453,7 +453,7 @@ type ( Update(ctx context.Context, pipeline *types.Pipeline) error // List lists the pipelines present in a parent space ID in the datastore. - List(ctx context.Context, spaceID int64, pagination types.Pagination) ([]*types.Pipeline, error) + List(ctx context.Context, spaceID int64, pagination types.ListQueryFilter) ([]*types.Pipeline, error) // UpdateOptLock updates the pipeline using the optimistic locking mechanism. UpdateOptLock(ctx context.Context, pipeline *types.Pipeline, @@ -463,7 +463,7 @@ type ( Delete(ctx context.Context, id int64) error // Count the number of pipelines in a space matching the given filter. - Count(ctx context.Context, spaceID int64, filter types.Pagination) (int64, error) + Count(ctx context.Context, spaceID int64, filter types.ListQueryFilter) (int64, error) // DeleteByUID deletes a pipeline with a given UID in a space DeleteByUID(ctx context.Context, spaceID int64, uid string) error @@ -483,7 +483,7 @@ type ( Create(ctx context.Context, secret *types.Secret) error // Count the number of secrets in a space matching the given filter. - Count(ctx context.Context, spaceID int64, pagination types.Pagination) (int64, error) + Count(ctx context.Context, spaceID int64, pagination types.ListQueryFilter) (int64, error) // UpdateOptLock updates the secret using the optimistic locking mechanism. UpdateOptLock(ctx context.Context, secret *types.Secret, @@ -499,7 +499,7 @@ type ( DeleteByUID(ctx context.Context, spaceID int64, uid string) error // List lists the secrets in a given space - List(ctx context.Context, spaceID int64, filter types.Pagination) ([]*types.Secret, error) + List(ctx context.Context, spaceID int64, filter types.ListQueryFilter) ([]*types.Secret, error) } ExecutionStore interface { diff --git a/internal/store/database/execution.go b/internal/store/database/execution.go index f78e5d6ce..187d33805 100644 --- a/internal/store/database/execution.go +++ b/internal/store/database/execution.go @@ -176,7 +176,7 @@ func (s *executionStore) Create(ctx context.Context, execution *types.Execution) } // Update tries to update an execution in the datastore with optimistic locking. -func (s *executionStore) Update(ctx context.Context, execution *types.Execution) error { +func (s *executionStore) Update(ctx context.Context, e *types.Execution) error { const executionUpdateStmt = ` UPDATE executions SET @@ -190,6 +190,8 @@ func (s *executionStore) Update(ctx context.Context, execution *types.Execution) WHERE execution_id = :execution_id AND execution_version = :execution_version - 1` updatedAt := time.Now() + execution := *e + execution.Version++ execution.Updated = updatedAt.UnixMilli() @@ -214,6 +216,8 @@ func (s *executionStore) Update(ctx context.Context, execution *types.Execution) return gitness_store.ErrVersionConflict } + e.Version = execution.Version + e.Updated = execution.Updated return nil } diff --git a/internal/store/database/migrate/ci/ci_migrations.sql b/internal/store/database/migrate/ci/ci_migrations.sql index 89975c98a..2b1ed484d 100644 --- a/internal/store/database/migrate/ci/ci_migrations.sql +++ b/internal/store/database/migrate/ci/ci_migrations.sql @@ -14,16 +14,16 @@ CREATE TABLE IF NOT EXISTS pipelines ( ,pipeline_version INTEGER NOT NULL -- Ensure unique combination of UID and ParentID - UNIQUE (pipeline_space_id, pipeline_uid), + ,UNIQUE (pipeline_space_id, pipeline_uid) -- Foreign key to spaces table - CONSTRAINT fk_pipeline_space_id FOREIGN KEY (pipeline_space_id) + ,CONSTRAINT fk_pipeline_space_id FOREIGN KEY (pipeline_space_id) REFERENCES spaces (space_id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE -- Foreign key to repositories table - CONSTRAINT fk_pipelines_repo_id FOREIGN KEY (pipeline_repo_id) + ,CONSTRAINT fk_pipelines_repo_id FOREIGN KEY (pipeline_repo_id) REFERENCES repositories (repo_id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE @@ -67,16 +67,16 @@ CREATE TABLE IF NOT EXISTS executions ( ,execution_version INTEGER NOT NULL -- Ensure unique combination of pipeline ID and number - UNIQUE (execution_pipeline_id, execution_number), + ,UNIQUE (execution_pipeline_id, execution_number) -- Foreign key to pipelines table - CONSTRAINT fk_executions_pipeline_id FOREIGN KEY (,execution_pipeline_id) + ,CONSTRAINT fk_executions_pipeline_id FOREIGN KEY (execution_pipeline_id) REFERENCES pipelines (pipeline_id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE -- Foreign key to repositories table - CONSTRAINT fk_executions_repo_id FOREIGN KEY (,execution_repo_id) + ,CONSTRAINT fk_executions_repo_id FOREIGN KEY (execution_repo_id) REFERENCES repositories (repo_id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE @@ -93,10 +93,10 @@ CREATE TABLE IF NOT EXISTS secrets ( ,secret_version INTEGER NOT NULL -- Ensure unique combination of space ID and UID - UNIQUE (secret_space_id, secret_uid), + ,UNIQUE (secret_space_id, secret_uid) -- Foreign key to spaces table - CONSTRAINT fk_secrets_space_id FOREIGN KEY (secret_space_id) + ,CONSTRAINT fk_secrets_space_id FOREIGN KEY (secret_space_id) REFERENCES spaces (space_id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE diff --git a/internal/store/database/pipeline.go b/internal/store/database/pipeline.go index d341302d6..9ac2aaaf7 100644 --- a/internal/store/database/pipeline.go +++ b/internal/store/database/pipeline.go @@ -127,7 +127,7 @@ func (s *pipelineStore) Create(ctx context.Context, pipeline *types.Pipeline) er } // Update updates a pipeline. -func (s *pipelineStore) Update(ctx context.Context, pipeline *types.Pipeline) error { +func (s *pipelineStore) Update(ctx context.Context, p *types.Pipeline) error { const pipelineUpdateStmt = ` UPDATE pipelines SET @@ -140,6 +140,7 @@ func (s *pipelineStore) Update(ctx context.Context, pipeline *types.Pipeline) er pipeline_version = :pipeline_version WHERE pipeline_id = :pipeline_id AND pipeline_version = :pipeline_version - 1` updatedAt := time.Now() + pipeline := *p pipeline.Version++ pipeline.Updated = updatedAt.UnixMilli() @@ -165,6 +166,8 @@ func (s *pipelineStore) Update(ctx context.Context, pipeline *types.Pipeline) er return gitness_store.ErrVersionConflict } + p.Updated = pipeline.Updated + p.Version = pipeline.Version return nil } @@ -172,19 +175,19 @@ func (s *pipelineStore) Update(ctx context.Context, pipeline *types.Pipeline) er func (s *pipelineStore) List( ctx context.Context, parentID int64, - pagination types.Pagination, + filter types.ListQueryFilter, ) ([]*types.Pipeline, error) { stmt := database.Builder. Select(pipelineColumns). From("pipelines"). Where("pipeline_space_id = ?", fmt.Sprint(parentID)) - if pagination.Query != "" { - stmt = stmt.Where("LOWER(pipeline_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(pagination.Query))) + if filter.Query != "" { + stmt = stmt.Where("LOWER(pipeline_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(filter.Query))) } - stmt = stmt.Limit(database.Limit(pagination.Size)) - stmt = stmt.Offset(database.Offset(pagination.Page, pagination.Size)) + stmt = stmt.Limit(database.Limit(filter.Size)) + stmt = stmt.Offset(database.Offset(filter.Page, filter.Size)) sql, args, err := stmt.ToSql() if err != nil { @@ -229,7 +232,7 @@ func (s *pipelineStore) UpdateOptLock(ctx context.Context, } // Count of pipelines in a space. -func (s *pipelineStore) Count(ctx context.Context, parentID int64, filter types.Pagination) (int64, error) { +func (s *pipelineStore) Count(ctx context.Context, parentID int64, filter types.ListQueryFilter) (int64, error) { stmt := database.Builder. Select("count(*)"). From("pipelines"). diff --git a/internal/store/database/secret.go b/internal/store/database/secret.go index 7f7e9386b..8ade23d57 100644 --- a/internal/store/database/secret.go +++ b/internal/store/database/secret.go @@ -110,7 +110,7 @@ func (s *secretStore) Create(ctx context.Context, secret *types.Secret) error { return nil } -func (s *secretStore) Update(ctx context.Context, secret *types.Secret) error { +func (s *secretStore) Update(ctx context.Context, p *types.Secret) error { const secretUpdateStmt = ` UPDATE secrets SET @@ -121,6 +121,7 @@ func (s *secretStore) Update(ctx context.Context, secret *types.Secret) error { secret_version = :secret_version WHERE secret_id = :secret_id AND secret_version = :secret_version - 1` updatedAt := time.Now() + secret := *p secret.Version++ secret.Updated = updatedAt.UnixMilli() @@ -146,6 +147,8 @@ func (s *secretStore) Update(ctx context.Context, secret *types.Secret) error { return gitness_store.ErrVersionConflict } + p.Version = secret.Version + p.Updated = secret.Updated return nil } @@ -178,18 +181,18 @@ func (s *secretStore) UpdateOptLock(ctx context.Context, } // List lists all the secrets present in a space. -func (s *secretStore) List(ctx context.Context, parentID int64, pagination types.Pagination) ([]*types.Secret, error) { +func (s *secretStore) List(ctx context.Context, parentID int64, filter types.ListQueryFilter) ([]*types.Secret, error) { stmt := database.Builder. Select(secretColumns). From("secrets"). Where("secret_space_id = ?", fmt.Sprint(parentID)) - if pagination.Query != "" { - stmt = stmt.Where("LOWER(secret_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(pagination.Query))) + if filter.Query != "" { + stmt = stmt.Where("LOWER(secret_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(filter.Query))) } - stmt = stmt.Limit(database.Limit(pagination.Size)) - stmt = stmt.Offset(database.Offset(pagination.Page, pagination.Size)) + stmt = stmt.Limit(database.Limit(filter.Size)) + stmt = stmt.Offset(database.Offset(filter.Page, filter.Size)) sql, args, err := stmt.ToSql() if err != nil { @@ -237,7 +240,7 @@ func (s *secretStore) DeleteByUID(ctx context.Context, spaceID int64, uid string } // Count of secrets in a space. -func (s *secretStore) Count(ctx context.Context, parentID int64, filter types.Pagination) (int64, error) { +func (s *secretStore) Count(ctx context.Context, parentID int64, filter types.ListQueryFilter) (int64, error) { stmt := database.Builder. Select("count(*)"). From("secrets"). diff --git a/internal/store/database/wire.go b/internal/store/database/wire.go index 04179824a..d61f7879c 100644 --- a/internal/store/database/wire.go +++ b/internal/store/database/wire.go @@ -7,7 +7,6 @@ package database import ( "context" - "github.com/harness/gitness/encrypt" "github.com/harness/gitness/internal/store" "github.com/harness/gitness/internal/store/database/migrate" "github.com/harness/gitness/store/database" @@ -27,7 +26,6 @@ var WireSet = wire.NewSet( ProvideExecutionStore, ProvidePipelineStore, ProvideSecretStore, - ProvideEncryptor, ProvideRepoGitInfoView, ProvideMembershipStore, ProvideTokenStore, @@ -63,22 +61,6 @@ func ProvidePrincipalStore(db *sqlx.DB, uidTransformation store.PrincipalUIDTran return NewPrincipalStore(db, uidTransformation) } -// ProvideEncryptor provides an encryptor implementation. -func ProvideEncryptor(config database.Config) (encrypt.Encrypter, error) { - enc, err := encrypt.New(config.Secret) - // mixed-content mode should be set to true if the database - // originally had encryption disabled and therefore has - // plaintext entries. This prevents gitness from returning an - // error if decryption fails; on failure, the ciphertext is - // returned as-is and the error is ignored. - if aesgcm, ok := enc.(*encrypt.Aesgcm); ok { - if config.EncryptMixedContent { - aesgcm.Compat = true - } - } - return enc, err -} - // ProvidePrincipalInfoView provides a principal info store. func ProvidePrincipalInfoView(db *sqlx.DB) store.PrincipalInfoView { return NewPrincipalInfoView(db) diff --git a/mocks/mock_client.go b/mocks/mock_client.go index b16a6490e..3cb746055 100644 --- a/mocks/mock_client.go +++ b/mocks/mock_client.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" user "github.com/harness/gitness/internal/api/controller/user" types "github.com/harness/gitness/types" + + gomock "github.com/golang/mock/gomock" ) // MockClient is a mock of Client interface. diff --git a/mocks/mock_store.go b/mocks/mock_store.go index 0af0bbc34..9310f3729 100644 --- a/mocks/mock_store.go +++ b/mocks/mock_store.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" types "github.com/harness/gitness/types" enum "github.com/harness/gitness/types/enum" + + gomock "github.com/golang/mock/gomock" ) // MockPrincipalStore is a mock of PrincipalStore interface. diff --git a/store/database/config.go b/store/database/config.go index 1b180c787..399e8494d 100644 --- a/store/database/config.go +++ b/store/database/config.go @@ -6,8 +6,6 @@ package database // Config specifies the config for the database package. type Config struct { - Driver string - Datasource string - Secret string - EncryptMixedContent bool + Driver string + Datasource string } diff --git a/types/config.go b/types/config.go index 9f4c10ec0..0af8a1d43 100644 --- a/types/config.go +++ b/types/config.go @@ -52,6 +52,12 @@ type Config struct { DefaultBranch string `envconfig:"GITNESS_GIT_DEFAULTBRANCH" default:"main"` } + // Encrypter defines the parameters for the encrypter + Encrypter struct { + Secret string `envconfig:"GITNESS_ENCRYPTER_SECRET"` // key used for encryption + EncryptMixedContent bool `envconfig:"GITNESS_ENCRYPT_MIXED_CONTENT"` + } + // Server defines the server configuration parameters. Server struct { // HTTP defines the http configuration parameters @@ -75,9 +81,6 @@ type Config struct { Database struct { Driver string `envconfig:"GITNESS_DATABASE_DRIVER" default:"sqlite3"` Datasource string `envconfig:"GITNESS_DATABASE_DATASOURCE" default:"database.sqlite3"` - // secret used for encryption/decryption in the DB - Secret string `envconfig:"GITNESS_DATABASE_SECRET"` - EncryptMixedContent bool `envconfig:"GITNESS_ENCRYPT_MIXED_CONTENT"` } // Token defines token configuration parameters. diff --git a/types/list_filters.go b/types/list_filters.go new file mode 100644 index 000000000..e7493bbe8 --- /dev/null +++ b/types/list_filters.go @@ -0,0 +1,7 @@ +package types + +// ListQueryFilter has pagination related info and a query param +type ListQueryFilter struct { + Pagination + Query string `json:"query"` +} diff --git a/types/pagination.go b/types/pagination.go index 28bd01971..a00c13333 100644 --- a/types/pagination.go +++ b/types/pagination.go @@ -1,12 +1,7 @@ package types -import "github.com/harness/gitness/types/enum" - // Pagination stores pagination related params type Pagination struct { - Page int `json:"page"` - Size int `json:"size"` - Query string `json:"query"` - Sort string `json:"sort"` - Order enum.Order `json:"order"` + Page int `json:"page"` + Size int `json:"size"` } diff --git a/types/secret.go b/types/secret.go index f4bfa5b88..547a5fc69 100644 --- a/types/secret.go +++ b/types/secret.go @@ -9,7 +9,7 @@ type Secret struct { Description string `db:"secret_description" json:"description"` SpaceID int64 `db:"secret_space_id" json:"space_id"` UID string `db:"secret_uid" json:"uid"` - Data string `db:"secret_data" json:"data"` + Data string `db:"secret_data" json:"-"` Created int64 `db:"secret_created" json:"created"` Updated int64 `db:"secret_updated" json:"updated"` Version int64 `db:"secret_version" json:"version"` From 430605a13e21a443d928cafa36825fef7d3d086b Mon Sep 17 00:00:00 2001 From: Vistaar Juneja Date: Fri, 11 Aug 2023 17:48:07 +0100 Subject: [PATCH 26/26] update env value --- types/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/config.go b/types/config.go index 0af8a1d43..95f22e094 100644 --- a/types/config.go +++ b/types/config.go @@ -54,8 +54,8 @@ type Config struct { // Encrypter defines the parameters for the encrypter Encrypter struct { - Secret string `envconfig:"GITNESS_ENCRYPTER_SECRET"` // key used for encryption - EncryptMixedContent bool `envconfig:"GITNESS_ENCRYPT_MIXED_CONTENT"` + Secret string `envconfig:"GITNESS_ENCRYPTER_SECRET"` // key used for encryption + MixedContent bool `envconfig:"GITNESS_ENCRYPTER_MIXED_CONTENT"` } // Server defines the server configuration parameters.