drone/internal/store/database/check.go
2023-07-31 13:23:34 +02:00

291 lines
7.4 KiB
Go

// 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"
"encoding/json"
"fmt"
"time"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/store/database"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)
var _ store.CheckStore = (*CheckStore)(nil)
// NewCheckStore returns a new CheckStore.
func NewCheckStore(
db *sqlx.DB,
pCache store.PrincipalInfoCache,
) *CheckStore {
return &CheckStore{
db: db,
pCache: pCache,
}
}
// CheckStore implements store.CheckStore backed by a relational database.
type CheckStore struct {
db *sqlx.DB
pCache store.PrincipalInfoCache
}
const (
checkColumns = `
check_id
,check_created_by
,check_created
,check_updated
,check_repo_id
,check_commit_sha
,check_uid
,check_status
,check_summary
,check_link
,check_payload
,check_metadata
,check_payload_kind
,check_payload_version`
)
type check struct {
ID int64 `db:"check_id"`
CreatedBy int64 `db:"check_created_by"`
Created int64 `db:"check_created"`
Updated int64 `db:"check_updated"`
RepoID int64 `db:"check_repo_id"`
CommitSHA string `db:"check_commit_sha"`
UID string `db:"check_uid"`
Status enum.CheckStatus `db:"check_status"`
Summary string `db:"check_summary"`
Link string `db:"check_link"`
Payload json.RawMessage `db:"check_payload"`
Metadata json.RawMessage `db:"check_metadata"`
PayloadKind enum.CheckPayloadKind `db:"check_payload_kind"`
PayloadVersion string `db:"check_payload_version"`
}
// Upsert creates new or updates an existing status check result.
func (s *CheckStore) Upsert(ctx context.Context, check *types.Check) error {
const sqlQuery = `
INSERT INTO checks (
check_created_by
,check_created
,check_updated
,check_repo_id
,check_commit_sha
,check_uid
,check_status
,check_summary
,check_link
,check_payload
,check_metadata
,check_payload_kind
,check_payload_version
) VALUES (
:check_created_by
,:check_created
,:check_updated
,:check_repo_id
,:check_commit_sha
,:check_uid
,:check_status
,:check_summary
,:check_link
,:check_payload
,:check_metadata
,:check_payload_kind
,:check_payload_version
)
ON CONFLICT (check_repo_id, check_commit_sha, check_uid) DO
UPDATE SET
check_updated = :check_updated
,check_status = :check_status
,check_summary = :check_summary
,check_link = :check_link
,check_payload = :check_payload
,check_metadata = :check_metadata
,check_payload_kind = :check_payload_kind
,check_payload_version = :check_payload_version
RETURNING check_id, check_created_by, check_created`
db := dbtx.GetAccessor(ctx, s.db)
query, arg, err := db.BindNamed(sqlQuery, mapInternalCheck(check))
if err != nil {
return database.ProcessSQLErrorf(err, "Failed to bind status check object")
}
if err = db.QueryRowContext(ctx, query, arg...).Scan(&check.ID, &check.CreatedBy, &check.Created); err != nil {
return database.ProcessSQLErrorf(err, "Upsert query failed")
}
return nil
}
// Count counts status check results for a specific commit in a repo.
func (s *CheckStore) Count(ctx context.Context,
repoID int64,
commitSHA string,
_ types.CheckListOptions,
) (int, error) {
stmt := database.Builder.
Select("count(*)").
From("checks").
Where("check_repo_id = ?", repoID).
Where("check_commit_sha = ?", commitSHA)
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 int
err = db.QueryRowContext(ctx, sql, args...).Scan(&count)
if err != nil {
return 0, database.ProcessSQLErrorf(err, "Failed to execute count status checks query")
}
return count, nil
}
// List returns a list of status check results for a specific commit in a repo.
func (s *CheckStore) List(ctx context.Context,
repoID int64,
commitSHA string,
opts types.CheckListOptions,
) ([]types.Check, error) {
stmt := database.Builder.
Select(checkColumns).
From("checks").
Where("check_repo_id = ?", repoID).
Where("check_commit_sha = ?", commitSHA).
Limit(database.Limit(opts.Size)).
Offset(database.Offset(opts.Page, opts.Size)).
OrderBy("check_updated desc")
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
}
dst := make([]*check, 0)
db := dbtx.GetAccessor(ctx, s.db)
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(err, "Failed to execute list status checks query")
}
result, err := s.mapSliceCheck(ctx, dst)
if err != nil {
return nil, err
}
return result, nil
}
// ListRecent returns a list of recently executed status checks in a repository.
func (s *CheckStore) ListRecent(ctx context.Context, repoID int64, since time.Time) ([]string, error) {
stmt := database.Builder.
Select("distinct check_uid").
From("checks").
Where("check_repo_id = ?", repoID).
Where("check_created > ?", since.UnixMilli()).
OrderBy("check_uid")
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
}
dst := make([]string, 0)
db := dbtx.GetAccessor(ctx, s.db)
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, database.ProcessSQLErrorf(err, "Failed to execute list recent status checks query")
}
return dst, nil
}
func mapInternalCheck(c *types.Check) *check {
m := &check{
ID: c.ID,
CreatedBy: c.CreatedBy,
Created: c.Created,
Updated: c.Updated,
RepoID: c.RepoID,
CommitSHA: c.CommitSHA,
UID: c.UID,
Status: c.Status,
Summary: c.Summary,
Link: c.Link,
Payload: c.Payload.Data,
Metadata: c.Metadata,
PayloadKind: c.Payload.Kind,
PayloadVersion: c.Payload.Version,
}
return m
}
func mapCheck(c *check) types.Check {
return types.Check{
ID: c.ID,
CreatedBy: c.CreatedBy,
Created: c.Created,
Updated: c.Updated,
RepoID: c.RepoID,
CommitSHA: c.CommitSHA,
UID: c.UID,
Status: c.Status,
Summary: c.Summary,
Link: c.Link,
Metadata: c.Metadata,
Payload: types.CheckPayload{
Version: c.PayloadVersion,
Kind: c.PayloadKind,
Data: c.Payload,
},
ReportedBy: types.PrincipalInfo{},
}
}
func (s *CheckStore) mapSliceCheck(ctx context.Context, checks []*check) ([]types.Check, error) {
// collect all principal IDs
ids := make([]int64, len(checks))
for i, req := range checks {
ids[i] = req.CreatedBy
}
// pull principal infos from cache
infoMap, err := s.pCache.Map(ctx, ids)
if err != nil {
return nil, fmt.Errorf("failed to load status check principal reporters: %w", err)
}
// attach the principal infos back to the slice items
m := make([]types.Check, len(checks))
for i, c := range checks {
m[i] = mapCheck(c)
if reportedBy, ok := infoMap[c.CreatedBy]; ok {
m[i].ReportedBy = *reportedBy
}
}
return m, nil
}