308 lines
8.5 KiB
Go
308 lines
8.5 KiB
Go
// Copyright 2019 Sorint.lab
|
|
//
|
|
// 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 readdb
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"path"
|
|
"strings"
|
|
|
|
"agola.io/agola/internal/db"
|
|
"agola.io/agola/internal/services/configstore/common"
|
|
"agola.io/agola/internal/services/types"
|
|
"agola.io/agola/internal/util"
|
|
|
|
sq "github.com/Masterminds/squirrel"
|
|
errors "golang.org/x/xerrors"
|
|
)
|
|
|
|
var (
|
|
projectgroupSelect = sb.Select("id", "data").From("projectgroup")
|
|
projectgroupInsert = sb.Insert("projectgroup").Columns("id", "name", "parentid", "data")
|
|
)
|
|
|
|
func (r *ReadDB) insertProjectGroup(tx *db.Tx, data []byte) error {
|
|
var group *types.ProjectGroup
|
|
if err := json.Unmarshal(data, &group); err != nil {
|
|
return errors.Errorf("failed to unmarshal group: %w", err)
|
|
}
|
|
|
|
// poor man insert or update...
|
|
if err := r.deleteProjectGroup(tx, group.ID); err != nil {
|
|
return err
|
|
}
|
|
q, args, err := projectgroupInsert.Values(group.ID, group.Name, group.Parent.ID, data).ToSql()
|
|
if err != nil {
|
|
return errors.Errorf("failed to build query: %w", err)
|
|
}
|
|
if _, err = tx.Exec(q, args...); err != nil {
|
|
errors.Errorf("failed to insert group: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *ReadDB) deleteProjectGroup(tx *db.Tx, id string) error {
|
|
// poor man insert or update...
|
|
if _, err := tx.Exec("delete from projectgroup where id = $1", id); err != nil {
|
|
return errors.Errorf("failed to delete group: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type Element struct {
|
|
ID string
|
|
Name string
|
|
Type types.ConfigType
|
|
ParentType types.ConfigType
|
|
ParentID string
|
|
}
|
|
|
|
func (r *ReadDB) GetProjectGroupHierarchy(tx *db.Tx, projectGroup *types.ProjectGroup) ([]*Element, error) {
|
|
projectGroupID := projectGroup.Parent.ID
|
|
elements := []*Element{
|
|
{
|
|
ID: projectGroup.ID,
|
|
Name: projectGroup.Name,
|
|
Type: types.ConfigTypeProjectGroup,
|
|
ParentType: projectGroup.Parent.Type,
|
|
ParentID: projectGroup.Parent.ID,
|
|
},
|
|
}
|
|
|
|
for projectGroup.Parent.Type == types.ConfigTypeProjectGroup {
|
|
var err error
|
|
projectGroup, err = r.GetProjectGroup(tx, projectGroupID)
|
|
if err != nil {
|
|
return nil, errors.Errorf("failed to get project group %q: %w", projectGroupID, err)
|
|
}
|
|
if projectGroup == nil {
|
|
return nil, errors.Errorf("project group %q doesn't exist", projectGroupID)
|
|
}
|
|
elements = append([]*Element{
|
|
{
|
|
ID: projectGroup.ID,
|
|
Name: projectGroup.Name,
|
|
Type: types.ConfigTypeProjectGroup,
|
|
ParentType: projectGroup.Parent.Type,
|
|
ParentID: projectGroup.Parent.ID,
|
|
},
|
|
}, elements...)
|
|
projectGroupID = projectGroup.Parent.ID
|
|
}
|
|
|
|
return elements, nil
|
|
}
|
|
|
|
func (r *ReadDB) GetProjectGroupPath(tx *db.Tx, group *types.ProjectGroup) (string, error) {
|
|
var p string
|
|
|
|
groups, err := r.GetProjectGroupHierarchy(tx, group)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
rootGroupType := groups[0].ParentType
|
|
rootGroupID := groups[0].ParentID
|
|
switch rootGroupType {
|
|
case types.ConfigTypeOrg:
|
|
fallthrough
|
|
case types.ConfigTypeUser:
|
|
var err error
|
|
p, err = r.GetPath(tx, rootGroupType, rootGroupID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
default:
|
|
return "", errors.Errorf("invalid root group type %q", rootGroupType)
|
|
}
|
|
|
|
for _, group := range groups {
|
|
p = path.Join(p, group.Name)
|
|
}
|
|
|
|
return p, nil
|
|
}
|
|
|
|
func (r *ReadDB) GetProjectGroupOwnerID(tx *db.Tx, group *types.ProjectGroup) (types.ConfigType, string, error) {
|
|
groups, err := r.GetProjectGroupHierarchy(tx, group)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
rootGroupType := groups[0].ParentType
|
|
rootGroupID := groups[0].ParentID
|
|
return rootGroupType, rootGroupID, nil
|
|
}
|
|
|
|
func (r *ReadDB) GetProjectGroup(tx *db.Tx, projectGroupRef string) (*types.ProjectGroup, error) {
|
|
groupRef, err := common.ParsePathRef(projectGroupRef)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var group *types.ProjectGroup
|
|
switch groupRef {
|
|
case common.RefTypeID:
|
|
group, err = r.GetProjectGroupByID(tx, projectGroupRef)
|
|
case common.RefTypePath:
|
|
group, err = r.GetProjectGroupByPath(tx, projectGroupRef)
|
|
}
|
|
return group, err
|
|
}
|
|
|
|
func (r *ReadDB) GetProjectGroupByID(tx *db.Tx, projectGroupID string) (*types.ProjectGroup, error) {
|
|
q, args, err := projectgroupSelect.Where(sq.Eq{"id": projectGroupID}).ToSql()
|
|
r.log.Debugf("q: %s, args: %s", q, util.Dump(args))
|
|
if err != nil {
|
|
return nil, errors.Errorf("failed to build query: %w", err)
|
|
}
|
|
|
|
projectGroups, _, err := fetchProjectGroups(tx, q, args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(projectGroups) > 1 {
|
|
return nil, errors.Errorf("too many rows returned")
|
|
}
|
|
if len(projectGroups) == 0 {
|
|
return nil, nil
|
|
}
|
|
return projectGroups[0], nil
|
|
}
|
|
|
|
func (r *ReadDB) GetProjectGroupByName(tx *db.Tx, parentID, name string) (*types.ProjectGroup, error) {
|
|
q, args, err := projectgroupSelect.Where(sq.Eq{"parentid": parentID, "name": name}).ToSql()
|
|
r.log.Debugf("q: %s, args: %s", q, util.Dump(args))
|
|
if err != nil {
|
|
return nil, errors.Errorf("failed to build query: %w", err)
|
|
}
|
|
|
|
projectGroups, _, err := fetchProjectGroups(tx, q, args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(projectGroups) > 1 {
|
|
return nil, errors.Errorf("too many rows returned")
|
|
}
|
|
if len(projectGroups) == 0 {
|
|
return nil, nil
|
|
}
|
|
return projectGroups[0], nil
|
|
}
|
|
|
|
func (r *ReadDB) GetProjectGroupByPath(tx *db.Tx, projectGroupPath string) (*types.ProjectGroup, error) {
|
|
parts := strings.Split(projectGroupPath, "/")
|
|
if len(parts) < 2 {
|
|
return nil, errors.Errorf("wrong project group path: %q", projectGroupPath)
|
|
}
|
|
var parentID string
|
|
switch parts[0] {
|
|
case "org":
|
|
org, err := r.GetOrgByName(tx, parts[1])
|
|
if err != nil {
|
|
return nil, errors.Errorf("failed to get org %q: %w", parts[1], err)
|
|
}
|
|
if org == nil {
|
|
return nil, errors.Errorf("cannot find org with name %q", parts[1])
|
|
}
|
|
parentID = org.ID
|
|
case "user":
|
|
user, err := r.GetUserByName(tx, parts[1])
|
|
if err != nil {
|
|
return nil, errors.Errorf("failed to get user %q: %w", parts[1], err)
|
|
}
|
|
if user == nil {
|
|
return nil, errors.Errorf("cannot find user with name %q", parts[1])
|
|
}
|
|
parentID = user.ID
|
|
default:
|
|
return nil, errors.Errorf("wrong project group path: %q", projectGroupPath)
|
|
}
|
|
|
|
var projectGroup *types.ProjectGroup
|
|
// add root project group (empty name)
|
|
for _, projectGroupName := range append([]string{""}, parts[2:]...) {
|
|
var err error
|
|
projectGroup, err = r.GetProjectGroupByName(tx, parentID, projectGroupName)
|
|
if err != nil {
|
|
return nil, errors.Errorf("failed to get project group %q: %w", projectGroupName, err)
|
|
}
|
|
if projectGroup == nil {
|
|
return nil, nil
|
|
}
|
|
parentID = projectGroup.ID
|
|
}
|
|
|
|
return projectGroup, nil
|
|
}
|
|
|
|
func (r *ReadDB) GetProjectGroupSubgroups(tx *db.Tx, parentID string) ([]*types.ProjectGroup, error) {
|
|
var projectGroups []*types.ProjectGroup
|
|
|
|
q, args, err := projectgroupSelect.Where(sq.Eq{"parentid": parentID}).ToSql()
|
|
r.log.Debugf("q: %s, args: %s", q, util.Dump(args))
|
|
if err != nil {
|
|
return nil, errors.Errorf("failed to build query: %w", err)
|
|
}
|
|
|
|
projectGroups, _, err = fetchProjectGroups(tx, q, args...)
|
|
return projectGroups, err
|
|
}
|
|
|
|
func fetchProjectGroups(tx *db.Tx, q string, args ...interface{}) ([]*types.ProjectGroup, []string, error) {
|
|
rows, err := tx.Query(q, args...)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
defer rows.Close()
|
|
return scanProjectGroups(rows)
|
|
}
|
|
|
|
func scanProjectGroup(rows *sql.Rows, additionalFields ...interface{}) (*types.ProjectGroup, string, error) {
|
|
var id string
|
|
var data []byte
|
|
if err := rows.Scan(&id, &data); err != nil {
|
|
return nil, "", errors.Errorf("failed to scan rows: %w", err)
|
|
}
|
|
group := types.ProjectGroup{}
|
|
if len(data) > 0 {
|
|
if err := json.Unmarshal(data, &group); err != nil {
|
|
return nil, "", errors.Errorf("failed to unmarshal group: %w", err)
|
|
}
|
|
}
|
|
|
|
return &group, id, nil
|
|
}
|
|
|
|
func scanProjectGroups(rows *sql.Rows) ([]*types.ProjectGroup, []string, error) {
|
|
projectGroups := []*types.ProjectGroup{}
|
|
ids := []string{}
|
|
for rows.Next() {
|
|
p, id, err := scanProjectGroup(rows)
|
|
if err != nil {
|
|
rows.Close()
|
|
return nil, nil, err
|
|
}
|
|
projectGroups = append(projectGroups, p)
|
|
ids = append(ids, id)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return projectGroups, ids, nil
|
|
}
|