Path: blob/main/components/public-api-server/pkg/apiv1/project.go
2499 views
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package apiv156import (7"context"8"errors"9"fmt"10"strings"1112connect "github.com/bufbuild/connect-go"13"github.com/gitpod-io/gitpod/common-go/log"14v1 "github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1"15"github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1/v1connect"16protocol "github.com/gitpod-io/gitpod/gitpod-protocol"17"github.com/gitpod-io/gitpod/public-api-server/pkg/auth"18"github.com/gitpod-io/gitpod/public-api-server/pkg/proxy"19"github.com/google/uuid"20)2122func NewProjectsService(pool proxy.ServerConnectionPool) *ProjectsService {23return &ProjectsService{24connectionPool: pool,25}26}2728type ProjectsService struct {29connectionPool proxy.ServerConnectionPool3031v1connect.UnimplementedProjectsServiceHandler32}3334func (s *ProjectsService) CreateProject(ctx context.Context, req *connect.Request[v1.CreateProjectRequest]) (*connect.Response[v1.CreateProjectResponse], error) {35spec := req.Msg.GetProject()3637name := strings.TrimSpace(spec.GetName())38if name == "" {39return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("Name is a required argument."))40}4142cloneURL := strings.TrimSpace(spec.GetCloneUrl())43if cloneURL == "" {44return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("Clone URL is a required argument."))45}4647teamID := spec.GetTeamId()48_, err := uuid.Parse(teamID)49if err != nil {50return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("Team ID is not a valid UUID."))51}5253conn, err := s.getConnection(ctx)54if err != nil {55return nil, err56}5758project, err := conn.CreateProject(ctx, &protocol.CreateProjectOptions{59Name: name,60TeamID: teamID,61CloneURL: cloneURL,62AppInstallationID: "undefined", // sadly that's how we store cases where there is no AppInstallationID63})64if err != nil {65return nil, proxy.ConvertError(err)66}6768return connect.NewResponse(&v1.CreateProjectResponse{69Project: projectToAPIResponse(project),70}), nil71}7273func (s *ProjectsService) ListProjects(ctx context.Context, req *connect.Request[v1.ListProjectsRequest]) (*connect.Response[v1.ListProjectsResponse], error) {74teamID := req.Msg.GetTeamId()75if teamID == "" {76return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("Organization ID not specified."))77}7879conn, err := s.getConnection(ctx)80if err != nil {81return nil, err82}8384var projects []*protocol.Project8586if teamID != "" {87_, err := uuid.Parse(teamID)88if err != nil {89return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("Organization ID is not a valid UUID."))90}9192projects, err = conn.GetTeamProjects(ctx, teamID)93if err != nil {94return nil, proxy.ConvertError(err)95}96}9798// We're extracting a particular page of results from the full set of results.99// This is wasteful, but necessary, until we either:100// * Add new APIs to server which support pagination101// * Port the query logic to Public API102results := pageFromResults(projects, req.Msg.GetPagination())103104return connect.NewResponse(&v1.ListProjectsResponse{105Projects: projectsToAPIResponse(results),106TotalResults: int32(len(projects)),107}), nil108}109110func (s *ProjectsService) DeleteProject(ctx context.Context, req *connect.Request[v1.DeleteProjectRequest]) (*connect.Response[v1.DeleteProjectResponse], error) {111projectID, err := validateProjectID(ctx, req.Msg.GetProjectId())112if err != nil {113return nil, err114}115116conn, err := s.getConnection(ctx)117if err != nil {118return nil, err119}120121err = conn.DeleteProject(ctx, projectID.String())122if err != nil {123return nil, proxy.ConvertError(err)124}125126return connect.NewResponse(&v1.DeleteProjectResponse{}), nil127}128129func (s *ProjectsService) getConnection(ctx context.Context) (protocol.APIInterface, error) {130token, err := auth.TokenFromContext(ctx)131if err != nil {132return nil, connect.NewError(connect.CodeUnauthenticated, fmt.Errorf("No credentials present on request."))133}134135conn, err := s.connectionPool.Get(ctx, token)136if err != nil {137log.Extract(ctx).WithError(err).Error("Failed to get connection to server.")138return nil, connect.NewError(connect.CodeInternal, errors.New("Failed to establish connection to downstream services. If this issue persists, please contact Gitpod Support."))139}140141return conn, nil142}143144func projectsToAPIResponse(ps []*protocol.Project) []*v1.Project {145var projects []*v1.Project146for _, p := range ps {147projects = append(projects, projectToAPIResponse(p))148}149150return projects151}152153func projectToAPIResponse(p *protocol.Project) *v1.Project {154return &v1.Project{155Id: p.ID,156TeamId: p.TeamID,157Name: p.Name,158CloneUrl: p.CloneURL,159CreationTime: parseGitpodTimeStampOrDefault(p.CreationTime),160Settings: projectSettingsToAPIResponse(p.Settings),161}162}163164func projectSettingsToAPIResponse(s *protocol.ProjectSettings) *v1.ProjectSettings {165if s == nil {166return &v1.ProjectSettings{}167}168169settings := &v1.ProjectSettings{170Prebuild: &v1.PrebuildSettings{},171Workspace: &v1.WorkspaceSettings{172WorkspaceClass: workspaceClassesToAPIResponse(s.WorkspaceClasses),173},174}175if s.PrebuildSettings != nil {176settings.Prebuild.EnablePrebuilds = s.PrebuildSettings.Enable177settings.Prebuild.BranchStrategy = s.PrebuildSettings.BranchStrategy178settings.Prebuild.BranchMatchingPattern = s.PrebuildSettings.BranchMatchingPattern179settings.Prebuild.PrebuildInterval = s.PrebuildSettings.PrebuildInterval180settings.Prebuild.WorkspaceClass = s.PrebuildSettings.WorkspaceClass181}182if s.RestrictedWorkspaceClasses != nil {183settings.Workspace.RestrictedWorkspaceClasses = *s.RestrictedWorkspaceClasses184}185186return settings187}188189func workspaceClassesToAPIResponse(s *protocol.WorkspaceClassesSettings) *v1.WorkspaceClassSettings {190if s == nil {191return &v1.WorkspaceClassSettings{}192}193194return &v1.WorkspaceClassSettings{195Regular: s.Regular,196Prebuild: s.Prebuild,197}198}199200201