woodpecker/plugin/remote/github/github.go

202 lines
5.2 KiB
Go

package github
import (
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/drone/drone/plugin/remote"
"github.com/drone/drone/shared/httputil"
"github.com/drone/go-github/github"
"github.com/drone/go-github/oauth2"
)
var (
scope = "repo,repo:status,user:email"
state = "FqB4EbagQ2o"
)
type Github struct {
URL string `json:"url"` // https://github.com
API string `json:"api"` // https://api.github.com
Client string `json:"client"`
Secret string `json:"secret"`
Enabled bool `json:"enabled"`
}
// GetName returns the name of this remote system.
func (g *Github) GetName() string {
switch g.URL {
case "https://github.com":
return "github.com"
default:
return "enterprise.github.com"
}
}
// GetHost returns the url.Host of this remote system.
func (g *Github) GetHost() (host string) {
u, err := url.Parse(g.URL)
if err != nil {
return
}
return u.Host
}
// GetHook parses the post-commit hook from the Request body
// and returns the required data in a standard format.
func (g *Github) GetHook(r *http.Request) (*remote.Hook, error) {
// handle github ping
if r.Header.Get("X-Github-Event") == "ping" {
return nil, nil
}
// handle github pull request hook differently
if r.Header.Get("X-Github-Event") == "pull_request" {
return g.GetPullRequestHook(r)
}
// get the payload of the message
payload := r.FormValue("payload")
// parse the github Hook payload
data, err := github.ParseHook([]byte(payload))
if err != nil {
return nil, nil
}
// make sure this is being triggered because of a commit
// and not something like a tag deletion or whatever
if data.IsTag() || data.IsGithubPages() ||
data.IsHead() == false || data.IsDeleted() {
return nil, nil
}
hook := remote.Hook{}
hook.Repo = data.Repo.Name
hook.Owner = data.Repo.Owner.Login
hook.Sha = data.Head.Id
hook.Branch = data.Branch()
if len(hook.Owner) == 0 {
hook.Owner = data.Repo.Owner.Name
}
// extract the author and message from the commit
// this is kind of experimental, since I don't know
// what I'm doing here.
if data.Head != nil && data.Head.Author != nil {
hook.Message = data.Head.Message
hook.Timestamp = data.Head.Timestamp
hook.Author = data.Head.Author.Email
} else if data.Commits != nil && len(data.Commits) > 0 && data.Commits[0].Author != nil {
hook.Message = data.Commits[0].Message
hook.Timestamp = data.Commits[0].Timestamp
hook.Author = data.Commits[0].Author.Email
}
return &hook, nil
}
func (g *Github) GetPullRequestHook(r *http.Request) (*remote.Hook, error) {
payload := r.FormValue("payload")
// parse the payload to retrieve the pull-request
// hook meta-data.
data, err := github.ParsePullRequestHook([]byte(payload))
if err != nil {
return nil, err
}
// ignore these
if data.Action != "opened" && data.Action != "synchronize" {
return nil, nil
}
// TODO we should also store the pull request branch (ie from x to y)
// we can find it here: data.PullRequest.Head.Ref
hook := remote.Hook{
Owner: data.Repo.Owner.Login,
Repo: data.Repo.Name,
Sha: data.PullRequest.Head.Sha,
Branch: data.PullRequest.Base.Ref,
Author: data.PullRequest.User.Login,
Gravatar: data.PullRequest.User.GravatarId,
Timestamp: time.Now().UTC().String(),
Message: data.PullRequest.Title,
PullRequest: strconv.Itoa(data.Number),
}
if len(hook.Owner) == 0 {
hook.Owner = data.Repo.Owner.Name
}
return &hook, nil
}
// GetLogin handles authentication to third party, remote services
// and returns the required user data in a standard format.
func (g *Github) GetLogin(w http.ResponseWriter, r *http.Request) (*remote.Login, error) {
// create the oauth2 client
oauth := oauth2.Client{
RedirectURL: fmt.Sprintf("%s://%s/login/%s", httputil.GetScheme(r), httputil.GetHost(r), g.GetName()),
AccessTokenURL: fmt.Sprintf("%s/login/oauth/access_token", g.URL),
AuthorizationURL: fmt.Sprintf("%s/login/oauth/authorize", g.URL),
ClientId: g.Client,
ClientSecret: g.Secret,
}
// get the OAuth code
code := r.FormValue("code")
if len(code) == 0 {
redirect := oauth.AuthorizeRedirect(scope, state)
http.Redirect(w, r, redirect, http.StatusSeeOther)
return nil, nil
}
// exchange code for an auth token
token, err := oauth.GrantToken(code)
if err != nil {
return nil, fmt.Errorf("Error granting GitHub authorization token. %s", err)
}
// create the client
client := github.New(token.AccessToken)
client.ApiUrl = g.API
// get the user information
user, err := client.Users.Current()
if err != nil {
return nil, fmt.Errorf("Error retrieving currently authenticated GitHub user. %s", err)
}
// put the user data in the common format
login := remote.Login{
ID: user.ID,
Login: user.Login,
Access: token.AccessToken,
Name: user.Name,
}
// get the users primary email address
email, err := client.Emails.FindPrimary()
if err == nil {
login.Email = email.Email
}
return &login, nil
}
// GetClient returns a new Github remote client.
func (g *Github) GetClient(access, secret string) remote.Client {
return &Client{g, access}
}
// IsMatch returns true if the hostname matches the
// hostname of this remote client.
func (g *Github) IsMatch(hostname string) bool {
return strings.HasSuffix(hostname, g.URL)
}