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