github.com/ianfoo/lab@v0.9.5-0.20180123060006-5ed79f2ccfc7/internal/gitlab/gitlab.go (about)

     1  // Package gitlab is an internal wrapper for the go-gitlab package
     2  //
     3  // Most functions serve to expose debug logging if set and accept a project
     4  // name string over an ID
     5  package gitlab
     6  
     7  import (
     8  	"fmt"
     9  	"io/ioutil"
    10  	"log"
    11  	"net/http"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  
    16  	"github.com/pkg/errors"
    17  	"github.com/xanzy/go-gitlab"
    18  	"github.com/zaquestion/lab/internal/git"
    19  )
    20  
    21  var (
    22  	ErrProjectNotFound = errors.New("gitlab project not found")
    23  )
    24  
    25  var (
    26  	lab  *gitlab.Client
    27  	host string
    28  	user string
    29  )
    30  
    31  // Host exposes the GitLab scheme://hostname used to interact with the API
    32  func Host() string {
    33  	return host
    34  }
    35  
    36  // User exposes the configured GitLab user
    37  func User() string {
    38  	return user
    39  }
    40  
    41  // Init initializes a gitlab client for use throughout lab.
    42  func Init(_host, _user, _token string) {
    43  	host = _host
    44  	user = _user
    45  	lab = gitlab.NewClient(nil, _token)
    46  	lab.SetBaseURL(host + "/api/v4")
    47  }
    48  
    49  // Defines filepath for default GitLab templates
    50  const (
    51  	TmplMR    = "merge_request_templates/default.md"
    52  	TmplIssue = "issue_templates/default.md"
    53  )
    54  
    55  // LoadGitLabTmpl loads gitlab templates for use in creating Issues and MRs
    56  //
    57  // https://gitlab.com/help/user/project/description_templates.md#setting-a-default-template-for-issues-and-merge-requests
    58  func LoadGitLabTmpl(tmplName string) string {
    59  	wd, err := git.WorkingDir()
    60  	if err != nil {
    61  		log.Fatal(err)
    62  	}
    63  
    64  	tmplFile := filepath.Join(wd, ".gitlab", tmplName)
    65  	f, err := os.Open(tmplFile)
    66  	if os.IsNotExist(err) {
    67  		return ""
    68  	} else if err != nil {
    69  		log.Fatal(err)
    70  	}
    71  
    72  	tmpl, err := ioutil.ReadAll(f)
    73  	if err != nil {
    74  		log.Fatal(err)
    75  	}
    76  
    77  	return string(tmpl[:len(tmpl)-1])
    78  }
    79  
    80  var (
    81  	localProjects map[string]*gitlab.Project = make(map[string]*gitlab.Project)
    82  )
    83  
    84  // FindProject looks up the Gitlab project. If the namespace is not provided in
    85  // the project string it will search for projects in the users namespace
    86  func FindProject(project string) (*gitlab.Project, error) {
    87  	if target, ok := localProjects[project]; ok {
    88  		return target, nil
    89  	}
    90  
    91  	search := project
    92  	// Assuming that a "/" in the project means its owned by an org
    93  	if !strings.Contains(project, "/") {
    94  		search = user + "/" + project
    95  	}
    96  
    97  	target, resp, err := lab.Projects.GetProject(search)
    98  	if resp.StatusCode == http.StatusNotFound {
    99  		return nil, ErrProjectNotFound
   100  	}
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	// fwiw, I feel bad about this
   105  	localProjects[project] = target
   106  
   107  	return target, nil
   108  }
   109  
   110  // Fork creates a user fork of a GitLab project
   111  func Fork(project string) (string, error) {
   112  	if !strings.Contains(project, "/") {
   113  		return "", errors.New("remote must include namespace")
   114  	}
   115  	parts := strings.Split(project, "/")
   116  
   117  	// See if a fork already exists
   118  	target, err := FindProject(parts[1])
   119  	if err == nil {
   120  		return target.SSHURLToRepo, nil
   121  	} else if err != nil && err != ErrProjectNotFound {
   122  		return "", err
   123  	}
   124  
   125  	target, err = FindProject(project)
   126  	if err != nil {
   127  		return "", err
   128  	}
   129  
   130  	fork, _, err := lab.Projects.ForkProject(target.ID)
   131  	if err != nil {
   132  		return "", err
   133  	}
   134  
   135  	return fork.SSHURLToRepo, nil
   136  }
   137  
   138  // MRCreate opens a merge request on GitLab
   139  func MRCreate(project string, opts *gitlab.CreateMergeRequestOptions) (string, error) {
   140  	p, err := FindProject(project)
   141  	if err != nil {
   142  		return "", err
   143  	}
   144  
   145  	mr, _, err := lab.MergeRequests.CreateMergeRequest(p.ID, opts)
   146  	if err != nil {
   147  		return "", err
   148  	}
   149  	return mr.WebURL, nil
   150  }
   151  
   152  // MRGet retrieves the merge request from GitLab project
   153  func MRGet(project string, mrNum int) (*gitlab.MergeRequest, error) {
   154  	p, err := FindProject(project)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	mr, _, err := lab.MergeRequests.GetMergeRequest(p.ID, mrNum)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	return mr, nil
   165  }
   166  
   167  // MRList lists the MRs on a GitLab project
   168  func MRList(project string, opts *gitlab.ListProjectMergeRequestsOptions) ([]*gitlab.MergeRequest, error) {
   169  	p, err := FindProject(project)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	list, _, err := lab.MergeRequests.ListProjectMergeRequests(p.ID, opts)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  	return list, nil
   179  }
   180  
   181  // MRClose closes an mr on a GitLab project
   182  func MRClose(pid interface{}, id int) error {
   183  	mr, _, err := lab.MergeRequests.GetMergeRequest(pid, id)
   184  	if err != nil {
   185  		return err
   186  	}
   187  	if mr.State == "closed" {
   188  		return fmt.Errorf("mr already closed")
   189  	}
   190  	_, _, err = lab.MergeRequests.UpdateMergeRequest(pid, int(id), &gitlab.UpdateMergeRequestOptions{
   191  		StateEvent: gitlab.String("close"),
   192  	})
   193  	if err != nil {
   194  		return err
   195  	}
   196  	return nil
   197  }
   198  
   199  // MRMerge merges an mr on a GitLab project
   200  func MRMerge(pid interface{}, id int) error {
   201  	_, _, err := lab.MergeRequests.AcceptMergeRequest(pid, int(id), &gitlab.AcceptMergeRequestOptions{
   202  		MergeWhenPipelineSucceeds: gitlab.Bool(true),
   203  	})
   204  	if err != nil {
   205  		return err
   206  	}
   207  	return nil
   208  }
   209  
   210  // IssueCreate opens a new issue on a GitLab Project
   211  func IssueCreate(project string, opts *gitlab.CreateIssueOptions) (string, error) {
   212  	p, err := FindProject(project)
   213  	if err != nil {
   214  		return "", err
   215  	}
   216  
   217  	mr, _, err := lab.Issues.CreateIssue(p.ID, opts)
   218  	if err != nil {
   219  		return "", err
   220  	}
   221  	return mr.WebURL, nil
   222  }
   223  
   224  // IssueGet retrieves the issue information from a GitLab project
   225  func IssueGet(project string, issueNum int) (*gitlab.Issue, error) {
   226  	p, err := FindProject(project)
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  
   231  	issue, _, err := lab.Issues.GetIssue(p.ID, issueNum)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	return issue, nil
   237  }
   238  
   239  // IssueList gets a list of issues on a GitLab Project
   240  func IssueList(project string, opts *gitlab.ListProjectIssuesOptions) ([]*gitlab.Issue, error) {
   241  	p, err := FindProject(project)
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  
   246  	list, _, err := lab.Issues.ListProjectIssues(p.ID, opts)
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  	return list, nil
   251  }
   252  
   253  // IssueClose closes an issue on a GitLab project
   254  func IssueClose(pid interface{}, id int) error {
   255  	_, err := lab.Issues.DeleteIssue(pid, int(id))
   256  	if err != nil {
   257  		return err
   258  	}
   259  	return nil
   260  }
   261  
   262  // BranchPushed checks if a branch exists on a GitLab project
   263  func BranchPushed(pid interface{}, branch string) bool {
   264  	b, _, err := lab.Branches.GetBranch(pid, branch)
   265  	if err != nil {
   266  		return false
   267  	}
   268  	return b != nil
   269  }
   270  
   271  // ProjectSnippetCreate creates a snippet in a project
   272  func ProjectSnippetCreate(pid interface{}, opts *gitlab.CreateProjectSnippetOptions) (*gitlab.Snippet, error) {
   273  	snip, _, err := lab.ProjectSnippets.CreateSnippet(pid, opts)
   274  	if err != nil {
   275  		return nil, err
   276  	}
   277  
   278  	return snip, nil
   279  }
   280  
   281  // ProjectSnippetDelete deletes a project snippet
   282  func ProjectSnippetDelete(pid interface{}, id int) error {
   283  	_, err := lab.ProjectSnippets.DeleteSnippet(pid, id)
   284  	return err
   285  }
   286  
   287  // ProjectSnippetList lists snippets on a project
   288  func ProjectSnippetList(pid interface{}, opts *gitlab.ListProjectSnippetsOptions) ([]*gitlab.Snippet, error) {
   289  	snips, _, err := lab.ProjectSnippets.ListSnippets(pid, opts)
   290  	if err != nil {
   291  		return nil, err
   292  	}
   293  	return snips, nil
   294  }
   295  
   296  // SnippetCreate creates a personal snippet
   297  func SnippetCreate(opts *gitlab.CreateSnippetOptions) (*gitlab.Snippet, error) {
   298  	snip, _, err := lab.Snippets.CreateSnippet(opts)
   299  	if err != nil {
   300  		return nil, err
   301  	}
   302  
   303  	return snip, nil
   304  }
   305  
   306  // SnippetDelete deletes a personal snippet
   307  func SnippetDelete(id int) error {
   308  	_, err := lab.Snippets.DeleteSnippet(id)
   309  	return err
   310  }
   311  
   312  // SnippetList lists snippets on a project
   313  func SnippetList(opts *gitlab.ListSnippetsOptions) ([]*gitlab.Snippet, error) {
   314  	snips, _, err := lab.Snippets.ListSnippets(opts)
   315  	if err != nil {
   316  		return nil, err
   317  	}
   318  	return snips, nil
   319  }
   320  
   321  // Lint validates .gitlab-ci.yml contents
   322  func Lint(content string) (bool, error) {
   323  	lint, _, err := lab.Validate.Lint(content)
   324  	if err != nil {
   325  		return false, err
   326  	}
   327  	if len(lint.Errors) > 0 {
   328  		return false, errors.New(strings.Join(lint.Errors, " - "))
   329  	}
   330  	return lint.Status == "valid", nil
   331  }