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
This commit is contained in:
Darko Draskovic 2024-12-03 15:31:56 +00:00 committed by Harness
parent ab9d78dc7b
commit 18da27f968
10 changed files with 229 additions and 23 deletions

View File

@ -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
}

View File

@ -33,6 +33,7 @@ type Controller struct {
tx dbtx.Transactor tx dbtx.Transactor
authorizer authz.Authorizer authorizer authz.Authorizer
repoStore store.RepoStore repoStore store.RepoStore
spaceStore store.SpaceStore
checkStore store.CheckStore checkStore store.CheckStore
git git.Interface git git.Interface
sanitizers map[enum.CheckPayloadKind]func(in *ReportInput, s *auth.Session) error sanitizers map[enum.CheckPayloadKind]func(in *ReportInput, s *auth.Session) error
@ -42,6 +43,7 @@ func NewController(
tx dbtx.Transactor, tx dbtx.Transactor,
authorizer authz.Authorizer, authorizer authz.Authorizer,
repoStore store.RepoStore, repoStore store.RepoStore,
spaceStore store.SpaceStore,
checkStore store.CheckStore, checkStore store.CheckStore,
git git.Interface, git git.Interface,
sanitizers map[enum.CheckPayloadKind]func(in *ReportInput, s *auth.Session) error, sanitizers map[enum.CheckPayloadKind]func(in *ReportInput, s *auth.Session) error,
@ -50,6 +52,7 @@ func NewController(
tx: tx, tx: tx,
authorizer: authorizer, authorizer: authorizer,
repoStore: repoStore, repoStore: repoStore,
spaceStore: spaceStore,
checkStore: checkStore, checkStore: checkStore,
git: git, git: git,
sanitizers: sanitizers, sanitizers: sanitizers,
@ -74,3 +77,24 @@ func (c *Controller) getRepoCheckAccess(ctx context.Context,
return repo, nil 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
}

View File

@ -35,6 +35,7 @@ func ProvideController(
tx dbtx.Transactor, tx dbtx.Transactor,
authorizer authz.Authorizer, authorizer authz.Authorizer,
repoStore store.RepoStore, repoStore store.RepoStore,
spaceStore store.SpaceStore,
checkStore store.CheckStore, checkStore store.CheckStore,
rpcClient git.Interface, rpcClient git.Interface,
sanitizers map[enum.CheckPayloadKind]func(in *ReportInput, s *auth.Session) error, sanitizers map[enum.CheckPayloadKind]func(in *ReportInput, s *auth.Session) error,
@ -43,6 +44,7 @@ func ProvideController(
tx, tx,
authorizer, authorizer,
repoStore, repoStore,
spaceStore,
checkStore, checkStore,
rpcClient, rpcClient,
sanitizers, sanitizers,

View File

@ -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)
}
}

View File

@ -222,7 +222,7 @@ func setupRoutesV1WithAuth(r chi.Router,
capabilitiesCtrl *capabilities.Controller, capabilitiesCtrl *capabilities.Controller,
) { ) {
setupAccountWithAuth(r, userCtrl, config) setupAccountWithAuth(r, userCtrl, config)
setupSpaces(r, appCtx, spaceCtrl, userGroupCtrl, webhookCtrl) setupSpaces(r, appCtx, spaceCtrl, userGroupCtrl, webhookCtrl, checkCtrl)
setupRepos(r, repoCtrl, repoSettingsCtrl, pipelineCtrl, executionCtrl, triggerCtrl, setupRepos(r, repoCtrl, repoSettingsCtrl, pipelineCtrl, executionCtrl, triggerCtrl,
logCtrl, pullreqCtrl, webhookCtrl, checkCtrl, uploadCtrl) logCtrl, pullreqCtrl, webhookCtrl, checkCtrl, uploadCtrl)
setupConnectors(r, connectorCtrl) setupConnectors(r, connectorCtrl)
@ -248,7 +248,7 @@ func setupSpaces(
spaceCtrl *space.Controller, spaceCtrl *space.Controller,
userGroupCtrl *usergroup.Controller, userGroupCtrl *usergroup.Controller,
webhookCtrl *webhook.Controller, webhookCtrl *webhook.Controller,
checkCtrl *check.Controller,
) { ) {
r.Route("/spaces", func(r chi.Router) { r.Route("/spaces", func(r chi.Router) {
// Create takes path and parentId via body, not uri // Create takes path and parentId via body, not uri
@ -294,6 +294,8 @@ func setupSpaces(
SetupSpaceLabels(r, spaceCtrl) SetupSpaceLabels(r, spaceCtrl)
SetupWebhookSpace(r, webhookCtrl) SetupWebhookSpace(r, webhookCtrl)
SetupRulesSpace(r, spaceCtrl) SetupRulesSpace(r, spaceCtrl)
r.Get("/checks", handlercheck.HandleCheckListRecentSpace(checkCtrl))
}) })
}) })
} }

