зеркало из https://github.com/mislav/hub.git
315 строки
6.5 KiB
Go
315 строки
6.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/howeyc/gopass"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
)
|
|
|
|
const (
|
|
GitHubUrl string = "https://" + GitHubHost
|
|
GitHubHost string = "api.github.com"
|
|
OAuthAppUrl string = "http://owenou.com/gh"
|
|
)
|
|
|
|
type GitHubError struct {
|
|
Resource string `json:"resource"`
|
|
Field string `json:"field"`
|
|
Value string `json:"value"`
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
type GitHubErrors struct {
|
|
Message string `json:"message"`
|
|
Errors []GitHubError `json:"errors"`
|
|
}
|
|
|
|
type App struct {
|
|
Url string `json:"url"`
|
|
Name string `json:"name"`
|
|
ClientId string `json:"client_id"`
|
|
}
|
|
|
|
type Authorization struct {
|
|
Scopes []string `json:"scopes"`
|
|
Url string `json:"url"`
|
|
App App `json:"app"`
|
|
Token string `json:"token"`
|
|
Note string `josn:"note"`
|
|
NoteUrl string `josn:"note_url"`
|
|
}
|
|
|
|
func NewGitHub() *GitHub {
|
|
config, _ := LoadConfig(ConfigFile)
|
|
|
|
var user, auth string
|
|
if config != nil {
|
|
user = config.User
|
|
auth = config.Token
|
|
}
|
|
|
|
if len(user) == 0 {
|
|
owner, err := git.Owner()
|
|
if err != nil {
|
|
// prompt for user
|
|
log.Fatal(err)
|
|
}
|
|
|
|
user = owner
|
|
}
|
|
|
|
if len(auth) > 0 {
|
|
auth = "token " + auth
|
|
}
|
|
|
|
return &GitHub{&http.Client{}, user, "", auth}
|
|
}
|
|
|
|
func hashAuth(u, p string) string {
|
|
var a = fmt.Sprintf("%s:%s", u, p)
|
|
return base64.StdEncoding.EncodeToString([]byte(a))
|
|
}
|
|
|
|
type GitHub struct {
|
|
httpClient *http.Client
|
|
User string
|
|
Password string
|
|
Authorization string
|
|
}
|
|
|
|
func (gh *GitHub) performBasicAuth() error {
|
|
msg := fmt.Sprintf("%s password for %s (never stored): ", GitHubHost, gh.User)
|
|
fmt.Print(msg)
|
|
|
|
pass := gopass.GetPasswd()
|
|
if len(pass) == 0 {
|
|
return errors.New("Password cannot be empty.")
|
|
}
|
|
gh.Password = string(pass)
|
|
|
|
return gh.obtainOAuthTokenWithBasicAuth()
|
|
}
|
|
|
|
func (gh *GitHub) obtainOAuthTokenWithBasicAuth() error {
|
|
gh.Authorization = fmt.Sprintf("Basic %s", hashAuth(gh.User, gh.Password))
|
|
response, err := gh.httpGet("/authorizations", nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var auths []Authorization
|
|
err = unmarshalBody(response, &auths)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var token string
|
|
for _, auth := range auths {
|
|
if auth.Url == OAuthAppUrl {
|
|
token = auth.Token
|
|
}
|
|
}
|
|
|
|
if len(token) == 0 {
|
|
authParam := AuthorizationParams{}
|
|
authParam.Scopes = append(authParam.Scopes, "repo")
|
|
authParam.Note = "gh"
|
|
authParam.NoteUrl = OAuthAppUrl
|
|
|
|
auth, err := gh.CreateAuthorization(authParam)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
token = auth.Token
|
|
}
|
|
|
|
SaveConfig(ConfigFile, Config{gh.User, token})
|
|
|
|
gh.Authorization = "token " + token
|
|
|
|
return nil
|
|
}
|
|
|
|
func (gh *GitHub) performRequest(request *http.Request) (*http.Response, error) {
|
|
if len(gh.Authorization) == 0 {
|
|
err := gh.performBasicAuth()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
request.Header.Set("Authorization", gh.Authorization)
|
|
|
|
response, err := gh.httpClient.Do(request)
|
|
if err != nil {
|
|
return response, err
|
|
}
|
|
|
|
if response.StatusCode >= 200 && response.StatusCode < 400 {
|
|
return response, err
|
|
}
|
|
|
|
err = handleGitHubErrors(response)
|
|
|
|
return response, err
|
|
}
|
|
|
|
func handleGitHubErrors(response *http.Response) error {
|
|
body, err := ioutil.ReadAll(response.Body)
|
|
if err == nil {
|
|
var githubErrors GitHubErrors
|
|
err = json.Unmarshal(body, &githubErrors)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
errorMessages := make([]string, len(githubErrors.Errors))
|
|
for _, e := range githubErrors.Errors {
|
|
switch e.Code {
|
|
case "custom":
|
|
errorMessages = append(errorMessages, e.Message)
|
|
case "missing_field":
|
|
errorMessages = append(errorMessages, "Missing field: "+e.Field)
|
|
case "invalid":
|
|
errorMessages = append(errorMessages, "Invalid value for "+e.Field+": "+e.Value)
|
|
case "unauthorized":
|
|
errorMessages = append(errorMessages, "Not allow to change field "+e.Field)
|
|
}
|
|
}
|
|
|
|
var text string
|
|
for _, m := range errorMessages {
|
|
if len(m) > 0 {
|
|
text = text + m + "\n"
|
|
}
|
|
}
|
|
|
|
if len(text) == 0 {
|
|
text = githubErrors.Message
|
|
}
|
|
|
|
err = errors.New(text)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func unmarshalBody(response *http.Response, v interface{}) error {
|
|
js, err := ioutil.ReadAll(response.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = json.Unmarshal(js, v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (gh *GitHub) httpGet(uri string, extraHeaders map[string]string) (*http.Response, error) {
|
|
url := fmt.Sprintf("%s%s", GitHubUrl, uri)
|
|
request, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if extraHeaders != nil {
|
|
for h, v := range extraHeaders {
|
|
request.Header.Set(h, v)
|
|
}
|
|
}
|
|
|
|
return gh.performRequest(request)
|
|
}
|
|
|
|
func (gh *GitHub) httpPost(uri string, extraHeaders map[string]string, content *bytes.Buffer) (*http.Response, error) {
|
|
url := fmt.Sprintf("%s%s", GitHubUrl, uri)
|
|
request, err := http.NewRequest("POST", url, content)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if extraHeaders != nil {
|
|
for h, v := range extraHeaders {
|
|
request.Header.Set(h, v)
|
|
}
|
|
}
|
|
|
|
request.Header.Set("Content-Type", "application/json; charset=utf-8")
|
|
|
|
return gh.performRequest(request)
|
|
}
|
|
|
|
type PullRequestParams struct {
|
|
Title string `json:"title"`
|
|
Body string `json:"body"`
|
|
Base string `json:"base"`
|
|
Head string `json:"head"`
|
|
}
|
|
|
|
type AuthorizationParams struct {
|
|
Scopes []string `json:"scopes"`
|
|
Note string `json:"note"`
|
|
NoteUrl string `json:"note_url"`
|
|
ClientId string `json:"client_id"`
|
|
ClientSecret string `json:"client_secret"`
|
|
}
|
|
|
|
func (gh *GitHub) CreateAuthorization(authParam AuthorizationParams) (*Authorization, error) {
|
|
b, err := json.Marshal(authParam)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
buffer := bytes.NewBuffer(b)
|
|
response, err := gh.httpPost("/authorizations", nil, buffer)
|
|
|
|
var auth Authorization
|
|
err = unmarshalBody(response, &auth)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &auth, nil
|
|
}
|
|
|
|
type PullRequestResponse struct {
|
|
Url string `json:"url"`
|
|
HtmlUrl string `json:"html_url"`
|
|
DiffUrl string `json:"diff_url"`
|
|
PatchUrl string `json:"patch_url"`
|
|
IssueUrl string `json:"issue_url"`
|
|
}
|
|
|
|
func (gh *GitHub) CreatePullRequest(owner, repo string, params PullRequestParams) (*PullRequestResponse, error) {
|
|
b, err := json.Marshal(params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
buffer := bytes.NewBuffer(b)
|
|
url := fmt.Sprintf("/repos/%s/%s/pulls", owner, repo)
|
|
response, err := gh.httpPost(url, nil, buffer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var pullRequestResponse PullRequestResponse
|
|
err = unmarshalBody(response, &pullRequestResponse)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &pullRequestResponse, nil
|
|
}
|