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 }