519 lines
15 KiB
Go
519 lines
15 KiB
Go
// Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
|
|
// Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
|
|
// Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// 1. Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
//
|
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
// this list of conditions and the following disclaimer in the documentation
|
|
// and/or other materials provided with the distribution.
|
|
//
|
|
// 3. Neither the name of the copyright holder nor the names of its contributors
|
|
// may be used to endorse or promote products derived from this software without
|
|
// specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
package peridotimplv1
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"database/sql"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
|
|
"go.temporal.io/sdk/client"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
|
peridotpb "peridot.resf.org/peridot/pb"
|
|
"peridot.resf.org/utils"
|
|
"strings"
|
|
)
|
|
|
|
func (s *Server) CreateProject(ctx context.Context, req *peridotpb.CreateProjectRequest) (*peridotpb.CreateProjectResponse, error) {
|
|
if err := req.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := req.Project.ValidateAll(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.checkPermission(ctx, ObjectGlobal, ObjectIdPeridot, PermissionManage); err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := utils.UserFromContext(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
beginTx, err := s.db.Begin()
|
|
if err != nil {
|
|
s.log.Errorf("CreateProject: beginTx: %v", err)
|
|
return nil, utils.InternalError
|
|
}
|
|
tx := s.db.UseTransaction(beginTx)
|
|
|
|
p, err := tx.CreateProject(req.GetProject())
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "unique") {
|
|
return nil, status.Error(codes.AlreadyExists, "project with name already exists")
|
|
}
|
|
s.log.Errorf("CreateProject: %v", err)
|
|
return nil, status.Error(codes.Internal, "failed to create project")
|
|
}
|
|
|
|
_, err = s.authz.WriteRelationships(ctx, &v1.WriteRelationshipsRequest{
|
|
Updates: []*v1.RelationshipUpdate{
|
|
{
|
|
Operation: v1.RelationshipUpdate_OPERATION_CREATE,
|
|
Relationship: &v1.Relationship{
|
|
Resource: &v1.ObjectReference{
|
|
ObjectType: ObjectProject,
|
|
ObjectId: p.ID.String(),
|
|
},
|
|
Relation: "manager",
|
|
Subject: &v1.SubjectReference{
|
|
Object: &v1.ObjectReference{
|
|
ObjectType: SubjectUser,
|
|
ObjectId: user.ID,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
_ = beginTx.Rollback()
|
|
s.log.Errorf("CreateProject: authz-create-relationships: %v", err)
|
|
return nil, status.Error(codes.Internal, "failed to create project")
|
|
}
|
|
|
|
_, err = tx.CreateRepositoryWithPackages("all", p.ID.String(), false, []string{})
|
|
if err != nil {
|
|
_ = beginTx.Rollback()
|
|
s.log.Errorf("CreateProject: db-create-repository: %v", err)
|
|
return nil, status.Error(codes.Internal, "failed to create project")
|
|
}
|
|
|
|
err = beginTx.Commit()
|
|
if err != nil {
|
|
s.log.Errorf("CreateProject: commit: %v", err)
|
|
return nil, status.Error(codes.Internal, "failed to create project")
|
|
}
|
|
|
|
return &peridotpb.CreateProjectResponse{
|
|
Project: p.ToProto(),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) UpdateProject(ctx context.Context, req *peridotpb.UpdateProjectRequest) (*peridotpb.UpdateProjectResponse, error) {
|
|
if err := req.ValidateAll(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := req.Project.ValidateAll(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.checkPermission(ctx, ObjectProject, req.ProjectId, PermissionManage); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p, err := s.db.UpdateProject(req.ProjectId, req.GetProject())
|
|
if err != nil {
|
|
s.log.Errorf("UpdateProject: %v", err)
|
|
return nil, status.Error(codes.Internal, "failed to update project")
|
|
}
|
|
|
|
return &peridotpb.UpdateProjectResponse{
|
|
Project: p.ToProto(),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) ListProjects(ctx context.Context, req *peridotpb.ListProjectsRequest) (*peridotpb.ListProjectsResponse, error) {
|
|
if err := req.ValidateAll(); err != nil {
|
|
return nil, err
|
|
}
|
|
resources, err := s.lookupResources(ctx, ObjectProject, PermissionView)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Subject doesn't have access to any projects
|
|
if len(resources) == 0 {
|
|
return &peridotpb.ListProjectsResponse{}, nil
|
|
}
|
|
|
|
projects, err := s.db.ListProjects(&peridotpb.ProjectFilters{
|
|
Ids: utils.Take[string](resources, "global"),
|
|
})
|
|
if err != nil {
|
|
s.log.Errorf("could not list projects: %v", err)
|
|
return nil, utils.CouldNotRetrieveObjects
|
|
}
|
|
|
|
return &peridotpb.ListProjectsResponse{
|
|
Projects: projects.ToProto(),
|
|
Total: int64(len(projects)),
|
|
Current: int64(len(projects)),
|
|
Page: 0,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) GetProject(ctx context.Context, req *peridotpb.GetProjectRequest) (*peridotpb.GetProjectResponse, error) {
|
|
if err := req.ValidateAll(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.checkPermission(ctx, ObjectProject, req.Id.Value, PermissionView); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
projects, err := s.db.ListProjects(&peridotpb.ProjectFilters{
|
|
Id: wrapperspb.String(req.Id.Value),
|
|
})
|
|
if err != nil {
|
|
return nil, utils.CouldNotFindObject
|
|
}
|
|
if len(projects) == 0 {
|
|
return nil, utils.CouldNotFindObject
|
|
}
|
|
|
|
return &peridotpb.GetProjectResponse{
|
|
Project: projects[0].ToProto(),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) ListRepositories(ctx context.Context, req *peridotpb.ListRepositoriesRequest) (*peridotpb.ListRepositoriesResponse, error) {
|
|
if err := req.ValidateAll(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.checkPermission(ctx, ObjectProject, req.ProjectId.Value, PermissionView); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
repos, err := s.db.FindRepositoriesForProject(req.ProjectId.Value, nil, false)
|
|
if err != nil {
|
|
s.log.Errorf("could not list repositories: %v", err)
|
|
return nil, utils.CouldNotRetrieveObjects
|
|
}
|
|
|
|
return &peridotpb.ListRepositoriesResponse{
|
|
Repositories: repos.ToProto(),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) GetRepository(ctx context.Context, req *peridotpb.GetRepositoryRequest) (*peridotpb.GetRepositoryResponse, error) {
|
|
if err := req.ValidateAll(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.checkPermission(ctx, ObjectProject, req.ProjectId.Value, PermissionView); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
repos, err := s.db.FindRepositoriesForProject(req.ProjectId.Value, &req.Id.Value, false)
|
|
if err != nil {
|
|
s.log.Errorf("could not list repositories: %v", err)
|
|
return nil, utils.CouldNotRetrieveObjects
|
|
}
|
|
if len(repos) == 0 {
|
|
return nil, utils.CouldNotFindObject
|
|
}
|
|
|
|
return &peridotpb.GetRepositoryResponse{
|
|
Repository: repos[0].ToProto(),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) GetProjectCredentials(ctx context.Context, req *peridotpb.GetProjectCredentialsRequest) (*peridotpb.GetProjectCredentialsResponse, error) {
|
|
if err := req.ValidateAll(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.checkPermission(ctx, ObjectProject, req.ProjectId.Value, PermissionManage); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
creds, err := s.db.GetProjectKeys(req.ProjectId.Value)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
s.log.Errorf("could not get project credentials: %v", err)
|
|
return nil, utils.CouldNotRetrieveObjects
|
|
}
|
|
if err == sql.ErrNoRows {
|
|
return &peridotpb.GetProjectCredentialsResponse{}, nil
|
|
}
|
|
|
|
return &peridotpb.GetProjectCredentialsResponse{
|
|
GitlabUsername: wrapperspb.String(creds.GitlabUsername),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) SetProjectCredentials(ctx context.Context, req *peridotpb.SetProjectCredentialsRequest) (*peridotpb.SetProjectCredentialsResponse, error) {
|
|
if err := req.ValidateAll(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.checkPermission(ctx, ObjectProject, req.ProjectId.Value, PermissionManage); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err := s.db.SetProjectKeys(req.ProjectId.Value, req.GitlabUsername.Value, req.GitlabPassword.Value)
|
|
if err != nil {
|
|
s.log.Errorf("could not set project credentials: %v", err)
|
|
return nil, utils.CouldNotUpdateObject
|
|
}
|
|
|
|
return &peridotpb.SetProjectCredentialsResponse{
|
|
GitlabUsername: req.GitlabUsername,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) SyncCatalog(ctx context.Context, req *peridotpb.SyncCatalogRequest) (*peridotpb.AsyncTask, error) {
|
|
if err := req.ValidateAll(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.checkPermission(ctx, ObjectProject, req.ProjectId.Value, PermissionManage); err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := utils.UserFromContext(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rollback := true
|
|
beginTx, err := s.db.Begin()
|
|
if err != nil {
|
|
s.log.Error(err)
|
|
return nil, utils.InternalError
|
|
}
|
|
defer func() {
|
|
if rollback {
|
|
_ = beginTx.Rollback()
|
|
}
|
|
}()
|
|
tx := s.db.UseTransaction(beginTx)
|
|
|
|
task, err := tx.CreateTask(user, "noarch", peridotpb.TaskType_TASK_TYPE_SYNC_CATALOG, &req.ProjectId.Value, nil)
|
|
if err != nil {
|
|
s.log.Errorf("could not create task: %v", err)
|
|
return nil, utils.InternalError
|
|
}
|
|
|
|
taskProto, err := task.ToProto(false)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "could not marshal task: %v", err)
|
|
}
|
|
|
|
rollback = false
|
|
err = beginTx.Commit()
|
|
if err != nil {
|
|
return nil, status.Error(codes.Internal, "could not save, try again")
|
|
}
|
|
|
|
_, err = s.temporal.ExecuteWorkflow(
|
|
context.Background(),
|
|
client.StartWorkflowOptions{
|
|
ID: task.ID.String(),
|
|
TaskQueue: MainTaskQueue,
|
|
},
|
|
s.temporalWorker.WorkflowController.SyncCatalogWorkflow,
|
|
req,
|
|
task,
|
|
)
|
|
if err != nil {
|
|
s.log.Errorf("could not start sync workflow in SyncCatalog: %v", err)
|
|
_ = s.db.SetTaskStatus(task.ID.String(), peridotpb.TaskStatus_TASK_STATUS_FAILED)
|
|
return nil, err
|
|
}
|
|
|
|
return &peridotpb.AsyncTask{
|
|
TaskId: task.ID.String(),
|
|
Subtasks: []*peridotpb.Subtask{taskProto},
|
|
Done: false,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) CreateHashedRepositories(ctx context.Context, req *peridotpb.CreateHashedRepositoriesRequest) (*peridotpb.AsyncTask, error) {
|
|
if err := req.ValidateAll(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.checkPermission(ctx, ObjectProject, req.ProjectId.Value, PermissionManage); err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := utils.UserFromContext(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rollback := true
|
|
beginTx, err := s.db.Begin()
|
|
if err != nil {
|
|
s.log.Error(err)
|
|
return nil, utils.InternalError
|
|
}
|
|
defer func() {
|
|
if rollback {
|
|
_ = beginTx.Rollback()
|
|
}
|
|
}()
|
|
tx := s.db.UseTransaction(beginTx)
|
|
|
|
task, err := tx.CreateTask(user, "noarch", peridotpb.TaskType_TASK_TYPE_CREATE_HASHED_REPOSITORIES, &req.ProjectId.Value, nil)
|
|
if err != nil {
|
|
s.log.Errorf("could not create task: %v", err)
|
|
return nil, utils.InternalError
|
|
}
|
|
|
|
taskProto, err := task.ToProto(false)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "could not marshal task: %v", err)
|
|
}
|
|
|
|
rollback = false
|
|
err = beginTx.Commit()
|
|
if err != nil {
|
|
return nil, status.Error(codes.Internal, "could not save, try again")
|
|
}
|
|
|
|
_, err = s.temporal.ExecuteWorkflow(
|
|
context.Background(),
|
|
client.StartWorkflowOptions{
|
|
ID: task.ID.String(),
|
|
TaskQueue: MainTaskQueue,
|
|
},
|
|
s.temporalWorker.WorkflowController.CreateHashedRepositoriesWorkflow,
|
|
req,
|
|
task,
|
|
)
|
|
if err != nil {
|
|
s.log.Errorf("could not start workflow: %v", err)
|
|
_ = s.db.SetTaskStatus(task.ID.String(), peridotpb.TaskStatus_TASK_STATUS_FAILED)
|
|
return nil, err
|
|
}
|
|
|
|
return &peridotpb.AsyncTask{
|
|
TaskId: task.ID.String(),
|
|
Subtasks: []*peridotpb.Subtask{taskProto},
|
|
Done: false,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) LookasideFileUpload(ctx context.Context, req *peridotpb.LookasideFileUploadRequest) (*peridotpb.LookasideFileUploadResponse, error) {
|
|
if err := req.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.checkPermission(ctx, ObjectGlobal, ObjectIdPeridot, PermissionManage); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
base64DecodedFile, err := base64.StdEncoding.DecodeString(req.File)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hasher := sha256.New()
|
|
_, err = hasher.Write(base64DecodedFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sha256Sum := hex.EncodeToString(hasher.Sum(nil))
|
|
|
|
exists, err := s.storage.Exists(sha256Sum)
|
|
if err != nil {
|
|
if !strings.Contains(err.Error(), "NotFound") {
|
|
return nil, err
|
|
}
|
|
}
|
|
if exists {
|
|
return &peridotpb.LookasideFileUploadResponse{
|
|
Digest: sha256Sum,
|
|
}, nil
|
|
}
|
|
|
|
_, err = s.storage.PutObjectBytes(sha256Sum, base64DecodedFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &peridotpb.LookasideFileUploadResponse{
|
|
Digest: sha256Sum,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) CloneSwap(ctx context.Context, req *peridotpb.CloneSwapRequest) (*peridotpb.AsyncTask, error) {
|
|
if err := req.ValidateAll(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.checkPermission(ctx, ObjectProject, req.TargetProjectId.Value, PermissionManage); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.checkPermission(ctx, ObjectProject, req.SrcProjectId.Value, PermissionView); err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := utils.UserFromContext(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rollback := true
|
|
beginTx, err := s.db.Begin()
|
|
if err != nil {
|
|
s.log.Error(err)
|
|
return nil, utils.InternalError
|
|
}
|
|
defer func() {
|
|
if rollback {
|
|
_ = beginTx.Rollback()
|
|
}
|
|
}()
|
|
tx := s.db.UseTransaction(beginTx)
|
|
|
|
task, err := tx.CreateTask(user, "noarch", peridotpb.TaskType_TASK_TYPE_CLONE_SWAP, &req.TargetProjectId.Value, nil)
|
|
if err != nil {
|
|
s.log.Errorf("could not create task: %v", err)
|
|
return nil, utils.InternalError
|
|
}
|
|
|
|
taskProto, err := task.ToProto(false)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "could not marshal task: %v", err)
|
|
}
|
|
|
|
rollback = false
|
|
err = beginTx.Commit()
|
|
if err != nil {
|
|
return nil, status.Error(codes.Internal, "could not save, try again")
|
|
}
|
|
|
|
_, err = s.temporal.ExecuteWorkflow(
|
|
context.Background(),
|
|
client.StartWorkflowOptions{
|
|
ID: task.ID.String(),
|
|
TaskQueue: MainTaskQueue,
|
|
},
|
|
s.temporalWorker.WorkflowController.CloneSwapWorkflow,
|
|
req,
|
|
task,
|
|
)
|
|
if err != nil {
|
|
s.log.Errorf("could not start workflow: %v", err)
|
|
_ = s.db.SetTaskStatus(task.ID.String(), peridotpb.TaskStatus_TASK_STATUS_FAILED)
|
|
return nil, err
|
|
}
|
|
|
|
return &peridotpb.AsyncTask{
|
|
TaskId: task.ID.String(),
|
|
Subtasks: []*peridotpb.Subtask{taskProto},
|
|
Done: false,
|
|
}, nil
|
|
}
|