peridot/peridot/impl/v1/project.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
}