woodpecker/remote/gitlab/gitlab.go

516 lines
13 KiB
Go
Raw Normal View History

2015-07-25 08:49:39 +00:00
package gitlab
import (
"crypto/tls"
"fmt"
"io/ioutil"
"net/http"
2015-07-26 23:52:18 +00:00
"net/url"
2015-07-25 08:49:39 +00:00
"strconv"
"strings"
2015-09-30 01:21:17 +00:00
"github.com/drone/drone/model"
"github.com/drone/drone/shared/envconfig"
"github.com/drone/drone/shared/httputil"
"github.com/drone/drone/shared/oauth2"
"github.com/drone/drone/shared/token"
2015-12-12 12:20:01 +00:00
"github.com/drone/drone/remote/gitlab/client"
2015-07-25 08:49:39 +00:00
)
const (
2015-07-26 12:32:49 +00:00
DefaultScope = "api"
2015-07-25 08:49:39 +00:00
)
type Gitlab struct {
URL string
Client string
Secret string
AllowedOrgs []string
CloneMode string
2015-07-25 08:49:39 +00:00
Open bool
PrivateMode bool
SkipVerify bool
Search bool
2015-07-25 08:49:39 +00:00
}
2015-09-30 01:21:17 +00:00
func Load(env envconfig.Env) *Gitlab {
config := env.String("REMOTE_CONFIG", "")
2015-07-25 08:49:39 +00:00
2015-08-06 22:53:39 +00:00
url_, err := url.Parse(config)
2015-07-25 08:49:39 +00:00
if err != nil {
2015-09-30 01:21:17 +00:00
panic(err)
2015-07-25 08:49:39 +00:00
}
2015-08-06 22:53:39 +00:00
params := url_.Query()
url_.RawQuery = ""
2015-07-25 08:49:39 +00:00
2015-08-06 22:53:39 +00:00
gitlab := Gitlab{}
gitlab.URL = url_.String()
2015-08-09 03:51:12 +00:00
gitlab.Client = params.Get("client_id")
gitlab.Secret = params.Get("client_secret")
2015-08-06 22:53:39 +00:00
gitlab.AllowedOrgs = params["orgs"]
gitlab.SkipVerify, _ = strconv.ParseBool(params.Get("skip_verify"))
gitlab.Open, _ = strconv.ParseBool(params.Get("open"))
switch params.Get("clone_mode") {
case "oauth":
gitlab.CloneMode = "oauth"
default:
gitlab.CloneMode = "token"
}
2015-08-06 22:53:39 +00:00
// this is a temp workaround
gitlab.Search, _ = strconv.ParseBool(params.Get("search"))
2015-09-30 01:21:17 +00:00
return &gitlab
2015-07-25 08:49:39 +00:00
}
2015-09-30 01:21:17 +00:00
// Login authenticates the session and returns the
// remote user details.
func (g *Gitlab) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
var config = &oauth2.Config{
ClientId: g.Client,
ClientSecret: g.Secret,
Scope: DefaultScope,
AuthURL: fmt.Sprintf("%s/oauth/authorize", g.URL),
TokenURL: fmt.Sprintf("%s/oauth/token", g.URL),
RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(req)),
}
trans_ := &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{InsecureSkipVerify: g.SkipVerify},
}
// get the OAuth code
var code = req.FormValue("code")
if len(code) == 0 {
http.Redirect(res, req, config.AuthCodeURL("drone"), http.StatusSeeOther)
return nil, false, nil
}
var trans = &oauth2.Transport{Config: config, Transport: trans_}
var token_, err = trans.Exchange(code)
2015-07-25 08:49:39 +00:00
if err != nil {
2015-09-30 01:21:17 +00:00
return nil, false, fmt.Errorf("Error exchanging token. %s", err)
}
client := NewClient(g.URL, token_.AccessToken, g.SkipVerify)
login, err := client.CurrentUser()
if err != nil {
return nil, false, err
2015-07-25 08:49:39 +00:00
}
2015-09-30 01:21:17 +00:00
user := &model.User{}
2015-07-25 08:49:39 +00:00
user.Login = login.Username
user.Email = login.Email
2015-09-30 01:21:17 +00:00
user.Token = token_.AccessToken
user.Secret = token_.RefreshToken
if strings.HasPrefix(login.AvatarUrl, "http") {
user.Avatar = login.AvatarUrl
} else {
user.Avatar = g.URL + "/" + login.AvatarUrl
}
2015-09-30 01:21:17 +00:00
return user, true, nil
2015-07-25 08:49:39 +00:00
}
2015-09-30 01:21:17 +00:00
func (g *Gitlab) Auth(token, secret string) (string, error) {
client := NewClient(g.URL, token, g.SkipVerify)
login, err := client.CurrentUser()
if err != nil {
return "", err
}
return login.Username, nil
2015-07-25 08:49:39 +00:00
}
// Repo fetches the named repository from the remote system.
2015-09-30 01:21:17 +00:00
func (g *Gitlab) Repo(u *model.User, owner, name string) (*model.Repo, error) {
client := NewClient(g.URL, u.Token, g.SkipVerify)
id, err := GetProjectId(g, client, owner, name)
if err != nil {
2015-07-28 06:38:15 +00:00
return nil, err
}
2015-07-25 08:49:39 +00:00
repo_, err := client.Project(id)
if err != nil {
return nil, err
}
2015-09-30 01:21:17 +00:00
repo := &model.Repo{}
2015-07-25 08:49:39 +00:00
repo.Owner = owner
repo.Name = name
repo.FullName = repo_.PathWithNamespace
repo.Link = repo_.Url
repo.Clone = repo_.HttpRepoUrl
repo.Branch = "master"
if repo_.DefaultBranch != "" {
repo.Branch = repo_.DefaultBranch
}
if g.PrivateMode {
2015-09-30 01:21:17 +00:00
repo.IsPrivate = true
2015-07-26 22:29:51 +00:00
} else {
2015-09-30 01:21:17 +00:00
repo.IsPrivate = !repo_.Public
2015-07-25 08:49:39 +00:00
}
2015-07-26 22:29:51 +00:00
2015-07-25 08:49:39 +00:00
return repo, err
}
2015-09-30 01:21:17 +00:00
// Repos fetches a list of repos from the remote system.
func (g *Gitlab) Repos(u *model.User) ([]*model.RepoLite, error) {
client := NewClient(g.URL, u.Token, g.SkipVerify)
var repos = []*model.RepoLite{}
all, err := client.AllProjects()
if err != nil {
return repos, err
2015-07-25 08:49:39 +00:00
}
2015-09-30 01:21:17 +00:00
for _, repo := range all {
var parts = strings.Split(repo.PathWithNamespace, "/")
var owner = parts[0]
var name = parts[1]
2016-01-31 22:55:59 +00:00
var avatar = repo.AvatarUrl
if len(avatar) != 0 && !strings.HasPrefix(avatar, "http") {
avatar = fmt.Sprintf("%s/%s", g.URL, avatar)
}
2015-09-30 01:21:17 +00:00
repos = append(repos, &model.RepoLite{
Owner: owner,
Name: name,
FullName: repo.PathWithNamespace,
2016-01-31 22:55:59 +00:00
Avatar: avatar,
2015-09-30 01:21:17 +00:00
})
}
2016-01-31 22:55:59 +00:00
2015-09-30 01:21:17 +00:00
return repos, err
}
// Perm fetches the named repository from the remote system.
func (g *Gitlab) Perm(u *model.User, owner, name string) (*model.Perm, error) {
client := NewClient(g.URL, u.Token, g.SkipVerify)
id, err := GetProjectId(g, client, owner, name)
if err != nil {
2015-07-28 06:38:15 +00:00
return nil, err
}
2015-07-25 08:49:39 +00:00
repo, err := client.Project(id)
if err != nil {
return nil, err
}
2016-01-07 14:35:53 +00:00
2016-01-12 15:40:13 +00:00
// repo owner is granted full access
if repo.Owner != nil && repo.Owner.Username == u.Login {
2016-01-31 22:55:59 +00:00
return &model.Perm{true, true, true}, nil
2016-01-07 14:35:53 +00:00
}
2016-01-12 15:40:13 +00:00
// check permission for current user
2015-09-30 01:21:17 +00:00
m := &model.Perm{}
2015-07-25 08:49:39 +00:00
m.Admin = IsAdmin(repo)
m.Pull = IsRead(repo)
m.Push = IsWrite(repo)
return m, nil
}
// GetScript fetches the build script (.drone.yml) from the remote
// repository and returns in string format.
2015-09-30 01:21:17 +00:00
func (g *Gitlab) Script(user *model.User, repo *model.Repo, build *model.Build) ([]byte, []byte, error) {
var client = NewClient(g.URL, user.Token, g.SkipVerify)
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
if err != nil {
2015-09-07 19:13:27 +00:00
return nil, nil, err
2015-07-28 06:38:15 +00:00
}
2015-10-29 23:10:26 +00:00
out1, err := client.RepoRawFile(id, build.Commit, ".drone.yml")
if err != nil {
return nil, nil, err
}
out2, err := client.RepoRawFile(id, build.Commit, ".drone.sec")
if err != nil {
return out1, nil, nil
2015-10-29 23:10:26 +00:00
}
return out1, out2, err
2015-07-25 08:49:39 +00:00
}
2015-07-25 21:06:06 +00:00
// NOTE Currently gitlab doesn't support status for commits and events,
// also if we want get MR status in gitlab we need implement a special plugin for gitlab,
// gitlab uses API to fetch build status on client side. But for now we skip this.
2015-09-30 01:21:17 +00:00
func (g *Gitlab) Status(u *model.User, repo *model.Repo, b *model.Build, link string) error {
2015-12-12 17:09:59 +00:00
client := NewClient(g.URL, u.Token, g.SkipVerify)
status := getStatus(b.Status)
desc := getDesc(b.Status)
client.SetStatus(
ns(repo.Owner, repo.Name),
b.Commit,
status,
desc,
strings.Replace(b.Ref, "refs/heads/", "", -1),
link,
)
// Gitlab statuses it's a new feature, just ignore error
// if gitlab version not support this
2015-07-25 08:49:39 +00:00
return nil
}
// Netrc returns a .netrc file that can be used to clone
// private repositories from a remote system.
2015-09-30 01:21:17 +00:00
func (g *Gitlab) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
url_, err := url.Parse(g.URL)
2015-07-26 23:52:18 +00:00
if err != nil {
return nil, err
}
2015-09-30 01:21:17 +00:00
netrc := &model.Netrc{}
netrc.Machine = url_.Host
switch g.CloneMode {
case "oauth":
netrc.Login = "oauth2"
netrc.Password = u.Token
case "token":
t := token.New(token.HookToken, r.FullName)
netrc.Login = "drone-ci-token"
netrc.Password, err = t.Sign(r.Hash)
}
return netrc, err
2015-07-25 08:49:39 +00:00
}
// Activate activates a repository by adding a Post-commit hook and
// a Public Deploy key, if applicable.
2015-09-30 01:21:17 +00:00
func (g *Gitlab) Activate(user *model.User, repo *model.Repo, k *model.Key, link string) error {
var client = NewClient(g.URL, user.Token, g.SkipVerify)
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
if err != nil {
2015-07-28 06:38:15 +00:00
return err
}
2015-08-22 03:32:22 +00:00
uri, err := url.Parse(link)
2015-07-25 08:49:39 +00:00
if err != nil {
return err
}
2015-08-22 03:32:22 +00:00
droneUrl := fmt.Sprintf("%s://%s", uri.Scheme, uri.Host)
droneToken := uri.Query().Get("access_token")
ssl_verify := strconv.FormatBool(!g.SkipVerify)
2015-07-25 08:49:39 +00:00
2015-08-29 22:18:45 +00:00
return client.AddDroneService(id, map[string]string{
"token": droneToken,
"drone_url": droneUrl,
"enable_ssl_verification": ssl_verify,
})
2015-07-25 08:49:39 +00:00
}
// Deactivate removes a repository by removing all the post-commit hooks
// which are equal to link and removing the SSH deploy key.
2015-09-30 01:21:17 +00:00
func (g *Gitlab) Deactivate(user *model.User, repo *model.Repo, link string) error {
var client = NewClient(g.URL, user.Token, g.SkipVerify)
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
if err != nil {
2015-07-28 06:38:15 +00:00
return err
}
2015-07-25 08:49:39 +00:00
2015-08-22 03:32:22 +00:00
return client.DeleteDroneService(id)
2015-07-25 08:49:39 +00:00
}
// ParseHook parses the post-commit hook from the Request body
// and returns the required data in a standard format.
2015-09-30 01:21:17 +00:00
func (g *Gitlab) Hook(req *http.Request) (*model.Repo, *model.Build, error) {
2015-07-25 23:22:16 +00:00
defer req.Body.Close()
2015-07-25 08:49:39 +00:00
var payload, _ = ioutil.ReadAll(req.Body)
2015-12-12 12:20:01 +00:00
var parsed, err = client.ParseHook(payload)
2015-07-25 23:22:16 +00:00
if err != nil {
2015-09-30 01:21:17 +00:00
return nil, nil, err
2015-07-25 23:22:16 +00:00
}
2015-08-22 03:32:22 +00:00
switch parsed.ObjectKind {
case "merge_request":
return mergeRequest(parsed, req)
2015-08-22 19:55:08 +00:00
case "tag_push", "push":
2015-08-22 03:32:22 +00:00
return push(parsed, req)
default:
2015-09-30 01:21:17 +00:00
return nil, nil, nil
}
2015-08-22 03:32:22 +00:00
}
2015-12-12 12:20:01 +00:00
func mergeRequest(parsed *client.HookPayload, req *http.Request) (*model.Repo, *model.Build, error) {
2015-09-30 01:21:17 +00:00
repo := &model.Repo{}
repo.Owner = req.FormValue("owner")
repo.Name = req.FormValue("name")
repo.FullName = fmt.Sprintf("%s/%s", repo.Owner, repo.Name)
repo.Link = parsed.ObjectAttributes.Target.WebUrl
repo.Clone = parsed.ObjectAttributes.Target.HttpUrl
repo.Branch = "master"
build := &model.Build{}
build.Event = "pull_request"
build.Message = parsed.ObjectAttributes.LastCommit.Message
build.Commit = parsed.ObjectAttributes.LastCommit.Id
//build.Remote = parsed.ObjectAttributes.Source.HttpUrl
2015-08-22 03:32:22 +00:00
2015-08-30 00:34:05 +00:00
if parsed.ObjectAttributes.SourceProjectId == parsed.ObjectAttributes.TargetProjectId {
2015-09-30 01:21:17 +00:00
build.Ref = fmt.Sprintf("refs/heads/%s", parsed.ObjectAttributes.SourceBranch)
2015-08-22 03:32:22 +00:00
} else {
2015-09-30 01:21:17 +00:00
build.Ref = fmt.Sprintf("refs/merge-requests/%d/head", parsed.ObjectAttributes.IId)
2015-07-25 08:49:39 +00:00
}
2015-09-30 01:21:17 +00:00
build.Branch = parsed.ObjectAttributes.SourceBranch
// build.Timestamp = parsed.ObjectAttributes.LastCommit.Timestamp
2015-08-30 00:34:05 +00:00
2015-09-30 01:21:17 +00:00
build.Author = parsed.ObjectAttributes.LastCommit.Author.Name
build.Email = parsed.ObjectAttributes.LastCommit.Author.Email
build.Title = parsed.ObjectAttributes.Title
build.Link = parsed.ObjectAttributes.Url
2015-08-22 03:32:22 +00:00
2015-09-30 01:21:17 +00:00
return repo, build, nil
2015-08-22 03:32:22 +00:00
}
2015-12-12 12:20:01 +00:00
func push(parsed *client.HookPayload, req *http.Request) (*model.Repo, *model.Build, error) {
2015-07-26 12:32:49 +00:00
var cloneUrl = parsed.Repository.GitHttpUrl
2015-09-30 01:21:17 +00:00
repo := &model.Repo{}
repo.Owner = req.FormValue("owner")
repo.Name = req.FormValue("name")
repo.FullName = fmt.Sprintf("%s/%s", repo.Owner, repo.Name)
repo.Link = parsed.Repository.URL
repo.Clone = cloneUrl
repo.Branch = "master"
2015-07-26 12:32:49 +00:00
switch parsed.Repository.VisibilityLevel {
case 0:
2015-09-30 01:21:17 +00:00
repo.IsPrivate = true
2015-07-26 12:32:49 +00:00
case 10:
2015-09-30 01:21:17 +00:00
repo.IsPrivate = true
2015-07-26 12:32:49 +00:00
case 20:
2015-09-30 01:21:17 +00:00
repo.IsPrivate = false
2015-07-26 12:32:49 +00:00
}
2015-09-30 01:21:17 +00:00
repo.FullName = fmt.Sprintf("%s/%s", req.FormValue("owner"), req.FormValue("name"))
2015-07-26 12:32:49 +00:00
2015-09-30 01:21:17 +00:00
build := &model.Build{}
build.Event = model.EventPush
build.Commit = parsed.After
build.Branch = parsed.Branch()
build.Ref = parsed.Ref
// hook.Commit.Remote = cloneUrl
2015-07-25 23:22:16 +00:00
var head = parsed.Head()
2015-09-30 01:21:17 +00:00
build.Message = head.Message
// build.Timestamp = head.Timestamp
2015-07-25 23:22:16 +00:00
// extracts the commit author (ideally email)
// from the post-commit hook
switch {
case head.Author != nil:
2015-09-30 01:21:17 +00:00
build.Email = head.Author.Email
build.Author = parsed.UserName
2015-07-25 23:22:16 +00:00
case head.Author == nil:
2015-09-30 01:21:17 +00:00
build.Author = parsed.UserName
2015-07-25 08:49:39 +00:00
}
2015-11-02 03:31:42 +00:00
if strings.HasPrefix(build.Ref, "refs/tags/") {
build.Event = model.EventTag
}
2015-09-30 01:21:17 +00:00
return repo, build, nil
2015-07-25 08:49:39 +00:00
}
// ¯\_(ツ)_/¯
func (g *Gitlab) Oauth2Transport(r *http.Request) *oauth2.Transport {
return &oauth2.Transport{
Config: &oauth2.Config{
ClientId: g.Client,
ClientSecret: g.Secret,
Scope: DefaultScope,
AuthURL: fmt.Sprintf("%s/oauth/authorize", g.URL),
2015-07-26 12:32:49 +00:00
TokenURL: fmt.Sprintf("%s/oauth/token", g.URL),
2015-07-25 08:49:39 +00:00
RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(r)),
//settings.Server.Scheme, settings.Server.Hostname),
},
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{InsecureSkipVerify: g.SkipVerify},
},
}
}
// Accessor method, to allowed remote organizations field.
func (g *Gitlab) GetOrgs() []string {
return g.AllowedOrgs
2015-07-25 08:49:39 +00:00
}
// Accessor method, to open field.
func (g *Gitlab) GetOpen() bool {
return g.Open
2015-07-25 08:49:39 +00:00
}
// return default scope for GitHub
func (g *Gitlab) Scope() string {
2015-07-25 08:49:39 +00:00
return DefaultScope
}
func (g *Gitlab) String() string {
return "gitlab"
}
2015-12-12 17:09:59 +00:00
const (
StatusPending = "pending"
StatusRunning = "running"
StatusSuccess = "success"
StatusFailure = "failed"
StatusCanceled = "canceled"
)
const (
DescPending = "this build is pending"
DescRunning = "this buils is running"
DescSuccess = "the build was successful"
DescFailure = "the build failed"
DescCanceled = "the build canceled"
)
// getStatus is a helper functin that converts a Drone
// status to a GitHub status.
func getStatus(status string) string {
switch status {
case model.StatusPending:
return StatusPending
case model.StatusRunning:
return StatusRunning
case model.StatusSuccess:
return StatusSuccess
case model.StatusFailure, model.StatusError:
return StatusFailure
case model.StatusKilled:
return StatusCanceled
default:
return StatusFailure
}
}
// getDesc is a helper function that generates a description
// message for the build based on the status.
func getDesc(status string) string {
switch status {
case model.StatusPending:
return DescPending
case model.StatusRunning:
return DescRunning
case model.StatusSuccess:
return DescSuccess
case model.StatusFailure, model.StatusError:
return DescFailure
case model.StatusKilled:
return DescCanceled
default:
return DescFailure
}
}