View File

@ -196,6 +196,9 @@ type (
// GetDescendantsData returns a list of space parent data for spaces that are descendants of the space. // 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) 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 creates a new space
Create(ctx context.Context, space *types.Space) error 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) 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 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 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) ListResults(ctx context.Context, repoID int64, commitSHA string) ([]types.CheckResult, error)

View File

@ -248,16 +248,42 @@ func (s *CheckStore) List(ctx context.Context,
} }
// ListRecent returns a list of recently executed status checks in a repository. // 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, repoID int64,
opts types.CheckRecentOptions, opts types.CheckRecentOptions,
) ([]string, error) { ) ([]string, error) {
stmt := database.Builder. stmt := database.Builder.
Select("distinct check_uid"). Select("distinct check_uid").
From("checks"). 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 = s.applyOpts(stmt, opts.Query)
stmt = stmt.OrderBy("check_uid") 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) 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) db := dbtx.GetAccessor(ctx, s.db)
dst := make([]string, 0)
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to execute list recent status checks query") return nil, database.ProcessSQLErrorf(ctx, err, "Failed to execute list recent status checks query")
} }

View File

@ -562,15 +562,14 @@ func (s *RepoStore) countAll(
parentID int64, parentID int64,
filter *types.RepoFilter, filter *types.RepoFilter,
) (int64, error) { ) (int64, error) {
query := spaceDescendantsQuery + `
SELECT space_descendant_id
FROM space_descendants`
db := dbtx.GetAccessor(ctx, s.db) db := dbtx.GetAccessor(ctx, s.db)
var spaceIDs []int64 spaceIDs, err := getSpaceDescendantsIDs(ctx, db, parentID)
if err := db.SelectContext(ctx, &spaceIDs, query, parentID); err != nil { if err != nil {
return 0, database.ProcessSQLErrorf(ctx, err, "failed to retrieve spaces") return 0, fmt.Errorf(
"failed to get space descendants ids for %d: %w",
parentID, err,
)
} }
stmt := database.Builder. stmt := database.Builder.
@ -691,15 +690,14 @@ func (s *RepoStore) listAll(
parentID int64, parentID int64,
filter *types.RepoFilter, filter *types.RepoFilter,
) ([]*types.Repository, error) { ) ([]*types.Repository, error) {
query := spaceDescendantsQuery + `
SELECT space_descendant_id
FROM space_descendants`
db := dbtx.GetAccessor(ctx, s.db) db := dbtx.GetAccessor(ctx, s.db)
var spaceIDs []int64 spaceIDs, err := getSpaceDescendantsIDs(ctx, db, parentID)
if err := db.SelectContext(ctx, &spaceIDs, query, parentID); err != nil { if err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "failed to retrieve spaces") return nil, fmt.Errorf(
"failed to get space descendants ids for %d: %w",
parentID, err,
)
} }
stmt := database.Builder. stmt := database.Builder.

View File

@ -336,6 +336,24 @@ func (s *SpaceStore) GetDescendantsData(ctx context.Context, spaceID int64) ([]t
return s.readParentsData(ctx, query, spaceID) 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( func (s *SpaceStore) readParentsData(
ctx context.Context, ctx context.Context,
query string, query string,

View File

@ -406,7 +406,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
principalController := principal.ProvideController(principalStore, authorizer) principalController := principal.ProvideController(principalStore, authorizer)
usergroupController := usergroup2.ProvideController(userGroupStore, spaceStore, authorizer, searchService) usergroupController := usergroup2.ProvideController(userGroupStore, spaceStore, authorizer, searchService)
v := check2.ProvideCheckSanitizers() 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) systemController := system.NewController(principalStore, config)
blobConfig, err := server.ProvideBlobStoreConfig(config) blobConfig, err := server.ProvideBlobStoreConfig(config)
if err != nil { if err != nil {