github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/pkg/platform/model/projects.go (about)

     1  package model
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/go-openapi/strfmt"
     8  
     9  	"github.com/ActiveState/cli/internal/errs"
    10  	"github.com/ActiveState/cli/internal/locale"
    11  	"github.com/ActiveState/cli/internal/logging"
    12  	"github.com/ActiveState/cli/pkg/platform/api"
    13  	"github.com/ActiveState/cli/pkg/platform/api/graphql"
    14  	"github.com/ActiveState/cli/pkg/platform/api/graphql/model"
    15  	"github.com/ActiveState/cli/pkg/platform/api/graphql/request"
    16  	"github.com/ActiveState/cli/pkg/platform/api/mono/mono_client/projects"
    17  	clientProjects "github.com/ActiveState/cli/pkg/platform/api/mono/mono_client/projects"
    18  	"github.com/ActiveState/cli/pkg/platform/api/mono/mono_models"
    19  	"github.com/ActiveState/cli/pkg/platform/authentication"
    20  )
    21  
    22  type ErrProjectNotFound struct {
    23  	Organization string
    24  	Project      string
    25  }
    26  
    27  func (e *ErrProjectNotFound) Error() string {
    28  	return fmt.Sprintf("project not found: %s/%s", e.Organization, e.Project)
    29  }
    30  
    31  // LegacyFetchProjectByName is intended for legacy code which still relies on localised errors, do NOT use it for new code.
    32  func LegacyFetchProjectByName(orgName string, projectName string) (*mono_models.Project, error) {
    33  	auth, err := authentication.LegacyGet()
    34  	if err != nil {
    35  		return nil, errs.Wrap(err, "Could not get auth")
    36  	}
    37  	project, err := FetchProjectByName(orgName, projectName, auth)
    38  	if err == nil || !errs.Matches(err, &ErrProjectNotFound{}) {
    39  		return project, err
    40  	}
    41  	if !auth.Authenticated() {
    42  		return nil, errs.AddTips(
    43  			locale.NewExternalError("err_api_project_not_found", "", orgName, projectName),
    44  			locale.T("tip_private_project_auth"))
    45  	}
    46  	return nil, errs.Pack(err, locale.NewExternalError("err_api_project_not_found", "", orgName, projectName))
    47  }
    48  
    49  // FetchProjectByName fetches a project for an organization.
    50  func FetchProjectByName(orgName string, projectName string, auth *authentication.Auth) (*mono_models.Project, error) {
    51  	logging.Debug("fetching project (%s) in organization (%s)", projectName, orgName)
    52  
    53  	request := request.ProjectByOrgAndName(orgName, projectName)
    54  
    55  	gql := graphql.New(auth)
    56  	response := model.Projects{}
    57  	err := gql.Run(request, &response)
    58  	if err != nil {
    59  		return nil, errs.Wrap(err, "GraphQL request failed")
    60  	}
    61  
    62  	if len(response.Projects) == 0 {
    63  		return nil, &ErrProjectNotFound{orgName, projectName}
    64  	}
    65  
    66  	return response.Projects[0].ToMonoProject()
    67  }
    68  
    69  // FetchOrganizationProjects fetches the projects for an organization
    70  func FetchOrganizationProjects(orgName string, auth *authentication.Auth) ([]*mono_models.Project, error) {
    71  	authClient, err := auth.Client()
    72  	if err != nil {
    73  		return nil, errs.Wrap(err, "Could not get auth client")
    74  	}
    75  	projParams := clientProjects.NewListProjectsParams()
    76  	projParams.SetOrganizationName(orgName)
    77  	orgProjects, err := authClient.Projects.ListProjects(projParams, auth.ClientAuth())
    78  	if err != nil {
    79  		switch statusCode := api.ErrorCode(err); statusCode {
    80  		case 401:
    81  			return nil, locale.WrapExternalError(err, "err_api_not_authenticated")
    82  		case 404:
    83  			// NOT a project not found error; we didn't ask for a specific project.
    84  			return nil, locale.WrapExternalError(err, "err_api_org_not_found")
    85  		default:
    86  			return nil, locale.WrapError(err, "err_api_unknown", "Unexpected API error")
    87  		}
    88  	}
    89  	return orgProjects.Payload, nil
    90  }
    91  
    92  func LanguageByCommit(commitID strfmt.UUID, auth *authentication.Auth) (Language, error) {
    93  	languages, err := FetchLanguagesForCommit(commitID, auth)
    94  	if err != nil {
    95  		return Language{}, err
    96  	}
    97  
    98  	if len(languages) == 0 {
    99  		return Language{}, locale.NewExternalError("err_no_languages")
   100  	}
   101  
   102  	return languages[0], nil
   103  }
   104  
   105  func FetchTimeStampForCommit(commitID strfmt.UUID, auth *authentication.Auth) (*time.Time, error) {
   106  	_, atTime, err := FetchCheckpointForCommit(commitID, auth)
   107  	if err != nil {
   108  		return nil, errs.Wrap(err, "Unable to fetch checkpoint for commit ID")
   109  	}
   110  
   111  	t := time.Time(atTime)
   112  	return &t, nil
   113  }
   114  
   115  // DefaultBranchForProjectName retrieves the default branch for the given project owner/name.
   116  func DefaultBranchForProjectName(owner, name string) (*mono_models.Branch, error) {
   117  	proj, err := LegacyFetchProjectByName(owner, name)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	return DefaultBranchForProject(proj)
   123  }
   124  
   125  func BranchesForProject(owner, name string) ([]*mono_models.Branch, error) {
   126  	proj, err := LegacyFetchProjectByName(owner, name)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	return proj.Branches, nil
   131  }
   132  
   133  func BranchNamesForProjectFiltered(owner, name string, excludes ...string) ([]string, error) {
   134  	proj, err := LegacyFetchProjectByName(owner, name)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  	branches := make([]string, 0)
   139  	for _, branch := range proj.Branches {
   140  		for _, exclude := range excludes {
   141  			if branch.Label != exclude {
   142  				branches = append(branches, branch.Label)
   143  			}
   144  		}
   145  	}
   146  	return branches, nil
   147  }
   148  
   149  // DefaultBranchForProject retrieves the default branch for the given project
   150  func DefaultBranchForProject(pj *mono_models.Project) (*mono_models.Branch, error) {
   151  	for _, branch := range pj.Branches {
   152  		if branch.Default {
   153  			return branch, nil
   154  		}
   155  	}
   156  	return nil, locale.NewError("err_no_default_branch")
   157  }
   158  
   159  // BranchForProjectNameByName retrieves the named branch for the given project
   160  // org/name
   161  func BranchForProjectNameByName(owner, name, branch string) (*mono_models.Branch, error) {
   162  	proj, err := LegacyFetchProjectByName(owner, name)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	return BranchForProjectByName(proj, branch)
   168  }
   169  
   170  // BranchForProjectByName retrieves the named branch for the given project
   171  func BranchForProjectByName(pj *mono_models.Project, name string) (*mono_models.Branch, error) {
   172  	if name == "" {
   173  		return nil, locale.NewInputError("err_empty_branch", "Empty branch name provided.")
   174  	}
   175  
   176  	for _, branch := range pj.Branches {
   177  		if branch.Label != "" && branch.Label == name {
   178  			return branch, nil
   179  		}
   180  	}
   181  
   182  	return nil, locale.NewInputError(
   183  		"err_no_matching_branch_label",
   184  		"This project has no branch with label matching '[NOTICE]{{.V0}}[/RESET]'.",
   185  		name,
   186  	)
   187  }
   188  
   189  // CreateEmptyProject will create the project on the platform
   190  func CreateEmptyProject(owner, name string, private bool, auth *authentication.Auth) (*mono_models.Project, error) {
   191  	authClient, err := auth.Client()
   192  	if err != nil {
   193  		return nil, errs.Wrap(err, "Could not get auth client")
   194  	}
   195  	addParams := projects.NewAddProjectParams()
   196  	addParams.SetOrganizationName(owner)
   197  	addParams.SetProject(&mono_models.Project{Name: name, Private: private})
   198  	pj, err := authClient.Projects.AddProject(addParams, auth.ClientAuth())
   199  	if err != nil {
   200  		msg := api.ErrorMessageFromPayload(err)
   201  		if errs.Matches(err, &projects.AddProjectConflict{}) || errs.Matches(err, &projects.AddProjectNotFound{}) {
   202  			return nil, locale.WrapInputError(err, msg)
   203  		}
   204  		return nil, locale.WrapError(err, msg)
   205  	}
   206  
   207  	return pj.Payload, nil
   208  }
   209  
   210  func CreateCopy(sourceOwner, sourceName, targetOwner, targetName string, makePrivate bool, auth *authentication.Auth) (*mono_models.Project, error) {
   211  	// Retrieve the source project that we'll be forking
   212  	sourceProject, err := LegacyFetchProjectByName(sourceOwner, sourceName)
   213  	if err != nil {
   214  		return nil, locale.WrapExternalError(err, "err_fork_fetchProject", "Could not find the source project: {{.V0}}/{{.V1}}", sourceOwner, sourceName)
   215  	}
   216  
   217  	// Create the target project
   218  	targetProject, err := CreateEmptyProject(targetOwner, targetName, false, auth)
   219  	if err != nil {
   220  		return nil, locale.WrapError(err, "err_fork_createProject", "Could not create project: {{.V0}}/{{.V1}}", targetOwner, targetName)
   221  	}
   222  
   223  	sourceBranch, err := DefaultBranchForProject(sourceProject)
   224  	if err != nil {
   225  		return nil, locale.WrapError(err, "err_branch_nodefault", "Project has no default branch.")
   226  	}
   227  	if sourceBranch.CommitID != nil {
   228  		targetBranch, err := DefaultBranchForProject(targetProject)
   229  		if err != nil {
   230  			return nil, locale.WrapError(err, "err_branch_nodefault", "Project has no default branch.")
   231  		}
   232  		if err := UpdateBranchCommit(targetBranch.BranchID, *sourceBranch.CommitID, auth); err != nil {
   233  			return nil, locale.WrapError(err, "err_fork_branchupdate", "Failed to update branch.")
   234  		}
   235  	}
   236  
   237  	// Turn the target project private if this was requested (unfortunately this can't be done int the Creation step)
   238  	if makePrivate {
   239  		if err := MakeProjectPrivate(targetOwner, targetName, auth); err != nil {
   240  			logging.Debug("Cannot make forked project private; deleting public fork.")
   241  			if authClient, err2 := auth.Client(); err2 == nil {
   242  				deleteParams := projects.NewDeleteProjectParams()
   243  				deleteParams.SetOrganizationName(targetOwner)
   244  				deleteParams.SetProjectName(targetName)
   245  				if _, err3 := authClient.Projects.DeleteProject(deleteParams, auth.ClientAuth()); err3 != nil {
   246  					err = errs.Pack(err, locale.WrapError(
   247  						err3, "err_fork_private_but_project_created",
   248  						"Your project was created but could not be made private, please head over to {{.V0}} to manually update your privacy settings.",
   249  						api.GetPlatformURL(fmt.Sprintf("%s/%s", targetOwner, targetName)).String()))
   250  				}
   251  			} else {
   252  				err = errs.Pack(err, errs.Wrap(err2, "Could not get auth client"))
   253  			}
   254  			return nil, locale.WrapError(err, "err_fork_private", "Your fork could not be made private.")
   255  		}
   256  	}
   257  
   258  	return targetProject, nil
   259  }
   260  
   261  // MakeProjectPrivate turns the given project private
   262  func MakeProjectPrivate(owner, name string, auth *authentication.Auth) error {
   263  	authClient, err := auth.Client()
   264  	if err != nil {
   265  		return errs.Wrap(err, "Could not get auth client")
   266  	}
   267  
   268  	editParams := projects.NewEditProjectParams()
   269  	yes := true
   270  	editParams.SetProject(&mono_models.ProjectEditable{
   271  		Private: &yes,
   272  	})
   273  	editParams.SetOrganizationName(owner)
   274  	editParams.SetProjectName(name)
   275  
   276  	_, err = authClient.Projects.EditProject(editParams, auth.ClientAuth())
   277  	if err != nil {
   278  		msg := api.ErrorMessageFromPayload(err)
   279  		if errs.Matches(err, &projects.EditProjectBadRequest{}) {
   280  			return locale.WrapExternalError(err, msg) // user does not have permission
   281  		}
   282  		return locale.WrapError(err, msg)
   283  	}
   284  
   285  	return nil
   286  }
   287  
   288  // ProjectURL creates a valid platform URL for the given project parameters
   289  func ProjectURL(owner, name, commitID string) string {
   290  	url := api.GetPlatformURL(fmt.Sprintf("%s/%s", owner, name))
   291  	if commitID != "" {
   292  		query := url.Query()
   293  		query.Add("commitID", commitID)
   294  		url.RawQuery = query.Encode()
   295  	}
   296  	return url.String()
   297  }
   298  
   299  func AddBranch(projectID strfmt.UUID, label string, auth *authentication.Auth) (strfmt.UUID, error) {
   300  	var branchID strfmt.UUID
   301  	authClient, err := auth.Client()
   302  	if err != nil {
   303  		return "", errs.Wrap(err, "Could not get auth client")
   304  	}
   305  	addParams := projects.NewAddBranchParams()
   306  	addParams.SetProjectID(projectID)
   307  	addParams.Body.Label = label
   308  
   309  	res, err := authClient.Projects.AddBranch(addParams, auth.ClientAuth())
   310  	if err != nil {
   311  		msg := api.ErrorMessageFromPayload(err)
   312  		return branchID, locale.WrapError(err, msg)
   313  	}
   314  
   315  	return res.Payload.BranchID, nil
   316  }
   317  
   318  func EditProject(owner, name string, project *mono_models.ProjectEditable, auth *authentication.Auth) error {
   319  	authClient, err := auth.Client()
   320  	if err != nil {
   321  		return errs.Wrap(err, "Could not get auth client")
   322  	}
   323  
   324  	editParams := projects.NewEditProjectParams()
   325  	editParams.SetOrganizationName(owner)
   326  	editParams.SetProjectName(name)
   327  	editParams.SetProject(project)
   328  
   329  	_, err = authClient.Projects.EditProject(editParams, auth.ClientAuth())
   330  	if err != nil {
   331  		msg := api.ErrorMessageFromPayload(err)
   332  		return locale.WrapError(err, msg)
   333  	}
   334  
   335  	return nil
   336  }
   337  
   338  func DeleteProject(owner, project string, auth *authentication.Auth) error {
   339  	authClient, err := auth.Client()
   340  	if err != nil {
   341  		return errs.Wrap(err, "Could not get auth client")
   342  	}
   343  
   344  	params := projects.NewDeleteProjectParams()
   345  	params.SetOrganizationName(owner)
   346  	params.SetProjectName(project)
   347  
   348  	_, err = authClient.Projects.DeleteProject(params, auth.ClientAuth())
   349  	if err != nil {
   350  		msg := api.ErrorMessageFromPayload(err)
   351  		return locale.WrapError(err, msg)
   352  	}
   353  
   354  	return nil
   355  }
   356  
   357  func MoveProject(owner, project, newOwner string, auth *authentication.Auth) error {
   358  	authClient, err := auth.Client()
   359  	if err != nil {
   360  		return errs.Wrap(err, "Could not get auth client")
   361  	}
   362  
   363  	params := projects.NewMoveProjectParams()
   364  	params.SetOrganizationIdentifier(owner)
   365  	params.SetProjectName(project)
   366  	params.SetDestination(projects.MoveProjectBody{newOwner})
   367  
   368  	_, err = authClient.Projects.MoveProject(params, auth.ClientAuth())
   369  	if err != nil {
   370  		msg := api.ErrorMessageFromPayload(err)
   371  		return locale.WrapError(err, msg)
   372  	}
   373  
   374  	return nil
   375  }