package gitlab import ( "context" "time" "github.com/xanzy/go-gitlab" ) const defProjectsPerPage = 100 type Client struct { Ctx context.Context gitlab *gitlab.Client } type Project struct { ID int Description string SSHURLToRepo string HTTPURLToRepo string WebURL string // Owner User Name string NameWithNamespace string Path string PathWithNamespace string AvatarURL string LastActivityAt time.Time } type User struct { ID int Username string Email string Name string AvatarURL string } type ProgressInfo struct { ProgressChan chan Progress ProjectsChan chan []*Project ErrorChan chan error DoneChan chan interface{} } type Progress struct { Page int Pages int Projects int TotalProjects int } // Given there may be thousands of projects, this will return // channels that stream progress info and then finally the full // list of projects on separate channels func (c *Client) StreamProjects() *ProgressInfo { pi := &ProgressInfo{ ProgressChan: make(chan Progress), ProjectsChan: make(chan []*Project), ErrorChan: make(chan error), DoneChan: make(chan interface{}), } go c.streamProjects(pi) return pi } func (c *Client) streamProjects(pi *ProgressInfo) { defer close(pi.ProgressChan) defer close(pi.ProjectsChan) listOpts := &gitlab.ListProjectsOptions{ ListOptions: gitlab.ListOptions{ PerPage: defProjectsPerPage, Page: 1, }, Archived: new(bool), } var numProjects int for { projects, resp, err := c.ListProjects(listOpts) // We're done when we have it all or our context is done if c.Ctx.Err() != nil || resp.NextPage == 0 { pi.DoneChan <- nil break } if err != nil { pi.ErrorChan <- err } numProjects += len(projects) pi.ProjectsChan <- projects pi.ProgressChan <- Progress{ Page: resp.CurrentPage, Pages: resp.TotalPages, Projects: numProjects, TotalProjects: resp.TotalItems, } listOpts.Page = resp.NextPage } } // Returns a list of projects along with the next page and an error // if there was an error func (c *Client) ListProjects(opts *gitlab.ListProjectsOptions) ( []*Project, *gitlab.Response, error) { pList := make([]*Project, 0) projects, resp, err := c.gitlab.Projects.ListProjects( opts, gitlab.WithContext(c.Ctx), ) if err == nil { pList = append(pList, c.handleProjects(projects)...) } return pList, resp, err } func (c *Client) handleProjects(projects []*gitlab.Project) []*Project { // Opportunity to perform any filtering or additional lookups // on a per-project basis pList := make([]*Project, 0, len(projects)) for _, project := range projects { p := &Project{ ID: project.ID, Description: project.Description, SSHURLToRepo: project.SSHURLToRepo, HTTPURLToRepo: project.HTTPURLToRepo, WebURL: project.WebURL, Name: project.Name, NameWithNamespace: project.NameWithNamespace, Path: project.Path, PathWithNamespace: project.PathWithNamespace, LastActivityAt: *project.LastActivityAt, } pList = append(pList, p) } return pList } func NewGitlabClient(ctx context.Context, host, token string) (*Client, error) { client, err := gitlab.NewClient(token, gitlab.WithBaseURL(host)) if err != nil { return nil, err } gitlabClient := &Client{ Ctx: ctx, gitlab: client, } return gitlabClient, nil }