package gitlab import ( "net/url" "strings" "go-common/app/tool/saga/model" "go-common/library/log" "github.com/pkg/errors" "github.com/xanzy/go-gitlab" ) // Gitlab def type Gitlab struct { url string token string client *gitlab.Client } // New new gitlab structure func New(url string, token string) (g *Gitlab) { g = &Gitlab{ url: url, token: token, client: gitlab.NewClient(nil, token), } g.client.SetBaseURL(url) return } // HostToken ... func (g *Gitlab) HostToken() (host string, token string, err error) { var u *url.URL if u, err = url.Parse(g.url); err != nil { return } host = u.Host token = g.token return } // LastGreenCommit get last green pipeline commit func (g *Gitlab) LastGreenCommit(ciProjectID string, ciCommitRefName string) (commit string, err error) { var ( status = gitlab.Success pipelines gitlab.PipelineList ) opt := &gitlab.ListProjectPipelinesOptions{ Status: &status, Ref: gitlab.String(ciCommitRefName), } if pipelines, _, err = g.client.Pipelines.ListProjectPipelines(ciProjectID, opt); err != nil { return } if len(pipelines) == 0 { return } commit = pipelines[0].Sha return } // AcceptMR accept Merge request func (g *Gitlab) AcceptMR(projID int, mrIID int, msg string) (state string, err error) { var ( opt = &gitlab.AcceptMergeRequestOptions{ ShouldRemoveSourceBranch: gitlab.Bool(true), MergeCommitMessage: gitlab.String(msg), } mergeRequest *gitlab.MergeRequest ) if mergeRequest, _, err = g.client.MergeRequests.AcceptMergeRequest(projID, mrIID, opt); err != nil { err = errors.Wrapf(err, "AcceptMergeRequest(%d,%d)", projID, mrIID) return } state = mergeRequest.State return } // CloseMR close merge request func (g *Gitlab) CloseMR(projID int, mrIID int) (err error) { var ( opt = &gitlab.UpdateMergeRequestOptions{ StateEvent: gitlab.String("close"), } ) if _, _, err = g.client.MergeRequests.UpdateMergeRequest(projID, mrIID, opt); err != nil { err = errors.Wrapf(err, "CloseMR(%d,%d)", projID, mrIID) } return } // CreateMRNote create note to merge request func (g *Gitlab) CreateMRNote(projID int, mrIID int, content string) (noteID int, err error) { var ( opt = &gitlab.CreateMergeRequestNoteOptions{ Body: gitlab.String(content), } note *gitlab.Note ) if note, _, err = g.client.Notes.CreateMergeRequestNote(projID, mrIID, opt); err != nil { err = errors.Wrapf(err, "CreateMergeRequestNote(%d,%d)", projID, mrIID) return } noteID = note.ID return } // UpdateMRNote update the specified merge request note func (g *Gitlab) UpdateMRNote(projID int, mrIID int, noteID int, content string) (err error) { var ( opt = &gitlab.UpdateMergeRequestNoteOptions{ Body: gitlab.String(content), } ) if _, _, err = g.client.Notes.UpdateMergeRequestNote(projID, mrIID, noteID, opt); err != nil { err = errors.Wrapf(err, "UpdateMergeRequestNote(%d,%d,%d)", projID, mrIID, noteID) return } return } // DeleteMRNote delete the specified note for merge request func (g *Gitlab) DeleteMRNote(projID int, mrIID int, noteID int) (err error) { if _, err = g.client.Notes.DeleteMergeRequestNote(projID, mrIID, noteID); err != nil { err = errors.Wrapf(err, "DeleteMergeRequestNote(%d,%d,%d)", projID, mrIID, noteID) } return } // UserName get user name for user id func (g *Gitlab) UserName(userID int) (userName string, err error) { var ( user *gitlab.User ) if user, _, err = g.client.Users.GetUser(userID); err != nil { err = errors.WithStack(err) return } userName = user.Username return } // Triggers get pipeline triggers func (g *Gitlab) Triggers(projectID int) (triggers []*gitlab.PipelineTrigger, err error) { var ( opt = &gitlab.ListPipelineTriggersOptions{} ) if triggers, _, err = g.client.PipelineTriggers.ListPipelineTriggers(projectID, opt); err != nil { err = errors.Wrapf(err, "ListPipelineTriggers (projectID: %d)", projectID) return nil, err } return triggers, nil } // CreateTrigger create the trigger to trigger pipeline func (g *Gitlab) CreateTrigger(projectID int) (trigger *gitlab.PipelineTrigger, err error) { var ( opt = &gitlab.AddPipelineTriggerOptions{ Description: gitlab.String("gitlab-ci-build-on-merge-request"), } ) if trigger, _, err = g.client.PipelineTriggers.AddPipelineTrigger(projectID, opt); err != nil { err = errors.Wrapf(err, "CreateTrigger (projectID: %d)", projectID) return nil, err } return trigger, nil } // TakeOwnership take ownership of the trigger func (g *Gitlab) TakeOwnership(projectID int, triggerID int) (trigger *gitlab.PipelineTrigger, err error) { if trigger, _, err = g.client.PipelineTriggers.TakeOwnershipOfPipelineTrigger(projectID, triggerID); err != nil { err = errors.Wrapf(err, "TakeOwnershipOfPipelineTrigger (projectID: %d, triggerID: %d)", projectID, triggerID) return nil, err } return trigger, nil } // TriggerPipeline trigger the pipeline func (g *Gitlab) TriggerPipeline(projectID int, ref string, token string) (pipeline *gitlab.Pipeline, err error) { var ( opt = &gitlab.RunPipelineTriggerOptions{ Ref: gitlab.String(ref), Token: gitlab.String(token), } ) if pipeline, _, err = g.client.PipelineTriggers.RunPipelineTrigger(projectID, opt); err != nil { err = errors.Wrapf(err, "RunPipelineTrigger (projectID: %d, ref: %s, token: %s)", projectID, ref, token) return nil, err } return pipeline, nil } // AwardEmojiUsernames get the username who gave the emoji func (g *Gitlab) AwardEmojiUsernames(projID int, mrIID int) (usernames []string, err error) { var ( opt = &gitlab.ListAwardEmojiOptions{ Page: 1, PerPage: 100, } awards []*gitlab.AwardEmoji ) if awards, _, err = g.client.AwardEmoji.ListMergeRequestAwardEmoji(projID, mrIID, opt); err != nil { return } for _, a := range awards { if a.Name == "thumbsup" { usernames = append(usernames, a.User.Username) } } return } // AuditProjects audit all the given projects func (g *Gitlab) AuditProjects(repos []*model.Repo, hooks []*model.WebHook) (err error) { var ( repo *model.Repo projID int ) log.Info("AuditProjects start") for _, repo = range repos { log.Info("AuditProjects project [%s]", repo.Config.URL) if projID, err = g.ProjectID(repo.Config.URL); err != nil { log.Error("Failed to get project ID [%s], err: %+v", repo.Config.URL, err) continue } if err = g.AuditProjectHooks(projID, hooks); err != nil { log.Error("Failed to get hooks [%s], err: %+v", repo.Config.URL, err) continue } log.Info("AuditProjects project [%s]", repo.Config.URL) } log.Info("AuditProjects end") return } // AuditProjectHooks check and add or edit the project webhooks func (g *Gitlab) AuditProjectHooks(projID int, hooks []*model.WebHook) (err error) { var ( webHooks []*gitlab.ProjectHook h *model.WebHook ) if webHooks, err = g.ListProjectHook(projID); err != nil { return } // 配置文件中的webhook是否配置在项目中 inWebHooks := func(h *model.WebHook) (in bool, ph *gitlab.ProjectHook) { for _, ph = range webHooks { if h.URL == ph.URL { in = true return } } in = false return } // 找到的webhook是否和配置文件中webhook配置相等 equalHook := func(h *model.WebHook, ph *gitlab.ProjectHook) (equal bool) { if h.PushEvents == ph.PushEvents && h.IssuesEvents == ph.IssuesEvents && h.ConfidentialIssuesEvents == ph.ConfidentialIssuesEvents && h.MergeRequestsEvents == ph.MergeRequestsEvents && h.TagPushEvents == ph.TagPushEvents && h.NoteEvents == ph.NoteEvents && h.JobEvents == ph.JobEvents && h.PipelineEvents == ph.PipelineEvents && h.WikiPageEvents == ph.WikiPageEvents && !ph.EnableSSLVerification { return true } return false } // 配置的webhook与查询出来的对比,如果存在,则比较属性,属性不同,则修改;不存在,则创建 for _, h = range hooks { if in, ph := inWebHooks(h); in { if !equalHook(h, ph) { if err = g.EditProjectHook(projID, ph.ID, h); err != nil { return } } } else { if err = g.AddProjectHook(projID, h); err != nil { return } } } return } // AddProjectHook add project hook for the project func (g *Gitlab) AddProjectHook(projID int, hook *model.WebHook) (err error) { var ( opt = &gitlab.AddProjectHookOptions{ URL: &hook.URL, PushEvents: &hook.PushEvents, IssuesEvents: &hook.IssuesEvents, ConfidentialIssuesEvents: &hook.ConfidentialIssuesEvents, MergeRequestsEvents: &hook.MergeRequestsEvents, TagPushEvents: &hook.TagPushEvents, NoteEvents: &hook.NoteEvents, JobEvents: &hook.JobEvents, PipelineEvents: &hook.PipelineEvents, WikiPageEvents: &hook.WikiPageEvents, EnableSSLVerification: gitlab.Bool(false), } ) if _, _, err = g.client.Projects.AddProjectHook(projID, opt); err != nil { return } return } // ListProjectHook list all the webhook for the project func (g *Gitlab) ListProjectHook(projID int) (hooks []*gitlab.ProjectHook, err error) { var ( opt = &gitlab.ListProjectHooksOptions{ Page: 1, PerPage: 100, } ) if hooks, _, err = g.client.Projects.ListProjectHooks(projID, opt); err != nil { return } return } // EditProjectHook edit the specified webhook for the project func (g *Gitlab) EditProjectHook(projID int, hookID int, hook *model.WebHook) (err error) { var ( opt = &gitlab.EditProjectHookOptions{ URL: &hook.URL, PushEvents: &hook.PushEvents, IssuesEvents: &hook.IssuesEvents, ConfidentialIssuesEvents: &hook.ConfidentialIssuesEvents, MergeRequestsEvents: &hook.MergeRequestsEvents, TagPushEvents: &hook.TagPushEvents, NoteEvents: &hook.NoteEvents, JobEvents: &hook.JobEvents, PipelineEvents: &hook.PipelineEvents, WikiPageEvents: &hook.WikiPageEvents, EnableSSLVerification: gitlab.Bool(false), } ) if _, _, err = g.client.Projects.EditProjectHook(projID, hookID, opt); err != nil { return } return } // DeletePojectHook delete the specified hook for the project func (g *Gitlab) DeletePojectHook(projID int, hookID int) (err error) { if _, err = g.client.Projects.DeleteProjectHook(projID, hookID); err != nil { return } return } // ProjectID get project id by project URL-encoded name func (g *Gitlab) ProjectID(url string) (projID int, err error) { var ( projectName = url[strings.LastIndex(url, ":")+1 : strings.LastIndex(url, ".git")] project *gitlab.Project ) if project, _, err = g.client.Projects.GetProject(projectName); err != nil { return } projID = project.ID return } // MRPipelineStatus query PipelineState for mr func (g *Gitlab) MRPipelineStatus(projID int, mrIID int) (id int, status string, err error) { var pipelineList gitlab.PipelineList if pipelineList, _, err = g.client.MergeRequests.ListMergeRequestPipelines(projID, mrIID); err != nil { return } if len(pipelineList) == 0 { status = model.PipelineSuccess return } id = pipelineList[0].ID status = pipelineList[0].Status return } // LastPipeLineState query Last PipeLineState func (g *Gitlab) LastPipeLineState(projID int, ciCommitRefName string) (state bool, err error) { var pipelines gitlab.PipelineList opt := &gitlab.ListProjectPipelinesOptions{ Ref: gitlab.String(ciCommitRefName), } state = true if pipelines, _, err = g.client.Pipelines.ListProjectPipelines(projID, opt); err != nil { return } if len(pipelines) < 2 { return } state = pipelines[1].Status == model.PipelineSuccess return } // MergeStatus query MergeStatus func (g *Gitlab) MergeStatus(projID int, mrIID int) (wip bool, state string, status string, err error) { var mergerequest *gitlab.MergeRequest if mergerequest, _, err = g.client.MergeRequests.GetMergeRequest(projID, mrIID); err != nil { return } state = mergerequest.State status = mergerequest.MergeStatus wip = mergerequest.WorkInProgress return } // MergeLabels get Merge request labels func (g *Gitlab) MergeLabels(projID int, mrIID int) (labels []string, err error) { var mergerequest *gitlab.MergeRequest if mergerequest, _, err = g.client.MergeRequests.GetMergeRequest(projID, mrIID); err != nil { return } labels = mergerequest.Labels return } // PlusUsernames get the username who +1 func (g *Gitlab) PlusUsernames(projID int, mrIID int) (usernames []string, err error) { var ( opt = &gitlab.ListMergeRequestNotesOptions{ Page: 1, PerPage: 100, } notes []*gitlab.Note exist bool ) if notes, _, err = g.client.Notes.ListMergeRequestNotes(projID, mrIID, opt); err != nil { return } for _, note := range notes { if (strings.TrimSpace(note.Body) == model.SagaCommandPlusOne) || (strings.TrimSpace(note.Body) == model.SagaCommandPlusOne1) { exist = false for _, user := range usernames { if user == note.Author.Username { exist = true break } } if !exist { usernames = append(usernames, note.Author.Username) } } } return } // CompareDiff ... func (g *Gitlab) CompareDiff(projID int, from string, to string) (files []string, err error) { var ( opt = &gitlab.CompareOptions{ From: &from, To: &to, } compare *gitlab.Compare ) if compare, _, err = g.client.Repositories.Compare(projID, opt); err != nil { return } for _, diff := range compare.Diffs { if diff.NewFile { files = append(files, diff.NewPath) } else { files = append(files, diff.OldPath) } } return } // CommitDiff ... func (g *Gitlab) CommitDiff(projID int, sha string) (files []string, err error) { var ( diffs []*gitlab.Diff opt = &gitlab.GetCommitDiffOptions{ Page: 1, PerPage: 100, } ) if diffs, _, err = g.client.Commits.GetCommitDiff(projID, sha, opt); err != nil { return } for _, diff := range diffs { if diff.NewFile { files = append(files, diff.NewPath) } else { files = append(files, diff.OldPath) } } return } // MRChanges ... func (g *Gitlab) MRChanges(projID, mrIID int) (changeFiles []string, deleteFiles []string, err error) { var ( mr *gitlab.MergeRequest ) if mr, _, err = g.client.MergeRequests.GetMergeRequestChanges(projID, mrIID); err != nil { return } for _, c := range mr.Changes { if c.RenamedFile { deleteFiles = append(deleteFiles, c.OldPath) changeFiles = append(changeFiles, c.NewPath) } else if c.DeletedFile { deleteFiles = append(deleteFiles, c.OldPath) } else if c.NewFile { changeFiles = append(changeFiles, c.NewPath) } else { changeFiles = append(changeFiles, c.OldPath) } } return } // RepoRawFile ... func (g *Gitlab) RepoRawFile(projID int, branch string, filename string) (content []byte, err error) { var ( opt = &gitlab.GetRawFileOptions{ Ref: &branch, } ) if content, _, err = g.client.RepositoryFiles.GetRawFile(projID, filename, opt); err != nil { return } return }