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 }