From 18da27f968e1aee8fbfe734c65f7694025bc676f Mon Sep 17 00:00:00 2001 From: Darko Draskovic Date: Tue, 3 Dec 2024 15:31:56 +0000 Subject: [PATCH] feat: [CODE-2857]: Add checks API to spaces (#3084) * Fix stmt reassignment * Add get descendent ids helper to space store * Add checks API to spaces --- .../controller/check/check_recent_space.go | 63 +++++++++++++++++++ app/api/controller/check/controller.go | 24 +++++++ app/api/controller/check/wire.go | 2 + app/api/handler/check/check_recent_space.go | 59 +++++++++++++++++ app/router/api.go | 6 +- app/store/database.go | 17 ++++- app/store/database/check.go | 35 +++++++++-- app/store/database/repo.go | 26 ++++---- app/store/database/space.go | 18 ++++++ cmd/gitness/wire_gen.go | 2 +- 10 files changed, 229 insertions(+), 23 deletions(-) create mode 100644 app/api/controller/check/check_recent_space.go create mode 100644 app/api/handler/check/check_recent_space.go diff --git a/app/api/controller/check/check_recent_space.go b/app/api/controller/check/check_recent_space.go new file mode 100644 index 000000000..e63d32b3f --- /dev/null +++ b/app/api/controller/check/check_recent_space.go @@ -0,0 +1,63 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package check + +import ( + "context" + "fmt" + "time" + + "github.com/harness/gitness/app/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// ListRecentChecksSpace return an array of status check UIDs that have been run recently. +func (c *Controller) ListRecentChecksSpace( + ctx context.Context, + session *auth.Session, + spaceRef string, + recursive bool, + opts types.CheckRecentOptions, +) ([]string, error) { + space, err := c.getSpaceCheckAccess(ctx, session, spaceRef, enum.PermissionSpaceEdit) + if err != nil { + return nil, fmt.Errorf("failed to acquire access to space: %w", err) + } + + if opts.Since == 0 { + opts.Since = time.Now().Add(-30 * 24 * time.Hour).UnixMilli() + } + + var spaceIDs []int64 + if recursive { + spaceIDs, err = c.spaceStore.GetDescendantsIDs(ctx, space.ID) + if err != nil { + return nil, fmt.Errorf("failed to get space descendants ids: %w", err) + } + } else { + spaceIDs = append(spaceIDs, space.ID) + } + + checkIdentifiers, err := c.checkStore.ListRecentSpace(ctx, spaceIDs, opts) + if err != nil { + return nil, fmt.Errorf( + "failed to list status check results for space=%s: %w", + space.Identifier, err, + ) + } + + return checkIdentifiers, nil +} diff --git a/app/api/controller/check/controller.go b/app/api/controller/check/controller.go index f13887b79..cb71affa2 100644 --- a/app/api/controller/check/controller.go +++ b/app/api/controller/check/controller.go @@ -33,6 +33,7 @@ type Controller struct { tx dbtx.Transactor authorizer authz.Authorizer repoStore store.RepoStore + spaceStore store.SpaceStore checkStore store.CheckStore git git.Interface sanitizers map[enum.CheckPayloadKind]func(in *ReportInput, s *auth.Session) error @@ -42,6 +43,7 @@ func NewController( tx dbtx.Transactor, authorizer authz.Authorizer, repoStore store.RepoStore, + spaceStore store.SpaceStore, checkStore store.CheckStore, git git.Interface, sanitizers map[enum.CheckPayloadKind]func(in *ReportInput, s *auth.Session) error, @@ -50,6 +52,7 @@ func NewController( tx: tx, authorizer: authorizer, repoStore: repoStore, + spaceStore: spaceStore, checkStore: checkStore, git: git, sanitizers: sanitizers, @@ -74,3 +77,24 @@ func (c *Controller) getRepoCheckAccess(ctx context.Context, return repo, nil } + +func (c *Controller) getSpaceCheckAccess( + ctx context.Context, + session *auth.Session, + spaceRef string, + permission enum.Permission, +) (*types.Space, error) { + space, err := c.spaceStore.FindByRef(ctx, spaceRef) + if err != nil { + return nil, fmt.Errorf("parent space not found: %w", err) + } + + scope := &types.Scope{SpacePath: space.Path} + resource := &types.Resource{Type: enum.ResourceTypeRepo} + err = apiauth.Check(ctx, c.authorizer, session, scope, resource, permission) + if err != nil { + return nil, fmt.Errorf("auth check failed: %w", err) + } + + return space, nil +} diff --git a/app/api/controller/check/wire.go b/app/api/controller/check/wire.go index f8e570a0f..7abe6f153 100644 --- a/app/api/controller/check/wire.go +++ b/app/api/controller/check/wire.go @@ -35,6 +35,7 @@ func ProvideController( tx dbtx.Transactor, authorizer authz.Authorizer, repoStore store.RepoStore, + spaceStore store.SpaceStore, checkStore store.CheckStore, rpcClient git.Interface, sanitizers map[enum.CheckPayloadKind]func(in *ReportInput, s *auth.Session) error, @@ -43,6 +44,7 @@ func ProvideController( tx, authorizer, repoStore, + spaceStore, checkStore, rpcClient, sanitizers, diff --git a/app/api/handler/check/check_recent_space.go b/app/api/handler/check/check_recent_space.go new file mode 100644 index 000000000..e7591b175 --- /dev/null +++ b/app/api/handler/check/check_recent_space.go @@ -0,0 +1,59 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pullreq + +import ( + "net/http" + + "github.com/harness/gitness/app/api/controller/check" + "github.com/harness/gitness/app/api/render" + "github.com/harness/gitness/app/api/request" +) + +// HandleCheckListRecentSpace is an HTTP handler for listing recently executed status checks for a space. +func HandleCheckListRecentSpace(checkCtrl *check.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(ctx, w, err) + return + } + + opts, err := request.ParseCheckRecentOptions(r) + if err != nil { + render.TranslatedUserError(ctx, w, err) + return + } + + recursive, err := request.ParseRecursiveFromQuery(r) + if err != nil { + render.TranslatedUserError(ctx, w, err) + return + } + + checkIdentifiers, err := checkCtrl.ListRecentChecksSpace( + ctx, session, spaceRef, recursive, opts, + ) + if err != nil { + render.TranslatedUserError(ctx, w, err) + return + } + + render.JSON(w, http.StatusOK, checkIdentifiers) + } +} diff --git a/app/router/api.go b/app/router/api.go index b8751de35..42c384578 100644 --- a/app/router/api.go +++ b/app/router/api.go @@ -222,7 +222,7 @@ func setupRoutesV1WithAuth(r chi.Router, capabilitiesCtrl *capabilities.Controller, ) { setupAccountWithAuth(r, userCtrl, config) - setupSpaces(r, appCtx, spaceCtrl, userGroupCtrl, webhookCtrl) + setupSpaces(r, appCtx, spaceCtrl, userGroupCtrl, webhookCtrl, checkCtrl) setupRepos(r, repoCtrl, repoSettingsCtrl, pipelineCtrl, executionCtrl, triggerCtrl, logCtrl, pullreqCtrl, webhookCtrl, checkCtrl, uploadCtrl) setupConnectors(r, connectorCtrl) @@ -248,7 +248,7 @@ func setupSpaces( spaceCtrl *space.Controller, userGroupCtrl *usergroup.Controller, webhookCtrl *webhook.Controller, - + checkCtrl *check.Controller, ) { r.Route("/spaces", func(r chi.Router) { // Create takes path and parentId via body, not uri @@ -294,6 +294,8 @@ func setupSpaces( SetupSpaceLabels(r, spaceCtrl) SetupWebhookSpace(r, webhookCtrl) SetupRulesSpace(r, spaceCtrl) + + r.Get("/checks", handlercheck.HandleCheckListRecentSpace(checkCtrl)) }) }) } diff --git a/app/store/database.go b/app/store/database.go index 7aa686d5e..a6e35afed 100644 --- a/app/store/database.go +++ b/app/store/database.go @@ -196,6 +196,9 @@ type ( // GetDescendantsData returns a list of space parent data for spaces that are descendants of the space. GetDescendantsData(ctx context.Context, spaceID int64) ([]types.SpaceParentData, error) + // GetDescendantsIDs returns a list of space ids for spaces that are descendants of the specified space. + GetDescendantsIDs(ctx context.Context, spaceID int64) ([]int64, error) + // Create creates a new space Create(ctx context.Context, space *types.Space) error @@ -646,7 +649,19 @@ type ( List(ctx context.Context, repoID int64, commitSHA string, opts types.CheckListOptions) ([]types.Check, error) // ListRecent returns a list of recently executed status checks in a repository. - ListRecent(ctx context.Context, repoID int64, opts types.CheckRecentOptions) ([]string, error) + ListRecent( + ctx context.Context, + repoID int64, + opts types.CheckRecentOptions, + ) ([]string, error) + + // ListRecentSpace returns a list of recently executed status checks in + // repositories in spaces with specified space IDs. + ListRecentSpace( + ctx context.Context, + spaceIDs []int64, + opts types.CheckRecentOptions, + ) ([]string, error) // ListResults returns a list of status check results for a specific commit in a repo. ListResults(ctx context.Context, repoID int64, commitSHA string) ([]types.CheckResult, error) diff --git a/app/store/database/check.go b/app/store/database/check.go index f4f20bb1f..df1efa8ee 100644 --- a/app/store/database/check.go +++ b/app/store/database/check.go @@ -248,16 +248,42 @@ func (s *CheckStore) List(ctx context.Context, } // ListRecent returns a list of recently executed status checks in a repository. -func (s *CheckStore) ListRecent(ctx context.Context, +func (s *CheckStore) ListRecent( + ctx context.Context, repoID int64, opts types.CheckRecentOptions, ) ([]string, error) { stmt := database.Builder. Select("distinct check_uid"). From("checks"). - Where("check_repo_id = ?", repoID). - Where("check_created > ?", opts.Since) + Where("check_created > ?", opts.Since). + Where("check_repo_id = ?", repoID) + return s.listRecent(ctx, stmt, opts) +} + +// ListRecentSpace returns a list of recently executed status checks in +// repositories in spaces with specified space IDs. +func (s *CheckStore) ListRecentSpace( + ctx context.Context, + spaceIDs []int64, + opts types.CheckRecentOptions, +) ([]string, error) { + stmt := database.Builder. + Select("distinct check_uid"). + From("checks"). + Join("repositories ON checks.check_repo_id = repositories.repo_id"). + Where("check_created > ?", opts.Since). + Where(squirrel.Eq{"repositories.repo_parent_id": spaceIDs}) + + return s.listRecent(ctx, stmt, opts) +} + +func (s *CheckStore) listRecent( + ctx context.Context, + stmt squirrel.SelectBuilder, + opts types.CheckRecentOptions, +) ([]string, error) { stmt = s.applyOpts(stmt, opts.Query) stmt = stmt.OrderBy("check_uid") @@ -267,10 +293,9 @@ func (s *CheckStore) ListRecent(ctx context.Context, return nil, fmt.Errorf("failed to convert list recent status checks query to sql: %w", err) } - dst := make([]string, 0) - db := dbtx.GetAccessor(ctx, s.db) + dst := make([]string, 0) if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { return nil, database.ProcessSQLErrorf(ctx, err, "Failed to execute list recent status checks query") } diff --git a/app/store/database/repo.go b/app/store/database/repo.go index 2276c471f..d985a0bc5 100644 --- a/app/store/database/repo.go +++ b/app/store/database/repo.go @@ -562,15 +562,14 @@ func (s *RepoStore) countAll( parentID int64, filter *types.RepoFilter, ) (int64, error) { - query := spaceDescendantsQuery + ` - SELECT space_descendant_id - FROM space_descendants` - db := dbtx.GetAccessor(ctx, s.db) - var spaceIDs []int64 - if err := db.SelectContext(ctx, &spaceIDs, query, parentID); err != nil { - return 0, database.ProcessSQLErrorf(ctx, err, "failed to retrieve spaces") + spaceIDs, err := getSpaceDescendantsIDs(ctx, db, parentID) + if err != nil { + return 0, fmt.Errorf( + "failed to get space descendants ids for %d: %w", + parentID, err, + ) } stmt := database.Builder. @@ -691,15 +690,14 @@ func (s *RepoStore) listAll( parentID int64, filter *types.RepoFilter, ) ([]*types.Repository, error) { - query := spaceDescendantsQuery + ` - SELECT space_descendant_id - FROM space_descendants` - db := dbtx.GetAccessor(ctx, s.db) - var spaceIDs []int64 - if err := db.SelectContext(ctx, &spaceIDs, query, parentID); err != nil { - return nil, database.ProcessSQLErrorf(ctx, err, "failed to retrieve spaces") + spaceIDs, err := getSpaceDescendantsIDs(ctx, db, parentID) + if err != nil { + return nil, fmt.Errorf( + "failed to get space descendants ids for %d: %w", + parentID, err, + ) } stmt := database.Builder. diff --git a/app/store/database/space.go b/app/store/database/space.go index 101b867b0..7f1e6b53a 100644 --- a/app/store/database/space.go +++ b/app/store/database/space.go @@ -336,6 +336,24 @@ func (s *SpaceStore) GetDescendantsData(ctx context.Context, spaceID int64) ([]t return s.readParentsData(ctx, query, spaceID) } +// GetDescendantsIDs returns a list of space ids for spaces that are descendants of the specified space. +func (s *SpaceStore) GetDescendantsIDs(ctx context.Context, spaceID int64) ([]int64, error) { + return getSpaceDescendantsIDs(ctx, dbtx.GetAccessor(ctx, s.db), spaceID) +} + +func getSpaceDescendantsIDs(ctx context.Context, db dbtx.Accessor, spaceID int64) ([]int64, error) { + query := spaceDescendantsQuery + ` + SELECT space_descendant_id + FROM space_descendants` + + var ids []int64 + if err := db.SelectContext(ctx, &ids, query, spaceID); err != nil { + return nil, database.ProcessSQLErrorf(ctx, err, "failed to retrieve spaces") + } + + return ids, nil +} + func (s *SpaceStore) readParentsData( ctx context.Context, query string, diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index bbea0f18d..3fcdf1db3 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -406,7 +406,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro principalController := principal.ProvideController(principalStore, authorizer) usergroupController := usergroup2.ProvideController(userGroupStore, spaceStore, authorizer, searchService) v := check2.ProvideCheckSanitizers() - checkController := check2.ProvideController(transactor, authorizer, repoStore, checkStore, gitInterface, v) + checkController := check2.ProvideController(transactor, authorizer, repoStore, spaceStore, checkStore, gitInterface, v) systemController := system.NewController(principalStore, config) blobConfig, err := server.ProvideBlobStoreConfig(config) if err != nil {