2015-07-25 08:49:39 +00:00
|
|
|
package gitlab
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/tls"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/Bugagazavr/go-gitlab-client"
|
|
|
|
"github.com/drone/drone/Godeps/_workspace/src/github.com/hashicorp/golang-lru"
|
|
|
|
"github.com/drone/drone/pkg/config"
|
|
|
|
"github.com/drone/drone/pkg/oauth2"
|
|
|
|
"github.com/drone/drone/pkg/remote"
|
|
|
|
common "github.com/drone/drone/pkg/types"
|
|
|
|
"github.com/drone/drone/pkg/utils/httputil"
|
|
|
|
)
|
|
|
|
|
|
|
|
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
|
|
|
|
Open bool
|
|
|
|
PrivateMode bool
|
|
|
|
SkipVerify bool
|
|
|
|
|
|
|
|
cache *lru.Cache
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
remote.Register("gitlab", NewDriver)
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewDriver(conf *config.Config) (remote.Remote, error) {
|
|
|
|
var gitlab = Gitlab{
|
2015-07-26 12:32:49 +00:00
|
|
|
URL: conf.Gitlab.Host,
|
2015-07-25 08:49:39 +00:00
|
|
|
Client: conf.Gitlab.Client,
|
|
|
|
Secret: conf.Gitlab.Secret,
|
|
|
|
AllowedOrgs: conf.Gitlab.Orgs,
|
|
|
|
Open: conf.Gitlab.Open,
|
|
|
|
SkipVerify: conf.Gitlab.SkipVerify,
|
|
|
|
}
|
|
|
|
var err error
|
|
|
|
gitlab.cache, err = lru.New(1028)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// the URL must NOT have a trailing slash
|
|
|
|
if strings.HasSuffix(gitlab.URL, "/") {
|
|
|
|
gitlab.URL = gitlab.URL[:len(gitlab.URL)-1]
|
|
|
|
}
|
|
|
|
return &gitlab, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Gitlab) Login(token, secret string) (*common.User, error) {
|
|
|
|
client := NewClient(r.URL, token, r.SkipVerify)
|
|
|
|
var login, err = client.CurrentUser()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
user := common.User{}
|
|
|
|
user.Login = login.Username
|
|
|
|
user.Email = login.Email
|
|
|
|
user.Token = token
|
|
|
|
user.Secret = secret
|
|
|
|
return &user, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Orgs fetches the organizations for the given user.
|
|
|
|
func (r *Gitlab) Orgs(u *common.User) ([]string, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Repo fetches the named repository from the remote system.
|
|
|
|
func (r *Gitlab) Repo(u *common.User, owner, name string) (*common.Repo, error) {
|
|
|
|
client := NewClient(r.URL, u.Token, r.SkipVerify)
|
|
|
|
id := ns(owner, name)
|
|
|
|
repo_, err := client.Project(id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
repo := &common.Repo{}
|
|
|
|
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 r.PrivateMode {
|
|
|
|
repo.Private = true
|
|
|
|
}
|
|
|
|
return repo, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Perm fetches the named repository from the remote system.
|
|
|
|
func (r *Gitlab) Perm(u *common.User, owner, name string) (*common.Perm, error) {
|
|
|
|
key := fmt.Sprintf("%s/%s/%s", u.Login, owner, name)
|
|
|
|
val, ok := r.cache.Get(key)
|
|
|
|
if ok {
|
|
|
|
return val.(*common.Perm), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
client := NewClient(r.URL, u.Token, r.SkipVerify)
|
|
|
|
id := ns(owner, name)
|
|
|
|
repo, err := client.Project(id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
m := &common.Perm{}
|
|
|
|
m.Admin = IsAdmin(repo)
|
|
|
|
m.Pull = IsRead(repo)
|
|
|
|
m.Push = IsWrite(repo)
|
|
|
|
r.cache.Add(key, m)
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetScript fetches the build script (.drone.yml) from the remote
|
|
|
|
// repository and returns in string format.
|
|
|
|
func (r *Gitlab) Script(user *common.User, repo *common.Repo, build *common.Build) ([]byte, error) {
|
|
|
|
var client = NewClient(r.URL, user.Token, r.SkipVerify)
|
|
|
|
var path = ns(repo.Owner, repo.Name)
|
|
|
|
return client.RepoRawFile(path, build.Commit.Sha, ".drone.yml")
|
|
|
|
}
|
|
|
|
|
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-07-25 08:49:39 +00:00
|
|
|
func (r *Gitlab) Status(u *common.User, repo *common.Repo, b *common.Build) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Netrc returns a .netrc file that can be used to clone
|
|
|
|
// private repositories from a remote system.
|
2015-07-25 21:06:06 +00:00
|
|
|
// NOTE gitlab does not support this, so now we skip this.
|
2015-07-25 08:49:39 +00:00
|
|
|
func (r *Gitlab) Netrc(u *common.User) (*common.Netrc, error) {
|
2015-07-25 21:06:06 +00:00
|
|
|
return nil, nil
|
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-07-26 12:32:49 +00:00
|
|
|
func (r *Gitlab) Activate(user *common.User, repo *common.Repo, k *common.Keypair, link string) error {
|
2015-07-25 08:49:39 +00:00
|
|
|
var client = NewClient(r.URL, user.Token, r.SkipVerify)
|
|
|
|
var path = ns(repo.Owner, repo.Name)
|
|
|
|
var title, err = GetKeyTitle(link)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the repository is private we'll need
|
|
|
|
// to upload a github key to the repository
|
|
|
|
if repo.Private {
|
2015-07-26 12:32:49 +00:00
|
|
|
if err := client.AddProjectDeployKey(path, title, k.Public); err != nil {
|
2015-07-25 08:49:39 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// append the repo owner / name to the hook url since gitlab
|
|
|
|
// doesn't send this detail in the post-commit hook
|
2015-07-26 12:32:49 +00:00
|
|
|
link += "&owner=" + repo.Owner + "&name=" + repo.Name
|
2015-07-25 08:49:39 +00:00
|
|
|
|
|
|
|
// add the hook
|
|
|
|
return client.AddProjectHook(path, link, true, false, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Deactivate removes a repository by removing all the post-commit hooks
|
|
|
|
// which are equal to link and removing the SSH deploy key.
|
|
|
|
func (r *Gitlab) Deactivate(user *common.User, repo *common.Repo, link string) error {
|
|
|
|
var client = NewClient(r.URL, user.Token, r.SkipVerify)
|
|
|
|
var path = ns(repo.Owner, repo.Name)
|
|
|
|
|
|
|
|
keys, err := client.ProjectDeployKeys(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
var pubkey = strings.TrimSpace(repo.Keys.Public)
|
|
|
|
for _, k := range keys {
|
|
|
|
if pubkey == strings.TrimSpace(k.Key) {
|
|
|
|
if err := client.RemoveProjectDeployKey(path, strconv.Itoa(k.Id)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
hooks, err := client.ProjectHooks(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-07-26 12:32:49 +00:00
|
|
|
link += "&owner=" + repo.Owner + "&name=" + repo.Name
|
2015-07-25 08:49:39 +00:00
|
|
|
for _, h := range hooks {
|
|
|
|
if link == h.Url {
|
|
|
|
if err := client.RemoveProjectHook(path, strconv.Itoa(h.Id)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseHook parses the post-commit hook from the Request body
|
|
|
|
// and returns the required data in a standard format.
|
|
|
|
func (r *Gitlab) Hook(req *http.Request) (*common.Hook, 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-07-25 23:22:16 +00:00
|
|
|
var parsed, err = gogitlab.ParseHook(payload)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(parsed.After) == 0 || parsed.TotalCommitsCount == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
2015-07-25 08:49:39 +00:00
|
|
|
|
2015-07-25 23:02:33 +00:00
|
|
|
if parsed.ObjectKind == "merge_request" {
|
|
|
|
// NOTE: in gitlab 8.0, gitlab will get same MR models as github
|
|
|
|
// https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/981/diffs
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2015-07-25 23:22:16 +00:00
|
|
|
if len(parsed.After) == 0 {
|
2015-07-25 08:49:39 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2015-07-26 12:32:49 +00:00
|
|
|
var cloneUrl = parsed.Repository.GitHttpUrl
|
|
|
|
|
|
|
|
if parsed.Repository.VisibilityLevel < 20 {
|
|
|
|
cloneUrl = parsed.Repository.GitSshUrl
|
|
|
|
}
|
|
|
|
|
2015-07-25 21:06:06 +00:00
|
|
|
var hook = new(common.Hook)
|
2015-07-26 12:32:49 +00:00
|
|
|
hook.Repo = &common.Repo{}
|
2015-07-25 23:22:16 +00:00
|
|
|
hook.Repo.Owner = req.FormValue("owner")
|
|
|
|
hook.Repo.Name = req.FormValue("name")
|
2015-07-26 12:32:49 +00:00
|
|
|
hook.Repo.Link = parsed.Repository.URL
|
|
|
|
hook.Repo.Clone = cloneUrl
|
|
|
|
hook.Repo.Branch = "master"
|
|
|
|
|
|
|
|
switch parsed.Repository.VisibilityLevel {
|
|
|
|
case 0:
|
|
|
|
hook.Repo.Private = true
|
|
|
|
case 10:
|
|
|
|
hook.Repo.Private = true
|
|
|
|
case 20:
|
|
|
|
hook.Repo.Private = false
|
|
|
|
}
|
|
|
|
|
|
|
|
hook.Repo.FullName = fmt.Sprintf("%s/%s", req.FormValue("owner"), req.FormValue("name"))
|
|
|
|
|
|
|
|
hook.Commit = &common.Commit{}
|
2015-07-25 23:22:16 +00:00
|
|
|
hook.Commit.Sha = parsed.After
|
|
|
|
hook.Commit.Branch = parsed.Branch()
|
2015-07-26 12:32:49 +00:00
|
|
|
hook.Commit.Ref = parsed.Ref
|
|
|
|
hook.Commit.Remote = cloneUrl
|
2015-07-25 23:22:16 +00:00
|
|
|
|
|
|
|
var head = parsed.Head()
|
|
|
|
hook.Commit.Message = head.Message
|
|
|
|
hook.Commit.Timestamp = head.Timestamp
|
2015-07-26 12:32:49 +00:00
|
|
|
hook.Commit.Author = &common.Author{}
|
2015-07-25 23:22:16 +00:00
|
|
|
|
|
|
|
// extracts the commit author (ideally email)
|
|
|
|
// from the post-commit hook
|
|
|
|
switch {
|
|
|
|
case head.Author != nil:
|
|
|
|
hook.Commit.Author.Email = head.Author.Email
|
2015-07-26 12:32:49 +00:00
|
|
|
hook.Commit.Author.Login = parsed.UserName
|
2015-07-25 23:22:16 +00:00
|
|
|
case head.Author == nil:
|
|
|
|
hook.Commit.Author.Login = parsed.UserName
|
2015-07-25 08:49:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return hook, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ¯\_(ツ)_/¯
|
|
|
|
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 (r *Gitlab) GetOrgs() []string {
|
|
|
|
return r.AllowedOrgs
|
|
|
|
}
|
|
|
|
|
|
|
|
// Accessor method, to open field.
|
|
|
|
func (r *Gitlab) GetOpen() bool {
|
|
|
|
return r.Open
|
|
|
|
}
|
|
|
|
|
|
|
|
// return default scope for GitHub
|
|
|
|
func (r *Gitlab) Scope() string {
|
|
|
|
return DefaultScope
|
|
|
|
}
|