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 }