mirror of
https://github.com/harness/drone.git
synced 2025-05-07 00:50:50 +08:00

This change introduces the concept of a principal (abstraction of call identity), and adds a new service account type principal. Also adds support for different tokens (session, PAT, SAT, OAuth2) and adds auth.Session which is being used to capture information about the caller and call method.
409 lines
11 KiB
Go
409 lines
11 KiB
Go
// Copyright 2021 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"
|
|
"time"
|
|
|
|
"github.com/harness/gitness/internal/paths"
|
|
"github.com/harness/gitness/internal/store"
|
|
"github.com/harness/gitness/types"
|
|
"github.com/harness/gitness/types/enum"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
)
|
|
|
|
var _ store.RepoStore = (*RepoStore)(nil)
|
|
|
|
// Returns a new RepoStore.
|
|
func NewRepoStore(db *sqlx.DB) *RepoStore {
|
|
return &RepoStore{db}
|
|
}
|
|
|
|
// Implements a RepoStore backed by a relational database.
|
|
type RepoStore struct {
|
|
db *sqlx.DB
|
|
}
|
|
|
|
// Finds the repo by id.
|
|
func (s *RepoStore) Find(ctx context.Context, id int64) (*types.Repository, error) {
|
|
dst := new(types.Repository)
|
|
if err := s.db.GetContext(ctx, dst, repoSelectByID, id); err != nil {
|
|
return nil, processSQLErrorf(err, "Select query failed")
|
|
}
|
|
return dst, nil
|
|
}
|
|
|
|
// Finds the repo by path.
|
|
func (s *RepoStore) FindByPath(ctx context.Context, path string) (*types.Repository, error) {
|
|
dst := new(types.Repository)
|
|
if err := s.db.GetContext(ctx, dst, repoSelectByPath, path); err != nil {
|
|
return nil, processSQLErrorf(err, "Select query failed")
|
|
}
|
|
return dst, nil
|
|
}
|
|
|
|
// Create creates a new repository.
|
|
func (s *RepoStore) Create(ctx context.Context, repo *types.Repository) error {
|
|
tx, err := s.db.BeginTxx(ctx, nil)
|
|
if err != nil {
|
|
return processSQLErrorf(err, "Failed to start a new transaction")
|
|
}
|
|
defer func(tx *sqlx.Tx) {
|
|
_ = tx.Rollback()
|
|
}(tx)
|
|
|
|
// insert repo first so we get id
|
|
query, arg, err := s.db.BindNamed(repoInsert, repo)
|
|
if err != nil {
|
|
return processSQLErrorf(err, "Failed to bind repo object")
|
|
}
|
|
|
|
if err = tx.QueryRow(query, arg...).Scan(&repo.ID); err != nil {
|
|
return processSQLErrorf(err, "Insert query failed")
|
|
}
|
|
|
|
// Get parent path (repo always has a parent)
|
|
parentPath, err := FindPathTx(ctx, tx, enum.PathTargetTypeSpace, repo.SpaceID)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to find path of parent space")
|
|
}
|
|
|
|
// all existing paths are valid, repo name is assumed to be valid.
|
|
path := paths.Concatinate(parentPath.Value, repo.PathName)
|
|
|
|
// create path only once we know the id of the repo
|
|
p := &types.Path{
|
|
TargetType: enum.PathTargetTypeRepo,
|
|
TargetID: repo.ID,
|
|
IsAlias: false,
|
|
Value: path,
|
|
CreatedBy: repo.CreatedBy,
|
|
Created: repo.Created,
|
|
Updated: repo.Updated,
|
|
}
|
|
|
|
if err = CreatePathTx(ctx, s.db, tx, p); err != nil {
|
|
return errors.Wrap(err, "Failed to create primary path of repo")
|
|
}
|
|
|
|
// commit
|
|
if err = tx.Commit(); err != nil {
|
|
return processSQLErrorf(err, "Failed to commit transaction")
|
|
}
|
|
|
|
// update path in repo object
|
|
repo.Path = p.Value
|
|
|
|
return nil
|
|
}
|
|
|
|
// Move moves an existing space.
|
|
func (s *RepoStore) Move(ctx context.Context, principalID int64, repoID int64, newSpaceID int64, newName string,
|
|
keepAsAlias bool) (*types.Repository, error) {
|
|
tx, err := s.db.BeginTxx(ctx, nil)
|
|
if err != nil {
|
|
return nil, processSQLErrorf(err, "Failed to start a new transaction")
|
|
}
|
|
defer func(tx *sqlx.Tx) {
|
|
_ = tx.Rollback() // should we take care about rollbacks errors?
|
|
}(tx)
|
|
|
|
// get current path of repo
|
|
currentPath, err := FindPathTx(ctx, tx, enum.PathTargetTypeRepo, repoID)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Failed to find the primary path of the repo")
|
|
}
|
|
|
|
// get path of new parent space
|
|
spacePath, err := FindPathTx(ctx, tx, enum.PathTargetTypeSpace, newSpaceID)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Failed to find the primary path of the new space")
|
|
}
|
|
newPath := paths.Concatinate(spacePath.Value, newName)
|
|
|
|
if newPath == currentPath.Value {
|
|
return nil, store.ErrNoChangeInRequestedMove
|
|
}
|
|
|
|
p := &types.Path{
|
|
TargetType: enum.PathTargetTypeRepo,
|
|
TargetID: repoID,
|
|
IsAlias: false,
|
|
Value: newPath,
|
|
CreatedBy: principalID,
|
|
Created: time.Now().UnixMilli(),
|
|
Updated: time.Now().UnixMilli(),
|
|
}
|
|
|
|
// replace the primary path (also updates all child primary paths)
|
|
if err = ReplacePathTx(ctx, s.db, tx, p, keepAsAlias); err != nil {
|
|
return nil, errors.Wrap(err, "Failed to update the primary path of the repo")
|
|
}
|
|
|
|
// Rename the repo itself
|
|
if _, err = tx.ExecContext(ctx, repoUpdateNameAndSpaceID, newName, newSpaceID, repoID); err != nil {
|
|
return nil, processSQLErrorf(err, "Query for renaming and updating the space id failed")
|
|
}
|
|
|
|
// TODO: return repo as part of rename db operation?
|
|
dst := new(types.Repository)
|
|
if err = tx.GetContext(ctx, dst, repoSelectByID, repoID); err != nil {
|
|
return nil, processSQLErrorf(err, "Select query to get the repo's latest state failed")
|
|
}
|
|
|
|
// commit
|
|
if err = tx.Commit(); err != nil {
|
|
return nil, processSQLErrorf(err, "Failed to commit transaction")
|
|
}
|
|
|
|
return dst, nil
|
|
}
|
|
|
|
// Updates the repo details.
|
|
func (s *RepoStore) Update(ctx context.Context, repo *types.Repository) error {
|
|
query, arg, err := s.db.BindNamed(repoUpdate, repo)
|
|
if err != nil {
|
|
return processSQLErrorf(err, "Failed to bind repo object")
|
|
}
|
|
|
|
if _, err = s.db.ExecContext(ctx, query, arg...); err != nil {
|
|
return processSQLErrorf(err, "Update query failed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Delete the repository.
|
|
func (s *RepoStore) Delete(ctx context.Context, id int64) error {
|
|
tx, err := s.db.BeginTxx(ctx, nil)
|
|
if err != nil {
|
|
return processSQLErrorf(err, "Failed to start a new transaction")
|
|
}
|
|
defer func(tx *sqlx.Tx) {
|
|
_ = tx.Rollback()
|
|
}(tx)
|
|
|
|
// delete all paths
|
|
err = DeleteAllPaths(ctx, tx, enum.PathTargetTypeRepo, id)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Failed to delete all paths of the repo")
|
|
}
|
|
|
|
// delete the repo
|
|
if _, err = tx.ExecContext(ctx, repoDelete, id); err != nil {
|
|
return processSQLErrorf(err, "The delete query failed")
|
|
}
|
|
|
|
if err = tx.Commit(); err != nil {
|
|
return processSQLErrorf(err, "Failed to commit transaction")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Count of repos in a space.
|
|
func (s *RepoStore) Count(ctx context.Context, spaceID int64) (int64, error) {
|
|
var count int64
|
|
err := s.db.QueryRow(repoCount, spaceID).Scan(&count)
|
|
if err != nil {
|
|
return 0, processSQLErrorf(err, "Failed executing count query")
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
// List returns a list of repos in a space.
|
|
// TODO: speed up list - for some reason is 200ms for 1 repo as well as 1000
|
|
func (s *RepoStore) List(ctx context.Context, spaceID int64, opts *types.RepoFilter) ([]*types.Repository, error) {
|
|
dst := []*types.Repository{}
|
|
|
|
// if the principal does not provide any customer filter
|
|
// or sorting we use the default select statement.
|
|
if opts.Sort == enum.RepoAttrNone {
|
|
err := s.db.SelectContext(ctx, &dst, repoSelect, spaceID, limit(opts.Size), offset(opts.Page, opts.Size))
|
|
if err != nil {
|
|
return nil, processSQLErrorf(err, "Failed executing default list query")
|
|
}
|
|
return dst, nil
|
|
}
|
|
|
|
// else we construct the sql statement.
|
|
stmt := builder.
|
|
Select("repositories.*,path_value AS repo_path").
|
|
From("repositories").
|
|
InnerJoin("paths ON repositories.repo_id=paths.path_targetId AND paths.path_targetType='repo' " +
|
|
"AND paths.path_isAlias=0").
|
|
Where("repo_spaceId = " + fmt.Sprint(spaceID))
|
|
stmt = stmt.Limit(uint64(limit(opts.Size)))
|
|
stmt = stmt.Offset(uint64(offset(opts.Page, opts.Size)))
|
|
|
|
switch opts.Sort {
|
|
case enum.RepoAttrName, enum.RepoAttrNone:
|
|
// NOTE: string concatenation is safe because the
|
|
// order attribute is an enum and is not user-defined,
|
|
// and is therefore not subject to injection attacks.
|
|
stmt = stmt.OrderBy("repo_name " + opts.Order.String())
|
|
case enum.RepoAttrCreated:
|
|
stmt = stmt.OrderBy("repo_created " + opts.Order.String())
|
|
case enum.RepoAttrUpdated:
|
|
stmt = stmt.OrderBy("repo_updated " + opts.Order.String())
|
|
case enum.RepoAttrID:
|
|
stmt = stmt.OrderBy("repo_id " + opts.Order.String())
|
|
case enum.RepoAttrPathName:
|
|
stmt = stmt.OrderBy("repo_pathName " + opts.Order.String())
|
|
case enum.RepoAttrPath:
|
|
stmt = stmt.OrderBy("repo_path " + opts.Order.String())
|
|
}
|
|
|
|
sql, _, err := stmt.ToSql()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "Failed to convert query to sql")
|
|
}
|
|
|
|
if err = s.db.SelectContext(ctx, &dst, sql); err != nil {
|
|
return nil, processSQLErrorf(err, "Failed executing custom list query")
|
|
}
|
|
|
|
return dst, nil
|
|
}
|
|
|
|
// ListAllPaths returns a list of all paths of a repo.
|
|
func (s *RepoStore) ListAllPaths(ctx context.Context, id int64, opts *types.PathFilter) ([]*types.Path, error) {
|
|
return ListPaths(ctx, s.db, enum.PathTargetTypeRepo, id, opts)
|
|
}
|
|
|
|
// CreatePath creates an alias for a repository.
|
|
func (s *RepoStore) CreatePath(ctx context.Context, repoID int64, params *types.PathParams) (*types.Path, error) {
|
|
p := &types.Path{
|
|
TargetType: enum.PathTargetTypeRepo,
|
|
TargetID: repoID,
|
|
IsAlias: true,
|
|
|
|
// get remaining infor from params
|
|
Value: params.Path,
|
|
CreatedBy: params.CreatedBy,
|
|
Created: params.Created,
|
|
Updated: params.Updated,
|
|
}
|
|
|
|
return p, CreateAliasPath(ctx, s.db, p)
|
|
}
|
|
|
|
// DeletePath an alias of a repository.
|
|
func (s *RepoStore) DeletePath(ctx context.Context, repoID int64, pathID int64) error {
|
|
return DeletePath(ctx, s.db, pathID)
|
|
}
|
|
|
|
const repoSelectBase = `
|
|
SELECT
|
|
repo_id
|
|
,repo_pathName
|
|
,repo_spaceId
|
|
,paths.path_value AS repo_path
|
|
,repo_name
|
|
,repo_description
|
|
,repo_isPublic
|
|
,repo_createdBy
|
|
,repo_created
|
|
,repo_updated
|
|
,repo_forkId
|
|
,repo_numForks
|
|
,repo_numPulls
|
|
,repo_numClosedPulls
|
|
,repo_numOpenPulls
|
|
`
|
|
|
|
const repoSelectBaseWithJoin = repoSelectBase + `
|
|
FROM repositories
|
|
INNER JOIN paths
|
|
ON repositories.repo_id=paths.path_targetId AND paths.path_targetType='repo' AND paths.path_isAlias=0
|
|
`
|
|
|
|
const repoSelect = repoSelectBaseWithJoin + `
|
|
WHERE repo_spaceId = $1
|
|
ORDER BY repo_pathName ASC
|
|
LIMIT $2 OFFSET $3
|
|
`
|
|
|
|
const repoCount = `
|
|
SELECT count(*)
|
|
FROM repositories
|
|
WHERE repo_spaceId = $1
|
|
`
|
|
|
|
const repoSelectByID = repoSelectBaseWithJoin + `
|
|
WHERE repo_id = $1
|
|
`
|
|
|
|
const repoSelectByPath = repoSelectBase + `
|
|
FROM paths paths1
|
|
INNER JOIN repositories ON repositories.repo_id=paths1.path_targetId AND paths1.path_targetType='repo'
|
|
AND paths1.path_value = $1
|
|
INNER JOIN paths ON repositories.repo_id=paths.path_targetId AND paths.path_targetType='repo' AND paths.path_isAlias=0
|
|
`
|
|
|
|
const repoDelete = `
|
|
DELETE FROM repositories
|
|
WHERE repo_id = $1
|
|
`
|
|
|
|
// TODO: do we have to worry about SQL injection for description?
|
|
const repoInsert = `
|
|
INSERT INTO repositories (
|
|
repo_pathName
|
|
,repo_spaceId
|
|
,repo_name
|
|
,repo_description
|
|
,repo_isPublic
|
|
,repo_createdBy
|
|
,repo_created
|
|
,repo_updated
|
|
,repo_forkId
|
|
,repo_numForks
|
|
,repo_numPulls
|
|
,repo_numClosedPulls
|
|
,repo_numOpenPulls
|
|
) values (
|
|
:repo_pathName
|
|
,:repo_spaceId
|
|
,:repo_name
|
|
,:repo_description
|
|
,:repo_isPublic
|
|
,:repo_createdBy
|
|
,:repo_created
|
|
,:repo_updated
|
|
,:repo_forkId
|
|
,:repo_numForks
|
|
,:repo_numPulls
|
|
,:repo_numClosedPulls
|
|
,:repo_numOpenPulls
|
|
) RETURNING repo_id
|
|
`
|
|
|
|
const repoUpdate = `
|
|
UPDATE repositories
|
|
SET
|
|
repo_name = :repo_name
|
|
,repo_description = :repo_description
|
|
,repo_isPublic = :repo_isPublic
|
|
,repo_updated = :repo_updated
|
|
,repo_numForks = :repo_numForks
|
|
,repo_numPulls = :repo_numPulls
|
|
,repo_numClosedPulls = :repo_numClosedPulls
|
|
,repo_numOpenPulls = :repo_numOpenPulls
|
|
WHERE repo_id = :repo_id
|
|
`
|
|
|
|
const repoUpdateNameAndSpaceID = `
|
|
UPDATE repositories
|
|
SET
|
|
repo_pathName = $1
|
|
,repo_spaceId = $2
|
|
WHERE repo_id = $3
|
|
`
|