github.com/jingweno/gh@v2.1.1-0.20221007190738-04a7985fa9a1+incompatible/github/client.go (about)

     1  package github
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"os"
     7  
     8  	"github.com/jingweno/go-octokit/octokit"
     9  )
    10  
    11  const (
    12  	GitHubHost    string = "github.com"
    13  	GitHubApiHost string = "api.github.com"
    14  	OAuthAppURL   string = "http://owenou.com/gh"
    15  )
    16  
    17  type ClientError struct {
    18  	error
    19  }
    20  
    21  func (e *ClientError) Error() string {
    22  	return e.error.Error()
    23  }
    24  
    25  func (e *ClientError) Is2FAError() bool {
    26  	re, ok := e.error.(*octokit.ResponseError)
    27  	return ok && re.Type == octokit.ErrorOneTimePasswordRequired
    28  }
    29  
    30  type Client struct {
    31  	Credentials *Credentials
    32  }
    33  
    34  func (client *Client) PullRequest(project *Project, id string) (pr *octokit.PullRequest, err error) {
    35  	url, err := octokit.PullRequestsURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name, "number": id})
    36  	if err != nil {
    37  		return
    38  	}
    39  
    40  	pr, result := client.octokit().PullRequests(client.requestURL(url)).One()
    41  	if result.HasError() {
    42  		err = fmt.Errorf("Error getting pull request: %s", result.Err)
    43  	}
    44  
    45  	return
    46  }
    47  
    48  func (client *Client) CreatePullRequest(project *Project, base, head, title, body string) (pr *octokit.PullRequest, err error) {
    49  	url, err := octokit.PullRequestsURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name})
    50  	if err != nil {
    51  		return
    52  	}
    53  
    54  	params := octokit.PullRequestParams{Base: base, Head: head, Title: title, Body: body}
    55  	pr, result := client.octokit().PullRequests(client.requestURL(url)).Create(params)
    56  	if result.HasError() {
    57  		err = fmt.Errorf("Error creating pull request: %s", result.Err)
    58  	}
    59  
    60  	return
    61  }
    62  
    63  func (client *Client) CreatePullRequestForIssue(project *Project, base, head, issue string) (pr *octokit.PullRequest, err error) {
    64  	url, err := octokit.PullRequestsURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name})
    65  	if err != nil {
    66  		return
    67  	}
    68  
    69  	params := octokit.PullRequestForIssueParams{Base: base, Head: head, Issue: issue}
    70  	pr, result := client.octokit().PullRequests(client.requestURL(url)).Create(params)
    71  	if result.HasError() {
    72  		err = fmt.Errorf("Error creating pull request: %s", result.Err)
    73  	}
    74  
    75  	return
    76  }
    77  
    78  func (client *Client) Repository(project *Project) (repo *octokit.Repository, err error) {
    79  	url, err := octokit.RepositoryURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name})
    80  	if err != nil {
    81  		return
    82  	}
    83  
    84  	repo, result := client.octokit().Repositories(client.requestURL(url)).One()
    85  	if result.HasError() {
    86  		err = fmt.Errorf("Error getting repository: %s", result.Err)
    87  	}
    88  
    89  	return
    90  }
    91  
    92  func (client *Client) IsRepositoryExist(project *Project) bool {
    93  	repo, err := client.Repository(project)
    94  
    95  	return err == nil && repo != nil
    96  }
    97  
    98  func (client *Client) CreateRepository(project *Project, description, homepage string, isPrivate bool) (repo *octokit.Repository, err error) {
    99  	var repoURL octokit.Hyperlink
   100  	if project.Owner != client.Credentials.User {
   101  		repoURL = octokit.OrgRepositoriesURL
   102  	} else {
   103  		repoURL = octokit.UserRepositoriesURL
   104  	}
   105  
   106  	url, err := repoURL.Expand(octokit.M{"org": project.Owner})
   107  	if err != nil {
   108  		return
   109  	}
   110  
   111  	params := octokit.Repository{
   112  		Name:        project.Name,
   113  		Description: description,
   114  		Homepage:    homepage,
   115  		Private:     isPrivate,
   116  	}
   117  	repo, result := client.octokit().Repositories(client.requestURL(url)).Create(params)
   118  	if result.HasError() {
   119  		if result.Response == nil || result.Response.StatusCode == 500 {
   120  			err = fmt.Errorf("Error creating repository: Internal Server Error (HTTP 500)")
   121  		} else {
   122  			err = fmt.Errorf("Error creating repository: %v", result.Err)
   123  		}
   124  	}
   125  
   126  	return
   127  }
   128  
   129  func (client *Client) Releases(project *Project) (releases []octokit.Release, err error) {
   130  	url, err := octokit.ReleasesURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name})
   131  	if err != nil {
   132  		return
   133  	}
   134  
   135  	releases, result := client.octokit().Releases(client.requestURL(url)).All()
   136  	if result.HasError() {
   137  		err = fmt.Errorf("Error getting release: %s", result.Err)
   138  	}
   139  
   140  	return
   141  }
   142  
   143  func (client *Client) CreateRelease(project *Project, params octokit.ReleaseParams) (release *octokit.Release, err error) {
   144  	url, err := octokit.ReleasesURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name})
   145  	if err != nil {
   146  		return
   147  	}
   148  
   149  	release, result := client.octokit().Releases(client.requestURL(url)).Create(params)
   150  	if result.HasError() {
   151  		err = fmt.Errorf("Error creating release: %s", result.Err)
   152  	}
   153  
   154  	return
   155  }
   156  
   157  func (client *Client) UploadReleaseAsset(uploadUrl *url.URL, asset *os.File, contentType string) (err error) {
   158  	c := client.octokit()
   159  	fileInfo, err := asset.Stat()
   160  	if err != nil {
   161  		return
   162  	}
   163  
   164  	result := c.Uploads(uploadUrl).UploadAsset(asset, contentType, fileInfo.Size())
   165  	if result.HasError() {
   166  		err = fmt.Errorf("Error uploading asset: %s", result.Err)
   167  	}
   168  	return
   169  }
   170  
   171  func (client *Client) CIStatus(project *Project, sha string) (status *octokit.Status, err error) {
   172  	url, err := octokit.StatusesURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name, "ref": sha})
   173  	if err != nil {
   174  		return
   175  	}
   176  
   177  	statuses, result := client.octokit().Statuses(client.requestURL(url)).All()
   178  	if result.HasError() {
   179  		err = fmt.Errorf("Error getting CI status: %s", result.Err)
   180  		return
   181  	}
   182  
   183  	if len(statuses) > 0 {
   184  		status = &statuses[0]
   185  	}
   186  
   187  	return
   188  }
   189  
   190  func (client *Client) ForkRepository(project *Project) (repo *octokit.Repository, err error) {
   191  	url, err := octokit.ForksURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name})
   192  	if err != nil {
   193  		return
   194  	}
   195  
   196  	repo, result := client.octokit().Repositories(client.requestURL(url)).Create(nil)
   197  	if result.HasError() {
   198  		err = fmt.Errorf("Error forking repository: %s", result.Err)
   199  	}
   200  
   201  	return
   202  }
   203  
   204  func (client *Client) Issues(project *Project) (issues []octokit.Issue, err error) {
   205  	var result *octokit.Result
   206  
   207  	err = client.issuesService(project, func(service *octokit.IssuesService) error {
   208  		issues, result = service.All()
   209  		return resultError(result)
   210  	})
   211  
   212  	return
   213  }
   214  
   215  func (client *Client) CreateIssue(project *Project, title, body string, labels []string) (issue *octokit.Issue, err error) {
   216  	params := octokit.IssueParams{
   217  		Title:  title,
   218  		Body:   body,
   219  		Labels: labels,
   220  	}
   221  
   222  	var result *octokit.Result
   223  
   224  	err = client.issuesService(project, func(service *octokit.IssuesService) error {
   225  		issue, result = service.Create(params)
   226  		return resultError(result)
   227  	})
   228  
   229  	return
   230  }
   231  
   232  func (client *Client) GhLatestTagName() (tagName string, err error) {
   233  	url, err := octokit.ReleasesURL.Expand(octokit.M{"owner": "jingweno", "repo": "gh"})
   234  	if err != nil {
   235  		return
   236  	}
   237  
   238  	c := octokit.NewClientWith(client.apiEndpoint(), nil, nil)
   239  	releases, result := c.Releases(client.requestURL(url)).All()
   240  	if result.HasError() {
   241  		err = fmt.Errorf("Error getting gh release: %s", result.Err)
   242  		return
   243  	}
   244  
   245  	if len(releases) == 0 {
   246  		err = fmt.Errorf("No gh release is available")
   247  		return
   248  	}
   249  
   250  	tagName = releases[0].TagName
   251  
   252  	return
   253  }
   254  
   255  func (client *Client) FindOrCreateToken(user, password, twoFactorCode string) (token string, err error) {
   256  	url, e := octokit.AuthorizationsURL.Expand(nil)
   257  	if e != nil {
   258  		err = &ClientError{e}
   259  		return
   260  	}
   261  
   262  	basicAuth := octokit.BasicAuth{Login: user, Password: password, OneTimePassword: twoFactorCode}
   263  	c := octokit.NewClientWith(client.apiEndpoint(), nil, basicAuth)
   264  	authsService := c.Authorizations(client.requestURL(url))
   265  
   266  	if twoFactorCode != "" {
   267  		// dummy request to trigger a 2FA SMS since a HTTP GET won't do it
   268  		authsService.Create(nil)
   269  	}
   270  
   271  	auths, result := authsService.All()
   272  	if result.HasError() {
   273  		err = &ClientError{result.Err}
   274  		return
   275  	}
   276  
   277  	for _, auth := range auths {
   278  		if auth.App.URL == OAuthAppURL {
   279  			token = auth.Token
   280  			break
   281  		}
   282  	}
   283  
   284  	if token == "" {
   285  		authParam := octokit.AuthorizationParams{}
   286  		authParam.Scopes = append(authParam.Scopes, "repo")
   287  		authParam.Note = "gh"
   288  		authParam.NoteURL = OAuthAppURL
   289  
   290  		auth, result := authsService.Create(authParam)
   291  		if result.HasError() {
   292  			err = &ClientError{result.Err}
   293  			return
   294  		}
   295  
   296  		token = auth.Token
   297  	}
   298  
   299  	return
   300  }
   301  
   302  func (client *Client) octokit() (c *octokit.Client) {
   303  	tokenAuth := octokit.TokenAuth{AccessToken: client.Credentials.AccessToken}
   304  	c = octokit.NewClientWith(client.apiEndpoint(), nil, tokenAuth)
   305  
   306  	return
   307  }
   308  
   309  func (client *Client) requestURL(u *url.URL) (uu *url.URL) {
   310  	uu = u
   311  	if client.Credentials != nil && client.Credentials.Host != GitHubHost {
   312  		uu, _ = url.Parse(fmt.Sprintf("/api/v3/%s", u.Path))
   313  	}
   314  
   315  	return
   316  }
   317  
   318  func (client *Client) apiEndpoint() string {
   319  	host := os.Getenv("GH_API_HOST")
   320  	if host == "" && client.Credentials != nil {
   321  		host = client.Credentials.Host
   322  	}
   323  
   324  	if host == GitHubHost {
   325  		host = GitHubApiHost
   326  	}
   327  
   328  	return absolute(host)
   329  }
   330  
   331  func absolute(endpoint string) string {
   332  	u, _ := url.Parse(endpoint)
   333  	if u.Scheme == "" {
   334  		u.Scheme = "https"
   335  	}
   336  
   337  	return u.String()
   338  }
   339  
   340  func NewClient(host string) *Client {
   341  	c := CurrentConfigs().PromptFor(host)
   342  	return &Client{Credentials: c}
   343  }
   344  
   345  func (client *Client) issuesService(project *Project, fn func(service *octokit.IssuesService) error) (err error) {
   346  	url, err := octokit.RepoIssuesURL.Expand(octokit.M{"owner": project.Owner, "repo": project.Name})
   347  	if err != nil {
   348  		return
   349  	}
   350  
   351  	service := client.octokit().Issues(client.requestURL(url))
   352  	return fn(service)
   353  }
   354  
   355  func resultError(result *octokit.Result) (err error) {
   356  	if result != nil && result.HasError() {
   357  		err = result.Err
   358  	}
   359  	return
   360  }