github.com/scorpionis/hub@v2.2.1+incompatible/github/client.go (about)

     1  package github
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/url"
     7  	"os"
     8  	"os/user"
     9  	"strings"
    10  
    11  	"github.com/github/hub/Godeps/_workspace/src/github.com/octokit/go-octokit/octokit"
    12  )
    13  
    14  const (
    15  	GitHubHost    string = "github.com"
    16  	GitHubApiHost string = "api.github.com"
    17  	UserAgent     string = "Hub"
    18  	OAuthAppURL   string = "http://hub.github.com/"
    19  )
    20  
    21  func NewClient(h string) *Client {
    22  	return NewClientWithHost(&Host{Host: h})
    23  }
    24  
    25  func NewClientWithHost(host *Host) *Client {
    26  	return &Client{host}
    27  }
    28  
    29  type AuthError struct {
    30  	Err error
    31  }
    32  
    33  func (e *AuthError) Error() string {
    34  	return e.Err.Error()
    35  }
    36  
    37  func (e *AuthError) IsRequired2FACodeError() bool {
    38  	re, ok := e.Err.(*octokit.ResponseError)
    39  	return ok && re.Type == octokit.ErrorOneTimePasswordRequired
    40  }
    41  
    42  func (e *AuthError) IsDuplicatedTokenError() bool {
    43  	re, ok := e.Err.(*octokit.ResponseError)
    44  	return ok && re.Type == octokit.ErrorUnprocessableEntity
    45  }
    46  
    47  type Client struct {
    48  	Host *Host
    49  }
    50  
    51  func (client *Client) PullRequest(project *Project, id string) (pr *octokit.PullRequest, err error) {
    52  	url, err := octokit.PullRequestsURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name, "number": id})
    53  	if err != nil {
    54  		return
    55  	}
    56  
    57  	api, err := client.api()
    58  	if err != nil {
    59  		err = FormatError("getting pull request", err)
    60  		return
    61  	}
    62  
    63  	pr, result := api.PullRequests(client.requestURL(url)).One()
    64  	if result.HasError() {
    65  		err = FormatError("getting pull request", result.Err)
    66  		return
    67  	}
    68  
    69  	return
    70  }
    71  
    72  func (client *Client) PullRequestPatch(project *Project, id string) (patch io.ReadCloser, err error) {
    73  	url, err := octokit.PullRequestsURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name, "number": id})
    74  	if err != nil {
    75  		return
    76  	}
    77  
    78  	api, err := client.api()
    79  	if err != nil {
    80  		err = FormatError("getting pull request", err)
    81  		return
    82  	}
    83  
    84  	patch, result := api.PullRequests(client.requestURL(url)).Patch()
    85  	if result.HasError() {
    86  		err = FormatError("getting pull request", result.Err)
    87  		return
    88  	}
    89  
    90  	return
    91  }
    92  
    93  func (client *Client) CreatePullRequest(project *Project, base, head, title, body string) (pr *octokit.PullRequest, err error) {
    94  	url, err := octokit.PullRequestsURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name})
    95  	if err != nil {
    96  		return
    97  	}
    98  
    99  	api, err := client.api()
   100  	if err != nil {
   101  		err = FormatError("creating pull request", err)
   102  		return
   103  	}
   104  
   105  	params := octokit.PullRequestParams{Base: base, Head: head, Title: title, Body: body}
   106  	pr, result := api.PullRequests(client.requestURL(url)).Create(params)
   107  	if result.HasError() {
   108  		err = FormatError("creating pull request", result.Err)
   109  		if e := warnExistenceOfRepo(project, result.Err); e != nil {
   110  			err = fmt.Errorf("%s\n%s", err, e)
   111  		}
   112  
   113  		return
   114  	}
   115  
   116  	return
   117  }
   118  
   119  func (client *Client) CreatePullRequestForIssue(project *Project, base, head, issue string) (pr *octokit.PullRequest, err error) {
   120  	url, err := octokit.PullRequestsURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name})
   121  	if err != nil {
   122  		return
   123  	}
   124  
   125  	api, err := client.api()
   126  	if err != nil {
   127  		err = FormatError("creating pull request", err)
   128  		return
   129  	}
   130  
   131  	params := octokit.PullRequestForIssueParams{Base: base, Head: head, Issue: issue}
   132  	pr, result := api.PullRequests(client.requestURL(url)).Create(params)
   133  	if result.HasError() {
   134  		err = FormatError("creating pull request", result.Err)
   135  		if e := warnExistenceOfRepo(project, result.Err); e != nil {
   136  			err = fmt.Errorf("%s\n%s", err, e)
   137  		}
   138  
   139  		return
   140  	}
   141  
   142  	return
   143  }
   144  
   145  func (client *Client) CommitPatch(project *Project, sha string) (patch io.ReadCloser, err error) {
   146  	url, err := octokit.CommitsURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name, "sha": sha})
   147  	if err != nil {
   148  		return
   149  	}
   150  
   151  	api, err := client.api()
   152  	if err != nil {
   153  		err = FormatError("getting pull request", err)
   154  		return
   155  	}
   156  
   157  	patch, result := api.Commits(client.requestURL(url)).Patch()
   158  	if result.HasError() {
   159  		err = FormatError("getting pull request", result.Err)
   160  		return
   161  	}
   162  
   163  	return
   164  }
   165  
   166  func (client *Client) GistPatch(id string) (patch io.ReadCloser, err error) {
   167  	url, err := octokit.GistsURL.Expand(octokit.M{"gist_id": id})
   168  	if err != nil {
   169  		return
   170  	}
   171  
   172  	api, err := client.api()
   173  	if err != nil {
   174  		err = FormatError("getting pull request", err)
   175  		return
   176  	}
   177  
   178  	patch, result := api.Gists(client.requestURL(url)).Raw()
   179  	if result.HasError() {
   180  		err = FormatError("getting pull request", result.Err)
   181  		return
   182  	}
   183  
   184  	return
   185  }
   186  
   187  func (client *Client) Repository(project *Project) (repo *octokit.Repository, err error) {
   188  	url, err := octokit.RepositoryURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name})
   189  	if err != nil {
   190  		return
   191  	}
   192  
   193  	api, err := client.api()
   194  	if err != nil {
   195  		err = FormatError("getting repository", err)
   196  		return
   197  	}
   198  
   199  	repo, result := api.Repositories(client.requestURL(url)).One()
   200  	if result.HasError() {
   201  		err = FormatError("getting repository", result.Err)
   202  		return
   203  	}
   204  
   205  	return
   206  }
   207  
   208  func (client *Client) IsRepositoryExist(project *Project) bool {
   209  	repo, err := client.Repository(project)
   210  
   211  	return err == nil && repo != nil
   212  }
   213  
   214  func (client *Client) CreateRepository(project *Project, description, homepage string, isPrivate bool) (repo *octokit.Repository, err error) {
   215  	var repoURL octokit.Hyperlink
   216  	if project.Owner != client.Host.User {
   217  		repoURL = octokit.OrgRepositoriesURL
   218  	} else {
   219  		repoURL = octokit.UserRepositoriesURL
   220  	}
   221  
   222  	url, err := repoURL.Expand(octokit.M{"org": project.Owner})
   223  	if err != nil {
   224  		return
   225  	}
   226  
   227  	api, err := client.api()
   228  	if err != nil {
   229  		err = FormatError("creating repository", err)
   230  		return
   231  	}
   232  
   233  	params := octokit.Repository{
   234  		Name:        project.Name,
   235  		Description: description,
   236  		Homepage:    homepage,
   237  		Private:     isPrivate,
   238  	}
   239  	repo, result := api.Repositories(client.requestURL(url)).Create(params)
   240  	if result.HasError() {
   241  		err = FormatError("creating repository", result.Err)
   242  		return
   243  	}
   244  
   245  	return
   246  }
   247  
   248  func (client *Client) Releases(project *Project) (releases []octokit.Release, err error) {
   249  	url, err := octokit.ReleasesURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name})
   250  	if err != nil {
   251  		return
   252  	}
   253  
   254  	api, err := client.api()
   255  	if err != nil {
   256  		err = FormatError("getting release", err)
   257  		return
   258  	}
   259  
   260  	releases, result := api.Releases(client.requestURL(url)).All()
   261  	if result.HasError() {
   262  		err = FormatError("getting release", result.Err)
   263  		return
   264  	}
   265  
   266  	return
   267  }
   268  
   269  func (client *Client) Release(project *Project, tagName string) (release *octokit.Release, err error) {
   270  	url, err := octokit.ReleasesURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name})
   271  	if err != nil {
   272  		return
   273  	}
   274  
   275  	api, err := client.api()
   276  	if err != nil {
   277  		err = FormatError("getting release", err)
   278  		return
   279  	}
   280  
   281  	releases, result := api.Releases(client.requestURL(url)).All()
   282  	if result.HasError() {
   283  		err = FormatError("creating release", result.Err)
   284  		return
   285  	}
   286  
   287  	for _, release := range releases {
   288  		if release.TagName == tagName {
   289  			return &release, nil
   290  		}
   291  	}
   292  
   293  	return
   294  }
   295  
   296  func (client *Client) CreateRelease(project *Project, params octokit.ReleaseParams) (release *octokit.Release, err error) {
   297  	url, err := octokit.ReleasesURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name})
   298  	if err != nil {
   299  		return
   300  	}
   301  
   302  	api, err := client.api()
   303  	if err != nil {
   304  		err = FormatError("creating release", err)
   305  		return
   306  	}
   307  
   308  	release, result := api.Releases(client.requestURL(url)).Create(params)
   309  	if result.HasError() {
   310  		err = FormatError("creating release", result.Err)
   311  		return
   312  	}
   313  
   314  	return
   315  }
   316  
   317  func (client *Client) UploadReleaseAsset(uploadUrl *url.URL, asset *os.File, contentType string) (err error) {
   318  	fileInfo, err := asset.Stat()
   319  	if err != nil {
   320  		return
   321  	}
   322  
   323  	api, err := client.api()
   324  	if err != nil {
   325  		err = FormatError("uploading asset", err)
   326  		return
   327  	}
   328  
   329  	result := api.Uploads(uploadUrl).UploadAsset(asset, contentType, fileInfo.Size())
   330  	if result.HasError() {
   331  		err = FormatError("uploading asset", result.Err)
   332  		return
   333  	}
   334  
   335  	return
   336  }
   337  
   338  func (client *Client) CIStatus(project *Project, sha string) (status *octokit.Status, err error) {
   339  	url, err := octokit.StatusesURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name, "ref": sha})
   340  	if err != nil {
   341  		return
   342  	}
   343  
   344  	api, err := client.api()
   345  	if err != nil {
   346  		err = FormatError("getting CI status", err)
   347  		return
   348  	}
   349  
   350  	statuses, result := api.Statuses(client.requestURL(url)).All()
   351  	if result.HasError() {
   352  		err = FormatError("getting CI status", result.Err)
   353  		return
   354  	}
   355  
   356  	if len(statuses) > 0 {
   357  		status = &statuses[0]
   358  	}
   359  
   360  	return
   361  }
   362  
   363  func (client *Client) ForkRepository(project *Project) (repo *octokit.Repository, err error) {
   364  	url, err := octokit.ForksURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name})
   365  	if err != nil {
   366  		return
   367  	}
   368  
   369  	api, err := client.api()
   370  	if err != nil {
   371  		err = FormatError("creating fork", err)
   372  		return
   373  	}
   374  
   375  	repo, result := api.Repositories(client.requestURL(url)).Create(nil)
   376  	if result.HasError() {
   377  		err = FormatError("creating fork", result.Err)
   378  		return
   379  	}
   380  
   381  	return
   382  }
   383  
   384  func (client *Client) Issues(project *Project) (issues []octokit.Issue, err error) {
   385  	url, err := octokit.RepoIssuesURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name})
   386  	if err != nil {
   387  		return
   388  	}
   389  
   390  	api, err := client.api()
   391  	if err != nil {
   392  		err = FormatError("getting issues", err)
   393  		return
   394  	}
   395  
   396  	issues, result := api.Issues(client.requestURL(url)).All()
   397  	if result.HasError() {
   398  		err = FormatError("getting issues", result.Err)
   399  		return
   400  	}
   401  
   402  	return
   403  }
   404  
   405  func (client *Client) CreateIssue(project *Project, title, body string, labels []string) (issue *octokit.Issue, err error) {
   406  	url, err := octokit.RepoIssuesURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name})
   407  	if err != nil {
   408  		return
   409  	}
   410  
   411  	api, err := client.api()
   412  	if err != nil {
   413  		err = FormatError("creating issues", err)
   414  		return
   415  	}
   416  
   417  	params := octokit.IssueParams{
   418  		Title:  title,
   419  		Body:   body,
   420  		Labels: labels,
   421  	}
   422  	issue, result := api.Issues(client.requestURL(url)).Create(params)
   423  	if result.HasError() {
   424  		err = FormatError("creating issue", result.Err)
   425  		return
   426  	}
   427  
   428  	return
   429  }
   430  
   431  func (client *Client) GhLatestTagName() (tagName string, err error) {
   432  	url, err := octokit.ReleasesURL.Expand(octokit.M{"owner": "jingweno", "repo": "gh"})
   433  	if err != nil {
   434  		return
   435  	}
   436  
   437  	c := client.newOctokitClient(nil)
   438  	releases, result := c.Releases(client.requestURL(url)).All()
   439  	if result.HasError() {
   440  		err = fmt.Errorf("Error getting gh release: %s", result.Err)
   441  		return
   442  	}
   443  
   444  	if len(releases) == 0 {
   445  		err = fmt.Errorf("No gh release is available")
   446  		return
   447  	}
   448  
   449  	tagName = releases[0].TagName
   450  
   451  	return
   452  }
   453  
   454  func (client *Client) CurrentUser() (user *octokit.User, err error) {
   455  	url, err := octokit.CurrentUserURL.Expand(nil)
   456  	if err != nil {
   457  		return
   458  	}
   459  
   460  	api, err := client.api()
   461  	if err != nil {
   462  		err = FormatError("getting current user", err)
   463  		return
   464  	}
   465  
   466  	user, result := api.Users(client.requestURL(url)).One()
   467  	if result.HasError() {
   468  		err = FormatError("getting current user", result.Err)
   469  		return
   470  	}
   471  
   472  	return
   473  }
   474  
   475  func (client *Client) FindOrCreateToken(user, password, twoFactorCode string) (token string, err error) {
   476  	authUrl, e := octokit.AuthorizationsURL.Expand(nil)
   477  	if e != nil {
   478  		err = &AuthError{e}
   479  		return
   480  	}
   481  
   482  	basicAuth := octokit.BasicAuth{
   483  		Login:           user,
   484  		Password:        password,
   485  		OneTimePassword: twoFactorCode,
   486  	}
   487  	c := client.newOctokitClient(basicAuth)
   488  	authsService := c.Authorizations(client.requestURL(authUrl))
   489  
   490  	authParam := octokit.AuthorizationParams{
   491  		Scopes:  []string{"repo"},
   492  		NoteURL: OAuthAppURL,
   493  	}
   494  
   495  	count := 1
   496  	for {
   497  		note, e := authTokenNote(count)
   498  		if e != nil {
   499  			err = e
   500  			return
   501  		}
   502  
   503  		authParam.Note = note
   504  		auth, result := authsService.Create(authParam)
   505  		if !result.HasError() {
   506  			token = auth.Token
   507  			break
   508  		}
   509  
   510  		authErr := &AuthError{result.Err}
   511  		if authErr.IsDuplicatedTokenError() {
   512  			if count >= 9 {
   513  				err = authErr
   514  				break
   515  			} else {
   516  				count++
   517  				continue
   518  			}
   519  		} else {
   520  			err = authErr
   521  			break
   522  		}
   523  	}
   524  
   525  	return
   526  }
   527  
   528  func (client *Client) api() (c *octokit.Client, err error) {
   529  	if client.Host.AccessToken == "" {
   530  		host, e := CurrentConfig().PromptForHost(client.Host.Host)
   531  		if e != nil {
   532  			err = e
   533  			return
   534  		}
   535  		client.Host = host
   536  	}
   537  
   538  	tokenAuth := octokit.TokenAuth{AccessToken: client.Host.AccessToken}
   539  	c = client.newOctokitClient(tokenAuth)
   540  
   541  	return
   542  }
   543  
   544  func (client *Client) newOctokitClient(auth octokit.AuthMethod) *octokit.Client {
   545  	var host string
   546  	if client.Host != nil {
   547  		host = client.Host.Host
   548  	}
   549  	host = normalizeHost(host)
   550  	apiHostURL := client.absolute(host)
   551  
   552  	httpClient := newHttpClient(os.Getenv("HUB_TEST_HOST"), os.Getenv("HUB_VERBOSE") != "")
   553  	c := octokit.NewClientWith(apiHostURL.String(), UserAgent, auth, httpClient)
   554  
   555  	return c
   556  }
   557  
   558  func (client *Client) absolute(endpoint string) *url.URL {
   559  	u, _ := url.Parse(endpoint)
   560  	if u.Scheme == "" && client.Host != nil {
   561  		u.Scheme = client.Host.Protocol
   562  	}
   563  	if u.Scheme == "" {
   564  		u.Scheme = "https"
   565  	}
   566  
   567  	return u
   568  }
   569  
   570  func (client *Client) requestURL(u *url.URL) (uu *url.URL) {
   571  	uu = u
   572  	if client.Host != nil && client.Host.Host != GitHubHost {
   573  		uu, _ = url.Parse(fmt.Sprintf("/api/v3/%s", u.Path))
   574  	}
   575  
   576  	return
   577  }
   578  
   579  func normalizeHost(host string) string {
   580  	host = strings.ToLower(host)
   581  	if host == "" {
   582  		host = GitHubHost
   583  	}
   584  
   585  	if host == GitHubHost {
   586  		host = GitHubApiHost
   587  	}
   588  
   589  	return host
   590  }
   591  
   592  func FormatError(action string, err error) (ee error) {
   593  	switch e := err.(type) {
   594  	default:
   595  		ee = err
   596  	case *AuthError:
   597  		return FormatError(action, e.Err)
   598  	case *octokit.ResponseError:
   599  		statusCode := e.Response.StatusCode
   600  		var reason string
   601  		if s := strings.SplitN(e.Response.Status, " ", 2); len(s) >= 2 {
   602  			reason = strings.TrimSpace(s[1])
   603  		}
   604  
   605  		errStr := fmt.Sprintf("Error %s: %s (HTTP %d)", action, reason, statusCode)
   606  
   607  		var errorSentences []string
   608  		for _, err := range e.Errors {
   609  			switch err.Code {
   610  			case "custom":
   611  				errorSentences = append(errorSentences, err.Message)
   612  			case "missing_field":
   613  				errorSentences = append(errorSentences, fmt.Sprintf("Missing field: \"%s\"", err.Field))
   614  			case "already_exists":
   615  				errorSentences = append(errorSentences, fmt.Sprintf("Duplicate value for \"%s\"", err.Field))
   616  			case "invalid":
   617  				errorSentences = append(errorSentences, fmt.Sprintf("Invalid value for \"%s\"", err.Field))
   618  			case "unauthorized":
   619  				errorSentences = append(errorSentences, fmt.Sprintf("Not allowed to change field \"%s\"", err.Field))
   620  			}
   621  		}
   622  
   623  		var errorMessage string
   624  		if len(errorSentences) > 0 {
   625  			errorMessage = strings.Join(errorSentences, "\n")
   626  		} else {
   627  			errorMessage = e.Message
   628  		}
   629  
   630  		if errorMessage != "" {
   631  			errStr = fmt.Sprintf("%s\n%s", errStr, errorMessage)
   632  		}
   633  
   634  		ee = fmt.Errorf(errStr)
   635  	}
   636  
   637  	return
   638  }
   639  
   640  func warnExistenceOfRepo(project *Project, ee error) (err error) {
   641  	if e, ok := ee.(*octokit.ResponseError); ok && e.Response.StatusCode == 404 {
   642  		var url string
   643  		if s := strings.SplitN(project.WebURL("", "", ""), "://", 2); len(s) >= 2 {
   644  			url = s[1]
   645  		}
   646  		if url != "" {
   647  			err = fmt.Errorf("Are you sure that %s exists?", url)
   648  		}
   649  	}
   650  
   651  	return
   652  }
   653  
   654  func authTokenNote(num int) (string, error) {
   655  	u, err := user.Current()
   656  	if err != nil {
   657  		return "", err
   658  	}
   659  
   660  	n := u.Username
   661  	h, err := os.Hostname()
   662  	if err != nil {
   663  		return "", err
   664  	}
   665  
   666  	if num > 1 {
   667  		return fmt.Sprintf("hub for %s@%s %d", n, h, num), nil
   668  	}
   669  
   670  	return fmt.Sprintf("hub for %s@%s", n, h), nil
   671  }